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

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

Increased failed pathfinding fudge factor.

Buildings accept the Guard command if they can attack.

Don't allow force capturing of ally structures.

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

Fixed flag animation rendering in CTF.

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

Fixed legacy Obelisk line rendering.

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

Disable capture flag on Commandos.

Drop the flag when entering the limbo state.

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

Fixed Chan spawn on SCB10EA.

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

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

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

Fixed crash when loading saves of custom campaign maps.

Reset harvester archived target when given a direct harvest order.

Prevent NOD cargo planes from being force attacked.

Fixed unit selection on load.

Migrated queued repair pad functionality from RA to TD.

Randomly animate infantry in area guard mode.

Fixed crash accessing inactive objects.

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

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

Fixed adjacent cell out-of-bounds crash issues.

Kill player on disconnect

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

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

Fix selection of individual control groups after mixed selection.

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

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

Fix loading of multiple infantry onto an APC.

Additional safety checks for invalid coordinates.

Prevent units from being instantly repaired.

Fix map passability.

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

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

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

Fix buffer overrun crash.

Ignore mines when determining win conditions.

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

750 lines
31 KiB
C++

//
// Copyright 2020 Electronic Arts Inc.
//
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
/* $Header: /CounterStrike/REINF.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 : REINF.CPP *
* *
* Programmer : Joe L. Bostic *
* *
* Start Date : May 24, 1994 *
* *
* Last Update : July 26, 1996 [JLB] *
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* Create_Air_Reinforcement -- Creates air strike reinforcement *
* Create_Special_Reinforcement -- Ad hoc reinforcement handler. *
* Do_Reinforcements -- Create and place a reinforcement team. *
* _Consists_Only_Of_Infantry -- Determine if this group consists only of infantry. *
* _Create_Group -- Create a group given team specification. *
* _Pop_Group_Out_Of_Object -- Process popping the group out of the object. *
* _Who_Can_Pop_Out_Of -- Find a suitable host for these reinforcements. *
* _Need_To_Take -- Examines unit to determine if it should be confiscated. *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "function.h"
/***********************************************************************************************
* _Pop_Group_Out_Of_Object -- Process popping the group out of the object. *
* *
* This routine will cause the group to pop out of the object specified. *
* *
* INPUT: group -- Pointer to the first object in the group to be popped out. *
* *
* object -- Pointer to the object that the group is to pop out of. *
* *
* OUTPUT: bool; Was the group popped out of the specified object? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1996 JLB : Created. *
*=============================================================================================*/
static bool _Pop_Group_Out_Of_Object(FootClass * group, TechnoClass * object)
{
assert(group != NULL && object != NULL);
int quantity = 0;
/*
** Take every infantry member of this group and detach it from the group list
** and then make it pop out of the candidate source.
*/
while (group != NULL) {
TechnoClass * todo = group;
group = (FootClass *)(ObjectClass *)group->Next;
todo->Next = NULL;
switch (object->What_Am_I()) {
/*
** The infantry just walks out of a building.
*/
case RTTI_BUILDING:
if (object->Exit_Object(todo) != 2) {
delete todo;
} else {
++quantity;
}
break;
/*
** Infantry get attached to transport vehicles and then unload.
*/
case RTTI_UNIT:
case RTTI_VESSEL:
case RTTI_AIRCRAFT:
object->Attach((FootClass *)todo);
object->Assign_Mission(MISSION_UNLOAD);
++quantity;
break;
default:
delete todo;
break;
}
}
return (quantity != 0);
}
/***********************************************************************************************
* _Need_To_Take -- Examines unit to determine if it should be confiscated. *
* *
* The unit is examined and if the owning house needs to confiscate it, then this routine *
* will return TRUE. In other cases, the unit should be left to its own devices. *
* *
* INPUT: unit -- Pointer to the object to examine. *
* *
* OUTPUT: bool; Should the object be confiscated by the player so that it becomes one of *
* his normal game objects? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/26/1996 JLB : Created. *
*=============================================================================================*/
bool _Need_To_Take(AircraftClass const * air)
{
if (*air == AIRCRAFT_YAK || *air == AIRCRAFT_MIG) {
int deficit = air->House->Get_Quantity(STRUCT_AIRSTRIP);
// int deficit = air->House->Get_Quantity(STRUCT_AIRSTRIP) - (air->House->Get_Quantity(AIRCRAFT_YAK)+air->House->Get_Quantity(AIRCRAFT_MIG));
/*
** Loop through all aircraft and subtract all the ones that are NOT loaners.
*/
for (int index = 0; index < Aircraft.Count(); index++) {
AircraftClass const * airptr = Aircraft.Ptr(index);
if ((*airptr == AIRCRAFT_YAK || *airptr == AIRCRAFT_MIG) && airptr->IsOwnedByPlayer && !airptr->IsALoaner && airptr != air) {
deficit -= 1;
if (deficit == 0) break;
}
}
if (deficit > 0) return(true);
}
return(false);
}
/***********************************************************************************************
* _Create_Group -- Create a group given team specification. *
* *
* This routine will create all members of the group as specified by the team type. *
* *
* INPUT: teamtype -- Pointer to the team type that specifies what objects should be *
* created in this group. *
* *
* OUTPUT: Returns with a pointer to the first member of the group created. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1996 JLB : Created. *
*=============================================================================================*/
static FootClass * _Create_Group(TeamTypeClass const * teamtype)
{
assert(teamtype != NULL);
TeamClass * team = new TeamClass(teamtype);
if (team != NULL) {
team->Force_Active();
}
bool hasunload = false;
for (int tm = 0; tm < teamtype->MissionCount; tm++) {
if (teamtype->MissionList[tm].Mission == TMISSION_UNLOAD) {
hasunload = true;
break;
}
}
/*
** Now that the official source for the reinforcement has been determined, the
** objects themselves must be created.
*/
FootClass * transport = NULL;
FootClass * object = NULL;
for (int index = 0; index < teamtype->ClassCount; index++) {
TechnoTypeClass const * tclass = teamtype->Members[index].Class;
for (int sub = 0; sub < teamtype->Members[index].Quantity; sub++) {
ScenarioInit++;
FootClass * temp = (FootClass *)tclass->Create_One_Of(HouseClass::As_Pointer(teamtype->House));
ScenarioInit--;
if (temp != NULL) {
/*
** Add the member to the team.
*/
if (team != NULL) {
ScenarioInit++;
bool ok = team->Add(temp);
//Mono_Printf("Added to team = %d.\n", ok);Keyboard->Get();
ScenarioInit--;
temp->IsInitiated = true;
}
if (temp->What_Am_I() == RTTI_AIRCRAFT && !_Need_To_Take((AircraftClass const *)temp)) {
temp->IsALoaner = true;
}
/*
** Build the list of transporters and passengers.
*/
if (tclass->Max_Passengers() > 0) {
/*
** Link to the list of transports.
*/
temp->Next = transport;
transport = temp;
} else {
/*
** Link to the list of normal objects.
*/
temp->Next = object;
object = temp;
}
}
}
}
/*
** If the group consists of transports and normal objects, then assign the normal
** objects to be passengers on the transport.
*/
if (transport != NULL && object != NULL) {
transport->Attach(object);
/*
** HACK ALERT! If the this team has an unload mission, then flag the transport
** as a loaner so that it will exit from the map when the unload process is
** complete, but only if the transport is an aircraft type.
*/
if (hasunload && (transport->What_Am_I() == RTTI_AIRCRAFT || transport->What_Am_I() == RTTI_VESSEL)) {
transport->IsALoaner = true;
}
}
/*
** For JUST transport helicopters, consider the loaner a gift if there are
** no passengers.
*/
if (transport != NULL && object == NULL && transport->What_Am_I() == RTTI_AIRCRAFT && *((AircraftClass *)transport) == AIRCRAFT_TRANSPORT) {
transport->IsALoaner = false;
}
if (transport == 0 && object == 0) {
if (team != NULL) delete team;
return(NULL);
}
/*
** If this group consists only of non-transport object, then just return with a pointer
** to the first member of the group.
*/
if (transport == NULL) {
return(object);
}
return(transport);
}
/***********************************************************************************************
* _Consists_Only_Of_Infantry -- Determine if this group consists only of infantry. *
* *
* Use this to determine if the specified group only contains infantry. Such a reinforcement*
* group is a candidate for popping out of a building or transport vehicle rather than *
* driving/walking/sailing/flying onto the map under its own power. *
* *
* INPUT: first -- Pointer to the first object in the group to examine. *
* *
* OUTPUT: bool; Is the entire group composed of infantry type units? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1996 JLB : Created. *
*=============================================================================================*/
static bool _Consists_Only_Of_Infantry(FootClass const * first)
{
while (first != NULL) {
if (first->What_Am_I() != RTTI_INFANTRY) {
return(false);
}
first = (FootClass const *)((ObjectClass *)first->Next);
}
return(true);
}
/***********************************************************************************************
* _Who_Can_Pop_Out_Of -- Find a suitable host for these reinforcements. *
* *
* This routine is used to scan nearby locations to determine if there is a suitable host *
* for these reinforcements to "pop out of" (apologies to Aliens). Typical hosts include *
* buildings and transport vehicles (of any kind). *
* *
* INPUT: origin -- The cell that should be scanned from. Only this location and immediate *
* adjacent locations will be scanned. *
* *
* OUTPUT: Returns with a pointer to a suitable host. If none could be found then NULL is *
* returned. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1996 JLB : Created. *
*=============================================================================================*/
static TechnoClass * _Who_Can_Pop_Out_Of(CELL origin)
{
CellClass * cellptr = &Map[origin];
TechnoClass * candidate = NULL;
for (int f = -1; f < 8; f++) {
CellClass * ptr = cellptr;
if (f != -1) {
ptr = ptr->Adjacent_Cell(FacingType(f));
if (!ptr) continue;
}
BuildingClass * building = ptr->Cell_Building();
if (building && building->Strength > 0) {
candidate = building;
}
UnitClass * unit = ptr->Cell_Unit();
if (unit && unit->Strength && unit->Class->Max_Passengers() > 0) {
return(unit);
}
}
return(candidate);
}
/***********************************************************************************************
* Do_Reinforcements -- Create and place a reinforcement team. *
* *
* This routine is called when a reinforcement team must be created and placed on the map. *
* It will create all members of the team and place them at the location determined from *
* the team composition. The reinforcement team should follow team orders until overridden *
* by AI or player intervention. *
* *
* INPUT: teamtype -- Pointer to the team type to create as a reinforcement. *
* *
* OUTPUT: Was the reinforcement successfully created and placed? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/08/1995 JLB : Created. *
* 05/18/1995 JLB : Returns success or failure condition. *
* 06/19/1995 JLB : Announces reinforcements. *
* 02/15/1996 JLB : Recognizes team reinforcement location. *
*=============================================================================================*/
bool Do_Reinforcements(TeamTypeClass const * teamtype)
{
assert(teamtype != 0);
/*
** perform some preliminary checks for validity.
*/
if (!teamtype || !teamtype->ClassCount) return(false);
/*
** HACK ALERT!
** Give this team an attack waypoint mission that will attack the waypoint location of this
** team if there are no team missions previously assigned.
*/
if (teamtype->MissionCount == 0) {
TeamTypeClass * tt = (TeamTypeClass *)teamtype;
tt->MissionCount = 1;
tt->MissionList[0].Mission = TMISSION_ATT_WAYPT;
tt->MissionList[0].Data.Value = teamtype->Origin;
}
FootClass * object = _Create_Group(teamtype);
//Mono_Printf("%d-%s (object=%p, team=%d).\n", __LINE__, __FILE__, object, object->Team.Is_Valid());Keyboard->Get();
/*
** Bail on this reinforcement if no reinforcements could be created.
** This is probably because the object maximum was reached.
*/
if (!object) {
return(false);
}
/*
** Special case code to handle infantry types that run from a building. This presumes
** that infantry are never a transport (which is safe to do).
*/
if (object != NULL && teamtype->Origin != -1 && _Consists_Only_Of_Infantry(object)) {
/*
** Search for an object that these infantry can pop out of.
*/
TechnoClass * candidate = _Who_Can_Pop_Out_Of(Scen.Waypoint[teamtype->Origin]);
if (candidate != NULL) {
return(_Pop_Group_Out_Of_Object(object, candidate));
}
}
/*
** The reinforcements must be delivered the old fashioned way -- by moving onto the
** map using their own power. First order of business is to determine where they
** should arrive from.
*/
SourceType source = HouseClass::As_Pointer(teamtype->House)->Control.Edge;
if (source == SOURCE_NONE) {
source = SOURCE_NORTH;
}
/*
** Pick the location where the reinforcements appear and then place
** them there.
*/
bool placed = false;
FacingType eface = (FacingType)(source << 1); // Facing to enter map.
CELL cell = Map.Calculated_Cell(source, teamtype->Origin, -1, object->Techno_Type_Class()->Speed);
#ifdef FIXIT_ANTS
/*
** For the ants, they will pop out of the ant hill directly.
*/
if (teamtype->Origin != -1 && object->What_Am_I() == RTTI_UNIT &&
(*((UnitClass*)object) == UNIT_ANT1 ||
*((UnitClass*)object) == UNIT_ANT2 ||
*((UnitClass*)object) == UNIT_ANT3)) {
CELL newcell = Scen.Waypoint[teamtype->Origin];
if (newcell != -1) {
if (Map[newcell].TType == TEMPLATE_HILL01) {
cell = newcell;
}
}
}
#endif
CELL newcell = cell;
FootClass * o = (FootClass *)(ObjectClass *)object->Next;
object->Next = 0;
bool okvoice = false;
while (newcell > 0 && object != NULL) {
DirType desiredfacing = Facing_Dir(eface);
if (object->What_Am_I() == RTTI_AIRCRAFT) {
desiredfacing = Random_Pick(DIR_N, DIR_MAX);
}
ScenarioInit++;
if (object->Unlimbo(Cell_Coord(newcell), desiredfacing)) {
okvoice = true;
/*
** If this object is part of a team, then the mission for this
** object will be guard. The team handler will assign the proper
** mission that it should follow.
*/
if (object->What_Am_I() != RTTI_AIRCRAFT) {
object->Assign_Mission(MISSION_GUARD);
object->Commence();
}
} else {
/*
** Could not unlimbo at location specified so find an adjacent location that it can
** be unlimboed at. If this fails, then abort the whole placement process.
*/
FacingType adj;
for (adj = FACING_N; adj < FACING_COUNT; adj++) {
CELL trycell = Adjacent_Cell(newcell, adj);
if (!Map.In_Radar(trycell) && object->Can_Enter_Cell(trycell, adj) == MOVE_OK) {
newcell = trycell;
break;
}
}
if (adj < FACING_COUNT) continue;
newcell = 0;
}
ScenarioInit--;
object = o;
if (object != NULL) {
o = (FootClass *)(ObjectClass *)object->Next;
object->Next = 0;
}
}
/*
** If there are still objects that could not be placed, then delete them.
*/
if (o != NULL) {
while (o != NULL) {
FootClass * old = o;
o = (FootClass *)(ObjectClass *)o->Next;
old->Next = 0;
delete old;
}
}
/*
** Announce when the reinforcements have arrived.
*/
if (okvoice && teamtype->House == PlayerPtr->Class->House) {
Speak(VOX_REINFORCEMENTS, NULL, newcell ? Cell_Coord(newcell) : 0);
}
return(true);
}
/***********************************************************************************************
* Create_Special_Reinforcement -- Ad hoc reinforcement handler. *
* *
* Use this routine to bring on a reinforcement that hasn't been anticipated by the trigger *
* system. An example of this would be replacement harvesters or airfield ordered units. *
* The appropriate transport is created (if necessary) and a mission is assigned such that *
* the object will legally bring itself onto the playing field. *
* *
* INPUT: house -- The owner of this reinforcement. *
* *
* type -- The object to bring on. *
* *
* another -- This is reserved for the transport class in those cases where the *
* transport MUST be forced to a specific type. *
* *
* mission -- The mission to assign this reinforcement team. *
* *
* argument -- Optional team mission argument (usually a waypoint). *
* *
* OUTPUT: Was the special reinforcement created without error? *
* *
* WARNINGS: This routine will fail if a team type cannot be created. *
* *
* HISTORY: *
* 07/04/1995 JLB : Created. *
*=============================================================================================*/
bool Create_Special_Reinforcement(HouseClass * house, TechnoTypeClass const * type, TechnoTypeClass const * another, TeamMissionType mission, int argument)
{
assert(house != 0);
assert(type != 0);
if (house && type) {
TeamTypeClass * team = new TeamTypeClass();
if (team) {
/*
** If there is no overridden mission assign to this special reinforcement, then
** we must assign something. If not, the reinforcement will just sit at the edge
** of the map.
*/
if (!another && mission == TMISSION_NONE) {
mission = TMISSION_MOVECELL;
argument = Map.Calculated_Cell(house->Control.Edge);
}
/*
** Fill in the team characteristics.
*/
strcpy((char *)&team->IniName[0], "TEMP");
team->IsReinforcable = false;
team->IsTransient = true;
team->ClassCount = 1;
team->Members[0].Class = type;
team->Members[0].Quantity = 1;
team->MissionCount = 1;
if (mission == TMISSION_NONE) {
team->MissionList[0].Mission = TMISSION_UNLOAD;
team->MissionList[0].Data.Value = WAYPT_REINF;
} else {
team->MissionList[0].Mission = mission;
team->MissionList[0].Data.Value = argument;
}
team->House = house->Class->House;
if (another) {
team->ClassCount++;
team->Members[1].Class = another;
team->Members[1].Quantity = 1;
}
bool ok = Do_Reinforcements(team);
if (!ok) delete team;
return(ok);
}
}
return(false);
}
/***********************************************************************************************
* Create_Air_Reinforcement -- Creates air strike reinforcement *
* *
* This routine is used to launch an airstrike. It will create the necessary aircraft and *
* assign them to attack the target specified. This routine bypasses the normal *
* reinforcement logic since it doesn't need the sophistication of unloading and following *
* team mission lists. *
* *
* INPUT: house -- The perpetrator of this air strike. *
* *
* air -- The type of aircraft to make up this airstrike. *
* *
* number -- The number of aircraft in this airstrike. *
* *
* mission -- The mission to assign the aircraft. *
* *
* tarcom -- The target to assign these aircraft. *
* *
* navcom -- The navigation target to assign (if necessary). *
* *
* OUTPUT: Returns the number of aircraft created for this airstrike. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/04/1995 JLB : Commented. *
*=============================================================================================*/
int Create_Air_Reinforcement(HouseClass * house, AircraftType air, int number, MissionType mission, TARGET tarcom, TARGET navcom, InfantryType passenger)
{
assert(house != 0);
assert((unsigned)air < AIRCRAFT_COUNT);
assert(number != 0);
assert((unsigned)mission < MISSION_COUNT);
/*
** Get a pointer to the class of the object that we are going to create.
*/
TechnoTypeClass const * type = (TechnoTypeClass *)&AircraftTypeClass::As_Reference(air);
/*
** Abort the airstrike if Tanya is the passenger and she's dead.
*/
if (passenger == INFANTRY_TANYA && IsTanyaDead) {
number = 0;
}
/*
** Loop through the number of objects we are supposed to create and
** create and place them on the map.
*/
int sub;
for (sub = 0; sub < number; sub++) {
/*
** Create one of the required objects. If this fails we could have
** a real problem.
*/
ScenarioInit++;
TechnoClass * obj = (TechnoClass *)type->Create_One_Of(house);
ScenarioInit--;
if (!obj) return(sub);
/*
** Flying objects always have the IsALoaner bit set.
*/
obj->IsALoaner = true;
/*
** Find a cell for the object to come in on. This is stolen from the
** the code that handles a SOURCE_AIR in the normal logic.
*/
SourceType source = house->Control.Edge;
switch (source) {
case SOURCE_NORTH:
case SOURCE_EAST:
case SOURCE_SOUTH:
case SOURCE_WEST:
break;
default:
source = SOURCE_NORTH;
break;
}
CELL newcell = Map.Calculated_Cell(source, -1, -1, SPEED_WINGED);
/*
** Try and place the object onto the map.
*/
ScenarioInit++;
int placed = obj->Unlimbo(Cell_Coord(newcell), DIR_N);
ScenarioInit--;
if (placed) {
/*
** If we succeeded in placing the obj onto the map then
** now we need to give it a mission and destination.
*/
obj->Assign_Mission(mission);
/*
** If a navcom was specified then set it.
*/
if (navcom != TARGET_NONE) {
obj->Assign_Destination(navcom);
}
/*
** If a tarcom was specified then set it.
*/
if (tarcom != TARGET_NONE) {
obj->Assign_Target(tarcom);
}
/*
** Assign generic passenger value here. This value is used to determine
** if this aircraft should drop parachute reinforcements.
*/
if (obj->What_Am_I() == RTTI_AIRCRAFT) {
AircraftClass * aircraft = (AircraftClass *)obj;
if (passenger != INFANTRY_NONE) {
aircraft->Passenger = passenger;
}
// if (Passenger == INFANTRY_TANYA) {
// aircraft->Ammo = 1;
//aircraft->AttacksRemaining = 1;
// }
}
/*
** Start the object into action.
*/
obj->Commence();
} else {
delete obj;
sub--;
return(sub);
}
}
return(sub);
}