4349 lines
184 KiB
C++
4349 lines
184 KiB
C++
/*
|
|
** Command & Conquer(tm)
|
|
** Copyright 2025 Electronic Arts Inc.
|
|
**
|
|
** This program is free software: you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation, either version 3 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* $Header: 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::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"
|
|
|
|
|
|
/***********************************************************************************************
|
|
* 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 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();
|
|
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;
|
|
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;
|
|
RemapTable = Class->RemapTable;
|
|
RemapColor = Class->RemapColor;
|
|
Resigned = false;
|
|
SpeakAttackDelay = 1;
|
|
SpeakMaxedDelay = 1;
|
|
SpeakMoneyDelay = 1;
|
|
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));
|
|
|
|
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::~HouseClass (void)
|
|
{
|
|
delete AircraftTotals;
|
|
delete InfantryTotals;
|
|
delete UnitTotals;
|
|
delete BuildingTotals;
|
|
|
|
delete DestroyedAircraft;
|
|
delete DestroyedInfantry;
|
|
delete DestroyedUnits;
|
|
delete DestroyedBuildings;
|
|
|
|
delete CapturedBuildings;
|
|
delete TotalCrates;
|
|
}
|
|
|
|
/***********************************************************************************************
|
|
* 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.
|
|
*/
|
|
if (!IsHuman) return(true);
|
|
|
|
/*
|
|
** Perform some equivalency fixups for the building existance flags.
|
|
*/
|
|
long flags = ActiveBScan;
|
|
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)) {
|
|
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;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* 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. *
|
|
*=============================================================================================*/
|
|
void HouseClass::AI(void)
|
|
{
|
|
Validate();
|
|
|
|
/*
|
|
** 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;
|
|
|
|
/*
|
|
** 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();
|
|
}
|
|
|
|
/*
|
|
** 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 (Special.IsDifficult) {
|
|
AlertTime = (TICKS_PER_MINUTE * Random_Pick(4, 10));
|
|
} else {
|
|
if (Special.IsEasy) {
|
|
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 damage;
|
|
int count;
|
|
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 they refuse,
|
|
** blow them up.
|
|
*/
|
|
if (FlagHome) {
|
|
techno = Map[FlagHome].Cell_Techno();
|
|
if (techno) {
|
|
moving = false;
|
|
techno->Scatter(0,true);
|
|
|
|
/*
|
|
** If the techno doesn't have a valid NavCom, he's not moving,
|
|
** so blow him up.
|
|
*/
|
|
if (techno->What_Am_I() == RTTI_INFANTRY ||
|
|
techno->What_Am_I() == RTTI_UNIT) {
|
|
if (Target_Legal(((FootClass *)techno)->NavCom)) {
|
|
moving = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** If the techno wasn't an infantry or unit (ie he's a building),
|
|
** or he refuses to move, blow him up
|
|
*/
|
|
if (!moving) {
|
|
count = 0;
|
|
while (!(techno->IsInLimbo) && count++ < 5) {
|
|
damage = 0x7fff;
|
|
Explosion_Damage(techno->Center_Coord(), damage, NULL, WARHEAD_HE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** 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) {
|
|
UnitClass *obj = NULL;
|
|
CELL cell;
|
|
|
|
if (Special.IsJurassic && AreThingiesEnabled) {
|
|
obj = new UnitClass(Random_Pick(UNIT_TRIC, UNIT_STEG), HOUSE_JP);
|
|
} else {
|
|
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) {
|
|
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 (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));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** 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].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 ((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.
|
|
*/
|
|
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 ((ActiveBScan & STRUCTF_TEMPLE) && Has_Nuke_Device() && IsHuman) {
|
|
NukeStrike.Enable((GameToPlay == GAME_NORMAL), this == PlayerPtr);
|
|
|
|
/*
|
|
** Flag the sidebar to be redrawn if necessary.
|
|
*/
|
|
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);
|
|
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
|
|
|
|
/*
|
|
** 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 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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* 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(void)
|
|
{
|
|
Validate();
|
|
if (SpeakAttackDelay.Expired() && PlayerPtr->Class->House == Class->House) {
|
|
Speak(VOX_BASE_UNDER_ATTACK);
|
|
SpeakAttackDelay.Set(Options.Normalize_Delay(SPEAK_DELAY));
|
|
|
|
/*
|
|
** 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 (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);
|
|
}
|
|
|
|
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 (result) {
|
|
fptr->Start();
|
|
|
|
/*
|
|
** Link this factory to the sidebar so that proper graphic feedback
|
|
** can take place.
|
|
*/
|
|
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.
|
|
*/
|
|
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;
|
|
int index;
|
|
switch (id) {
|
|
|
|
case SPC_ION_CANNON:
|
|
if (IonCannon.Is_Ready()) {
|
|
anim = new AnimClass(ANIM_ION_CANNON, Cell_Coord(cell));
|
|
if (anim) anim->Owner = Class->House;
|
|
if (this == PlayerPtr) {
|
|
Map.IsTargettingMode = false;
|
|
}
|
|
IonCannon.Discharged(PlayerPtr == this);
|
|
IsRecalcNeeded = true;
|
|
}
|
|
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);
|
|
Sound_Effect(VOC_NUKE_FIRE, start);
|
|
}
|
|
}
|
|
if (this == PlayerPtr) {
|
|
Map.IsTargettingMode = false;
|
|
}
|
|
NukeStrike.Discharged(this == PlayerPtr);
|
|
IsRecalcNeeded = true;
|
|
}
|
|
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;
|
|
}
|
|
break;
|
|
}
|
|
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. *
|
|
*=============================================================================================*/
|
|
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);
|
|
|
|
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->Completed();
|
|
Abandon_Production(type);
|
|
} else {
|
|
|
|
/*
|
|
** The object could not leave under it's own power. Just wait
|
|
** until the player tries to place the object again.
|
|
*/
|
|
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;
|
|
|
|
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 (!Special.IsEasy && !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.
|
|
*/
|
|
for (int 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], 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.
|
|
*/
|
|
for (int 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], 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();
|
|
int rc;
|
|
|
|
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);
|
|
}
|
|
return(false);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* 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.
|
|
*/
|
|
if (rc) {
|
|
FlagLocation = As_Target(newcell);
|
|
|
|
if (set_home || FlagHome == 0) {
|
|
Map[newcell].Overlay = OVERLAY_FLAG_SPOT;
|
|
FlagHome = newcell;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
* 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;
|
|
|
|
/*------------------------------------------------------------------------
|
|
Remove this house's flag & flag home cell
|
|
------------------------------------------------------------------------*/
|
|
if (Special.IsCaptureTheFlag) {
|
|
if (FlagLocation) {
|
|
Flag_Remove(FlagLocation,true);
|
|
} else {
|
|
if (FlagHome) {
|
|
Flag_Remove(FlagHome,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;
|
|
Debug_Unshroud = true;
|
|
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);
|
|
|
|
} 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;
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------------
|
|
Be sure our messages get displayed, even if we're about to exit.
|
|
------------------------------------------------------------------------*/
|
|
Map.Render();
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
* 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;
|
|
ActLike = house;
|
|
RemapColor = color;
|
|
switch (color) {
|
|
case REMAP_YELLOW:
|
|
RemapTable = RemapYellow;
|
|
((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_AQUA:
|
|
RemapTable = RemapBlueGreen;
|
|
((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) {
|
|
int cost = 0;
|
|
switch (overlay) {
|
|
case OVERLAY_SANDBAG_WALL:
|
|
cost = BuildingTypeClass::As_Reference(STRUCT_SANDBAG_WALL).Cost_Of();
|
|
break;
|
|
|
|
case OVERLAY_CYCLONE_WALL:
|
|
cost = BuildingTypeClass::As_Reference(STRUCT_CYCLONE_WALL).Cost_Of();
|
|
break;
|
|
|
|
case OVERLAY_BRICK_WALL:
|
|
cost = BuildingTypeClass::As_Reference(STRUCT_BRICK_WALL).Cost_Of();
|
|
break;
|
|
|
|
case OVERLAY_BARBWIRE_WALL:
|
|
cost = BuildingTypeClass::As_Reference(STRUCT_BARBWIRE_WALL).Cost_Of();
|
|
break;
|
|
|
|
case OVERLAY_WOOD_WALL:
|
|
cost = BuildingTypeClass::As_Reference(STRUCT_WOOD_WALL).Cost_Of();
|
|
break;
|
|
|
|
default:
|
|
cost = 0;
|
|
break;
|
|
}
|
|
Refund_Money(cost/2);
|
|
Map[cell].Overlay = OVERLAY_NONE;
|
|
Map[cell].OverlayData = 0;
|
|
Map[cell].Owner = HOUSE_NONE;
|
|
Map[cell].Wall_Update();
|
|
Map[cell].Recalc_Attributes();
|
|
Map[cell].Redraw_Objects();
|
|
ObjectClass::Detach_This_From_All(::As_Target(cell), true);
|
|
}
|
|
}
|
|
}
|
|
}
|