// // 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: F:\projects\c&c\vcs\code\reinf.cpv 2.17 16 Oct 1995 16:48:58 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 4, 1995 [JLB] * * * *---------------------------------------------------------------------------------------------* * Functions: * * Create_Air_Reinforcement -- Creates air strike reinforcement * * Create_Special_Reinforcement -- Ad hoc reinforcement handler. * * Do_Reinforcements -- Create and place a reinforcement team. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "function.h" /*********************************************************************************************** * 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 overriden * * 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. * *=============================================================================================*/ bool Do_Reinforcements(TeamTypeClass *teamtype) { /* ** preform some preliminary checks for validity. */ if (!teamtype || !teamtype->ClassCount) return(false); /* ** Create the controlling team. All the objects are grouped ** under this team control. If there are no missions for this team ** then don't actually create the team -- it won't serve a purpose. */ TeamClass * team = NULL; if (teamtype->MissionCount) { team = new TeamClass(teamtype, HouseClass::As_Pointer(teamtype->House)); if (!team) return(false); team->Force_Active(); } /* ** Determine if this team contains its own transport. In such a case, the ** transport is used as a loaner. This is only true, if there is other ** objects to be transport. Without such cargo objects, then the transport ** is presumed to be the reinforcement itself and thus should not be a ** loaner. */ bool okvoice = true; // Presume ok to announce reinforcement? bool airtransport = false; // Transport can fly in? bool watertransport = false; // Transport needs a beach to land at? bool onlytransport = true; // Just transport is in reinforcement? bool hastransport = false; // Group comes with transport? int index; for (index=0; index < teamtype->ClassCount; index++) { if (teamtype->Class[index]->IsTransporter || teamtype->Class[index]->What_Am_I() == RTTI_AIRCRAFTTYPE) { hastransport = true; if (teamtype->Class[index]->What_Am_I() == RTTI_AIRCRAFTTYPE) { airtransport = true; } else { watertransport = (((UnitTypeClass const *)teamtype->Class[index])->Type == UNIT_HOVER); } } else { onlytransport = false; } } /* ** Now determine how the reinforcement should be delivered. This is largely determined ** by whether there is a transport with the reinforcements. */ SourceType source = SOURCE_NONE; if (airtransport) { source = SOURCE_AIR; } else { if (watertransport) { source = SOURCE_BEACH; } else { /* ** Special case for the gunboat. It always arrives according to the shipping source. */ if (teamtype->Class[0]->What_Am_I() == RTTI_UNITTYPE && ((UnitTypeClass const *)teamtype->Class[0])->Type == UNIT_GUNBOAT) { source = SOURCE_SHIPPING; } else { source = HouseClass::As_Pointer(teamtype->House)->Edge; } } } /* ** If we can't determine where the reinforcement should come from, then delete it ** and return a failure condition. */ if (source == SOURCE_NONE) { if (team) delete team; return(false); } /* ** Now that the official source for the reinforcement has been determined, the ** objects themselves must be created. */ TechnoClass * transport = NULL; TechnoClass * object = NULL; for (index = 0; index < teamtype->ClassCount; index++) { TechnoTypeClass const * tclass = teamtype->Class[index]; for (int sub = 0; sub < teamtype->DesiredNum[index]; sub++) { ScenarioInit++; FootClass * temp = (FootClass *)tclass->Create_One_Of(HouseClass::As_Pointer(teamtype->House)); ScenarioInit--; if (temp) { /* ** Add the object to the team. This is true even for the transports. The one ** exception is for the hover lander which never becomes part of the team. */ if (team && (temp->What_Am_I() != RTTI_UNIT || *((UnitClass*)temp) != UNIT_HOVER)) { ScenarioInit++; team->Add(temp); ScenarioInit--; } /* ** Build the list of transporters and passengers. */ if (tclass->IsTransporter && (!airtransport || tclass->What_Am_I() == RTTI_AIRCRAFTTYPE)) { /* ** Transports are considered loaners if they are transporting ** something. They are presumed to only serve as a delivery ** agent. */ if (!onlytransport && temp->What_Am_I() != RTTI_UNIT) { temp->IsALoaner = true; } /* ** Link to the list of transports. */ temp->Next = transport; transport = temp; } else { /* ** A-10s are always considered loaners since the player should ** never be allowed to control them. */ if (temp->What_Am_I() == RTTI_AIRCRAFT && *((AircraftClass *)temp) == AIRCRAFT_A10) { temp->IsALoaner = true; } /* ** Link to the list of normal objects. */ temp->Next = object; object = temp; } } } } /* ** Bail on this reinforcement if no reinforcements could be created. ** This is probably because the object maximum was reached. */ if (!object && !transport) { if (team) delete team; return(false); } /* ** Now it is time to place the objects on the map. If there is a transport, then the ** transported objects must be placed inside the transport at this time as well. */ if (transport) { if (object) { /* ** For cargo planes that carry reinforcements, don't announce arrival ** when the transport is created. The announcement will occur when the ** transport unloads. */ if (transport->What_Am_I() == RTTI_AIRCRAFT && *((AircraftClass *)transport) == AIRCRAFT_CARGO) { okvoice = false; } transport->Attach((FootClass *)object); } object = transport; } /* ** Pick the location where the reinforcements appear and then place ** them there. */ bool placed = false; CELL cell = 0; FacingType eface; switch (source) { case SOURCE_SHIPPING: cell = Map.Calculated_Cell(source, teamtype->House); object->IsALoaner = true; if (object->Unlimbo(Cell_Coord(cell), DIR_W)) { object->Assign_Mission(MISSION_GUARD); object->Assign_Destination(::As_Target(XY_Cell(Map.MapCellX-1, Cell_Y(Coord_Cell(object->Coord)) ))); } else { if (team) delete team; delete object; return(false); } break; case SOURCE_NORTH: case SOURCE_SOUTH: case SOURCE_EAST: case SOURCE_WEST: { eface = (FacingType)(source << 1); // Facing to enter map. if (airtransport) ScenarioInit++; cell = Map.Calculated_Cell(source, teamtype->House); if (airtransport) ScenarioInit--; CELL newcell = cell; FootClass * o = (FootClass *)object->Next; object->Next = 0; bool ok = true; while (newcell > 0 && object) { DirType desiredfacing = Facing_Dir(eface); if (object->What_Am_I() == RTTI_AIRCRAFT) { desiredfacing = Random_Pick(DIR_N, DIR_MAX); } if (ok && object->Unlimbo(Cell_Coord(newcell), desiredfacing)) { /* ** 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) { ok = false; } else { if (team) { object->Assign_Mission(MISSION_GUARD); } else { object->Assign_Mission(MISSION_MOVE); object->Assign_Destination(Adjacent_Cell(newcell, eface)); } object->Commence(); } } else { ok = true; /* ** 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 = -1; } object = o; if (object) { o = (FootClass *)object->Next; object->Next = 0; } } /* ** If there are still objects that could not be placed, then delete them. */ if (o) { while (o) { FootClass * old = o; o = (FootClass *)o->Next; old->Next = 0; delete old; } } } break; /* ** Bring out the aircraft as separate "groups" of one. */ case SOURCE_AIR: { AircraftClass * thisone = (AircraftClass *)object; TARGET target = TARGET_NONE; while (thisone) { AircraftClass * next = (AircraftClass *)thisone->Next; /* ** Find a suitable map entry location. Cargo planes will try to find a cell that ** exactly lines up with the airfield they will unload at. */ COORDINATE newcoord; long reinforcement_delay = -1; ScenarioInit++; newcoord = Cell_Coord(Map.Calculated_Cell(HouseClass::As_Pointer(teamtype->House)->Edge, teamtype->House)); ScenarioInit--; if (*thisone == AIRCRAFT_CARGO) { BuildingClass const * building = thisone->Find_Docking_Bay(STRUCT_AIRSTRIP, false); if (building) { COORDINATE docking_coord = building->Docking_Coord(); const int border_x = Cell_To_Lepton(Map.MapCellX + Map.MapCellWidth) | 0x80; if (Special.ModernBalance) { /* ** Cargo plane takes 5 seconds to reach the airstrip on Normal (1.5x legacy), or (75 / 10) seconds at speed. ** Assumes a 45ms (1000 / 45 ticks per second) service rate. */ const int speed = AircraftTypeClass::As_Reference(AIRCRAFT_CARGO).MaxSpeed; int spawn_x = Coord_X(docking_coord) + ((speed * 1000 * 75) / (45 * 10)); if (spawn_x > border_x) { reinforcement_delay = (spawn_x - border_x) / speed; spawn_x = border_x; } newcoord = XY_Coord(spawn_x, Coord_Y(docking_coord)); } else { newcoord = XY_Coord(border_x, Coord_Y(docking_coord)); } if (teamtype->MissionCount) { teamtype->MissionList[0].Argument = building->As_Target(); } } } thisone->Next = 0; ScenarioInit++; placed = thisone->Unlimbo(newcoord, DIR_W); if (Special.ModernBalance && reinforcement_delay >= 0) { thisone->Set_Reinforcement_Delay(reinforcement_delay); } ScenarioInit--; if (placed) { if (!team) { if (thisone->Class->IsFixedWing) { thisone->Assign_Mission(MISSION_HUNT); if (*thisone == AIRCRAFT_A10) { /* ** Groups of A10s always go after the same target initally. */ if (target == TARGET_NONE) { target = thisone->Greatest_Threat(THREAT_NORMAL); } thisone->Assign_Target(target); } } else { if (thisone->Class->IsTransporter && thisone->Is_Something_Attached()) { thisone->Assign_Mission(MISSION_UNLOAD); } else { thisone->Assign_Mission(MISSION_MOVE); } thisone->Assign_Destination(::As_Target(Map.Calculated_Cell(source, teamtype->House))); } thisone->Commence(); } } else { delete thisone; } thisone = next; } if (!placed && team) delete team; /* ** Fixes bug that can happen if the reinforcement cannot be created. ** This prevent "phantom" teams and team types from being left around. */ if (GameToPlay == GAME_NORMAL && !placed) return(false); } break; case SOURCE_OCEAN: case SOURCE_BEACH: cell = Map.Calculated_Cell(SOURCE_BEACH, teamtype->House); if (cell) { CELL edge = XY_Cell(Cell_X(cell), Map.MapCellY+Map.MapCellHeight); placed = object->Unlimbo(Cell_Coord(edge), DIR_N); if (placed) { if (!team) { object->IsLocked = false; object->Assign_Mission(MISSION_UNLOAD); object->Commence(); object->Assign_Destination(::As_Target(cell)); } } else { if (team) delete team; delete object; return(false); } } break; default: break; } /* ** Announce when the reinforcements have arrived. */ if (okvoice && teamtype->House == PlayerPtr->Class->House) { Speak(VOX_REINFORCEMENTS, NULL, cell ? Cell_Coord(cell) : 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) { if (house && type) { TeamTypeClass * team = new TeamTypeClass(); if (team) { /* ** If a ground based reinforcement is desired, but the edge of the map that the ** reinforcement will arrive at is completely covered with water, then add ** a hover lander for transport. */ if (!another && (type->What_Am_I() == RTTI_UNITTYPE || type->What_Am_I() == RTTI_INFANTRYTYPE)) { /* ** Hover lander reinforcements can only arrive from the south. Yes, this is an ** arbitrary limitation, but that's the way it is (for now). */ if (house->Edge == SOURCE_SOUTH) { bool found = false; for (int index = Map.MapCellX; index < Map.MapCellX+Map.MapCellWidth-1; index++) { CELL cell = XY_Cell(index, Map.MapCellY+Map.MapCellHeight); if (Map[cell].Is_Generally_Clear() && Map[cell-MAP_CELL_W].Is_Generally_Clear()) { found = true; break; } } /* ** No land route was found for the reinforcement to drive itself onto the ** map. Assign it a hover lander. This presumes that if the south edge is ** non drivable, it will have water there instead. Risky, but is not a ** problem with the current C&C maps. */ if (!found) { mission = TMISSION_NONE; another = &UnitTypeClass::As_Reference(UNIT_HOVER); } } if (!another) { mission = TMISSION_MOVECELL; argument = Map.Calculated_Cell(SOURCE_AIR, house->Class->House); } } /* ** 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(SOURCE_AIR, house->Class->House); } /* ** Fill in the team characteristics. */ strcpy((char *)&team->IniName[0], "TEMP"); team->IsReinforcable = false; team->IsTransient = true; team->ClassCount = 1; team->Class[0] = type; team->DesiredNum[0] = 1; team->MissionCount = 1; if (mission == TMISSION_NONE) { if (another && (another->What_Am_I() != RTTI_UNITTYPE || ((UnitTypeClass const *)another)->Type != UNIT_HOVER)) { team->MissionList[0].Mission = TMISSION_UNLOAD; team->MissionList[0].Argument = WAYPT_REINF; } } else { team->MissionList[0].Mission = mission; team->MissionList[0].Argument = argument; } team->House = house->Class->House; if (another) { team->ClassCount++; team->Class[1] = another; team->DesiredNum[1] = 1; } bool ok = Do_Reinforcements(team); if (!ok && GameToPlay == GAME_NORMAL) { 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 purpetrator 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) { /* ** Get a pointer to the class of the object that we are going to create. */ TechnoTypeClass const * type = (TechnoTypeClass *)&AircraftTypeClass::As_Reference(air); /* ** 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->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, house->Class->House); /* ** Try and place the object onto the map. */ ScenarioInit++; int placed = obj->Unlimbo(Cell_Coord(newcell), DIR_N); ScenarioInit--; if (placed) { /* ** If we suceeded 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); } /* ** Start the object into action. */ obj->Commence(); } else { delete obj; sub--; return(sub); } } return(sub); }