CnC_Remastered_Collection/TIBERIANDAWN/HOUSE.CPP
PG-SteveT fd05be35c1 September 16th patch update
DLL version incremented
Beacon functionality added
Support for loading screen match preview display
Placeholder handling of new key-bindable mod commands
2020-09-16 10:03:04 -07:00

8676 lines
350 KiB
C++

//
// Copyright 2020 Electronic Arts Inc.
//
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
/* $Header: F:\projects\c&c\vcs\code\house.cpv 2.13 02 Aug 1995 17:03:50 JOE_BOSTIC $ */
/***********************************************************************************************
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
***********************************************************************************************
* *
* Project Name : Command & Conquer *
* *
* File Name : HOUSE.CPP *
* *
* Programmer : Joe L. Bostic *
* *
* Start Date : May 21, 1994 *
* *
* Last Update : August 12, 1995 [JLB] *
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* HouseClass::AI -- Process house logic. *
* HouseClass::Abandon_Production -- Abandons production of item type specified. *
* HouseClass::Add_Nuke_Piece -- Add a nuclear piece to the collection. *
* HouseClass::Adjust_Capacity -- Adjusts the house Tiberium storage capacity. *
* 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 -- Determines if the aircraft type can be built. *
* HouseClass::Can_Build -- Determines if the building type can be built. *
* HouseClass::Can_Build -- Determines if the infantry unit can be built by this house. *
* HouseClass::Can_Build -- Determines if the unit can be built by this house. *
* HouseClass::Can_Build -- General purpose build legality checker. *
* HouseClass::Clobber_All -- removes house & all its objects *
* HouseClass::Debug_Dump -- Dumps the house status data to the mono screen. *
* HouseClass::Detach -- Removes specified object from house tracking systems. *
* HouseClass::Does_Enemy_Building_Exist -- Checks for enemy building of specified type. *
* 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::Harvested -- Adds Tiberium to the harvest storage. *
* HouseClass::Has_Nuke_Device -- Deteremines if the house has a nuclear device. *
* HouseClass::HouseClass -- Constructor for a house object. *
* HouseClass::Init -- init's in preparation for new scenario *
* HouseClass::Init_Air_Strike -- Add (or reset) the air strike sidebar button. *
* HouseClass::Init_Data -- Initializes the multiplayer color data. *
* HouseClass::Init_Ion_Cannon -- Initialize the ion cannon countdown. *
* HouseClass::Init_Nuke_Bomb -- Adds (if necessary) the atom bomb to the sidebar. *
* 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_Ally -- Determines if the specified house is an ally. *
* HouseClass::MPlayer_Defeated -- multiplayer; house is defeated *
* HouseClass::Make_Air_Strike_Available -- Make the airstrike available. *
* 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::Read_INI -- Reads house specific data from INI. *
* HouseClass::Refund_Money -- Refunds money to back to the house. *
* HouseClass::Remap_Table -- Fetches the remap table for this house object. *
* HouseClass::Remove_Air_Strike -- Removes the air strike button from the sidebar. *
* HouseClass::Remove_Ion_Cannon -- Disables the ion cannon. *
* HouseClass::Remove_Nuke_Bomb -- Removes the nuclear bomb from the sidebar. *
* HouseClass::Sell_Wall -- Tries to sell the wall at the specified location. *
* 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_Object -- Determine what would the next buildable object be. *
* HouseClass::Suggested_New_Team -- Determine what team should be created. *
* HouseClass::Suspend_Production -- Temporarily puts production on hold. *
* HouseClass::Validate -- validates house pointer *
* HouseClass::Write_INI -- Writes house specific data into INI file. *
* 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. *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "function.h"
/*
** New sidebar for GlyphX multiplayer. ST - 3/26/2019 12:24PM
*/
#include "SidebarGlyphx.h"
#include "defines.h"
/***********************************************************************************************
* HouseClass::Validate -- validates house pointer *
* *
* INPUT: *
* none. *
* *
* OUTPUT: *
* 1 = ok, 0 = error *
* *
* WARNINGS: *
* none. *
* *
* HISTORY: *
* 08/09/1995 BRR : Created. *
*=============================================================================================*/
#ifdef CHEAT_KEYS
int HouseClass::Validate(void) const
{
int num;
num = Houses.ID(this);
if (num < 0 || num >= HOUSE_MAX) {
Validate_Error("HOUSE");
return (0);
}
else
return (1);
}
#else
#define Validate()
#endif
/***********************************************************************************************
* 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
{
Validate();
return(Class->House);
}
/***********************************************************************************************
* 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)
{
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)
{
// for (HousesType index = HOUSE_FIRST; index < HOUSE_COUNT; index++) {
// new(index) HouseClass;
// }
#ifdef USE_RA_AI
/*
** Required for Red Alert AI. ST - 7/23/2019 3:21PM
*/
BuildChoice.Set_Heap(STRUCT_COUNT);
#endif
}
/***********************************************************************************************
* 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 (GameToPlay != GAME_NORMAL) {
HouseTypeClass const * hptr = &HouseTypeClass::As_Reference(ActLike);
FirepowerBias = hptr->FirepowerBias * Rule.Diff[handicap].FirepowerBias;
GroundspeedBias = hptr->GroundspeedBias * Rule.Diff[handicap].GroundspeedBias;
AirspeedBias = hptr->AirspeedBias * Rule.Diff[handicap].AirspeedBias;
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;
} else {
FirepowerBias = Rule.Diff[handicap].FirepowerBias;
GroundspeedBias = Rule.Diff[handicap].GroundspeedBias;
AirspeedBias = Rule.Diff[handicap].AirspeedBias;
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;
}
return(old);
}
#ifdef CHEAT_KEYS
/***********************************************************************************************
* 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 *) const
{
Validate();
}
#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. *
*=============================================================================================*/
HouseClass::HouseClass(HousesType house) :
Class(&HouseTypeClass::As_Reference(house)),
IonCannon(ION_CANNON_GONE_TIME, VOX_ION_READY, VOX_ION_CHARGING, VOX_ION_CHARGING, VOX_NO_POWER),
AirStrike(AIR_CANNON_GONE_TIME, VOX_AIRSTRIKE_READY, VOX_NONE, VOX_NOT_READY, VOX_NOT_READY),
NukeStrike(NUKE_GONE_TIME, VOX_NUKE_AVAILABLE, VOX_NONE, VOX_NOT_READY, VOX_NO_POWER)
{
for (HousesType i = HOUSE_FIRST; i < HOUSE_COUNT; i++) {
UnitsKilled[i] = 0;
BuildingsKilled[i] = 0;
}
WhoLastHurtMe = house; // init this to myself
IsVisionary = false;
IsFreeHarvester = false;
Blockage = 0;
UnitsLost = 0;
BuildingsLost = 0;
NewActiveBScan = 0;
ActiveBScan = 0;
NewActiveUScan = 0;
ActiveUScan = 0;
NewActiveIScan = 0;
ActiveIScan = 0;
NewActiveAScan = 0;
ActiveAScan = 0;
strcpy((char *)Name, "Computer"); // Default computer name.
JustBuilt = STRUCT_NONE;
AlertTime = 0;
IsAlerted = false;
IsAirstrikePending = false;
AircraftFactory = -1;
AircraftFactories = 0;
ActLike = Class->House;
Allies = 0;
AScan = 0;
NukeDest = 0;
BlitzTime.Clear();
ScreenShakeTime.Clear();
BScan = 0;
BuildingFactories = 0;
BuildingFactory = -1;
Capacity = 0;
Credits = 0;
CreditsSpent = 0;
CurBuildings = 0;
CurUnits = 0;
DamageTime = DAMAGE_DELAY;
Drain = 0;
Edge = SOURCE_NORTH;
FlagHome = 0;
FlagLocation = TARGET_NONE;
HarvestedCredits = 0;
HouseTriggers[house].Clear();
IGaveUp = false;
InfantryFactories = 0;
InfantryFactory = -1;
InitialCredits = 0;
InitialCredits = 0;
IScan = 0;
IsRecalcNeeded = true;
IsCivEvacuated = false;
IsDefeated = false;
IsDiscovered = false;
IsHuman = false;
WasHuman = false;
IsMaxedOut = false;
IsStarted = false;
IsToDie = false;
IsToLose = false;
IsToWin = false;
Make_Ally(house);
MaxBuilding = 0;
MaxUnit = 0;
NewAScan = 0;
NewBScan = 0;
NewIScan = 0;
NewUScan = 0;
NukePieces = 0x07;
Power = 0;
Radar = RADAR_NONE;
RemapTable = Class->RemapTable;
RemapColor = Class->RemapColor;
Resigned = false;
SpeakAttackDelay = 1;
SpeakMaxedDelay = 1;
SpeakMoneyDelay = 1;
SpeakMoneyDelay.Set(Options.Normalize_Delay(SPEAK_DELAY)); // 2 minutes
SpeakPowerDelay = 1;
SpecialFactories = 0;
SpecialFactory = -1;
TeamTime = TEAM_DELAY;
Tiberium = 0;
TriggerTime = 0;
UnitFactories = 0;
UnitFactory = -1;
UScan = 0;
memset((void *)&Regions[0], 0x00, sizeof(Regions));
Init_Unit_Trackers();
DebugUnlockBuildables = false;
StartLocationOverride = -1;
/*
** New AI variables from RA. Need to add to save/load?
*/
#ifdef USE_RA_AI
IsBaseBuilding = true;
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;
}
LATime = 0;
LAType = RTTI_NONE;
LAZone = ZONE_NONE;
LAEnemy = HOUSE_NONE;
ToCapture = TARGET_NONE;
RadarSpied = 0;
PointTotal = 0;
memset(BQuantity, '\0', sizeof(BQuantity));
memset(UQuantity, '\0', sizeof(UQuantity));
memset(IQuantity, '\0', sizeof(IQuantity));
memset(AQuantity, '\0', sizeof(AQuantity));
Attack = 0;
Enemy = HOUSE_NONE;
AITimer = 0;
BuildStructure = STRUCT_NONE;
BuildUnit = UNIT_NONE;
BuildInfantry = INFANTRY_NONE;
BuildAircraft = AIRCRAFT_NONE;
State = STATE_BUILDUP;
IsTiberiumShort = false;
IQ = Rule.MaxIQ;
IsParanoid = false;
OldBScan = 0;
Assign_Handicap(DIFF_NORMAL);
#endif
}
HouseClass::~HouseClass (void)
{
Free_Unit_Trackers();
}
/***********************************************************************************************
* 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. *
*=============================================================================================*/
bool HouseClass::Can_Build(TechnoTypeClass const * type, HousesType house) const
{
Validate();
if (!type || !type->IsBuildable || !((1L << house) & type->Ownable)) return(false);
/*
** The computer can always build everthing.
*/
#ifdef USE_RA_AI
if (!IsHuman && GameToPlay == GAME_NORMAL) return(true); // Added game type check for AI from RA. ST - 7/25/2019 3:25PM
#else
if (!IsHuman) return(true);
#endif
/*
** Perform some equivalency fixups for the building existance flags.
*/
long flags = ActiveBScan;
/*
** AI players update flags using building quantity tracker.
** Ensures consistent logic when determining building choices.
*/
if (!IsHuman) {
flags = 0;
for (int i = 0; i < 32; i++) {
if (BQuantity[i] > 0) {
flags |= (1 << i);
}
}
}
int pre = type->Pre;
if (flags & STRUCTF_ADVANCED_POWER) flags |= STRUCTF_POWER;
if (flags & STRUCTF_HAND) flags |= STRUCTF_BARRACKS;
if (flags & STRUCTF_OBELISK) flags |= STRUCTF_ATOWER;
if (flags & STRUCTF_TEMPLE) flags |= STRUCTF_EYE;
if (flags & STRUCTF_AIRSTRIP) flags |= STRUCTF_WEAP;
if (flags & STRUCTF_SAM) flags |= STRUCTF_HELIPAD;
/*
** Multiplayer game uses a different legality check for building.
*/
if (GameToPlay != GAME_NORMAL || (Special.IsJurassic && AreThingiesEnabled)) {
if (DebugUnlockBuildables) {
return true;
}
return((pre & flags) == pre && type->Level <= BuildLevel);
}
#ifdef NEWMENU
int level = BuildLevel;
#else
int level = Scenario;
#endif
/*
** Special check to make the mission objective buildings the prerequisite
** for the stealth tank in mission #11 only.
*/
if (house == HOUSE_BAD &&
type->What_Am_I() == RTTI_UNITTYPE &&
((UnitTypeClass const *)type)->Type == UNIT_STANK &&
level == 11) {
pre = STRUCTF_MISSION;
level = type->Scenario;
}
/*
** Special case check to ensure that GDI doesn't get the bazooka guy
** until mission #8.
*/
if (house == HOUSE_GOOD &&
type->What_Am_I() == RTTI_INFANTRYTYPE &&
((InfantryTypeClass const *)type)->Type == INFANTRY_E3 &&
level < 7) {
return(false);
}
/*
** Special check to allow GDI to build the MSAM by mission #9
** and no sooner.
*/
if (house == HOUSE_GOOD &&
type->What_Am_I() == RTTI_UNITTYPE &&
((UnitTypeClass const *)type)->Type == UNIT_MLRS &&
level < 9) {
return(false);
}
/*
** Special case to disable the APC from the Nod player.
*/
if (house == HOUSE_BAD &&
type->What_Am_I() == RTTI_UNITTYPE &&
((UnitTypeClass const *)type)->Type == UNIT_APC) {
return(false);
}
/*
** Ensure that the Temple of Nod cannot be built by GDI even
** if GDI has captured the Nod construction yard.
*/
if (type->What_Am_I() == RTTI_BUILDINGTYPE &&
(((BuildingTypeClass const *)type)->Type == STRUCT_TEMPLE || ((BuildingTypeClass const *)type)->Type == STRUCT_OBELISK) &&
Class->House == HOUSE_GOOD) {
return(false);
}
/*
** Ensure that the rocket launcher tank cannot be built by Nod.
*/
if (type->What_Am_I() == RTTI_UNITTYPE &&
((UnitTypeClass const *)type)->Type == UNIT_MLRS &&
Class->House == HOUSE_BAD) {
return(false);
}
/*
** Ensure that the ion cannon cannot be built if
** Nod has captured the GDI construction yard.
*/
if (type->What_Am_I() == RTTI_BUILDINGTYPE &&
(((BuildingTypeClass const *)type)->Type == STRUCT_EYE) &&
Class->House == HOUSE_BAD) {
return(false);
}
/*
** Nod can build the advanced power plant at scenario #12.
*/
if (house == HOUSE_BAD &&
level >= 12 &&
type->What_Am_I() == RTTI_BUILDINGTYPE &&
((BuildingTypeClass const *)type)->Type == STRUCT_ADVANCED_POWER) {
level = type->Scenario;
}
/*
** Nod cannot build a helipad in the normal game.
*/
if (house == HOUSE_BAD &&
type->What_Am_I() == RTTI_BUILDINGTYPE &&
((BuildingTypeClass const *)type)->Type == STRUCT_HELIPAD) {
return(false);
}
/*
** GDI can build the sandbag wall only from scenario #9 onwards.
*/
if (house == HOUSE_GOOD &&
level < 8 &&
type->What_Am_I() == RTTI_BUILDINGTYPE &&
((BuildingTypeClass const *)type)->Type == STRUCT_SANDBAG_WALL) {
return(false);
}
/*
** GDI has a special second training mission. Adjust the scenario level so that
** scenario two will still feel like scenario #1.
*/
if (house == HOUSE_GOOD && level == 2) {
level = 1;
}
// ST - 8/23/2019 4:53PM
if (DebugUnlockBuildables) {
level = 98;
pre = 0;
}
if (Debug_Cheat) level = 98;
return((pre & flags) == pre && type->Scenario <= level);
}
/***********************************************************************************************
* HouseClass::Can_Build -- Determines if the building type can be built. *
* *
* This routine is used by the construction preparation code to building a list of building *
* types that can be built. It determines if a building can be built by checking if the *
* prerequisite buildings have been built (and still exist) as well as checking to see if *
* the house can build the specified structure. *
* *
* INPUT: s -- The structure type number that is being checked. *
* *
* house -- The house number to use when determining if the object can be built. *
* This is necessary because the current owner of the factory doesn't *
* control what the factory can produce. Rather, the original builder of *
* the factory controls this. *
* *
* OUTPUT: bool; Can this structure type be built at this time? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/08/1994 JLB : Created. *
* 05/31/1995 JLB : Handles specified ownership override. *
*=============================================================================================*/
bool HouseClass::Can_Build(StructType s, HousesType house) const
{
Validate();
return(Can_Build(&BuildingTypeClass::As_Reference(s), house));
}
/***********************************************************************************************
* HouseClass::Can_Build -- Determines if the infantry unit can be built by this house. *
* *
* Use this routine to determine if the infantry type specified can be built by this *
* house. It determines this by checking the ownership allowed bits in the infantry *
* type class. *
* *
* INPUT: infantry -- The infantry type to check against this house. *
* *
* house -- The house number to use when determining if the object can be built. *
* This is necessary because the current owner of the factory doesn't *
* control what the factory can produce. Rather, the original builder of *
* the factory controls this. *
* *
* OUTPUT: bool; Can the infantry be produced by this house? *
* *
* WARNINGS: It does not check to see if there is a functional barracks available, but *
* merely checks to see if it is legal for this house to build that infantry *
* type. *
* *
* HISTORY: *
* 12/09/1994 JLB : Created. *
* 05/31/1995 JLB : Handles specified ownership override. *
*=============================================================================================*/
bool HouseClass::Can_Build(InfantryType infantry, HousesType house) const
{
Validate();
return(Can_Build(&InfantryTypeClass::As_Reference(infantry), house));
}
/***********************************************************************************************
* HouseClass::Can_Build -- Determines if the unit can be built by this house. *
* *
* This routine is used to determine if the unit type specified can in fact be built by *
* this house. It checks the ownable bits in the unit's type to determine this. *
* *
* INPUT: unit -- The unit type to check against this house. *
* *
* house -- The house number to use when determining if the object can be built. *
* This is necessary because the current owner of the factory doesn't *
* control what the factory can produce. Rather, the original builder of *
* the factory controls this. *
* *
* OUTPUT: bool; Can the unit be built by this house? *
* *
* WARNINGS: This doesn't check to see if there is a functional factory that can build *
* this unit, but merely if the unit can be built according to ownership rules. *
* *
* HISTORY: *
* 12/09/1994 JLB : Created. *
* 05/31/1995 JLB : Handles specified ownership override. *
*=============================================================================================*/
bool HouseClass::Can_Build(UnitType unit, HousesType house) const
{
Validate();
return(Can_Build(&UnitTypeClass::As_Reference(unit), house));
}
/***********************************************************************************************
* HouseClass::Can_Build -- Determines if the aircraft type can be built. *
* *
* Use this routine to determine if the specified aircraft type can be built. This routine *
* is used by the sidebar and factory to determine what can be built. *
* *
* INPUT: aircraft -- The aircraft type to check for build legality. *
* *
* house -- The house that is performing the check. This is typically the house *
* of the original building of the factory rather than the current *
* owner. *
* *
* OUTPUT: Can this aircraft type be built by the house specified? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/24/1995 JLB : Created. *
*=============================================================================================*/
bool HouseClass::Can_Build(AircraftType aircraft, HousesType house) const
{
Validate();
return(Can_Build(&AircraftTypeClass::As_Reference(aircraft), house));
}
/***************************************************************************
* 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 - 4/17/2019 9:42AM
extern void Logic_Switch_Player_Context(HouseClass *house);
/***********************************************************************************************
* 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();
extern void On_Message(const char* message, float timeout_seconds, long long message_id);
void HouseClass::AI(void)
{
Validate();
// Set PlayerPtr to be this house. Not really keen on this solution but it means I have to make fewer code changes to the original code. ST - 4/17/2019 4:32PM
Logic_Switch_Player_Context(this);
/*
** Reset the scan accumulation bits for the next logic pass.
*/
IScan = NewIScan;
BScan = NewBScan;
UScan = NewUScan;
AScan = NewAScan;
ActiveIScan = NewActiveIScan;
ActiveBScan = NewActiveBScan;
ActiveUScan = NewActiveUScan;
ActiveAScan = NewActiveAScan;
NewIScan = 0;
NewBScan = 0;
NewUScan = 0;
NewAScan = 0;
NewActiveIScan = 0;
NewActiveBScan = 0;
NewActiveUScan = 0;
NewActiveAScan = 0;
#ifdef USE_RA_AI
//
// Copied from RA for AI. ST - 7/25/2019 3:58PM
//
/*
** 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 && GameToPlay != GAME_NORMAL && (IsBaseBuilding || IQ >= Rule.IQProduction)) {
IsBaseBuilding = true;
IsStarted = true;
IsAlerted = true;
}
#endif
/*
** Check to see if the house wins.
*/
if (GameToPlay == GAME_NORMAL && IsToWin && BorrowedTime.Expired() && Blockage <= 0) {
IsToWin = false;
if (this == PlayerPtr) {
PlayerWins = true;
} else {
PlayerLoses = true;
}
}
/*
** Check to see if the house loses.
*/
if (GameToPlay == GAME_NORMAL && IsToLose && BorrowedTime.Expired()) {
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.Expired()) {
IsToDie = false;
Blowup_All();
// MBL 07.15.2020 - Steve made this change for RA, so also applying here to TD
// See Change 737595 by Steve_Tall@STEVET3-VICTORY-H on 2020/07/10 13:40:02
if (GameToPlay == 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 adjustements. If the
** power rating drops below zero, then make it zero.
*/
if (GameToPlay == GAME_NORMAL) {
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.Expired()) {
/*
** Adjusted to reduce maximum number of teams created.
*/
int maxteams = Random_Pick(2, (int)(((BuildLevel-1)/3)+1));
for (int index = 0; index < maxteams; index++) {
TeamTypeClass const * ttype = Suggested_New_Team(true);
if (ttype) {
ScenarioInit++;
ttype->Create_One_Of();
ScenarioInit--;
}
}
if (GameToPlay == GAME_NORMAL && PlayerPtr->Difficulty == DIFF_HARD) {
AlertTime = (TICKS_PER_MINUTE * Random_Pick(4, 10));
} else {
if (GameToPlay == GAME_NORMAL && PlayerPtr->Difficulty == DIFF_EASY) {
AlertTime = (TICKS_PER_MINUTE * Random_Pick(16, 40));
} else {
AlertTime = (TICKS_PER_MINUTE * Random_Pick(5, 20));
}
}
}
/*
** Create teams for this house if necessary.
** (Use the same timer for some extra capture-the-flag logic.)
*/
if (TeamTime.Expired()) {
TeamTypeClass const * ttype = Suggested_New_Team(false);
if (ttype) {
ttype->Create_One_Of();
}
/*
** Also use this timer to detect if someone is sitting on my flag cell.
*/
if (Special.IsCaptureTheFlag && GameToPlay != GAME_NORMAL) {
TechnoClass *techno;
int moving;
/*
** 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) {
techno = Map[FlagHome].Cell_Techno();
if (techno) {
moving = false;
if (techno->What_Am_I() == RTTI_INFANTRY ||
techno->What_Am_I() == RTTI_UNIT) {
if (Target_Legal(((FootClass *)techno)->NavCom)) {
moving = true;
}
}
if (!moving) {
techno->Scatter(0,true,true);
}
}
}
}
/*
** Randomly create a Visceroid or other disastrous multiplayer object.
** Create the object, and use Scan_Place_Object to place the object near
** the center of the map.
*/
if (GameToPlay != GAME_NORMAL && Class->House==HOUSE_JP) {
int rlimit;
if (Special.IsJurassic && AreThingiesEnabled) {
rlimit = 450;
} else {
rlimit = 1000;
}
//if (IRandom(0, rlimit) == 0) {
if (IRandom(0, rlimit) <= 5) { // More visceroids! ST - 3/3/2020 4:34PM
UnitClass *obj = NULL;
CELL cell;
if (Special.IsJurassic && AreThingiesEnabled) {
obj = new UnitClass(Random_Pick(UNIT_TRIC, UNIT_STEG), HOUSE_JP);
} else if (Special.IsVisceroids) {
if (BuildLevel >= 7) {
if (!(UScan & UNITF_VICE)) {
obj = new UnitClass(UNIT_VICE, HOUSE_JP);
}
}
}
if (obj) {
cell = XY_Cell (Map.MapCellX + Random_Pick(0, Map.MapCellWidth - 1),
Map.MapCellY + Random_Pick(0, Map.MapCellHeight - 1));
if (!Scan_Place_Object(obj, cell)) {
delete obj;
}
}
}
}
TeamTime.Set(TEAM_DELAY);
}
/*
** If there is insufficient power, then all buildings that are above
** half strength take a little bit of damage.
*/
if (DamageTime.Expired()) {
/*
** No free harvesters for computer or player. - 8/16/95
*/
#ifdef OBSOLETE
/*
** Replace the last harvester if there is a refinery present.
*/
if (GameToPlay == GAME_NORMAL &&
Frame > 5 &&
(!IsHuman && BuildLevel <= 6) &&
(ActiveBScan & STRUCTF_REFINERY) != 0 &&
(UScan & UNITF_HARVESTER) == 0 &&
!IsFreeHarvester) {
IsFreeHarvester = true;
FreeHarvester = TICKS_PER_MINUTE * 2;
}
#endif
/*
** If a free harvester is to be created and the time is right, then create
** the harvester and clear the free harvester pending flag.
*/
if (IsFreeHarvester && FreeHarvester.Expired()) {
IsFreeHarvester = false;
Create_Special_Reinforcement(this, (TechnoTypeClass *)&UnitTypeClass::As_Reference(UNIT_HARVESTER), NULL);
}
/*
** When the power is below required, then the buildings will take damage over
** time.
*/
if (Power_Fraction() < 0x100) {
for (int index = 0; index < Buildings.Count(); index++) {
BuildingClass & b = *Buildings.Ptr(index);
if (b.House == this && b.Health_Ratio() > 0x080) {
if (b.Class->Drain) {
int damage = 1;
b.Take_Damage(damage, 0, WARHEAD_AP, 0);
}
}
}
}
DamageTime.Set(DAMAGE_DELAY);
}
/*
** If there are no more buildings to sell, then automatically cancel the
** sell mode.
*/
if (PlayerPtr == this && !BScan && Map.IsSellMode) {
Map.Sell_Mode_Control(0);
}
/*
** Various base conditions may be announced to the player.
*/
if (PlayerPtr == this) {
if (SpeakMoneyDelay.Expired() && Available_Money() < 100 && UnitFactories+BuildingFactories+InfantryFactories > 0) {
// MBL 03.23.2020 - Change "Need more funds" to "Insufficient funds" per https://jaas.ea.com/browse/TDRA-5370
// Speak(VOX_NEED_MO_MONEY);
Speak(VOX_NO_CASH);
//
// !!! MBL 03.18.2020:
// !!! Changing "Insufficient Funds" speak delay for this case to 1 minute instead of 2.
// !!! Note that all other speak delays are 2 minutes in TD (SPEAK_DELAY) and RA (Rule.SpeakDelay)
// !!! This is per https://jaas.ea.com/browse/TDRA-4659 (Ted and Jim)
// !!! I Checked with Joe mostly okay with this change, but want to note that we are changing original behavior
// !!! All other speak delays in TD and RA (max capacity and low power) remain at 2 minutes.
// !!! Also, in Red Alert, this is still 2 minutes from Rules.ini (SpeakDelay variable)
//
// SpeakMoneyDelay.Set(Options.Normalize_Delay(SPEAK_DELAY)); // 2 minutes
//
// MBL 05.18.2020: Setting back to 2 minutes as requested per https://jaas.ea.com/browse/TDRA-5834
//
// SpeakMoneyDelay.Set(Options.Normalize_Delay(SPEAK_DELAY / 2)); // 1 minute (new)
SpeakMoneyDelay.Set(Options.Normalize_Delay(SPEAK_DELAY)); // 2 minutes (original)
int text_id = TXT_INSUFFICIENT_FUNDS;
char const * text = Text_String(TXT_INSUFFICIENT_FUNDS);
if (text != NULL) {
On_Message(text, 35.0f, text_id);
}
}
if (SpeakMaxedDelay.Expired() && IsMaxedOut) {
IsMaxedOut = false;
if ((Capacity - Tiberium) < 300 && Capacity > 500 && (BScan & (STRUCTF_REFINERY | STRUCTF_CONST))) {
Speak(VOX_NEED_MO_CAPACITY);
SpeakMaxedDelay.Set(Options.Normalize_Delay(SPEAK_DELAY));
}
}
if (SpeakPowerDelay.Expired() && Power_Fraction() < 0x0100) {
if (BScan & STRUCTF_CONST) {
Speak(VOX_LOW_POWER);
SpeakPowerDelay.Set(Options.Normalize_Delay(SPEAK_DELAY));
int text_id = TXT_LOW_POWER;
char const * text = Text_String(TXT_LOW_POWER);
if (text != NULL) {
On_Message(text, 35.0f, text_id);
}
}
}
}
/*
** 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.Expired()) {
is_time = true;
TriggerTime = TICKS_PER_MINUTE/10;
}
/*
** Check to see if the ion cannon should be removed from the sidebar
** because of outside circumstances. The advanced communications facility
** being destroyed is a good example of this.
*/
if (IonCannon.Is_Present()) {
if (!(ActiveBScan & STRUCTF_EYE) && !IonCannon.Is_One_Time()) {
/*
** Remove the ion cannon when there is no advanced communication facility.
** Note that this will not remove the one time created ion cannon.
*/
if (IonCannon.Remove()) {
if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw();
IsRecalcNeeded = true;
}
} else {
/*
** Turn the ion cannon suspension on or off depending on the available
** power. Note that one time ion cannon will not be affected by this.
*/
IonCannon.Suspend(Power_Fraction() < 0x0100);
/*
** Process the ion cannon AI and if something changed that would affect
** the sidebar, then flag the sidebar to be redrawn.
*/
if (IonCannon.AI(this == PlayerPtr)) {
if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw();
}
}
/*
** The computer may decide to fire the ion cannon if it is ready.
*/
if (IonCannon.Is_Ready() && !IsHuman) {
Special_Weapon_AI(SPC_ION_CANNON);
}
} else {
/*
** If there is no ion cannon present, but there is an advanced communcation
** center available, then make the ion cannon available as well.
*/
if ((GameToPlay == GAME_NORMAL || Rule.AllowSuperWeapons) &&
(ActiveBScan & STRUCTF_EYE) &&
(ActLike == HOUSE_GOOD || GameToPlay != GAME_NORMAL) &&
(IsHuman || GameToPlay != GAME_NORMAL)) {
IonCannon.Enable(false, this == PlayerPtr, Power_Fraction() < 0x0100);
/*
** Flag the sidebar to be redrawn if necessary.
*/
// Add to Glyphx multiplayer sidebar. ST - 3/22/2019 1:50PM
if (GameToPlay == GAME_GLYPHX_MULTIPLAYER) {
if (IsHuman) {
Sidebar_Glyphx_Add(RTTI_SPECIAL, SPC_ION_CANNON, this);
}
} else {
if (this == PlayerPtr) {
Map.Add(RTTI_SPECIAL, SPC_ION_CANNON);
Map.Column[1].Flag_To_Redraw();
}
}
}
}
/*
** Check to see if the nuke strike should be removed from the sidebar
** because of outside circumstances. The Temple of Nod
** being destroyed is a good example of this.
*/
if (NukeStrike.Is_Present()) {
if (!(ActiveBScan & STRUCTF_TEMPLE) && (!NukeStrike.Is_One_Time() || GameToPlay == GAME_NORMAL)) {
/*
** Remove the nuke strike when there is no Temple of Nod.
** Note that this will not remove the one time created nuke strike.
*/
if (NukeStrike.Remove(true)) {
IsRecalcNeeded = true;
if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw();
}
} else {
/*
** Turn the nuke strike suspension on or off depending on the available
** power. Note that one time nuke strike will not be affected by this.
*/
NukeStrike.Suspend(Power_Fraction() < 0x0100);
/*
** Process the nuke strike AI and if something changed that would affect
** the sidebar, then flag the sidebar to be redrawn.
*/
if (NukeStrike.AI(this == PlayerPtr)) {
if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw();
}
}
/*
** The computer may decide to fire the nuclear missile if it is ready.
*/
if (NukeStrike.Is_Ready() && !IsHuman) {
Special_Weapon_AI(SPC_NUCLEAR_BOMB);
}
} else {
/*
** If there is no nuke strike present, but there is a Temple of Nod
** available, then make the nuke strike strike available.
*/
if ((GameToPlay == GAME_NORMAL || Rule.AllowSuperWeapons) && (ActiveBScan & STRUCTF_TEMPLE) && Has_Nuke_Device() && IsHuman) {
NukeStrike.Enable((GameToPlay == GAME_NORMAL), this == PlayerPtr);
/*
** Flag the sidebar to be redrawn if necessary.
*/
// Add to Glyphx multiplayer sidebar. ST - 3/22/2019 1:50PM
if (GameToPlay == 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();
}
}
}
}
/*
** Process the airstrike AI and if something changed that would affect
** the sidebar, then flag the sidebar to be redrawn.
*/
if (AirStrike.Is_Present()) {
if (AirStrike.AI(this == PlayerPtr)) {
if (this == PlayerPtr) Map.Column[1].Flag_To_Redraw();
}
/*
** The computer may decide to call in the airstrike if it is ready.
*/
if (AirStrike.Is_Ready() && !IsHuman) {
Special_Weapon_AI(SPC_AIR_STRIKE);
}
}
/*
** Add the airstrike option if it is pending.
*/
if (IsAirstrikePending) {
IsAirstrikePending = false;
if (AirStrike.Enable(false, this == PlayerPtr)) {
AirStrike.Forced_Charge(this == PlayerPtr);
// Add to Glyphx multiplayer sidebar. ST - 3/22/2019 1:50PM
if (GameToPlay == GAME_GLYPHX_MULTIPLAYER) {
if (IsHuman) {
Sidebar_Glyphx_Add(RTTI_SPECIAL, SPC_AIR_STRIKE, this);
}
} else {
if (this == PlayerPtr) {
Map.Add(RTTI_SPECIAL, SPC_AIR_STRIKE);
Map.Column[1].Flag_To_Redraw();
}
}
}
}
#ifdef NEVER
/*
** The following logic deals with the nuclear warhead state machine. It
** handles all the different stages of the temple firing and the rocket
** travelling up and down. The rocket explosion is handled by the anim
** which is attached to the bullet.
*/
if (!IsHuman && NukePresent) {
Special_Weapon_AI(SPC_NUCLEAR_BOMB);
}
#endif
if (GameToPlay != GAME_NORMAL && Class->House != HOUSE_JP) {
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 (GameToPlay != GAME_NORMAL && !IsDefeated &&
!ActiveBScan && !ActiveAScan && !UScan && !ActiveIScan && Frame > 0) {
MPlayer_Defeated();
}
for (int index = 0; index < HouseTriggers[Class->House].Count(); index++) {
TriggerClass * t = HouseTriggers[Class->House][index];
/*
** Check for just built the building trigger event.
*/
if (JustBuilt != STRUCT_NONE) {
if (t->Spring(EVENT_BUILD, Class->House, JustBuilt)) {
JustBuilt = STRUCT_NONE;
continue;
}
}
/*
** Check for civilian evacuation trigger event.
*/
if (IsCivEvacuated && t->Spring(EVENT_EVAC_CIVILIAN, Class->House)) {
continue;
}
/*
** Number of buildings destroyed checker.
*/
if (t->Spring(EVENT_NBUILDINGS_DESTROYED, Class->House, BuildingsLost)) {
continue;
}
/*
** Number of units destroyed checker.
*/
if (t->Spring(EVENT_NUNITS_DESTROYED, Class->House, UnitsLost)) {
continue;
}
/*
** House has been revealed trigger event.
*/
if (IsDiscovered && t->Spring(EVENT_HOUSE_DISCOVERED, Class->House)) {
IsDiscovered = false;
continue;
}
/*
** The "all destroyed" triggers are only processed after a certain
** amount of safety time has expired.
*/
if (!EndCountDown) {
/*
** All buildings destroyed checker.
*/
if (!ActiveBScan) {
if (t->Spring(EVENT_BUILDINGS_DESTROYED, Class->House)) {
continue;
}
}
/*
** All units destroyed checker.
*/
if (!((ActiveUScan & ~(UNITF_GUNBOAT)) | IScan | (ActiveAScan & ~(AIRCRAFTF_TRANSPORT|AIRCRAFTF_CARGO|AIRCRAFTF_A10)))) {
if (t->Spring(EVENT_UNITS_DESTROYED, Class->House)) {
continue;
}
}
/*
** All buildings AND units destroyed checker.
*/
if (!(ActiveBScan | (ActiveUScan & ~(UNITF_GUNBOAT)) | IScan | (ActiveAScan & ~(AIRCRAFTF_TRANSPORT|AIRCRAFTF_CARGO|AIRCRAFTF_A10)))) {
if (t->Spring(EVENT_ALL_DESTROYED, Class->House)) {
continue;
}
}
}
/*
** Credit check.
*/
if (t->Spring(EVENT_CREDITS, Class->House, Credits)) {
continue;
}
/*
** Timeout check.
*/
if (is_time && t->Spring(EVENT_TIME, Class->House)) {
continue;
}
/*
** All factories destroyed check.
*/
if (!(BScan & (STRUCTF_AIRSTRIP|STRUCTF_HAND|STRUCTF_WEAP|STRUCTF_BARRACKS)) && t->Spring(EVENT_NOFACTORIES, Class->House)) {
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) {
if (Map.Is_Radar_Active()) {
if (BScan & (STRUCTF_RADAR|STRUCTF_EYE)) {
if (Power_Fraction() < 0x0100) {
Map.Radar_Activate(0);
}
} else {
Map.Radar_Activate(0);
}
} else {
if (BScan & (STRUCTF_RADAR|STRUCTF_EYE)) {
if (Power_Fraction() >= 0x0100) {
Map.Radar_Activate(1);
}
} else {
if (Map.Is_Radar_Existing()) {
Map.Radar_Activate(4);
}
}
}
if (!(BScan & (STRUCTF_RADAR|STRUCTF_EYE))) {
Radar = RADAR_NONE;
} else {
Radar = (Map.Is_Radar_Active() || Map.Is_Radar_Activating()) ? RADAR_ON : RADAR_OFF;
}
}
VisibleCredits.AI(false, this, true);
/*
** Copied from Red Alert for multiplayer AI. ST - 7/23/2019 3:02PM
**
**
**
*/
#ifdef USE_RA_AI
if (GameToPlay == GAME_GLYPHX_MULTIPLAYER && IsHuman == false) {
/*
** 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();
}
#endif
/*
** 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();
#ifdef NEVER
/*
** Remove the ion cannon if necessary.
*/
if (IonCannon.Is_Present() && !(BScan & STRUCTF_EYE)) {
IonCannon.Remove();
}
/*
** Remove the nuclear bomb if necessary.
*/
if (NukeStrike.Is_Present() && !(BScan & STRUCTF_TEMPLE)) {
NukeStrike.Remove();
}
#endif
/*
** 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->Owner() == Class->House) {
building->Update_Specials();
if (PlayerPtr == building->House) {
building->Update_Buildables();
}
}
}
Recalculate_Placement_Distances();
}
}
/***********************************************************************************************
* 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)
{
Validate();
if (SpeakAttackDelay.Expired() && PlayerPtr->Class->House == Class->House) {
if (GameToPlay == 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.Set(Options.Normalize_Delay(SPEAK_DELAY)); // 2 minutes
// SpeakAttackDelay.Set(Options.Normalize_Delay(TICKS_PER_MINUTE/2)); // 30 seconds as requested
SpeakAttackDelay.Set(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(EVENT_ATTACKED, Class->House);
}
}
}
/***********************************************************************************************
* 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)
{
Validate();
long oldtib = Tiberium;
Tiberium += tiberium;
if (Tiberium > Capacity) {
Tiberium = Capacity;
IsMaxedOut = true;
}
HarvestedCredits += tiberium;
Silo_Redraw_Check(oldtib, Capacity);
}
/***********************************************************************************************
* 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
{
Validate();
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)
{
Validate();
long oldtib = Tiberium;
if ((int)money > 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)
{
Validate();
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)
{
Validate();
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)
{
Validate();
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::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(char *buffer)
{
HouseClass *p; // Pointer to current player data.
char const *hname; // Pointer to house name.
char buf[128];
for (HousesType index = HOUSE_FIRST; index < HOUSE_COUNT; index++) {
hname = HouseTypeClass::As_Reference(index).IniName;
int maxunit = WWGetPrivateProfileInt(hname, "MaxUnit", EACH_UNIT_MAX, buffer);
maxunit = MAX(maxunit, 150);
int maxbuilding = WWGetPrivateProfileInt(hname, "MaxBuilding", EACH_BUILDING_MAX, buffer);
maxbuilding = MAX(maxbuilding, 150);
int credits = WWGetPrivateProfileInt(hname, "Credits", 0, buffer);
p = new HouseClass(index);
p->MaxBuilding = maxbuilding;
p->MaxUnit = maxunit;
p->Credits = (long)credits * 100;
p->InitialCredits = p->Credits;
WWGetPrivateProfileString(hname, "Edge", "", buf, sizeof(buf)-1, buffer);
p->Edge = Source_From_Name(buf);
if (p->Edge == SOURCE_NONE) {
p->Edge = SOURCE_NORTH;
}
if (GameToPlay == GAME_NORMAL) {
WWGetPrivateProfileString(hname, "Allies", "", buf, sizeof(buf)-1, buffer);
if (strlen(buf)) {
char * tok = strtok(buf, ", \t");
while (tok) {
HousesType h = HouseTypeClass::From_Name(tok);
p->Make_Ally(h);
tok = strtok(NULL, ", \t");
}
} else {
/*
** Special case for when no allies are specified in the INI file.
** The GDI side defaults to considering the neutral side to be
** friendly.
*/
if (p->Class->House == HOUSE_GOOD) {
p->Make_Ally(HOUSE_NEUTRAL);
}
}
}
}
}
/***********************************************************************************************
* HouseClass::Write_INI -- Writes house specific data into INI file. *
* *
* Use this routine to write the house specific data (for all houses) into the INI file. *
* It is used by the scenario editor when saving the scenario. *
* *
* INPUT: buffer -- INI file staging buffer. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/28/1994 JLB : Created. *
*=============================================================================================*/
void HouseClass::Write_INI(char *buffer)
{
for (HousesType i = HOUSE_FIRST; i < HOUSE_COUNT; i++) {
HouseClass * p = As_Pointer(i);
if (p) {
WWWritePrivateProfileInt(p->Class->IniName, "Credits", (int)(p->Credits / 100), buffer);
WWWritePrivateProfileString(p->Class->IniName, "Edge", Name_From_Source(p->Edge), buffer);
WWWritePrivateProfileInt(p->Class->IniName, "MaxUnit", p->MaxUnit, buffer);
WWWritePrivateProfileInt(p->Class->IniName, "MaxBuilding", p->MaxBuilding, buffer);
bool first = true;
char sbuffer[100] = "";
for (HousesType house = HOUSE_FIRST; house < HOUSE_COUNT; house++) {
if (p->Is_Ally(house)) {
if (!first) strcat(sbuffer, ",");
strcat(sbuffer, As_Pointer(house)->Class->IniName);
first = false;
}
}
WWWritePrivateProfileString(p->Class->IniName, "Allies", sbuffer, buffer);
}
}
}
/***********************************************************************************************
* 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
{
Validate();
if (house != HOUSE_NONE) {
return(((1<<house) & Allies) != 0);
}
return(false);
}
/***********************************************************************************************
* HouseClass::Is_Ally -- Determines if the specified house is an ally. *
* *
* This routine will examine the specified house and determine if it is an ally. *
* *
* INPUT: house -- Pointer to the house object to check for ally relationship. *
* *
* OUTPUT: Is the specified house an ally? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/08/1995 JLB : Created. *
*=============================================================================================*/
bool HouseClass::Is_Ally(HouseClass const * house) const
{
Validate();
if (house) {
return(Is_Ally(house->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
{
Validate();
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. *
*=============================================================================================*/
void HouseClass::Make_Ally(HousesType house)
{
Validate();
if (house != HOUSE_NONE && !Is_Ally(house)) {
/*
** If in normal game play but the house is defeated, then don't allow the ally
** key to work.
*/
if (!ScenarioInit && (IsDefeated || house == HOUSE_JP)) return;
Allies |= (1 << house);
#ifdef CHEAT_KEYS
if (Debug_Flag) {
HouseClass * enemy = HouseClass::As_Pointer(house);
if (enemy && !enemy->Is_Ally(this)) {
enemy->Make_Ally(Class->House);
}
}
#endif
if ((Debug_Flag || GameToPlay != GAME_NORMAL) && !ScenarioInit) {
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 && !object->IsInLimbo && object->Owner() == Class->House) {
TARGET target = ((TechnoClass *)object)->TarCom;
if (Target_Legal(target) && As_Techno(target)) {
if (Is_Ally(As_Techno(target))) {
((TechnoClass *)object)->TarCom = TARGET_NONE;
}
}
}
}
sprintf(buffer, Text_String(TXT_HAS_ALLIED), Name, HouseClass::As_Pointer(house)->Name);
Messages.Add_Message(buffer, MPlayerTColors[RemapColor], TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_FULLSHADOW, 1200, 0, 0);
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 aaction. *
*=============================================================================================*/
void HouseClass::Make_Enemy(HousesType house)
{
Validate();
if (house != HOUSE_NONE && Is_Ally(house)) {
HouseClass * enemy = HouseClass::As_Pointer(house);
Allies &= ~(1 << house);
if (enemy && enemy->Is_Ally(this)) {
enemy->Allies &= ~(1 << Class->House);
}
if ((Debug_Flag || GameToPlay != GAME_NORMAL) && !ScenarioInit) {
char buffer[80];
sprintf(buffer, Text_String(TXT_AT_WAR), Name, enemy->Name);
Messages.Add_Message(buffer, MPlayerTColors[RemapColor], TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_FULLSHADOW, 600, 0, 0);
Map.Flag_To_Redraw(false);
}
}
}
/***********************************************************************************************
* 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)? *
* *
* unit -- Is the object a vehicle or infantry? *
* *
* OUTPUT: Returns with a pointer to the remap table to use when drawing this object. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/08/1995 JLB : Created. *
*=============================================================================================*/
unsigned char const * HouseClass::Remap_Table(bool blushing, bool unit) const
{
Validate();
if (blushing) return(&Map.FadingLight[0]);
/*
** For normal game play, return the TypeClass's remap table for this
** house type
*/
if (GameToPlay == GAME_NORMAL) {
/*
** Special case exception for Nod and single player only. Remap
** buildings to red as opposed to the default color of bluegrey.
*/
if (!unit && Class->House == HOUSE_BAD) {
return(RemapRed);
}
/*
** Special case Jurassic missions to use the bluegrey remapping
*/
if (Special.IsJurassic && Class->House == HOUSE_MULTI4) {
return(RemapLtBlue);
}
return(Class->RemapTable);
} else {
/*
** For multiplayer games, return the remap table for this exact house instance.
*/
return(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: none *
* *
* 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)
{
Validate();
return(TeamTypeClass::Suggested_New_Team(this, UScan, IScan, IsAlerted && 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)
{
Validate();
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. *
*=============================================================================================*/
ProdFailType HouseClass::Begin_Production(RTTIType type, int id)
{
Validate();
int * factory = 0;
int result = true;
bool initial_start = false;
FactoryClass * fptr;
TechnoTypeClass const * tech = Fetch_Techno_Type(type, id);
switch (type) {
case RTTI_AIRCRAFT:
case RTTI_AIRCRAFTTYPE:
factory = &AircraftFactory;
break;
case RTTI_UNIT:
case RTTI_UNITTYPE:
factory = &UnitFactory;
break;
case RTTI_BUILDING:
case RTTI_BUILDINGTYPE:
factory = &BuildingFactory;
break;
case RTTI_INFANTRY:
case RTTI_INFANTRYTYPE:
factory = &InfantryFactory;
break;
case RTTI_SPECIAL:
factory = &SpecialFactory;
break;
}
/*
** Check for legality of the production object type suggested.
*/
if (!factory) return(PROD_ILLEGAL);
/*
** If the house is already busy producing the requested object, then
** return with this failure code, unless we are restarting production.
*/
if (*factory != -1) {
fptr = Factories.Raw_Ptr(*factory);
if (fptr->Is_Building())
return(PROD_CANT);
} else {
fptr = new FactoryClass();
if (!fptr) return(PROD_CANT);
*factory = Factories.ID(fptr);
result = (tech) ? fptr->Set(*tech, *this) : fptr->Set(id, *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) {
delete fptr;
fptr = NULL;
*factory = -1;
}
}
if (result) {
fptr->Start();
/*
** Link this factory to the sidebar so that proper graphic feedback
** can take place.
*/
// Handle Glyphx multiplayer sidebar. ST - 3/26/2019 1:27PM
if (GameToPlay == GAME_GLYPHX_MULTIPLAYER) {
if (IsHuman) {
Sidebar_Glyphx_Factory_Link(*factory, type, id, this);
}
} else {
if (PlayerPtr == this) {
Map.Factory_Link(*factory, type, id);
}
}
return(PROD_OK);
}
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)
{
Validate();
int * factory = 0;
switch (type) {
case RTTI_AIRCRAFT:
case RTTI_AIRCRAFTTYPE:
factory = &AircraftFactory;
break;
case RTTI_UNIT:
case RTTI_UNITTYPE:
factory = &UnitFactory;
break;
case RTTI_BUILDING:
case RTTI_BUILDINGTYPE:
factory = &BuildingFactory;
break;
case RTTI_INFANTRY:
case RTTI_INFANTRYTYPE:
factory = &InfantryFactory;
break;
case RTTI_SPECIAL:
factory = &SpecialFactory;
break;
}
/*
** Check for legality of the production object type suggested.
*/
if (!factory) return(PROD_ILLEGAL);
/*
** If the house is already busy producing the requested object, then
** return with this failure code.
*/
if (*factory == -1) return(PROD_CANT);
/*
** Create the factory pointer object.
** If the factory could not be created, then report this error condition.
*/
FactoryClass * fptr = Factories.Raw_Ptr(*factory);
if (!fptr) 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;
Map.SidebarClass::Column[0].IsToRedraw = true;
Map.SidebarClass::Column[1].IsToRedraw = true;
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)
{
Validate();
int * factory = 0;
switch (type) {
case RTTI_AIRCRAFT:
case RTTI_AIRCRAFTTYPE:
factory = &AircraftFactory;
break;
case RTTI_UNIT:
case RTTI_UNITTYPE:
factory = &UnitFactory;
break;
case RTTI_BUILDING:
case RTTI_BUILDINGTYPE:
factory = &BuildingFactory;
break;
case RTTI_INFANTRY:
case RTTI_INFANTRYTYPE:
factory = &InfantryFactory;
break;
case RTTI_SPECIAL:
factory = &SpecialFactory;
break;
}
/*
** Check for legality of the production object type suggested.
*/
if (!factory) return(PROD_ILLEGAL);
/*
** If there is no factory to abandon, then return with a failure code.
*/
if (*factory == -1) return(PROD_CANT);
/*
** Fetch the factory pointer object.
*/
FactoryClass * fptr = Factories.Raw_Ptr(*factory);
if (!fptr) return(PROD_CANT);
/*
** Tell the sidebar that it needs to be redrawn because of this.
*/
// Handle Glyphx multiplayer sidebar. ST - 3/22/2019 2:01PM
if (GameToPlay == GAME_GLYPHX_MULTIPLAYER) {
if (IsHuman) {
Sidebar_Glyphx_Abandon_Production(type, *factory, 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, *factory);
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();
delete fptr;
*factory = -1;
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)
{
Validate();
/*
** 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 && !b->IsInLimbo && b->Strength && !Is_Ally(b)) {
if (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. *
*=============================================================================================*/
bool HouseClass::Place_Special_Blast(SpecialWeaponType id, CELL cell)
{
Validate();
BuildingClass * launchsite = 0;
AnimClass * anim = 0;
// Added. ST - 12/2/2019 11:26AM
bool fired = false;
const char *what = NULL;
int index;
switch (id) {
case SPC_ION_CANNON:
if (IonCannon.Is_Ready()) {
anim = new AnimClass(ANIM_ION_CANNON, Cell_Coord(cell));
if (anim) anim->Set_Owner(Class->House);
if (this == PlayerPtr) {
Map.IsTargettingMode = false;
}
IonCannon.Discharged(PlayerPtr == this);
IsRecalcNeeded = true;
fired = true;
what = "ION";
}
break;
case SPC_NUCLEAR_BOMB:
if (NukeStrike.Is_Ready()) {
#ifdef NEVER
/*
** Scatter the nuclear bomb impact point into an adjacent cell.
*/
for (;;) {
CELL newcell = Adjacent_Cell(cell, Random_Pick(FACING_N, FACING_COUNT));
if (Map.In_Radar(newcell)) {
cell = newcell;
break;
}
}
#endif
/*
** Search for a suitable launch site for this missile.
*/
for (index = 0; index < Buildings.Count(); index++) {
BuildingClass * b = Buildings.Ptr(index);
if (b && !b->IsInLimbo && b->House == this && *b == STRUCT_TEMPLE) {
launchsite = b;
break;
}
}
/*
** If a launch site was found, then proceed with the normal launch
** sequence.
*/
if (launchsite) {
launchsite->Assign_Mission(MISSION_MISSILE);
launchsite->Commence();
NukeDest = cell;
NukePieces = 0;
} else {
/*
** Only in the multiplayer version can the nuclear bomb be
** sent from some off screen source.
*/
if (GameToPlay == GAME_NORMAL) return(false);
/*
** Since no launch site was found, just bring the missile in
** directly from the North map edge.
*/
BulletClass *bullet = new BulletClass(BULLET_NUKE_DOWN);
if (bullet) {
COORDINATE start = Cell_Coord(XY_Cell(Cell_X(cell), 0));
bullet->Assign_Target(::As_Target(cell));
bullet->Payback = NULL;
bullet->Strength = 1;
if (!bullet->Unlimbo(start, DIR_S)) {
delete bullet;
} else {
bullet->PrimaryFacing.Set_Current(DIR_S);
}
Speak(VOX_INCOMING_NUKE); // "Nuclear Warhead Approaching" - "NUKE1"
Sound_Effect(VOC_NUKE_FIRE, start);
}
}
if (this == PlayerPtr) {
Map.IsTargettingMode = false;
}
NukeStrike.Discharged(this == PlayerPtr);
IsRecalcNeeded = true;
fired = true;
what = "NUKE";
}
break;
case SPC_AIR_STRIKE:
if (AirStrike.Is_Ready()) {
int strike = 1;
if (GameToPlay == GAME_NORMAL) {
strike = Bound(BuildLevel/3, 1, 3);
} else {
strike = Bound(MPlayerUnitCount/5, 1, 3);
}
Create_Air_Reinforcement(this, AIRCRAFT_A10, strike, MISSION_HUNT, ::As_Target(cell), TARGET_NONE);
if (this == PlayerPtr) {
Map.IsTargettingMode = false;
}
AirStrike.Discharged(this == PlayerPtr);
IsRecalcNeeded = true;
fired = true;
what = "AIR";
}
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)
{
Validate();
TechnoClass * tech = 0;
FactoryClass * factory = 0;
switch (type) {
case RTTI_AIRCRAFT:
case RTTI_AIRCRAFTTYPE:
if (AircraftFactory != -1) {
factory = Factories.Raw_Ptr(AircraftFactory);
}
break;
case RTTI_INFANTRY:
case RTTI_INFANTRYTYPE:
if (InfantryFactory != -1) {
factory = Factories.Raw_Ptr(InfantryFactory);
}
break;
case RTTI_UNIT:
case RTTI_UNITTYPE:
if (UnitFactory != -1) {
factory = Factories.Raw_Ptr(UnitFactory);
}
break;
case RTTI_BUILDING:
case RTTI_BUILDINGTYPE:
if (BuildingFactory != -1) {
factory = Factories.Raw_Ptr(BuildingFactory);
}
break;
}
/*
** 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) {
TechnoClass * builder = pending->Who_Can_Build_Me(false, false);
TechnoTypeClass const *object_type = pending->Techno_Type_Class();
if (builder && 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());
}
}
} 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) {
/*
** Ensures that the proximity check is performed even when the building is
** placed by way of a remote event.
*/
if (tech->What_Am_I() != RTTI_BUILDING || ((BuildingClass *)tech)->Passes_Proximity_Check(cell)) {
builder->Transmit_Message(RADIO_HELLO, tech);
if (tech->Unlimbo(Cell_Coord(cell))) {
factory->Completed();
Abandon_Production(type);
if (PlayerPtr == this) {
Sound_Effect(VOC_SLAM);
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)
{
Validate();
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);
}
#ifdef OBSOLETE
/***********************************************************************************************
* HouseClass::Init_Ion_Cannon -- Initialize the ion cannon countdown. *
* *
* This routine will initiate the ion cannon charging countdown. It will add the ion *
* cannon to the sidebar if it isn't there and it is specified to be added. *
* *
* INPUT: first_time -- Set to true if the ion cannon must be added to the sidebar. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/18/1995 JLB : commented *
*=============================================================================================*/
void HouseClass::Init_Ion_Cannon(SpecialControlType control)
{
Validate();
switch (control) {
case CONTROL_RESET:
if (IonCannonPresent) {
IonOldStage = -1;
IonControl.Set(ION_CANNON_GONE_TIME);
if (PlayerPtr == this) {
Map.Add(RTTI_SPECIAL, SPC_ION_CANNON);
if (!ScenarioInit) {
Speak(VOX_ION_CHARGING);
}
}
}
break;
/*
** Adds the special no-prerequisite ion cannon option.
*/
case CONTROL_ONE_TIME:
if (!IonCannonPresent) {
Init_Ion_Cannon(CONTROL_ADD);
IonOneTimeFlag = true;
}
break;
/*
** Adds the normal legitimate ion cannon option. If there was
** already a one-time ion cannon available, the charging state
** is NOT interrupted.
*/
case CONTROL_ADD:
IonOneTimeFlag = false;
if (!IconCannonPresent) {
IonCannonPresent = true;
IonReady = false;
Init_Ion_Cannon(CONTROL_RESET);
}
break;
case CONTROL_REMOVE:
break;
}
if (!(first_time && IonCannonPresent)) {
if (IonCannonPresent && IonOneTimeFlag) {
IonOneTimeFlag = false;
if (this == PlayerPtr) Map.Recalc();
return;
}
if (first_time && this == PlayerPtr) {
Map.Add(RTTI_SPECIAL, SPC_ION_CANNON);
}
if (!ScenarioInit) {
if (this == PlayerPtr) {
Speak(VOX_ION_CHARGING);
}
}
IonControl.Set(ION_CANNON_GONE_TIME);
IonCannonPresent = true;
IonReady = false;
IonOldStage = -1;
IonOneTimeFlag = one_time_effect;
} else {
if (first_time && IonCannonPresent && !one_time_effect && IonOneTimeFlag) {
IonOneTimeFlag = false;
}
}
}
#ifdef NEVER
void HouseClass::Init_Ion_Cannon(bool first_time, bool one_time_effect)
{
Validate();
if (!(first_time && IonCannonPresent)) {
if (IonCannonPresent && IonOneTimeFlag) {
IonOneTimeFlag = false;
if (this == PlayerPtr) Map.Recalc();
return;
}
if (first_time && this == PlayerPtr) {
Map.Add(RTTI_SPECIAL, SPC_ION_CANNON);
}
if (!ScenarioInit) {
if (this == PlayerPtr) {
Speak(VOX_ION_CHARGING);
}
}
IonControl.Set(ION_CANNON_GONE_TIME);
IonCannonPresent = true;
IonReady = false;
IonOldStage = -1;
IonOneTimeFlag = one_time_effect;
} else {
if (first_time && IonCannonPresent && !one_time_effect && IonOneTimeFlag) {
IonOneTimeFlag = false;
}
}
}
#endif
/***********************************************************************************************
* HouseClass::Remove_Ion_Cannon -- Disables the ion cannon. *
* *
* This routine will disable the ion cannon. It is called when the ion cannon cannot *
* establish a command link to the ground (usually when there is insufficient power). *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/18/1995 JLB : commented *
*=============================================================================================*/
void HouseClass::Remove_Ion_Cannon(void)
{
Validate();
if (IonCannonPresent) {
IonCannonPresent = false;
IonOneTimeFlag = false;
IonReady = false;
IonControl.Clear();
IonOldStage = -1;
}
}
#endif
/***************************************************************************
* HouseClass::Clobber_All -- removes house & all its objects *
* *
* INPUT: *
* none. *
* *
* OUTPUT: *
* none. *
* *
* WARNINGS: *
* none. *
* *
* HISTORY: *
* 05/16/1995 BRR : Created. *
* 06/09/1995 JLB : Handles aircraft. *
*=========================================================================*/
void HouseClass::Clobber_All(void)
{
Validate();
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 < 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)->House == Class->House) {
delete Triggers.Ptr(i);
i--;
}
}
delete this;
}
#ifdef NEVER
/***********************************************************************************************
* HouseClass::Init_Nuke_Bomb -- Adds (if necessary) the atom bomb to the sidebar. *
* *
* Use this routine whenever a piece of atom bomb has been discovered (also at scenario *
* start). It will add the nuclear bomb button to the sidebar if necessary. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/18/1995 JLB : commented *
*=============================================================================================*/
void HouseClass::Init_Nuke_Bomb(bool first_time, bool one_time_effect)
{
Validate();
if (!first_time || !NukePresent) {
if (NukePresent && NukeOneTimeFlag) {
NukeOneTimeFlag = false;
if (this == PlayerPtr) Map.Recalc();
return;
}
if (first_time && this == PlayerPtr) {
Map.Add(RTTI_SPECIAL, SPC_NUCLEAR_BOMB);
}
NukeControl.Set(NUKE_GONE_TIME);
NukePresent = true;
NukeReady = false;
NukeOldStage = -1;
NukeOneTimeFlag = one_time_effect;
} else {
if (!one_time_effect && NukeOneTimeFlag) {
NukeOneTimeFlag = false;
}
}
}
/***********************************************************************************************
* HouseClass::Remove_Nuke_Bomb -- Removes the nuclear bomb from the sidebar. *
* *
* This routine will remove the nuclear bomb from the sidebar. It should be called when *
* the nuclear strike has been launched. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/18/1995 JLB : commented *
* 07/25/1995 JLB : Handles recharge reset logic. *
*=============================================================================================*/
void HouseClass::Remove_Nuke_Bomb(void)
{
Validate();
if (NukePresent && !NukeOneTimeFlag) {
NukePresent = false;
NukeControl.Clear();
NukeOldStage = -1;
NukeReady = false;
}
}
/***********************************************************************************************
* HouseClass::Init_Air_Strike -- Add (or reset) the air strike sidebar button. *
* *
* This routine will activate (add if so indicated) the air strike button to the sidebar. *
* Call this routine when events indicate that a special air strike is available. *
* *
* INPUT: first_time -- If the air strike button is to be added, then this will be true. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/18/1995 JLB : commented *
*=============================================================================================*/
void HouseClass::Init_Air_Strike(bool first_time, bool one_time_effect)
{
Validate();
if (!(first_time && AirPresent)) {
if (AirPresent && AirOneTimeFlag) {
AirOneTimeFlag = false;
AirPresent = false;
Map.Recalc();
return;
}
if (first_time) {
if (PlayerPtr == this) {
Map.Add(RTTI_SPECIAL, SPC_AIR_STRIKE);
}
AirControl.Set(0);
} else {
AirControl.Set(AIR_CANNON_GONE_TIME);
}
AirReady = first_time;
AirPresent = true;
AirOldStage = -1;
AirOneTimeFlag = one_time_effect;
if (AirReady && !IsHuman) {
Special_Weapon_AI(SPC_AIR_STRIKE);
}
} else {
if (first_time && AirPresent && !one_time_effect && AirOneTimeFlag) {
AirOneTimeFlag = false;
}
}
}
/***********************************************************************************************
* HouseClass::Remove_Air_Strike -- Removes the air strike button from the sidebar. *
* *
* This routine will remove the air strike button from the sidebar. Call this routine when *
* the air strike has been launched. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/18/1995 JLB : commented *
*=============================================================================================*/
void HouseClass::Remove_Air_Strike(void)
{
Validate();
AirPresent = false;
AirReady = false;
AirControl.Clear();
AirOldStage = -1;
}
/***********************************************************************************************
* HouseClass::Make_Air_Strike_Available -- Make the airstrike available. *
* *
* This routine will make the airstrike available. Typically, this results in a new icon *
* added to the sidebar. *
* *
* INPUT: present -- The the airstrike being added? *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/20/1995 JLB : Created. *
*=============================================================================================*/
void HouseClass::Make_Air_Strike_Available(bool present, bool one_time_effect)
{
Validate();
Init_Air_Strike(true, one_time_effect);
AirPresent = present;
}
#endif
/***********************************************************************************************
* HouseClass::Add_Nuke_Piece -- Add a nuclear piece to the collection. *
* *
* This routine will a the specified nuclear piece to the house collection of parts. When *
* all the pieces have been added, a nuclear strike ability is made available. *
* *
* INPUT: piece -- The nuclear piece to add. If equal to "-1", then the next possible piece *
* is added. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/20/1995 JLB : Created. *
*=============================================================================================*/
void HouseClass::Add_Nuke_Piece(int piece)
{
Validate();
if (piece == -1) {
piece = 1;
if (!(NukePieces & 0x01)) {
piece = 1;
}
if (!(NukePieces & 0x02)) {
piece = 2;
}
if (!(NukePieces & 0x04)) {
piece = 3;
}
}
NukePieces |= 1 << (piece - 1);
// Init_Nuke_Bomb(false);
}
/***********************************************************************************************
* 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, bool )
{
Validate();
// if (LaunchSite == target) {
// LaunchSite = TARGET_NONE;
// }
}
/***********************************************************************************************
* 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
{
Validate();
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->BScan & 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. *
* *
* 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) const
{
Validate();
TechnoTypeClass const * techno = NULL;
#ifdef USE_RA_AI
//
// Copied from RA for AI. ST - 7/25/2019 3:58PM
//
if (!IsHuman && GameToPlay != GAME_NORMAL) {
switch (objecttype) {
case RTTI_AIRCRAFT:
case RTTI_AIRCRAFTTYPE:
if (BuildAircraft != AIRCRAFT_NONE) {
return(&AircraftTypeClass::As_Reference(BuildAircraft));
}
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) {
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 NULL;
}
#endif //USE_RA_AI
switch (objecttype) {
/*
** 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 (CurUnits < MaxUnit) {
/*
** A computer controlled house will try to build a replacement
** harvester if possible. Never replace harvesters if the game
** is in easy mode.
*/
if (!(GameToPlay == GAME_NORMAL && PlayerPtr->Difficulty == DIFF_EASY) && !IsHuman && (ActiveBScan & STRUCTF_REFINERY) && !(UScan & UNITF_HARVESTER)) {
techno = &UnitTypeClass::As_Reference(UNIT_HARVESTER);
if (techno->Scenario <= BuildLevel) break;
techno = 0;
}
int counter[UNIT_COUNT];
if (GameToPlay == GAME_NORMAL) {
memset(counter, 0x00, sizeof(counter));
} else {
for (UnitType index = UNIT_FIRST; index < UNIT_COUNT; index++) {
if (Can_Build(index, Class->House) && UnitTypeClass::As_Reference(index).Level <= BuildLevel) {
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) && team->House == Class->House) {
for (int subindex = 0; subindex < team->ClassCount; subindex++) {
if (team->Class[subindex]->What_Am_I() == RTTI_UNITTYPE) {
counter[((UnitTypeClass const *)(team->Class[subindex]))->Type] = 1;
// counter[((UnitTypeClass const *)(team->Class[subindex]))->Type] += team->DesiredNum[subindex]*2;
}
}
}
}
}
/*
** 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->Class[subindex]->What_Am_I() == RTTI_UNITTYPE) {
int subtype = ((UnitTypeClass const *)(team->Class[subindex]))->Type;
counter[subtype] = MAX(counter[subtype], (int)team->DesiredNum[subindex]);
}
}
}
}
}
/*
** 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 && !unit->Team && unit->House == this && (unit->Mission != MISSION_GUARD_AREA && unit->Mission != MISSION_HUNT && unit->Mission != MISSION_STICKY && unit->Mission != MISSION_SLEEP)) {
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;
UnitType bestlist[UNIT_COUNT];
for (UnitType utype = UNIT_FIRST; utype < UNIT_COUNT; utype++) {
if (counter[utype] > 0 && Can_Build(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) {
techno = &UnitTypeClass::As_Reference(bestlist[Random_Pick(0, bestcount-1)]);
}
}
break;
/*
** 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 (CurUnits < MaxUnit) {
int counter[INFANTRY_COUNT];
if (GameToPlay == GAME_NORMAL) {
memset(counter, 0x00, sizeof(counter));
} else {
for (InfantryType index = INFANTRY_FIRST; index < INFANTRY_COUNT; index++) {
if (Can_Build(index, Class->House) && InfantryTypeClass::As_Reference(index).Level <= BuildLevel) {
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) && team->House == Class->House) {
for (int subindex = 0; subindex < team->ClassCount; subindex++) {
if (team->Class[subindex]->What_Am_I() == RTTI_INFANTRYTYPE) {
counter[((InfantryTypeClass const *)(team->Class[subindex]))->Type] += team->DesiredNum[subindex]+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->Class[subindex]->What_Am_I() == RTTI_INFANTRYTYPE) {
int subtype = ((InfantryTypeClass const *)(team->Class[subindex]))->Type;
// counter[subtype] = 1;
counter[subtype] = MAX(counter[subtype], (int)team->DesiredNum[subindex]);
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 && !infantry->Team && infantry->House == this && (infantry->Mission != MISSION_GUARD_AREA && infantry->Mission != MISSION_HUNT && infantry->Mission != MISSION_STICKY && infantry->Mission != MISSION_SLEEP)) {
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 (counter[utype] > 0 && Can_Build(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) {
techno = &InfantryTypeClass::As_Reference(bestlist[Random_Pick(0, bestcount-1)]);
}
}
break;
/*
** Building construction is based upon the preconstruction list.
*/
case RTTI_BUILDING:
case RTTI_BUILDINGTYPE:
if (CurBuildings < MaxBuilding) {
BaseNodeClass * node = Base.Next_Buildable();
if (node) {
techno = &BuildingTypeClass::As_Reference(node->Type);
}
}
break;
}
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)
{
Validate();
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) {
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. *
*=============================================================================================*/
bool HouseClass::Flag_Attach(CELL cell, bool set_home)
{
Validate();
bool rc;
bool clockwise;
FacingType rot;
FacingType fcounter;
/*
** Randomly decide if we're going to search cells clockwise or counter-
** clockwise
*/
clockwise = IRandom(0,1);
/*
** 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) {
/*
** 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++) {
/*
** Clockwise search.
*/
if (clockwise) {
rot = (FacingType)IRandom(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 = (FacingType)IRandom (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;
}
}
}
}
/*
** 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;
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)
{
Validate();
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)
{
Validate();
char txt[80];
int i,j,k;
unsigned char id;
HousesType house;
HouseClass *hptr;
HouseClass *hptr2;
int num_alive;
int num_humans;
int all_allies;
int max_index;
int max_count;
int count;
int score_index[MAX_PLAYERS]; // array of each multi-player's index into
// the score array
/*------------------------------------------------------------------------
Set the defeat flag for this house
------------------------------------------------------------------------*/
IsDefeated = true;
#ifdef USE_RA_AI
/*
** Moved in from RA for AI. ST - 7/24/2019 4:02PM
*/
/*
** If this is a computer controlled house, then all computer controlled
** houses become paranoid.
*/
if (IQ == Rule.MaxIQ && !IsHuman && Rule.IsComputerParanoid) {
Computer_Paranoid();
}
#endif // USE_RA_AI
/*------------------------------------------------------------------------
Remove this house's flag & flag home cell
------------------------------------------------------------------------*/
if (Special.IsCaptureTheFlag) {
if (FlagLocation) {
Flag_Remove(FlagLocation,true);
} else {
if (FlagHome) {
Flag_Remove(FlagHome,true);
}
}
}
/*
** Remove any one-time superweapons the player might have.
*/
IonCannon.Remove(true);
AirStrike.Remove(true);
NukeStrike.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) {
MPlayerObiWan = 1;
HiddenPage.Clear();
Map.Flag_To_Redraw(true);
/*.....................................................................
Pop up a message showing that I was defeated
.....................................................................*/
sprintf(txt,Text_String(TXT_PLAYER_DEFEATED), MPlayerName);
//Messages.Add_Message(txt, MPlayerTColors[MPlayerColorIdx], TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_FULLSHADOW, 600, 0, 0);
Map.Flag_To_Redraw(false);
int timeout = 600;
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), Text_String(TXT_UNKNOWN));
id = 0;
for (i = 0; i < MPlayerCount; i++) {
house = MPlayerHouses[i];
if (HouseClass::As_Pointer(house) == this) {
sprintf (txt,Text_String(TXT_PLAYER_DEFEATED), MPlayerNames[i]);
id = MPlayerID[i];
}
}
Messages.Add_Message(txt, MPlayerTColors[MPlayerID_To_ColorIndex(id)],
TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, 0, 0);
Map.Flag_To_Redraw(false);
}
}
/*------------------------------------------------------------------------
Find out how many players are left alive.
------------------------------------------------------------------------*/
num_alive = 0;
num_humans = 0;
for (i = 0; i < MPlayerMax; i++) {
hptr = HouseClass::As_Pointer((HousesType)(HOUSE_MULTI1 + i));
if (hptr && hptr->IsDefeated==0) {
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 < MPlayerMax; 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 < MPlayerMax; 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
MPlayerObiWan flag
- Find all players' indices in the MPlayerScore array
- Tally up scores for this game
------------------------------------------------------------------------*/
if (num_alive == 1 || num_humans == 0) {
if (PlayerPtr->IsDefeated) {
PlayerLoses = true;
} else {
PlayerWins = true;
}
/*---------------------------------------------------------------------
Find each player's score index
---------------------------------------------------------------------*/
for (i = 0; i < MPlayerCount; i++) {
score_index[i] = -1;
/*..................................................................
Search for this player's name in the MPlayerScore array
..................................................................*/
for (j = 0; j < MPlayerNumScores; j++) {
if (!stricmp(MPlayerNames[i],MPlayerScore[j].Name)) {
score_index[i] = j;
break;
}
}
/*..................................................................
If the index is still -1, the name wasn't found; add a new entry.
..................................................................*/
if (score_index[i] == -1) {
if (MPlayerNumScores < MAX_MULTI_NAMES) {
score_index[i] = MPlayerNumScores;
MPlayerNumScores++;
} else {
/*...............................................................
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.
...............................................................*/
max_index = 0;
max_count = 0;
for (j = 0; j < MPlayerNumScores; j++) {
count = 0;
for (k = MPlayerNumScores - 1; k >= 0; k--) {
if (MPlayerScore[j].Kills[k]==-1) {
count++;
} else {
break;
}
}
if (count > max_count) {
max_count = count;
max_index = j;
}
}
score_index[i] = max_index;
}
/*...............................................................
Initialize this score entry
...............................................................*/
MPlayerScore[score_index[i]].Wins = 0;
strcpy (MPlayerScore[score_index[i]].Name,MPlayerNames[i]);
for (j = 0; j < MAX_MULTI_GAMES; j++)
MPlayerScore[score_index[i]].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).
..................................................................*/
MPlayerScore[score_index[i]].Kills[MPlayerCurGame] = 0;
/*..................................................................
Init this player's color to his last-used color index
..................................................................*/
MPlayerScore[score_index[i]].Color = MPlayerID_To_ColorIndex(MPlayerID[i]);
}
#if 0 // (This is the old method of tallying scores:
/*---------------------------------------------------------------------
Tally up the scores for this game:
- For each house:
- If this house is human & wasn't defeated, its the winner
- If this house was defeated, find out who did it & increment their
Kills value.
---------------------------------------------------------------------*/
for (house = HOUSE_MULTI1; house < (HOUSE_MULTI1 + MPlayerMax); house++) {
hptr = HouseClass::As_Pointer(house);
if (!hptr) continue;
if (!hptr->IsDefeated) {
/*...............................................................
If this is the winning house, find which player it was & increment
their 'Wins' value
...............................................................*/
if (hptr->IsHuman) {
for (i = 0; i < MPlayerCount; i++) {
if (house == MPlayerHouses[i]) {
MPlayerScore[score_index[i]].Wins++;
MPlayerWinner = score_index[i];
}
}
}
} else {
/*..................................................................
This house was defeated; find which player who defeated him & increment
his 'Kills' value for this game
..................................................................*/
for (i = 0; i < MPlayerCount; i++) {
if (hptr->WhoLastHurtMe == MPlayerHouses[i]) {
MPlayerScore[score_index[i]].Kills[MPlayerCurGame]++;
}
}
}
}
#else // This is the new method:
/*---------------------------------------------------------------------
Tally up the scores for this game:
- For each player:
- If this player is undefeated this round, he's the winner
- Each player's Kills value is the sum of the unit's they killed
---------------------------------------------------------------------*/
for (i = 0; i < MPlayerCount; i++) {
hptr = HouseClass::As_Pointer(MPlayerHouses[i]);
/*..................................................................
If this house was undefeated, it must have been the winner. (If
no human houses are undefeated, the computer won.)
..................................................................*/
if (!hptr->IsDefeated) {
MPlayerScore[score_index[i]].Wins++;
MPlayerWinner = score_index[i];
}
/*..................................................................
Tally up all kills for this player
..................................................................*/
for (house = HOUSE_FIRST; house < HOUSE_COUNT; house++) {
MPlayerScore[score_index[i]].Kills[MPlayerCurGame] +=
hptr->UnitsKilled[house];
MPlayerScore[score_index[i]].Kills[MPlayerCurGame] +=
hptr->BuildingsKilled[house];
}
}
#endif
/*---------------------------------------------------------------------
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 (GameToPlay==GAME_IPX || GameToPlay == GAME_INTERNET) {
i = 0;
while (Ipx.Num_Connections() && (i++ < 1000) ) {
id = Ipx.Connection_ID(0);
Ipx.Delete_Connection(id);
}
MPlayerCount = 0;
}
}
}
/***************************************************************************
* HouseClass::Blowup_All -- blows up everything *
* *
* INPUT: *
* none. *
* *
* OUTPUT: *
* none. *
* *
* WARNINGS: *
* none. *
* *
* HISTORY: *
* 05/16/1995 BRR : Created. *
* 06/09/1995 JLB : Handles aircraft. *
*=========================================================================*/
void HouseClass::Blowup_All(void)
{
Validate();
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) {
damage = 0x7fff;
Explosion_Damage(uptr->Center_Coord(), damage, NULL, WARHEAD_HE);
count++;
if (count > 5) {
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);
damage = 0x7fff;
aptr->Take_Damage(damage, 0, WARHEAD_HE, NULL);
if (!aptr->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;
bptr->IsSurvivorless = true;
while (Buildings.Ptr(i)==bptr && bptr->Strength) {
damage = 0x7fff;
Explosion_Damage(bptr->Center_Coord(), damage, NULL, WARHEAD_HE);
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) {
damage = 0x7fff;
warhead = (WarheadType)IRandom (WARHEAD_SA, WARHEAD_FIRE);
Explosion_Damage(iptr->Center_Coord(), damage, NULL, warhead);
if (iptr->IsActive) {
damage = 0x7fff;
iptr->Take_Damage(damage, 0, warhead);
}
count++;
if (count > 5) {
delete iptr;
break;
}
}
}
}
#ifdef NEVER
/*
** Just delete the teams & triggers for this house.
*/
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)->House == Class->House) {
delete Triggers.Ptr(i);
i--;
}
}
#endif
}
/***********************************************************************************************
* 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)
{
Validate();
if (!IsToWin && !IsToDie && !IsToLose) {
IsToDie = true;
if (IsV107) {
BorrowedTime = TICKS_PER_SECOND * 3;
} else {
BorrowedTime = TICKS_PER_SECOND * 1;
}
}
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)
{
Validate();
if (!IsToWin && !IsToDie && !IsToLose) {
IsToWin = true;
if (IsV107) {
BorrowedTime = TICKS_PER_SECOND * 3;
} else {
BorrowedTime = TICKS_PER_SECOND * 1;
}
}
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)
{
Validate();
IsToWin = false;
if (!IsToDie && !IsToLose) {
IsToLose = true;
if (IsV107) {
BorrowedTime = TICKS_PER_SECOND * 3;
} else {
BorrowedTime = TICKS_PER_SECOND * 1;
}
}
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. *
* *
* 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. *
*=============================================================================================*/
void HouseClass::Init_Data(PlayerColorType color, HousesType house, int credits)
{
Validate();
Credits = InitialCredits = credits;
VisibleCredits.Current = Credits;
ActLike = house;
RemapColor = color;
switch (color) {
case REMAP_GOLD:
RemapTable = RemapGold;
((unsigned char &)Class->Color) = 157;
((unsigned char &)Class->BrightColor) = 5;
break;
case REMAP_RED:
RemapTable = RemapRed;
((unsigned char &)Class->Color) = 123;
((unsigned char &)Class->BrightColor) = 127;
break;
case REMAP_LTBLUE:
RemapTable = RemapLtBlue;
((unsigned char &)Class->Color) = 135;
((unsigned char &)Class->BrightColor) = 2;
break;
case REMAP_ORANGE:
RemapTable = RemapOrange;
((unsigned char &)Class->Color) = 26;
((unsigned char &)Class->BrightColor) = 24;
break;
case REMAP_GREEN:
RemapTable = RemapGreen;
((unsigned char &)Class->Color) = 167;
((unsigned char &)Class->BrightColor) = 159;
break;
case REMAP_BLUE:
RemapTable = RemapBlue;
((unsigned char &)Class->Color) = 203;
((unsigned char &)Class->BrightColor) = 201;
break;
}
}
/***********************************************************************************************
* 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. *
*=============================================================================================*/
int HouseClass::Power_Fraction(void) const
{
Validate();
if (Power) {
if (Drain) {
return(Cardinal_To_Fixed(Drain, Power));
} else {
return(0x0100);
}
}
return(0);
}
/***********************************************************************************************
* HouseClass::Has_Nuke_Device -- Deteremines if the house has a nuclear device. *
* *
* This routine checks to see if the house has a nuclear device to launch. A nuclear *
* device is available when the necessary parts have been retrieved in earlier scenarios *
* or if this is the multiplayer version. *
* *
* INPUT: none *
* *
* OUTPUT: Does the house have a nuclear device? *
* *
* WARNINGS: This does not check to see if there is a suitable launch facility (i.e., the *
* Temple of Nod), only that there is a nuclear device potential. *
* *
* HISTORY: *
* 07/24/1995 JLB : Created. *
*=============================================================================================*/
bool HouseClass::Has_Nuke_Device(void)
{
Validate();
if (GameToPlay != GAME_NORMAL || !IsHuman) return(true);
return((NukePieces & 0x07) == 0x07);
}
/***********************************************************************************************
* 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. *
*=============================================================================================*/
void HouseClass::Sell_Wall(CELL cell)
{
Validate();
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;
default:
break;
}
if (btype != NULL && !btype->IsUnsellable) {
if (PlayerPtr == this) {
Sound_Effect(VOC_CASHTURN);
}
Refund_Money(btype->Cost_Of()/2);
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();
ObjectClass::Detach_This_From_All(::As_Target(cell), true);
}
}
}
}
}
/***********************************************************************************************
* 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) {
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)
{
AircraftTotals = new UnitTrackerClass( (int) AIRCRAFT_COUNT);
InfantryTotals = new UnitTrackerClass( (int) INFANTRY_COUNT);
UnitTotals = new UnitTrackerClass ( (int) UNIT_COUNT);
BuildingTotals = new UnitTrackerClass ( (int) STRUCT_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);
CapturedBuildings = new UnitTrackerClass ( (int) STRUCT_COUNT);
TotalCrates = new UnitTrackerClass ( TOTAL_CRATE_TYPES ); //15 crate types
}
/***********************************************************************************************
* 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 (DestroyedAircraft) {
delete DestroyedAircraft;
DestroyedAircraft = NULL;
}
if (DestroyedInfantry) {
delete DestroyedInfantry;
DestroyedInfantry = NULL;
}
if (DestroyedUnits) {
delete DestroyedUnits;
DestroyedUnits = NULL;
}
if (DestroyedBuildings) {
delete DestroyedBuildings;
DestroyedBuildings = NULL;
}
if (CapturedBuildings) {
delete CapturedBuildings;
CapturedBuildings = NULL;
}
if (TotalCrates) {
delete TotalCrates;
TotalCrates = NULL;
}
}
#ifdef USE_RA_AI
/***********************************************************************************************
Below AI code imported from RA
***********************************************************************************************/
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
#endif
/*
** In RA, Control is a container for other variables. In TD, they are defined in the class
*/
#define Control (*this)
/*
** Percent_Chance - implementation similar to Red Alert
*/
inline bool Percent_Chance(int percent)
{
return (Random_Pick(0, 99) < percent);
}
/*
** Engineer was renamed to RENOVATOR for RA
*/
#define INFANTRY_RENOVATOR INFANTRY_E7
TFixedIHeapClass<HouseClass::BuildChoiceClass> HouseClass::BuildChoice;
/*
** This is a replacement for the RA 'fixed' round up function. It takes the equivalent of a 'fixed' value, but returns just the integer part
** ST - 7/26/2019 11:13AM
*/
unsigned short Round_Up(unsigned short val)
{
if ((val & 0xff) == 0) {
return val;
}
val &= 0xff00;
val += 0x0100;
val >>= 8;
return val;
}
unsigned short fixed(int val) {return (unsigned short)val;}
/***********************************************************************************************
* 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(0U, 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();
int frac = Power_Fraction();
UrgencyType urgency = URGENCY_NONE;
//if (frac < 1 && Can_Make_Money()) {
if (frac < 0x0100 && Can_Make_Money()) {
urgency = URGENCY_LOW;
/*
** Very low power condition is considered a higher priority.
*/
//if (frac < fixed::_3_4) urgency = URGENCY_MEDIUM;
if (frac < 0x00C0) urgency = URGENCY_MEDIUM;
/*
** When under attack and there is a need for power in defense,
** then consider power building a higher priority.
*/
// No chronosphere in TD. ST - 7/19/2019 4:38PM
//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))) {
if (State != STATE_ATTACKED && CurBuildings && !(ActiveBScan & (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.
*/
#if (0)
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}
};
#endif
static struct {
StructType Structure;
UrgencyType Urgency;
} _types[] = {
{STRUCT_BIO_LAB, URGENCY_LOW},
{STRUCT_EYE, URGENCY_MEDIUM},
{STRUCT_RADAR, URGENCY_MEDIUM},
{STRUCT_REPAIR, URGENCY_MEDIUM},
{STRUCT_OBELISK, URGENCY_HIGH},
{STRUCT_TURRET, URGENCY_HIGH},
{STRUCT_ATOWER, URGENCY_HIGH},
{STRUCT_GTOWER, 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.
*/
#if (0)
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}
};
#endif
static struct {
StructType Structure;
UrgencyType Urgency;
} _types[] = {
{STRUCT_BIO_LAB, URGENCY_LOW},
{STRUCT_EYE, URGENCY_MEDIUM},
{STRUCT_RADAR, URGENCY_MEDIUM},
{STRUCT_STORAGE,URGENCY_LOW},
{STRUCT_REPAIR, URGENCY_MEDIUM},
{STRUCT_OBELISK, URGENCY_HIGH},
{STRUCT_TURRET, URGENCY_HIGH},
{STRUCT_ATOWER, URGENCY_HIGH},
{STRUCT_GTOWER, URGENCY_HIGH},
{STRUCT_HELIPAD,URGENCY_MEDIUM},
{STRUCT_POWER,URGENCY_HIGH},
{STRUCT_AIRSTRIP,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 (0) // Not used for GAME_NORMAL in C&C. ST - 7/23/2019 3:04PM
if (Session.Type == GAME_NORMAL && Base.House == Class->House) {
BaseNodeClass * node = Base.Next_Buildable();
if (node) {
BuildStructure = node->Type;
}
}
#endif
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];
current = BQuantity[STRUCT_BARRACKS] + BQuantity[STRUCT_HAND];
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);
b = &BuildingTypeClass::As_Reference(STRUCT_HAND);
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);
}
}
}
}
#if (0)
/*
** 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);
}
}
}
#endif
/*
** 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];
current = BQuantity[STRUCT_TURRET] + BQuantity[STRUCT_OBELISK];
if (current < Round_Up(Rule.DefenseRatio*fixed(CurBuildings)) && current < (unsigned)Rule.DefenseLimit) {
//b = &BuildingTypeClass::As_Reference(STRUCT_FLAME_TURRET);
b = &BuildingTypeClass::As_Reference(STRUCT_OBELISK);
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);
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);
}
}
#if (0)
} 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);
}
}
}
#endif
}
}
/*
** Build some air defense.
*/
//current = BQuantity[STRUCT_SAM] + BQuantity[STRUCT_AAGUN];
current = BQuantity[STRUCT_SAM];
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 {
#if (0)
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);
}
}
#endif
}
}
}
#if (0)
/*
** 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);
}
}
}
}
#endif
/*
** 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);
}
}
}
/*
** 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) {
if (UnitTypeClass::As_Reference(UNIT_HARVESTER).Level <= (unsigned)BuildLevel) {
BuildUnit = UNIT_HARVESTER;
return(TICKS_PER_SECOND);
}
}
//if (Session.Type == GAME_NORMAL) { // Why? ST - 7/24/2019 2:38PM
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) {
if (((team->IsReinforcable && !tptr->IsFullStrength) || (!tptr->IsForcedActive && !tptr->IsHasBeen && !tptr->IsAltered)) && team->House == Class->House) {
for (int subindex = 0; subindex < team->ClassCount; subindex++) {
//TechnoTypeClass const * memtype = team->Members[subindex].Class;
TechnoTypeClass const * memtype = team->Class[subindex];
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;
TechnoTypeClass const * memtype = team->Class[subindex];
if (memtype->What_Am_I() == RTTI_UNITTYPE) {
int subtype = ((UnitTypeClass const *)memtype)->Type;
//counter[subtype] = max(counter[subtype], team->Members[subindex].Quantity);
counter[subtype] = max(counter[subtype], 1);
}
}
}
}
/*
** 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) {
if (unit != NULL && 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) {
if (utype->Primary != WEAPON_NONE) {
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)
{
#if (0)
//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) {
if (Can_Build(&VesselTypeClass::As_Reference(index), Class->House) && VesselTypeClass::As_Reference(index).Level <= (unsigned)BuildLevel) {
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) {
if (((team->IsReinforcable && !tptr->IsFullStrength) || (!tptr->IsForcedActive && !tptr->IsHasBeen && !tptr->IsAltered)) && 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);
counter[subtype] = max(counter[subtype], 1);
}
}
}
}
}
/*
** 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) {
if (unit != NULL && 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;
}
#endif
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) {
if (GameToPlay == GAME_NORMAL) { // Not used for skirmish? ST - 7/24/2019 2:59PM
#if (0)
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) {
if (((team->IsReinforcable && !tptr->IsFullStrength) || (!tptr->IsForcedActive && !tptr->IsHasBeen && !tptr->IsAltered)) && 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);
counter[((InfantryTypeClass const *)(team->Members[subindex].Class))->Type] += 1 + (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] = max(counter[subtype], 1);
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) {
if (infantry != NULL && 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];
}
#endif
}
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) {
if (Can_Build(&InfantryTypeClass::As_Reference(index), ActLike) && InfantryTypeClass::As_Reference(index).Level <= (unsigned)BuildLevel) {
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:
case INFANTRY_E7:
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_HELICOPTER), ActLike) &&
//AircraftTypeClass::As_Reference(AIRCRAFT_MIG).Level <= (unsigned)Control.TechLevel &&
AircraftTypeClass::As_Reference(AIRCRAFT_HELICOPTER).Level <= (unsigned)BuildLevel &&
BQuantity[STRUCT_AIRSTRIP] > AQuantity[AIRCRAFT_HELICOPTER] + AQuantity[AIRCRAFT_ORCA]) {
BuildAircraft = AIRCRAFT_HELICOPTER;
return(TICKS_PER_SECOND);
}
if (Can_Build(&AircraftTypeClass::As_Reference(AIRCRAFT_ORCA), ActLike) &&
//AircraftTypeClass::As_Reference(AIRCRAFT_YAK).Level <= (unsigned)Control.TechLevel &&
AircraftTypeClass::As_Reference(AIRCRAFT_ORCA).Level <= (unsigned)BuildLevel &&
BQuantity[STRUCT_AIRSTRIP] > AQuantity[AIRCRAFT_ORCA] + AQuantity[AIRCRAFT_HELICOPTER]) {
BuildAircraft = AIRCRAFT_ORCA;
return(TICKS_PER_SECOND);
}
#if (0)
if (Can_Build(&AircraftTypeClass::As_Reference(AIRCRAFT_LONGBOW), ActLike) &&
//AircraftTypeClass::As_Reference(AIRCRAFT_LONGBOW).Level <= (unsigned)Control.TechLevel &&
AircraftTypeClass::As_Reference(AIRCRAFT_LONGBOW).Level <= (unsigned)BuildLevel &&
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 &&
AircraftTypeClass::As_Reference(AIRCRAFT_HIND).Level <= (unsigned)BuildLevel &&
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 &&
AircraftTypeClass::As_Reference(AIRCRAFT_MIG).Level <= (unsigned)BuildLevel &&
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 &&
AircraftTypeClass::As_Reference(AIRCRAFT_YAK).Level <= (unsigned)BuildLevel &&
BQuantity[STRUCT_AIRSTRIP] > AQuantity[AIRCRAFT_MIG] + AQuantity[AIRCRAFT_YAK]) {
BuildAircraft = AIRCRAFT_YAK;
return(TICKS_PER_SECOND);
}
#endif
}
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;
#if (0)
case RTTI_VESSEL:
if (*((VesselClass*)product) == BuildVessel) {
BuildVessel = VESSEL_NONE;
}
break;
#endif
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;
#if (0)
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;
#endif
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 (0) // This is a stats thing. ST - 7/24/2019 3:08PM
if (Session.Type == GAME_INTERNET) {
BuildingTotals->Increment_Unit_Total(techno->Class_Of().ID);
}
#endif
break;
case RTTI_AIRCRAFT:
CurAircraft++;
aircraft = ((AircraftTypeClass const &)techno->Class_Of()).Type;
AQuantity[aircraft]++;
AScan |= (1L << aircraft);
#if (0) // This is a stats thing. ST - 7/24/2019 3:08PM
if (Session.Type == GAME_INTERNET) {
AircraftTotals->Increment_Unit_Total(techno->Class_Of().ID);
}
#endif
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 (0) // This is a stats thing. ST - 7/24/2019 3:08PM
if (!((InfantryTypeClass const &)techno->Class_Of()).IsCivilian && Session.Type == GAME_INTERNET) {
InfantryTotals->Increment_Unit_Total(techno->Class_Of().ID);
}
#endif
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 (0) // This is a stats thing. ST - 7/24/2019 3:08PM
if (Session.Type == GAME_INTERNET) {
UnitTotals->Increment_Unit_Total(techno->Class_Of().ID);
}
#endif
break;
#if (0)
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;
#endif
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);
#if (0)
case RTTI_VESSELTYPE:
case RTTI_VESSEL:
return(&VesselFactories);
#endif
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;
#if (0)
house->VScan = 0;
house->ActiveVScan = 0;
#endif
}
}
/*
** 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->IsLocked) {
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->IsLocked) {
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->IsLocked) {
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->IsLocked) {
if (!building->IsInLimbo) {
building->House->ActiveBScan |= (1L << building->Class->Type);
building->House->OldBScan |= (1L << building->Class->Type);
}
}
}
}
#if (0)
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);
}
}
}
#endif
}
/***********************************************************************************************
* 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;
#if (0)
case RTTI_VESSEL:
case RTTI_VESSELTYPE:
factory_index = VesselFactory;
break;
#endif
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;
#if (0)
case RTTI_VESSEL:
case RTTI_VESSELTYPE:
factory_index = &VesselFactory;
break;
#endif
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;
*factory_index = Factories.ID(factory);
} 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. *
*=============================================================================================*/
#if (0) // ST 7/17/2019
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);
}
}
}
}
#endif
/***********************************************************************************************
* 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. *
*=============================================================================================*/
#if (0) // ST 7/17/2019
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)));
}
}
}
#endif
#if (0)
/***********************************************************************************************
* 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);
}
#endif
/***********************************************************************************************
* 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);
}
}
#if (0)
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);
}
}
#endif
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) {
if (GameToPlay == 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 (GameToPlay != 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;
}
/***********************************************************************************************
* 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;
}
/***********************************************************************************************
* 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;
}
#endif