// // 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/HOUSE.CPP 4 3/13/97 7:11p Steve_tall $ */ /*********************************************************************************************** *** 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 : HOUSE.CPP * * * * Programmer : Joe L. Bostic * * * * Start Date : May 21, 1994 * * * * Last Update : November 4, 1996 [JLB] * * * *---------------------------------------------------------------------------------------------* * Functions: * * HouseClass::AI -- Process house logic. * * HouseClass::AI_Aircraft -- Determines what aircraft to build next. * * HouseClass::AI_Attack -- Handles offensive attack logic. * * HouseClass::AI_Base_Defense -- Handles maintaining a strong base defense. * * HouseClass::AI_Building -- Determines what building to build. * * HouseClass::AI_Fire_Sale -- Check for and perform a fire sale. * * HouseClass::AI_Infantry -- Determines the infantry unit to build. * * HouseClass::AI_Money_Check -- Handles money production logic. * * HouseClass::AI_Power_Check -- Handle the power situation. * * HouseClass::AI_Unit -- Determines what unit to build next. * * HouseClass::Abandon_Production -- Abandons production of item type specified. * * HouseClass::Active_Add -- Add an object to active duty for this house. * * HouseClass::Active_Remove -- Remove this object from active duty for this house. * * HouseClass::Adjust_Capacity -- Adjusts the house Tiberium storage capacity. * * HouseClass::Adjust_Drain -- Adjust the power drain value of the house. * * HouseClass::Adjust_Power -- Adjust the power value of the house. * * HouseClass::Adjust_Threat -- Adjust threat for the region specified. * * HouseClass::As_Pointer -- Converts a house number into a house object pointer. * * HouseClass::Assign_Handicap -- Assigns the specified handicap rating to the house. * * HouseClass::Attacked -- Lets player know if base is under attack. * * HouseClass::Available_Money -- Fetches the total credit worth of the house. * * HouseClass::Begin_Production -- Starts production of the specified object type. * * HouseClass::Blowup_All -- blows up everything * * HouseClass::Can_Build -- General purpose build legality checker. * * HouseClass::Clobber_All -- removes all objects for this house * * HouseClass::Computer_Paranoid -- Cause the computer players to becom paranoid. * * HouseClass::Debug_Dump -- Dumps the house status data to the mono screen. * * HouseClass::Detach -- Removes specified object from house tracking systems. * * HouseClass::Do_All_To_Hunt -- Send all units to hunt. * * HouseClass::Does_Enemy_Building_Exist -- Checks for enemy building of specified type. * * HouseClass::Expert_AI -- Handles expert AI processing. * * HouseClass::Factory_Count -- Fetches the number of factories for specified type. * * HouseClass::Factory_Counter -- Fetches a pointer to the factory counter value. * * HouseClass::Fetch_Factory -- Finds the factory associated with the object type specified. * * HouseClass::Find_Build_Location -- Finds a suitable building location. * * HouseClass::Find_Building -- Finds a building of specified type. * * HouseClass::Find_Cell_In_Zone -- Finds a legal placement cell within the zone. * * HouseClass::Find_Juicy_Target -- Finds a suitable field target. * * HouseClass::Fire_Sale -- Cause all buildings to be sold. * * HouseClass::Flag_Attach -- Attach flag to specified cell (or thereabouts). * * HouseClass::Flag_Attach -- Attaches the house flag the specified unit. * * HouseClass::Flag_Remove -- Removes the flag from the specified target. * * HouseClass::Flag_To_Die -- Flags the house to blow up soon. * * HouseClass::Flag_To_Lose -- Flags the house to die soon. * * HouseClass::Flag_To_Win -- Flags the house to win soon. * * HouseClass::Get_Quantity -- Fetches the total number of aircraft of the specified type. * * HouseClass::Get_Quantity -- Gets the quantity of the building type specified. * * HouseClass::Harvested -- Adds Tiberium to the harvest storage. * * HouseClass::HouseClass -- Constructor for a house object. * * HouseClass::Init -- init's in preparation for new scenario * * HouseClass::Init_Data -- Initializes the multiplayer color data. * * HouseClass::Is_Allowed_To_Ally -- Determines if this house is allied to make allies. * * HouseClass::Is_Ally -- Checks to see if the object is an ally. * * HouseClass::Is_Ally -- Determines if the specified house is an ally. * * HouseClass::Is_Hack_Prevented -- Is production of the specified type and id prohibted? * * HouseClass::Is_No_YakMig -- Determines if no more yaks or migs should be allowed. * * HouseClass::MPlayer_Defeated -- multiplayer; house is defeated * * HouseClass::Make_Ally -- Make the specified house an ally. * * HouseClass::Make_Enemy -- Make an enemy of the house specified. * * HouseClass::Manual_Place -- Inform display system of building placement mode. * * HouseClass::One_Time -- Handles one time initialization of the house array. * * HouseClass::Place_Object -- Places the object (building) at location specified. * * HouseClass::Place_Special_Blast -- Place a special blast effect at location specified. * * HouseClass::Power_Fraction -- Fetches the current power output rating. * * HouseClass::Production_Begun -- Records that production has begun. * * HouseClass::Read_INI -- Reads house specific data from INI. * * HouseClass::Recalc_Attributes -- Recalcs all houses existence bits. * * HouseClass::Recalc_Center -- Recalculates the center point of the base. * * HouseClass::Refund_Money -- Refunds money to back to the house. * * HouseClass::Remap_Table -- Fetches the remap table for this house object. * * HouseClass::Sell_Wall -- Tries to sell the wall at the specified location. * * HouseClass::Set_Factory -- Assign specified factory to house tracking. * * HouseClass::Silo_Redraw_Check -- Flags silos to be redrawn if necessary. * * HouseClass::Special_Weapon_AI -- Fires special weapon. * * HouseClass::Spend_Money -- Removes money from the house. * * HouseClass::Suggest_New_Building -- Examines the situation and suggests a building. * * HouseClass::Suggest_New_Object -- Determine what would the next buildable object be. * * HouseClass::Suggested_New_Team -- Determine what team should be created. * * HouseClass::Super_Weapon_Handler -- Handles the super weapon charge and discharge logic. * * HouseClass::Suspend_Production -- Temporarily puts production on hold. * * HouseClass::Tally_Score -- Fills in the score system for this round * * HouseClass::Tiberium_Fraction -- Calculates the tiberium fraction of capacity. * * HouseClass::Tracking_Add -- Informs house of new inventory item. * * HouseClass::Tracking_Remove -- Remove object from house tracking system. * * HouseClass::Where_To_Go -- Determines where the object should go and wait. * * HouseClass::Which_Zone -- Determines what zone a coordinate lies in. * * HouseClass::Which_Zone -- Determines which base zone the specified cell lies in. * * HouseClass::Which_Zone -- Determines which base zone the specified object lies in. * * HouseClass::Write_INI -- Writes the house data to the INI database. * * HouseClass::Zone_Cell -- Finds the cell closest to the center of the zone. * * HouseClass::delete -- Deallocator function for a house object. * * HouseClass::new -- Allocator for a house class. * * HouseClass::operator HousesType -- Conversion to HousesType operator. * * HouseClass::~HouseClass -- Default destructor for a house object. * * HouseStaticClass::HouseStaticClass -- Default constructor for house static class. * * HouseClass::AI_Raise_Power -- Try to raise power levels by selling off buildings. * * HouseClass::AI_Raise_Money -- Raise emergency cash by selling buildings. * * HouseClass::Random_Cell_In_Zone -- Find a (technically) legal cell in the zone specified. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "function.h" #include "vortex.h" //#include "WolDebug.h" /* ** New sidebar for GlyphX multiplayer. ST - 8/7/2019 10:10AM */ #include "SidebarGlyphx.h" TFixedIHeapClass HouseClass::BuildChoice; int TFixedIHeapClass::Save(Pipe &) const { return(true); } int TFixedIHeapClass::Load(Straw &) { return(0); } void TFixedIHeapClass::Code_Pointers(void) { } void TFixedIHeapClass::Decode_Pointers(void) { } extern bool RedrawOptionsMenu; /*********************************************************************************************** * HouseClass::operator HousesType -- Conversion to HousesType operator. * * * * This operator will automatically convert from a houses class object into the HousesType * * enumerated value. * * * * INPUT: none * * * * OUTPUT: Returns with the object's HousesType value. * * * * WARNINGS: none * * * * HISTORY: * * 01/23/1995 JLB : Created. * *=============================================================================================*/ HouseClass::operator HousesType(void) const { assert(Houses.ID(this) == ID); return(Class->House); } /*********************************************************************************************** * HouseClass::Tiberium_Fraction -- Calculates the tiberium fraction of capacity. * * * * This will calculate the current tiberium (gold) load as a ratio of the maximum storage * * capacity. * * * * INPUT: none * * * * OUTPUT: Returns the current tiberium storage situation as a ratio of load over capacity. * * * * WARNINGS: none * * * * HISTORY: * * 07/31/1996 JLB : Created. * *=============================================================================================*/ fixed HouseClass::Tiberium_Fraction(void) const { if (Tiberium == 0) { return(0); } return(fixed(Tiberium, Capacity)); } /*********************************************************************************************** * HouseClass::As_Pointer -- Converts a house number into a house object pointer. * * * * Use this routine to convert a house number into the house pointer that it represents. * * A simple index into the Houses template array is not sufficient, since the array order * * is arbitrary. An actual scan through the house object is required in order to find the * * house object desired. * * * * INPUT: house -- The house type number to look up. * * * * OUTPUT: Returns with a pointer to the house object that the house number represents. * * * * WARNINGS: none * * * * HISTORY: * * 01/23/1995 JLB : Created. * *=============================================================================================*/ HouseClass * HouseClass::As_Pointer(HousesType house) { if (house != HOUSE_NONE) { for (int index = 0; index < Houses.Count(); index++) { if (Houses.Ptr(index)->Class->House == house) { return(Houses.Ptr(index)); } } } return(0); } /*********************************************************************************************** * HouseClass::One_Time -- Handles one time initialization of the house array. * * * * This basically calls the constructor for each of the houses in the game. All other * * data specific to the house is initialized when the scenario is loaded. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: Only call this ONCE at the beginning of the game. * * * * HISTORY: * * 12/09/1994 JLB : Created. * *=============================================================================================*/ void HouseClass::One_Time(void) { BuildChoice.Set_Heap(STRUCT_COUNT); } /*********************************************************************************************** * HouseClass::Assign_Handicap -- Assigns the specified handicap rating to the house. * * * * The handicap rating will affect combat, movement, and production for the house. It can * * either make it more or less difficult for the house (controlled by the handicap value). * * * * INPUT: handicap -- The handicap value to assign to this house. The default value for * * a house is DIFF_NORMAL. * * * * OUTPUT: Returns with the old handicap value. * * * * WARNINGS: none * * * * HISTORY: * * 07/09/1996 JLB : Created. * * 10/22/1996 JLB : Uses act like value for multiplay only. * *=============================================================================================*/ DiffType HouseClass::Assign_Handicap(DiffType handicap) { DiffType old = Difficulty; Difficulty = handicap; if (Session.Type != GAME_NORMAL) { HouseTypeClass const * hptr = &HouseTypeClass::As_Reference(ActLike); FirepowerBias = hptr->FirepowerBias * Rule.Diff[handicap].FirepowerBias; GroundspeedBias = hptr->GroundspeedBias * Rule.Diff[handicap].GroundspeedBias * Rule.GameSpeedBias; AirspeedBias = hptr->AirspeedBias * Rule.Diff[handicap].AirspeedBias * Rule.GameSpeedBias; ArmorBias = hptr->ArmorBias * Rule.Diff[handicap].ArmorBias; ROFBias = hptr->ROFBias * Rule.Diff[handicap].ROFBias; CostBias = hptr->CostBias * Rule.Diff[handicap].CostBias; RepairDelay = Rule.Diff[handicap].RepairDelay; BuildDelay = Rule.Diff[handicap].BuildDelay; BuildSpeedBias = hptr->BuildSpeedBias * Rule.Diff[handicap].BuildSpeedBias * Rule.GameSpeedBias; } else { FirepowerBias = Rule.Diff[handicap].FirepowerBias; GroundspeedBias = Rule.Diff[handicap].GroundspeedBias * Rule.GameSpeedBias; AirspeedBias = Rule.Diff[handicap].AirspeedBias * Rule.GameSpeedBias; ArmorBias = Rule.Diff[handicap].ArmorBias; ROFBias = Rule.Diff[handicap].ROFBias; CostBias = Rule.Diff[handicap].CostBias; RepairDelay = Rule.Diff[handicap].RepairDelay; BuildDelay = Rule.Diff[handicap].BuildDelay; BuildSpeedBias = Rule.Diff[handicap].BuildSpeedBias * Rule.GameSpeedBias; } return(old); } #ifdef CHEAT_KEYS void HouseClass::Print_Zone_Stats(int x, int y, ZoneType zone, MonoClass * mono) const { mono->Set_Cursor(x, y); mono->Printf("A:%-5d I:%-5d V:%-5d", ZoneInfo[zone].AirDefense, ZoneInfo[zone].InfantryDefense, ZoneInfo[zone].ArmorDefense); } /*********************************************************************************************** * HouseClass::Debug_Dump -- Dumps the house status data to the mono screen. * * * * This utility function will output the current status of the house class to the mono * * screen. Through this information bugs may be fixed or detected. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 05/31/1994 JLB : Created. * *=============================================================================================*/ void HouseClass::Debug_Dump(MonoClass * mono) const { mono->Set_Cursor(0, 0); mono->Print(Text_String(TXT_DEBUG_HOUSE)); mono->Set_Cursor(1, 1);mono->Printf("[%d]%14.14s", Class->House, Name()); mono->Set_Cursor(20, 1);mono->Printf("[%d]%13.13s", ActLike, HouseTypeClass::As_Reference(ActLike).Name()); mono->Set_Cursor(39, 1);mono->Printf("%2d", Control.TechLevel); mono->Set_Cursor(45, 1);mono->Printf("%2d", Difficulty); mono->Set_Cursor(52, 1);mono->Printf("%2d", State); mono->Set_Cursor(58, 1);mono->Printf("%2d", Blockage); mono->Set_Cursor(65, 1);mono->Printf("%2d", IQ); mono->Set_Cursor(72, 1);mono->Printf("%5d", (long)RepairTimer); mono->Set_Cursor(1, 3);mono->Printf("%08X", AScan); mono->Set_Cursor(10, 3);mono->Printf("%8.8s", (BuildAircraft == AIRCRAFT_NONE) ? " " : AircraftTypeClass::As_Reference(BuildAircraft).Graphic_Name()); mono->Set_Cursor(21, 3);mono->Printf("%3d", CurAircraft); mono->Set_Cursor(27, 3);mono->Printf("%8d", Credits); mono->Set_Cursor(37, 3);mono->Printf("%5d", Power); mono->Set_Cursor(45, 3);mono->Printf("%04X", RadarSpied); mono->Set_Cursor(52, 3);mono->Printf("%5d", PointTotal); mono->Set_Cursor(62, 3);mono->Printf("%5d", (long)TeamTime); mono->Set_Cursor(71, 3);mono->Printf("%5d", (long)AlertTime); mono->Set_Cursor(1, 5);mono->Printf("%08X", BScan); mono->Set_Cursor(10, 5);mono->Printf("%8.8s", (BuildStructure == STRUCT_NONE) ? " " : BuildingTypeClass::As_Reference(BuildStructure).Graphic_Name()); mono->Set_Cursor(21, 5);mono->Printf("%3d", CurBuildings); mono->Set_Cursor(27, 5);mono->Printf("%8d", Tiberium); mono->Set_Cursor(37, 5);mono->Printf("%5d", Drain); mono->Set_Cursor(44, 5);mono->Printf("%16.16s", QuarryName[PreferredTarget]); mono->Set_Cursor(62, 5);mono->Printf("%5d", (long)TriggerTime); mono->Set_Cursor(71, 5);mono->Printf("%5d", (long)BorrowedTime); mono->Set_Cursor(1, 7);mono->Printf("%08X", UScan); mono->Set_Cursor(10, 7);mono->Printf("%8.8s", (BuildUnit == UNIT_NONE) ? " " : UnitTypeClass::As_Reference(BuildUnit).Graphic_Name()); mono->Set_Cursor(21, 7);mono->Printf("%3d", CurUnits); mono->Set_Cursor(27, 7);mono->Printf("%8d", Control.InitialCredits); mono->Set_Cursor(38, 7);mono->Printf("%5d", UnitsLost); mono->Set_Cursor(44, 7);mono->Printf("%08X", Allies); mono->Set_Cursor(71, 7);mono->Printf("%5d", (long)Attack); mono->Set_Cursor(1, 9);mono->Printf("%08X", IScan); mono->Set_Cursor(10, 9);mono->Printf("%8.8s", (BuildInfantry == INFANTRY_NONE) ? " " : InfantryTypeClass::As_Reference(BuildInfantry).Graphic_Name()); mono->Set_Cursor(21, 9);mono->Printf("%3d", CurInfantry); mono->Set_Cursor(27, 9);mono->Printf("%8d", Capacity); mono->Set_Cursor(38, 9);mono->Printf("%5d", BuildingsLost); mono->Set_Cursor(45, 9);mono->Printf("%4d", Radius / CELL_LEPTON_W); mono->Set_Cursor(71, 9);mono->Printf("%5d", (long)AITimer); mono->Set_Cursor(1, 11);mono->Printf("%08X", VScan); mono->Set_Cursor(10, 11);mono->Printf("%8.8s", (BuildVessel == VESSEL_NONE) ? " " : VesselTypeClass::As_Reference(BuildVessel).Graphic_Name()); mono->Set_Cursor(21, 11);mono->Printf("%3d", CurVessels); mono->Set_Cursor(54, 11);mono->Printf("%04X", Coord_Cell(Center)); mono->Set_Cursor(71, 11);mono->Printf("%5d", (long)DamageTime); for (int index = 0; index < ARRAY_SIZE(Scen.GlobalFlags); index++) { mono->Set_Cursor(1+index, 15); if (Scen.GlobalFlags[index] != 0) { mono->Print("1"); } else { mono->Print("0"); } if (index >= 24) break; } if (Enemy != HOUSE_NONE) { char const * name = ""; name = HouseClass::As_Pointer(Enemy)->Name(); mono->Set_Cursor(53, 15);mono->Printf("[%d]%21.21s", Enemy, HouseTypeClass::As_Reference(Enemy).Name()); } Print_Zone_Stats(27, 11, ZONE_NORTH, mono); Print_Zone_Stats(27, 13, ZONE_CORE, mono); Print_Zone_Stats(27, 15, ZONE_SOUTH, mono); Print_Zone_Stats(1, 13, ZONE_WEST, mono); Print_Zone_Stats(53, 13, ZONE_EAST, mono); mono->Fill_Attrib(1, 17, 12, 1, IsActive ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(1, 18, 12, 1, IsHuman ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(1, 19, 12, 1, IsPlayerControl ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(1, 20, 12, 1, IsAlerted ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(1, 21, 12, 1, IsDiscovered ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(1, 22, 12, 1, IsMaxedOut ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(14, 17, 12, 1, IsDefeated ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(14, 18, 12, 1, IsToDie ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(14, 19, 12, 1, IsToWin ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(14, 20, 12, 1, IsToLose ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(14, 21, 12, 1, IsCivEvacuated ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(14, 22, 12, 1, IsRecalcNeeded ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(27, 17, 12, 1, IsVisionary ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(27, 18, 12, 1, IsTiberiumShort ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(27, 19, 12, 1, IsSpied ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(27, 20, 12, 1, IsThieved ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(27, 21, 12, 1, IsGPSActive ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(27, 22, 12, 1, IsStarted ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(40, 17, 12, 1, IsResigner ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(40, 18, 12, 1, IsGiverUpper ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(40, 19, 12, 1, IsBuiltSomething ? MonoClass::INVERSE : MonoClass::NORMAL); mono->Fill_Attrib(40, 20, 12, 1, IsBaseBuilding ? MonoClass::INVERSE : MonoClass::NORMAL); } #endif /*********************************************************************************************** * HouseClass::new -- Allocator for a house class. * * * * This is the allocator for a house class. Since there can be only * * one of each type of house, this is allocator has restricted * * functionality. Any attempt to allocate a house structure for a * * house that already exists, just returns a pointer to the previously * * allocated house. * * * * INPUT: house -- The house to allocate a class object for. * * * * OUTPUT: Returns with a pointer to the allocated class object. * * * * WARNINGS: none * * * * HISTORY: * * 05/22/1994 JLB : Created. * *=============================================================================================*/ void * HouseClass::operator new(size_t) { void * ptr = Houses.Allocate(); if (ptr) { ((HouseClass *)ptr)->IsActive = true; } return(ptr); } /*********************************************************************************************** * HouseClass::delete -- Deallocator function for a house object. * * * * This function marks the house object as "deallocated". Such a * * house object is available for reallocation later. * * * * INPUT: ptr -- Pointer to the house object to deallocate. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 05/22/1994 JLB : Created. * *=============================================================================================*/ void HouseClass::operator delete(void * ptr) { if (ptr) { ((HouseClass *)ptr)->IsActive = false; } Houses.Free((HouseClass *)ptr); } /*********************************************************************************************** * HouseClass::HouseClass -- Constructor for a house object. * * * * This function is the constructor and it marks the house object * * as being allocated. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 05/22/1994 JLB : Created. * *=============================================================================================*/ #define VOX_NOT_READY VOX_NONE HouseClass::HouseClass(HousesType house) : RTTI(RTTI_HOUSE), ID(Houses.ID(this)), Class(HouseTypes.Ptr(house)), Difficulty(Scen.CDifficulty), FirepowerBias(1), GroundspeedBias(1), AirspeedBias(1), ArmorBias(1), ROFBias(1), CostBias(1), BuildSpeedBias(1), RepairDelay(0), BuildDelay(0), ActLike(Class->House), IsHuman(false), WasHuman(false), IsPlayerControl(false), IsStarted(false), IsAlerted(false), IsBaseBuilding(false), IsDiscovered(false), IsMaxedOut(false), IsDefeated(false), IsToDie(false), IsToLose(false), IsToWin(false), IsCivEvacuated(false), IsRecalcNeeded(true), IsVisionary(false), IsTiberiumShort(false), IsSpied(false), IsThieved(false), IsGPSActive(false), IsBuiltSomething(false), IsResigner(false), IsGiverUpper(false), IsParanoid(false), IsToLook(true), IsQueuedMovementToggle(false), DidRepair(false), IQ(Control.IQ), State(STATE_BUILDUP), JustBuiltStructure(STRUCT_NONE), JustBuiltInfantry(INFANTRY_NONE), JustBuiltUnit(UNIT_NONE), JustBuiltAircraft(AIRCRAFT_NONE), JustBuiltVessel(VESSEL_NONE), Blockage(0), RepairTimer(0), AlertTime(0), BorrowedTime(0), BScan(0), ActiveBScan(0), OldBScan(0), UScan(0), ActiveUScan(0), OldUScan(0), IScan(0), ActiveIScan(0), OldIScan(0), AScan(0), ActiveAScan(0), OldAScan(0), VScan(0), ActiveVScan(0), OldVScan(0), CreditsSpent(0), HarvestedCredits(0), StolenBuildingsCredits(0), CurUnits(0), CurBuildings(0), CurInfantry(0), CurVessels(0), CurAircraft(0), Tiberium(0), Credits(0), Capacity(0), AircraftTotals(NULL), InfantryTotals(NULL), UnitTotals(NULL), BuildingTotals(NULL), VesselTotals(NULL), DestroyedAircraft(NULL), DestroyedInfantry(NULL), DestroyedUnits(NULL), DestroyedBuildings(NULL), DestroyedVessels(NULL), CapturedBuildings(NULL), TotalCrates(NULL), AircraftFactories(0), InfantryFactories(0), UnitFactories(0), BuildingFactories(0), VesselFactories(0), Power(0), Drain(0), AircraftFactory(-1), InfantryFactory(-1), UnitFactory(-1), BuildingFactory(-1), VesselFactory(-1), Radar(RADAR_NONE), FlagLocation(TARGET_NONE), FlagHome(0), UnitsLost(0), BuildingsLost(0), WhoLastHurtMe(house), StartLocationOverride(-1), Center(0), Radius(0), LATime(0), LAType(RTTI_NONE), LAZone(ZONE_NONE), LAEnemy(HOUSE_NONE), ToCapture(TARGET_NONE), RadarSpied(0), PointTotal(0), PreferredTarget(QUARRY_ANYTHING), ScreenShakeTime(0), Attack(0), Enemy(HOUSE_NONE), AITimer(0), UnitToTeleport(0), BuildStructure(STRUCT_NONE), BuildUnit(UNIT_NONE), BuildInfantry(INFANTRY_NONE), BuildAircraft(AIRCRAFT_NONE), BuildVessel(VESSEL_NONE), NukeDest(0), Allies(0), DamageTime(TICKS_PER_MINUTE * Rule.DamageDelay), TeamTime(TICKS_PER_MINUTE * Rule.TeamDelay), TriggerTime(0), SpeakAttackDelay(1), SpeakPowerDelay(1), SpeakMoneyDelay(1), SpeakMaxedDelay(1), RemapColor(Class->RemapColor), DebugUnlockBuildables(false) { /* ** Explicit in-place construction of the super weapons is ** required here because the default constructor for super ** weapons must serve as a no-initialization constructor (save/load reasons). */ new (&SuperWeapon[SPC_NUCLEAR_BOMB]) SuperClass(TICKS_PER_MINUTE * Rule.NukeTime, true, VOX_ABOMB_PREPPING, VOX_ABOMB_READY, VOX_NOT_READY, VOX_INSUFFICIENT_POWER); new (&SuperWeapon[SPC_SONAR_PULSE]) SuperClass(TICKS_PER_MINUTE * Rule.SonarTime, false, VOX_NONE, VOX_SONAR_AVAILABLE, VOX_NOT_READY, VOX_NOT_READY); new (&SuperWeapon[SPC_CHRONOSPHERE]) SuperClass(TICKS_PER_MINUTE * Rule.ChronoTime, true, VOX_CHRONO_CHARGING, VOX_CHRONO_READY, VOX_NOT_READY, VOX_INSUFFICIENT_POWER); new (&SuperWeapon[SPC_PARA_BOMB]) SuperClass(TICKS_PER_MINUTE * Rule.ParaBombTime, false, VOX_NONE, VOX_NONE, VOX_NOT_READY, VOX_NOT_READY); new (&SuperWeapon[SPC_PARA_INFANTRY]) SuperClass(TICKS_PER_MINUTE * Rule.ParaInfantryTime, false, VOX_NONE, VOX_NONE, VOX_NOT_READY, VOX_NOT_READY); new (&SuperWeapon[SPC_SPY_MISSION]) SuperClass(TICKS_PER_MINUTE * Rule.SpyTime, false, VOX_NONE, VOX_SPY_PLANE, VOX_NOT_READY, VOX_NOT_READY); new (&SuperWeapon[SPC_IRON_CURTAIN]) SuperClass(TICKS_PER_MINUTE * Rule.IronCurtainTime, true, VOX_IRON_CHARGING, VOX_IRON_READY, VOX_NOT_READY, VOX_INSUFFICIENT_POWER); new (&SuperWeapon[SPC_GPS]) SuperClass(TICKS_PER_MINUTE * Rule.GPSTime, true, VOX_NONE, VOX_NONE, VOX_NOT_READY, VOX_INSUFFICIENT_POWER); memset(UnitsKilled, '\0', sizeof(UnitsKilled)); memset(BuildingsKilled, '\0', sizeof(BuildingsKilled)); memset(BQuantity, '\0', sizeof(BQuantity)); memset(UQuantity, '\0', sizeof(UQuantity)); memset(IQuantity, '\0', sizeof(IQuantity)); memset(AQuantity, '\0', sizeof(AQuantity)); memset(VQuantity, '\0', sizeof(VQuantity)); strcpy(IniName, Text_String(TXT_COMPUTER)); // Default computer name. HouseTriggers[house].Clear(); memset((void *)&Regions[0], 0x00, sizeof(Regions)); Make_Ally(house); Assign_Handicap(Scen.CDifficulty); /* ** Set the time of the first AI attack. */ Attack = Rule.AttackDelay * Random_Pick(TICKS_PER_MINUTE/2, TICKS_PER_MINUTE*2); Init_Unit_Trackers(); } /*********************************************************************************************** * HouseClass::~HouseClass -- House class destructor * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 8/6/96 4:48PM ST : Created * *=============================================================================================*/ HouseClass::~HouseClass (void) { Class = 0; Free_Unit_Trackers(); } /*********************************************************************************************** * HouseStaticClass::HouseStaticClass -- Default constructor for house static class. * * * * This is the default constructor that initializes all the values to their default * * settings. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 07/31/1996 JLB : Created. * *=============================================================================================*/ HouseStaticClass::HouseStaticClass(void) : IQ(0), TechLevel(1), Allies(0), MaxUnit(Rule.UnitMax/6), MaxBuilding(Rule.BuildingMax/6), MaxInfantry(Rule.InfantryMax/6), MaxVessel(Rule.VesselMax/6), MaxAircraft(Rule.UnitMax/6), InitialCredits(0), Edge(SOURCE_NORTH) { } /*********************************************************************************************** * HouseClass::Can_Build -- General purpose build legality checker. * * * * This routine is called when it needs to be determined if the specified object type can * * be built by this house. Production and sidebar maintenance use this routine heavily. * * * * INPUT: type -- Pointer to the type of object that legality is to be checked for. * * * * house -- This is the house to check for legality against. Note that this might * * not be 'this' house since the check could be from a captured factory. * * Captured factories build what the original owner of them could build. * * * * OUTPUT: Can the specified object be built? * * * * WARNINGS: none * * * * HISTORY: * * 07/04/1995 JLB : Created. * * 08/12/1995 JLB : Updated for GDI building sandbag walls in #9. * * 10/23/1996 JLB : Hack to allow Tanya to both sides in multiplay. * * 11/04/1996 JLB : Computer uses prerequisite record. * *=============================================================================================*/ bool HouseClass::Can_Build(ObjectTypeClass const * type, HousesType house) const { assert(Houses.ID(this) == ID); assert(type != NULL); /* ** An object with a prohibited tech level availability will never be allowed, regardless ** of who requests it. */ if (((TechnoTypeClass const *)type)->Level == -1) return(false); #ifdef FIXIT_CSII // checked - ajw 9/28/98 /* ** If this is a CounterStrike II-only unit, and we're playing a multiplayer ** game in 'downshifted' mode against CounterStrike or Red Alert, then ** don't allow building this unit. */ if (!NewUnitsEnabled) { switch(type->What_Am_I()) { case RTTI_INFANTRYTYPE: if ( ((InfantryTypeClass *)type)->ID >= INFANTRY_RA_COUNT) return(false); break; case RTTI_UNITTYPE: if ( ((UnitTypeClass *)type)->ID >= UNIT_RA_COUNT) return(false); break; case RTTI_VESSELTYPE: if ( ((VesselTypeClass *)type)->ID >= VESSEL_RA_COUNT) return(false); break; default: break; } } #endif /* ** The computer can always build everything. */ if (!IsHuman && Session.Type == GAME_NORMAL) return(true); /* ** Special hack to get certain objects to exist for both sides in the game. */ int own = type->Get_Ownable(); /* ** Check to see if this owner can build the object type specified. */ if (((1L << house) & own) == 0) { return(false); } /* ** Perform some equivalency fixups for the building existence flags. */ long flags = ActiveBScan; /* ** The computer records prerequisite buildings because it can't relay on the ** sidebar to keep track of this information. */ if (!IsHuman) { flags = OldBScan; } int pre = ((TechnoTypeClass const *)type)->Prerequisite; /* ** Advanced power also serves as a prerequisite for normal power. */ if (flags & STRUCTF_ADVANCED_POWER) flags |= STRUCTF_POWER; /* ** Either tech center counts as a prerequisite. */ if (Session.Type != GAME_NORMAL) { if ((flags & (STRUCTF_SOVIET_TECH|STRUCTF_ADVANCED_TECH)) != 0) flags |= STRUCTF_SOVIET_TECH|STRUCTF_ADVANCED_TECH; } int level = Control.TechLevel; #ifdef CHEAT_KEYS if (Debug_Cheat) { level = 98; pre = 0; } #endif // ST - 8/23/2019 4:53PM if (DebugUnlockBuildables) { level = 98; pre = 0; } /* ** See if the prerequisite requirements have been met. */ return((pre & flags) == pre && ((TechnoTypeClass const *)type)->Level <= (unsigned)level); } /*************************************************************************** * HouseClass::Init -- init's in preparation for new scenario * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 12/07/1994 BR : Created. * * 12/17/1994 JLB : Resets tracker bits. * *=========================================================================*/ void HouseClass::Init(void) { Houses.Free_All(); for (HousesType index = HOUSE_FIRST; index < HOUSE_COUNT; index++) { HouseTriggers[index].Clear(); } } // Object selection list is switched with player context for GlyphX. ST - 8/7/2019 10:11AM extern void Logic_Switch_Player_Context(HouseClass *house); extern bool MPSuperWeaponDisable; /*********************************************************************************************** * HouseClass::AI -- Process house logic. * * * * This handles the AI for the house object. It should be called once per house per game * * tick. It processes all house global tasks such as low power damage accumulation and * * house specific trigger events. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 12/27/1994 JLB : Created. * * 07/17/1995 JLB : Limits EVA speaking unless the player can do something. * *=============================================================================================*/ extern void Recalculate_Placement_Distances(); void HouseClass::AI(void) { assert(Houses.ID(this) == ID); // Set PlayerPtr to be this house. ST - 8/7/2019 10:12AM Logic_Switch_Player_Context(this); /* ** If base building has been turned on by a trigger, then force the house to begin ** production and team creation as well. This is also true if the IQ is high enough to ** being base building. */ if (!IsHuman && (IsBaseBuilding || IQ >= Rule.IQProduction)) { IsBaseBuilding = true; IsStarted = true; IsAlerted = true; } /* ** Check to see if the house wins. */ if (Session.Type == GAME_NORMAL && IsToWin && BorrowedTime == 0 && Blockage <= 0) { IsToWin = false; if (this == PlayerPtr) { PlayerWins = true; } else { PlayerLoses = true; } } /* ** Check to see if the house loses. */ if (Session.Type == GAME_NORMAL && IsToLose && BorrowedTime == 0) { IsToLose = false; if (this == PlayerPtr) { PlayerLoses = true; } else { PlayerWins = true; } } /* ** Check to see if all objects of this house should be blown up. */ if (IsToDie && BorrowedTime == 0) { IsToDie = false; Blowup_All(); if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { MPlayer_Defeated(); } } /* ** Double check power values to correct illegal conditions. It is possible to ** get a power output of negative (one usually) as a result of damage sustained ** and the fixed point fractional math involved with power adjustments. If the ** power rating drops below zero, then make it zero. */ Power = max(Power, 0); Drain = max(Drain, 0); /* ** If the base has been alerted to the enemy and should be attacking, then ** see if the attack timer has expired. If it has, then create the attack ** teams. */ if (IsAlerted && AlertTime == 0) { /* ** Adjusted to reduce maximum number of teams created. */ int maxteams = Random_Pick(2, (int)(((Control.TechLevel-1)/3)+1)); for (int index = 0; index < maxteams; index++) { TeamTypeClass const * ttype = Suggested_New_Team(true); if (ttype != NULL) { ScenarioInit++; ttype->Create_One_Of(); ScenarioInit--; } } AlertTime = Rule.AutocreateTime * Random_Pick(TICKS_PER_MINUTE/2, TICKS_PER_MINUTE*2); // int mintime = Rule.AutocreateTime * (TICKS_PER_MINUTE/2); // int maxtime = Rule.AutocreateTime * (TICKS_PER_MINUTE*2); // AlertTime = Random_Pick(mintime, maxtime); } /* ** If this house's flag waypoint is a valid cell, see if there's ** someone sitting on it. If so, make the scatter. */ if (FlagHome != 0 && (Frame % TICKS_PER_SECOND) == 0) { TechnoClass * techno = Map[FlagHome].Cell_Techno(); if (techno != NULL) { bool moving = false; if (techno->Is_Foot()) { if (Target_Legal(((FootClass *)techno)->NavCom)) { moving = true; } } if (!moving) { techno->Scatter(0, true, true); } } } /* ** Create teams for this house if necessary. ** (Use the same timer for some extra capture-the-flag logic.) */ if (!IsAlerted && !TeamTime) { TeamTypeClass const * ttype = Suggested_New_Team(false); if (ttype) { ttype->Create_One_Of(); } TeamTime = Rule.TeamDelay * TICKS_PER_MINUTE; } /* ** If there is insufficient power, then all buildings that are above ** half strength take a little bit of damage. */ if (DamageTime == 0) { /* ** When the power is below required, then the buildings will take damage over ** time. */ if (Power_Fraction() < 1) { for (int index = 0; index < Buildings.Count(); index++) { BuildingClass & b = *Buildings.Ptr(index); if (b.House == this && b.Health_Ratio() > Rule.ConditionYellow) { // BG: Only damage buildings that require power, to keep the // land mines from blowing up under low-power conditions if (b.Class->Drain) { int damage = 1; b.Take_Damage(damage, 0, WARHEAD_AP, 0); } } } } DamageTime = TICKS_PER_MINUTE * Rule.DamageDelay; } /* ** If there are no more buildings to sell, then automatically cancel the ** sell mode. */ if (PlayerPtr == this && !ActiveBScan && Map.IsSellMode) { Map.Sell_Mode_Control(0); } /* ** Various base conditions may be announced to the player. Typically, this would be ** low tiberium capacity or low power. */ if (PlayerPtr == this) { if (SpeakMaxedDelay == 0 && Available_Money() < 100 && UnitFactories+BuildingFactories+InfantryFactories > 0) { Speak(VOX_NEED_MO_MONEY); Map.Flash_Money(); SpeakMaxedDelay = Options.Normalize_Delay(TICKS_PER_MINUTE * Rule.SpeakDelay); int text_id = TXT_INSUFFICIENT_FUNDS; char const * text = Text_String(TXT_INSUFFICIENT_FUNDS); if (text != NULL) { Session.Messages.Add_Message(NULL, text_id, text, PCOLOR_GREEN, TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, Rule.MessageDelay * TICKS_PER_MINUTE); } } if (SpeakMaxedDelay == 0 && IsMaxedOut) { IsMaxedOut = false; if ((Capacity - Tiberium) < 300 && Capacity > 500 && (ActiveBScan & (STRUCTF_REFINERY | STRUCTF_CONST))) { Speak(VOX_NEED_MO_CAPACITY); SpeakMaxedDelay = Options.Normalize_Delay(TICKS_PER_MINUTE * Rule.SpeakDelay); } } if (SpeakPowerDelay == 0 && Power_Fraction() < 1) { if (ActiveBScan & STRUCTF_CONST) { Speak(VOX_LOW_POWER); SpeakPowerDelay = Options.Normalize_Delay(TICKS_PER_MINUTE * Rule.SpeakDelay); Map.Flash_Power(); int text_id = -1; char const * text = NULL; if (BQuantity[STRUCT_AAGUN] > 0) { text = Text_String(TXT_POWER_AAGUN); text_id = TXT_POWER_AAGUN; } if (BQuantity[STRUCT_TESLA] > 0) { text = Text_String(TXT_POWER_TESLA); text_id = TXT_POWER_TESLA; } if (text == NULL) { text = Text_String(TXT_LOW_POWER); text_id = TXT_LOW_POWER; } if (text != NULL) { Session.Messages.Add_Message(NULL, text_id, text, PCOLOR_GREEN, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_FULLSHADOW, Rule.MessageDelay * TICKS_PER_MINUTE); } } } } /* ** If there is a flag associated with this house, then mark it to be ** redrawn. */ if (Target_Legal(FlagLocation)) { UnitClass * unit = As_Unit(FlagLocation); if (unit) { unit->Mark(MARK_CHANGE); } else { CELL cell = As_Cell(FlagLocation); Map[cell].Flag_Update(); Map[cell].Redraw_Objects(); } } bool is_time = false; /* ** Triggers are only checked every so often. If the trigger timer has expired, ** then set the trigger processing flag. */ if (TriggerTime == 0 || IsBuiltSomething) { is_time = true; TriggerTime = TICKS_PER_MINUTE/10; IsBuiltSomething = false; } /* ** Process any super weapon logic required. */ if (Session.Type != GAME_GLYPHX_MULTIPLAYER || !MPSuperWeaponDisable) { Super_Weapon_Handler(); } #ifdef FIXIT_VERSION_3 // For endgame auto-sonar pulse. if( (Session.Type != GAME_NORMAL || !IsHuman) && Scen.AutoSonarTimer == 0 ) { // If house has nothing but subs left, do an automatic sonar pulse to reveal them. if( VQuantity[ VESSEL_SS ] > 0 ) // Includes count of VESSEL_MISSILESUBs. ajw { int iCount = 0; int i; for( i = 0; i != STRUCT_COUNT-3; ++i ) { iCount += BQuantity[ i ]; } if( !iCount ) { for( i = 0; i != UNIT_RA_COUNT-3; ++i ) { iCount += UQuantity[ i ]; } if( !iCount ) { // ajw - Found bug - house's civilians are not removed from IQuantity when they die. // Workaround... for( i = 0; i <= INFANTRY_DOG; ++i ) { iCount += IQuantity[ i ]; } if( !iCount ) { for( i = 0; i != AIRCRAFT_COUNT; ++i ) { iCount += AQuantity[ i ]; } if( !iCount ) { for( i = 0; i != VESSEL_RA_COUNT; ++i ) { if( i != VESSEL_SS ) iCount += VQuantity[ i ]; } if( !iCount ) { // Do the ping. for (int index = 0; index < Vessels.Count(); index++) { VesselClass * sub = Vessels.Ptr(index); if (*sub == VESSEL_SS || *sub == VESSEL_MISSILESUB) { sub->PulseCountDown = 15 * TICKS_PER_SECOND; sub->Do_Uncloak(); } } bAutoSonarPulse = true; } } } } } } } #endif if (Session.Type != GAME_NORMAL) { Check_Pertinent_Structures(); } /* ** Special win/lose check for multiplayer games; by-passes the ** trigger system. We must wait for non-zero frame, because init ** may not properly set IScan etc for each house; you have to go ** through each object's AI before it will be properly set. */ if (Session.Type != GAME_NORMAL && !IsDefeated && !ActiveBScan && !ActiveAScan && !UScan && !ActiveIScan && !ActiveVScan && Frame > 0) { MPlayer_Defeated(); } /* ** Try to spring all events attached to this house. The triggers will check ** for themselves if they actually need to be sprung or not. */ for (int index = 0; index < HouseTriggers[Class->House].Count(); index++) { if (HouseTriggers[Class->House][index]->Spring() && index > 0) { index--; continue; } } /* ** If a radar facility is not present, but the radar is active, then turn the radar off. ** The radar also is turned off when the power gets below 100% capacity. */ if (PlayerPtr == this) { bool jammed = true; /* ** Find if there are any radar facilities, and if they're jammed or not */ if (IsGPSActive) { jammed = false; } else { for (int index = 0; index < Buildings.Count(); index++) { BuildingClass * building = Buildings.Ptr(index); #ifdef FIXIT_RADAR_JAMMED if (building != NULL && !building->IsInLimbo && building->House == PlayerPtr) { #else if (building && building->House == PlayerPtr) { #endif if (*building == STRUCT_RADAR /* || *building == STRUCT_EYE */) { if (!building->IsJammed) { jammed = false; break; } } } } } Map.Set_Jammed(this, jammed); // Need to add in here where we activate it when only GPS is active. if (Map.Is_Radar_Active()) { if (ActiveBScan & STRUCTF_RADAR) { if (Power_Fraction() < 1 && !IsGPSActive) { Map.Radar_Activate(0); } } else { if (!IsGPSActive) { Map.Radar_Activate(0); } } } else { if (IsGPSActive || (ActiveBScan & STRUCTF_RADAR)) { if (Power_Fraction() >= 1 || IsGPSActive) { Map.Radar_Activate(1); } } else { if (Map.Is_Radar_Existing()) { Map.Radar_Activate(4); } } } if (!IsGPSActive && !(ActiveBScan & STRUCTF_RADAR)) { Radar = RADAR_NONE; } else { Radar = (Map.Is_Radar_Active() || Map.Is_Radar_Activating()) ? RADAR_ON : RADAR_OFF; } } VisibleCredits.AI(false, this, true); /* ** Perform any expert system AI processing. */ if (IsBaseBuilding && AITimer == 0) { AITimer = Expert_AI(); } if (!IsBaseBuilding && State == STATE_ENDGAME) { Fire_Sale(); Do_All_To_Hunt(); } AI_Building(); AI_Unit(); AI_Vessel(); AI_Infantry(); AI_Aircraft(); /* ** If the production possibilities need to be recalculated, then do so now. This must ** occur after the scan bits have been properly updated. */ if (PlayerPtr == this && IsRecalcNeeded) { IsRecalcNeeded = false; Map.Recalc(); /* ** This placement might affect any prerequisite requirements for construction ** lists. Update the buildable options accordingly. */ for (int index = 0; index < Buildings.Count(); index++) { BuildingClass * building = Buildings.Ptr(index); if (building && building->Strength > 0 && building->Owner() == Class->House && building->Mission != MISSION_DECONSTRUCTION && building->MissionQueue != MISSION_DECONSTRUCTION) { if (PlayerPtr == building->House) { building->Update_Buildables(); } } } Recalculate_Placement_Distances(); Check_Pertinent_Structures(); } /* ** See if it's time to re-set the can-repair flag */ if (DidRepair && RepairTimer == 0) { DidRepair = false; } if (this == PlayerPtr && IsToLook) { IsToLook = false; Map.All_To_Look(PlayerPtr); } } /*********************************************************************************************** * HouseClass::Super_Weapon_Handler -- Handles the super weapon charge and discharge logic. * * * * This handles any super weapons assigned to this house. It also performs any necessary * * maintenance that the super weapons require. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 09/17/1996 JLB : Created. * *=============================================================================================*/ void HouseClass::Super_Weapon_Handler(void) { /* ** Perform all super weapon AI processing. This just checks to see if ** the graphic needs changing for the special weapon and updates the ** sidebar as necessary. */ for (SpecialWeaponType special = SPC_FIRST; special < SPC_COUNT; special++) { SuperClass * super = &SuperWeapon[special]; if (super->Is_Present()) { /* ** Perform any charge-up logic for the super weapon. If the super ** weapon is owned by the player and a graphic change is detected, then ** flag the sidebar to be redrawn so the player will see the change. */ if (super->AI(this == PlayerPtr)) { if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw(); } /* ** Repeating super weapons that require power will be suspended if there ** is insufficient power available. */ if (!super->Is_Ready() && super->Is_Powered() && !super->Is_One_Time()) { super->Suspend(Power_Fraction() < 1); } } } /* ** Check to see if they have launched the GPS, but subsequently lost their ** tech center. If so, remove the GPS, and shroud the map. */ if (IsGPSActive && !(ActiveBScan & STRUCTF_ADVANCED_TECH) ) { IsGPSActive = false; /* ** Updated for client/server multiplayer. ST - 8/12/2019 11:32AM */ if (Session.Type != GAME_GLYPHX_MULTIPLAYER) { if (IsPlayerControl) { Map.Shroud_The_Map(PlayerPtr); } } else { if (IsHuman) { Map.Shroud_The_Map(this); } } } /* ** Check to see if the GPS Satellite should be removed from the sidebar ** because of outside circumstances. The advanced technology facility ** being destroyed is a good example of this. Having fired the satellite ** is another good example, because it's a one-shot item. */ if (SuperWeapon[SPC_GPS].Is_Present()) { if (!(ActiveBScan & STRUCTF_ADVANCED_TECH) || IsGPSActive || IsDefeated) { /* ** Remove the missile capability when there is no advanced tech facility. */ if (SuperWeapon[SPC_GPS].Remove()) { if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw(); IsRecalcNeeded = true; } } else { /* ** Auto-fire the GPS satellite if it's charged up. */ if (SuperWeapon[SPC_GPS].Is_Ready()) { SuperWeapon[SPC_GPS].Discharged(this == PlayerPtr); if (SuperWeapon[SPC_GPS].Remove()) { if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw(); } IsRecalcNeeded = true; for (int index = 0; index < Buildings.Count(); index++) { BuildingClass * bldg = Buildings.Ptr(index); if (*bldg == STRUCT_ADVANCED_TECH && bldg->House == this) { bldg->HasFired = true; bldg->Assign_Mission(MISSION_MISSILE); break; } } } } } else { /* ** If there is no GPS satellite present, but there is a GPS satellite ** facility available, then make the GPS satellite available as well. */ if ((ActiveBScan & STRUCTF_ADVANCED_TECH) != 0 && !IsGPSActive && Control.TechLevel >= Rule.GPSTechLevel && (IsHuman || IQ >= Rule.IQSuperWeapons)) { bool canfire = false; for (int index = 0; index < Buildings.Count(); index++) { BuildingClass * bldg = Buildings.Ptr(index); if (*bldg == STRUCT_ADVANCED_TECH && bldg->House == this && !bldg->IsInLimbo) { if (!bldg->HasFired) { canfire = true; break; } } } if (canfire) { SuperWeapon[SPC_GPS].Enable(false, this == PlayerPtr, Power_Fraction() < 1); /* ** Flag the sidebar to be redrawn if necessary. */ // Add to Glyphx multiplayer sidebar. ST - 8/7/2019 10:13AM if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { if (IsHuman) { Sidebar_Glyphx_Add(RTTI_SPECIAL, SPC_GPS, this); } } else { if (this == PlayerPtr) { Map.Add(RTTI_SPECIAL, SPC_GPS); Map.Column[1].Flag_To_Redraw(); } } } } } /* ** Check to see if the chronosphere should be removed from the sidebar ** because of outside circumstances. The chronosphere facility ** being destroyed is a good example of this. */ if (SuperWeapon[SPC_CHRONOSPHERE].Is_Present()) { if ( (!(ActiveBScan & STRUCTF_CHRONOSPHERE) && !SuperWeapon[SPC_CHRONOSPHERE].Is_One_Time()) || IsDefeated) { /* ** Remove the chronosphere when there is no chronosphere facility. ** Note that this will not remove the one time created chronosphere. */ if (SuperWeapon[SPC_CHRONOSPHERE].Remove()) { if (this == PlayerPtr) { #ifdef FIXIT_CSII // checked - ajw 9/28/98 if (Map.IsTargettingMode == SPC_CHRONOSPHERE || Map.IsTargettingMode == SPC_CHRONO2) { if (Map.IsTargettingMode == SPC_CHRONO2) { TechnoClass * tech = (TechnoClass *)::As_Object(UnitToTeleport); if (tech && tech->IsActive && tech->What_Am_I() == RTTI_UNIT && *(UnitClass *)tech == UNIT_CHRONOTANK) { } else { Map.IsTargettingMode = SPC_NONE; } } else { Map.IsTargettingMode = SPC_NONE; } } #else if (Map.IsTargettingMode == SPC_CHRONOSPHERE || Map.IsTargettingMode == SPC_CHRONO2) { Map.IsTargettingMode = SPC_NONE; } #endif Map.Column[1].Flag_To_Redraw(); } IsRecalcNeeded = true; } } } else { /* ** If there is no chronosphere present, but there is a chronosphere ** facility available, then make the chronosphere available as well. */ if ((ActiveBScan & STRUCTF_CHRONOSPHERE) && // (ActLike == HOUSE_GOOD || Session.Type != GAME_NORMAL) && (unsigned)Control.TechLevel >= BuildingTypeClass::As_Reference(STRUCT_CHRONOSPHERE).Level && // Control.TechLevel >= Rule.ChronoTechLevel && (IsHuman || IQ >= Rule.IQSuperWeapons)) { SuperWeapon[SPC_CHRONOSPHERE].Enable(false, this == PlayerPtr, Power_Fraction() < 1); /* ** Flag the sidebar to be redrawn if necessary. */ // Add to Glyphx multiplayer sidebar. ST - 8/7/2019 10:13AM if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { if (IsHuman) { Sidebar_Glyphx_Add(RTTI_SPECIAL, SPC_CHRONOSPHERE, this); } } else { if (this == PlayerPtr) { Map.Add(RTTI_SPECIAL, SPC_CHRONOSPHERE); Map.Column[1].Flag_To_Redraw(); } } } } /* ** Check to see if the iron curtain should be removed from the sidebar ** because of outside circumstances. The iron curtain facility ** being destroyed is a good example of this. */ if (SuperWeapon[SPC_IRON_CURTAIN].Is_Present()) { if ( (!(ActiveBScan & STRUCTF_IRON_CURTAIN) && !SuperWeapon[SPC_IRON_CURTAIN].Is_One_Time()) || IsDefeated) { /* ** Remove the iron curtain when there is no iron curtain facility. ** Note that this will not remove the one time created iron curtain. */ if (SuperWeapon[SPC_IRON_CURTAIN].Remove()) { if (this == PlayerPtr) { if (Map.IsTargettingMode == SPC_IRON_CURTAIN) { Map.IsTargettingMode = SPC_NONE; } Map.Column[1].Flag_To_Redraw(); } IsRecalcNeeded = true; } } } else { /* ** If there is no iron curtain present, but there is an iron curtain ** facility available, then make the iron curtain available as well. */ if ((ActiveBScan & STRUCTF_IRON_CURTAIN) && (ActLike == HOUSE_USSR || ActLike == HOUSE_UKRAINE || Session.Type != GAME_NORMAL) && (IsHuman || IQ >= Rule.IQSuperWeapons)) { SuperWeapon[SPC_IRON_CURTAIN].Enable(false, this == PlayerPtr, Power_Fraction() < 1); /* ** Flag the sidebar to be redrawn if necessary. */ // Add to Glyphx multiplayer sidebar. ST - 8/7/2019 10:13AM if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { if (IsHuman) { Sidebar_Glyphx_Add(RTTI_SPECIAL, SPC_IRON_CURTAIN, this); } } else { if (this == PlayerPtr) { Map.Add(RTTI_SPECIAL, SPC_IRON_CURTAIN); Map.Column[1].Flag_To_Redraw(); } } } } /* ** Check to see if the sonar pulse should be removed from the sidebar ** because of outside circumstances. The spied-upon enemy sub pen ** being destroyed is a good example of this. */ if (SuperWeapon[SPC_SONAR_PULSE].Is_Present()) { int usspy = 1 << (Class->House); bool present = false; bool powered = false; for (int q = 0; q < Buildings.Count() && !powered; q++) { BuildingClass * bldg = Buildings.Ptr(q); if ((*bldg == STRUCT_SUB_PEN) && (bldg->House->Class->House != Class->House) && (bldg->Spied_By() & usspy) ) { present = true; powered = !(bldg->House->Power_Fraction() < 1); } } if ( (!present && !SuperWeapon[SPC_SONAR_PULSE].Is_One_Time()) || IsDefeated) { /* ** Remove the sonar pulse when there is no spied-upon enemy sub pen. ** Note that this will not remove the one time created sonar pulse. */ if (SuperWeapon[SPC_SONAR_PULSE].Remove()) { if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw(); IsRecalcNeeded = true; } } } /* ** Check to see if the nuclear weapon should be removed from the sidebar ** because of outside circumstances. The missile silos ** being destroyed is a good example of this. */ if (SuperWeapon[SPC_NUCLEAR_BOMB].Is_Present()) { if ( (!(ActiveBScan & STRUCTF_MSLO) && !SuperWeapon[SPC_NUCLEAR_BOMB].Is_One_Time()) || IsDefeated) { /* ** Remove the nuke when there is no missile silo. ** Note that this will not remove the one time created nuke. */ if (SuperWeapon[SPC_NUCLEAR_BOMB].Remove()) { if (this == PlayerPtr) { if (Map.IsTargettingMode == SPC_NUCLEAR_BOMB) { Map.IsTargettingMode = SPC_NONE; } Map.Column[1].Flag_To_Redraw(); } IsRecalcNeeded = true; } } else { /* ** Allow the computer to fire the nuclear weapon when the weapon is ** ready and the owner is the computer. */ if (SuperWeapon[SPC_NUCLEAR_BOMB].Is_Ready() && !IsHuman) { Special_Weapon_AI(SPC_NUCLEAR_BOMB); } } } else { /* ** If there is no nuclear missile present, but there is a missile ** silo available, then make the missile available as well. */ if ((ActiveBScan & STRUCTF_MSLO) && ((ActLike != HOUSE_USSR && ActLike != HOUSE_UKRAINE) || Session.Type != GAME_NORMAL) && (IsHuman || IQ >= Rule.IQSuperWeapons)) { SuperWeapon[SPC_NUCLEAR_BOMB].Enable(false, this == PlayerPtr, Power_Fraction() < 1); /* ** Flag the sidebar to be redrawn if necessary. */ // Add to Glyphx multiplayer sidebar. ST - 8/7/2019 10:13AM if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { if (IsHuman) { Sidebar_Glyphx_Add(RTTI_SPECIAL, SPC_NUCLEAR_BOMB, this); } } else { if (this == PlayerPtr) { Map.Add(RTTI_SPECIAL, SPC_NUCLEAR_BOMB); Map.Column[1].Flag_To_Redraw(); } } } } if (SuperWeapon[SPC_SPY_MISSION].Is_Present()) { if ((ActiveBScan & STRUCTF_AIRSTRIP) == 0) { if (SuperWeapon[SPC_SPY_MISSION].Remove()) { if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw(); IsRecalcNeeded = true; } } else { if (this == PlayerPtr && !SuperWeapon[SPC_SPY_MISSION].Is_Ready()) { Map.Column[1].Flag_To_Redraw(); } if (SuperWeapon[SPC_SPY_MISSION].Is_Ready() && !IsHuman) { Special_Weapon_AI(SPC_SPY_MISSION); } } } else { if ((ActiveBScan & STRUCTF_AIRSTRIP) != 0 && !Scen.IsNoSpyPlane && Control.TechLevel >= Rule.SpyPlaneTechLevel) { SuperWeapon[SPC_SPY_MISSION].Enable(false, this == PlayerPtr, false); // Add to Glyphx multiplayer sidebar. ST - 8/7/2019 10:13AM if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { if (IsHuman) { Sidebar_Glyphx_Add(RTTI_SPECIAL, SPC_SPY_MISSION, this); } } else { if (this == PlayerPtr) { Map.Add(RTTI_SPECIAL, SPC_SPY_MISSION); Map.Column[1].Flag_To_Redraw(); } } } } if (SuperWeapon[SPC_PARA_BOMB].Is_Present()) { if ((ActiveBScan & STRUCTF_AIRSTRIP) == 0) { if (SuperWeapon[SPC_PARA_BOMB].Remove()) { if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw(); IsRecalcNeeded = true; } } else { if (SuperWeapon[SPC_PARA_BOMB].Is_Ready() && !IsHuman) { Special_Weapon_AI(SPC_PARA_BOMB); } } } else { if ((ActiveBScan & STRUCTF_AIRSTRIP) != 0 && Control.TechLevel >= Rule.ParaBombTechLevel && Session.Type == GAME_NORMAL) { SuperWeapon[SPC_PARA_BOMB].Enable(false, this == PlayerPtr, false); // Add to Glyphx multiplayer sidebar. ST - 8/7/2019 10:13AM if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { if (IsHuman) { Sidebar_Glyphx_Add(RTTI_SPECIAL, SPC_PARA_BOMB, this); } } else { if (this == PlayerPtr) { Map.Add(RTTI_SPECIAL, SPC_PARA_BOMB); Map.Column[1].Flag_To_Redraw(); } } } } if (SuperWeapon[SPC_PARA_INFANTRY].Is_Present()) { if ((ActiveBScan & STRUCTF_AIRSTRIP) == 0) { if (SuperWeapon[SPC_PARA_INFANTRY].Remove()) { if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw(); IsRecalcNeeded = true; } } else { if (SuperWeapon[SPC_PARA_INFANTRY].Is_Ready() && !IsHuman) { Special_Weapon_AI(SPC_PARA_INFANTRY); } } } else { if ((ActiveBScan & STRUCTF_AIRSTRIP) != 0 && Control.TechLevel >= Rule.ParaInfantryTechLevel) { SuperWeapon[SPC_PARA_INFANTRY].Enable(false, this == PlayerPtr, false); // Add to Glyphx multiplayer sidebar. ST - 8/7/2019 10:13AM if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { if (IsHuman) { Sidebar_Glyphx_Add(RTTI_SPECIAL, SPC_PARA_INFANTRY, this); } } else { if (this == PlayerPtr) { Map.Add(RTTI_SPECIAL, SPC_PARA_INFANTRY); Map.Column[1].Flag_To_Redraw(); } } } } } /*********************************************************************************************** * HouseClass::Attacked -- Lets player know if base is under attack. * * * * Call this function whenever a building is attacked (with malice). This function will * * then announce to the player that his base is under attack. It checks to make sure that * * this is referring to the player's house rather than the enemy's. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 12/27/1994 JLB : Created. * *=============================================================================================*/ void HouseClass::Attacked(BuildingClass* source) { assert(Houses.ID(this) == ID); #ifdef FIXIT_BASE_ANNOUNCE if (SpeakAttackDelay == 0 && ((Session.Type == GAME_NORMAL && IsPlayerControl) || PlayerPtr->Class->House == Class->House)) { #else if (SpeakAttackDelay == 0 && PlayerPtr->Class->House == Class->House) { #endif if (Session.Type == GAME_NORMAL) { Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0); } else { Speak(VOX_BASE_UNDER_ATTACK, this); } // MBL 06.13.2020 - Timing change from 2 minute cooldown, per https://jaas.ea.com/browse/TDRA-6784 // SpeakAttackDelay = Options.Normalize_Delay(TICKS_PER_MINUTE * Rule.SpeakDelay); // 2 minutes // SpeakAttackDelay = Options.Normalize_Delay(TICKS_PER_MINUTE/2); // 30 seconds as requested SpeakAttackDelay = Options.Normalize_Delay( (TICKS_PER_MINUTE/2)+(TICKS_PER_SECOND*5) ); // Tweaked for accuracy /* ** If there is a trigger event associated with being attacked, process it ** now. */ for (int index = 0; index < HouseTriggers[Class->House].Count(); index++) { HouseTriggers[Class->House][index]->Spring(TEVENT_ATTACKED); } } } /*********************************************************************************************** * HouseClass::Harvested -- Adds Tiberium to the harvest storage. * * * * Use this routine whenever Tiberium is harvested. The Tiberium is stored equally between * * all storage capable buildings for the house. Harvested Tiberium adds to the credit * * value of the house, but only up to the maximum storage capacity that the house can * * currently maintain. * * * * INPUT: tiberium -- The number of Tiberium credits to add to the House's total. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 01/25/1995 JLB : Created. * *=============================================================================================*/ void HouseClass::Harvested(unsigned tiberium) { assert(Houses.ID(this) == ID); long oldtib = Tiberium; Tiberium += tiberium; if (Tiberium > Capacity) { Tiberium = Capacity; IsMaxedOut = true; } HarvestedCredits += tiberium; Silo_Redraw_Check(oldtib, Capacity); } /*********************************************************************************************** * HouseClass::Stole -- Accounts for the value of a captured building. * * * * Use this routine whenever a building is captured. It keeps track of the cost of the * * building for use in the scoring routine, because you get an 'economy' boost for the * * value of the stolen building (but you don't get the credit value for it.) * * * * INPUT: worth -- The worth of the building we captured (stole). * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 09/05/1996 BWG : Created. * *=============================================================================================*/ void HouseClass::Stole(unsigned worth) { assert(Houses.ID(this) == ID); StolenBuildingsCredits += worth; } /*********************************************************************************************** * HouseClass::Available_Money -- Fetches the total credit worth of the house. * * * * Use this routine to determine the total credit value of the house. This is the sum of * * the harvested Tiberium in storage and the initial unspent cash reserves. * * * * INPUT: none * * * * OUTPUT: Returns with the total credit value of the house. * * * * WARNINGS: none * * * * HISTORY: * * 01/25/1995 JLB : Created. * *=============================================================================================*/ long HouseClass::Available_Money(void) const { assert(Houses.ID(this) == ID); return(Tiberium + Credits); } /*********************************************************************************************** * HouseClass::Spend_Money -- Removes money from the house. * * * * Use this routine to extract money from the house. Typically, this is a result of * * production spending. The money is extracted from available cash reserves first. When * * cash reserves are exhausted, then Tiberium is consumed. * * * * INPUT: money -- The amount of money to spend. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 01/25/1995 JLB : Created. * * 06/20/1995 JLB : Spends Tiberium before spending cash. * *=============================================================================================*/ void HouseClass::Spend_Money(unsigned money) { assert(Houses.ID(this) == ID); long oldtib = Tiberium; if (money > (unsigned)Tiberium) { money -= (unsigned)Tiberium; Tiberium = 0; Credits -= money; } else { Tiberium -= money; } Silo_Redraw_Check(oldtib, Capacity); CreditsSpent += money; } /*********************************************************************************************** * HouseClass::Refund_Money -- Refunds money to back to the house. * * * * Use this routine when money needs to be refunded back to the house. This can occur when * * construction is aborted. At this point, the exact breakdown of Tiberium or initial * * credits used for the orignal purchase is lost. Presume as much of the money is in the * * form of Tiberium as storage capacity will allow. * * * * INPUT: money -- The number of credits to refund back to the house. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 01/25/1995 JLB : Created. * * 06/01/1995 JLB : Refunded money is never lost * *=============================================================================================*/ void HouseClass::Refund_Money(unsigned money) { assert(Houses.ID(this) == ID); Credits += money; } /*********************************************************************************************** * HouseClass::Adjust_Capacity -- Adjusts the house Tiberium storage capacity. * * * * Use this routine to adjust the maximum storage capacity for the house. This storage * * capacity will limit the number of Tiberium credits that can be stored at any one time. * * * * INPUT: adjust -- The adjustment to the Tiberium storage capacity. * * * * inanger -- Is this a forced adjustment to capacity due to some hostile event? * * * * OUTPUT: Returns with the number of Tiberium credits lost. * * * * WARNINGS: none * * * * HISTORY: * * 01/25/1995 JLB : Created. * *=============================================================================================*/ int HouseClass::Adjust_Capacity(int adjust, bool inanger) { assert(Houses.ID(this) == ID); long oldcap = Capacity; int retval = 0; Capacity += adjust; Capacity = max(Capacity, 0L); if (Tiberium > Capacity) { retval = Tiberium - Capacity; Tiberium = Capacity; if (!inanger) { Refund_Money(retval); retval = 0; } else { IsMaxedOut = true; } } Silo_Redraw_Check(Tiberium, oldcap); return(retval); } /*********************************************************************************************** * HouseClass::Silo_Redraw_Check -- Flags silos to be redrawn if necessary. * * * * Call this routine when either the capacity or tiberium levels change for a house. This * * routine will determine if the aggregate tiberium storage level will result in the * * silos changing their imagery. If this is detected, then all the silos for this house * * are flagged to be redrawn. * * * * INPUT: oldtib -- Pre-change tiberium level. * * * * oldcap -- Pre-change tiberium storage capacity. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 02/02/1995 JLB : Created. * *=============================================================================================*/ void HouseClass::Silo_Redraw_Check(long oldtib, long oldcap) { assert(Houses.ID(this) == ID); int oldratio = 0; if (oldcap) oldratio = (oldtib * 5) / oldcap; int newratio = 0; if (Capacity) newratio = (Tiberium * 5) / Capacity; if (oldratio != newratio) { for (int index = 0; index < Buildings.Count(); index++) { BuildingClass * b = Buildings.Ptr(index); if (b && !b->IsInLimbo && b->House == this && *b == STRUCT_STORAGE) { b->Mark(MARK_CHANGE); } } } } /*********************************************************************************************** * HouseClass::Is_Ally -- Determines if the specified house is an ally. * * * * This routine will determine if the house number specified is a ally to this house. * * * * INPUT: house -- The house number to check to see if it is an ally. * * * * OUTPUT: Is the house an ally? * * * * WARNINGS: none * * * * HISTORY: * * 05/08/1995 JLB : Created. * *=============================================================================================*/ bool HouseClass::Is_Ally(HousesType house) const { assert(Houses.ID(this) == ID); if (house != HOUSE_NONE) { return(((1<Class->House)); } return(false); } /*********************************************************************************************** * HouseClass::Is_Ally -- Checks to see if the object is an ally. * * * * This routine will examine the specified object and return whether it is an ally or not. * * * * INPUT: object -- The object to examine to see if it is an ally. * * * * OUTPUT: Is the specified object an ally? * * * * WARNINGS: none * * * * HISTORY: * * 05/08/1995 JLB : Created. * *=============================================================================================*/ bool HouseClass::Is_Ally(ObjectClass const * object) const { assert(Houses.ID(this) == ID); if (object) { return(Is_Ally(object->Owner())); } return(false); } /*********************************************************************************************** * HouseClass::Make_Ally -- Make the specified house an ally. * * * * This routine will make the specified house an ally to this house. An allied house will * * not be considered a threat or potential target. * * * * INPUT: house -- The house to make an ally of this house. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 05/08/1995 JLB : Created. * * 08/08/1995 JLB : Breaks off combat when ally commences. * * 10/17/1995 JLB : Added reveal base when allied. * *=============================================================================================*/ void HouseClass::Make_Ally(HousesType house) { assert(Houses.ID(this) == ID); if (Is_Allowed_To_Ally(house)) { Allies |= (1L << house); /* ** Don't consider the newfound ally to be an enemy -- of course. */ if (Enemy == house) { Enemy = HOUSE_NONE; } if (ScenarioInit) { Control.Allies |= (1L << house); } if (Session.Type != GAME_NORMAL && !ScenarioInit) { HouseClass * hptr = HouseClass::As_Pointer(house); /* ** An alliance with another human player will cause the computer ** players (if present) to become paranoid. */ if (hptr != NULL && IsHuman && Rule.IsComputerParanoid) { // if (hptr != NULL && hptr->IsHuman) { // if (!hptr->IsHuman) { // hptr->Make_Ally(Class->House); // } Computer_Paranoid(); } char buffer[80]; /* ** Sweep through all techno objects and perform a cheeseball tarcom clear to ensure ** that fighting will most likely stop when the cease fire begins. */ for (int index = 0; index < Logic.Count(); index++) { ObjectClass * object = Logic[index]; if (object != NULL && object->Is_Techno() && !object->IsInLimbo && object->Owner() == Class->House) { TARGET target = ((TechnoClass *)object)->TarCom; if (Target_Legal(target) && As_Techno(target) != NULL) { if (Is_Ally(As_Techno(target))) { ((TechnoClass *)object)->Assign_Target(TARGET_NONE); } } } } /* ** Cause all structures to be revealed to the house that has been ** allied with. */ if (Rule.IsAllyReveal && house == PlayerPtr->Class->House) { for (int index = 0; index < Buildings.Count(); index++) { BuildingClass const * b = Buildings.Ptr(index); if (b && !b->IsInLimbo && (HouseClass *)b->House == this) { Map.Sight_From(Coord_Cell(b->Center_Coord()), b->Class->SightRange, PlayerPtr, false); } } } if (IsHuman) { sprintf(buffer, Text_String(TXT_HAS_ALLIED), IniName, HouseClass::As_Pointer(house)->IniName); // sprintf(buffer, Text_String(TXT_HAS_ALLIED), Session.Players[Class->House - HOUSE_MULTI1]->Name, Session.Players[((HouseClass::As_Pointer(house))->Class->House) - HOUSE_MULTI1]->Name); Session.Messages.Add_Message(NULL, 0, buffer, RemapColor, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_FULLSHADOW, TICKS_PER_MINUTE * Rule.MessageDelay); } #if(TEN) // // Notify the TEN server of the new alliance // if (this == PlayerPtr && hptr != NULL && Session.Type == GAME_TEN) { Send_TEN_Alliance(hptr->IniName, 1); } #endif // TEN #if(MPATH) // // Notify the MPATH server of the new alliance // //if (this == PlayerPtr && hptr != NULL && Session.Type == GAME_MPATH) { //Send_MPATH_Alliance(hptr->IniName, 1); //} #endif // MPATH Map.Flag_To_Redraw(false); } } } /*********************************************************************************************** * HouseClass::Make_Enemy -- Make an enemy of the house specified. * * * * This routine will flag the house specified so that it will be an enemy to this house. * * Enemy houses are legal targets for attack. * * * * INPUT: house -- The house to make an enemy of this house. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 05/08/1995 JLB : Created. * * 07/27/1995 JLB : Making war is a bilateral action. * *=============================================================================================*/ void HouseClass::Make_Enemy(HousesType house) { assert(Houses.ID(this) == ID); if (house != HOUSE_NONE && Is_Ally(house)) { HouseClass * enemy = HouseClass::As_Pointer(house); Allies &= ~(1L << house); if (ScenarioInit) { Control.Allies &= !(1L << house); } /* ** Breaking an alliance is a bilateral event. */ if (enemy != NULL && enemy->Is_Ally(this)) { enemy->Allies &= ~(1L << Class->House); if (ScenarioInit) { Control.Allies &= ~(1L << Class->House); } } if ((Debug_Flag || Session.Type != GAME_NORMAL) && !ScenarioInit && IsHuman) { char buffer[80]; sprintf(buffer, Text_String(TXT_AT_WAR), IniName, HouseClass::As_Pointer(house)->IniName); // sprintf(buffer, Text_String(TXT_AT_WAR), Session.Players[Class->House - HOUSE_MULTI1]->Name, Session.Players[enemy->Class->House - HOUSE_MULTI1]->Name); Session.Messages.Add_Message(NULL, 0, buffer, RemapColor, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_FULLSHADOW, TICKS_PER_MINUTE * Rule.MessageDelay); Map.Flag_To_Redraw(false); #if(TEN) // // Notify the TEN server of the broken alliance // if (this == PlayerPtr && enemy != NULL && Session.Type == GAME_TEN) { Send_TEN_Alliance(enemy->IniName, 0); } #endif // TEN #if(MPATH) // // Notify the MPATH server of the broken alliance // //if (this == PlayerPtr && enemy != NULL && Session.Type == GAME_MPATH) { //Send_MPATH_Alliance(enemy->IniName, 0); //} #endif // MPATH } } } /*********************************************************************************************** * HouseClass::Remap_Table -- Fetches the remap table for this house object. * * * * This routine will return with the remap table to use when displaying an object owned * * by this house. If the object is blushing (flashing), then the lightening remap table is * * always used. The "unit" parameter allows proper remap selection for those houses that * * have a different remap table for buildings or units. * * * * INPUT: blushing -- Is the object blushing (flashing)? * * * * remap -- The remap control value to use. * * REMAP_NONE No remap pointer returned at all. * * REMAP_NORMAL Return the remap pointer for this house. * * REMAP_ALTERNATE (Nod solo play only -- forces red remap). * * Multiplay returns same as REMAP_NORMAL * * * * OUTPUT: Returns with a pointer to the remap table to use when drawing this object. * * * * WARNINGS: none * * * * HISTORY: * * 05/08/1995 JLB : Created. * * 10/25/1995 JLB : Uses remap control value. * *=============================================================================================*/ unsigned char const * HouseClass::Remap_Table(bool blushing, RemapType remap) const { assert(Houses.ID(this) == ID); if (blushing) return(&Map.FadingLight[0]); if (remap == REMAP_NONE) return(0); return(ColorRemaps[RemapColor].RemapTable); } /*********************************************************************************************** * HouseClass::Suggested_New_Team -- Determine what team should be created. * * * * This routine examines the house condition and returns with the team that it thinks * * should be created. The units that are not currently a member of a team are examined * * to determine the team needed. * * * * INPUT: alertcheck -- Select from the auto-create team list. * * * * OUTPUT: Returns with a pointer to the team type that should be created. If no team should * * be created, then NULL is returned. * * * * WARNINGS: none * * * * HISTORY: * * 05/08/1995 JLB : Created. * *=============================================================================================*/ TeamTypeClass const * HouseClass::Suggested_New_Team(bool alertcheck) { assert(Houses.ID(this) == ID); return(TeamTypeClass::Suggested_New_Team(this, AScan, UScan, IScan, VScan, alertcheck)); } /*********************************************************************************************** * HouseClass::Adjust_Threat -- Adjust threat for the region specified. * * * * This routine is called when the threat rating for a region needs to change. The region * * and threat adjustment are provided. * * * * INPUT: region -- The region that adjustment is to occur on. * * * * threat -- The threat adjustment to perform. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 05/08/1995 JLB : Created. * *=============================================================================================*/ void HouseClass::Adjust_Threat(int region, int threat) { assert(Houses.ID(this) == ID); static int _val[] = { -MAP_REGION_WIDTH - 1, -MAP_REGION_WIDTH, -MAP_REGION_WIDTH + 1, -1, 0, 1, MAP_REGION_WIDTH -1, MAP_REGION_WIDTH, MAP_REGION_WIDTH +1 }; static int _thr[] = { 2, 1, 2, 1, 0, 1, 2, 1, 2 }; int neg; int * val = &_val[0]; int * thr = &_thr[0]; if (threat < 0) { threat = -threat; neg = true; } else { neg = false; } for (int lp = 0; lp < 9; lp ++) { Regions[region + *val].Adjust_Threat(threat >> *thr, neg); val++; thr++; } } /*********************************************************************************************** * HouseClass::Begin_Production -- Starts production of the specified object type. * * * * This routine is called from the event system. It will start production for the object * * type specified. This will be reflected in the sidebar as well as the house factory * * tracking variables. * * * * INPUT: type -- The type of object to begin production on. * * * * id -- The subtype of object. * * * * OUTPUT: Returns with the reason why, or why not, production was started. * * * * WARNINGS: none * * * * HISTORY: * * 05/08/1995 JLB : Created. * * 10/21/1996 JLB : Handles max object case. * *=============================================================================================*/ ProdFailType HouseClass::Begin_Production(RTTIType type, int id) { assert(Houses.ID(this) == ID); int result = true; bool initial_start = false; FactoryClass * fptr; TechnoTypeClass const * tech = Fetch_Techno_Type(type, id); fptr = Fetch_Factory(type); /* ** If the house is already busy producing the requested object, then ** return with this failure code, unless we are restarting production. */ if (fptr != NULL) { if (fptr->Is_Building()) { return(PROD_CANT); } } else { fptr = new FactoryClass(); if (!fptr) return(PROD_CANT); Set_Factory(type, fptr); result = fptr->Set(*tech, *this); initial_start = true; /* ** If set failed, we probably reached the production cap. Don't let the factory linger, preventing further production attempts. ** ST - 3/17/2020 2:03PM */ if (!result) { Set_Factory(type, NULL); delete fptr; fptr = NULL; } } if (result) { fptr->Start(); /* ** Link this factory to the sidebar so that proper graphic feedback ** can take place. */ // Handle Glyphx multiplayer sidebar. ST - 8/14/2019 1:26PM if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { if (IsHuman) { Sidebar_Glyphx_Factory_Link(fptr->ID, type, id, this); } } else { if (PlayerPtr == this) { Map.Factory_Link(fptr->ID, type, id); } } return(PROD_OK); } delete fptr; return(PROD_CANT); } /*********************************************************************************************** * HouseClass::Suspend_Production -- Temporarily puts production on hold. * * * * This routine is called from the event system whenever the production of the specified * * type needs to be suspended. The suspended production will be reflected in the sidebar * * as well as in the house control structure. * * * * INPUT: type -- The type of object that production is being suspended for. * * * * OUTPUT: Returns why, or why not, production was suspended. * * * * WARNINGS: none * * * * HISTORY: * * 05/08/1995 JLB : Created. * *=============================================================================================*/ ProdFailType HouseClass::Suspend_Production(RTTIType type) { assert(Houses.ID(this) == ID); FactoryClass * fptr = Fetch_Factory(type); /* ** If the house is already busy producing the requested object, then ** return with this failure code. */ if (fptr == NULL) return(PROD_CANT); /* ** Actually suspend the production. */ fptr->Suspend(); /* ** Tell the sidebar that it needs to be redrawn because of this. */ if (PlayerPtr == this) { Map.SidebarClass::IsToRedraw = true; if (!RunningAsDLL) { // Don't force a redraw when running under GlyphX. PlayerPtr==this will always be true in this case, and we don't want to force a redraw even for AI players Map.Flag_To_Redraw(false); } } return(PROD_OK); } /*********************************************************************************************** * HouseClass::Abandon_Production -- Abandons production of item type specified. * * * * This routine is called from the event system whenever production must be abandoned for * * the type specified. This will remove the factory and pending object from the sidebar as * * well as from the house factory record. * * * * INPUT: type -- The object type that production is being suspended for. * * * * OUTPUT: Returns the reason why or why not, production was suspended. * * * * WARNINGS: none * * * * HISTORY: * * 05/08/1995 JLB : Created. * *=============================================================================================*/ ProdFailType HouseClass::Abandon_Production(RTTIType type) { assert(Houses.ID(this) == ID); FactoryClass * fptr = Fetch_Factory(type); /* ** If there is no factory to abandon, then return with a failure code. */ if (fptr == NULL) return(PROD_CANT); /* ** Tell the sidebar that it needs to be redrawn because of this. */ // Handle Glyphx multiplayer sidebar. ST - 8/7/2019 10:18AM if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { if (IsHuman) { Sidebar_Glyphx_Abandon_Production(type, fptr->ID, this); // Need to clear pending object here if legacy renderer enabled if (type == RTTI_BUILDINGTYPE || type == RTTI_BUILDING && Map.PendingObjectPtr) { Map.PendingObjectPtr = 0; Map.PendingObject = 0; Map.PendingHouse = HOUSE_NONE; Map.Set_Cursor_Shape(0); } } } else { if (PlayerPtr == this) { Map.Abandon_Production(type, fptr->ID); if (type == RTTI_BUILDINGTYPE || type == RTTI_BUILDING) { Map.PendingObjectPtr = 0; Map.PendingObject = 0; Map.PendingHouse = HOUSE_NONE; Map.Set_Cursor_Shape(0); } } } /* ** Abandon production of the object. */ fptr->Abandon(); Set_Factory(type, NULL); delete fptr; return(PROD_OK); } /*********************************************************************************************** * HouseClass::Special_Weapon_AI -- Fires special weapon. * * * * This routine will pick a good target to fire the special weapon specified. * * * * INPUT: id -- The special weapon id to fire. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 06/24/1995 PWG : Created. * *=============================================================================================*/ void HouseClass::Special_Weapon_AI(SpecialWeaponType id) { assert(Houses.ID(this) == ID); /* ** Loop through all of the building objects on the map ** and see which ones are available. */ BuildingClass * bestptr = NULL; int best = -1; for (int index = 0; index < Buildings.Count(); index++) { BuildingClass * b = Buildings.Ptr(index); /* ** If the building is valid, not in limbo, not in the process of ** being destroyed and not our ally, then we can consider it. */ if (b != NULL && !b->IsInLimbo && b->Strength && !Is_Ally(b)) { if (Percent_Chance(90) && (b->Value() > best || best == -1)) { best = b->Value(); bestptr = b; } } } if (bestptr) { CELL cell = Coord_Cell(bestptr->Center_Coord()); Place_Special_Blast(id, cell); } } /*********************************************************************************************** * HouseClass::Place_Special_Blast -- Place a special blast effect at location specified. * * * * This routine will create a blast effect at the cell specified. This is the result of * * the special weapons. * * * * INPUT: id -- The special weapon id number. * * * * cell -- The location where the special weapon attack is to occur. * * * * OUTPUT: Was the special weapon successfully fired at the location specified? * * * * WARNINGS: none * * * * HISTORY: * * 05/18/1995 JLB : commented. * * 07/25/1995 JLB : Added scatter effect for nuclear bomb. * * 07/28/1995 JLB : Revamped to use super weapon class controller. * *=============================================================================================*/ extern void Logic_Switch_Player_Context(ObjectClass *object); extern void Logic_Switch_Player_Context(HouseClass *object); extern void On_Special_Weapon_Targetting(const HouseClass* player_ptr, SpecialWeaponType weapon_type); bool HouseClass::Place_Special_Blast(SpecialWeaponType id, CELL cell) { assert(Houses.ID(this) == ID); // Added. ST - 12/2/2019 11:26AM bool fired = false; const char *what = NULL; BuildingClass * launchsite = 0; AnimClass * anim = 0; switch (id) { case SPC_SONAR_PULSE: // Automatically discharge the sonar pulse and uncloak all subs. if (SuperWeapon[SPC_SONAR_PULSE].Is_Ready()) { SuperWeapon[SPC_SONAR_PULSE].Discharged(this == PlayerPtr); if (this == PlayerPtr) { Map.Column[1].Flag_To_Redraw(); Map.Activate_Pulse(); } Sound_Effect(VOC_SONAR); IsRecalcNeeded = true; fired = true; what = "SONAR"; for (int index = 0; index < Vessels.Count(); index++) { VesselClass * sub = Vessels.Ptr(index); #ifdef FIXIT_CSII // checked - ajw 9/28/98 if (*sub == VESSEL_SS || *sub == VESSEL_MISSILESUB) { #else if (*sub == VESSEL_SS) { #endif sub->PulseCountDown = 15 * TICKS_PER_SECOND; sub->Do_Uncloak(); } } } break; case SPC_NUCLEAR_BOMB: if (SuperWeapon[SPC_NUCLEAR_BOMB].Is_Ready()) { if (SuperWeapon[SPC_NUCLEAR_BOMB].Is_One_Time()) { BulletClass * bullet = new BulletClass(BULLET_NUKE_DOWN, ::As_Target(cell), 0, 200, WARHEAD_NUKE, MPH_VERY_FAST); if (bullet) { int celly = Cell_Y(cell); celly -= 15; if (celly < 1) celly = 1; COORDINATE start = Cell_Coord(XY_Cell(Cell_X(cell), celly)); if (!bullet->Unlimbo(start, DIR_S)) { delete bullet; } SuperWeapon[SPC_NUCLEAR_BOMB].Discharged(this == PlayerPtr); IsRecalcNeeded = true; fired = true; what = "NUKE"; if (this == PlayerPtr) { Map.Column[1].Flag_To_Redraw(); Map.IsTargettingMode = SPC_NONE; } } } else { /* ** Search for a suitable launch site for this missile. */ launchsite = Find_Building(STRUCT_MSLO); /* ** If a launch site was found, then proceed with the normal launch ** sequence. */ if (launchsite) { launchsite->Assign_Mission(MISSION_MISSILE); launchsite->Commence(); NukeDest = cell; } if (this == PlayerPtr) { Map.IsTargettingMode = SPC_NONE; } SuperWeapon[SPC_NUCLEAR_BOMB].Discharged(this == PlayerPtr); IsRecalcNeeded = true; fired = true; what = "NUKE"; } } break; case SPC_PARA_INFANTRY: if (SuperWeapon[SPC_PARA_INFANTRY].Is_Ready()) { TeamTypeClass * ttype = TeamTypeClass::As_Pointer("@PINF"); if (ttype == NULL) { ttype = new TeamTypeClass; if (ttype != NULL) { strcpy(ttype->IniName, "@PINF"); ttype->IsTransient = true; ttype->IsPrebuilt = false; ttype->IsReinforcable = false; ttype->Origin = WAYPT_SPECIAL; ttype->MissionCount = 1; ttype->MissionList[0].Mission = TMISSION_ATT_WAYPT; ttype->MissionList[0].Data.Value = WAYPT_SPECIAL; ttype->ClassCount = 2; ttype->Members[0].Quantity = AircraftTypeClass::As_Reference(AIRCRAFT_BADGER).Max_Passengers(); ttype->Members[0].Class = &InfantryTypeClass::As_Reference(INFANTRY_E1); ttype->Members[1].Quantity = 1; ttype->Members[1].Class = &AircraftTypeClass::As_Reference(AIRCRAFT_BADGER); } } if (ttype != NULL) { ttype->House = Class->House; Scen.Waypoint[WAYPT_SPECIAL] = Map.Nearby_Location(cell, SPEED_FOOT); Do_Reinforcements(ttype); } // Create_Air_Reinforcement(this, AIRCRAFT_BADGER, 1, MISSION_HUNT, ::As_Target(cell), TARGET_NONE, INFANTRY_E1); if (this == PlayerPtr) { Map.IsTargettingMode = SPC_NONE; } SuperWeapon[SPC_PARA_INFANTRY].Discharged(this == PlayerPtr); IsRecalcNeeded = true; fired = true; what = "PARA"; } break; case SPC_SPY_MISSION: if (SuperWeapon[SPC_SPY_MISSION].Is_Ready()) { Create_Air_Reinforcement(this, AIRCRAFT_U2, 1, MISSION_HUNT, ::As_Target(cell), ::As_Target(cell)); if (this == PlayerPtr) { Map.IsTargettingMode = SPC_NONE; } SuperWeapon[SPC_SPY_MISSION].Discharged(this == PlayerPtr); IsRecalcNeeded = true; fired = true; what = "SPY"; } break; case SPC_PARA_BOMB: if (SuperWeapon[SPC_PARA_BOMB].Is_Ready()) { Create_Air_Reinforcement(this, AIRCRAFT_BADGER, Rule.BadgerBombCount, MISSION_HUNT, ::As_Target(cell), TARGET_NONE); if (this == PlayerPtr) { Map.IsTargettingMode = SPC_NONE; } SuperWeapon[SPC_PARA_BOMB].Discharged(this == PlayerPtr); IsRecalcNeeded = true; fired = true; what = "PARABOMB"; } break; case SPC_IRON_CURTAIN: if (SuperWeapon[SPC_IRON_CURTAIN].Is_Ready()) { int x = Keyboard->MouseQX - Map.TacPixelX; int y = Keyboard->MouseQY - Map.TacPixelY; TechnoClass * tech = Map[cell].Cell_Techno(x, y); if (tech) { switch (tech->What_Am_I()) { case RTTI_UNIT: case RTTI_BUILDING: case RTTI_VESSEL: case RTTI_AIRCRAFT: tech->IronCurtainCountDown = Rule.IronCurtainDuration * TICKS_PER_MINUTE; #ifdef FIXIT_CSII // checked - ajw 9/28/98 if (tech->What_Am_I() == RTTI_UNIT && *(UnitClass *)tech == UNIT_DEMOTRUCK) { tech->IronCurtainCountDown = Rule.IronCurtainDuration * TICKS_PER_SECOND; } #endif tech->Mark(MARK_CHANGE); Sound_Effect(VOC_IRON1, tech->Center_Coord()); if (this == PlayerPtr) { Map.IsTargettingMode = SPC_NONE; } SuperWeapon[SPC_IRON_CURTAIN].Discharged(this == PlayerPtr); break; default: break; } } IsRecalcNeeded = true; fired = true; what = "IRON"; } break; case SPC_CHRONOSPHERE: if (SuperWeapon[SPC_CHRONOSPHERE].Is_Ready()) { int x = Keyboard->MouseQX - Map.TacPixelX; int y = Keyboard->MouseQY - Map.TacPixelY; TechnoClass * tech = Map[cell].Cell_Techno(x, y); if (tech && Is_Ally(tech)) { if (tech->What_Am_I() == RTTI_UNIT || tech->What_Am_I() == RTTI_INFANTRY || #ifdef FIXIT_CARRIER // checked - ajw 9/28/98 (tech->What_Am_I() == RTTI_VESSEL && (*((VesselClass *)tech) != VESSEL_TRANSPORT && *((VesselClass *)tech) != VESSEL_CARRIER) )) { #else (tech->What_Am_I() == RTTI_VESSEL && *((VesselClass *)tech) != VESSEL_TRANSPORT)) { #endif if (tech->What_Am_I() != RTTI_UNIT || !((UnitClass *)tech)->IsDeploying) { #ifdef FIXIT_CSII // checked - ajw 9/28/98 bool porthim = true; if(tech->What_Am_I() == RTTI_UNIT && ((UnitClass *)tech)->Class->Type == UNIT_CHRONOTANK) { porthim = false; } if (porthim) { #endif HouseClass* old_player_ptr = PlayerPtr; Logic_Switch_Player_Context(this); Map.IsTargettingMode = SPC_CHRONO2; On_Special_Weapon_Targetting(PlayerPtr, Map.IsTargettingMode); Logic_Switch_Player_Context(old_player_ptr); UnitToTeleport = tech->As_Target(); fired = true; what = "CHRONO"; #ifdef FIXIT_CSII // checked - ajw 9/28/98 } #endif } } } } break; case SPC_CHRONO2: { TechnoClass * tech = (TechnoClass *)::As_Object(UnitToTeleport); CELL oldcell = cell; #ifdef FIXIT_CSII // checked - ajw 9/28/98 if (tech != NULL && tech->IsActive && tech->Is_Foot() && tech->What_Am_I() != RTTI_AIRCRAFT) { #else if (tech != NULL && tech->Is_Foot() && tech->What_Am_I() != RTTI_AIRCRAFT) { #endif /* ** Destroy any infantryman that gets teleported */ if (tech->What_Am_I() == RTTI_INFANTRY) { InfantryClass * inf = (InfantryClass *)tech; inf->Mark(MARK_UP); inf->Coord = Cell_Coord(cell); inf->Mark(MARK_DOWN); int damage = inf->Strength; inf->Take_Damage(damage, 0, WARHEAD_FIRE, 0, true); #ifdef FIXIT_CSII // checked - ajw 9/28/98 } else if(tech->What_Am_I() == RTTI_UNIT && *(UnitClass *)tech == UNIT_DEMOTRUCK) { tech->Assign_Target(tech->As_Target()); #endif } else { /* ** Warp the unit to the new location. */ DriveClass * drive = (DriveClass *)tech; drive->MoebiusCell = Coord_Cell(drive->Coord); oldcell = drive->MoebiusCell; drive->Teleport_To(cell); drive->IsMoebius = true; #ifdef FIXIT_CSII // checked - ajw 9/28/98 if(tech->What_Am_I() == RTTI_UNIT && *(UnitClass *)tech == UNIT_CHRONOTANK) { drive->IsMoebius = false; } drive->MoebiusCountDown = Rule.ChronoDuration * TICKS_PER_MINUTE; if (tech->What_Am_I() == RTTI_UNIT && *(UnitClass *)tech == UNIT_CHRONOTANK) { drive->MoebiusCountDown = ChronoTankDuration * TICKS_PER_MINUTE; } #else drive->MoebiusCountDown = Rule.ChronoDuration * TICKS_PER_MINUTE; #endif Scen.Do_BW_Fade(); Sound_Effect(VOC_CHRONO, drive->Coord); /* ** Set active animation on Chronospheres. */ for (int index = 0; index < Buildings.Count(); ++index) { BuildingClass* building = Buildings.Ptr(index); if (building != nullptr && building->IsActive && building->Owner() == Class->House && *building == STRUCT_CHRONOSPHERE) { building->Begin_Mode(BSTATE_ACTIVE); } } } } UnitToTeleport = TARGET_NONE; if (this == PlayerPtr) { Map.IsTargettingMode = SPC_NONE; } #ifdef FIXIT_CSII // checked - ajw 9/28/98 if(tech && tech->IsActive && (tech->What_Am_I() != RTTI_UNIT || *(UnitClass *)tech != UNIT_CHRONOTANK)) { #endif SuperWeapon[SPC_CHRONOSPHERE].Discharged(this == PlayerPtr); #ifdef FIXIT_CSII // checked - ajw 9/28/98 } #endif IsRecalcNeeded = true; fired = true; what = "CHRONO2"; /* ** Now set a percentage chance that a time quake will occur. */ if (!TimeQuake) { TimeQuake = Percent_Chance(Rule.QuakeChance * 100); } /* ** Now set a percentage chance that a chronal vortex will appear. It ** might appear where the object teleported to or it might appear ** where it teleported from -- random chance. */ #ifdef FIXIT_CSII // checked - ajw 9/28/98 // Don't allow a vortex if the teleportation was due to a chrono tank. if(tech && tech->IsActive && (tech->What_Am_I() != RTTI_UNIT || *(UnitClass *)tech != UNIT_CHRONOTANK)) #endif if (!ChronalVortex.Is_Active() && Percent_Chance(Rule.VortexChance * 100)) { int x = Random_Pick(0, Map.MapCellWidth-1); int y = Random_Pick(0, Map.MapCellHeight-1); ChronalVortex.Appear(Cell_Coord(XY_Cell(Map.MapCellX + x, Map.MapCellY + y))); // if (Percent_Chance(50)) { // ChronalVortex.Appear(Cell_Coord(oldcell)); // } else { // ChronalVortex.Appear(Cell_Coord(cell)); // } } break; } } /* ** Maybe trigger an achivement. ST - 12/2/2019 11:25AM */ if (IsHuman && fired && what) { On_Achievement_Event(this, "SUPERWEAPON_FIRED", what); } return(true); } /*********************************************************************************************** * HouseClass::Place_Object -- Places the object (building) at location specified. * * * * This routine is called when a building has been produced and now must be placed on * * the map. When the player clicks on the map, this routine is ultimately called when the * * event passes through the event queue system. * * * * INPUT: type -- The object type to place. The actual object is lifted from the sidebar. * * * * * * cell -- The location to place the object on the map. * * * * OUTPUT: Was the placement successful? * * * * WARNINGS: none * * * * HISTORY: * * 05/18/1995 JLB : Created. * *=============================================================================================*/ extern void On_Ping(const HouseClass* player_ptr, COORDINATE coord); bool HouseClass::Place_Object(RTTIType type, CELL cell) { assert(Houses.ID(this) == ID); TechnoClass * tech = 0; FactoryClass * factory = Fetch_Factory(type); /* ** Only if there is a factory active for this type, can it be "placed". ** In the case of a missing factory, then this request is completely bogus -- ** ignore it. This might occur if, between two events to exit the same ** object, the mouse was clicked on the sidebar to start building again. ** The second placement event should NOT try to place the object that is ** just starting construction. */ if (factory && factory->Has_Completed()) { tech = factory->Get_Object(); if (cell == -1) { TechnoClass * pending = factory->Get_Object(); if (pending != NULL) { #ifdef FIXIT_HELI_LANDING /* ** Try to find a place for the object to appear from. For helicopters, it has the ** option of finding a nearby helipad if no helipads are free. */ TechnoClass * builder = pending->Who_Can_Build_Me(false, false); if (builder == NULL && pending->What_Am_I() == RTTI_AIRCRAFT && !((AircraftClass *)pending)->Class->IsFixedWing) { builder = pending->Who_Can_Build_Me(true, false); } #else bool intheory = false; if (pending->What_Am_I() == RTTI_AIRCRAFT) { /* ** BG hack - helicopters don't need a specific building to ** emerge from, in fact, they'll land next to a building if ** need be. */ if( !((AircraftClass *)pending)->Class->IsFixedWing) { intheory = true; } } TechnoClass * builder = pending->Who_Can_Build_Me(intheory, false); #endif TechnoTypeClass const *object_type = pending->Techno_Type_Class(); if (builder != NULL && builder->Exit_Object(pending)) { /* ** Since the object has left the factory under its own power, delete ** the production manager tied to this slot in the sidebar. Its job ** has been completed. */ factory->Set_Is_Blocked(false); factory->Completed(); Abandon_Production(type); /* ** Could be tied to an achievement. ST - 11/11/2019 11:56AM */ if (IsHuman) { if (object_type) { On_Achievement_Event(this, "UNIT_CONSTRUCTED", object_type->IniName); } if (pending->IsActive) { On_Ping(this, pending->Center_Coord()); } } switch (pending->What_Am_I()) { case RTTI_UNIT: JustBuiltUnit = ((UnitClass*)pending)->Class->Type; IsBuiltSomething = true; break; case RTTI_VESSEL: JustBuiltVessel = ((VesselClass*)pending)->Class->Type; IsBuiltSomething = true; break; case RTTI_INFANTRY: JustBuiltInfantry = ((InfantryClass*)pending)->Class->Type; IsBuiltSomething = true; break; case RTTI_BUILDING: JustBuiltStructure = ((BuildingClass*)pending)->Class->Type; IsBuiltSomething = true; break; case RTTI_AIRCRAFT: JustBuiltAircraft = ((AircraftClass*)pending)->Class->Type; IsBuiltSomething = true; break; } } else { /* ** The object could not leave under it's own power. Just wait ** until the player tries to place the object again. */ /* ** Flag that it's blocked so we can re-try the exit later. ** This would have been a bad idea under the old peer-peer code since it would have pumped events into ** the queue too often. ST - 2/25/2020 11:56AM */ factory->Set_Is_Blocked(true); return(false); } } } else { if (tech) { TechnoClass * builder = tech->Who_Can_Build_Me(false, false); if (builder) { builder->Transmit_Message(RADIO_HELLO, tech); if (tech->Unlimbo(Cell_Coord(cell))) { factory->Completed(); Abandon_Production(type); if (PlayerPtr == this) { Sound_Effect(VOC_PLACE_BUILDING_DOWN); Map.Set_Cursor_Shape(0); Map.PendingObjectPtr = 0; Map.PendingObject = 0; Map.PendingHouse = HOUSE_NONE; } return(true); } else { if (this == PlayerPtr) { Speak(VOX_DEPLOY); } } builder->Transmit_Message(RADIO_OVER_OUT); } return(false); } else { // Play a bad sound here? return(false); } } } return(true); } /*********************************************************************************************** * HouseClass::Manual_Place -- Inform display system of building placement mode. * * * * This routine will inform the display system that building placement mode has begun. * * The cursor will be created that matches the layout of the building shape. * * * * INPUT: builder -- The factory that is building this object. * * * * object -- The building that is going to be placed down on the map. * * * * OUTPUT: Was the building placement mode successfully initiated? * * * * WARNINGS: This merely adjusts the cursor shape. Nothing that affects networked games * * is affected. * * * * HISTORY: * * 05/04/1995 JLB : Created. * * 05/30/1995 JLB : Uses the Bib_And_Offset() function to determine bib size. * *=============================================================================================*/ bool HouseClass::Manual_Place(BuildingClass * builder, BuildingClass * object) { assert(Houses.ID(this) == ID); if (this == PlayerPtr && !Map.PendingObject && builder && object) { /* ** Ensures that object selection doesn't remain when ** building placement takes place. */ Unselect_All(); Map.Repair_Mode_Control(0); Map.Sell_Mode_Control(0); Map.PendingObject = object->Class; Map.PendingObjectPtr = object; Map.PendingHouse = Class->House; Map.Set_Cursor_Shape(object->Occupy_List(true)); Map.Set_Cursor_Pos(Coord_Cell(builder->Coord)); builder->Mark(MARK_CHANGE); return(true); } return(false); } /*************************************************************************** * HouseClass::Clobber_All -- removes all objects for this house * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * This routine removes the house itself, so the multiplayer code * * must not rely on there being "empty" houses lying around. * * * * HISTORY: * * 05/16/1995 BRR : Created. * * 06/09/1995 JLB : Handles aircraft. * *=========================================================================*/ void HouseClass::Clobber_All(void) { assert(Houses.ID(this) == ID); int i; for (i = 0; i < ::Aircraft.Count(); i++) { if (::Aircraft.Ptr(i)->House == this) { delete ::Aircraft.Ptr(i); i--; } } for (i = 0; i < ::Units.Count(); i++) { if (::Units.Ptr(i)->House == this) { delete ::Units.Ptr(i); i--; } } for (i = 0; i < ::Vessels.Count(); i++) { if (::Vessels.Ptr(i)->House == this) { delete ::Vessels.Ptr(i); i--; } } for (i = 0; i < Infantry.Count(); i++) { if (Infantry.Ptr(i)->House == this) { delete Infantry.Ptr(i); i--; } } for (i = 0; i < Buildings.Count(); i++) { if (Buildings.Ptr(i)->House == this) { delete Buildings.Ptr(i); i--; } } for (i = 0; i < TeamTypes.Count(); i++) { if (TeamTypes.Ptr(i)->House == Class->House) { delete TeamTypes.Ptr(i); i--; } } for (i = 0; i < Triggers.Count(); i++) { if (Triggers.Ptr(i)->Class->House == Class->House) { delete Triggers.Ptr(i); i--; } } for (i = 0; i < TriggerTypes.Count(); i++) { if (TriggerTypes.Ptr(i)->House == Class->House) { delete TriggerTypes.Ptr(i); i--; } } delete this; } /*********************************************************************************************** * HouseClass::Detach -- Removes specified object from house tracking systems. * * * * This routine is called when an object is to be removed from the game system. If the * * specified object is part of the house tracking system, then it will be removed. * * * * INPUT: target -- The target value of the object that is 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: * * 05/18/1995 JLB : commented * *=============================================================================================*/ void HouseClass::Detach(TARGET target, bool ) { assert(Houses.ID(this) == ID); if (ToCapture == target) { ToCapture = TARGET_NONE; } if (Is_Target_Trigger(target)) { HouseTriggers[ID].Delete(As_Trigger(target)); } } /*********************************************************************************************** * HouseClass::Does_Enemy_Building_Exist -- Checks for enemy building of specified type. * * * * This routine will examine the enemy houses and if there is a building owned by one * * of those house, true will be returned. * * * * INPUT: btype -- The building type to check for. * * * * OUTPUT: Does a building of the specified type exist for one of the enemy houses? * * * * WARNINGS: none * * * * HISTORY: * * 05/23/1995 JLB : Created. * *=============================================================================================*/ bool HouseClass::Does_Enemy_Building_Exist(StructType btype) const { assert(Houses.ID(this) == ID); int bflag = 1L << btype; for (HousesType index = HOUSE_FIRST; index < HOUSE_COUNT; index++) { HouseClass * house = HouseClass::As_Pointer(index); if (house && !Is_Ally(house) && (house->ActiveBScan & bflag) != 0) { return(true); } } return(false); } /*********************************************************************************************** * HouseClass::Suggest_New_Object -- Determine what would the next buildable object be. * * * * This routine will examine the house status and return with a techno type pointer to * * the object type that it thinks should be created. The type is restricted to match the * * type specified. Typical use of this routine is by computer controlled factories. * * * * INPUT: objecttype -- The type of object to restrict the scan for. * * * * kennel -- Is this from a kennel? There are special hacks to ensure that only * * dogs can be produced from a kennel. * * * * OUTPUT: Returns with a pointer to a techno type for the object type that should be * * created. If no object should be created, then NULL is returned. * * * * WARNINGS: This is a time consuming routine. Only call when necessary. * * * * HISTORY: * * 05/23/1995 JLB : Created. * *=============================================================================================*/ TechnoTypeClass const * HouseClass::Suggest_New_Object(RTTIType objecttype, bool kennel) const { assert(Houses.ID(this) == ID); TechnoTypeClass const * techno = NULL; switch (objecttype) { case RTTI_AIRCRAFT: case RTTI_AIRCRAFTTYPE: if (BuildAircraft != AIRCRAFT_NONE) { return(&AircraftTypeClass::As_Reference(BuildAircraft)); } return(NULL); case RTTI_VESSEL: case RTTI_VESSELTYPE: if (BuildVessel != VESSEL_NONE) { return(&VesselTypeClass::As_Reference(BuildVessel)); } return(NULL); /* ** Unit construction is based on the rule that up to twice the number required ** to fill all teams will be created. */ case RTTI_UNIT: case RTTI_UNITTYPE: if (BuildUnit != UNIT_NONE) { return(&UnitTypeClass::As_Reference(BuildUnit)); } return(NULL); /* ** Infantry construction is based on the rule that up to twice the number required ** to fill all teams will be created. */ case RTTI_INFANTRY: case RTTI_INFANTRYTYPE: if (BuildInfantry != INFANTRY_NONE) { if (kennel && BuildInfantry != INFANTRY_DOG) return(NULL); if (!kennel && BuildInfantry == INFANTRY_DOG) return(NULL); return(&InfantryTypeClass::As_Reference(BuildInfantry)); } return(NULL); /* ** Building construction is based upon the preconstruction list. */ case RTTI_BUILDING: case RTTI_BUILDINGTYPE: if (BuildStructure != STRUCT_NONE) { return(&BuildingTypeClass::As_Reference(BuildStructure)); } return(NULL); } return(techno); } /*********************************************************************************************** * HouseClass::Flag_Remove -- Removes the flag from the specified target. * * * * This routine will remove the flag attached to the specified target object or cell. * * Call this routine before placing the object down. This is called inherently by the * * the Flag_Attach() functions. * * * * INPUT: target -- The target that the flag was attached to but will be removed from. * * * * set_home -- if true, clears the flag's waypoint designation * * * * OUTPUT: Was the flag successfully removed from the specified target? * * * * WARNINGS: none * * * * HISTORY: * * 05/23/1995 JLB : Created. * *=============================================================================================*/ bool HouseClass::Flag_Remove(TARGET target, bool set_home) { assert(Houses.ID(this) == ID); bool rc = false; if (Target_Legal(target)) { /* ** Remove the flag from a unit */ UnitClass * object = As_Unit(target); if (object) { rc = object->Flag_Remove(); if (rc && FlagLocation == target) { FlagLocation = TARGET_NONE; } } else { /* ** Remove the flag from a cell */ CELL cell = As_Cell(target); if (Map.In_Radar(cell)) { rc = Map[cell].Flag_Remove(); if (rc && FlagLocation == target) { FlagLocation = TARGET_NONE; } } } /* ** Handle the flag home cell: ** If 'set_home' is set, clear the home value & the cell's overlay */ if (set_home) { if (FlagHome != 0) { Map[FlagHome].Overlay = OVERLAY_NONE; Map.Flag_Cell(FlagHome); FlagHome = 0; } } } return(rc); } /*********************************************************************************************** * HouseClass::Flag_Attach -- Attach flag to specified cell (or thereabouts). * * * * This routine will attach the house flag to the location specified. If the location * * cannot contain the flag, then a suitable nearby location will be selected. * * * * INPUT: cell -- The desired cell location to place the flag. * * * * set_home -- if true, resets the flag's waypoint designation * * * * OUTPUT: Was the flag successfully placed? * * * * WARNINGS: The cell picked for the flag might very likely not be the cell requested. * * Check the FlagLocation value to determine the final cell resting spot. * * * * HISTORY: * * 05/23/1995 JLB : Created. * * 10/08/1996 JLB : Uses map nearby cell scanning handler. * *=============================================================================================*/ bool HouseClass::Flag_Attach(CELL cell, bool set_home) { assert(Houses.ID(this) == ID); bool rc; bool clockwise; /* ** Randomly decide if we're going to search cells clockwise or counter- ** clockwise */ clockwise = Percent_Chance(50); /* ** Only continue if this cell is a legal placement cell. */ if (Map.In_Radar(cell)) { /* ** If the flag already exists, then it must be removed from the object ** it is attached to. */ Flag_Remove(FlagLocation, set_home); /* ** Attach the flag to the cell specified. If it can't be placed, then pick ** a nearby cell where it can be placed. */ CELL newcell = cell; rc = Map[newcell].Flag_Place(Class->House); if (!rc) { newcell = Map.Nearby_Location(cell, SPEED_TRACK, -1, MZONE_NORMAL, true); if (newcell != 0) { rc = Map[newcell].Flag_Place(Class->House); } #ifdef OBSOLETE /* ** Loop for increasing distance from the desired cell. ** For each distance, randomly pick a starting direction. Between ** this and the clockwise/counterclockwise random value, the flag ** should appear to be placed fairly randomly. */ for (int dist = 1; dist < 32; dist++) { FacingType fcounter; FacingType rot; /* ** Clockwise search. */ if (clockwise) { rot = Random_Pick(FACING_N, FACING_NW); for (fcounter = FACING_N; fcounter <= FACING_NW; fcounter++) { newcell = Coord_Cell(Coord_Move(Cell_Coord(cell), Facing_Dir(rot), dist*256)); if (Map.In_Radar(newcell) && Map[newcell].Flag_Place(Class->House)) { dist = 32; rc = true; break; } rot++; if (rot > FACING_NW) rot = FACING_N; } } else { /* ** Counter-clockwise search */ rot = Random_Pick(FACING_N, FACING_NW); for (fcounter = FACING_NW; fcounter >= FACING_N; fcounter--) { newcell = Coord_Cell(Coord_Move(Cell_Coord(cell), Facing_Dir(rot), dist*256)); if (Map.In_Radar(newcell) && Map[newcell].Flag_Place(Class->House)) { dist = 32; rc = true; break; } rot--; if (rot < FACING_N) rot = FACING_NW; } } } #endif } /* ** If we've found a spot for the flag, place the flag at the new cell. ** if 'set_home' is set, OR this house has no current flag home cell, ** mark that cell as this house's flag home cell. Otherwise fall back ** on returning the flag to its home. */ if (rc) { FlagLocation = As_Target(newcell); if (set_home || FlagHome == 0) { Map[newcell].Overlay = OVERLAY_FLAG_SPOT; Map[newcell].OverlayData = 0; Map[newcell].Recalc_Attributes(); FlagHome = newcell; } } else if (FlagHome != 0) { rc = Map[FlagHome].Flag_Place(Class->House); } return(rc); } return(false); } /*********************************************************************************************** * HouseClass::Flag_Attach -- Attaches the house flag the specified unit. * * * * This routine will attach the house flag to the specified unit. This routine is called * * when a unit drives over a cell containing a flag. * * * * INPUT: object -- Pointer to the object that the house flag is to be attached to. * * * * set_home -- if true, clears the flag's waypoint designation * * * * OUTPUT: Was the flag attached successfully? * * * * WARNINGS: none * * * * HISTORY: * * 05/23/1995 JLB : Created. * *=============================================================================================*/ bool HouseClass::Flag_Attach(UnitClass * object, bool set_home) { assert(Houses.ID(this) == ID); if (object && !object->IsInLimbo) { Flag_Remove(FlagLocation, set_home); /* ** Attach the flag to the object. */ object->Flag_Attach(Class->House); FlagLocation = object->As_Target(); return(true); } return(false); } extern void On_Defeated_Message(const char* message, float timeout_seconds); /*************************************************************************** * HouseClass::MPlayer_Defeated -- multiplayer; house is defeated * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 05/25/1995 BRR : Created. * *=========================================================================*/ void HouseClass::MPlayer_Defeated(void) { assert(Houses.ID(this) == ID); char txt[80]; int i,j; unsigned char id; HouseClass * hptr; HouseClass * hptr2; int num_alive; int num_humans; int all_allies; /* ** Set the defeat flag for this house */ IsDefeated = true; /* ** If this is a computer controlled house, then all computer controlled ** houses become paranoid. */ if (IQ == Rule.MaxIQ && !IsHuman && Rule.IsComputerParanoid) { Computer_Paranoid(); } /* ** Remove this house's flag & flag home cell */ if (Special.IsCaptureTheFlag) { if (FlagLocation) { Flag_Remove(FlagLocation, true); } else { if (FlagHome != 0) { Flag_Remove(FlagHome, true); } } } /* ** Remove any one-time superweapons the player might have. */ for (i = SPC_FIRST; i < SPC_COUNT; i++) { SuperWeapon[i].Remove(true); } /* ** If this is me: ** - Set MPlayerObiWan, so I can only send messages to all players, and ** not just one (so I can't be obnoxiously omnipotent) ** - Reveal the map ** - Add my defeat message */ if (PlayerPtr == this) { Session.ObiWan = 1; HidPage.Clear(); Map.Flag_To_Redraw(true); /* ** Pop up a message showing that I was defeated */ sprintf(txt, Text_String(TXT_PLAYER_DEFEATED), IniName); if (Session.Type == GAME_NORMAL) { Session.Messages.Add_Message(NULL, 0, txt, Session.ColorIdx, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_FULLSHADOW, Rule.MessageDelay * TICKS_PER_MINUTE); } Map.Flag_To_Redraw(false); if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { int timeout = Rule.MessageDelay * TICKS_PER_MINUTE; On_Defeated_Message(txt, timeout * 60.0f / TICKS_PER_MINUTE); Sound_Effect(VOC_INCOMING_MESSAGE); } } else { /* ** If it wasn't me, find out who was defeated */ if (IsHuman) { sprintf (txt, Text_String(TXT_PLAYER_DEFEATED), IniName); //Session.Messages.Add_Message(NULL, 0, txt, RemapColor, // TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, Rule.MessageDelay * TICKS_PER_MINUTE); Map.Flag_To_Redraw(false); RedrawOptionsMenu = true; int timeout = Rule.MessageDelay * TICKS_PER_MINUTE; On_Defeated_Message(txt, timeout * 60.0f / TICKS_PER_MINUTE); Sound_Effect(VOC_INCOMING_MESSAGE); } } /* ** Find out how many players are left alive. */ num_alive = 0; num_humans = 0; for (i = 0; i < Session.MaxPlayers; i++) { hptr = HouseClass::As_Pointer((HousesType)(HOUSE_MULTI1 + i)); if (hptr && !hptr->IsDefeated) { if (hptr->IsHuman) { num_humans++; } num_alive++; } } /* ** If all the houses left alive are allied with each other, then in reality ** there's only one player left: */ all_allies = 1; for (i = 0; i < Session.MaxPlayers; i++) { /* ** Get a pointer to this house */ hptr = HouseClass::As_Pointer((HousesType)(HOUSE_MULTI1 + i)); if (!hptr || hptr->IsDefeated) continue; /* ** Loop through all houses; if there's one left alive that this house ** isn't allied with, then all_allies will be false */ for (j = 0; j < Session.MaxPlayers; j++) { hptr2 = HouseClass::As_Pointer((HousesType)(HOUSE_MULTI1 + j)); if (!hptr2) { continue; } if (!hptr2->IsDefeated && !hptr->Is_Ally(hptr2)) { all_allies = 0; break; } } if (!all_allies) { break; } } /* ** If all houses left are allies, set 'num_alive' to 1; game over. */ if (all_allies) { num_alive = 1; } /* ** If there's only one human player left or no humans left, the game is over: ** - Determine whether this player wins or loses, based on the state of the ** player's IsDefeated flag ** - Find all players' indices in the Session.Score array ** - Tally up scores for this game */ if (num_alive == 1 || num_humans == 0) { if (PlayerPtr->IsDefeated) { PlayerLoses = true; } else { PlayerWins = true; } /* ** Add up the scores */ Tally_Score(); /* ** Destroy all the IPX connections, since we have to go through the rest ** of the Main_Loop() before we detect that the game is over, and we'll ** end up waiting for frame sync packets from the other machines. */ if (Session.Type==GAME_IPX || Session.Type == GAME_INTERNET) { i = 0; while (Ipx.Num_Connections() && (i++ < 1000) ) { id = Ipx.Connection_ID(0); Ipx.Delete_Connection(id); } Session.NumPlayers = 0; } #if(TEN) // // Tell the TEN server who won // if (Session.Type == GAME_TEN) { Send_TEN_Win_Packet(); } #endif // TEN #if(MPATH) // // Tell the MPATH server who won // if (Session.Type == GAME_MPATH) { FILE *fp; fp = fopen("winner.txt","wt"); if (fp) { for (i = 0; i < Session.Players.Count(); i++) { hptr = HouseClass::As_Pointer(Session.Players[i]->Player.ID); if (!hptr->IsDefeated) { fprintf(fp,"%s\n",hptr->IniName); } } fclose(fp); } } #endif // MPATH } } /*************************************************************************** * HouseClass::Tally_Score -- Fills in the score system for this round * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/29/1995 BRR : Created. * *=========================================================================*/ void HouseClass::Tally_Score(void) { HousesType house; HousesType house2; HouseClass * hptr; int score_index; int i,j,k; int max_index; int max_count; int count; /* ** Loop through all houses, tallying up each player's score */ for (house = HOUSE_FIRST; house < HOUSE_COUNT; house++) { hptr = HouseClass::As_Pointer(house); /* ** Skip this house if it's not human. */ if (!hptr || !hptr->IsHuman) { continue; } /* ** Now find out where this player is in the score array */ score_index = -1; for (i = 0; i < Session.NumScores; i++) { if (!stricmp(hptr->IniName, Session.Score[i].Name)) { score_index = i; break; } } /* ** If the index is still -1, the name wasn't found; add a new entry. */ if (score_index == -1) { /* ** Just add this player to the end of the array, if there's room */ if (Session.NumScores < MAX_MULTI_NAMES) { score_index = Session.NumScores; Session.NumScores++; } /* ** If there's not room, we have to remove somebody. ** For each player in the scores array, count the # of '-1' entries ** from this game backwards; the one with the most is the one that ** hasn't played the longest; replace him with this new guy. */ else { max_index = 0; max_count = 0; for (j = 0; j < Session.NumScores; j++) { count = 0; for (k = Session.NumScores - 1; k >= 0; k--) { if (Session.Score[j].Kills[k]==-1) { count++; } else { break; } } if (count > max_count) { max_count = count; max_index = j; } } score_index = max_index; } /* ** Initialize this new score entry */ Session.Score[score_index].Wins = 0; strcpy (Session.Score[score_index].Name, hptr->IniName); for (j = 0; j < MAX_MULTI_GAMES; j++) Session.Score[score_index].Kills[j] = -1; } /* ** Init this player's Kills to 0 (-1 means he didn't play this round; ** 0 means he played but got no kills). */ Session.Score[score_index].Kills[Session.CurGame] = 0; /* ** Init this player's color to his last-used color index */ Session.Score[score_index].Color = hptr->RemapColor; /* ** If this house was undefeated, it must have been the winner. ** (If no human houses are undefeated, the computer won.) */ if (!hptr->IsDefeated) { Session.Score[score_index].Wins++; Session.Winner = score_index; } /* ** Tally up all kills for this player */ for (house2 = HOUSE_FIRST; house2 < HOUSE_COUNT; house2++) { Session.Score[score_index].Kills[Session.CurGame] += hptr->UnitsKilled[house2]; Session.Score[score_index].Kills[Session.CurGame] += hptr->BuildingsKilled[house2]; } } } /*************************************************************************** * HouseClass::Blowup_All -- blows up everything * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 05/16/1995 BRR : Created. * * 06/09/1995 JLB : Handles aircraft. * * 05/07/1996 JLB : Handles ships. * *=========================================================================*/ void HouseClass::Blowup_All(void) { assert(Houses.ID(this) == ID); int i; int damage; UnitClass * uptr; InfantryClass * iptr; BuildingClass * bptr; int count; WarheadType warhead; /* ** Find everything owned by this house & blast it with a huge amount of damage ** at zero range. Do units before infantry, so the units' drivers are killed ** too. Using Explosion_Damage is like dropping a big bomb right on the ** object; it will also damage anything around it. */ for (i = 0; i < ::Units.Count(); i++) { if (::Units.Ptr(i)->House == this && !::Units.Ptr(i)->IsInLimbo) { uptr = ::Units.Ptr(i); /* ** Some units can't be killed with one shot, so keep damaging them until ** they're gone. The unit will destroy itself, and put an infantry in ** its place. When the unit destroys itself, decrement 'i' since ** its pointer will be removed from the active pointer list. */ count = 0; while (::Units.Ptr(i)==uptr && uptr->Strength) { // MBL 06.22.2020 RA: Not all aircraft die in this case; See https://jaas.ea.com/browse/TDRA-6840 // Likely due to damage biasing based on RA factions and/or difficulty settings // Applying this to units (vehicles), ships, buildings, and infantry, too // // damage = uptr->Strength; // Original damage = 0x7fff; // Copied from TD uptr->Take_Damage(damage, 0, WARHEAD_HE, NULL, true); count++; if (count > 5 && uptr->IsActive) { delete uptr; break; } } i--; } } /* ** Destroy all aircraft owned by this house. */ for (i = 0; i < ::Aircraft.Count(); i++) { if (::Aircraft.Ptr(i)->House == this && !::Aircraft.Ptr(i)->IsInLimbo) { AircraftClass * aptr = ::Aircraft.Ptr(i); // MBL 06.22.2020 RA: Not all aircraft die in this case; See https://jaas.ea.com/browse/TDRA-6840 // Likely due to damage biasing based on RA factions and/or difficulty settings // Applying this to units (vehicles), ships, buildings, and infantry, too // // damage = aptr->Strength; // Original damage = 0x7fff; // Copied from TD aptr->Take_Damage(damage, 0, WARHEAD_HE, NULL, true); if (!aptr->IsActive) { i--; } } } /* ** Destroy all vessels owned by this house. */ for (i = 0; i < ::Vessels.Count(); i++) { if (::Vessels.Ptr(i)->House == this && !::Vessels.Ptr(i)->IsInLimbo) { VesselClass * vptr = ::Vessels.Ptr(i); // MBL 06.22.2020 RA: Not all aircraft die in this case; See https://jaas.ea.com/browse/TDRA-6840 // Likely due to damage biasing based on RA factions and/or difficulty settings // Applying this to units (vehicles), ships, buildings, and infantry, too // // damage = vptr->Strength; // Original damage = 0x7fff; // Copied from TD vptr->Take_Damage(damage, 0, WARHEAD_HE, NULL, true); if (!vptr->IsActive) { i--; } } } /* ** Buildings don't delete themselves when they die; they shake the screen ** and begin a countdown, so don't decrement 'i' when it's destroyed. */ for (i = 0; i < Buildings.Count(); i++) { if (Buildings.Ptr(i)->House == this && !Buildings.Ptr(i)->IsInLimbo) { bptr = Buildings.Ptr(i); count = 0; while (Buildings.Ptr(i)==bptr && bptr->Strength) { // MBL 06.22.2020 RA: Not all aircraft die in this case; See https://jaas.ea.com/browse/TDRA-6840 // Likely due to damage biasing based on RA factions and/or difficulty settings // Applying this to units (vehicles), ships, buildings, and infantry, too // // damage = bptr->Strength; // Original damage = 0x7fff; // Copied from TD bptr->Take_Damage(damage, 0, WARHEAD_HE, NULL, true); count++; if (count > 5) { delete bptr; break; } } } } /* ** Infantry don't delete themselves when they die; they go into a death- ** animation sequence, so there's no need to decrement 'i' when they die. ** Infantry should die by different types of warheads, so their death ** anims aren't all synchronized. */ for (i = 0; i < Infantry.Count(); i++) { if (Infantry.Ptr(i)->House == this && !Infantry.Ptr(i)->IsInLimbo) { iptr = Infantry.Ptr(i); count = 0; while (Infantry.Ptr(i)==iptr && iptr->Strength) { // MBL 06.22.2020 RA: Not all aircraft die in this case; See https://jaas.ea.com/browse/TDRA-6840 // Likely due to damage biasing based on RA factions and/or difficulty settings // Applying this to units (vehicles), ships, buildings, and infantry, too // // damage = iptr->Strength; // Original damage = 0x7fff; // Copied from TD warhead = Random_Pick(WARHEAD_SA, WARHEAD_FIRE); iptr->Take_Damage(damage, 0, warhead, NULL, true); count++; if (count > 5) { delete iptr; break; } } } } } /*********************************************************************************************** * HouseClass::Flag_To_Die -- Flags the house to blow up soon. * * * * When this routine is called, the house will blow up after a period of time. Typically * * this is called when the flag is captured or the HQ destroyed. * * * * INPUT: none * * * * OUTPUT: Was the house flagged to blow up? * * * * WARNINGS: none * * * * HISTORY: * * 06/20/1995 JLB : Created. * *=============================================================================================*/ bool HouseClass::Flag_To_Die(void) { assert(Houses.ID(this) == ID); if (!IsToWin && !IsToDie && !IsToLose) { IsToDie = true; BorrowedTime = TICKS_PER_MINUTE * Rule.SavourDelay; } return(IsToDie); } /*********************************************************************************************** * HouseClass::Flag_To_Win -- Flags the house to win soon. * * * * When this routine is called, the house will be declared the winner after a period of * * time. * * * * INPUT: none * * * * OUTPUT: Was the house flagged to win? * * * * WARNINGS: none * * * * HISTORY: * * 06/20/1995 JLB : Created. * *=============================================================================================*/ bool HouseClass::Flag_To_Win(void) { assert(Houses.ID(this) == ID); if (!IsToWin && !IsToDie && !IsToLose) { IsToWin = true; BorrowedTime = TICKS_PER_MINUTE * Rule.SavourDelay; } return(IsToWin); } /*********************************************************************************************** * HouseClass::Flag_To_Lose -- Flags the house to die soon. * * * * When this routine is called, it will spell the doom of this house. In a short while * * all of the object owned by this house will explode. Typical use of this routine is when * * the flag has been captured or the command vehicle has been destroyed. * * * * INPUT: none * * * * OUTPUT: Has the doom been initiated? * * * * WARNINGS: none * * * * HISTORY: * * 06/12/1995 JLB : Created. * *=============================================================================================*/ bool HouseClass::Flag_To_Lose(void) { assert(Houses.ID(this) == ID); IsToWin = false; if (!IsToDie && !IsToLose) { IsToLose = true; BorrowedTime = TICKS_PER_MINUTE * Rule.SavourDelay; } return(IsToLose); } /*********************************************************************************************** * HouseClass::Init_Data -- Initializes the multiplayer color data. * * * * This routine is called when initializing the color and remap data for this house. The * * primary user of this routine is the multiplayer version of the game, especially for * * saving & loading multiplayer games. * * * * INPUT: color -- The color of this house. * * * * house -- The house that this should act like. * * * * credits -- The initial credits to assign to this house. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 07/29/1995 JLB : Created. * *=============================================================================================*/ extern bool NowSavingGame; // TEMP MBL: Need to discuss better solution with Steve void HouseClass::Init_Data(PlayerColorType color, HousesType house, int credits) { assert(Houses.ID(this) == ID); Credits = Control.InitialCredits = credits; VisibleCredits.Current = Credits; RemapColor = color; ActLike = house; // MBL 03.20.2020 // Attempt to fix Red Alert credit tick-up bug after saving a game that has had harvesting underway // Note that this code gets called with both game loads and saves // When this function is called, sometimes credits value has Tiberium (or HarvestedCredits?) variables applied, and sometimes now // if (NowSavingGame == true) { // At this point VisibleCredits.Current (set above) does not have harvested ore/tiberium applied, but VisibleCredits.Credits does VisibleCredits.Current = VisibleCredits.Credits; } } /*********************************************************************************************** * HouseClass::Power_Fraction -- Fetches the current power output rating. * * * * Use this routine to fetch the current power output as a fixed point fraction. The * * value 0x0100 is 100% power. * * * * INPUT: none * * * * OUTPUT: Returns with power rating as a fixed pointer number. * * * * WARNINGS: none * * * * HISTORY: * * 07/22/1995 JLB : Created. * *=============================================================================================*/ fixed HouseClass::Power_Fraction(void) const { assert(Houses.ID(this) == ID); if (Power >= Drain || Drain == 0) return(1); if (Power) { return(fixed(Power, Drain)); } return(0); } /*********************************************************************************************** * HouseClass::Sell_Wall -- Tries to sell the wall at the specified location. * * * * This routine will try to sell the wall at the specified location. If there is a wall * * present and it is owned by this house, then it can be sold. * * * * INPUT: cell -- The cell that wall selling is desired. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 08/05/1995 JLB : Created. * * 11/02/1996 JLB : Checks unsellable bit for wall type. * *=============================================================================================*/ void HouseClass::Sell_Wall(CELL cell) { assert(Houses.ID(this) == ID); if ((unsigned)cell > 0) { OverlayType overlay = Map[cell].Overlay; if (overlay != OVERLAY_NONE && Map[cell].Owner == Class->House) { OverlayTypeClass const & optr = OverlayTypeClass::As_Reference(overlay); if (optr.IsWall) { BuildingTypeClass const * btype = NULL; switch (overlay) { case OVERLAY_SANDBAG_WALL: btype = &BuildingTypeClass::As_Reference(STRUCT_SANDBAG_WALL); break; case OVERLAY_CYCLONE_WALL: btype = &BuildingTypeClass::As_Reference(STRUCT_CYCLONE_WALL); break; case OVERLAY_BRICK_WALL: btype = &BuildingTypeClass::As_Reference(STRUCT_BRICK_WALL); break; case OVERLAY_BARBWIRE_WALL: btype = &BuildingTypeClass::As_Reference(STRUCT_BARBWIRE_WALL); break; case OVERLAY_WOOD_WALL: btype = &BuildingTypeClass::As_Reference(STRUCT_WOOD_WALL); break; case OVERLAY_FENCE: btype = &BuildingTypeClass::As_Reference(STRUCT_FENCE); break; default: break; } if (btype != NULL && !btype->IsUnsellable) { if (PlayerPtr == this) { Sound_Effect(VOC_CASHTURN); } Refund_Money(btype->Raw_Cost() * Rule.RefundPercent); Map[cell].Overlay = OVERLAY_NONE; Map[cell].OverlayData = 0; Map[cell].Owner = HOUSE_NONE; Map[cell].Wall_Update(); CellClass * ncell = Map[cell].Adjacent_Cell(FACING_N); if (ncell) ncell->Wall_Update(); CellClass * wcell = Map[cell].Adjacent_Cell(FACING_W); if (wcell) wcell->Wall_Update(); CellClass * scell = Map[cell].Adjacent_Cell(FACING_S); if (scell) scell->Wall_Update(); CellClass * ecell = Map[cell].Adjacent_Cell(FACING_E); if (ecell) ecell->Wall_Update(); Map[cell].Recalc_Attributes(); Map[cell].Redraw_Objects(); Map.Radar_Pixel(cell); Detach_This_From_All(::As_Target(cell), true); if (optr.IsCrushable) { Map.Zone_Reset(MZONEF_NORMAL); } else { Map.Zone_Reset(MZONEF_CRUSHER|MZONEF_NORMAL); } } } } } } /*********************************************************************************************** * HouseClass::Suggest_New_Building -- Examines the situation and suggests a building. * * * * This routine is called when a construction yard needs to know what to build next. It * * will either examine the prebuilt base list or try to figure out what to build next * * based on the current game situation. * * * * INPUT: none * * * * OUTPUT: Returns with a pointer to the building type class to build. * * * * WARNINGS: none * * * * HISTORY: * * 09/27/1995 JLB : Created. * *=============================================================================================*/ BuildingTypeClass const * HouseClass::Suggest_New_Building(void) const { assert(Houses.ID(this) == ID); if (BuildStructure != STRUCT_NONE) { return(&BuildingTypeClass::As_Reference(BuildStructure)); } return(NULL); } /*********************************************************************************************** * HouseClass::Find_Building -- Finds a building of specified type. * * * * This routine is used to find a building of the specified type. This is particularly * * useful for when some event requires a specific building instance. The nuclear missile * * launch is a good example. * * * * INPUT: type -- The building type to scan for. * * * * zone -- The zone that the building must be located in. If no zone specific search * * is desired, then pass ZONE_NONE. * * * * OUTPUT: Returns with a pointer to the building type requested. If there is no building * * of the type requested, then NULL is returned. * * * * WARNINGS: none * * * * HISTORY: * * 09/27/1995 JLB : Created. * * 10/02/1995 JLB : Allows for zone specifics. * *=============================================================================================*/ BuildingClass * HouseClass::Find_Building(StructType type, ZoneType zone) const { assert(Houses.ID(this) == ID); /* ** Only scan if we KNOW there is at least one building of the type ** requested. */ if (BQuantity[type] > 0) { /* ** Search for a suitable launch site for this missile. */ for (int index = 0; index < Buildings.Count(); index++) { BuildingClass * b = Buildings.Ptr(index); if (b && !b->IsInLimbo && b->House == this && *b == type) { if (zone == ZONE_NONE || Which_Zone(b) == zone) { return(b); } } } } return(NULL); } /*********************************************************************************************** * HouseClass::Find_Build_Location -- Finds a suitable building location. * * * * This routine is used to find a suitable building location for the building specified. * * The auto base building logic uses this when building the base for the computer. * * * * INPUT: building -- Pointer to the building that needs to be placed down. * * * * OUTPUT: Returns with the coordinate to place the building at. If there are no suitable * * locations, then NULL is returned. * * * * WARNINGS: none * * * * HISTORY: * * 09/27/1995 JLB : Created. * *=============================================================================================*/ COORDINATE HouseClass::Find_Build_Location(BuildingClass * building) const { assert(Houses.ID(this) == ID); int zonerating[ZONE_COUNT]; struct { int AntiAir; // Average air defense for the base. int AntiArmor; // Average armor defense for the base. int AntiInfantry; // Average infantry defense for the base. } zoneinfo = {0,0,0}; int antiair = building->Anti_Air(); int antiarmor = building->Anti_Armor(); int antiinfantry = building->Anti_Infantry(); bool adj = true; /* ** Never place combat buildings adjacent to each other. This is partly ** because combat buildings don't have a bib and jamming will occur as well ** as because spacing defensive buildings out will yield a better ** defense. */ if (antiair || antiarmor || antiinfantry) { adj = false; } /* ** Determine the average zone strengths for the base. This value is ** used to determine what zones are considered under or over strength. */ ZoneType z; for (z = ZONE_NORTH; z < ZONE_COUNT; z++) { zoneinfo.AntiAir += ZoneInfo[z].AirDefense; zoneinfo.AntiArmor += ZoneInfo[z].ArmorDefense; zoneinfo.AntiInfantry += ZoneInfo[z].InfantryDefense; } zoneinfo.AntiAir /= ZONE_COUNT-ZONE_NORTH; zoneinfo.AntiArmor /= ZONE_COUNT-ZONE_NORTH; zoneinfo.AntiInfantry /= ZONE_COUNT-ZONE_NORTH; /* ** Give each zone a rating for value. The higher the value the more desirable ** to place the specified building in that zone. Factor the average value of ** zone defense such that more weight is given to zones that are very under ** defended. */ memset(&zonerating[0], '\0', sizeof(zonerating)); for (z = ZONE_FIRST; z < ZONE_COUNT; z++) { int diff; diff = zoneinfo.AntiAir-ZoneInfo[z].AirDefense; if (z == ZONE_CORE) diff /= 2; if (diff > 0) { zonerating[z] += min(antiair, diff); } diff = zoneinfo.AntiArmor-ZoneInfo[z].ArmorDefense; if (z == ZONE_CORE) diff /= 2; if (diff > 0) { zonerating[z] += min(antiarmor, diff); } diff = zoneinfo.AntiInfantry-ZoneInfo[z].InfantryDefense; if (z == ZONE_CORE) diff /= 2; if (diff > 0) { zonerating[z] += min(antiinfantry, diff); } } /* ** Now that each zone has been given a desirability rating, find the zone ** with the greatest value and try to place the building in that zone. */ ZoneType zone = Random_Pick(ZONE_FIRST, ZONE_WEST); int largest = 0; for (z = ZONE_FIRST; z < ZONE_COUNT; z++) { if (zonerating[z] > largest) { zone = z; largest = zonerating[z]; } } CELL zcell = Find_Cell_In_Zone(building, zone); if (zcell) { return(Cell_Coord(zcell)); } /* ** Could not build in preferred zone, so try building in any zone. */ static ZoneType _zones[] = {ZONE_CORE, ZONE_NORTH, ZONE_SOUTH, ZONE_EAST, ZONE_WEST}; int start = Random_Pick(0, ARRAY_SIZE(_zones)-1); for (int zz = 0; zz < ARRAY_SIZE(_zones); zz++) { ZoneType tryzone = _zones[(zz + start) % ARRAY_SIZE(_zones)]; zcell = Find_Cell_In_Zone(building, tryzone); if (zcell) return(zcell); } return(NULL); } /*********************************************************************************************** * HouseClass::Recalc_Center -- Recalculates the center point of the base. * * * * This routine will average the location of the base and record the center point. The * * recorded center point is used to determine such things as how far the base is spread * * out and where to protect the most. This routine should be called whenever a building * * is created or destroyed. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 09/28/1995 JLB : Created. * *=============================================================================================*/ void HouseClass::Recalc_Center(void) { assert(Houses.ID(this) == ID); /* ** First presume that there is no base. If there is a base, then these values will be ** properly filled in below. */ Center = 0; Radius = 0; for (ZoneType zone = ZONE_FIRST; zone < ZONE_COUNT; zone++) { ZoneInfo[zone].AirDefense = 0; ZoneInfo[zone].ArmorDefense = 0; ZoneInfo[zone].InfantryDefense = 0; } /* ** Only process the center base size/position calculation if there are buildings to ** consider. When no buildings for this house are present, then no processing need ** occur. */ if (CurBuildings > 0) { int x = 0; int y = 0; int count = 0; int index; for (index = 0; index < Buildings.Count(); index++) { BuildingClass const * b = Buildings.Ptr(index); if (b != NULL && !b->IsInLimbo && (HouseClass *)b->House == this && b->Strength > 0) { /* ** Give more "weight" to buildings that cost more. The presumption is that cheap ** buildings don't affect the base disposition as much as the more expensive ** buildings do. */ int weight = (b->Class->Cost_Of() / 1000)+1; for (int i = 0; i < weight; i++) { x += Coord_X(b->Center_Coord()); y += Coord_Y(b->Center_Coord()); count++; } } } /* ** This second check for quantity of buildings is necessary because the first ** check against CurBuildings doesn't take into account if the building is in ** limbo, but for base calculation, the limbo state disqualifies a building ** from being processed. Thus, CurBuildings may indicate a base, but count may ** not match. */ if (count > 0) { x /= count; y /= count; #ifdef NEVER /* ** Bias the center of the base away from the edges of the map. */ LEPTON left = Cell_To_Lepton(Map.MapCellX + 10); LEPTON top = Cell_To_Lepton(Map.MapCellY + 10); LEPTON right = Cell_To_Lepton(Map.MapCellX + Map.MapCellWidth - 10); LEPTON bottom = Cell_To_Lepton(Map.MapCellY + Map.MapCellHeight - 10); if (x < left) x = left; if (x > right) x = right; if (y < top) y = top; if (y > bottom) y = bottom; #endif Center = XY_Coord(x, y); } /* ** If there were any buildings discovered as legal to consider as part of the base, ** then figure out the general average radius of the building disposition as it ** relates to the center of the base. */ if (count > 1) { int radius = 0; for (index = 0; index < Buildings.Count(); index++) { BuildingClass const * b = Buildings.Ptr(index); if (b != NULL && !b->IsInLimbo && (HouseClass *)b->House == this && b->Strength > 0) { radius += Distance(Center, b->Center_Coord()); } } Radius = max(radius / count, 2 * CELL_LEPTON_W); /* ** Determine the relative strength of each base defense zone. */ for (index = 0; index < Buildings.Count(); index++) { BuildingClass const * b = Buildings.Ptr(index); if (b != NULL && !b->IsInLimbo && (HouseClass *)b->House == this && b->Strength > 0) { ZoneType z = Which_Zone(b); if (z != ZONE_NONE) { ZoneInfo[z].ArmorDefense += b->Anti_Armor(); ZoneInfo[z].AirDefense += b->Anti_Air(); ZoneInfo[z].InfantryDefense += b->Anti_Infantry(); } } } } else { Radius = 0x0200; } } } /*********************************************************************************************** * HouseClass::Expert_AI -- Handles expert AI processing. * * * * This routine is called when the computer should perform expert AI processing. This * * method of AI is categorized as an "Expert System" process. * * * * INPUT: none * * * * OUTPUT: Returns the number of game frames to delay before calling this routine again. * * * * WARNINGS: This is relatively time consuming -- call periodically. * * * * HISTORY: * * 09/29/1995 JLB : Created. * *=============================================================================================*/ int HouseClass::Expert_AI(void) { assert(Houses.ID(this) == ID); BuildingClass * b = 0; bool stop = false; int time = TICKS_PER_SECOND * 10; /* ** If the current enemy no longer has a base or is defeated, then don't consider ** that house a threat anymore. Clear out the enemy record and then try ** to find a new enemy. */ if (Enemy != HOUSE_NONE) { HouseClass * h = HouseClass::As_Pointer(Enemy); if (h == NULL || !h->IsActive || h->IsDefeated || Is_Ally(h) || h->BScan == 0) { Enemy = HOUSE_NONE; } } /* ** If there is no enemy assigned to this house, then assign one now. The ** enemy that is closest is picked. However, don't pick an enemy if the ** base has not been established yet. */ if (ActiveBScan && Center && Attack == 0) { int close = 0; HousesType enemy = HOUSE_NONE; int maxunit = 0; int maxinfantry = 0; int maxvessel = 0; int maxaircraft = 0; int maxbuilding = 0; int enemycount = 0; for (HousesType house = HOUSE_FIRST; house < HOUSE_COUNT; house++) { HouseClass * h = HouseClass::As_Pointer(house); if (h != NULL && h->IsActive && !h->IsDefeated && !Is_Ally(h)) { /* ** Perform a special restriction check to ensure that no enemy is chosen if ** there is even one enemy that has not established a base yet. This will ** ensure an accurate first pick for enemy since the distance to base ** value can be determined. */ if (!h->IsStarted) { enemy = HOUSE_NONE; break; } /* ** Keep track of the number of buildings and units owned by the ** enemy. This is used to bring up the maximum allowed to match. */ maxunit += h->CurUnits; maxbuilding += h->CurBuildings; maxinfantry += h->CurInfantry; maxvessel += h->CurVessels; maxaircraft += h->CurAircraft; enemycount++; /* ** Determine a priority value based on distance to the center of the ** candidate base. The higher the value, the better the candidate house ** is to becoming the preferred enemy for this house. */ int value = ((MAP_CELL_W*2)-Distance(Center, h->Center)); value *= 2; /* ** In addition to distance, record the number of kills directed ** against this house. The enemy that does more damage might be ** considered a greater threat. */ value += h->BuildingsKilled[Class->House]*5; value += h->UnitsKilled[Class->House]; /* ** Factor in the relative sizes of the bases. An enemy that has a ** larger base will be considered a bigger threat. Conversely, a ** smaller base is considered a lesser threat. */ value += h->CurUnits - CurUnits; value += h->CurBuildings - CurBuildings; value += (h->CurInfantry - CurInfantry)/4; /* ** Whoever last attacked is given a little more priority as ** a potential designated enemy. */ if (house == LAEnemy) { value += 100; } #ifdef OBSOLETE /* ** Human players are a given preference as the target. */ if (h->IsHuman) { value *= 2; } #endif /* ** Compare the calculated value for this candidate house and if it is ** greater than the previously recorded maximum, record this house as ** the prime candidate for enemy. */ if (value > close) { enemy = house; close = value; } } } /* ** Record this closest enemy base as the first enemy to attack. */ Enemy = enemy; /* ** Up the maximum allowed units and buildings to match a rough average ** of what the enemies are allowed. */ if (enemycount) { maxunit /= enemycount; maxbuilding /= enemycount; maxinfantry /= enemycount; maxvessel /= enemycount; maxaircraft /= enemycount; } if (Control.MaxBuilding < (unsigned)maxbuilding + 10) { Control.MaxBuilding = maxbuilding + 10; } if (Control.MaxUnit < (unsigned)maxunit + 10) { Control.MaxUnit = maxunit + 10; } if (Control.MaxInfantry < (unsigned)maxinfantry + 10) { Control.MaxInfantry = maxinfantry + 10; } if (Control.MaxVessel < (unsigned)maxvessel + 10) { Control.MaxVessel = maxvessel + 10; } if (Control.MaxAircraft < (unsigned)maxaircraft + 10) { Control.MaxAircraft = maxaircraft + 10; } } /* ** House state transition check occurs here. Transitions that occur here are ones ** that relate to general base condition rather than specific combat events. ** Typically, this is limited to transitions between normal buildup mode and ** broke mode. */ if (State == STATE_ENDGAME) { Fire_Sale(); Do_All_To_Hunt(); } else { if (State == STATE_BUILDUP) { if (Available_Money() < 25) { State = STATE_BROKE; } } if (State == STATE_BROKE) { if (Available_Money() >= 25) { State = STATE_BUILDUP; } } if (State == STATE_ATTACKED && LATime + TICKS_PER_MINUTE < Frame) { State = STATE_BUILDUP; } if (State != STATE_ATTACKED && LATime + TICKS_PER_MINUTE > Frame) { State = STATE_ATTACKED; } } /* ** Records the urgency of all actions possible. */ UrgencyType urgency[STRATEGY_COUNT]; StrategyType strat; for (strat = STRATEGY_FIRST; strat < STRATEGY_COUNT; strat++) { urgency[strat] = URGENCY_NONE; switch (strat) { case STRATEGY_BUILD_POWER: urgency[strat] = Check_Build_Power(); break; case STRATEGY_BUILD_DEFENSE: urgency[strat] = Check_Build_Defense(); break; case STRATEGY_BUILD_INCOME: urgency[strat] = Check_Build_Income(); break; case STRATEGY_FIRE_SALE: urgency[strat] = Check_Fire_Sale(); break; case STRATEGY_BUILD_ENGINEER: urgency[strat] = Check_Build_Engineer(); break; case STRATEGY_BUILD_OFFENSE: urgency[strat] = Check_Build_Offense(); break; case STRATEGY_RAISE_MONEY: urgency[strat] = Check_Raise_Money(); break; case STRATEGY_RAISE_POWER: urgency[strat] = Check_Raise_Power(); break; case STRATEGY_LOWER_POWER: urgency[strat] = Check_Lower_Power(); break; case STRATEGY_ATTACK: urgency[strat] = Check_Attack(); break; default: urgency[strat] = URGENCY_NONE; break; } } /* ** Performs the action required for each of the strategies that share ** the most urgent category. Stop processing if any strategy at the ** highest urgency performed any action. This is because higher urgency ** actions tend to greatly affect the lower urgency actions. */ for (UrgencyType u = URGENCY_CRITICAL; u >= URGENCY_LOW; u--) { bool acted = false; for (strat = STRATEGY_FIRST; strat < STRATEGY_COUNT; strat++) { if (urgency[strat] == u) { switch (strat) { case STRATEGY_BUILD_POWER: acted |= AI_Build_Power(u); break; case STRATEGY_BUILD_DEFENSE: acted |= AI_Build_Defense(u); break; case STRATEGY_BUILD_INCOME: acted |= AI_Build_Income(u); break; case STRATEGY_FIRE_SALE: acted |= AI_Fire_Sale(u); break; case STRATEGY_BUILD_ENGINEER: acted |= AI_Build_Engineer(u); break; case STRATEGY_BUILD_OFFENSE: acted |= AI_Build_Offense(u); break; case STRATEGY_RAISE_MONEY: acted |= AI_Raise_Money(u); break; case STRATEGY_RAISE_POWER: acted |= AI_Raise_Power(u); break; case STRATEGY_LOWER_POWER: acted |= AI_Lower_Power(u); break; case STRATEGY_ATTACK: acted |= AI_Attack(u); break; default: break; } } } } return(TICKS_PER_SECOND*5 + Random_Pick(1, TICKS_PER_SECOND/2)); } UrgencyType HouseClass::Check_Build_Power(void) const { assert(Houses.ID(this) == ID); fixed frac = Power_Fraction(); UrgencyType urgency = URGENCY_NONE; if (frac < 1 && Can_Make_Money()) { urgency = URGENCY_LOW; /* ** Very low power condition is considered a higher priority. */ if (frac < fixed::_3_4) urgency = URGENCY_MEDIUM; /* ** When under attack and there is a need for power in defense, ** then consider power building a higher priority. */ if (State == STATE_THREATENED || State == STATE_ATTACKED) { if (BScan | (STRUCTF_CHRONOSPHERE)) { urgency = URGENCY_HIGH; } } } return(urgency); } UrgencyType HouseClass::Check_Build_Defense(void) const { assert(Houses.ID(this) == ID); /* ** This routine determines what urgency level that base defense ** should be given. The more vulnerable the base is, the higher ** the urgency this routine should return. */ return(URGENCY_NONE); } UrgencyType HouseClass::Check_Build_Offense(void) const { assert(Houses.ID(this) == ID); /* ** This routine determines what urgency level that offensive ** weaponry should be given. Surplus money or a very strong ** defense will cause the offensive urgency to increase. */ return(URGENCY_NONE); } /* ** Determines what the attack state of the base is. The higher the state, ** the greater the immediate threat to base defense is. */ UrgencyType HouseClass::Check_Attack(void) const { assert(Houses.ID(this) == ID); if (Frame > TICKS_PER_MINUTE && Attack == 0) { if (State == STATE_ATTACKED) { return(URGENCY_LOW); } return(URGENCY_CRITICAL); } return(URGENCY_NONE); } UrgencyType HouseClass::Check_Build_Income(void) const { assert(Houses.ID(this) == ID); /* ** This routine should determine if income processing buildings ** should be constructed and at what urgency. The lower the money, ** the lower the refineries, or recent harvester losses should ** cause a greater urgency to be returned. */ return(URGENCY_NONE); } UrgencyType HouseClass::Check_Fire_Sale(void) const { assert(Houses.ID(this) == ID); /* ** If there are no more factories at all, then sell everything off because the game ** is basically over at this point. */ if (State != STATE_ATTACKED && CurBuildings && !(ActiveBScan & (STRUCTF_TENT|STRUCTF_BARRACKS|STRUCTF_CONST|STRUCTF_AIRSTRIP|STRUCTF_WEAP|STRUCTF_HELIPAD))) { return(URGENCY_CRITICAL); } return(URGENCY_NONE); } UrgencyType HouseClass::Check_Build_Engineer(void) const { assert(Houses.ID(this) == ID); /* ** This routine should check to see what urgency that the production of ** engineers should be. If a friendly building has been captured or the ** enemy has weak defenses, then building an engineer would be a priority. */ return(URGENCY_NONE); } /* ** Checks to see if money is critically low and something must be done ** to immediately raise cash. */ UrgencyType HouseClass::Check_Raise_Money(void) const { assert(Houses.ID(this) == ID); UrgencyType urgency = URGENCY_NONE; if (Available_Money() < 100) { urgency = URGENCY_LOW; } if (Available_Money() < 2000 && !Can_Make_Money()) { urgency++; } return(urgency); } /* ** Checks to see if power is very low and if so, a greater urgency to ** build more power is returned. */ UrgencyType HouseClass::Check_Lower_Power(void) const { assert(Houses.ID(this) == ID); if (Power > Drain+300) { return(URGENCY_LOW); } return(URGENCY_NONE); } /* ** This routine determines if there is a power emergency. Such an ** emergency might require selling of structures in order to free ** up power. This might occur if the base is being attacked and there ** are defenses that require power, but are just short of having ** enough. */ UrgencyType HouseClass::Check_Raise_Power(void) const { assert(Houses.ID(this) == ID); UrgencyType urgency = URGENCY_NONE; if (Power_Fraction() < Rule.PowerEmergencyFraction && Power < Drain - 400) { // if (Power_Fraction() < Rule.PowerEmergencyFraction && (BQuantity[STRUCT_CONST] == 0 || Available_Money() < 200 || Power < Drain-400)) { urgency = URGENCY_MEDIUM; if (State == STATE_ATTACKED) { urgency++; } } return(urgency); } bool HouseClass::AI_Attack(UrgencyType ) { assert(Houses.ID(this) == ID); bool shuffle = !((Frame > TICKS_PER_MINUTE && !CurBuildings) || Percent_Chance(33)); bool forced = (CurBuildings == 0); int index; for (index = 0; index < Aircraft.Count(); index++) { AircraftClass * a = Aircraft.Ptr(index); if (a != NULL && !a->IsInLimbo && a->House == this && a->Strength > 0) { if (!shuffle && a->Is_Weapon_Equipped() && (forced || Percent_Chance(75))) { a->Assign_Mission(MISSION_HUNT); } } } for (index = 0; index < Units.Count(); index++) { UnitClass * u = Units.Ptr(index); if (u != NULL && !u->IsInLimbo && u->House == this && u->Strength > 0) { if (!shuffle && u->Is_Weapon_Equipped() && (forced || Percent_Chance(75))) { u->Assign_Mission(MISSION_HUNT); } else { /* ** If this unit is guarding the base, then cause it to shuffle ** location instead. */ if (Percent_Chance(20) && u->Mission == MISSION_GUARD_AREA && Which_Zone(u) != ZONE_NONE) { u->ArchiveTarget = ::As_Target(Where_To_Go(u)); } } } } for (index = 0; index < Infantry.Count(); index++) { InfantryClass * i = Infantry.Ptr(index); if (i != NULL && !i->IsInLimbo && i->House == this && i->Strength > 0) { if (!shuffle && (i->Is_Weapon_Equipped() || *i == INFANTRY_RENOVATOR) && (forced || Percent_Chance(75))) { i->Assign_Mission(MISSION_HUNT); } else { /* ** If this soldier is guarding the base, then cause it to shuffle ** location instead. */ if (Percent_Chance(20) && i->Mission == MISSION_GUARD_AREA && Which_Zone(i) != ZONE_NONE) { i->ArchiveTarget = ::As_Target(Where_To_Go(i)); } } } } Attack = Rule.AttackInterval * Random_Pick(TICKS_PER_MINUTE/2, TICKS_PER_MINUTE*2); return(true); } /* ** Given the specified urgency, build a power structure to meet ** this need. */ bool HouseClass::AI_Build_Power(UrgencyType ) const { assert(Houses.ID(this) == ID); return(false); } /* ** Given the specified urgency, build base defensive structures ** according to need and according to existing base disposition. */ bool HouseClass::AI_Build_Defense(UrgencyType ) const { assert(Houses.ID(this) == ID); return(false); } /* ** Given the specified urgency, build offensive units according ** to need and according to the opponents base defenses. */ bool HouseClass::AI_Build_Offense(UrgencyType ) const { assert(Houses.ID(this) == ID); return(false); } /* ** Given the specified urgency, build income producing ** structures according to need. */ bool HouseClass::AI_Build_Income(UrgencyType ) const { assert(Houses.ID(this) == ID); return(false); } bool HouseClass::AI_Fire_Sale(UrgencyType urgency) { assert(Houses.ID(this) == ID); if (CurBuildings && urgency == URGENCY_CRITICAL) { Fire_Sale(); Do_All_To_Hunt(); return(true); } return(false); } /* ** Given the specified urgency, build an engineer. */ bool HouseClass::AI_Build_Engineer(UrgencyType ) const { assert(Houses.ID(this) == ID); return(false); } /* ** Given the specified urgency, sell of some power since ** there appears to be excess. */ bool HouseClass::AI_Lower_Power(UrgencyType ) const { assert(Houses.ID(this) == ID); BuildingClass * b = Find_Building(STRUCT_POWER); if (b != NULL) { b->Sell_Back(1); return(true); } b = Find_Building(STRUCT_ADVANCED_POWER); if (b != NULL) { b->Sell_Back(1); return(true); } return(false); } /*********************************************************************************************** * HouseClass::AI_Raise_Power -- Try to raise power levels by selling off buildings. * * * * This routine is called when the computer needs to raise power by selling off buildings. * * Usually this occurs because of some catastrophe that has lowered power levels to * * the danger zone. * * * * INPUT: urgency -- The urgency that the power needs to be raised. This controls what * * buildings will be sold. * * * * OUTPUT: bool; Was a building sold to raise power? * * * * WARNINGS: none * * * * HISTORY: * * 11/02/1996 JLB : Created. * *=============================================================================================*/ bool HouseClass::AI_Raise_Power(UrgencyType urgency) const { assert(Houses.ID(this) == ID); /* ** Sell off structures in this order. */ static struct { StructType Structure; UrgencyType Urgency; } _types[] = { {STRUCT_CHRONOSPHERE, URGENCY_LOW}, {STRUCT_SHIP_YARD, URGENCY_LOW}, {STRUCT_SUB_PEN, URGENCY_LOW}, {STRUCT_ADVANCED_TECH, URGENCY_LOW}, {STRUCT_FORWARD_COM, URGENCY_LOW}, {STRUCT_SOVIET_TECH, URGENCY_LOW}, {STRUCT_IRON_CURTAIN, URGENCY_MEDIUM}, {STRUCT_RADAR, URGENCY_MEDIUM}, {STRUCT_REPAIR, URGENCY_MEDIUM}, {STRUCT_TESLA, URGENCY_HIGH} }; /* ** Find a structure to sell and then sell it. Bail from further scanning until ** the next time. */ for (int i = 0; i < ARRAY_SIZE(_types); i++) { if (urgency >= _types[i].Urgency) { BuildingClass * b = Find_Building(_types[i].Structure); if (b != NULL) { b->Sell_Back(1); return(true); } } } return(false); } /*********************************************************************************************** * HouseClass::AI_Raise_Money -- Raise emergency cash by selling buildings. * * * * This routine handles the situation where the computer desperately needs cash but cannot * * wait for normal harvesting to raise it. Buildings must be sold. * * * * INPUT: urgency -- The urgency level that cash must be raised. The greater the urgency, * * the more important the buildings that can be sold become. * * * * OUTPUT: bool; Was a building sold to raise cash? * * * * WARNINGS: none * * * * HISTORY: * * 11/02/1996 JLB : Created. * *=============================================================================================*/ bool HouseClass::AI_Raise_Money(UrgencyType urgency) const { assert(Houses.ID(this) == ID); /* ** Sell off structures in this order. */ static struct { StructType Structure; UrgencyType Urgency; } _types[] = { {STRUCT_CHRONOSPHERE, URGENCY_LOW}, {STRUCT_SHIP_YARD, URGENCY_LOW}, {STRUCT_SUB_PEN, URGENCY_LOW}, {STRUCT_ADVANCED_TECH, URGENCY_LOW}, {STRUCT_FORWARD_COM, URGENCY_LOW}, {STRUCT_SOVIET_TECH, URGENCY_LOW}, {STRUCT_STORAGE,URGENCY_LOW}, {STRUCT_REPAIR,URGENCY_LOW}, {STRUCT_TESLA,URGENCY_MEDIUM}, {STRUCT_HELIPAD,URGENCY_MEDIUM}, {STRUCT_POWER,URGENCY_HIGH}, {STRUCT_AIRSTRIP,URGENCY_HIGH}, // {STRUCT_WEAP,URGENCY_HIGH}, // {STRUCT_BARRACKS,URGENCY_HIGH}, // {STRUCT_TENT,URGENCY_HIGH}, {STRUCT_CONST,URGENCY_CRITICAL} }; BuildingClass * b = 0; /* ** Find a structure to sell and then sell it. Bail from further scanning until ** the next time. */ for (int i = 0; i < ARRAY_SIZE(_types); i++) { if (urgency >= _types[i].Urgency) { b = Find_Building(_types[i].Structure); if (b != NULL) { b->Sell_Back(1); return(true); } } } return(false); } #ifdef NEVER /*********************************************************************************************** * HouseClass::AI_Base_Defense -- Handles maintaining a strong base defense. * * * * This logic is used to maintain a base defense. * * * * INPUT: none * * * * OUTPUT: Returns with the number of game frames to delay before calling this routine again. * * * * WARNINGS: none * * * * HISTORY: * * 09/29/1995 JLB : Created. * *=============================================================================================*/ int HouseClass::AI_Base_Defense(void) { assert(Houses.ID(this) == ID); /* ** Check to find if any zone of the base is over defended. Such zones should have ** some of their defenses sold off to make better use of the money. */ /* ** Make sure that the core defense is only about 1/2 of the perimeter defense average. */ int average = 0; for (ZoneType z = ZONE_NORTH; z < ZONE_COUNT; z++) { average += ZoneInfo[z].AirDefense; average += ZoneInfo[z].ArmorDefense; average += ZoneInfo[z].InfantryDefense; } average /= (ZONE_COUNT-ZONE_NORTH); /* ** If the core value is greater than the average, then sell off some of the ** inner defensive structures. */ int core = ZoneInfo[ZONE_CORE].AirDefense + ZoneInfo[ZONE_CORE].ArmorDefense + ZoneInfo[ZONE_CORE].InfantryDefense; if (core >= average) { static StructType _stype[] = { STRUCT_GTOWER, STRUCT_TURRET, STRUCT_ATOWER, STRUCT_OBELISK, STRUCT_TESLA, STRUCT_SAM }; BuildingClass * b; for (int index = 0; index < sizeof(_stype)/sizeof(_stype[0]); index++) { b = Find_Building(_stype[index], ZONE_CORE); if (b) { b->Sell_Back(1); break; } } } /* ** If the enemy doesn't have any offensive air capability, then sell off any ** SAM sites. Only do this when money is moderately low. */ if (Available_Money() < 1000 && (ActiveBScan & STRUCTF_SAM)) { /* ** Scan to find if ANY human opponents have aircraft or a helipad. If one ** is found then consider that opponent to have a valid air threat potential. ** Don't sell off SAM sites in that case. */ bool nothreat = true; for (HousesType h = HOUSE_FIRST; h < HOUSE_COUNT; h++) { HouseClass * house = HouseClass::As_Pointer(h); if (house && house->IsActive && house->IsHuman && !Is_Ally(house)) { if ((house->ActiveAScan & (AIRCRAFTF_ORCA|AIRCRAFTF_TRANSPORT|AIRCRAFTF_HELICOPTER)) || (house->ActiveBScan & STRUCTF_HELIPAD)) { nothreat = false; break; } } } } return(TICKS_PER_SECOND*5); } #endif /*********************************************************************************************** * HouseClass::AI_Building -- Determines what building to build. * * * * This routine handles the general case of determining what building to build next. * * * * INPUT: none * * * * OUTPUT: Returns with the number of game frames to delay before calling this routine again. * * * * WARNINGS: none * * * * HISTORY: * * 09/29/1995 JLB : Created. * * 11/03/1996 JLB : Tries to match aircraft of enemy * *=============================================================================================*/ int HouseClass::AI_Building(void) { assert(Houses.ID(this) == ID); if (BuildStructure != STRUCT_NONE) return(TICKS_PER_SECOND); if (Session.Type == GAME_NORMAL && Base.House == Class->House) { BaseNodeClass * node = Base.Next_Buildable(); if (node) { BuildStructure = node->Type; } } if (IsBaseBuilding) { /* ** Don't suggest anything to build if the base is already big enough. */ unsigned int quant = 0; for (HousesType h = HOUSE_FIRST; h < HOUSE_COUNT; h++) { HouseClass const * hptr = HouseClass::As_Pointer(h); if (hptr != NULL && hptr->IsActive && hptr->IsHuman && quant < hptr->CurBuildings) { quant = hptr->CurBuildings; } } quant += Rule.BaseSizeAdd; // TCTC -- Should multiply largest player base by some rational number. // if (CurBuildings >= quant) return(TICKS_PER_SECOND); BuildChoice.Free_All(); BuildChoiceClass * choiceptr; StructType stype = STRUCT_NONE; int money = Available_Money(); int level = Control.TechLevel; bool hasincome = (BQuantity[STRUCT_REFINERY] > 0 && !IsTiberiumShort && UQuantity[UNIT_HARVESTER] > 0); BuildingTypeClass const * b = NULL; HouseClass const * enemy = NULL; if (Enemy != HOUSE_NONE) { enemy = HouseClass::As_Pointer(Enemy); } level = Control.TechLevel; /* ** Try to build a power plant if there is insufficient power and there is enough ** money available. */ b = &BuildingTypeClass::As_Reference(STRUCT_ADVANCED_POWER); if (Can_Build(b, ActLike) && Power <= Drain+Rule.PowerSurplus && b->Cost_Of() < money) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(BQuantity[STRUCT_REFINERY] == 0 ? URGENCY_LOW : URGENCY_MEDIUM, b->Type); } } else { b = &BuildingTypeClass::As_Reference(STRUCT_POWER); if (Can_Build(b, ActLike) && Power <= Drain+Rule.PowerSurplus && b->Cost_Of() < money) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(BQuantity[STRUCT_REFINERY] == 0 ? URGENCY_LOW : URGENCY_MEDIUM, b->Type); } } } /* ** Build a refinery if there isn't one already available. */ unsigned int current = BQuantity[STRUCT_REFINERY]; if (!IsTiberiumShort && current < Round_Up(Rule.RefineryRatio*fixed(CurBuildings)) && current < (unsigned)Rule.RefineryLimit) { b = &BuildingTypeClass::As_Reference(STRUCT_REFINERY); if (Can_Build(b, ActLike) && (money > b->Cost_Of() || hasincome)) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(BQuantity[STRUCT_REFINERY] == 0 ? URGENCY_HIGH : URGENCY_MEDIUM, b->Type); } } } /* ** Always make sure there is a barracks available, but only if there ** will be sufficient money to train troopers. */ current = BQuantity[STRUCT_BARRACKS] + BQuantity[STRUCT_TENT]; if (current < Round_Up(Rule.BarracksRatio*fixed(CurBuildings)) && current < (unsigned)Rule.BarracksLimit && (money > 300 || hasincome)) { b = &BuildingTypeClass::As_Reference(STRUCT_BARRACKS); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome)) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(current > 0 ? URGENCY_LOW : URGENCY_MEDIUM, b->Type); } } else { b = &BuildingTypeClass::As_Reference(STRUCT_TENT); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome)) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(current > 0 ? URGENCY_LOW : URGENCY_MEDIUM, b->Type); } } } } /* ** Try to build one dog house. */ current = BQuantity[STRUCT_KENNEL]; if (current < 1 && (money > 300 || hasincome)) { b = &BuildingTypeClass::As_Reference(STRUCT_KENNEL); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome)) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(URGENCY_MEDIUM, b->Type); } } } /* ** Try to build one gap generator. */ current = BQuantity[STRUCT_GAP]; if (current < 1 && Power_Fraction() >= 1 && hasincome) { b = &BuildingTypeClass::As_Reference(STRUCT_GAP); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome)) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(URGENCY_MEDIUM, b->Type); } } } /* ** A source of combat vehicles is always needed, but only if there will ** be sufficient money to build vehicles. */ current = BQuantity[STRUCT_WEAP]; if (current < Round_Up(Rule.WarRatio*fixed(CurBuildings)) && current < (unsigned)Rule.WarLimit && (money > 2000 || hasincome)) { b = &BuildingTypeClass::As_Reference(STRUCT_WEAP); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome)) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(current > 0 ? URGENCY_LOW : URGENCY_MEDIUM, b->Type); } } } /* ** Always build up some base defense. */ current = BQuantity[STRUCT_PILLBOX] + BQuantity[STRUCT_CAMOPILLBOX] + BQuantity[STRUCT_TURRET] + BQuantity[STRUCT_FLAME_TURRET]; if (current < Round_Up(Rule.DefenseRatio*fixed(CurBuildings)) && current < (unsigned)Rule.DefenseLimit) { b = &BuildingTypeClass::As_Reference(STRUCT_FLAME_TURRET); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome)) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(URGENCY_MEDIUM, b->Type); } } else { if (Percent_Chance(50)) { b = &BuildingTypeClass::As_Reference(STRUCT_PILLBOX); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome)) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(URGENCY_MEDIUM, b->Type); } } } else { b = &BuildingTypeClass::As_Reference(STRUCT_TURRET); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome)) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(URGENCY_MEDIUM, b->Type); } } } } } /* ** Build some air defense. */ current = BQuantity[STRUCT_SAM] + BQuantity[STRUCT_AAGUN]; if (current < Round_Up(Rule.AARatio*fixed(CurBuildings)) && current < (unsigned)Rule.AALimit) { /* ** Building air defense only makes sense if the opponent has aircraft ** of some kind. */ bool airthreat = false; int threat_quantity = 0; if (enemy != NULL && enemy->AScan != 0) { airthreat = true; threat_quantity = enemy->CurAircraft; } if (!airthreat) { for (HousesType house = HOUSE_FIRST; house < HOUSE_COUNT; house++) { HouseClass * h = HouseClass::As_Pointer(house); if (h != NULL && !Is_Ally(house) && h->AScan != 0) { airthreat = true; break; } } } if (airthreat) { if (BQuantity[STRUCT_RADAR] == 0) { b = &BuildingTypeClass::As_Reference(STRUCT_RADAR); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome)) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(URGENCY_HIGH, b->Type); } } } b = &BuildingTypeClass::As_Reference(STRUCT_SAM); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome)) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass((current < (unsigned)threat_quantity) ? URGENCY_HIGH : URGENCY_MEDIUM, b->Type); } } else { b = &BuildingTypeClass::As_Reference(STRUCT_AAGUN); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome)) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass((current < (unsigned)threat_quantity) ? URGENCY_HIGH : URGENCY_MEDIUM, b->Type); } } } } } /* ** Advanced base defense would be good. */ current = BQuantity[STRUCT_TESLA]; if (current < Round_Up(Rule.TeslaRatio*fixed(CurBuildings)) && current < (unsigned)Rule.TeslaLimit) { b = &BuildingTypeClass::As_Reference(STRUCT_TESLA); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome) && Power_Fraction() >= 1) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(URGENCY_MEDIUM, b->Type); } } } /* ** Build a tech center as soon as possible. */ current = BQuantity[STRUCT_ADVANCED_TECH] + BQuantity[STRUCT_SOVIET_TECH]; if (current < 1) { b = &BuildingTypeClass::As_Reference(STRUCT_ADVANCED_TECH); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome) && Power_Fraction() >= 1) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(URGENCY_MEDIUM, b->Type); } } else { b = &BuildingTypeClass::As_Reference(STRUCT_SOVIET_TECH); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome) && Power_Fraction() >= 1) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { *choiceptr = BuildChoiceClass(URGENCY_MEDIUM, b->Type); } } } } /* ** A helipad would be good. */ current = BQuantity[STRUCT_HELIPAD]; if (current < Round_Up(Rule.HelipadRatio*fixed(CurBuildings)) && current < (unsigned)Rule.HelipadLimit) { b = &BuildingTypeClass::As_Reference(STRUCT_HELIPAD); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome)) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { int threat_quantity = 0; if (enemy != NULL) { threat_quantity = enemy->CurAircraft; } *choiceptr = BuildChoiceClass((CurAircraft < (unsigned)threat_quantity) ? URGENCY_HIGH : URGENCY_MEDIUM, b->Type); } } } /* ** An airstrip would be good. */ current = BQuantity[STRUCT_AIRSTRIP]; if (current < Round_Up(Rule.AirstripRatio*fixed(CurBuildings)) && current < (unsigned)Rule.AirstripLimit) { b = &BuildingTypeClass::As_Reference(STRUCT_AIRSTRIP); if (Can_Build(b, ActLike) && (b->Cost_Of() < money || hasincome)) { choiceptr = BuildChoice.Alloc(); if (choiceptr != NULL) { int threat_quantity = 0; if (enemy != NULL) { threat_quantity = enemy->CurAircraft; } *choiceptr = BuildChoiceClass((CurAircraft < (unsigned)threat_quantity) ? URGENCY_HIGH : URGENCY_MEDIUM, b->Type); } } } #ifdef OLD /* ** Build a repair bay if there isn't one already available. */ current = BQuantity[STRUCT_REPAIR]; if (current == 0) { b = &BuildingTypeClass::As_Reference(STRUCT_REPAIR); if (Can_Build(b, ActLike) && b->Cost_Of() < money) { choiceptr = BuildChoice.Alloc(); if (choiceptr) { *choiceptr = BuildChoiceClass(URGENCY_MEDIUM, b->Type); } } } #endif /* ** Pick the choice that is the most urgent. */ UrgencyType best = URGENCY_NONE; int bestindex; for (int index = 0; index < BuildChoice.Count(); index++) { if (BuildChoice.Ptr(index)->Urgency > best) { bestindex = index; best = BuildChoice.Ptr(index)->Urgency; } } if (best != URGENCY_NONE) { BuildStructure = BuildChoice.Ptr(bestindex)->Structure; } } return(TICKS_PER_SECOND); } /*********************************************************************************************** * HouseClass::AI_Unit -- Determines what unit to build next. * * * * This routine handles the general case of determining what units to build next. * * * * INPUT: none * * * * OUTPUT: Returns with the number of games frames to delay before calling this routine again.* * * * WARNINGS: none * * * * HISTORY: * * 09/29/1995 JLB : Created. * *=============================================================================================*/ int HouseClass::AI_Unit(void) { assert(Houses.ID(this) == ID); if (BuildUnit != UNIT_NONE) return(TICKS_PER_SECOND); if (CurUnits >= Control.MaxUnit) return(TICKS_PER_SECOND); /* ** A computer controlled house will try to build a replacement ** harvester if possible. */ if (IQ >= Rule.IQHarvester && !IsTiberiumShort && !IsHuman && BQuantity[STRUCT_REFINERY] > UQuantity[UNIT_HARVESTER] && Difficulty != DIFF_HARD) { if (UnitTypeClass::As_Reference(UNIT_HARVESTER).Level <= (unsigned)Control.TechLevel) { BuildUnit = UNIT_HARVESTER; return(TICKS_PER_SECOND); } } if (Session.Type == GAME_NORMAL) { int counter[UNIT_COUNT]; memset(counter, 0x00, sizeof(counter)); /* ** Build a list of the maximum of each type we wish to produce. This will be ** twice the number required to fill all teams. */ int index; for (index = 0; index < Teams.Count(); index++) { TeamClass * tptr = Teams.Ptr(index); if (tptr != NULL) { TeamTypeClass const * team = tptr->Class; if (((team->IsReinforcable && !tptr->IsFullStrength) || (!tptr->IsForcedActive && !tptr->IsHasBeen && !tptr->JustAltered)) && team->House == Class->House) { for (int subindex = 0; subindex < team->ClassCount; subindex++) { TechnoTypeClass const * memtype = team->Members[subindex].Class; if (memtype->What_Am_I() == RTTI_UNITTYPE) { counter[((UnitTypeClass const *)memtype)->Type] = 1; } } } } } /* ** Team types that are flagged as prebuilt, will always try to produce enough ** to fill one team of this type regardless of whether there is a team active ** of that type. */ for (index = 0; index < TeamTypes.Count(); index++) { TeamTypeClass const * team = TeamTypes.Ptr(index); if (team != NULL && team->House == Class->House && team->IsPrebuilt && (!team->IsAutocreate || IsAlerted)) { for (int subindex = 0; subindex < team->ClassCount; subindex++) { TechnoTypeClass const * memtype = team->Members[subindex].Class; if (memtype->What_Am_I() == RTTI_UNITTYPE) { int subtype = ((UnitTypeClass const *)memtype)->Type; counter[subtype] = max(counter[subtype], team->Members[subindex].Quantity); } } } } /* ** Reduce the theoretical maximum by the actual number of objects currently ** in play. */ for (int uindex = 0; uindex < Units.Count(); uindex++) { UnitClass * unit = Units.Ptr(uindex); if (unit != NULL && unit->Is_Recruitable(this) && counter[unit->Class->Type] > 0) { counter[unit->Class->Type]--; } } /* ** Pick to build the most needed object but don't consider those objects that ** can't be built because of scenario restrictions or insufficient cash. */ int bestval = -1; int bestcount = 0; UnitType bestlist[UNIT_COUNT]; for (UnitType utype = UNIT_FIRST; utype < UNIT_COUNT; utype++) { if (counter[utype] > 0 && Can_Build(&UnitTypeClass::As_Reference(utype), Class->House) && UnitTypeClass::As_Reference(utype).Cost_Of() <= Available_Money()) { if (bestval == -1 || bestval < counter[utype]) { bestval = counter[utype]; bestcount = 0; } bestlist[bestcount++] = utype; } } /* ** The unit type to build is now known. Fetch a pointer to the techno type class. */ if (bestcount) { BuildUnit = bestlist[Random_Pick(0, bestcount-1)]; } } if (IsBaseBuilding) { int counter[UNIT_COUNT]; int total = 0; UnitType index; for (index = UNIT_FIRST; index < UNIT_COUNT; index++) { UnitTypeClass const * utype = &UnitTypeClass::As_Reference(index); if (Can_Build(utype, ActLike) && utype->Type != UNIT_HARVESTER) { if (utype->PrimaryWeapon != NULL) { counter[index] = 20; } else { counter[index] = 1; } } else { counter[index] = 0; } total += counter[index]; } if (total > 0) { int choice = Random_Pick(0, total-1); for (index = UNIT_FIRST; index < UNIT_COUNT; index++) { if (choice < counter[index]) { BuildUnit = index; break; } choice -= counter[index]; } } } return(TICKS_PER_SECOND); } int HouseClass::AI_Vessel(void) { assert(Houses.ID(this) == ID); if (BuildVessel != VESSEL_NONE) return(TICKS_PER_SECOND); if (CurVessels >= Control.MaxVessel) { return(TICKS_PER_SECOND); } if (Session.Type == GAME_NORMAL) { int counter[VESSEL_COUNT]; if (Session.Type == GAME_NORMAL) { memset(counter, 0x00, sizeof(counter)); } else { for (VesselType index = VESSEL_FIRST; index < VESSEL_COUNT; index++) { if (Can_Build(&VesselTypeClass::As_Reference(index), Class->House) && VesselTypeClass::As_Reference(index).Level <= (unsigned)Control.TechLevel) { counter[index] = 16; } else { counter[index] = 0; } } } /* ** Build a list of the maximum of each type we wish to produce. This will be ** twice the number required to fill all teams. */ int index; for (index = 0; index < Teams.Count(); index++) { TeamClass * tptr = Teams.Ptr(index); if (tptr) { TeamTypeClass const * team = tptr->Class; if (((team->IsReinforcable && !tptr->IsFullStrength) || (!tptr->IsForcedActive && !tptr->IsHasBeen && !tptr->JustAltered)) && team->House == Class->House) { for (int subindex = 0; subindex < team->ClassCount; subindex++) { if (team->Members[subindex].Class->What_Am_I() == RTTI_VESSELTYPE) { counter[((VesselTypeClass const *)(team->Members[subindex].Class))->Type] = 1; } } } } } /* ** Team types that are flagged as prebuilt, will always try to produce enough ** to fill one team of this type regardless of whether there is a team active ** of that type. */ for (index = 0; index < TeamTypes.Count(); index++) { TeamTypeClass const * team = TeamTypes.Ptr(index); if (team) { if (team->House == Class->House && team->IsPrebuilt && (!team->IsAutocreate || IsAlerted)) { for (int subindex = 0; subindex < team->ClassCount; subindex++) { if (team->Members[subindex].Class->What_Am_I() == RTTI_VESSELTYPE) { int subtype = ((VesselTypeClass const *)(team->Members[subindex].Class))->Type; counter[subtype] = max(counter[subtype], team->Members[subindex].Quantity); } } } } } /* ** Reduce the theoretical maximum by the actual number of objects currently ** in play. */ for (int vindex = 0; vindex < Vessels.Count(); vindex++) { VesselClass * unit = Vessels.Ptr(vindex); if (unit != NULL && unit->Is_Recruitable(this) && counter[unit->Class->Type] > 0) { counter[unit->Class->Type]--; } } /* ** Pick to build the most needed object but don't consider those object that ** can't be built because of scenario restrictions or insufficient cash. */ int bestval = -1; int bestcount = 0; VesselType bestlist[VESSEL_COUNT]; for (VesselType utype = VESSEL_FIRST; utype < VESSEL_COUNT; utype++) { if (counter[utype] > 0 && Can_Build(&VesselTypeClass::As_Reference(utype), Class->House) && VesselTypeClass::As_Reference(utype).Cost_Of() <= Available_Money()) { if (bestval == -1 || bestval < counter[utype]) { bestval = counter[utype]; bestcount = 0; } bestlist[bestcount++] = utype; } } /* ** The unit type to build is now known. Fetch a pointer to the techno type class. */ if (bestcount) { BuildVessel = bestlist[Random_Pick(0, bestcount-1)]; } } if (IsBaseBuilding) { BuildVessel = VESSEL_NONE; } return(TICKS_PER_SECOND); } /*********************************************************************************************** * HouseClass::AI_Infantry -- Determines the infantry unit to build. * * * * This routine handles the general case of determining what infantry unit to build * * next. * * * * INPUT: none * * * * OUTPUT: Returns with the number of game frames to delay before being called again. * * * * WARNINGS: none * * * * HISTORY: * * 09/29/1995 JLB : Created. * *=============================================================================================*/ int HouseClass::AI_Infantry(void) { assert(Houses.ID(this) == ID); if (BuildInfantry != INFANTRY_NONE) return(TICKS_PER_SECOND); if (CurInfantry >= Control.MaxInfantry) return(TICKS_PER_SECOND); if (Session.Type == GAME_NORMAL) { TechnoTypeClass const * techno = 0; int counter[INFANTRY_COUNT]; memset(counter, 0x00, sizeof(counter)); /* ** Build a list of the maximum of each type we wish to produce. This will be ** twice the number required to fill all teams. */ int index; for (index = 0; index < Teams.Count(); index++) { TeamClass * tptr = Teams.Ptr(index); if (tptr != NULL) { TeamTypeClass const * team = tptr->Class; if (((team->IsReinforcable && !tptr->IsFullStrength) || (!tptr->IsForcedActive && !tptr->IsHasBeen && !tptr->JustAltered)) && team->House == Class->House) { for (int subindex = 0; subindex < team->ClassCount; subindex++) { if (team->Members[subindex].Class->What_Am_I() == RTTI_INFANTRYTYPE) { counter[((InfantryTypeClass const *)(team->Members[subindex].Class))->Type] += team->Members[subindex].Quantity + (team->IsReinforcable ? 1 : 0); } } } } } /* ** Team types that are flagged as prebuilt, will always try to produce enough ** to fill one team of this type regardless of whether there is a team active ** of that type. */ for (index = 0; index < TeamTypes.Count(); index++) { TeamTypeClass const * team = TeamTypes.Ptr(index); if (team != NULL) { if (team->House == Class->House && team->IsPrebuilt && (!team->IsAutocreate || IsAlerted)) { for (int subindex = 0; subindex < team->ClassCount; subindex++) { if (team->Members[subindex].Class->What_Am_I() == RTTI_INFANTRYTYPE) { int subtype = ((InfantryTypeClass const *)(team->Members[subindex].Class))->Type; // counter[subtype] = 1; counter[subtype] = max(counter[subtype], team->Members[subindex].Quantity); counter[subtype] = min(counter[subtype], 5); } } } } } /* ** Reduce the theoretical maximum by the actual number of objects currently ** in play. */ for (int uindex = 0; uindex < Infantry.Count(); uindex++) { InfantryClass * infantry = Infantry.Ptr(uindex); if (infantry != NULL && infantry->Is_Recruitable(this) && counter[infantry->Class->Type] > 0) { counter[infantry->Class->Type]--; } } /* ** Pick to build the most needed object but don't consider those object that ** can't be built because of scenario restrictions or insufficient cash. */ int bestval = -1; int bestcount = 0; InfantryType bestlist[INFANTRY_COUNT]; for (InfantryType utype = INFANTRY_FIRST; utype < INFANTRY_COUNT; utype++) { if (utype != INFANTRY_DOG || !(IScan & INFANTRYF_DOG)) { if (counter[utype] > 0 && Can_Build(&InfantryTypeClass::As_Reference(utype), Class->House) && InfantryTypeClass::As_Reference(utype).Cost_Of() <= Available_Money()) { if (bestval == -1 || bestval < counter[utype]) { bestval = counter[utype]; bestcount = 0; } bestlist[bestcount++] = utype; } } } /* ** The infantry type to build is now known. Fetch a pointer to the techno type class. */ if (bestcount) { int pick = Random_Pick(0, bestcount-1); BuildInfantry = bestlist[pick]; } } if (IsBaseBuilding) { HouseClass const * enemy = NULL; if (Enemy != HOUSE_NONE) { enemy = HouseClass::As_Pointer(Enemy); } /* ** This structure is used to keep track of the list of infantry types that should be ** built. The infantry type and the value assigned to it is recorded. */ struct { InfantryType Type; // Infantry type. int Value; // Relative value assigned. } typetrack[INFANTRY_COUNT]; int count = 0; int total = 0; for (InfantryType index = INFANTRY_FIRST; index < INFANTRY_COUNT; index++) { if (Can_Build(&InfantryTypeClass::As_Reference(index), ActLike) && InfantryTypeClass::As_Reference(index).Level <= (unsigned)Control.TechLevel) { typetrack[count].Value = 0; #ifdef FIXIT_CSII // checked - ajw 9/28/98 This looks like a potential bug. It is prob. for save game format compatibility. int clipindex = index; if (clipindex >= INFANTRY_RA_COUNT) clipindex -= INFANTRY_RA_COUNT; if ((enemy != NULL && enemy->IQuantity[clipindex] > IQuantity[clipindex]) || Available_Money() > Rule.InfantryReserve || CurInfantry < CurBuildings * Rule.InfantryBaseMult) { #else if ((enemy != NULL && enemy->IQuantity[index] > IQuantity[index]) || Available_Money() > Rule.InfantryReserve || CurInfantry < CurBuildings * Rule.InfantryBaseMult) { #endif switch (index) { case INFANTRY_E1: typetrack[count].Value = 3; break; case INFANTRY_E2: typetrack[count].Value = 5; break; case INFANTRY_E3: typetrack[count].Value = 2; break; case INFANTRY_E4: typetrack[count].Value = 5; break; case INFANTRY_RENOVATOR: if (CurInfantry > 5) { typetrack[count].Value = 1 - max(IQuantity[index], 0); } break; case INFANTRY_TANYA: typetrack[count].Value = 1 - max(IQuantity[index], 0); break; default: typetrack[count].Value = 0; break; } } if (typetrack[count].Value > 0) { typetrack[count].Type = index; total += typetrack[count].Value; count++; } } } /* ** If there is at least one choice, then pick it. The object picked ** is influenced by the weight (value) assigned to it. This is accomplished ** by picking a number between 0 and the total weight value. The appropriate ** infantry object that matches the number picked is then selected to be built. */ if (count > 0) { int pick = Random_Pick(0, total-1); for (int index = 0; index < count; index++) { if (pick < typetrack[index].Value) { BuildInfantry = typetrack[index].Type; break; } pick -= typetrack[index].Value; } } } return(TICKS_PER_SECOND); } /*********************************************************************************************** * HouseClass::AI_Aircraft -- Determines what aircraft to build next. * * * * This routine is used to determine the general case of what aircraft to build next. * * * * INPUT: none * * * * OUTPUT: Returns with the number of frame to delay before calling this routine again. * * * * WARNINGS: none * * * * HISTORY: * * 09/29/1995 JLB : Created. * *=============================================================================================*/ int HouseClass::AI_Aircraft(void) { assert(Houses.ID(this) == ID); if (!IsHuman && IQ >= Rule.IQAircraft) { if (BuildAircraft != AIRCRAFT_NONE) return(TICKS_PER_SECOND); if (CurAircraft >= Control.MaxAircraft) return(TICKS_PER_SECOND); if (Can_Build(&AircraftTypeClass::As_Reference(AIRCRAFT_LONGBOW), ActLike) && AircraftTypeClass::As_Reference(AIRCRAFT_LONGBOW).Level <= (unsigned)Control.TechLevel && BQuantity[STRUCT_HELIPAD] > AQuantity[AIRCRAFT_LONGBOW] + AQuantity[AIRCRAFT_HIND]) { BuildAircraft = AIRCRAFT_LONGBOW; return(TICKS_PER_SECOND); } if (Can_Build(&AircraftTypeClass::As_Reference(AIRCRAFT_HIND), ActLike) && AircraftTypeClass::As_Reference(AIRCRAFT_HIND).Level <= (unsigned)Control.TechLevel && BQuantity[STRUCT_HELIPAD] > AQuantity[AIRCRAFT_LONGBOW] + AQuantity[AIRCRAFT_HIND]) { BuildAircraft = AIRCRAFT_HIND; return(TICKS_PER_SECOND); } if (Can_Build(&AircraftTypeClass::As_Reference(AIRCRAFT_MIG), ActLike) && AircraftTypeClass::As_Reference(AIRCRAFT_MIG).Level <= (unsigned)Control.TechLevel && BQuantity[STRUCT_AIRSTRIP] > AQuantity[AIRCRAFT_MIG] + AQuantity[AIRCRAFT_YAK]) { BuildAircraft = AIRCRAFT_MIG; return(TICKS_PER_SECOND); } if (Can_Build(&AircraftTypeClass::As_Reference(AIRCRAFT_YAK), ActLike) && AircraftTypeClass::As_Reference(AIRCRAFT_YAK).Level <= (unsigned)Control.TechLevel && BQuantity[STRUCT_AIRSTRIP] > AQuantity[AIRCRAFT_MIG] + AQuantity[AIRCRAFT_YAK]) { BuildAircraft = AIRCRAFT_YAK; return(TICKS_PER_SECOND); } } return(TICKS_PER_SECOND); } /*********************************************************************************************** * HouseClass::Production_Begun -- Records that production has begun. * * * * This routine is used to inform the Expert System that production of the specified object * * has begun. This allows the AI to proceed with picking another object to begin production * * on. * * * * INPUT: product -- Pointer to the object that production has just begun on. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 09/29/1995 JLB : Created. * *=============================================================================================*/ void HouseClass::Production_Begun(TechnoClass const * product) { assert(Houses.ID(this) == ID); if (product != NULL) { switch (product->What_Am_I()) { case RTTI_UNIT: if (*((UnitClass*)product) == BuildUnit) { BuildUnit = UNIT_NONE; } break; case RTTI_VESSEL: if (*((VesselClass*)product) == BuildVessel) { BuildVessel = VESSEL_NONE; } break; case RTTI_INFANTRY: if (*((InfantryClass*)product) == BuildInfantry) { BuildInfantry = INFANTRY_NONE; } break; case RTTI_BUILDING: if (*((BuildingClass*)product) == BuildStructure) { BuildStructure = STRUCT_NONE; } break; case RTTI_AIRCRAFT: if (*((AircraftClass*)product) == BuildAircraft) { BuildAircraft = AIRCRAFT_NONE; } break; default: break; } } } /*********************************************************************************************** * HouseClass::Tracking_Remove -- Remove object from house tracking system. * * * * This routine informs the Expert System that the specified object is no longer part of * * this house's inventory. This occurs when the object is destroyed or captured. * * * * INPUT: techno -- Pointer to the object to remove from the tracking systems of this * * house. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 09/29/1995 JLB : Created. * *=============================================================================================*/ void HouseClass::Tracking_Remove(TechnoClass const * techno) { assert(Houses.ID(this) == ID); int type; switch (techno->What_Am_I()) { case RTTI_BUILDING: CurBuildings--; BQuantity[((BuildingTypeClass const &)techno->Class_Of()).Type]--; break; case RTTI_AIRCRAFT: CurAircraft--; AQuantity[((AircraftTypeClass const &)techno->Class_Of()).Type]--; break; case RTTI_INFANTRY: CurInfantry--; if (!((InfantryClass *)techno)->IsTechnician) { type = ((InfantryTypeClass const &)techno->Class_Of()).Type; #ifdef FIXIT_CSII // checked - ajw 9/28/98 if (type >= INFANTRY_RA_COUNT) type -= INFANTRY_RA_COUNT; #endif IQuantity[type]--; } break; case RTTI_UNIT: CurUnits--; type = ((UnitTypeClass const &)techno->Class_Of()).Type; #ifdef FIXIT_CSII // checked - ajw 9/28/98 if (type >= UNIT_RA_COUNT) type -= UNIT_RA_COUNT; #endif UQuantity[type]--; break; case RTTI_VESSEL: CurVessels--; type = ((VesselTypeClass const &)techno->Class_Of()).Type; #ifdef FIXIT_CSII // checked - ajw 9/28/98 if (type >= VESSEL_RA_COUNT) type -= VESSEL_RA_COUNT; #endif VQuantity[type]--; break; default: break; } } /*********************************************************************************************** * HouseClass::Tracking_Add -- Informs house of new inventory item. * * * * This function is called when the specified object is now available as part of the house's* * inventory. This occurs when the object is newly produced and also when it is captured * * by this house. * * * * INPUT: techno -- Pointer to the object that is now part of the house inventory. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 09/29/1995 JLB : Created. * *=============================================================================================*/ void HouseClass::Tracking_Add(TechnoClass const * techno) { assert(Houses.ID(this) == ID); StructType building; AircraftType aircraft; InfantryType infantry; UnitType unit; VesselType vessel; int quant; switch (techno->What_Am_I()) { case RTTI_BUILDING: CurBuildings++; building = ((BuildingTypeClass const &)techno->Class_Of()).Type; BQuantity[building]++; BScan |= (1L << building); if (Session.Type == GAME_INTERNET) { BuildingTotals->Increment_Unit_Total(techno->Class_Of().ID); } break; case RTTI_AIRCRAFT: CurAircraft++; aircraft = ((AircraftTypeClass const &)techno->Class_Of()).Type; AQuantity[aircraft]++; AScan |= (1L << aircraft); if (Session.Type == GAME_INTERNET) { AircraftTotals->Increment_Unit_Total(techno->Class_Of().ID); } break; case RTTI_INFANTRY: CurInfantry++; infantry = ((InfantryTypeClass const &)techno->Class_Of()).Type; if (!((InfantryClass *)techno)->IsTechnician) { #ifdef FIXIT_CSII // checked - ajw 9/28/98 quant = infantry; if (quant >= INFANTRY_RA_COUNT) quant -= INFANTRY_RA_COUNT; IQuantity[quant]++; #else IQuantity[infantry]++; #endif if (!((InfantryTypeClass const &)techno->Class_Of()).IsCivilian && Session.Type == GAME_INTERNET) { InfantryTotals->Increment_Unit_Total(techno->Class_Of().ID); } IScan |= (1L << infantry); } break; case RTTI_UNIT: CurUnits++; unit = ((UnitTypeClass const &)techno->Class_Of()).Type; #ifdef FIXIT_CSII // checked - ajw 9/28/98 quant = unit; if (quant >= UNIT_RA_COUNT) quant -= UNIT_RA_COUNT; UQuantity[quant]++; #else UQuantity[unit]++; #endif UScan |= (1L << unit); if (Session.Type == GAME_INTERNET) { UnitTotals->Increment_Unit_Total(techno->Class_Of().ID); } break; case RTTI_VESSEL: CurVessels++; vessel = ((VesselTypeClass const &)techno->Class_Of()).Type; #ifdef FIXIT_CSII // checked - ajw 9/28/98 quant = vessel; if (quant >= VESSEL_RA_COUNT) quant -= VESSEL_RA_COUNT; VQuantity[quant]++; #else VQuantity[vessel]++; #endif VScan |= (1L << vessel); if (Session.Type == GAME_INTERNET) { VesselTotals->Increment_Unit_Total(techno->Class_Of().ID); } break; default: break; } } /*********************************************************************************************** * HouseClass::Factory_Counter -- Fetches a pointer to the factory counter value. * * * * Use this routine to fetch a pointer to the variable that holds the number of factories * * that can produce the specified object type. This is a helper routine used when * * examining the number of factories as well as adjusting their number. * * * * INPUT: rtti -- The RTTI of the object that could be produced. * * * * OUTPUT: Returns with the number of factories owned by this house that could produce the * * object of the type specified. * * * * WARNINGS: none * * * * HISTORY: * * 07/30/1996 JLB : Created. * *=============================================================================================*/ int * HouseClass::Factory_Counter(RTTIType rtti) { switch (rtti) { case RTTI_UNITTYPE: case RTTI_UNIT: return(&UnitFactories); case RTTI_VESSELTYPE: case RTTI_VESSEL: return(&VesselFactories); case RTTI_AIRCRAFTTYPE: case RTTI_AIRCRAFT: return(&AircraftFactories); case RTTI_INFANTRYTYPE: case RTTI_INFANTRY: return(&InfantryFactories); case RTTI_BUILDINGTYPE: case RTTI_BUILDING: return(&BuildingFactories); default: break; } return(NULL); } /*********************************************************************************************** * HouseClass::Active_Remove -- Remove this object from active duty for this house. * * * * This routine will recognize the specified object as having been removed from active * * duty. * * * * INPUT: techno -- Pointer to the object to remove from active duty. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 07/16/1996 JLB : Created. * *=============================================================================================*/ void HouseClass::Active_Remove(TechnoClass const * techno) { if (techno == NULL) return; if (techno->What_Am_I() == RTTI_BUILDING) { int * fptr = Factory_Counter(((BuildingClass *)techno)->Class->ToBuild); if (fptr != NULL) { *fptr = *fptr - 1; } } } /*********************************************************************************************** * HouseClass::Active_Add -- Add an object to active duty for this house. * * * * This routine will recognize the specified object as having entered active duty. Any * * abilities granted to the house by that object are now available. * * * * INPUT: techno -- Pointer to the object that is entering active duty. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 07/16/1996 JLB : Created. * *=============================================================================================*/ void HouseClass::Active_Add(TechnoClass const * techno) { if (techno == NULL) return; if (techno->What_Am_I() == RTTI_BUILDING) { int * fptr = Factory_Counter(((BuildingClass *)techno)->Class->ToBuild); if (fptr != NULL) { *fptr = *fptr + 1; } } } /*********************************************************************************************** * HouseClass::Which_Zone -- Determines what zone a coordinate lies in. * * * * This routine will determine what zone the specified coordinate lies in with respect to * * this house's base. A location that is too distant from the base, even though it might * * be a building, is not considered part of the base and returns ZONE_NONE. * * * * INPUT: coord -- The coordinate to examine. * * * * OUTPUT: Returns with the base zone that the specified coordinate lies in. * * * * WARNINGS: none * * * * HISTORY: * * 10/02/1995 JLB : Created. * *=============================================================================================*/ ZoneType HouseClass::Which_Zone(COORDINATE coord) const { assert(Houses.ID(this) == ID); if (coord == 0) return(ZONE_NONE); int distance = Distance(Center, coord); if (distance <= Radius) return(ZONE_CORE); if (distance > Radius*4) return(ZONE_NONE); DirType facing = Direction(Center, coord); if (facing < DIR_NE || facing > DIR_NW) return(ZONE_NORTH); if (facing >= DIR_NE && facing < DIR_SE) return(ZONE_EAST); if (facing >= DIR_SE && facing < DIR_SW) return(ZONE_SOUTH); return(ZONE_WEST); } /*********************************************************************************************** * HouseClass::Which_Zone -- Determines which base zone the specified object lies in. * * * * Use this routine to determine what zone the specified object lies in. * * * * INPUT: object -- Pointer to the object that will be checked for zone occupation. * * * * OUTPUT: Returns with the base zone that the object lies in. For objects that are too * * distant from the center of the base, ZONE_NONE is returned. * * * * WARNINGS: none * * * * HISTORY: * * 10/02/1995 JLB : Created. * *=============================================================================================*/ ZoneType HouseClass::Which_Zone(ObjectClass const * object) const { assert(Houses.ID(this) == ID); if (!object) return(ZONE_NONE); return(Which_Zone(object->Center_Coord())); } /*********************************************************************************************** * HouseClass::Which_Zone -- Determines which base zone the specified cell lies in. * * * * This routine is used to determine what base zone the specified cell is in. * * * * INPUT: cell -- The cell to examine. * * * * OUTPUT: Returns the base zone that the cell lies in or ZONE_NONE if the cell is too far * * away. * * * * WARNINGS: none * * * * HISTORY: * * 10/02/1995 JLB : Created. * *=============================================================================================*/ ZoneType HouseClass::Which_Zone(CELL cell) const { assert(Houses.ID(this) == ID); return(Which_Zone(Cell_Coord(cell))); } /*********************************************************************************************** * HouseClass::Recalc_Attributes -- Recalcs all houses existence bits. * * * * This routine will go through all game objects and reset the existence bits for the * * owning house. This method ensures that if the object exists, then the corresponding * * existence bit is also set. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 10/02/1995 JLB : Created. * *=============================================================================================*/ void HouseClass::Recalc_Attributes(void) { /* ** Clear out all tracking values that will be filled in by this ** routine. This allows the filling in process to not worry about ** old existing values. */ int index; for (index = 0; index < Houses.Count(); index++) { HouseClass * house = Houses.Ptr(index); if (house != NULL) { house->BScan = 0; house->ActiveBScan = 0; house->IScan = 0; house->ActiveIScan = 0; house->UScan = 0; house->ActiveUScan = 0; house->AScan = 0; house->ActiveAScan = 0; house->VScan = 0; house->ActiveVScan = 0; } } /* ** A second pass through the sentient objects is required so that the appropriate scan ** bits will be set for the owner house. */ for (index = 0; index < Units.Count(); index++) { UnitClass const * unit = Units.Ptr(index); unit->House->UScan |= (1L << unit->Class->Type); if (unit->IsLocked && (Session.Type != GAME_NORMAL || !unit->House->IsHuman || unit->IsDiscoveredByPlayer)) { if (!unit->IsInLimbo) { unit->House->ActiveUScan |= (1L << unit->Class->Type); } } } for (index = 0; index < Infantry.Count(); index++) { InfantryClass const * infantry = Infantry.Ptr(index); infantry->House->IScan |= (1L << infantry->Class->Type); if (infantry->IsLocked && (Session.Type != GAME_NORMAL || !infantry->House->IsHuman || infantry->IsDiscoveredByPlayer)) { if (!infantry->IsInLimbo) { infantry->House->ActiveIScan |= (1L << infantry->Class->Type); infantry->House->OldIScan |= (1L << infantry->Class->Type); } } } for (index = 0; index < Aircraft.Count(); index++) { AircraftClass const * aircraft = Aircraft.Ptr(index); aircraft->House->AScan |= (1L << aircraft->Class->Type); if (aircraft->IsLocked && (Session.Type != GAME_NORMAL || !aircraft->House->IsHuman || aircraft->IsDiscoveredByPlayer)) { if (!aircraft->IsInLimbo) { aircraft->House->ActiveAScan |= (1L << aircraft->Class->Type); aircraft->House->OldAScan |= (1L << aircraft->Class->Type); } } } for (index = 0; index < Buildings.Count(); index++) { BuildingClass const * building = Buildings.Ptr(index); if (building->Class->Type < 32) { building->House->BScan |= (1L << building->Class->Type); if (building->IsLocked && (Session.Type != GAME_NORMAL || !building->House->IsHuman || building->IsDiscoveredByPlayer)) { if (!building->IsInLimbo) { building->House->ActiveBScan |= (1L << building->Class->Type); building->House->OldBScan |= (1L << building->Class->Type); } } } } for (index = 0; index < Vessels.Count(); index++) { VesselClass const * vessel = Vessels.Ptr(index); vessel->House->VScan |= (1L << vessel->Class->Type); if (vessel->IsLocked && (Session.Type != GAME_NORMAL || !vessel->House->IsHuman || vessel->IsDiscoveredByPlayer)) { if (!vessel->IsInLimbo) { vessel->House->ActiveVScan |= (1L << vessel->Class->Type); vessel->House->OldVScan |= (1L << vessel->Class->Type); } } } } /*********************************************************************************************** * HouseClass::Zone_Cell -- Finds the cell closest to the center of the zone. * * * * This routine is used to find the cell that is closest to the center point of the * * zone specified. Typical use of this routine is for building and unit placement so that * * they can "cover" the specified zone. * * * * INPUT: zone -- The zone that the center point is to be returned. * * * * OUTPUT: Returns with the cell that is closest to the center point of the zone specified. * * * * WARNINGS: none * * * * HISTORY: * * 10/02/1995 JLB : Created. * *=============================================================================================*/ CELL HouseClass::Zone_Cell(ZoneType zone) const { assert(Houses.ID(this) == ID); switch (zone) { case ZONE_CORE: return(Coord_Cell(Center)); case ZONE_NORTH: return(Coord_Cell(Coord_Move(Center, DIR_N, Radius*3))); case ZONE_EAST: return(Coord_Cell(Coord_Move(Center, DIR_E, Radius*3))); case ZONE_WEST: return(Coord_Cell(Coord_Move(Center, DIR_W, Radius*3))); case ZONE_SOUTH: return(Coord_Cell(Coord_Move(Center, DIR_S, Radius*3))); default: break; } return(0); } /*********************************************************************************************** * HouseClass::Where_To_Go -- Determines where the object should go and wait. * * * * This function is called for every new unit produced or delivered in order to determine * * where the unit should "hang out" to await further orders. The best area for the * * unit to loiter is returned as a cell location. * * * * INPUT: object -- Pointer to the object that needs to know where to go. * * * * OUTPUT: Returns with the cell that the unit should move to. * * * * WARNINGS: none * * * * HISTORY: * * 10/02/1995 JLB : Created. * * 11/04/1996 JLB : Simplified to use helper functions * *=============================================================================================*/ CELL HouseClass::Where_To_Go(FootClass const * object) const { assert(Houses.ID(this) == ID); assert(object != NULL); ZoneType zone; // The zone that the object should go to. if (object->Anti_Air() + object->Anti_Armor() + object->Anti_Infantry() == 0) { zone = ZONE_CORE; } else { zone = Random_Pick(ZONE_NORTH, ZONE_WEST); } CELL cell = Random_Cell_In_Zone(zone); assert(cell != 0); return(Map.Nearby_Location(cell, SPEED_TRACK, Map[cell].Zones[MZONE_NORMAL], MZONE_NORMAL)); } /*********************************************************************************************** * HouseClass::Find_Juicy_Target -- Finds a suitable field target. * * * * This routine is used to find targets out in the field and away from base defense. * * Typical of this would be the attack helicopters and the roving attack bands of * * hunter killers. * * * * INPUT: coord -- The coordinate of the attacker. Closer targets are given preference. * * * * OUTPUT: Returns with a suitable target to attack. * * * * WARNINGS: none * * * * HISTORY: * * 10/12/1995 JLB : Created. * *=============================================================================================*/ TARGET HouseClass::Find_Juicy_Target(COORDINATE coord) const { assert(Houses.ID(this) == ID); UnitClass * best = 0; int value = 0; for (int index = 0; index < Units.Count(); index++) { UnitClass * unit = Units.Ptr(index); if (unit && !unit->IsInLimbo && !Is_Ally(unit) && unit->House->Which_Zone(unit) == ZONE_NONE) { int val = Distance(coord, unit->Center_Coord()); if (unit->Anti_Air()) val *= 2; if (*unit == UNIT_HARVESTER) val /= 2; if (value == 0 || val < value) { value = val; best = unit; } } } if (best) { return(best->As_Target()); } return(TARGET_NONE); } /*********************************************************************************************** * HouseClass::Get_Quantity -- Fetches the total number of aircraft of the specified type. * * * * Call this routine to fetch the total quantity of aircraft of the type specified that is * * owned by this house. * * * * INPUT: aircraft -- The aircraft type to check the quantity of. * * * * OUTPUT: Returns with the total quantity of all aircraft of that type that is owned by this * * house. * * * * WARNINGS: none * * * * HISTORY: * * 07/09/1996 JLB : Created. * *=============================================================================================*/ int HouseClass::Get_Quantity(AircraftType aircraft) { return(AQuantity[aircraft]); } /*********************************************************************************************** * HouseClass::Fetch_Factory -- Finds the factory associated with the object type specified. * * * * This is the counterpart to the Set_Factory function. It will return with a factory * * pointer that is associated with the object type specified. * * * * INPUT: rtti -- The RTTI of the object type to find the factory for. * * * * OUTPUT: Returns with a pointer to the factory (if present) that can manufacture the * * object type specified. * * * * WARNINGS: If this returns a non-NULL pointer, then the factory is probably already busy * * producing another unit of that category. * * * * HISTORY: * * 07/09/1996 JLB : Created. * *=============================================================================================*/ FactoryClass * HouseClass::Fetch_Factory(RTTIType rtti) const { int factory_index = -1; switch (rtti) { case RTTI_INFANTRY: case RTTI_INFANTRYTYPE: factory_index = InfantryFactory; break; case RTTI_UNIT: case RTTI_UNITTYPE: factory_index = UnitFactory; break; case RTTI_BUILDING: case RTTI_BUILDINGTYPE: factory_index = BuildingFactory; break; case RTTI_AIRCRAFT: case RTTI_AIRCRAFTTYPE: factory_index = AircraftFactory; break; case RTTI_VESSEL: case RTTI_VESSELTYPE: factory_index = VesselFactory; break; default: factory_index = -1; break; } /* ** Fetch the actual pointer to the factory object. If there is ** no object factory that matches the specified rtti type, then ** null is returned. */ if (factory_index != -1) { return(Factories.Raw_Ptr(factory_index)); } return(NULL); } /*********************************************************************************************** * HouseClass::Set_Factory -- Assign specified factory to house tracking. * * * * Call this routine when a factory has been created and it now must be passed on to the * * house for tracking purposes. The house maintains several factory pointers and this * * routine will ensure that the factory pointer gets stored correctly. * * * * INPUT: rtti -- The RTTI of the object the factory it to manufacture. * * * * factory -- The factory object pointer. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 07/09/1996 JLB : Created. * *=============================================================================================*/ void HouseClass::Set_Factory(RTTIType rtti, FactoryClass * factory) { int * factory_index = 0; assert(rtti != RTTI_NONE); switch (rtti) { case RTTI_UNIT: case RTTI_UNITTYPE: factory_index = &UnitFactory; break; case RTTI_INFANTRY: case RTTI_INFANTRYTYPE: factory_index = &InfantryFactory; break; case RTTI_VESSEL: case RTTI_VESSELTYPE: factory_index = &VesselFactory; break; case RTTI_BUILDING: case RTTI_BUILDINGTYPE: factory_index = &BuildingFactory; break; case RTTI_AIRCRAFT: case RTTI_AIRCRAFTTYPE: factory_index = &AircraftFactory; break; } assert(factory_index != NULL); /* ** Assign the factory to the appropriate slot. For the case of clearing ** the factory out, then -1 is assigned. */ if (factory != NULL) { *factory_index = factory->ID; } else { *factory_index = -1; } } /*********************************************************************************************** * HouseClass::Factory_Count -- Fetches the number of factories for specified type. * * * * This routine will count the number of factories owned by this house that can build * * objects of the specified type. * * * * INPUT: rtti -- The type of object (RTTI) that the factories are to be counted for. * * * * OUTPUT: Returns with the number of factories that can build the object type specified. * * * * WARNINGS: none * * * * HISTORY: * * 07/30/1996 JLB : Created. * *=============================================================================================*/ int HouseClass::Factory_Count(RTTIType rtti) const { int const * ptr = ((HouseClass *)this)->Factory_Counter(rtti); if (ptr != NULL) { return(*ptr); } return(0); } /*********************************************************************************************** * HouseClass::Get_Quantity -- Gets the quantity of the building type specified. * * * * This will return the total number of buildings of that type owned by this house. * * * * INPUT: building -- The building type to check. * * * * OUTPUT: Returns with the number of buildings of that type owned by this house. * * * * WARNINGS: none * * * * HISTORY: * * 07/09/1996 JLB : Created. * *=============================================================================================*/ int HouseClass::Get_Quantity(StructType building) { return(BQuantity[building]); } /*********************************************************************************************** * HouseClass::Read_INI -- Reads house specific data from INI. * * * * This routine reads the house specific data for a particular * * scenario from the scenario INI file. Typical data includes starting * * credits, maximum unit count, etc. * * * * INPUT: buffer -- Pointer to loaded scenario INI file. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 05/24/1994 JLB : Created. * * 05/18/1995 JLB : Creates all houses. * *=============================================================================================*/ void HouseClass::Read_INI(CCINIClass & ini) { HouseClass * p; // Pointer to current player data. char const * hname; // Pointer to house name. for (HousesType index = HOUSE_FIRST; index < HOUSE_COUNT; index++) { hname = HouseTypeClass::As_Reference(index).IniName; p = new HouseClass(index); p->Control.TechLevel = ini.Get_Int(hname, "TechLevel", Scen.Scenario); p->Control.MaxBuilding = ini.Get_Int(hname, "MaxBuilding", p->Control.MaxBuilding); p->Control.MaxUnit = ini.Get_Int(hname, "MaxUnit", p->Control.MaxUnit); p->Control.MaxInfantry = ini.Get_Int(hname, "MaxInfantry", p->Control.MaxInfantry); p->Control.MaxVessel = ini.Get_Int(hname, "MaxVessel", p->Control.MaxVessel); if (p->Control.MaxVessel == 0) p->Control.MaxVessel = p->Control.MaxUnit; p->Control.InitialCredits = ini.Get_Int(hname, "Credits", 0) * 100; p->Credits = p->Control.InitialCredits; int iq = ini.Get_Int(hname, "IQ", 0); if (iq > Rule.MaxIQ) iq = 1; p->IQ = p->Control.IQ = iq; p->Control.Edge = ini.Get_SourceType(hname, "Edge", SOURCE_NORTH); p->IsPlayerControl = ini.Get_Bool(hname, "PlayerControl", false); int owners = ini.Get_Owners(hname, "Allies", (1 << HOUSE_NEUTRAL)); p->Make_Ally(index); p->Make_Ally(HOUSE_NEUTRAL); for (HousesType h = HOUSE_FIRST; h < HOUSE_COUNT; h++) { if ((owners & (1 << h)) != 0) { p->Make_Ally(h); } } } } /*********************************************************************************************** * HouseClass::Write_INI -- Writes the house data to the INI database. * * * * This routine will write out all data necessary to recreate it in anticipation of a * * new scenario. All houses (that are active) will have their scenario type data written * * out. * * * * INPUT: ini -- Reference to the INI database to write the data to. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 07/09/1996 JLB : Created. * *=============================================================================================*/ void HouseClass::Write_INI(CCINIClass & ini) { /* ** The identity house control object. Only if the house value differs from the ** identity, will the data be written out. */ HouseStaticClass control; for (HousesType i = HOUSE_FIRST; i < HOUSE_COUNT; i++) { HouseClass * p = As_Pointer(i); if (p != NULL) { char const * name = p->Class->IniName; ini.Clear(name); if (i >= HOUSE_MULTI1) continue; if (p->Control.InitialCredits != control.InitialCredits) { ini.Put_Int(name, "Credits", (int)(p->Control.InitialCredits / 100)); } if (p->Control.Edge != control.Edge) { ini.Put_SourceType(name, "Edge", p->Control.Edge); } if (p->Control.MaxUnit > 0 && p->Control.MaxUnit != control.MaxUnit) { ini.Put_Int(name, "MaxUnit", p->Control.MaxUnit); } if (p->Control.MaxInfantry > 0 && p->Control.MaxInfantry != control.MaxInfantry) { ini.Put_Int(name, "MaxInfantry", p->Control.MaxInfantry); } if (p->Control.MaxBuilding > 0 && p->Control.MaxBuilding != control.MaxBuilding) { ini.Put_Int(name, "MaxBuilding", p->Control.MaxBuilding); } if (p->Control.MaxVessel > 0 && p->Control.MaxVessel != control.MaxVessel) { ini.Put_Int(name, "MaxVessel", p->Control.MaxVessel); } if (p->Control.TechLevel != control.TechLevel) { ini.Put_Int(name, "TechLevel", p->Control.TechLevel); } if (p->Control.IQ != control.IQ) { ini.Put_Int(name, "IQ", p->Control.IQ); } if (p->IsPlayerControl != false && p != PlayerPtr) { ini.Put_Bool(name, "PlayerControl", p->IsPlayerControl); } ini.Put_Owners(name, "Allies", p->Control.Allies & ~((1 << p->Class->House) | (1 << HOUSE_NEUTRAL))); } } } /*********************************************************************************************** * HouseClass::Is_No_YakMig -- Determines if no more yaks or migs should be allowed. * * * * This routine will examine the current yak and mig situation verses airfields. If there * * are equal aircraft to airfields, then this routine will return TRUE. * * * * INPUT: none * * * * OUTPUT: bool; Are all airfields full and thus no more yaks or migs are allowed? * * * * WARNINGS: none * * * * HISTORY: * * 09/23/1996 JLB : Created. * *=============================================================================================*/ bool HouseClass::Is_No_YakMig(void) const { int quantity = AQuantity[AIRCRAFT_YAK] + AQuantity[AIRCRAFT_MIG]; /* ** Adjust the quantity down one if there is an aircraft in production. This will ** allow production to resume after being held. */ FactoryClass const * factory = Fetch_Factory(RTTI_AIRCRAFT); if (factory != NULL && factory->Get_Object() != NULL) { AircraftClass const * air = (AircraftClass const *)factory->Get_Object(); if (*air == AIRCRAFT_MIG || *air == AIRCRAFT_YAK) { quantity -= 1; } } if (quantity >= BQuantity[STRUCT_AIRSTRIP]) { return(true); } return(false); } /*********************************************************************************************** * HouseClass::Is_Hack_Prevented -- Is production of the specified type and id prohibted? * * * * This is a special hack check routine to see if the object type and id specified is * * prevented from being produced. The Yak and the Mig are so prevented if there would be * * insufficient airfields for them to land upon. * * * * INPUT: rtti -- The RTTI type of the value specified. * * * * value -- The type number (according to the RTTI type specified). * * * * OUTPUT: bool; Is production of this object prohibited? * * * * WARNINGS: none * * * * HISTORY: * * 09/23/1996 JLB : Created. * *=============================================================================================*/ bool HouseClass::Is_Hack_Prevented(RTTIType rtti, int value) const { if (rtti == RTTI_AIRCRAFTTYPE && (value == AIRCRAFT_MIG || value == AIRCRAFT_YAK)) { return(Is_No_YakMig()); } return(false); } /*********************************************************************************************** * HouseClass::Fire_Sale -- Cause all buildings to be sold. * * * * This routine will sell back all buildings owned by this house. * * * * INPUT: none * * * * OUTPUT: bool; Was a fire sale performed? * * * * WARNINGS: none * * * * HISTORY: * * 09/23/1996 JLB : Created. * *=============================================================================================*/ bool HouseClass::Fire_Sale(void) { if (CurBuildings > 0) { for (int index = 0; index < Buildings.Count(); index++) { BuildingClass * b = Buildings.Ptr(index); if (b != NULL && !b->IsInLimbo && b->House == this && b->Strength > 0) { b->Sell_Back(1); } } return(true); } return(false); } /*********************************************************************************************** * HouseClass::Do_All_To_Hunt -- Send all units to hunt. * * * * This routine will cause all combatants of this house to go into hunt mode. The effect of * * this is to throw everything this house has to muster at the enemies of this house. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 09/23/1996 JLB : Created. * * 10/02/1996 JLB : Handles aircraft too. * *=============================================================================================*/ void HouseClass::Do_All_To_Hunt(void) const { int index; for (index = 0; index < Units.Count(); index++) { UnitClass * unit = Units.Ptr(index); if (unit->House == this && unit->IsDown && !unit->IsInLimbo) { if (unit->Team) unit->Team->Remove(unit); unit->Assign_Mission(MISSION_HUNT); } } for (index = 0; index < Infantry.Count(); index++) { InfantryClass * infantry = Infantry.Ptr(index); if (infantry->House == this && infantry->IsDown && !infantry->IsInLimbo) { if (infantry->Team) infantry->Team->Remove(infantry); infantry->Assign_Mission(MISSION_HUNT); } } for (index = 0; index < Vessels.Count(); index++) { VesselClass * vessel = Vessels.Ptr(index); if (vessel->House == this && vessel->IsDown && !vessel->IsInLimbo) { if (vessel->Team) vessel->Team->Remove(vessel); vessel->Assign_Mission(MISSION_HUNT); } } for (index = 0; index < Aircraft.Count(); index++) { AircraftClass * aircraft = Aircraft.Ptr(index); if (aircraft->House == this && aircraft->IsDown && !aircraft->IsInLimbo) { if (aircraft->Team) aircraft->Team->Remove(aircraft); aircraft->Assign_Mission(MISSION_HUNT); } } } /*********************************************************************************************** * HouseClass::Is_Allowed_To_Ally -- Determines if this house is allied to make allies. * * * * Use this routine to determine if this house is legally allowed to ally with the * * house specified. There are many reason why an alliance is not allowed. Typically, this * * is when there would be no more opponents left to fight or if this house has been * * defeated. * * * * INPUT: house -- The house that alliance with is desired. * * * * OUTPUT: bool; Is alliance with the house specified prohibited? * * * * WARNINGS: none * * * * HISTORY: * * 09/23/1996 JLB : Created. * *=============================================================================================*/ bool HouseClass::Is_Allowed_To_Ally(HousesType house) const { /* ** Is not allowed to ally with a house that is patently invalid, such ** as one that is illegally defined. */ if (house == HOUSE_NONE) { return(false); } /* ** One cannot ally twice with the same house. */ if (Is_Ally(house)) { return(false); } /* ** If the scenario is being set up, then alliances are always ** allowed. No further checking is required. */ if (ScenarioInit) { return(true); } /* ** Alliances (outside of scneario init time) are allowed only if ** this is a multiplayer game. Otherwise, they are prohibited. */ if (Session.Type == GAME_NORMAL) { return(false); } /* ** When the house is defeated, it can no longer make alliances. */ if (IsDefeated) { return(false); } #ifdef FIXIT_VERSION_3 // Fix to prevent ally with computer. if ( !HouseClass::As_Pointer(house)->IsHuman) { return(false); } #else // FIXIT_VERSION_3 #ifdef FIXIT_NO_COMP_ALLY // Fix to prevent ally with computer. if (PlayingAgainstVersion > VERSION_RED_ALERT_104 && !HouseClass::As_Pointer(house)->IsHuman) { return(false); } #endif #endif // FIXIT_VERSION_3 /* ** Count the number of active houses in the game as well as the ** number of existing allies with this house. */ int housecount = 0; int allycount = 0; for (HousesType house2 = HOUSE_MULTI1; house2 < HOUSE_COUNT; house2++) { HouseClass * hptr = HouseClass::As_Pointer(house2); if (hptr != NULL && hptr->IsActive && !hptr->IsDefeated) { housecount++; if (Is_Ally(hptr)) { allycount++; } } } /* ** Alliance is not allowed if there wouldn't be any enemies left to ** fight. */ if (housecount == allycount+1) { return(false); } return(true); } /*********************************************************************************************** * HouseClass::Computer_Paranoid -- Cause the computer players to becom paranoid. * * * * This routine will cause the computer players to become suspicious of the human * * players and thus the computer players will band together in order to defeat the * * human players. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 09/23/1996 JLB : Created. * *=============================================================================================*/ void HouseClass::Computer_Paranoid(void) { if (Session.Type != GAME_GLYPHX_MULTIPLAYER) { // Re-enable this for multiplayer if we support classic team/ally mode. ST - 10/29/2019 /* ** Loop through every computer controlled house and make allies with all other computer ** controlled houses and then make enemies with all other human controlled houses. */ for (HousesType house = HOUSE_MULTI1; house < HOUSE_COUNT; house++) { HouseClass * hptr = HouseClass::As_Pointer(house); if (hptr != NULL && hptr->IsActive && !hptr->IsDefeated && !hptr->IsHuman) { hptr->IsParanoid = true; /* ** Break alliance with every human it is allied with and make friends with ** any other computer players. */ for (HousesType house2 = HOUSE_MULTI1; house2 < HOUSE_COUNT; house2++) { HouseClass * hptr2 = HouseClass::As_Pointer(house2); if (hptr2 != NULL && hptr2->IsActive && !hptr2->IsDefeated) { if (hptr2->IsHuman) { hptr->Make_Enemy(house2); } else { hptr->Make_Ally(house2); } } } } } } } /*********************************************************************************************** * HouseClass::Adjust_Power -- Adjust the power value of the house. * * * * This routine will update the power output value of the house. It will cause any buildgins* * that need to be redrawn to do so. * * * * INPUT: adjust -- The amount to adjust the power output value. * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 11/01/1996 BWG : Created. * *=============================================================================================*/ void HouseClass::Adjust_Power(int adjust) { Power += adjust; Update_Spied_Power_Plants(); } /*********************************************************************************************** * HouseClass::Adjust_Drain -- Adjust the power drain value of the house. * * * * This routine will update the drain value of the house. It will cause any buildings that * * need to be redraw to do so. * * * * INPUT: adjust -- The amount to adjust the drain (positive means more drain). * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 11/01/1996 BWG : Created. * *=============================================================================================*/ void HouseClass::Adjust_Drain(int adjust) { Drain += adjust; Update_Spied_Power_Plants(); } /*********************************************************************************************** * HouseClass::Update_Spied_Power_Plants -- Redraw power graphs on spied-upon power plants. * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 10/11/1996 BWG : Created. * *=============================================================================================*/ void HouseClass::Update_Spied_Power_Plants(void) { int count = CurrentObject.Count(); if (count) { for (int index = 0; index < count; index++) { ObjectClass const * tech = CurrentObject[index]; if (tech && tech->What_Am_I()==RTTI_BUILDING) { BuildingClass *bldg = (BuildingClass *)tech; if (!bldg->IsOwnedByPlayer && *bldg == STRUCT_POWER || *bldg == STRUCT_ADVANCED_POWER) { if ( bldg->Spied_By() & (1<<(PlayerPtr->Class->House)) ) { bldg->Mark(MARK_CHANGE); } } } } } } /*********************************************************************************************** * HouseClass::Find_Cell_In_Zone -- Finds a legal placement cell within the zone. * * * * Use this routine to determine where the specified object should go if it were to go * * some random (but legal) location within the zone specified. * * * * INPUT: techno -- The object that is desirous of going into the zone specified. * * * * zone -- The zone to find a location within. * * * * OUTPUT: Returns with the cell that the specified object could be placed in the zone. If * * no valid location could be found, then 0 is returned. * * * * WARNINGS: none * * * * HISTORY: * * 11/01/1996 JLB : Created. * * 11/04/1996 JLB : Not so strict on zone requirement. * *=============================================================================================*/ CELL HouseClass::Find_Cell_In_Zone(TechnoClass const * techno, ZoneType zone) const { if (techno == NULL) return(0); int bestval = -1; int bestcell = 0; TechnoTypeClass const * ttype = techno->Techno_Type_Class(); /* ** Pick a random location within the zone specified. */ CELL trycell = Random_Cell_In_Zone(zone); short const * list = NULL; if (techno->What_Am_I() == RTTI_BUILDING) { list = techno->Occupy_List(true); } /* ** Find a legal placement position as close as possible to the picked location while still ** remaining within the zone. */ for (CELL cell = 0; cell < MAP_CELL_TOTAL; cell++) { // if (Map.In_Radar(cell)) { if (Map.In_Radar(cell) && Which_Zone(cell) != ZONE_NONE) { bool ok = ttype->Legal_Placement(cell); /* ** Another (adjacency) check is required for buildings. */ if (ok && list != NULL && !Map.Passes_Proximity_Check(ttype, techno->House->Class->House, list, cell)) { ok = false; } if (ok) { int dist = Distance(Cell_Coord(cell), Cell_Coord(trycell)); if (bestval == -1 || dist < bestval) { bestval = dist; bestcell = cell; } } } } /* ** Return the best location to move to. */ return(bestcell); } /*********************************************************************************************** * HouseClass::Random_Cell_In_Zone -- Find a (technically) legal cell in the zone specified. * * * * This routine will pick a random cell within the zone specified. The pick will be * * clipped to the map edge when necessary. * * * * INPUT: zone -- The zone to pick a cell from. * * * * OUTPUT: Returns with a picked cell within the zone. If the entire zone lies outside of the * * map, then a cell in the core zone is returned instead. * * * * WARNINGS: none * * * * HISTORY: * * 11/04/1996 JLB : Created. * *=============================================================================================*/ CELL HouseClass::Random_Cell_In_Zone(ZoneType zone) const { COORDINATE coord = 0; int maxdist = 0; switch (zone) { case ZONE_CORE: coord = Coord_Scatter(Center, Random_Pick(0, Radius), true); break; case ZONE_NORTH: maxdist = min(Radius*3, (Coord_Y(Center) - Cell_To_Lepton(Map.MapCellY)) - CELL_LEPTON_H); if (maxdist < 0) break; coord = Coord_Move(Center, (DirType)(Random_Pick(DIR_N, DIR_E)-((DirType)32)), Random_Pick(min(Radius*2, maxdist), min(Radius*3, maxdist))); break; case ZONE_EAST: maxdist = min(Radius*3, (Cell_To_Lepton(Map.MapCellX + Map.MapCellWidth) - Coord_X(Center)) - CELL_LEPTON_W); if (maxdist < 0) break; coord = Coord_Move(Center, Random_Pick(DIR_NE, DIR_SE), Random_Pick(min(Radius*2, maxdist), min(Radius*3, maxdist))); break; case ZONE_SOUTH: maxdist = min(Radius*3, (Cell_To_Lepton(Map.MapCellY + Map.MapCellHeight) - Coord_Y(Center)) - CELL_LEPTON_H); if (maxdist < 0) break; coord = Coord_Move(Center, Random_Pick(DIR_SE, DIR_SW), Random_Pick(min(Radius*2, maxdist), min(Radius*3, maxdist))); break; case ZONE_WEST: maxdist = min(Radius*3, (Coord_X(Center) - Cell_To_Lepton(Map.MapCellX)) - CELL_LEPTON_W); if (maxdist < 0) break; coord = Coord_Move(Center, Random_Pick(DIR_SW, DIR_NW), Random_Pick(min(Radius*2, maxdist), min(Radius*3, maxdist))); break; } /* ** Double check that the location is valid and if so, convert it into a cell ** number. */ CELL cell; if (coord == 0 || !Map.In_Radar(Coord_Cell(coord))) { if (zone == ZONE_CORE) { /* ** Finding a cell within the core failed, so just pick the center ** cell. This cell is guaranteed to be valid. */ cell = Coord_Cell(Center); } else { /* ** If the edge fails, then try to find a cell within the core. */ cell = Random_Cell_In_Zone(ZONE_CORE); } } else { cell = Coord_Cell(coord); } /* ** If the randomly picked location is not in the legal map area, then clip it to ** the legal map area. */ if (!Map.In_Radar(cell)) { int x = Cell_X(cell); int y = Cell_Y(cell); if (x < Map.MapCellX) x = Map.MapCellX; if (y < Map.MapCellY) y = Map.MapCellY; if (x >= Map.MapCellX + Map.MapCellWidth) x = Map.MapCellX + Map.MapCellWidth -1; if (y >= Map.MapCellY + Map.MapCellHeight) y = Map.MapCellY + Map.MapCellHeight -1; cell = XY_Cell(x, y); } return(cell); } /*********************************************************************************************** * HouseClass::Get_Ally_Flags -- Get the bit flags denoting the allies this house has. * * * * INPUT: none * * * * OUTPUT: Returns the bit field storing which houses this house is allied with. * * * * WARNINGS: none * * * * HISTORY: * * 09/12/2019 JAS : Created. * *=============================================================================================*/ unsigned HouseClass::Get_Ally_Flags() { return Allies; } /*********************************************************************************************** * HouseClass::Check_Pertinent_Structures -- See if any useful structures remain * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 1/31/2020 3:34PM ST : Created. * *=============================================================================================*/ void HouseClass::Check_Pertinent_Structures(void) { /* ** New default win mode to avoid griefing. ST - 1/31/2020 3:33PM ** ** Game is over when no pertinent structures remain */ if (!Special.IsEarlyWin) { return; } if (IsToDie || IsToWin || IsToLose) { return; } // MBL 07.15.2020 - Prevention of recent issue with constant "player defeated logic" and message to client spamming // Per https://jaas.ea.com/browse/TDRA-7433 // if (IsDefeated) { return; } bool any_good_buildings = false; for (int index = 0; index < Buildings.Count(); index++) { BuildingClass *b = Buildings.Ptr(index); if (b && b->IsActive && b->House == this) { if (!b->Class->IsWall && *b != STRUCT_APMINE && *b != STRUCT_AVMINE) { if (!Special.ModernBalance || (*b != STRUCT_SHIP_YARD && *b != STRUCT_FAKE_YARD && *b != STRUCT_SUB_PEN && *b != STRUCT_FAKE_PEN)) { if (!b->IsInLimbo && b->Strength > 0) { any_good_buildings = true; break; } } } } } if (!any_good_buildings) { for (int index = 0; index < Units.Count(); index++) { UnitClass * unit = Units.Ptr(index); if (unit && unit->IsActive && *unit == UNIT_MCV && unit->House == this) { if (!unit->IsInLimbo && unit->Strength > 0) { any_good_buildings = true; break; } } } } if (!any_good_buildings) { Flag_To_Die(); } } /*********************************************************************************************** * HouseClass::Init_Unit_Trackers -- Allocate the unit trackers for the house * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 4/23/2020 11:06PM ST : Created. * *=============================================================================================*/ void HouseClass::Init_Unit_Trackers(void) { if (Session.Type == GAME_INTERNET || Session.Type == GAME_GLYPHX_MULTIPLAYER) { AircraftTotals = new UnitTrackerClass( (int) AIRCRAFT_COUNT); InfantryTotals = new UnitTrackerClass( (int) INFANTRY_COUNT); UnitTotals = new UnitTrackerClass ( (int) UNIT_COUNT); BuildingTotals = new UnitTrackerClass ( (int) STRUCT_COUNT); VesselTotals = new UnitTrackerClass ( (int) VESSEL_COUNT); DestroyedAircraft = new UnitTrackerClass ( (int) AIRCRAFT_COUNT); DestroyedInfantry = new UnitTrackerClass( (int) INFANTRY_COUNT); DestroyedUnits = new UnitTrackerClass ( (int) UNIT_COUNT); DestroyedBuildings = new UnitTrackerClass ( (int) STRUCT_COUNT); DestroyedVessels = new UnitTrackerClass ( (int) VESSEL_COUNT); CapturedBuildings = new UnitTrackerClass ( (int) STRUCT_COUNT); TotalCrates = new UnitTrackerClass ( CRATE_COUNT ); } else { AircraftTotals = NULL; InfantryTotals = NULL; UnitTotals = NULL; BuildingTotals = NULL; VesselTotals = NULL; DestroyedAircraft = NULL; DestroyedInfantry = NULL; DestroyedUnits = NULL; DestroyedBuildings = NULL; DestroyedVessels = NULL; CapturedBuildings = NULL; TotalCrates = NULL; } } /*********************************************************************************************** * HouseClass::Free_Unit_Trackers -- Free the unit trackers for the house * * * * INPUT: none * * * * OUTPUT: none * * * * WARNINGS: none * * * * HISTORY: * * 4/23/2020 11:06PM ST : Created. * *=============================================================================================*/ void HouseClass::Free_Unit_Trackers(void) { if (AircraftTotals) { delete AircraftTotals; AircraftTotals = NULL; } if (InfantryTotals) { delete InfantryTotals; InfantryTotals = NULL; } if (UnitTotals) { delete UnitTotals; UnitTotals = NULL; } if (BuildingTotals) { delete BuildingTotals; BuildingTotals = NULL; } if (VesselTotals) { delete VesselTotals; VesselTotals = NULL; } if (DestroyedAircraft) { delete DestroyedAircraft; DestroyedAircraft = NULL; } if (DestroyedInfantry) { delete DestroyedInfantry; DestroyedInfantry = NULL; } if (DestroyedUnits) { delete DestroyedUnits; DestroyedUnits = NULL; } if (DestroyedBuildings) { delete DestroyedBuildings; DestroyedBuildings = NULL; } if (DestroyedVessels) { delete DestroyedVessels; DestroyedVessels = NULL; } if (CapturedBuildings) { delete CapturedBuildings; CapturedBuildings = NULL; } if (TotalCrates) { delete TotalCrates; TotalCrates = NULL; } }