CnC_Remastered_Collection/REDALERT/TEAM.CPP
PG-SteveT fc5cd5a775 Command & Conquer Remastered post-launch patch
Improvements to harvester resource finding logic.

Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer.

Increased failed pathfinding fudge factor.

Buildings accept the Guard command if they can attack.

Don't allow force capturing of ally structures.

Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads.

Fixed flag animation rendering in CTF.

Potentially fix a crash if aircraft are destroyed outside the map bounds.

Fixed legacy Obelisk line rendering.

Fix out-of-bounds crash in TD; issue was already fixed in RA.

Disable capture flag on Commandos.

Drop the flag when entering the limbo state.

Fixed end game race condition, winning team ID is always sent before individual player win/lose messages.

Fixed Chan spawn on SCB10EA.

Don't show enter cursor for enemy units on refineries and repair pads.

Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold.

Don't debug reveal legacy rendering when a player is defeated.

Fixed crash when loading saves of custom campaign maps.

Reset harvester archived target when given a direct harvest order.

Prevent NOD cargo planes from being force attacked.

Fixed unit selection on load.

Migrated queued repair pad functionality from RA to TD.

Randomly animate infantry in area guard mode.

Fixed crash accessing inactive objects.

Added some walls in SCG08EB to prevent civilians from killing themselves.

TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often.

Fixed adjacent cell out-of-bounds crash issues.

Kill player on disconnect

Fixed and improved build time calculations to be consistent between TD and RA.

Don't show health bars for cloaked Stealth Tanks in the legacy renderer.

Fix selection of individual control groups after mixed selection.

More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium.

Extra safety checks for units that have no weapons and aircraft that can't hunt.

Fix loading of multiple infantry onto an APC.

Additional safety checks for invalid coordinates.

Prevent units from being instantly repaired.

Fix map passability.

Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior).

Fixed multiplayer formation move causing units to move at light speed.

Ignore movement destination checks if a unit is part of a mission-driven team.

Fix buffer overrun crash.

Ignore mines when determining win conditions.

Fixed river passability in Blue Lakes.
2020-06-22 09:43:21 -07:00

3075 lines
125 KiB
C++

