3077 lines
124 KiB
C++
3077 lines
124 KiB
C++
/*
|
|
** Command & Conquer Red Alert(tm)
|
|
** Copyright 2025 Electronic Arts Inc.
|
|
**
|
|
** This program 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.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* $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)->IsActive = true;
|
|
}
|
|
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 ((unsigned)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;
|
|
|
|
/*
|
|
** 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) {
|
|
TeamSpeed[group] = SPEED_WHEEL;
|
|
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 < TeamMaxSpeed[group]) {
|
|
TeamMaxSpeed[group] = memmax;
|
|
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 = TeamSpeed[group];
|
|
member->FormationMaxSpeed = 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);
|
|
}
|