// // 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); }