//
// Copyright 2020 Electronic Arts Inc.
//
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
/* $Header: /CounterStrike/TEAM.CPP 1 3/03/97 10:25a Joe_bostic $ */
/***********************************************************************************************
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
***********************************************************************************************
* *
* Project Name : Command & Conquer *
* *
* File Name : TEAM.CPP *
* *
* Programmer : Joe L. Bostic *
* *
* Start Date : 12/11/94 *
* *
* Last Update : August 27, 1996 [JLB] *
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* TeamClass::AI -- Process team logic. *
* TeamClass::Add -- Adds specified object to team. *
* TeamClass::Assign_Mission_Target -- Sets teams mission target and clears old target *
* TeamClass::Calc_Center -- Determines average location of team members. *
* TeamClass::Can_Add -- Determines if the specified object can be added to team. *
* TeamClass::Control -- Updates control on a member unit. *
* TeamClass::Coordinate_Attack -- Handles coordinating a team attack. *
* TeamClass::Coordinate_Conscript -- Gives orders to new recruit. *
* TeamClass::Coordinate_Do -- Handles the team performing specified mission. *
* TeamClass::Coordinate_Move -- Handles team movement coordination. *
* TeamClass::Coordinate_Regroup -- Handles team idling (regrouping). *
* TeamClass::Debug_Dump -- Displays debug information about the team. *
* TeamClass::Detach -- Removes specified target from team tracking. *
* TeamClass::Fetch_A_Leader -- Looks for a suitable leader member of the team. *
* TeamClass::Has_Entered_Map -- Determines if the entire team has entered the map. *
* TeamClass::Init -- Initializes the team objects for scenario preparation. *
* TeamClass::Is_A_Member -- Tests if a unit is a member of a team *
* TeamClass::Is_Leaving_Map -- Checks if team is in process of leaving the map *
* TeamClass::Lagging_Units -- Finds and orders any lagging units to catch up. *
* TeamClass::Recruit -- Attempts to recruit members to the team for the given index ID. *
* TeamClass::Remove -- Removes the specified object from the team. *
* TeamClass::Scan_Limit -- Force all members of the team to have limited scan range. *
* TeamClass::Suspend_Teams -- Suspends activity for low priority teams *
* TeamClass::TMision_Patrol -- Handles patrolling from one location to another. *
* TeamClass::TMission_Attack -- Perform the team attack mission command. *
* TeamClass::TMission_Follow -- Perform the "follow friendlies" team command. *
* TeamClass::TMission_Formation -- Process team formation change command. *
* TeamClass::TMission_Invulnerable -- Makes the entire team invulnerable for a period of tim*
* TeamClass::TMission_Load -- Tells the team to load onto the transport now. *
* TeamClass::TMission_Loop -- Causes the team mission processor to jump to new location. *
* TeamClass::TMission_Set_Global -- Performs a set global flag operation. *
* TeamClass::TMission_Spy -- Perform the team spy mission. *
* TeamClass::TMission_Unload -- Tells the team to unload passengers now. *
* TeamClass::TeamClass -- Constructor for the team object type. *
* TeamClass::Took_Damage -- Informs the team when the team member takes damage. *
* TeamClass::operator delete -- Deallocates a team object. *
* TeamClass::operator new -- Allocates a team object. *
* TeamClass::~TeamClass -- Team object destructor. *
* _Is_It_Breathing -- Checks to see if unit is an active team member. *
* _Is_It_Playing -- Determines if unit is active and an initiated team member. *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "function.h"
#include "mission.h"
/***********************************************************************************************
* _Is_It_Breathing -- Checks to see if unit is an active team member. *
* *
* A unit could be a team member, but not be active. Such a case would occur when a *
* reinforcement team is inside a transport. It could also occur if a unit is in the *
* process of dying. Call this routine to ensure that the specified unit is a will and *
* able participant in the team. *
* *
* INPUT: object -- Pointer to the unit/infantry/aircraft that is to be checked. *
* *
* OUTPUT: bool; Is the specified unit active and able to be given commands by the team? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/11/1996 JLB : Created. *
*=============================================================================================*/
static inline bool _Is_It_Breathing(FootClass const * object)
{
/*
** If the object is not present or appears to be dead, then it
** certainly isn't an active member of the team.
*/
if (object == NULL || !object->IsActive || object->Strength == 0) return(false);
/*
** If the object is in limbo, then it isn't an active team member either. However, if the
** scenario init flag is on, then it is probably a reinforcement issue or scenario
** creation situation. In such a case, the members are considered active because they need to
** be given special orders and treatment.
*/
if (!ScenarioInit && object->IsInLimbo) return(false);
/*
** Nothing eliminated this object from being considered an active member of the team (i.e.,
** "breathing"), then return that it is ok.
*/
return(true);
}
/***********************************************************************************************
* _Is_It_Playing -- Determines if unit is active and an initiated team member. *
* *
* Use this routine to determine if the specified unit is an active participant of the *
* team. When a unit is first recruited to the team, it must travel to the team's location *
* before it can become an active player. Call this routine to determine if the specified *
* unit can be considered an active player. *
* *
* INPUT: object -- Pointer to the object that is to be checked to see if it is an *
* active player. *
* *
* OUTPUT: bool; Is the specified unit an active, living, initiated member of the team? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/11/1996 JLB : Created. *
*=============================================================================================*/
static inline bool _Is_It_Playing(FootClass const * object)
{
/*
** If the object is not active, then it certainly can be a participating member of the
** team.
*/
if (!_Is_It_Breathing(object)) return(false);
/*
** Only members that have been "Initiated" are considered "playing" participants of the
** team. This results in the team members that are racing to regroup with the team (i.e.,
** not initiated), will continue to catch up to the team even while the initiated team members
** carry out their team specific orders.
*/
if (!object->IsInitiated && object->What_Am_I() != RTTI_AIRCRAFT) return(false);
/*
** If it reaches this point, then nothing appears to disqualify the specified object from
** being considered an active playing member of the team. In this case, return that
** information.
*/
return(true);
}
#ifdef CHEAT_KEYS
/***********************************************************************************************
* TeamClass::Debug_Dump -- Displays debug information about the team. *
* *
* This routine will display information about the team. This is useful for debugging *
* purposes. *
* *
* INPUT: mono -- Pointer to the monochrome screen that the debugging information will *
* be displayed on. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/11/1996 JLB : Created. *
*=============================================================================================*/
void TeamClass::Debug_Dump(MonoClass * mono) const
{
mono->Set_Cursor(1, 20);mono->Printf("%8.8s", Class->IniName);
mono->Set_Cursor(10, 20);mono->Printf("%3d", Total);
mono->Set_Cursor(17, 20);mono->Printf("%3d", Quantity[Class->ID]);
if (CurrentMission != -1) {
mono->Set_Cursor(1, 22);
mono->Printf("%-29s", Class->MissionList[CurrentMission].Description(CurrentMission));
}
mono->Set_Cursor(40, 20);mono->Printf("%-10s", FormationName[Formation]);
mono->Set_Cursor(22, 20);mono->Printf("%08X", Zone);
mono->Set_Cursor(31, 20);mono->Printf("%08X", Target);
mono->Fill_Attrib(53, 20, 12, 1, IsUnderStrength ? MonoClass::INVERSE : MonoClass::NORMAL);
mono->Fill_Attrib(53, 21, 12, 1, IsFullStrength ? MonoClass::INVERSE : MonoClass::NORMAL);
mono->Fill_Attrib(53, 22, 12, 1, IsHasBeen ? MonoClass::INVERSE : MonoClass::NORMAL);
mono->Fill_Attrib(66, 20, 12, 1, IsMoving ? MonoClass::INVERSE : MonoClass::NORMAL);
mono->Fill_Attrib(66, 21, 12, 1, IsForcedActive ? MonoClass::INVERSE : MonoClass::NORMAL);
mono->Fill_Attrib(66, 22, 12, 1, IsReforming ? MonoClass::INVERSE : MonoClass::NORMAL);
}
#endif
/***********************************************************************************************
* TeamClass::Init -- Initializes the team objects for scenario preparation. *
* *
* This routine clears out the team object array in preparation for starting a new *
* scenario. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 12/29/1994 JLB : Created. *
*=============================================================================================*/
void TeamClass::Init(void)
{
Teams.Free_All();
}
/***********************************************************************************************
* TeamClass::operator new -- Allocates a team object. *
* *
* This routine will allocate a team object from the team object pool. *
* *
* INPUT: size -- The size of the requested allocation. *
* *
* OUTPUT: Returns with a pointer to the freshly allocated team object. If an allocation *
* could not be made, then NULL is returned. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 09/21/1995 JLB : Created. *
*=============================================================================================*/
void * TeamClass::operator new(size_t)
{
void * ptr = Teams.Allocate();
if (ptr != NULL) {
((TeamClass *)ptr)->Set_Active();
}
return(ptr);
}
/***********************************************************************************************
* TeamClass::operator delete -- Deallocates a team object. *
* *
* This routine will return a team object to the team object pool. *
* *
* INPUT: ptr -- Pointer to the team object to deallocate. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 09/21/1995 JLB : Created. *
*=============================================================================================*/
void TeamClass::operator delete(void * ptr)
{
if (ptr != NULL) {
((TeamClass *)ptr)->IsActive = false;
}
Teams.Free((TeamClass *)ptr);
}
/***********************************************************************************************
* TeamClass::~TeamClass -- Team object destructor. *
* *
* This routine is called when a team object is destroyed. It handles updating the total *
* number count for this team object type. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 09/21/1995 JLB : Created. *
* 07/04/1996 JLB : Keeps trigger if trigger still attached to objects. *
*=============================================================================================*/
TeamClass::~TeamClass(void)
{
if (GameActive && Class.Is_Valid()) {
while (Member != NULL) {
Remove(Member);
}
Class->Number--;
/*
** When the team dies, any trigger associated with it, dies as well. This will only occur
** if there are no other objects linked to this trigger. Only player reinforcement
** members that broke off of the team earlier will have this occur.
*/
if (Trigger.Is_Valid()) {
if (Trigger->AttachCount == 0) {
delete (TriggerClass *)Trigger;
}
Trigger = NULL;
}
}
}
/***********************************************************************************************
* TeamClass::TeamClass -- Constructor for the team object type. *
* *
* This routine is called when the team object is created. *
* *
* INPUT: type -- Pointer to the team type to make this team object from. *
* *
* owner -- The owner of this team. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 09/21/1995 JLB : Created. *
*=============================================================================================*/
TeamClass::TeamClass(TeamTypeClass const * type, HouseClass * owner) :
AbstractClass(RTTI_TEAM, Teams.ID(this)),
Class((TeamTypeClass *)type),
House(owner),
IsForcedActive(false),
IsHasBeen(false),
IsFullStrength(false),
IsUnderStrength(true),
IsReforming(false),
IsLagging(false),
IsAltered(true),
JustAltered(false),
IsMoving(false),
IsNextMission(true),
IsLeaveMap(false),
Suspended(false),
Trigger(NULL),
Zone(TARGET_NONE),
ClosestMember(TARGET_NONE),
MissionTarget(TARGET_NONE),
Target(TARGET_NONE),
Total(0),
Risk(0),
Formation(FORMATION_NONE),
SuspendTimer(0),
CurrentMission(-1),
TimeOut(0),
Member(0)
{
assert(Class);
assert(Class->IsActive);
assert(Class->ClassCount > 0);
if (owner == NULL) {
House = HouseClass::As_Pointer(Class->House);
}
memset(Quantity, 0, sizeof(Quantity));
if (Class->Origin != -1) {
Zone = ::As_Target(Scen.Waypoint[Class->Origin]);
}
Class->Number++;
/*
** If there is a trigger tightly associated with this team, then
** create an instance of that trigger and attach it to the team.
*/
if (Class->Trigger.Is_Valid()) {
Trigger = new TriggerClass(Class->Trigger);
}
}
/***************************************************************************
* TeamClass::Assign_Mission_Target -- Sets mission target and clears old *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/16/1995 PWG : Created. *
*=========================================================================*/
void TeamClass::Assign_Mission_Target(TARGET new_target)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
/*
** First go through and find anyone who is currently targeting
** the old mission target and clear their Tarcom.
*/
FootClass * unit = Member;
if (MissionTarget != TARGET_NONE) {
while (unit != NULL) {
bool tar = (unit->TarCom == MissionTarget);
bool nav = (unit->NavCom == MissionTarget);
if (tar || nav) {
/*
** If the unit was doing something related to the team mission
** then we kick him into guard mode so that he is easy to change
** missions for.
*/
unit->Assign_Mission(MISSION_GUARD);
/*
** If the unit's tarcom is set to the old mission target, then
** clear it, so that it will be reset by whatever happens next.
*/
if (nav) {
unit->Assign_Destination(TARGET_NONE);
}
/*
** If the unit's navcom is set to the old mission target, then
** clear it, so that it will be reset by whatever happens next.
*/
if (tar) {
unit->Assign_Target(TARGET_NONE);
}
}
unit = unit->Member;
}
}
/*
** If there is not currently an override on the current mission target
** then assign both MissionTarget and Target to the new target. If
** there is an override, allow the team to keep fighting the override but
** make sure they pick up on the new mission when they are ready.
*/
if (Target == MissionTarget || !Target_Legal(Target)) {
MissionTarget = Target = new_target;
} else {
MissionTarget = new_target;
}
}
/***********************************************************************************************
* TeamClass::AI -- Process team logic. *
* *
* General purpose team logic is handled by this routine. It should be called once per *
* active team per game tick. This routine handles recruitment and assigning orders to *
* member units. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 12/29/1994 JLB : Created. *
* 01/06/1995 JLB : Choreographed gesture. *
*=============================================================================================*/
void TeamClass::AI(void)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
int desired = 0;
int old_under = IsUnderStrength;
int old_full = IsFullStrength;
/*
** If the team has been suspended then we need to check if it's time for
** us to reactivate the team. If not, no team logic will be processed
** for this team.
*/
if (Suspended) {
if (SuspendTimer != 0) {
return;
}
Suspended = false;
}
/*
** If this team senses that its composition has been altered, then it should
** recalculate the under strength and full strength flags.
*/
if (IsAltered) {
/*
** Figure out the total number of objects that this team type requires.
*/
for (int index = 0; index < Class->ClassCount; index++) {
desired += Class->Members[index].Quantity;
}
assert(desired != 0);
if (Total) {
IsFullStrength = (Total == desired);
if (IsFullStrength) {
IsHasBeen = true;
}
/*
** Reinforceable teams will revert (or snap out of) the under strength
** mode when the members transition the magic 1/3 strength threshold.
*/
if (Class->IsReinforcable) {
if (desired > 2) {
IsUnderStrength = (Total <= desired / 3);
} else {
IsUnderStrength = (Total < desired);
}
} else {
/*
** Teams that are not flagged as reinforceable are never considered under
** strength if the team has already started its main mission. This
** ensures that once the team has started, it won't dally to pick up
** new members.
*/
IsUnderStrength = !IsHasBeen;
}
IsAltered = JustAltered = false;
} else {
IsUnderStrength = true;
IsFullStrength = false;
Zone = TARGET_NONE;
/*
** A team that exists on the player's side is automatically destroyed
** when there are no team members left. This team was created as a
** result of reinforcement logic and no longer needs to exist when there
** are no more team members.
*/
if (IsHasBeen || Session.Type != GAME_NORMAL) {
/*
** If this team had no members (i.e., the team object wasn't terminated by some
** outside means), then pass through the logic triggers to see if one that
** depends on this team leaving the map should be sprung.
*/
if (IsLeaveMap) {
for (int index = 0; index < LogicTriggers.Count(); index++) {
TriggerClass * trig = LogicTriggers[index];
if (trig->Spring(TEVENT_LEAVES_MAP)) {
index--;
if (LogicTriggers.Count() == 0) break;
}
}
}
delete this;
return;
}
}
/*
** If the team has gone from under strength to no longer under
** strength than the team needs to reform.
*/
if (old_under != IsUnderStrength) {
IsReforming = true;
}
}
/*
** If the team is under strength, then flag it to regroup.
*/
if (IsMoving && IsUnderStrength) {
IsMoving = false;
CurrentMission = -1;
if (Total) {
Calc_Center(Zone, ClosestMember);
/*
** When a team is badly damaged and needs to regroup it should
** pick a friendly building to go and regroup at. Its first preference
** should be somewhere near repair factory. If it cannot find a repair
** factory then it should pick another structure that is friendly to
** its side.
*/
CELL dest = As_Cell(Zone);
int max = 0x7FFFFFFF;
for (int index = 0; index < Buildings.Count(); index++) {
BuildingClass * b = Buildings.Ptr(index);
if (b != NULL && !b->IsInLimbo && b->House == House && b->Class->PrimaryWeapon == NULL) {
CELL cell = Coord_Cell(b->Center_Coord());
int dist = ::Distance(b->Center_Coord(), As_Coord(Zone)) * (Map.Cell_Threat(cell, House->Class->House) + 1);
if (*b == STRUCT_REPAIR) {
dist /= 2;
}
if (dist < max) {
cell = Fetch_A_Leader()->Safety_Point(As_Cell(Zone), cell, 2, 4);
// cell = Member->Safety_Point(As_Cell(Zone), cell, 2, 4);
if (cell != -1) {
max = dist;
dest = cell;
}
}
}
}
// Should calculate a regroup location.
Target = ::As_Target(dest);
Coordinate_Move();
return;
} else {
Zone = TARGET_NONE;
}
}
/*
** Flag this team into action when it gets to full strength. Human owned teams are only
** used for reinforcement purposes -- always consider them at full strength.
*/
if (!IsMoving && (IsFullStrength || IsForcedActive)) {
IsMoving = true;
IsHasBeen = true;
IsUnderStrength = false;
/*
** Infantry can do a gesture when they start their mission. Pick
** a gesture at random.
*/
FootClass * techno = Member;
DoType doaction = Percent_Chance(50) ? DO_GESTURE1 : DO_GESTURE2;
while (techno) {
if (_Is_It_Breathing(techno) && techno->What_Am_I() == RTTI_INFANTRY) {
((InfantryClass *)techno)->Do_Action(doaction);
}
if (IsReforming || IsForcedActive) {
techno->IsInitiated = true;
}
techno = techno->Member;
}
CurrentMission = -1;
IsNextMission = true;
// IsForcedActive = false;
}
/*
** If the team is moving or if there is no center position for
** the team, then the center position must be recalculated.
*/
if (IsReforming || IsMoving || Zone == TARGET_NONE || ClosestMember == TARGET_NONE) {
Calc_Center(Zone, ClosestMember);
}
/*
** Try to recruit members if there is room to do so for this team.
** Only try to recruit members for a non player controlled team.
*/
if ((!IsMoving || (!IsFullStrength && Class->IsReinforcable)) && ((!House->IsHuman || !IsHasBeen) && Session.Type == GAME_NORMAL)) {
// if ((!IsMoving || (!IsFullStrength && Class->IsReinforcable)) && ((/*!House->IsHuman ||*/ !IsHasBeen) && Session.Type == GAME_NORMAL)) {
for (int index = 0; index < Class->ClassCount; index++) {
if (Quantity[index] < Class->Members[index].Quantity) {
Recruit(index);
}
}
}
/*
** If there are no members of the team and the team has reached
** full strength at one time, then delete the team.
*/
if (Member == NULL && IsHasBeen) {
/*
** If this team had no members (i.e., the team object wasn't terminated by some
** outside means), then pass through the logic triggers to see if one that
** depends on this team leaving the map should be sprung.
*/
if (IsLeaveMap) {
for (int index = 0; index < LogicTriggers.Count(); index++) {
TriggerClass * trig = LogicTriggers[index];
if (trig->Spring(TEVENT_LEAVES_MAP)) {
index--;
if (LogicTriggers.Count() == 0) break;
}
}
}
delete this;
return;
}
/*
** If the mission should be advanced to the next entry, then do so at
** this time. Various events may cause the mission to advance, but it
** all boils down to the following change-mission code.
*/
if (IsMoving && !IsReforming && IsNextMission) {
IsNextMission = false;
CurrentMission++;
if (CurrentMission < Class->MissionCount) {
TeamMissionClass const * mission = &Class->MissionList[CurrentMission];
TimeOut = mission->Data.Value * (TICKS_PER_MINUTE/10);
Target = TARGET_NONE;
switch (mission->Mission) {
case TMISSION_MOVECELL:
Assign_Mission_Target(::As_Target((CELL)(mission->Data.Value)));
break;
case TMISSION_MOVE:
if ((unsigned)mission->Data.Value < WAYPT_COUNT && Member != NULL) {
FootClass * leader = Fetch_A_Leader();
CELL movecell = Scen.Waypoint[mission->Data.Value];
if (!Is_Leaving_Map()) {
if (leader->Can_Enter_Cell(movecell) != MOVE_OK) {
movecell = Map.Nearby_Location(movecell, leader->Techno_Type_Class()->Speed);
}
}
Assign_Mission_Target(::As_Target(movecell));
Target = ::As_Target(movecell);
}
break;
case TMISSION_ATT_WAYPT:
case TMISSION_PATROL:
case TMISSION_SPY:
if ((unsigned)mission->Data.Value < WAYPT_COUNT) {
Assign_Mission_Target(::As_Target(Scen.Waypoint[mission->Data.Value]));
}
break;
case TMISSION_ATTACKTARCOM:
Assign_Mission_Target(mission->Data.Value);
break;
case TMISSION_UNLOAD:
default:
Assign_Mission_Target(TARGET_NONE);
break;
}
} else {
delete this;
return;
}
}
/*
** Perform mission of the team. This depends on the mission list.
*/
if (Member != NULL && IsMoving && !IsReforming && !IsUnderStrength) {
/*
** If the current Target has been dealt with but the mission target
** has not, then the current target needs to be reset to the mission
** target.
*/
if (!Target_Legal(Target)) {
Target = MissionTarget;
}
/*
** If the current mission is one that times out, then check for
** this case. If it has timed out then advance to the next
** mission in the list or disband the team.
*/
TeamMissionClass const * mission = &Class->MissionList[CurrentMission];
// FootClass * member = Member;
switch (mission->Mission) {
case TMISSION_PATROL:
TMission_Patrol();
break;
case TMISSION_FORMATION:
TMission_Formation();
break;
case TMISSION_ATTACKTARCOM:
case TMISSION_ATTACK:
TMission_Attack();
break;
case TMISSION_LOAD:
TMission_Load();
break;
case TMISSION_DEPLOY:
TMission_Deploy();
break;
case TMISSION_UNLOAD:
TMission_Unload();
break;
case TMISSION_MOVE:
case TMISSION_MOVECELL:
Coordinate_Move();
break;
/*
** All members of this team become invulnerable as if by magic.
*/
case TMISSION_INVULNERABLE:
TMission_Invulnerable();
break;
case TMISSION_GUARD:
Coordinate_Regroup();
break;
case TMISSION_DO:
Coordinate_Do();
break;
case TMISSION_SET_GLOBAL:
TMission_Set_Global();
break;
case TMISSION_ATT_WAYPT:
if (!Target_Legal(MissionTarget)) {
Assign_Mission_Target(TARGET_NONE);
IsNextMission = true;
} else {
Coordinate_Attack();
}
break;
case TMISSION_SPY:
TMission_Spy();
break;
case TMISSION_HOUND_DOG:
TMission_Follow();
break;
case TMISSION_LOOP:
TMission_Loop();
break;
}
/*
** Check for mission time out condition. If the mission does in fact time out, then
** flag it so that the team mission list will advance.
*/
switch (mission->Mission) {
// case TMISSION_UNLOAD:
case TMISSION_GUARD:
if (TimeOut == 0) {
IsNextMission = true;
}
break;
}
} else {
if (IsMoving) {
IsReforming = !Coordinate_Regroup();
} else {
Coordinate_Move();
}
}
}
/***********************************************************************************************
* TeamClass::Add -- Adds specified object to team. *
* *
* Use this routine to add the specified object to the team. The object is checked to make *
* sure that it can be assigned to the team. If it can't, then the object will be left *
* alone and false will be returned. *
* *
* INPUT: obj -- Pointer to the object that is to be assigned to this team. *
* *
* OUTPUT: bool; Was the unit added to the team? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 12/29/1994 JLB : Created. *
* 01/02/1995 JLB : Initiation flag setup. *
* 08/06/1995 JLB : Allows member stealing from lesser priority teams. *
*=============================================================================================*/
bool TeamClass::Add(FootClass * obj)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
if (!obj) return(false);
int typeindex;
if (!Can_Add(obj, typeindex)) return(false);
/*
** All is ok to add the object to the team, but if the object is already part of
** another team, then it must be removed from that team first.
*/
if (obj->Team.Is_Valid()) {
obj->Team->Remove(obj);
}
/*
** Actually add the object to the team.
*/
Quantity[typeindex]++;
obj->IsInitiated = (Member == NULL);
obj->Member = Member;
Member = obj;
obj->Team = this;
/*
** If a common trigger is designated for this team type, then attach the
** trigger to this team member.
*/
if (Trigger.Is_Valid()) {
obj->Attach_Trigger(Trigger);
}
Total++;
Risk += obj->Risk();
if (Zone == TARGET_NONE) {
Calc_Center(Zone, ClosestMember);
}
/*
** Return with success, since the object was added to the team.
*/
IsAltered = JustAltered = true;
return(true);
}
/***********************************************************************************************
* TeamClass::Can_Add -- Determines if the specified object can be added to team. *
* *
* This routine will examine the team and determine if the specified object can be *
* properly added to this team. This is a security check to filter out those objects that *
* should not be added because of conflicting priorities or other restrictions. *
* *
* INPUT: obj -- Pointer to the candidate object that is being checked for legal *
* adding to this team. *
* *
* typeindex-- The class index number (according to the team type's class array) that *
* the candidate object is classified as. The routine processes much *
* faster if you can provide this information, but if you don't, the *
* routine will figure it out. *
* *
* OUTPUT: bool; Can the specified object be added to this team? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 02/27/1996 JLB : Created. *
*=============================================================================================*/
bool TeamClass::Can_Add(FootClass * obj, int & typeindex) const
{
assert(IsActive);
assert(Teams.ID(this) == ID);
/*
** Trying to add the team member to itself is an error condition.
*/
if (obj->Team == this) {
return(false);
}
/*
** The object must be active, a member of this house. A special dispensation is given to
** units that are in radio contact. It is presumed that they are very busy and should
** not be disturbed.
*/
if (!_Is_It_Breathing(obj) || obj->In_Radio_Contact() || obj->House != House) {
return(false);
}
/*
** If the object is doing some mission that precludes it from joining
** a team then don't add it.
*/
if (obj->Mission != MISSION_NONE && !MissionClass::Is_Recruitable_Mission(obj->Mission)) {
return(false);
}
/*
** If this object is part of another team, then check to make sure that it
** is permitted to leave the other team in order to join this one. If not,
** then no further processing is allowed -- bail.
*/
if (obj->Team.Is_Valid() && (obj->Team->Class->RecruitPriority >= Class->RecruitPriority)) {
return(false);
}
/*
** Aircraft that have no ammo for their weapons cannot be recruited into a team.
*/
if (obj->What_Am_I() == RTTI_AIRCRAFT && obj->Techno_Type_Class()->PrimaryWeapon != NULL && !obj->Ammo) {
return(false);
}
/*
** Search for the exact member index that the candidate object matches.
** If no match could be found, then adding the object to the team cannot
** occur.
*/
for (typeindex = 0; typeindex < Class->ClassCount; typeindex++) {
if (Class->Members[typeindex].Class == &obj->Class_Of()) {
break;
}
}
if (typeindex == Class->ClassCount) {
return(false);
}
/*
** If the team is already full of this type, then adding the object is not allowed.
** Return with a failure flag in this case.
*/
if (Quantity[typeindex] >= Class->Members[typeindex].Quantity) {
return(false);
}
return(true);
}
/***********************************************************************************************
* TeamClass::Remove -- Removes the specified object from the team. *
* *
* Use this routine to remove an object from a team. Objects removed from the team are *
* then available to be recruited by other teams, or even by the same team at a later time. *
* *
* INPUT: obj -- Pointer to the object that is to be removed from this team. *
* *
* typeindex-- Optional index of where this object type is specified in the type *
* type class. This parameter can be omitted. It only serves to make *
* the removal process faster. *
* *
* OUTPUT: bool; Was the object removed from this team? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 12/29/1994 JLB : Created. *
* 01/02/1995 JLB : Initiation tracking and team captain selection. *
*=============================================================================================*/
bool TeamClass::Remove(FootClass * obj, int typeindex)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
/*
** Make sure that the object is in fact a member of this team. If not, then it can't
** be removed. Return success because the end result is the same.
*/
if (this != obj->Team) {
return(true);
}
/*
** Detach the common trigger for this team type. Only current and active members of the
** team have that trigger attached. The exception is for player team members that
** get removed from a reinforcement team.
*/
if (obj->Trigger == Trigger && !obj->House->IsPlayerControl) {
obj->Attach_Trigger(NULL);
}
/*
** If the proper team index was not provided, then find it in the type type class. The
** team type class will not be set if the appropriate type could not be found
** for this object. This indicates that the object was illegally added. Continue to
** process however, since removing this object from the team is a good idea.
*/
if (typeindex == -1) {
for (typeindex = 0; typeindex < Class->ClassCount; typeindex++) {
if (Class->Members[typeindex].Class == &obj->Class_Of()) {
break;
}
}
}
/*
** Decrement the counter for the team class. There is now one less of this object type.
*/
if (typeindex < Class->ClassCount) {
Quantity[typeindex]--;
}
/*
** Actually remove the object from the team. Scan through the team members
** looking for the one that matches the one specified. If it is found, it
** is unlinked from the member chain. During this scan, a check is made to
** ensure that at least one remaining member is still initiated. If not, then
** a new team captain must be chosen.
*/
bool initiated = false;
FootClass * prev = 0;
FootClass * curr = Member;
bool found = false;
while (curr != NULL && (!found || !initiated)) {
if (curr == obj) {
if (prev != NULL) {
prev->Member = curr->Member;
} else {
Member = curr->Member;
}
FootClass * temp = curr->Member;
curr->Member = 0;
curr->Team = 0;
curr->SuspendedMission = MISSION_NONE;
curr->SuspendedNavCom = TARGET_NONE;
curr->SuspendedTarCom = TARGET_NONE;
curr = temp;
Total--;
found = true;
Risk -= obj->Risk();
continue;
}
/*
** If this (remaining) member is initiated, then keep a record of this.
*/
initiated |= curr->IsInitiated;
prev = curr;
curr = curr->Member;
}
/*
** A unit that breaks off of a team will enter idle mode.
*/
obj->Enter_Idle_Mode();
/*
** If, after removing the team member, there are no initiated members left
** in the team, then just make the first remaining member of the team the
** team captain. Mark the center location of the team as invalid so that
** it will be centered around the captain.
*/
if (!initiated && Member != NULL) {
Member->IsInitiated = true;
Zone = TARGET_NONE;
}
/*
** Must record that the team composition has changed. At the next opportunity,
** the team members will be counted and appropriate AI adjustments made.
*/
IsAltered = JustAltered = true;
return(true);
}
/***********************************************************************************************
* TeamClass::Recruit -- Attempts to recruit members to the team for the given index ID. *
* *
* This routine will take the given index ID and scan for available objects of that type *
* to recruit to the team. Recruiting will continue until that object type has either *
* been exhausted or if the team's requirement for that type has been filled. *
* *
* INPUT: typeindex -- The index for the object type to recruit. The index is used to *
* look into the type type's array of object types that make up this *
* team. *
* *
* OUTPUT: Returns with the number of objects added to this team. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 12/29/1994 JLB : Created. *
* 04/10/1995 JLB : Scans for units too. *
*=============================================================================================*/
int TeamClass::Recruit(int typeindex)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
COORDINATE center = As_Coord(Zone);
if (Class->Origin != -1) {
center = Cell_Coord(Scen.Waypoint[Class->Origin]);
}
int added = 0; // Total number added to team.
/*
** Quick check to see if recruiting is really allowed for this index or not.
*/
if (Class->Members[typeindex].Quantity > Quantity[typeindex]) {
switch (Class->Members[typeindex].Class->What_Am_I()) {
/*
** For infantry objects, sweep through the infantry in the game looking for
** ones owned by the house that owns the team. When found, try to add.
*/
case RTTI_INFANTRYTYPE:
case RTTI_INFANTRY:
{
InfantryClass * best = 0;
int bestdist = -1;
for (int index = 0; index < Infantry.Count(); index++) {
InfantryClass * infantry = Infantry.Ptr(index);
int d = infantry->Distance(center);
if ((d < bestdist || bestdist == -1) && Can_Add(infantry, typeindex)) {
best = infantry;
bestdist = d;
}
}
if (best) {
best->Assign_Target(TARGET_NONE);
Add(best);
added++;
}
}
break;
case RTTI_AIRCRAFTTYPE:
case RTTI_AIRCRAFT:
{
AircraftClass * best = 0;
int bestdist = -1;
for (int index = 0; index < Aircraft.Count(); index++) {
AircraftClass * aircraft = Aircraft.Ptr(index);
int d = aircraft->Distance(center);
if ((d < bestdist || bestdist == -1) && Can_Add(aircraft, typeindex)) {
best = aircraft;
bestdist = d;
}
}
if (best) {
best->Assign_Target(TARGET_NONE);
Add(best);
added++;
}
}
break;
case RTTI_UNITTYPE:
case RTTI_UNIT:
{
UnitClass * best = 0;
int bestdist = -1;
for (int index = 0; index < Units.Count(); index++) {
UnitClass * unit = Units.Ptr(index);
int d = unit->Distance(center);
if (unit->House == House && unit->Class == Class->Members[typeindex].Class) {
if ((d < bestdist || bestdist == -1) && Can_Add(unit, typeindex)) {
best = unit;
bestdist = d;
}
}
if (best) {
best->Assign_Target(TARGET_NONE);
Add(best);
added++;
/*
** If a transport is added to the team, the occupants
** are added by default.
*/
FootClass * f = best->Attached_Object();
while (f) {
Add(f);
f = (FootClass *)(ObjectClass *)f->Next;
}
}
}
}
break;
case RTTI_VESSELTYPE:
case RTTI_VESSEL:
{
VesselClass * best = 0;
int bestdist = -1;
for (int index = 0; index < Vessels.Count(); index++) {
VesselClass * vessel = Vessels.Ptr(index);
int d = vessel->Distance(center);
if (vessel->House == House && vessel->Class == Class->Members[typeindex].Class) {
if ((d < bestdist || bestdist == -1) && Can_Add(vessel, typeindex)) {
best = vessel;
bestdist = d;
}
}
if (best) {
best->Assign_Target(TARGET_NONE);
Add(best);
added++;
/*
** If a transport is added to the team, the occupants
** are added by default.
*/
FootClass * f = best->Attached_Object();
while (f) {
Add(f);
f = (FootClass *)(ObjectClass *)f->Next;
}
}
}
}
break;
}
}
return(added);
}
/***********************************************************************************************
* TeamClass::Detach -- Removes specified target from team tracking. *
* *
* When a target object is about to be removed from the game (e.g., it was killed), then *
* any team that is looking at that target must abort from that target. *
* *
* INPUT: target -- The target object that is going to be removed from the game. *
* *
* all -- Is the target going away for good as opposed to just cloaking/hiding? *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 12/29/1994 JLB : Created. *
*=============================================================================================*/
void TeamClass::Detach(TARGET target, bool )
{
assert(IsActive);
assert(Teams.ID(this) == ID);
/*
** If the target to detach matches the target of this team, then remove
** the target from this team's Tar/Nav com and let the chips fall
** where they may.
*/
if (Target == target) {
Target = TARGET_NONE;
}
if (MissionTarget == target) {
MissionTarget = TARGET_NONE;
}
if (Trigger.Is_Valid() && Trigger->As_Target() == target) {
Trigger = 0;
}
}
/***********************************************************************************************
* TeamClass::Calc_Center -- Determines average location of team members. *
* *
* Use this routine to calculate the "center" location of the team. This is the average *
* position of all members of the team. Using this center value it is possible to tell *
* if a team member is too far away and where to head to in order to group up. *
* *
* INPUT: center -- Average center target location of the team. Only initiated members *
* will be considered. *
* *
* close_member--Location (as target) of the unit that is closest to the team's *
* target. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 12/29/1994 JLB : Created. *
*=============================================================================================*/
void TeamClass::Calc_Center(TARGET & center, TARGET & close_member) const
{
assert(IsActive);
assert(Teams.ID(this) == ID);
/*
** Presume there is no center. This will be confirmed in the following scanning
** operation.
*/
close_member = TARGET_NONE;
center = TARGET_NONE;
FootClass const * team_member = Member; // Working team member pointer.
/*
** If there are no members of the team, then there can be no center point of the team.
*/
if (team_member == NULL) return;
/*
** If the team is supposed to follow a nearby friendly unit, then the
** team's "center" will actually be that unit. Otherwise, calculated the
** average center location for the team.
*/
if (Class->MissionList[CurrentMission].Mission == TMISSION_HOUND_DOG) {
/*
** First pick a member of the team. The closest friendly object to that member
** will be picked.
*/
if (!team_member) return;
FootClass const * closest = NULL; // Current closest friendly object.
int distance = -1; // Record of last closest distance calc.
/*
** Scan through all vehicles.
*/
for (int unit_index = 0; unit_index < Units.Count(); unit_index++) {
FootClass const * trial_unit = Units.Ptr(unit_index);
if (_Is_It_Breathing(trial_unit) && trial_unit->House->Is_Ally(House) && trial_unit->Team != this) {
int trial_distance = team_member->Distance(trial_unit);
if (distance == -1 || trial_distance < distance) {
distance = trial_distance;
closest = trial_unit;
}
}
}
/*
** Scan through all infantry.
*/
for (int infantry_index = 0; infantry_index < Infantry.Count(); infantry_index++) {
FootClass const * trial_infantry = Infantry.Ptr(infantry_index);
if (_Is_It_Breathing(trial_infantry) && trial_infantry->House->Is_Ally(House) && trial_infantry->Team != this) {
int trial_distance = team_member->Distance(trial_infantry);
if (distance == -1 || trial_distance < distance) {
distance = trial_distance;
closest = trial_infantry;
}
}
}
/*
** Scan through all vessels.
*/
for (int vessel_index = 0; vessel_index < Vessels.Count(); vessel_index++) {
FootClass const * trial_vessel = Vessels.Ptr(vessel_index);
if (_Is_It_Breathing(trial_vessel) && trial_vessel->House->Is_Ally(House) && trial_vessel->Team != this) {
int trial_distance = team_member->Distance(trial_vessel);
if (distance == -1 || trial_distance < distance) {
distance = trial_distance;
closest = trial_vessel;
}
}
}
/*
** Set the center location as actually the friendly object that is closest. If there
** is no friendly object, then don't set any center location at all.
*/
if (closest) {
center = closest->As_Target();
close_member = Member->As_Target();
}
} else {
long x = 0; // Accumulated X coordinate.
long y = 0; // Accumulated Y coordinate.
int dist = 0; // Closest recorded distance to team target.
int quantity = 0; // Number of team members counted.
FootClass const * closest = 0; // Closest member to target.
/*
** Scan through all team members and accumulate the X and Y component of their
** location. Only team members that are active will be considered. Also keep
** track of the team member that is closest to the team's target.
*/
while (team_member != NULL) {
if (_Is_It_Playing(team_member)) {
/*
** Accumulate X and Y components of qualified team members.
*/
x += Coord_X(team_member->Coord);
y += Coord_Y(team_member->Coord);
quantity++;
/*
** Keep a record of the team member that is nearest to the team's
** target.
*/
int try_dist = team_member->Distance(Target);
if (!dist || try_dist < dist) {
dist = try_dist;
closest = team_member;
}
}
team_member = team_member->Member;
}
/*
** If there were any qualifying members, then the team's center point can be
** determined.
*/
if (quantity) {
x /= quantity;
y /= quantity;
COORDINATE coord = XY_Coord((int)x, (int)y);
center = ::As_Target(coord);
/*
** If the center location is impassable, then just pick the location of
** one of the team members.
*/
if (!closest->Can_Enter_Cell(As_Cell(center))) {
// if (Class->Origin != -1) {
// center = ::As_Target(Scen.Waypoint[Class->Origin]);
// } else {
center = ::As_Target(Coord_Cell(closest->Center_Coord()));
// }
}
}
/*
** Record the position of the closest member to the team's target and
** that will be used as the regroup point.
*/
if (closest != NULL) {
close_member = ::As_Target(Coord_Cell(closest->Center_Coord()));
}
}
}
/***********************************************************************************************
* TeamClass::Took_Damage -- Informs the team when the team member takes damage. *
* *
* This routine is used when a team member takes damage. Usually the team will react in *
* some fashion to the attack. This reaction can range from running away to assigning this *
* new target as the team's target. *
* *
* INPUT: obj -- The team member that was damaged. *
* *
* result -- The severity of the damage taken. *
* *
* source -- The perpetrator of the damage. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 12/29/1994 JLB : Created. *
*=============================================================================================*/
void TeamClass::Took_Damage(FootClass * , ResultType result, TechnoClass * source)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
if ((result != RESULT_NONE) && (!Class->IsSuicide)) {
if (!IsMoving) {
// TCTCTC
// Should run to a better hiding place or disband into a group of hunting units.
} else {
/*
** Respond to the attack, but not if we're an aircraft or a LST.
*/
if (source && !Is_A_Member(source) && Member && Member->What_Am_I() != RTTI_AIRCRAFT && (Member->What_Am_I() != RTTI_VESSEL || *(VesselClass *)((FootClass *)Member) != VESSEL_TRANSPORT)) {
if (Target != source->As_Target()) {
/*
** Don't change target if the team's target is one that can fire as well. There is
** no point in endlessly shuffling between targets that have firepower.
*/
if (Target_Legal(Target)) {
TechnoClass * techno = As_Techno(Target);
if (techno && ((TechnoTypeClass const &)techno->Class_Of()).PrimaryWeapon != NULL) {
if (techno->In_Range(As_Coord(Zone), 0)) {
return;
}
}
}
/*
** Don't change target to aggressor if the aggressor cannot normally be attacked.
*/
if (source->What_Am_I() == RTTI_AIRCRAFT || (source->What_Am_I() == RTTI_VESSEL && (Member->What_Am_I() == RTTI_UNIT || Member->What_Am_I() == RTTI_INFANTRY))) {
return;
}
Target = source->As_Target();
}
}
}
}
}
/***********************************************************************************************
* TeamClass::Coordinate_Attack -- Handles coordinating a team attack. *
* *
* This function is called when the team knows what it should attack. This routine will *
* give the necessary orders to the members of the team. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 04/06/1995 JLB : Created. *
*=============================================================================================*/
void TeamClass::Coordinate_Attack(void)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
if (!Target_Legal(Target)) {
Target = MissionTarget;
}
/*
** Check if they're attacking a cell. If the contents of the cell are
** a bridge or a building/unit/techno, then it's a valid target. Otherwise,
** the target is invalid. This only applies to non-aircraft teams. An aircraft team
** can "attack" an empty cell and this is perfectly ok (paratrooper drop and parabombs
** are prime examples).
*/
if (Is_Target_Cell(Target) && Member != NULL && Fetch_A_Leader()->What_Am_I() != RTTI_AIRCRAFT) {
CellClass *cellptr = &Map[As_Cell(Target)];
TemplateType tt = cellptr->TType;
if (cellptr->Cell_Object()) {
Target = cellptr->Cell_Object()->As_Target();
} else {
if (tt != TEMPLATE_BRIDGE1 && tt != TEMPLATE_BRIDGE2 &&
tt != TEMPLATE_BRIDGE1H && tt != TEMPLATE_BRIDGE2H &&
tt != TEMPLATE_BRIDGE_1A && tt != TEMPLATE_BRIDGE_1B &&
tt != TEMPLATE_BRIDGE_2A && tt != TEMPLATE_BRIDGE_2B &&
tt != TEMPLATE_BRIDGE_3A && tt != TEMPLATE_BRIDGE_3B ) {
#ifdef FIXIT_CSII // checked - ajw 9/28/98
FootClass *unit = Member;
TeamMissionClass const * mission = &Class->MissionList[CurrentMission];
if(unit->What_Am_I() != RTTI_UNIT ||
*(UnitClass *)unit != UNIT_CHRONOTANK ||
mission->Mission != TMISSION_SPY)
#endif
Target = 0; // invalidize the target so it'll go to next mission.
}
}
}
if (!Target_Legal(Target)) {
IsNextMission = true;
} else {
TeamMissionClass const * mission = &Class->MissionList[CurrentMission];
FootClass * unit = Member;
while (unit != NULL) {
Coordinate_Conscript(unit);
if (_Is_It_Playing(unit)) {
if (mission->Mission == TMISSION_SPY && unit->What_Am_I() == RTTI_INFANTRY && *(InfantryClass *)unit == INFANTRY_SPY) {
unit->Assign_Mission(MISSION_CAPTURE);
unit->Assign_Target(Target);
} else {
#ifdef FIXIT_CSII // checked - ajw 9/28/98
if (mission->Mission == TMISSION_SPY && unit->What_Am_I() == RTTI_UNIT && *(UnitClass *)unit == UNIT_CHRONOTANK) {
UnitClass *tank = (UnitClass *)unit;
tank->Teleport_To(::As_Cell(Target));
tank->MoebiusCountDown = ChronoTankDuration * TICKS_PER_MINUTE;
Scen.Do_BW_Fade();
Sound_Effect(VOC_CHRONOTANK1, unit->Coord);
tank->Assign_Target(TARGET_NONE);
tank->Assign_Mission(MISSION_GUARD);
} else {
#endif
if (unit->Mission != MISSION_ATTACK && unit->Mission != MISSION_ENTER && unit->Mission != MISSION_CAPTURE) {
unit->Transmit_Message(RADIO_OVER_OUT);
unit->Assign_Mission(MISSION_ATTACK);
unit->Assign_Target(TARGET_NONE);
unit->Assign_Destination(TARGET_NONE);
}
}
#ifdef FIXIT_CSII // checked - ajw 9/28/98
}
#endif
if (unit->TarCom != Target) {
unit->Assign_Target(Target);
}
}
unit = unit->Member;
}
}
}
/***********************************************************************************************
* TeamClass::Coordinate_Regroup -- Handles team idling (regrouping). *
* *
* This routine is called when the team must delay at its current location. Team members *
* are grouped together by this function. It is called when the team needs to sit and *
* wait. *
* *
* INPUT: none *
* *
* OUTPUT: bool; Has the team completely regrouped? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 04/06/1995 JLB : Created. *
*=============================================================================================*/
bool TeamClass::Coordinate_Regroup(void)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
FootClass * unit = Member;
bool retval = true;
/*
** Regroup default logic.
*/
while (unit != NULL) {
Coordinate_Conscript(unit);
if (_Is_It_Playing(unit)) {
if (unit->Distance(Zone) > Rule.StrayDistance && (unit->Mission != MISSION_GUARD_AREA || !Target_Legal(unit->TarCom))) {
if (!Target_Legal(unit->NavCom)) {
// TCTCTC
// if (!Target_Legal(unit->NavCom) || ::Distance(unit->NavCom, Zone) > Rule.StrayDistance) {
unit->Assign_Mission(MISSION_MOVE);
unit->Assign_Destination(Zone);
retval = false;
if (!unit->IsFormationMove) {
unit->Assign_Mission(MISSION_MOVE);
CELL dest = unit->Adjust_Dest(As_Cell(Zone));
unit->Assign_Destination(::As_Target(dest));
} else {
retval = true; // formations are always considered regrouped.
}
}
} else {
/*
** The team is regrouping, so just sit here and wait.
*/
if (unit->Mission != MISSION_GUARD_AREA) {
unit->Assign_Mission(MISSION_GUARD);
unit->Assign_Destination(TARGET_NONE);
}
}
}
unit = unit->Member;
}
return(retval);
}
/***********************************************************************************************
* TeamClass::Coordinate_Do -- Handles the team performing specified mission. *
* *
* This will assign the specified mission to the team. If there are team members that are *
* too far away from the center of the team, then they will be told to move to the team's *
* location. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: This only works if the special mission the team members are to perform does not *
* require extra parameters. The ATTACK and MOVE missions are particularly bad. *
* *
* HISTORY: *
* 05/11/1996 JLB : Created. *
*=============================================================================================*/
void TeamClass::Coordinate_Do(void)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
FootClass * unit = Member;
MissionType do_mission = Class->MissionList[CurrentMission].Data.Mission;
/*
** For each unit either head it back to the team center or give it the main
** team mission order as appropriate.
*/
while (unit != NULL) {
Coordinate_Conscript(unit);
if (_Is_It_Playing(unit)) {
if (!Target_Legal(unit->TarCom) && !Target_Legal(unit->NavCom) && unit->Distance(Zone) > Rule.StrayDistance * 2) {
/*
** Only if the unit isn't already heading to regroup with the team, will it
** be given orders to do so.
*/
unit->Assign_Mission(MISSION_MOVE);
unit->Assign_Destination(Zone);
unit->Assign_Mission(MISSION_MOVE);
CELL dest = unit->Adjust_Dest(As_Cell(Zone));
unit->Assign_Destination(::As_Target(dest));
} else {
/*
** The team is regrouping, so just sit here and wait.
*/
if (!Target_Legal(unit->TarCom) && !Target_Legal(unit->NavCom) && unit->Mission != do_mission) {
unit->ArchiveTarget = TARGET_NONE;
unit->Assign_Mission(do_mission);
unit->Assign_Target(TARGET_NONE);
unit->Assign_Destination(TARGET_NONE);
}
}
}
unit = unit->Member;
}
}
/***********************************************************************************************
* TeamClass::Coordinate_Move -- Handles team movement coordination. *
* *
* This routine is called when the team must move to a new location. Movement and grouping *
* commands associated with this task are initiated here. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 04/06/1995 JLB : Created. *
*=============================================================================================*/
void TeamClass::Coordinate_Move(void)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
FootClass * unit = Member;
bool finished = true;
bool found = false;
if (!Target_Legal(Target)) {
Target = MissionTarget;
}
if (Target_Legal(Target)) {
if (!Lagging_Units()) {
while (unit != NULL) {
/*
** Tell the unit, if necessary, that it should regroup
** with the main team location. If the unit is regrouping, then
** the team should continue NOT qualify as fully reaching the desired
** location.
*/
if (Coordinate_Conscript(unit)) {
finished = false;
}
if (unit->Mission == MISSION_UNLOAD || unit->MissionQueue == MISSION_UNLOAD) {
finished = false;
}
if (_Is_It_Playing(unit) && unit->Mission != MISSION_UNLOAD && unit->MissionQueue != MISSION_UNLOAD) {
int stray = Rule.StrayDistance;
if (unit->What_Am_I() == RTTI_AIRCRAFT) {
stray *= 3;
}
if (unit->What_Am_I() == RTTI_INFANTRY && ((InfantryClass const *)unit)->Class->IsDog) {
if (Target_Legal(unit->TarCom)) stray = unit->Techno_Type_Class()->ThreatRange;
if (Target_Legal(unit->TarCom) && unit->Distance(unit->TarCom) > stray) {
unit->Assign_Target(TARGET_NONE);
}
}
found = true;
int dist = unit->Distance(Target);
if (unit->IsFormationMove) {
if (::As_Target(Coord_Cell(unit->Coord)) != unit->NavCom) {
dist = Rule.StrayDistance + 1; // formation moves must be exact.
}
}
if (dist > stray ||
(unit->What_Am_I() == RTTI_AIRCRAFT &&
// (unit->In_Which_Layer() == LAYER_TOP &&
((AircraftClass *)unit)->Height > 0 &&
Coord_Cell(unit->Center_Coord()) != As_Cell(Target) &&
!((AircraftClass *)unit)->Class->IsFixedWing &&
Class->MissionList[CurrentMission+1].Mission != TMISSION_MOVE)) {
bool wasform = false;
if (unit->Mission != MISSION_MOVE) {
unit->Assign_Mission(MISSION_MOVE);
}
if (unit->NavCom != Target) {
/*
** Check if this destination should be adjusted for
** a formation move
*/
if (Is_Target_Cell(Target) && unit->IsFormationMove) {
CELL newcell = unit->Adjust_Dest(As_Cell(Target));
if (Coord_Cell(unit->Coord) != newcell) {
unit->Assign_Destination(::As_Target(newcell));
} else {
unit->Assign_Mission(MISSION_GUARD);
unit->Assign_Destination(TARGET_NONE);
wasform = true;
}
} else {
unit->Assign_Destination(Target);
}
}
if (!wasform) {
finished = false;
}
} else {
if (unit->Mission == MISSION_MOVE && (!Target_Legal(unit->NavCom) || Distance(unit->NavCom) < CELL_LEPTON_W)) {
unit->Assign_Destination(TARGET_NONE);
unit->Enter_Idle_Mode();
}
}
/*
** If any member still has a valid NavCom then consider this
** movement mission to still be in progress. This will ensure
** that the members come to a complete stop before the next
** mission commences. Without this, the team will prematurely
** start on the next mission even when all members aren't yet
** in their proper spot.
*/
if (Target_Legal(unit->NavCom)) {
finished = false;
}
}
unit = unit->Member;
}
} else {
finished = false;
}
}
/*
** If there are no initiated members to this team, then it certainly
** could not have managed to move to the target destination.
*/
if (!found) {
finished = false;
}
/*
** If all the team members are close enough to the desired destination, then
** move to the next mission.
*/
if (finished && IsMoving) {
IsNextMission = true;
}
}
/***********************************************************************************************
* TeamClass::Lagging_Units -- Finds and orders any lagging units to catch up. *
* *
* This routine will examine the team and find any lagging units. The units are then *
* ordered to catch up to the team member that is closest to the team's destination. This *
* routine will not do anything unless lagging members are suspected. This fact is *
* indicated by setting the IsLagging flag. The flag is set by some outside agent. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: Be sure to set IsLagging for the team if a lagging member is suspected. *
* *
* HISTORY: *
* 08/01/1995 PWG : Created. *
* 04/11/1996 JLB : Modified. *
*=============================================================================================*/
bool TeamClass::Lagging_Units(void)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
FootClass * unit = Member;
bool lag = false;
//BG: HACK - if it's in a formation move, then disable the check for
// VG added NULL check laggers, 'cause they're all moving simultaneously.
if (unit != NULL && unit->IsFormationMove) IsLagging = false;
/*
** If the IsLagging bit is not set, then obviously there are no lagging
** units.
*/
if (!IsLagging) return(false);
/*
** Scan through all of the units, searching for units who are having
** trouble keeping up with the pack.
*/
while (unit != NULL) {
if (_Is_It_Playing(unit)) {
int stray = Rule.StrayDistance;
if (unit->What_Am_I() == RTTI_AIRCRAFT) {
stray *= 3;
}
/*
** If we find a unit who has fallen too far away from the center of
** the pack, then we need to order that unit to catch up with the
** first unit.
*/
if (unit->Distance(ClosestMember) > stray) {
// TCTCTC
if (!Target_Legal(unit->NavCom)) {
// if (!Target_Legal(unit->NavCom) || ::Distance(unit->NavCom, ClosestMember) > Rule.StrayDistance) {
unit->Assign_Mission(MISSION_MOVE);
unit->Assign_Destination(ClosestMember);
}
lag = true;
} else {
/*
** We need to order all of the other units to hold their
** position until all lagging units catch up.
*/
if (unit->Mission != MISSION_GUARD) {
unit->Assign_Mission(MISSION_GUARD);
unit->Assign_Destination(TARGET_NONE);
}
}
}
unit = unit->Member;
}
/*
** Once we have handled the loop we know whether there are any lagging
** units or not.
*/
IsLagging = lag;
return(lag);
}
/***********************************************************************************************
* TeamClass::TMission_Unload -- Tells the team to unload passengers now. *
* *
* This routine tells all transport vehicles to unload passengers now. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/14/1995 JLB : Created. *
*=============================================================================================*/
int TeamClass::TMission_Unload(void)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
FootClass * unit = Member;
bool finished = true;
while (unit != NULL) {
Coordinate_Conscript(unit);
if (_Is_It_Playing(unit)) {
/*
** Only assign the mission if the unit is carrying a passenger, OR
** if the unit is a minelayer, with mines in it, and the cell it's
** on doesn't have a building (read: mine) in it already.
*/
#ifdef FIXIT_CSII // checked - ajw 9/28/98
/* Also, allow unload if it's a MAD Tank. */
if (unit->Is_Something_Attached() || (unit->What_Am_I() == RTTI_UNIT && *(UnitClass *)unit == UNIT_MINELAYER && unit->Ammo) || (unit->What_Am_I() == RTTI_UNIT && *(UnitClass *)unit == UNIT_MAD )) {
#else
if (unit->Is_Something_Attached() || (unit->What_Am_I() == RTTI_UNIT && *(UnitClass *)unit == UNIT_MINELAYER && unit->Ammo) ) {
#endif
if (unit->Is_Something_Attached()) {
/*
** Passenger-carrying vehicles will always return false until
** they've unloaded all passengers.
*/
finished = false;
}
/*
** The check for a building is located here because the mine layer may have
** already unloaded the mine but is still in the process of retracting
** the mine layer. During this time, it should not be considered to have
** finished its unload mission.
*/
if (Map[unit->Center_Coord()].Cell_Building() == NULL && unit->Mission != MISSION_UNLOAD) {
unit->Assign_Destination(TARGET_NONE);
unit->Assign_Target(TARGET_NONE);
unit->Assign_Mission(MISSION_UNLOAD);
finished = false;
}
} else {
/*
** A loaner transport should vacate the map when all transported objects
** have been offloaded.
*/
if (unit->IsALoaner) {
Remove(unit);
unit->Assign_Mission(MISSION_RETREAT);
unit->Commence();
}
}
}
unit = unit->Member;
}
if (finished) {
IsNextMission = true;
}
return(1);
}
/***********************************************************************************************
* TeamClass::TMission_Load -- Tells the team to load onto the transport now. *
* *
* This routine tells all non-transport units in the team to climb onto the transport in the*
* team. Note the transport must be a member of this team. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/28/1996 BWG : Created. *
*=============================================================================================*/
int TeamClass::TMission_Load(void)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
FootClass * unit = Member;
FootClass * trans = 0;
/*
** First locate the transport in the team, if there is one. There should
** only be one transport in the team.
*/
while(unit != NULL && trans == NULL) {
if (unit->Techno_Type_Class()->Max_Passengers() > 0) {
trans = unit;
break;
}
unit = unit->Member;
}
/*
** In the case of no transport available, then consider the mission complete
** since it can never complete otherwise.
*/
if (trans == NULL) {
IsNextMission = true;
return(1);
}
/*
** If the transport is already in radio contact, then this means that
** it is in the process of loading. During this time, don't bother to assign
** the enter mission to the other team members.
*/
if (trans->In_Radio_Contact()) {
return(1);
}
/*
** Find a member to assign the entry logic for.
*/
bool finished = true;
unit = Member; // re-point at the first member of the team again.
while (unit != NULL && Total > 1) {
Coordinate_Conscript(unit);
/*
** Only assign the mission if the unit is not the transport.
*/
if (_Is_It_Playing(unit) && unit != trans) {
if (unit->Mission != MISSION_ENTER) {
unit->Assign_Mission(MISSION_ENTER);
unit->Assign_Target(TARGET_NONE);
unit->Assign_Destination(trans->As_Target());
finished = false;
break;
}
finished = false;
}
unit = unit->Member;
}
if (finished) {
IsNextMission = true;
}
return(1);
}
/***********************************************************************************************
* TeamClass::Coordinate_Conscript -- Gives orders to new recruit. *
* *
* This routine will give the movement orders to the conscript so that it will group *
* with the other members of the team. *
* *
* INPUT: unit -- Pointer to the conscript unit. *
* *
* OUTPUT: bool; Is the unit still scurrying to reach the team's current location? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 04/06/1995 JLB : Created. *
*=============================================================================================*/
bool TeamClass::Coordinate_Conscript(FootClass * unit)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
if (_Is_It_Breathing(unit) && !unit->IsInitiated) {
if (unit->Distance(Zone) > Rule.StrayDistance) {
if (!Target_Legal(unit->NavCom)) {
unit->Assign_Mission(MISSION_MOVE);
unit->Assign_Target(TARGET_NONE);
unit->IsFormationMove = false;
unit->Assign_Destination(Zone);
}
return(true);
} else {
/*
** This unit has gotten close enough to the team center so that it is
** now considered initiated. An initiated unit is considered when calculating
** the center of the team.
*/
unit->IsInitiated = true;
}
}
return(false);
}
/***************************************************************************
* TeamClass::Is_A_Member -- Tests if a unit is a member of a team *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/16/1995 PWG : Created. *
*=========================================================================*/
bool TeamClass::Is_A_Member(void const * who) const
{
assert(IsActive);
assert(Teams.ID(this) == ID);
FootClass * unit = Member;
while (unit != NULL) {
if (unit == who) {
return(true);
}
unit = unit->Member;
}
return(false);
}
/***************************************************************************
* TeamClass::Suspend_Teams -- Suspends activity for low priority teams *
* *
* INPUT: int priority - determines what is considered low priority. *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/19/1995 PWG : Created. *
*=========================================================================*/
void TeamClass::Suspend_Teams(int priority, HouseClass const * house)
{
for (int index = 0; index < Teams.Count(); index++) {
TeamClass * team = Teams.Ptr(index);
/*
** If a team is below the "survival priority level", then it gets
** destroyed. The team members are then free to be reassigned.
*/
if (team != NULL && team->House == house && team->Class->RecruitPriority < priority) {
FootClass * unit = team->Member;
while (team->Member) {
team->Remove(team->Member);
}
team->IsAltered = team->JustAltered = true;
team->SuspendTimer = Rule.SuspendDelay * TICKS_PER_MINUTE;
team->Suspended = true;
}
}
}
/***********************************************************************************************
* TeamClass::Is_Leaving_Map -- Checks if team is in process of leaving the map *
* *
* This routine is used to see if the team is leaving the map. A team that is leaving the *
* map gives implicit permission for its members to leave the map. *
* *
* INPUT: none *
* *
* OUTPUT: bool; Is this team trying to leave the map? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 04/30/1996 JLB : Created. *
*=============================================================================================*/
bool TeamClass::Is_Leaving_Map(void) const
{
assert(IsActive);
assert(Teams.ID(this) == ID);
if (IsMoving && CurrentMission >= 0) {
TeamMissionClass const * mission = &Class->MissionList[CurrentMission];
if (mission->Mission == TMISSION_MOVE && !Map.In_Radar(Scen.Waypoint[mission->Data.Value])) {
return(true);
}
}
return(false);
}
/***********************************************************************************************
* TeamClass::Has_Entered_Map -- Determines if the entire team has entered the map. *
* *
* This will examine all team members and only if all of them have entered the map, will *
* it return true. This routine is used to recognize the case of a team that has been *
* generated off map and one that has already entered game play. This knowledge can lead *
* to more intelligent behavior regarding team and member disposition. *
* *
* INPUT: none *
* *
* OUTPUT: bool; Have all members of this team entered the map? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/26/1996 JLB : Created. *
*=============================================================================================*/
bool TeamClass::Has_Entered_Map(void) const
{
bool ok = true;
FootClass * foot = Member;
while (foot != NULL) {
if (!foot->IsLocked) {
ok = false;
break;
}
foot = (FootClass *)(ObjectClass *)(foot->Next);
}
return(ok);
}
/***********************************************************************************************
* TeamClass::Scan_Limit -- Force all members of the team to have limited scan range. *
* *
* This routine is used when one of the team members cannot get within range of the team's *
* target. In such a case, the team must be assigned a new target and all members of that *
* team must recognize that a restricted target scan is required. This is done by clearing *
* out the team's target so that it will be forced to search for a new one. Also, since the *
* members are flagged for short scanning, whichever team member is picked to scan for a *
* target will scan for one that is within range. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: The team will reassign its target as a result of this routine. *
* *
* HISTORY: *
* 07/26/1996 JLB : Created. *
*=============================================================================================*/
void TeamClass::Scan_Limit(void)
{
Assign_Mission_Target(TARGET_NONE);
FootClass * foot = Member;
while (foot != NULL) {
foot->Assign_Target(TARGET_NONE);
foot->IsScanLimited = true;
foot = foot->Member;
}
}
/***********************************************************************************************
* TeamClass::TMission_Formation -- Process team formation change command. *
* *
* This routine will change the team's formation to that specified in the team command *
* parameter. It is presumed that the team will have further movement orders so that the *
* formation can serve some purpose. Merely changing the formation doesn't alter the *
* member's location. The team must be given a movement order before team member *
* repositioning will occur. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the time to delay before further team actions should occur. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/06/1996 JLB : Created. *
*=============================================================================================*/
int TeamClass::TMission_Formation(void)
{
FootClass * member = Member;
TeamMissionClass const * mission = &Class->MissionList[CurrentMission];
Formation = mission->Data.Formation;
int group = ID + 10;
int xdir = 0;
int ydir = 0;
bool evenodd = 1;
HousesType house = (member != NULL) ? member->Owner() : HOUSE_NONE;
/*
** Assign appropriate formation offsets for each of the members
** of this team.
*/
switch (Formation) {
case FORMATION_NONE:
while (member != NULL) {
member->Group = 0xFF;
member->XFormOffset = 0x80000000;
member->YFormOffset = 0x80000000;
member->IsFormationMove = false;
member = member->Member;
}
break;
case FORMATION_TIGHT:
while (member != NULL) {
member->Group = group;
member->XFormOffset = 0;
member->YFormOffset = 0;
member->IsFormationMove = true;
member = member->Member;
}
break;
case FORMATION_LOOSE:
break;
case FORMATION_WEDGE_N:
ydir = -(Total / 2);
xdir = 0;
while (member != NULL) {
member->Group = group;
member->XFormOffset = xdir;
member->YFormOffset = ydir;
member->IsFormationMove = true;
xdir = -xdir;
evenodd ^= 1;
if (!evenodd) {
xdir -= 2;
ydir += 2;
}
member = member->Member;
}
break;
case FORMATION_WEDGE_E:
xdir = (Total / 2);
ydir = 0;
while (member != NULL) {
member->Group = group;
member->XFormOffset = xdir;
member->YFormOffset = ydir;
member->IsFormationMove = true;
ydir = -ydir;
evenodd ^= 1;
if (!evenodd) {
xdir -= 2;
ydir -= 2;
}
member = member->Member;
}
break;
case FORMATION_WEDGE_S:
ydir = (Total / 2);
xdir = 0;
while (member != NULL) {
member->Group = group;
member->XFormOffset = xdir;
member->YFormOffset = ydir;
member->IsFormationMove = true;
xdir = -xdir;
evenodd ^= 1;
if (!evenodd) {
xdir -= 2;
ydir -= 2;
}
member = member->Member;
}
break;
case FORMATION_WEDGE_W:
xdir = -(Total / 2);
ydir = 0;
while (member != NULL) {
member->Group = group;
member->XFormOffset = xdir;
member->YFormOffset = ydir;
member->IsFormationMove = true;
ydir = -ydir;
evenodd ^= 1;
if (!evenodd) {
xdir += 2;
ydir -= 2;
}
member = member->Member;
}
break;
case FORMATION_LINE_NS:
ydir = -(Total/2);
while (member != NULL) {
member->Group = group;
member->XFormOffset = 0;
member->YFormOffset = ydir;
member->IsFormationMove = true;
member = member->Member;
ydir += 2;
}
break;
case FORMATION_LINE_EW:
xdir = -(Total/2);
while (member != NULL) {
member->Group = group;
member->XFormOffset = xdir;
member->YFormOffset = 0;
member->IsFormationMove = true;
member = member->Member;
xdir += 2;
}
break;
}
/*
** Now calculate the group's movement type and speed
*/
if (Formation != FORMATION_NONE && house != HOUSE_NONE) {
TeamFormDataStruct& team_form_data = TeamFormData[house];
team_form_data.TeamSpeed[group] = SPEED_WHEEL;
team_form_data.TeamMaxSpeed[group] = MPH_LIGHT_SPEED;
member = Member;
while (member != NULL) {
RTTIType mytype = member->What_Am_I();
SpeedType memspeed;
MPHType memmax;
bool speedcheck = false;
if (mytype == RTTI_INFANTRY) {
memspeed = SPEED_FOOT;
memmax = ((InfantryClass *)member)->Class->MaxSpeed;
speedcheck = true;
}
if (mytype == RTTI_UNIT) {
memspeed = ((UnitClass *)member)->Class->Speed;
memmax = ((UnitClass *)member)->Class->MaxSpeed;
speedcheck = true;
}
if (mytype == RTTI_VESSEL) {
memspeed = ((VesselClass *)member)->Class->Speed;
memmax = ((VesselClass *)member)->Class->MaxSpeed;
speedcheck = true;
}
if (speedcheck) {
if (memmax < team_form_data.TeamMaxSpeed[group]) {
team_form_data.TeamMaxSpeed[group] = memmax;
team_form_data.TeamSpeed[group] = memspeed;
}
}
member = member->Member;
}
/*
** Now that it's all calculated, assign the movement type and
** speed to every member of the team.
*/
member = Member;
while (member != NULL) {
member->FormationSpeed = team_form_data.TeamSpeed[group];
member->FormationMaxSpeed = team_form_data.TeamMaxSpeed[group];
if (member->What_Am_I() == RTTI_INFANTRY) {
member->FormationSpeed = SPEED_FOOT;
member->FormationMaxSpeed = MPH_SLOW_ISH;
}
member = member->Member;
}
}
// Advance past the formation-setting command.
IsNextMission = true;
return(1);
}
/***********************************************************************************************
* TeamClass::TMission_Attack -- Perform the team attack mission command. *
* *
* This will tell the team to attack the quarry specified in the team command. If the team *
* already has a target, this it is presumed that this target take precidence and it won't *
* be changed. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the delay before the next team logic operation should occur. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/06/1996 JLB : Created. *
*=============================================================================================*/
int TeamClass::TMission_Attack(void)
{
if (!Target_Legal(MissionTarget) && Member != NULL) {
TeamMissionClass const * mission = &Class->MissionList[CurrentMission];
/*
** Pick a team leader that has a weapon. Only in the case of no
** team members having any weapons, will a member without a weapon
** be chosen.
*/
FootClass const * candidate = Fetch_A_Leader();
/*
** Have the team leader pick what the next team target will be.
*/
switch (mission->Data.Quarry) {
case QUARRY_ANYTHING:
Assign_Mission_Target(candidate->Greatest_Threat(THREAT_NORMAL));
break;
case QUARRY_BUILDINGS:
Assign_Mission_Target(candidate->Greatest_Threat(THREAT_BUILDINGS));
break;
case QUARRY_HARVESTERS:
Assign_Mission_Target(candidate->Greatest_Threat(THREAT_TIBERIUM));
break;
case QUARRY_INFANTRY:
Assign_Mission_Target(candidate->Greatest_Threat(THREAT_INFANTRY));
break;
case QUARRY_VEHICLES:
Assign_Mission_Target(candidate->Greatest_Threat(THREAT_VEHICLES));
break;
case QUARRY_FACTORIES:
Assign_Mission_Target(candidate->Greatest_Threat(THREAT_FACTORIES));
break;
case QUARRY_DEFENSE:
Assign_Mission_Target(candidate->Greatest_Threat(THREAT_BASE_DEFENSE));
break;
case QUARRY_THREAT:
Assign_Mission_Target(candidate->Greatest_Threat(THREAT_NORMAL));
break;
case QUARRY_POWER:
Assign_Mission_Target(candidate->Greatest_Threat(THREAT_POWER));
break;
case QUARRY_FAKES:
Assign_Mission_Target(candidate->Greatest_Threat(THREAT_FAKES));
break;
default:
break;
}
if (!Target_Legal(MissionTarget)) IsNextMission = true;
}
Coordinate_Attack();
return(1);
}
/***********************************************************************************************
* TeamClass::TMission_Spy -- Perform the team spy mission. *
* *
* This will give the team a spy mission to the location specified. It is presumed that *
* the location of the team mission actually resides under the building to be spied. If *
* no building exists at the location, then the spy operation is presumed to be a mere *
* move operation. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the delay before the next team logic operation should occur. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/06/1996 JLB : Created. *
*=============================================================================================*/
int TeamClass::TMission_Spy(void)
{
if (Is_Target_Cell(MissionTarget))
{
CELL cell = ::As_Cell(MissionTarget);
CellClass * cellptr = &Map[cell];
#ifdef FIXIT_CSII // checked - ajw 9/28/98
ObjectClass * bldg = cellptr->Cell_Building();
#else
ObjectClass * bldg = cellptr->Cell_Object();
#endif
if (bldg != NULL)
{
Assign_Mission_Target(bldg->As_Target());
Coordinate_Attack();
}
#ifdef FIXIT_CSII // checked - ajw 9/28/98
else
{
FootClass *member = Member;
if(member->What_Am_I() == RTTI_UNIT && *(UnitClass *)member == UNIT_CHRONOTANK)
{
bool finished = true;
while (member)
{
if ( !((UnitClass *)member)->MoebiusCountDown) finished = false;
member = member->Member;
}
if (!finished)
{
Coordinate_Attack();
}
else
{
Assign_Mission_Target(TARGET_NONE);
IsNextMission = true;
}
}
}
#endif
}
else
{
if (!Target_Legal(MissionTarget))
{
Assign_Mission_Target(TARGET_NONE);
IsNextMission = true;
}
else
{
Coordinate_Attack();
}
}
return(1);
}
/***********************************************************************************************
* TeamClass::TMission_Follow -- Perform the "follow friendlies" team command. *
* *
* This will cause the team members to search out and follow the nearest friendly object. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the delay before the next team logic operation should be performed. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/06/1996 JLB : Created. *
*=============================================================================================*/
int TeamClass::TMission_Follow(void)
{
Calc_Center(Zone, ClosestMember);
Target = Zone;
Coordinate_Move();
return(1);
}
/***********************************************************************************************
* TeamClass::TMission_Loop -- Causes the team mission processor to jump to new location. *
* *
* This is equivalent to a jump or goto command. It will alter the team command processing *
* such that it will continue processing at the command number specified. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the delay before the next team logic operation should be performed. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/06/1996 JLB : Created. *
*=============================================================================================*/
int TeamClass::TMission_Loop(void)
{
TeamMissionClass const * mission = &Class->MissionList[CurrentMission];
CurrentMission = mission->Data.Value-1;
IsNextMission = true;
return(1);
}
/***********************************************************************************************
* TeamClass::TMission_Invulnerable -- Makes the entire team invulnerable for a period of time *
* *
* This is a team mission that simulates the Iron Curtain device. It will make all team *
* members invlunerable for a temporary period of time. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the time delay before the next team logic operation should occur. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/06/1996 JLB : Created. *
*=============================================================================================*/
int TeamClass::TMission_Invulnerable(void)
{
FootClass * foot = Member;
while (foot != NULL) {
foot->IronCurtainCountDown = Rule.IronCurtainDuration * TICKS_PER_MINUTE;
foot->Mark(MARK_CHANGE);
foot = foot->Member;
}
IsNextMission = true;
return(1);
}
/***********************************************************************************************
* TeamClass::TMission_Set_Global -- Performs a set global flag operation. *
* *
* This routine is used by the team to set a global variable but otherwise perform no *
* visible effect on the team. By using this routine, sophisticated trigger dependencies *
* can be implemented. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the delay before the next team logic operation should occur. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/06/1996 JLB : Created. *
*=============================================================================================*/
int TeamClass::TMission_Set_Global(void)
{
TeamMissionClass const * mission = &Class->MissionList[CurrentMission];
Scen.Set_Global_To(mission->Data.Value, true);
IsNextMission = true;
return(1);
}
/***********************************************************************************************
* TeamClass::TMision_Patrol -- Handles patrolling from one location to another. *
* *
* A patrolling team will move to the designated waypoint, but along the way it will *
* periodically scan for nearby enemies. If an enemy is found, the patrol mission turns *
* into an attack mission until the target is destroyed -- after which it resumes its *
* patrol duties. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the delay before the next call to this routine is needed. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/12/1996 JLB : Created. *
*=============================================================================================*/
int TeamClass::TMission_Patrol(void)
{
/*
** Reassign the movement destination if the target has been prematurely
** cleared (probably because the object has been destroyed).
*/
if (!Target_Legal(Target)) {
TeamMissionClass const * mission = &Class->MissionList[CurrentMission];
if ((unsigned)mission->Data.Value < WAYPT_COUNT) {
Assign_Mission_Target(::As_Target(Scen.Waypoint[mission->Data.Value]));
}
}
/*
** Every so often, scan for a nearby enemy.
*/
if (Frame % (Rule.PatrolTime * TICKS_PER_MINUTE) == 0) {
FootClass * leader = Fetch_A_Leader();
if (leader != NULL) {
TARGET target = leader->Greatest_Threat(THREAT_NORMAL|THREAT_RANGE);
if (Target_Legal(target)) {
Assign_Mission_Target(target);
} else {
Assign_Mission_Target(TARGET_NONE);
}
}
}
/*
** If the mission target looks like it should be attacked, then do so, otherwise
** treat it as a movement destination.
*/
if (Is_Target_Object(Target)) {
Coordinate_Attack();
} else {
Coordinate_Move();
}
return(1);
}
int TeamClass::TMission_Deploy(void)
{
assert(IsActive);
assert(Teams.ID(this) == ID);
FootClass * unit = Member;
bool finished = true;
while (unit != NULL) {
Coordinate_Conscript(unit);
if (_Is_It_Playing(unit)) {
if (unit->What_Am_I() == RTTI_UNIT && *(UnitClass *)unit == UNIT_MCV) {
if (unit->Mission != MISSION_UNLOAD) {
unit->Assign_Destination(TARGET_NONE);
unit->Assign_Target(TARGET_NONE);
unit->Assign_Mission(MISSION_UNLOAD);
finished = false;
}
}
if (unit->What_Am_I() == RTTI_UNIT && *(UnitClass *)unit == UNIT_MINELAYER && unit->Ammo != 0) {
/*
** The check for a building is located here because the mine layer may have
** already unloaded the mine but is still in the process of retracting
** the mine layer. During this time, it should not be considered to have
** finished its unload mission.
*/
if (!Map[unit->Center_Coord()].Cell_Building() && unit->Mission != MISSION_UNLOAD) {
unit->Assign_Destination(TARGET_NONE);
unit->Assign_Target(TARGET_NONE);
unit->Assign_Mission(MISSION_UNLOAD);
finished = false;
}
}
}
unit = unit->Member;
}
if (finished) {
IsNextMission = true;
}
return(1);
}
/***********************************************************************************************
* TeamClass::Fetch_A_Leader -- Looks for a suitable leader member of the team. *
* *
* This will scan through the team members looking for one that is suitable as a leader *
* type. A team can sometimes contain limboed or unarmed members. These members are not *
* suitable for leadership roles. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with a suitable leader type unit. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/27/1996 JLB : Created. *
*=============================================================================================*/
FootClass * TeamClass::Fetch_A_Leader(void) const
{
FootClass * leader = Member;
/*
** Scan through the team members trying to find one that is an active member and
** is equipped with a weapon.
*/
while (leader != NULL) {
if (_Is_It_Playing(leader) && leader->Is_Weapon_Equipped()) break;
leader = leader->Member;
}
/*
** If no suitable leader was found, then just return with the first conveniently
** accessable team member. This presumes that some member is better than no member
** at all.
*/
if (leader == NULL) {
leader = Member;
}
return(leader);
}