CnC_Remastered_Collection/REDALERT/BUILDING.CPP

6088 lines
247 KiB
C++
Raw Normal View History

//
// Copyright 2020 Electronic Arts Inc.
//
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is free
// software: you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed
// in the hope that it will be useful, but with permitted additional restrictions
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
// distributed with this program. You should have received a copy of the
// GNU General Public License along with permitted additional restrictions
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
/* $Header: /CounterStrike/BUILDING.CPP 5 3/13/97 5:18p Joe_b $ */
/***********************************************************************************************
*** 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 : BUILDING.CPP *
* *
* Programmer : Joe L. Bostic *
* *
* Start Date : September 10, 1993 *
* *
* Last Update : October 27, 1996 [JLB] *
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* BuildingClass::AI -- Handles non-graphic AI processing for buildings. *
* BuildingClass::Active_Click_With -- Handles cell selection for buildings. *
* BuildingClass::Animation_AI -- Handles normal building animation processing. *
* BuildingClass::Assign_Target -- Assigns a target to the building. *
* BuildingClass::Begin_Mode -- Begins an animation mode for the building. *
* BuildingClass::BuildingClass -- Constructor for buildings. *
* BuildingClass::Can_Demolish -- Can the player demolish (sell back) the building? *
* BuildingClass::Can_Enter_Cell -- Determines if building can be placed down. *
* BuildingClass::Can_Fire -- Determines if this building can fire. *
* BuildingClass::Can_Player_Move -- Can this building be moved? *
* BuildingClass::Captured -- Captures the building. *
* BuildingClass::Center_Coord -- Fetches the center coordinate for the building. *
* BuildingClass::Charging_AI -- Handles the special charging logic for Tesla coils. *
* BuildingClass::Check_Point -- Fetches the landing checkpoint for the given flight pattern.*
* BuildingClass::Click_With -- Handles clicking on the map while the building is selected. *
* BuildingClass::Crew_Type -- This determines the crew that this object generates. *
* BuildingClass::Death_Announcement -- Announce the death of this building. *
* BuildingClass::Debug_Dump -- Displays building status to the monochrome screen. *
* BuildingClass::Detach -- Handles target removal from the game system. *
* BuildingClass::Detach_All -- Possibly abandons production according to factory type. *
* BuildingClass::Docking_Coord -- Fetches the coordinate to use for docking. *
* BuildingClass::Draw_It -- Displays the building at the location specified. *
* BuildingClass::Drop_Debris -- Drops rubble when building is destroyed. *
* BuildingClass::Enter_Idle_Mode -- The building will enter its idle mode. *
* BuildingClass::Exit_Coord -- Determines location where object will leave it. *
* BuildingClass::Exit_Object -- Initiates an object to leave the building. *
* BuildingClass::Factory_AI -- Handle factory production and initiation. *
* BuildingClass::Find_Exit_Cell -- Find a clear location to exit an object from this buildin*
* BuildingClass::Fire_Direction -- Fetches the direction of firing. *
* BuildingClass::Fire_Out -- Handles when attached animation expires. *
* BuildingClass::Flush_For_Placement -- Handles clearing a zone for object placement. *
* BuildingClass::Get_Image_Data -- Fetch the image pointer for the building. *
* BuildingClass::Grand_Opening -- Handles construction completed special operations. *
* BuildingClass::Greatest_Threat -- Searches for target that building can fire upon. *
* BuildingClass::How_Many_Survivors -- This determine the maximum number of survivors. *
* BuildingClass::Init -- Initialize the building system to an empty null state. *
* BuildingClass::Limbo -- Handles power adjustment as building goes into limbo. *
* BuildingClass::Mark -- Building interface to map rendering system. *
* BuildingClass::Mission_Attack -- Handles attack mission for building. *
* BuildingClass::Mission_Construction -- Handles mission construction. *
* BuildingClass::Mission_Deconstruction -- Handles building deconstruction. *
* BuildingClass::Mission_Guard -- Handles guard mission for combat buildings. *
* BuildingClass::Mission_Harvest -- Handles refinery unloading harvesters. *
* BuildingClass::Mission_Missile -- State machine for nuclear missile launch. *
* BuildingClass::Mission_Repair -- Handles the repair (active) state for building. *
* BuildingClass::Mission_Unload -- Handles the unload mission for a building. *
* BuildingClass::Pip_Count -- Determines "full" pips to display for building. *
* BuildingClass::Power_Output -- Fetches the current power output from this building. *
* BuildingClass::Read_INI -- Reads buildings from INI file. *
* BuildingClass::Receive_Message -- Handle an incoming message to the building. *
* BuildingClass::Remap_Table -- Fetches the remap table to use for this building. *
* BuildingClass::Remove_Gap_Effect -- Stop a gap generator from jamming cells *
* BuildingClass::Repair -- Initiates or terminates the repair process. *
* BuildingClass::Repair_AI -- Handle the repair (and sell) logic for the building. *
* BuildingClass::Revealed -- Reveals the building to the specified house. *
* BuildingClass::Rotation_AI -- Process any turret rotation required of this building. *
* BuildingClass::Sell_Back -- Controls the sell back (demolish) operation. *
* BuildingClass::Shape_Number -- Fetch the shape number for this building. *
* BuildingClass::Sort_Y -- Returns the building coordinate used for sorting. *
* BuildingClass::Take_Damage -- Inflicts damage points upon a building. *
* BuildingClass::Target_Coord -- Return the coordinate to use when firing on this building. *
* BuildingClass::Toggle_Primary -- Toggles the primary factory state. *
* BuildingClass::Turret_Facing -- Fetches the turret facing for this building. *
* BuildingClass::Unlimbo -- Removes a building from limbo state. *
* BuildingClass::Update_Buildables -- Informs sidebar of additional construction options. *
* BuildingClass::Value -- Determine the value of this building. *
* BuildingClass::What_Action -- Determines action to perform if click on specified object. *
* BuildingClass::What_Action -- Determines what action will occur. *
* BuildingClass::Write_INI -- Write out the building data to the INI file specified. *
* BuildingClass::delete -- Deallocates building object. *
* BuildingClass::new -- Allocates a building object from building pool. *
* BuildingClass::~BuildingClass -- Destructor for building type objects. *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "function.h"
/*
** New sidebar for GlyphX multiplayer. ST - 8/2/2019 2:35PM
*/
#include "SidebarGlyphx.h"
enum SAMState {
SAM_READY, // Launcher can be facing any direction tracking targets.
SAM_FIRING // Stationary while missile is being fired.
};
/***************************************************************************
** Center of building offset table.
*/
COORDINATE const BuildingClass::CenterOffset[BSIZE_COUNT] = {
0x00800080L,
0x008000FFL,
0x00FF0080L,
0x00FF00FFL,
0x018000FFL,
0x00FF0180L,
0x01800180L,
0x00FF0200L,
0x02800280L,
};
/***********************************************************************************************
* BuildingClass::Receive_Message -- Handle an incoming message to the building. *
* *
* This routine handles an incoming message to the building. Messages regulate the *
* various cooperative ventures between buildings and units. This might include such *
* actions as coordinating the construction yard animation with the actual building's *
* construction animation. *
* *
* INPUT: from -- The originator of the message received. *
* *
* message -- The radio message received. *
* *
* param -- Reference to an optional parameter that might be used to return *
* extra information to the message originator. *
* *
* OUTPUT: Returns with the response to the message (typically, this is just RADIO_OK). *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/09/1994 JLB : Created. *
* 06/26/1995 JLB : Forces refinery load anim to start immediately. *
* 08/13/1995 JLB : Uses ScenarioInit for special loose "CAN_LOAD" check. *
*=============================================================================================*/
RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageType message, long & param)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
switch (message) {
/*
** This message is received as a request to attach/load/dock with this building.
** Verify that this is allowed and return the appropriate response.
*/
case RADIO_CAN_LOAD:
TechnoClass::Receive_Message(from, message, param);
if (!House->Is_Ally(from)) return(RADIO_STATIC);
if (Mission == MISSION_CONSTRUCTION || Mission == MISSION_DECONSTRUCTION || BState == BSTATE_CONSTRUCTION || (!ScenarioInit && Class->Type != STRUCT_REFINERY && In_Radio_Contact())) return(RADIO_NEGATIVE);
switch (Class->Type) {
case STRUCT_AIRSTRIP:
if (from->What_Am_I() == RTTI_AIRCRAFT && ((AircraftClass const *)from)->Class->IsFixedWing) {
return(RADIO_ROGER);
}
break;
case STRUCT_HELIPAD:
if (from->What_Am_I() == RTTI_AIRCRAFT && !((AircraftClass const *)from)->Class->IsFixedWing) {
return(RADIO_ROGER);
}
break;
case STRUCT_REPAIR:
if (from->What_Am_I() == RTTI_UNIT || (from->What_Am_I() == RTTI_AIRCRAFT)) {
if (Transmit_Message(RADIO_ON_DEPOT, from) != RADIO_ROGER) {
return(RADIO_ROGER);
}
}
return(RADIO_NEGATIVE);
case STRUCT_REFINERY:
if (from->What_Am_I() == RTTI_UNIT &&
*((UnitClass *)from) == UNIT_HARVESTER &&
(ScenarioInit || !Is_Something_Attached())) {
return((Contact_With_Whom() != from) ? RADIO_ROGER : RADIO_NEGATIVE);
}
break;
default:
break;
}
return(RADIO_STATIC);
/*
** This message is received when the object has attached itself to this
** building.
*/
case RADIO_IM_IN:
if (Mission == MISSION_DECONSTRUCTION) {
return(RADIO_NEGATIVE);
}
switch (Class->Type) {
case STRUCT_REPAIR:
IsReadyToCommence = true;
Assign_Mission(MISSION_REPAIR);
from->Assign_Mission(MISSION_SLEEP);
return(RADIO_ROGER);
case STRUCT_AIRSTRIP:
case STRUCT_HELIPAD:
Assign_Mission(MISSION_REPAIR);
from->Assign_Mission(MISSION_SLEEP);
return(RADIO_ROGER);
case STRUCT_REFINERY:
Mark(MARK_CHANGE);
from->Assign_Mission(MISSION_UNLOAD);
return(RADIO_ROGER);
default:
break;
}
break;
/*
** Docking maneuver maintenance message. See if new order should be given to the
** unit trying to dock.
*/
case RADIO_DOCKING:
TechnoClass::Receive_Message(from, message, param);
/*
** When in radio contact for loading, the refinery starts
** flashing the lights.
*/
if (*this == STRUCT_REFINERY && BState != BSTATE_FULL) {
Begin_Mode(BSTATE_FULL);
}
/*
** If this building is already in radio contact, then it might
** be able to satisfy the request to load by bumping off any
** preoccupying task.
*/
if (*this == STRUCT_REPAIR) {
if (Contact_With_Whom() != from) {
if (Transmit_Message(RADIO_ON_DEPOT) == RADIO_ROGER) {
if (Transmit_Message(RADIO_NEED_REPAIR) == RADIO_NEGATIVE) {
Transmit_Message(RADIO_RUN_AWAY);
Transmit_Message(RADIO_OVER_OUT);
return(RADIO_ROGER);
}
}
}
}
/*
** Establish contact with the object if this building isn't already in contact
** with another.
*/
if (!In_Radio_Contact()) {
Transmit_Message(RADIO_HELLO, from);
}
if (Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER) {
switch (Class->Type) {
case STRUCT_AIRSTRIP:
param = As_Target();
break;
case STRUCT_HELIPAD:
param = As_Target();
break;
case STRUCT_REPAIR:
Transmit_Message(RADIO_TETHER);
param = ::As_Target(Coord_Cell(Center_Coord()));
break;
case STRUCT_REFINERY:
param = ::As_Target(Coord_Cell(Adjacent_Cell(Center_Coord(), DIR_S)));
break;
}
/*
** Tell the harvester to move to the docking pad of the building.
*/
if (Transmit_Message(RADIO_MOVE_HERE, param) == RADIO_YEA_NOW_WHAT) {
/*
** Since the harvester is already there, tell it to begin the backup
** procedure now. If it can't, then tell it to get outta here.
*/
Transmit_Message(RADIO_TETHER);
if (*this == STRUCT_REFINERY && Transmit_Message(RADIO_BACKUP_NOW, from) != RADIO_ROGER) {
from->Scatter(NULL, true, true);
}
}
}
return(RADIO_ROGER);
/*
** If a transport or harvester is requesting permission to head toward, dock
** and load/unload, check to make sure that this is allowed given the current
** state of the building.
*/
case RADIO_ARE_REFINERY:
if (Is_Something_Attached() || In_Radio_Contact() || IsInLimbo || House->Class->House != from->Owner() || (*this != STRUCT_REFINERY/* && *this != STRUCT_REPAIR*/)) {
return(RADIO_NEGATIVE);
}
return(RADIO_ROGER);
/*
** Someone is telling us that it is starting construction. This should only
** occur if this is a construction yard and a building was just placed on
** the map.
*/
case RADIO_BUILDING:
Assign_Mission(MISSION_REPAIR);
TechnoClass::Receive_Message(from, message, param);
return(RADIO_ROGER);
/*
** Someone is telling us that they have finished construction. This should
** only occur if this is a construction yard and the building that was being
** constructed has finished. In this case, stop the construction yard
** animation.
*/
case RADIO_COMPLETE:
if (Mission != MISSION_DECONSTRUCTION) {
Assign_Mission(MISSION_GUARD);
}
TechnoClass::Receive_Message(from, message, param);
return(RADIO_ROGER);
/*
** This message may occur unexpectedly if the unit in contact with this
** building is suddenly destroyed. Handle any cleanup necessary. For example,
** a construction yard should stop its construction animation in this case.
*/
case RADIO_OVER_OUT:
Begin_Mode(BSTATE_IDLE);
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
if (*this == STRUCT_REPAIR) {
Assign_Mission(MISSION_GUARD);
}
TechnoClass::Receive_Message(from, message, param);
return(RADIO_ROGER);
/*
** This message is received when an object has completely left
** building. Sometimes special cleanup action is required when
** this event occurs.
*/
case RADIO_UNLOADED:
if (*this == STRUCT_REPAIR) {
if (Distance(from) < 0x0180) {
return(RADIO_ROGER);
}
}
TechnoClass::Receive_Message(from, message, param);
if (*this == STRUCT_WEAP || *this == STRUCT_AIRSTRIP || *this == STRUCT_REPAIR) return(RADIO_RUN_AWAY);
return(RADIO_ROGER);
default:
break;
}
/*
** Pass along the message to the default message handler in the radio itself.
*/
return(TechnoClass::Receive_Message(from, message, param));
}
#ifdef CHEAT_KEYS
/***********************************************************************************************
* BuildingClass::Debug_Dump -- Displays building status to the monochrome screen. *
* *
* This utility function will output the current status of the building class to the *
* monochrome screen. It is through this data that bugs may be fixed or detected. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/31/1994 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Debug_Dump(MonoClass * mono) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
mono->Set_Cursor(0, 0);
mono->Print(Text_String(TXT_DEBUG_BUILDING));
mono->Fill_Attrib(66, 13, 12, 1, IsRepairing ? MonoClass::INVERSE : MonoClass::NORMAL);
mono->Fill_Attrib(66, 14, 12, 1, IsToRebuild ? MonoClass::INVERSE : MonoClass::NORMAL);
mono->Fill_Attrib(66, 15, 12, 1, IsAllowedToSell ? MonoClass::INVERSE : MonoClass::NORMAL);
mono->Fill_Attrib(66, 16, 12, 1, IsCharging ? MonoClass::INVERSE : MonoClass::NORMAL);
mono->Fill_Attrib(66, 17, 12, 1, IsCharged ? MonoClass::INVERSE : MonoClass::NORMAL);
mono->Fill_Attrib(66, 18, 12, 1, IsJamming ? MonoClass::INVERSE : MonoClass::NORMAL);
mono->Fill_Attrib(66, 19, 12, 1, IsJammed ? MonoClass::INVERSE : MonoClass::NORMAL);
mono->Set_Cursor(1, 11);
if (Factory) {
mono->Printf("%s %d%%", Factory->Get_Object()->Class_Of().IniName, (100*Factory->Completion())/FactoryClass::STEP_COUNT);
}
TechnoClass::Debug_Dump(mono);
}
#endif
/***********************************************************************************************
* BuildingClass::Draw_It -- Displays the building at the location specified. *
* *
* This is the low level graphic routine that displays the building at the location *
* specified. *
* *
* INPUT: x,y -- The coordinate to draw the building at. *
* *
* window -- The clipping window to use. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/20/1994 JLB : Created. *
* 06/27/1994 JLB : Takes a clipping window parameter. *
* 07/06/1995 JLB : Handles damaged silos correctly. *
*=============================================================================================*/
void BuildingClass::Draw_It(int x, int y, WindowNumberType window) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
/*
** The shape file to use for rendering depends on whether the building
** is undergoing construction or not.
*/
void const * shapefile = Get_Image_Data();
if (shapefile == NULL) return;
/*
** Actually draw the building shape.
*/
IsTheaterShape = Class->IsTheater; //Let Build_Frame know if this is a theater specific shape
Techno_Draw_Object(shapefile, Shape_Number(), x, y, window);
IsTheaterShape = false;
/*
** Patch for adding overlay onto weapon factory. Only add the overlay if
** the building has more than 1 hp. Also, if the building's in radio
** contact, he must be unloading a constructed vehicle, so draw that
** vehicle before drawing the overlay.
*/
if (BState != BSTATE_CONSTRUCTION) {
/*
** A Tethered object is always rendered AFTER the building.
*/
if (*this == STRUCT_WEAP && IsTethered && In_Radio_Contact() && !Contact_With_Whom()->IsInLimbo && Contact_With_Whom()->What_Am_I() != RTTI_BUILDING) {
TechnoClass * contact = Contact_With_Whom();
assert(contact->IsActive);
int xxx = x + ((int)Lepton_To_Pixel((int)Coord_X(contact->Render_Coord())) - (int)Lepton_To_Pixel((int)Coord_X(Render_Coord())));
int yyy = y + ((int)Lepton_To_Pixel((int)Coord_Y(contact->Render_Coord())) - (int)Lepton_To_Pixel((int)Coord_Y(Render_Coord())));
contact->Draw_It(xxx, yyy, window);
contact->IsToDisplay = false;
}
/*
** Draw the weapon factory custom overlay graphic.
*/
if ( (*this == STRUCT_WEAP || *this == STRUCT_FAKEWEAP)) {
int shapenum = Door_Stage();
if (Health_Ratio() <= Rule.ConditionYellow) shapenum += 4;
// Added override shape file name. ST - 8/1/2019 5:24PM
//Techno_Draw_Object(Class->WarFactoryOverlay, shapenum, x, y, window);
Techno_Draw_Object_Virtual(Class->WarFactoryOverlay, shapenum, x, y, window, DIR_N, 0x0100, "WEAP2");
}
/*
** Draw any repair feedback graphic required.
*/
if (IsRepairing && IsWrenchVisible) {
CC_Draw_Shape(ObjectTypeClass::SelectShapes, SELECT_WRENCH, x, y, window, SHAPE_CENTER|SHAPE_WIN_REL);
}
}
TechnoClass::Draw_It(x, y, window);
/*
** If this is a factory that we're spying on, show what it's producing
*/
if ((Spied_By() & (1<<(PlayerPtr->Class->House)) && Is_Selected_By_Player()) || ((window == WINDOW_VIRTUAL) && (Session.Type != GAME_NORMAL))) {
/*
** Fetch the factory that is associate with this building. For computer controlled buildings, the
** factory pointer is integral to the building itself. For human controlled buildings, the factory
** pointer is part of the house structure and must be retrieved from there.
*/
FactoryClass * factory = NULL;
if (House->IsHuman) {
factory = House->Fetch_Factory(Class->ToBuild);
} else {
factory = Factory;
}
/*
** If there is a factory associated with this building, then fetch any attached
** object under production and display its cameo image over the top of this building.
*/
if (factory != NULL) {
TechnoClass * obj = factory->Get_Object();
if (obj != NULL) {
#ifdef FIXIT_CSII
CC_Draw_Shape(obj, obj->Techno_Type_Class()->Get_Cameo_Data(), 0, x, y, window, SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_NORMAL, NULL);
#else
void const * remapper = obj->House->Remap_Table(false, obj->Techno_Type_Class()->Remap);
CC_Draw_Shape(obj->Techno_Type_Class()->Get_Cameo_Data(), 0, x, y, window, SHAPE_CENTER|SHAPE_WIN_REL | ((remapper != NULL) ? SHAPE_FADING : SHAPE_NORMAL), remapper);
#endif
}
}
}
}
/***********************************************************************************************
* BuildingClass::Shape_Number -- Fetch the shape number for this building. *
* *
* This routine will examine the current state of the building and return with the shape *
* number to use. The shape number is subordinate to the building graphic image data. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the shape number to use when rendering this building. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/29/1996 JLB : Created. *
*=============================================================================================*/
int BuildingClass::Shape_Number(void) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
int shapenum = Fetch_Stage();
/*
** The shape file to use for rendering depends on whether the building
** is undergoing construction or not.
*/
if (BState == BSTATE_CONSTRUCTION) {
/*
** If the building is deconstructing, then the display frame progresses
** from the end to the beginning. Reverse the shape number accordingly.
*/
if (Mission == MISSION_DECONSTRUCTION) {
shapenum = (Class->Anims[BState].Start+Class->Anims[BState].Count-1)-shapenum;
}
} else {
/*
** If this is a camouflaged pill box and it is not owned by the player, then
** it is displayed with the MEGA-camouflaged imagery.
*/
if ((!IsOwnedByPlayer) && (*this == STRUCT_CAMOPILLBOX)) {
shapenum += 1;
}
/*
** The Tesla Coil has a stage value that can be overridden by
** its current state.
*/
if (*this == STRUCT_TESLA) {
if (IsCharged) {
shapenum = 3;
} else {
if (IsCharging) {
shapenum = Fetch_Stage();
} else {
shapenum = 0;
}
}
}
/*
** Buildings that contain a turret handle their shape determination
** differently than normal buildings. They need to take into consideration
** the direction the turret is facing.
*/
if (Class->IsTurretEquipped) {
shapenum = UnitClass::BodyShape[Dir_To_32(PrimaryFacing.Current())];
if (*this == STRUCT_SAM) {
/*
** SAM sites that are free to rotate fetch their animation frame
** from the building's turret facing. All other animation stages
** fetch their frame from the embedded animation sequencer.
*/
// if (Status == SAM_READY || Status == SAM_FIRING || Status == SAM_LOCKING) {
// shapenum = Fetch_Stage();
// }
if (Health_Ratio() <= Rule.ConditionYellow) {
shapenum += 35;
}
} else {
if (IsInRecoilState) {
shapenum += 32;
}
if (Health_Ratio() <= Rule.ConditionYellow) {
shapenum += 64;
}
}
} else {
/*
** If it is a significantly damaged weapons factory, it is shown in
** the worst state possible.
*/
if (*this == STRUCT_WEAP || *this == STRUCT_FAKEWEAP) {
shapenum = 0;
if (Health_Ratio() <= Rule.ConditionYellow) {
shapenum = 1;
}
} else {
/*
** Special render stage for silos. The stage is dependent on the current
** Tiberium collected as it relates to Tiberium capacity.
*/
if (*this == STRUCT_STORAGE) {
int level = 0;
if (House->Capacity) {
level = (House->Tiberium * 5) / House->Capacity;
}
shapenum += Bound(level, 0, 4);
if (Health_Ratio() <= Rule.ConditionYellow) {
shapenum += 5;
}
} else {
/*
** If below half strenth, then show the damage frames of the
** building.
*/
if (Health_Ratio() <= Rule.ConditionYellow) {
if (*this == STRUCT_CHRONOSPHERE) {
shapenum += 29;
} else {
int last1 = Class->Anims[BSTATE_IDLE].Start + Class->Anims[BSTATE_IDLE].Count;
int last2 = Class->Anims[BSTATE_ACTIVE].Start + Class->Anims[BSTATE_ACTIVE].Count;
int largest = max(last1, last2);
last2 = Class->Anims[BSTATE_AUX1].Start + Class->Anims[BSTATE_AUX1].Count;
largest = max(largest, last2);
last2 = Class->Anims[BSTATE_AUX2].Start + Class->Anims[BSTATE_AUX2].Count;
largest = max(largest, last2);
shapenum += largest;
}
}
}
}
}
}
return(shapenum);
}
/***********************************************************************************************
* BuildingClass::Mark -- Building interface to map rendering system. *
* *
* This routine is used to mark the map cells so that when it renders *
* the underlying icons will also be updated as necessary. *
* *
* INPUT: mark -- Type of image change (MARK_UP, _DOWN, _CHANGE) *
* MARK_UP -- Building is removed. *
* MARK_CHANGE -- Building changes shape. *
* MARK_DOWN -- Building is added. *
* *
* OUTPUT: bool; Did the mark operation succeed? Failure could be the result of marking down *
* when the building is already marked down, or visa versa. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/31/1994 JLB : Created. *
* 04/15/1994 JLB : Converted to member function. *
* 04/16/1994 JLB : Added health bar tracking. *
* 12/23/1994 JLB : Calls low level check before proceeding. *
* 01/27/1995 JLB : Special road spacer template added. *
*=============================================================================================*/
bool BuildingClass::Mark(MarkType mark)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (TechnoClass::Mark(mark)) {
short const * offset = Overlap_List();
short const * occupy = Occupy_List();
CELL cell = Coord_Cell(Coord);
SmudgeType bib;
switch (mark) {
case MARK_UP:
Map.Pick_Up(cell, this);
if (Class->Bib_And_Offset(bib, cell)) {
SmudgeClass * smudge = new SmudgeClass(bib);
if (smudge != NULL) {
smudge->Disown(cell);
delete smudge;
}
}
break;
case MARK_DOWN:
/*
** Special wall logic is handled here. A building that is really a wall
** gets converted into an overlay wall type when it is placed down. The
** actual building object itself is destroyed.
*/
if (Class->IsWall) {
switch (Class->Type) {
case STRUCT_BRICK_WALL:
new OverlayClass(OVERLAY_BRICK_WALL, cell, House->Class->House);
break;
case STRUCT_BARBWIRE_WALL:
new OverlayClass(OVERLAY_BARBWIRE_WALL, cell, House->Class->House);
break;
case STRUCT_SANDBAG_WALL:
new OverlayClass(OVERLAY_SANDBAG_WALL, cell, House->Class->House);
break;
case STRUCT_WOOD_WALL:
new OverlayClass(OVERLAY_WOOD_WALL, cell, House->Class->House);
break;
case STRUCT_CYCLONE_WALL:
new OverlayClass(OVERLAY_CYCLONE_WALL, cell, House->Class->House);
break;
case STRUCT_FENCE:
new OverlayClass(OVERLAY_FENCE, cell, House->Class->House);
break;
default:
break;
}
Transmit_Message(RADIO_OVER_OUT);
delete this;
} else {
if (Can_Enter_Cell(cell) == MOVE_OK) {
/*
** Determine if a bib is required for this building. If one is, then
** create and place it.
*/
CELL newcell = cell;
if (Class->Bib_And_Offset(bib, newcell)) {
new SmudgeClass(bib, Cell_Coord(newcell), Class->IsBase ? House->Class->House : HOUSE_NONE);
}
Map.Place_Down(cell, this);
} else {
return(false);
}
}
break;
case MARK_CHANGE_REDRAW:
Map.Refresh_Cells(cell, Overlap_List(true));
break;
default:
Map.Refresh_Cells(cell, Overlap_List(false));
Map.Refresh_Cells(cell, occupy);
break;
}
return(true);
}
return(false);
}
/***********************************************************************************************
* BuildingClass::AI -- Handles non-graphic AI processing for buildings. *
* *
* This function is to handle the AI logic for the building. The graphic logic (facing, *
* firing, and animation) is handled elsewhere. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/31/1994 JLB : Created. *
* 12/26/1994 JLB : Handles production. *
* 06/11/1995 JLB : Revamped. *
*=============================================================================================*/
void BuildingClass::AI(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
/*
** Process building animation state changes. Transition to a following state
** if there is one specified and the current animation sequence has expired.
** This process must occur before mission AI since the mission AI relies on
** the bstate change to occur immediately before the MissionClass::AI.
*/
Animation_AI();
/*
** If now is a good time to act on a new mission, then do so. This process occurs
** here because some outside event may have requested a mission change for the building.
** Such outside requests (player input) must be initiated BEFORE the normal AI process.
*/
if (IsReadyToCommence && BState != BSTATE_CONSTRUCTION) {
/*
** Clear the commencement flag ONLY if something actually occurred. By acting
** this way, a building can set the IsReadyToCommence flag before it goes
** to "sleep" knowing that it will wake up as soon as a new mission comes
** along.
*/
if (Commence()) {
IsReadyToCommence = false;
}
}
/*
** Proceed with normal logic processing. This is where the mission processing
** occurs. This call must be located after the animation sequence makes the
** transition to the next frame (see above) in order for the mission logic to
** act at the exact moment of graphic transition BEFORE it has a chance to
** be displayed.
*/
TechnoClass::AI();
/*
** Bail if the object died in the AI routine.
*/
if (!IsActive) {
return;
}
/*
** Building ammo is instantly reloaded.
*/
if (!Ammo) {
Ammo = Class->MaxAmmo;
}
/*
** If now is a good time to act on a new mission, then do so. This occurs here because
** some AI event may have requested a mission change (usually from another mission
** state machine). This must occur here before it has a chance to render.
*/
if (IsReadyToCommence) {
/*
** Clear the commencement flag ONLY if something actually occurred. By acting
** this way, a building can set the IsReadyToCommence flag before it goes
** to "sleep" knowing that it will wake up as soon as a new mission comes
** along.
*/
if (Commence()) {
IsReadyToCommence = false;
}
}
/*
** If a change of animation was requested, then make the change
** now. The building animation system acts independently but subordinate
** to the mission state machine system. By performing the animation change-up
** here, the mission AI system is ensured of immediate visual affect when it
** decides to change the animation state of the building.
*/
if (QueueBState != BSTATE_NONE) {
if (BState != QueueBState) {
BState = QueueBState;
BuildingTypeClass::AnimControlType const * ctrl = Fetch_Anim_Control();
if (BState == BSTATE_CONSTRUCTION || BState == BSTATE_IDLE) {
Set_Rate(Options.Normalize_Delay(ctrl->Rate));
} else {
Set_Rate(ctrl->Rate);
}
Set_Stage(ctrl->Start);
}
QueueBState = BSTATE_NONE;
}
/*
** If the building's strength has changed, then update the power
** accordingly.
*/
if (Strength != LastStrength) {
int oldpower = Power_Output();
LastStrength = Strength;
int newpower = Power_Output();
House->Adjust_Power(newpower - oldpower);
}
/*
** Check to see if the destruction countdown timer is active. If so, then decrement it.
** When this timer reaches zero, the building is removed from the map. All the explosions
** are presumed to be in progress at this time.
*/
if (Strength == 0) {
if (CountDown == 0) {
Limbo();
Drop_Debris(WhomToRepay);
delete this;
}
return;
}
/*
** Charging logic.
*/
Charging_AI();
/*
** Handle any repair process that may be going on.
*/
Repair_AI();
/*
** For computer controlled buildings, determine what should be produced and start
** production accordingly.
*/
Factory_AI();
/*
** Check for demolition timeout. When timeout has expired, the building explodes.
*/
if (IsGoingToBlow && CountDown == 0) {
/*
** Maybe trigger an achivement. ST - 11/14/2019 1:53PM
*/
TechnoTypeClass const *object_type = Techno_Type_Class();
if (object_type) {
TechnoClass *saboteur = As_Techno(WhomToRepay);
if (saboteur && saboteur->IsActive && saboteur->House && saboteur->House->IsHuman) {
On_Achievement_Event(saboteur->House, "BUILDING_DESTROYED_C4", object_type->IniName);
}
}
int damage = Strength;
Take_Damage(damage, 0, WARHEAD_FIRE, As_Techno(WhomToRepay), true);
if (!IsActive) {
return;
}
Mark(MARK_CHANGE);
}
/*
** Turret equiped buildings must handle turret rotation logic here. This entails
** rotating the turret to the desired facing as well as figuring out what that
** desired facing should be.
*/
Rotation_AI();
/*
** Gap Generators need to scan if they've just become activated, or if
** the power has just come on enough so they can scan. Also, they need
** to un-jam if the power has just dropped off.
*/
if (*this == STRUCT_GAP) {
if (Arm == 0) {
IsJamming = false;
Arm = TICKS_PER_MINUTE * Rule.GapRegenInterval + Random_Pick(1, TICKS_PER_SECOND);
}
if (!IsJamming) {
if (House->Power_Fraction() >= 1) {
Map.Jam_From(Coord_Cell(Center_Coord()), Rule.GapShroudRadius, House);
IsJamming = true;
}
} else {
if (House->Power_Fraction() < 1) {
IsJamming = false;
Map.UnJam_From(Coord_Cell(Center_Coord()), Rule.GapShroudRadius, House);
}
}
}
/*
** Radar facilities and SAMs need to check for the proximity of a mobile
** radar jammer.
*/
if ((*this == STRUCT_RADAR || *this == STRUCT_SAM) && (Frame % TICKS_PER_SECOND) == 0) {
IsJammed = false;
for (int index = 0; index < Units.Count(); index++) {
UnitClass * obj = Units.Ptr(index);
if (obj != NULL &&
!obj->IsInLimbo &&
!obj->House->Is_Ally(House) &&
obj->Class->IsJammer &&
Distance(obj) <= Rule.RadarJamRadius) {
IsJammed = true;
break;
}
}
}
/*
** Chronosphere active animation control.
*/
if (*this == STRUCT_CHRONOSPHERE && BState == BSTATE_ACTIVE && QueueBState == BSTATE_NONE && Scen.FadeTimer == 0) {
Begin_Mode(BSTATE_IDLE);
}
}
/***********************************************************************************************
* BuildingClass::Unlimbo -- Removes a building from limbo state. *
* *
* Use this routine to transform a building that has been held in limbo *
* state, into one that really exists on the map. Once a building as *
* been unlimboed, then it becomes a normal object in the game world. *
* *
* INPUT: pos -- The position to place the building on the map. *
* *
* dir (optional) -- not used for this class *
* *
* OUTPUT: bool; Was the unlimbo successful? *
* *
* WARNINGS: The unlimbo operation might not be successful if the *
* building could not be placed at the location specified. *
* *
* HISTORY: *
* 04/16/1994 JLB : Created. *
* 06/07/1994 JLB : Matches virtual function format for base class. *
* 05/09/1995 JLB : Handles wall placement. *
* 06/18/1995 JLB : Checks for wall legality before placing down. *
*=============================================================================================*/
bool BuildingClass::Unlimbo(COORDINATE coord, DirType dir)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
/*
** If this is a wall type building, then it never gets unlimboed. Instead, it gets
** converted to an overlay type.
*/
if (Class->IsWall) {
if (Can_Enter_Cell(Coord_Cell(coord), FACING_NONE) == MOVE_OK) {
OverlayType otype = OVERLAY_NONE;
switch (Class->Type) {
case STRUCT_SANDBAG_WALL:
otype = OVERLAY_SANDBAG_WALL;
break;
case STRUCT_CYCLONE_WALL:
otype = OVERLAY_CYCLONE_WALL;
break;
case STRUCT_BRICK_WALL:
otype = OVERLAY_BRICK_WALL;
break;
case STRUCT_BARBWIRE_WALL:
otype = OVERLAY_BARBWIRE_WALL;
break;
case STRUCT_WOOD_WALL:
otype = OVERLAY_WOOD_WALL;
break;
case STRUCT_FENCE:
otype = OVERLAY_FENCE;
break;
default:
otype = OVERLAY_NONE;
break;
}
if (otype != OVERLAY_NONE) {
ObjectClass * o = OverlayTypeClass::As_Reference(otype).Create_One_Of(House);
if (o && o->Unlimbo(coord)) {
Map[coord].Owner = House->Class->House;
Transmit_Message(RADIO_OVER_OUT);
Map.Sight_From(Coord_Cell(coord), Class->SightRange, House);
delete this;
return(true);
}
}
}
return(false);
}
/*
** Normal building unlimbo process.
*/
if (TechnoClass::Unlimbo(coord, dir)) {
/*
** Ensure that the owning house knows about the
** new object.
*/
House->BScan |= (1L << Class->Type);
House->ActiveBScan |= (1L << Class->Type);
/*
** Recalculate the center point of the house's base.
*/
House->Recalc_Center();
/*
** Update the total factory type, assuming this building has a factory.
*/
House->Active_Add(this);
/*
** Possibly the sidebar will be affected by this addition.
*/
House->IsRecalcNeeded = true;
LastStrength = 0;
// Changes to support client/server multiplayer. ST - 8/2/2019 2:36PM
//if ((!IsDiscoveredByPlayer && Map[coord].IsVisible) || Session.Type != GAME_NORMAL) {
if ((!Is_Discovered_By_Player(House) && Map[coord].Is_Visible(House)) || Session.Type != GAME_NORMAL) {
if (House->IsHuman) {
//Revealed(PlayerPtr);
Revealed(House);
}
}
if (!House->IsHuman) {
Revealed(House);
}
// Changes to support client/server multiplayer. ST - 8/2/2019 2:36PM
//if (IsOwnedByPlayer) {
if (Is_Owned_By_Player()) {
Map.PowerClass::IsToRedraw = true;
Map.Flag_To_Redraw(false);
}
if ((Class->Ownable & (HOUSEF_GOOD | HOUSEF_BAD)) != (HOUSEF_GOOD | HOUSEF_BAD)) {
if (Class->Ownable & HOUSEF_GOOD) {
ActLike = HOUSE_GREECE;
} else {
ActLike = HOUSE_USSR;
}
}
return(true);
}
return(false);
}
/***********************************************************************************************
* BuildingClass::Take_Damage -- Inflicts damage points upon a building. *
* *
* This routine will inflict damage points upon the specified building. *
* It will handle the damage animation and building destruction. Use *
* this routine whenever a building is attacked. *
* *
* INPUT: damage -- Amount of damage to inflict. *
* *
* distance -- The distance from the damage center point to the object's center point.*
* *
* warhead -- The kind of damage to inflict. *
* *
* source -- The source of the damage. This is used to change targeting. *
* *
* forced -- Is the damage forced upon the object regardless of whether it *
* is normally immune? *
* *
* OUTPUT: true/false; Was the building destroyed? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/21/1991 : Created. *
* 04/15/1994 JLB : Converted to member function. *
* 04/16/1994 JLB : Added warhead modifier to damage. *
* 06/03/1994 JLB : Added source of damage as target value. *
* 06/20/1994 JLB : Source is a base class pointer. *
* 11/22/1994 JLB : Shares base damage handler for techno objects. *
* 07/15/1995 JLB : Power ratio gets adjusted. *
*=============================================================================================*/
ResultType BuildingClass::Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source, bool forced)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
ResultType res = RESULT_NONE;
int shakes;
if (this != source /*&& !Class->IsInsignificant*/) {
if (source) {
House->LATime = Frame;
House->LAType = source->What_Am_I();
House->LAZone = House->Which_Zone(this);
House->LAEnemy = source->Owner();
if (!House->Is_Ally(source)) {
House->Enemy = source->Owner();
}
Base_Is_Attacked(source);
}
short const * offset = Occupy_List();
/*
** Memorize who they used to be in radio contact with.
*/
TechnoClass *tech = Contact_With_Whom();
/*
** Perform the low level damage assessment.
*/
res = TechnoClass::Take_Damage(damage, distance, warhead, source, forced);
switch (res) {
case RESULT_DESTROYED:
/*
** Add the building to the base prebuild list if allowed. This will force
** the computer to rebuild this structure if it can.
*/
if (IsToRebuild && Class->Level != -1 && Base.House == House->Class->House && Base.Get_Node(this) == 0) {
// if (IsToRebuild && Class->IsBuildable && Base.House == House->Class->House && Base.Get_Node(this) == 0) {
Base.Nodes.Add(BaseNodeClass(Class->Type, Coord_Cell(Coord)));
}
/*
** Destroy all attached objects.
*/
while (Attached_Object()) {
FootClass * obj = Detach_Object();
Detach_All(true);
delete obj;
}
/*
** If we were in contact with a landed plane, blow the plane up too.
*/
if (tech && tech->IsActive && tech->What_Am_I() == RTTI_AIRCRAFT && ((AircraftClass *)tech)->Class->IsFixedWing && ((AircraftClass *)tech)->In_Which_Layer() == LAYER_GROUND) {
int damage = 500;
tech->Take_Damage(damage, 0, WARHEAD_AP, source, forced);
}
Sound_Effect(VOC_KABOOM22, Coord);
while (*offset != REFRESH_EOL) {
CELL cell = Coord_Cell(Coord) + *offset++;
/*
** If the building is destroyed, then lots of
** explosions occur.
*/
new SmudgeClass(Random_Pick(SMUDGE_CRATER1, SMUDGE_CRATER6), Cell_Coord(cell));
if (Percent_Chance(50)) {
new AnimClass(ANIM_FIRE_SMALL, Coord_Scatter(Cell_Coord(cell), 0x0080), Random_Pick(0, 7), Random_Pick(1, 3));
if (Percent_Chance(50)) {
new AnimClass(ANIM_FIRE_MED, Coord_Scatter(Cell_Coord(cell), 0x0040), Random_Pick(0, 7), Random_Pick(1, 3));
}
}
new AnimClass(ANIM_FBALL1, Coord_Scatter(Cell_Coord(cell), 0x0040), Random_Pick(0, 3));
}
shakes = Class->Cost_Of() / 400;
if (shakes) {
Shake_The_Screen(shakes, Owner());
if (source && Owner() != source->Owner()) {
Shake_The_Screen(shakes, source->Owner());
}
}
Sound_Effect(VOC_CRUMBLE, Coord);
if (Mission == MISSION_DECONSTRUCTION) {
CountDown = 0;
Set_Rate(0);
} else {
CountDown = 8;
}
/*
** If it is in radio contact and the object seems to be attached, then tell
** it to run away.
*/
if (In_Radio_Contact() && Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER) {
Transmit_Message(RADIO_RUN_AWAY);
}
/*
** A force destruction will not generate survivors.
*/
if (forced || *this == STRUCT_KENNEL) {
IsSurvivorless = true;
}
/*
** Destruction of a radar facility or advanced communications
** center will cause the spiedby field to change...
*/
if (SpiedBy) {
SpiedBy = 0;
StructType struc = *this;
if (struc == STRUCT_RADAR /* || struc == STRUCT_EYE */) {
Update_Radar_Spied();
}
}
/*
** Destruction of a gap generator will cause the cells it affects
** to stop being jammed.
*/
if (*this == STRUCT_GAP) {
Remove_Gap_Effect();
}
/*
** Destruction of a shipyard or sub pen may cause attached ships
** who are repairing themselves to discontinue repairs.
*/
if (*this == STRUCT_SHIP_YARD || *this == STRUCT_SUB_PEN) {
for (int index = 0; index < Vessels.Count(); index++) {
VesselClass *obj = Vessels.Ptr(index);
if (obj && !obj->IsInLimbo && obj->House == House) {
if (obj->IsSelfRepairing) {
if (::Distance(Center_Coord(), obj->Center_Coord()) < 0x0200) {
obj->IsSelfRepairing = false;
obj->IsToSelfRepair = false;
}
}
}
}
}
/*
** Destruction of a barrel will cause the surrounding squares to
** be hit with damage.
*/
if (*this == STRUCT_BARREL || *this == STRUCT_BARREL3) {
COORDINATE center = Center_Coord();
CELL cellcenter = Coord_Cell(center);
BulletClass * bullet;
bullet = new BulletClass(BULLET_INVISIBLE, ::As_Target(Adjacent_Cell(cellcenter, FACING_N)), 0, 200, WARHEAD_FIRE, MPH_MEDIUM_FAST);
if (bullet) {
bullet->Unlimbo(center, DIR_N);
}
bullet = new BulletClass(BULLET_INVISIBLE, ::As_Target(Adjacent_Cell(cellcenter, FACING_E)), 0, 200, WARHEAD_FIRE, MPH_MEDIUM_FAST);
if (bullet) {
bullet->Unlimbo(center, DIR_E);
}
bullet = new BulletClass(BULLET_INVISIBLE, ::As_Target(Adjacent_Cell(cellcenter, FACING_S)), 0, 200, WARHEAD_FIRE, MPH_MEDIUM_FAST);
if (bullet) {
bullet->Unlimbo(center, DIR_S);
}
bullet = new BulletClass(BULLET_INVISIBLE, ::As_Target(Adjacent_Cell(cellcenter, FACING_W)), 0, 200, WARHEAD_FIRE, MPH_MEDIUM_FAST);
if (bullet) {
bullet->Unlimbo(center, DIR_W);
}
}
if (House) {
House->Check_Pertinent_Structures();
}
break;
case RESULT_HALF:
if (*this == STRUCT_PUMP) {
AnimClass * anim = new AnimClass(ANIM_OILFIELD_BURN, Coord_Add(Coord, 0x00400130L), 1);
if (anim) {
anim->Attach_To(this);
}
}
// Fall into next case.
case RESULT_MAJOR:
Sound_Effect(VOC_KABOOM1, Coord);
while (*offset != REFRESH_EOL) {
CELL cell = Coord_Cell(Coord) + *offset++;
AnimClass * anim = NULL;
/*
** Show pieces of fire to indicate that a significant change in
** damage level has occurred.
*/
if (warhead == WARHEAD_FIRE) {
switch (Random_Pick(0, 5+Class->Width()+Class->Height())) {
case 0:
break;
case 1:
case 2:
case 3:
case 4:
case 5:
anim = new AnimClass(ANIM_ON_FIRE_SMALL, Coord_Scatter(Cell_Coord(cell), 0x0060), 0, Random_Pick(1, 3));
break;
case 6:
case 7:
case 8:
anim = new AnimClass(ANIM_ON_FIRE_MED, Coord_Scatter(Cell_Coord(cell), 0x0060), 0, Random_Pick(1, 3));
break;
case 9:
anim = new AnimClass(ANIM_ON_FIRE_BIG, Coord_Scatter(Cell_Coord(cell), 0x0060), 0, 1);
break;
default:
break;
}
} else {
if (Percent_Chance(50)) {
/*
** Building may catch on fire, but only if it wasn't a
** renovator that caused the damage.
*/
if (source == NULL || source->What_Am_I() != RTTI_INFANTRY || *(InfantryClass *)source != INFANTRY_RENOVATOR) {
anim = new AnimClass(ANIM_FIRE_SMALL, Coord_Scatter(Cell_Coord(cell), 0x0060), Random_Pick(0, 7), Random_Pick(1, 3));
}
}
}
/*
** If the animation was created, then attach it to the building.
*/
if (anim) {
anim->Attach_To(this);
}
}
break;
case RESULT_NONE:
break;
case RESULT_LIGHT:
break;
}
if (source && res != RESULT_NONE) {
/*
** If any damage occurred, then inform the house of this fact. If it is the player's
** house, it might announce this fact.
*/
if (!Class->IsInsignificant) {
House->Attacked(this);
}
/*
** Save the type of the house that's doing the damage, so if the building burns
** to death credit can still be given for the kill
*/
WhoLastHurtMe = source->Owner();
/*
** When certain buildings are hit, they "snap out of it" and
** return fire if they are able and allowed.
*/
if (*this != STRUCT_SAM && *this != STRUCT_AAGUN &&
!House->Is_Ally(source) &&
Class->PrimaryWeapon != NULL &&
(!Target_Legal(TarCom) || !In_Range(TarCom))) {
if (source->What_Am_I() != RTTI_AIRCRAFT && (!House->IsHuman || Rule.IsSmartDefense)) {
Assign_Target(source->As_Target());
} else {
/*
** Generate a random rotation effect since there is nothing else that this
** building can do.
*/
if (!PrimaryFacing.Is_Rotating()) {
PrimaryFacing.Set_Desired(Random_Pick(DIR_N, DIR_MAX));
}
}
}
}
}
return(res);
}
/***********************************************************************************************
* BuildingClass::new -- Allocates a building object from building pool. *
* *
* This routine will allocate a building slot from the building alloc *
* system. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with a pointer to the allocated building. If NULL is *
* returned, then this indicates a failure to allocate. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 04/11/1994 JLB : Created. *
* 04/21/1994 JLB : Converted to operator new. *
* 05/17/1994 JLB : Revamped allocation scheme *
* 07/29/1994 JLB : Simplified. *
*=============================================================================================*/
void * BuildingClass::operator new(size_t )
{
void * ptr = Buildings.Allocate();
if (ptr) {
((BuildingClass *)ptr)->Set_Active();
}
return(ptr);
}
/***********************************************************************************************
* BuildingClass::delete -- Deallocates building object. *
* *
* This is the memory deallocation operation for a building object. *
* Since buildings are allocated out of a fixed memory block, all that *
* is needed is to flag the unit as inactive. *
* *
* INPUT: ptr -- Pointer to building to deallocate. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 04/21/1994 JLB : Created. *
*=============================================================================================*/
void BuildingClass::operator delete(void *ptr)
{
if (ptr) {
((BuildingClass *)ptr)->IsActive = false;
}
Buildings.Free((BuildingClass *)ptr);
}
/***********************************************************************************************
* BuildingClass::BuildingClass -- Constructor for buildings. *
* *
* This routine inserts a building into the object tracking system. *
* It is placed into a limbo state unless a location is provided for *
* it to unlimbo at. *
* *
* INPUT: type -- The structure type to make this object. *
* *
* house -- The owner of this building. *
* *
* pos -- The position to unlimbo the building. If -1 is *
* specified, then the building remains in a limbo *
* state. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 04/21/1994 JLB : Created. *
* 08/07/1995 JLB : Fixed act like value to match expected value. *
*=============================================================================================*/
BuildingClass::BuildingClass(StructType type, HousesType house) :
TechnoClass(RTTI_BUILDING, Buildings.ID(this), house),
Class(BuildingTypes.Ptr((int)type)),
Factory(0),
ActLike(House->ActLike),
IsToRebuild(false),
IsToRepair(false),
IsAllowedToSell(true),
IsReadyToCommence(false),
IsRepairing(false),
IsWrenchVisible(false),
IsGoingToBlow(false),
IsSurvivorless(false),
IsCharging(false),
IsCharged(false),
IsCaptured(false),
IsJamming(false),
IsJammed(false),
HasFired(false),
HasOpened(false),
CountDown(0),
BState(BSTATE_NONE),
QueueBState(BSTATE_NONE),
WhoLastHurtMe(house),
WhomToRepay(TARGET_NONE),
AnimToTrack(TARGET_NONE),
LastStrength(0),
PlacementDelay(0)
{
House->Tracking_Add(this);
IsSecondShot = !Class->Is_Two_Shooter();
Strength = Class->MaxStrength;
Ammo = Class->MaxAmmo;
/*
** If the building could never be built, then it can never be sold either. This
** is due to the lack of buildup animation.
*/
if (Class->Get_Buildup_Data() != NULL) {
// if (!Class->IsBuildable) {
IsAllowedToSell = false;
}
// if (Session.Type == GAME_INTERNET) {
// House->BuildingTotals->Increment_Unit_Total( (int) type);
// }
}
/***********************************************************************************************
* BuildingClass::~BuildingClass -- Destructor for building type objects. *
* *
* This destructor for building objects will put the building in limbo if possible. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 01/18/1995 JLB : Created. *
*=============================================================================================*/
BuildingClass::~BuildingClass(void)
{
if (GameActive && Class) {
if (House) {
House->Tracking_Remove(this);
}
BuildingClass::Limbo();
}
Class = 0;
delete (FactoryClass *)Factory;
Factory = 0;
ID = -1;
}
/***********************************************************************************************
* BuildingClass::Drop_Debris -- Drops rubble when building is destroyed. *
* *
* This routine is called when a building is destroyed. It handles *
* placing the rubble down. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/14/1994 JLB : Created. *
* 06/13/1995 JLB : Added smoke and normal infantry survivor possibility. *
* 07/16/1995 JLB : Survival rate depends on if captured or sabotaged. *
*=============================================================================================*/
void BuildingClass::Drop_Debris(TARGET source)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
CELL const * offset;
CELL cell;
/*
** Generate random survivors from the destroyed building.
*/
cell = Coord_Cell(Coord);
offset = Occupy_List();
int odds = 2;
if (Target_Legal(WhomToRepay)) odds -= 1;
if (IsCaptured) odds += 6;
int count = How_Many_Survivors();
while (*offset != REFRESH_EOL) {
CELL newcell;
newcell = cell + *offset++;
CellClass const * cellptr = &Map[newcell];
/*
** Infantry could run out of a destroyed building.
*/
if (!House->IsToDie && count > 0) {
InfantryClass * i = NULL;
if (Random_Pick(0, odds) == 1) {
i = NULL;
InfantryType typ = Crew_Type();
if (typ != INFANTRY_NONE) i = new InfantryClass(typ, House->Class->House);
if (i != NULL) {
if (Class->Get_Buildup_Data() != NULL && i->Class->IsNominal) i->IsTechnician = true;
ScenarioInit++;
if (i->Unlimbo(Cell_Coord(newcell), DIR_N)) {
count--;
i->Strength = Random_Pick(5, (int)i->Class->MaxStrength);
i->Scatter(0, true);
if (source != TARGET_NONE && !House->Is_Ally(As_Object(source))) {
i->Assign_Mission(MISSION_ATTACK);
i->Assign_Target(source);
} else {
if (House->IsHuman) {
i->Assign_Mission(MISSION_GUARD);
} else {
i->Assign_Mission(MISSION_HUNT);
}
}
} else {
delete i;
}
ScenarioInit--;
}
}
}
/*
** Smoke and fire only appear on terrestrail cells. They should not appear on
** rivers, clifs, or water cells.
*/
if (cellptr->Is_Clear_To_Move(SPEED_TRACK, true, true)) {
/*
** Possibly add some smoke rising from the ashes of the building.
*/
switch (Random_Pick(0, 5)) {
case 0:
case 1:
case 2:
new AnimClass(ANIM_SMOKE_M, Coord_Scatter(Cell_Coord(newcell), 0x0050, false), Random_Pick(0, 5), Random_Pick(1, 2));
break;
default:
break;
}
/*
** The building always scars the ground in some fashion.
*/
if (Percent_Chance(25)) {
new SmudgeClass(Random_Pick(SMUDGE_SCORCH1, SMUDGE_SCORCH6), Cell_Coord(newcell));
} else {
new SmudgeClass(Random_Pick(SMUDGE_CRATER1, SMUDGE_CRATER6), Coord_Scatter(Cell_Coord(newcell), 0x0080, false));
}
}
}
}
/***********************************************************************************************
* BuildingClass::Active_Click_With -- Handles clicking on the map while the building is selected.*
* *
* This interface routine handles when the player clicks on the map while this building *
* is currently selected. This is used to assign an override target to a turret or *
* guard tower. *
* *
* INPUT: target -- The target that was clicked upon. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/28/1994 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Active_Click_With(ActionType action, ObjectClass * object)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (action == ACTION_ATTACK && object != NULL) {
Player_Assign_Mission(MISSION_ATTACK, object->As_Target());
}
if (action == ACTION_TOGGLE_PRIMARY && Class->Is_Factory()) {
OutList.Add(EventClass(EventClass::PRIMARY, TargetClass(this)));
}
}
/***********************************************************************************************
* BuildingClass::Active_Click_With -- Handles cell selection for buildings. *
* *
* This routine really only serves one purpose -- to allow targeting of the ground for *
* buildings that are equipped with weapons. *
* *
* INPUT: action -- The requested action to perform. *
* *
* cell -- The cell location to perform the action upon. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/04/1995 JLB : Created. *
* 10/04/1995 JLB : Handles construction yard undeploy to move logic. *
*=============================================================================================*/
void BuildingClass::Active_Click_With(ActionType action, CELL cell)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (action == ACTION_ATTACK) {
Player_Assign_Mission(MISSION_ATTACK, ::As_Target(cell));
}
if (action == ACTION_MOVE && *this == STRUCT_CONST) {
OutList.Add(EventClass(EventClass::ARCHIVE, TargetClass(this), TargetClass(::As_Target(cell))));
OutList.Add(EventClass(EventClass::SELL, TargetClass(this)));
COORDINATE coord = Map.Pixel_To_Coord(Get_Mouse_X(), Get_Mouse_Y());
OutList.Add(EventClass(ANIM_MOVE_FLASH, PlayerPtr->Class->House, coord, 1 << PlayerPtr->Class->House));
}
}
/***********************************************************************************************
* BuildingClass::Assign_Target -- Assigns a target to the building. *
* *
* Assigning of a target to a building makes sense if the building is one that can attack. *
* This routine would be used to assign the attack target to a turret or guard tower. *
* *
* INPUT: target -- The target that was clicked on while this building was selected. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/28/1994 JLB : Created. *
* 11/02/1994 JLB : Checks for range before assigning target. *
*=============================================================================================*/
void BuildingClass::Assign_Target(TARGET target)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (*this != STRUCT_SAM && *this != STRUCT_AAGUN && !In_Range(target, 0)) {
target = TARGET_NONE;
}
TechnoClass::Assign_Target(target);
}
/***********************************************************************************************
* BuildingClass::Init -- Initialize the building system to an empty null state. *
* *
* This routine initializes the building system in preparation for a scenario load. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 09/19/1994 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Init(void)
{
Buildings.Free_All();
}
/***********************************************************************************************
* BuildingClass::Exit_Object -- Initiates an object to leave the building. *
* *
* This function is used to cause an object to exit the building. It is called when a *
* factory produces a vehicle or other mobile object and that object needs to exit the *
* building to join the ranks of a regular unit. Typically, the object is placed down on *
* the map such that it overlaps the building and then it is given a movement order so that *
* it will move to an adjacent free cell. *
* *
* INPUT: base -- Pointer to the object that is to exit the building. *
* *
* OUTPUT: Returns the success rating for the exit attempt; *
* 0 = complete failure (refund money please) *
* 1 = temporarily prevented (try again later please) *
* 2 = successful *
* *
* WARNINGS: The building is placed in radio contact with the object. The object is in a *
* tethered condition. This condition will be automatically broken when the *
* object reaches the adjacent square. *
* *
* HISTORY: *
* 11/28/1994 JLB : Created. *
* 04/10/1995 JLB : Handles building production by computer. *
* 06/17/1995 JLB : Handles refinery exit. *
*=============================================================================================*/
int BuildingClass::Exit_Object(TechnoClass * base)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (!base) return(0);
TechnoTypeClass const * ttype = (TechnoTypeClass const *)&base->Class_Of();
/*
** A unit exiting a building is always considered to be "locked". That means, it
** will be considered as to have legally entered the visible map domain.
*/
base->IsLocked = true;
/*
** Find a good cell to unload the object to. The object, probably a vehicle
** will drive/walk to the adjacent free cell.
*/
CELL cell = 0;
switch (base->What_Am_I()) {
case RTTI_AIRCRAFT:
if (!In_Radio_Contact()) {
AircraftClass * air = (AircraftClass *)base;
air->Height = 0;
ScenarioInit++;
if (air->Unlimbo(Docking_Coord(), air->Pose_Dir())) {
Transmit_Message(RADIO_HELLO, air);
Transmit_Message(RADIO_TETHER);
ScenarioInit--;
return(2);
}
ScenarioInit--;
} else {
AircraftClass * air = (AircraftClass *)base;
if (Cell_X(Coord_Cell(Center_Coord())) - Map.MapCellX < Map.MapCellWidth/2) {
cell = XY_Cell(Map.MapCellX-1, Random_Pick(0, Map.MapCellHeight-1)+Map.MapCellY);
} else {
cell = XY_Cell(Map.MapCellX+Map.MapCellWidth, Random_Pick(0, Map.MapCellHeight-1)+Map.MapCellY);
}
ScenarioInit++;
if (air->Unlimbo(Cell_Coord(cell), DIR_N)) {
//BG air->Assign_Destination(::As_Target(Nearby_Location(air)));
/*BG*/ air->Assign_Destination(::As_Target(air->Nearby_Location(this)));
air->Assign_Mission(MISSION_MOVE);
ScenarioInit--;
return(2);
}
ScenarioInit--;
}
break;
case RTTI_VESSEL:
switch (Class->Type) {
case STRUCT_SUB_PEN:
case STRUCT_SHIP_YARD:
ScenarioInit++;
cell = Find_Exit_Cell(base);
if (cell != 0 && base->Unlimbo(Cell_Coord(cell), Direction(Cell_Coord(cell)))) {
base->Assign_Mission(MISSION_GUARD);
ScenarioInit--;
return(2);
}
ScenarioInit--;
break;
default:
break;
}
break;
case RTTI_INFANTRY:
case RTTI_UNIT:
switch (Class->Type) {
case STRUCT_REFINERY:
if (base->What_Am_I() == RTTI_UNIT) {
cell = Coord_Cell(Center_Coord());
UnitClass * unit = (UnitClass *)base;
cell = Adjacent_Cell(cell, FACING_SW);
ScenarioInit++;
if (unit->Unlimbo(Cell_Coord(Adjacent_Cell(cell, DIR_S)), DIR_SW_X2)) {
unit->PrimaryFacing = DIR_S;
unit->Assign_Mission(MISSION_HARVEST);
}
ScenarioInit--;
} else {
base->Scatter(0, true);
}
break;
case STRUCT_WEAP:
if (Mission == MISSION_UNLOAD) {
for(int index = 0; index < Buildings.Count(); index++) {
BuildingClass *bldg = Buildings.Ptr(index);
if (bldg->Owner() == Owner() && *bldg == STRUCT_WEAP && bldg != this && bldg->Mission == MISSION_GUARD && !bldg->Factory) {
FactoryClass * temp = Factory;
bldg->Factory = Factory;
Factory = 0;
int retval = (bldg->Exit_Object(base));
bldg->Factory = 0;
Factory = temp;
return(retval);
}
}
return(1); // fail while we're still unloading previous
}
ScenarioInit++;
if (base->Unlimbo(Exit_Coord(), DIR_S)) {
base->Mark(MARK_UP);
base->Coord = Exit_Coord();
base->Mark(MARK_DOWN);
Transmit_Message(RADIO_HELLO, base);
Transmit_Message(RADIO_TETHER);
Assign_Mission(MISSION_UNLOAD);
ScenarioInit--;
return(2);
}
ScenarioInit--;
break;
case STRUCT_BARRACKS:
case STRUCT_TENT:
case STRUCT_KENNEL:
cell = Find_Exit_Cell(base);
if (cell != 0) {
DirType dir = Direction(cell);
COORDINATE start = Exit_Coord();
ScenarioInit++;
if (base->Unlimbo(start, dir)) {
base->Assign_Mission(MISSION_MOVE);
/*
** When disembarking from a transport then guard an area around the
** center of the base.
*/
base->Assign_Destination(::As_Target(cell));
if (House->IQ >= Rule.IQGuardArea) {
base->Assign_Mission(MISSION_GUARD_AREA);
base->ArchiveTarget = ::As_Target(House->Where_To_Go((FootClass *)base));
}
/*
** Establish radio contact so unload coordination can occur. This
** radio contact should always succeed.
*/
if (Transmit_Message(RADIO_HELLO, base) == RADIO_ROGER) {
Transmit_Message(RADIO_UNLOAD);
}
ScenarioInit--;
return(2);
}
ScenarioInit--;
}
break;
default:
cell = Find_Exit_Cell(base);
if (cell != 0) {
DirType dir = Direction(cell);
COORDINATE start = Exit_Coord();
ScenarioInit++;
if (base->Unlimbo(start, dir)) {
base->Assign_Mission(MISSION_MOVE);
/*
** When disembarking from a transport then guard an area around the
** center of the base.
*/
base->Assign_Destination(::As_Target(cell));
if (House->IQ >= Rule.IQGuardArea) {
base->Assign_Mission(MISSION_GUARD_AREA);
base->ArchiveTarget = ::As_Target(House->Where_To_Go((FootClass *)base));
}
ScenarioInit--;
return(2);
}
ScenarioInit--;
}
break;
}
break;
case RTTI_BUILDING:
if (!House->IsHuman) {
/*
** Find the next available spot to place this newly created building. If the
** building could be placed at the desired location, fine. If not, then this
** routine will return failure. The calling routine will probably abandon this
** building in preference to building another.
*/
BaseNodeClass * node = Base.Next_Buildable(((BuildingClass *)base)->Class->Type);
COORDINATE coord = 0;
if (node) {
coord = Cell_Coord(node->Cell);
} else {
/*
** Find a suitable new spot to place.
*/
coord = House->Find_Build_Location((BuildingClass *)base);
}
if (coord) {
if (Flush_For_Placement(base, Coord_Cell(coord))) {
return(1);
}
if (base->Unlimbo(coord)) {
if (node && ((BuildingClass *)base)->Class->Type == House->BuildStructure) {
House->BuildStructure = STRUCT_NONE;
}
return(2);
}
}
}
break;
default:
break;
}
/*
** Failure to exit the object results in a false return value.
*/
return(0);
}
/***********************************************************************************************
* BuildingClass::Update_Buildables -- Informs sidebar of additional construction options. *
* *
* This routine will tell the sidebar of objects that can be built. The function is called *
* whenever a building matures. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 11/11/1994 JLB : Created. *
* 12/23/1994 JLB : Only updates for PLAYER buildings. *
*=============================================================================================*/
void BuildingClass::Update_Buildables(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
/*
** Only do this for real human players. ST - 3/22/2019 1:38PM
*/
if (PlayerPtr != House) {
if (Session.Type != GAME_GLYPHX_MULTIPLAYER || House->IsHuman == false) {
return;
}
}
bool buildable_via_capture = (IsCaptured && ActLike != House->ActLike) ? true : false;
if (!IsInLimbo && Is_Discovered_By_Player()) {
switch (Class->ToBuild) {
int i;
int u;
int f;
int a;
int v;
case RTTI_VESSELTYPE:
for (v = VESSEL_FIRST; v < VESSEL_COUNT; v++) {
if (PlayerPtr->Can_Build(&VesselTypeClass::As_Reference((VesselType)v), ActLike)) {
if (Session.Type == GAME_GLYPHX_MULTIPLAYER) {
Sidebar_Glyphx_Add(RTTI_VESSELTYPE, v, House, buildable_via_capture);
} else {
Map.Add(RTTI_VESSELTYPE, v, buildable_via_capture);
}
}
}
break;
case RTTI_BUILDINGTYPE:
for (i = STRUCT_FIRST; i < STRUCT_COUNT; i++) {
if (PlayerPtr->Can_Build(&BuildingTypeClass::As_Reference((StructType)i), ActLike)) {
if (Session.Type == GAME_GLYPHX_MULTIPLAYER) {
Sidebar_Glyphx_Add(RTTI_BUILDINGTYPE, i, House, buildable_via_capture);
} else {
Map.Add(RTTI_BUILDINGTYPE, i, buildable_via_capture);
}
}
}
break;
case RTTI_UNITTYPE:
for (u = UNIT_FIRST; u < UNIT_COUNT; u++) {
if (PlayerPtr->Can_Build(&UnitTypeClass::As_Reference((UnitType)u), ActLike)) {
if (Session.Type == GAME_GLYPHX_MULTIPLAYER) {
Sidebar_Glyphx_Add(RTTI_UNITTYPE, u, House, buildable_via_capture);
} else {
Map.Add(RTTI_UNITTYPE, u, buildable_via_capture);
}
}
}
break;
case RTTI_INFANTRYTYPE:
for (f = INFANTRY_FIRST; f < INFANTRY_COUNT; f++) {
if (PlayerPtr->Can_Build(&InfantryTypeClass::As_Reference((InfantryType)f), ActLike)) {
if (InfantryTypeClass::As_Reference((InfantryType)f).IsDog) {
if (*this == STRUCT_KENNEL) {
if (Session.Type == GAME_GLYPHX_MULTIPLAYER) {
Sidebar_Glyphx_Add(RTTI_INFANTRYTYPE, f, House, buildable_via_capture);
} else {
Map.Add(RTTI_INFANTRYTYPE, f, buildable_via_capture);
}
}
} else {
if (*this != STRUCT_KENNEL) {
if (Session.Type == GAME_GLYPHX_MULTIPLAYER) {
Sidebar_Glyphx_Add(RTTI_INFANTRYTYPE, f, House, buildable_via_capture);
} else {
Map.Add(RTTI_INFANTRYTYPE, f, buildable_via_capture);
}
}
}
}
}
break;
case RTTI_AIRCRAFTTYPE:
for (a = AIRCRAFT_FIRST; a < AIRCRAFT_COUNT; a++) {
if (PlayerPtr->Can_Build(&AircraftTypeClass::As_Reference((AircraftType)a), ActLike)) {
if (Session.Type == GAME_GLYPHX_MULTIPLAYER) {
Sidebar_Glyphx_Add(RTTI_AIRCRAFTTYPE, a, House, buildable_via_capture);
} else {
Map.Add(RTTI_AIRCRAFTTYPE, a, buildable_via_capture);
}
}
}
break;
default:
break;
}
}
}
#if (0) //Old code for reference. ST - 8/2/2019 2:41PM
/***********************************************************************************************
* BuildingClass::Update_Buildables -- Informs sidebar of additional construction options. *
* *
* This routine will tell the sidebar of objects that can be built. The function is called *
* whenever a building matures. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 11/11/1994 JLB : Created. *
* 12/23/1994 JLB : Only updates for PLAYER buildings. *
*=============================================================================================*/
void BuildingClass::Update_Buildables(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (House == PlayerPtr && !IsInLimbo && IsDiscoveredByPlayer) {
switch (Class->ToBuild) {
int i;
int u;
int f;
int a;
int v;
case RTTI_VESSELTYPE:
for (v = VESSEL_FIRST; v < VESSEL_COUNT; v++) {
if (PlayerPtr->Can_Build(&VesselTypeClass::As_Reference((VesselType)v), ActLike)) {
Map.Add(RTTI_VESSELTYPE, v);
}
}
break;
case RTTI_BUILDINGTYPE:
for (i = STRUCT_FIRST; i < STRUCT_COUNT; i++) {
if (PlayerPtr->Can_Build(&BuildingTypeClass::As_Reference((StructType)i), ActLike)) {
Map.Add(RTTI_BUILDINGTYPE, i);
}
}
break;
case RTTI_UNITTYPE:
for (u = UNIT_FIRST; u < UNIT_COUNT; u++) {
if (PlayerPtr->Can_Build(&UnitTypeClass::As_Reference((UnitType)u), ActLike)) {
Map.Add(RTTI_UNITTYPE, u);
}
}
break;
case RTTI_INFANTRYTYPE:
for (f = INFANTRY_FIRST; f < INFANTRY_COUNT; f++) {
if (PlayerPtr->Can_Build(&InfantryTypeClass::As_Reference((InfantryType)f), ActLike)) {
if (InfantryTypeClass::As_Reference((InfantryType)f).IsDog) {
if (*this == STRUCT_KENNEL) {
Map.Add(RTTI_INFANTRYTYPE, f);
}
} else {
if (*this != STRUCT_KENNEL) {
Map.Add(RTTI_INFANTRYTYPE, f);
}
}
}
}
break;
case RTTI_AIRCRAFTTYPE:
for (a = AIRCRAFT_FIRST; a < AIRCRAFT_COUNT; a++) {
if (PlayerPtr->Can_Build(&AircraftTypeClass::As_Reference((AircraftType)a), ActLike)) {
Map.Add(RTTI_AIRCRAFTTYPE, a);
}
}
break;
default:
break;
}
}
}
#endif
/***********************************************************************************************
* BuildingClass::Fire_Out -- Handles when attached animation expires. *
* *
* This routine is used to perform any fixups necessary when the attached animation has *
* terminated. This occurs when the fire & smoke animation that a SAM site produces stops. *
* At that point, normal reload procedures can commence. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 11/30/1994 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Fire_Out(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
}
/***********************************************************************************************
* BuildingClass::Limbo -- Handles power adjustment as building goes into limbo. *
* *
* This routine will handle the power adjustments for the associated house when the *
* building goes into limbo. This means that its power drain or production is subtracted *
* from the house accumulated totals. *
* *
* INPUT: none *
* *
* OUTPUT: bool; Was the building limboed? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 12/24/1994 JLB : Created. *
*=============================================================================================*/
bool BuildingClass::Limbo(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (!IsInLimbo) {
/*
** Update the total factory type, assuming this building has a factory.
*/
House->Active_Remove(this);
House->IsRecalcNeeded = true;
House->Recalc_Center();
/*
** Update the power status of the owner's house.
*/
House->Adjust_Power(-Power_Output());
House->Adjust_Drain(-Class->Drain);
House->Adjust_Capacity(-Class->Capacity, true);
if (House == PlayerPtr) {
Map.PowerClass::IsToRedraw = true;
Map.Flag_To_Redraw(false);
}
/*
** This could be a building that builds. If so, then the sidebar may need adjustment.
** Set IsInLimbo to true to "fool" the sidebar into knowing that this building
** isn't available. Set it back to false so the rest of the Limbo code works.
** Otherwise, the sidebar won't properly remove non-available buildables.
*/
// if (IsOwnedByPlayer && !ScenarioInit) {
// IsInLimbo = true;
// Map.Recalc();
// IsInLimbo = false;
// }
}
return(TechnoClass::Limbo());
}
/***********************************************************************************************
* BuildingClass::Turret_Facing -- Fetches the turret facing for this building. *
* *
* This will return the turret facing for this building. Some buildings don't have a *
* visual turret (e.g., pillbox) so they return a turret facing that always faces their *
* current target. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the current facing of the turret. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/29/1996 JLB : Created. *
*=============================================================================================*/
DirType BuildingClass::Turret_Facing(void) const
{
if (!Class->IsTurretEquipped && Target_Legal(TarCom)) {
return(::Direction(Center_Coord(), As_Coord(TarCom)));
}
return(PrimaryFacing.Current());
}
/***********************************************************************************************
* BuildingClass::Greatest_Threat -- Searches for target that building can fire upon. *
* *
* This routine intercepts the Greatest_Threat function so that it can add the ability *
* to search for ground targets, if this isn't a SAM site. *
* *
* INPUT: threat -- The base threat control value. Typically, it might be THREAT_RANGE *
* or THREAT_NORMAL. *
* *
* OUTPUT: Returns with a suitable target. If none could be found, then TARGET_NONE is *
* returned instead. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 01/01/1995 JLB : Created. *
*=============================================================================================*/
TARGET BuildingClass::Greatest_Threat(ThreatType threat) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (Class->PrimaryWeapon != NULL) {
threat = threat | Class->PrimaryWeapon->Allowed_Threats();
}
if (Class->SecondaryWeapon != NULL) {
threat = threat | Class->SecondaryWeapon->Allowed_Threats();
}
if (House->IsHuman) {
threat = threat & ~THREAT_BUILDINGS;
}
threat = threat | THREAT_RANGE;
// if (Class->PrimaryWeapon != NULL) {
// if (Class->PrimaryWeapon->Bullet->IsAntiAircraft) {
// threat = threat | THREAT_AIR;
// }
// if (Class->PrimaryWeapon->Bullet->IsAntiGround) {
// threat = threat | THREAT_BUILDINGS|THREAT_INFANTRY|THREAT_BOATS|THREAT_VEHICLES;
// }
// threat = threat | THREAT_RANGE;
// }
return(TechnoClass::Greatest_Threat(threat));
}
/***********************************************************************************************
* BuildingClass::Grand_Opening -- Handles construction completed special operations. *
* *
* This routine is called when construction has finished. Typically, this enables *
* new production options for factories. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 01/08/1995 JLB : Created. *
* 06/13/1995 JLB : Added helipad. *
*=============================================================================================*/
void BuildingClass::Grand_Opening(bool captured)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (!HasOpened || captured) {
HasOpened = true;
/*
** Adjust the owning house according to the power, drain, and Tiberium capacity that
** this building has.
*/
House->Adjust_Drain(Class->Drain);
House->Adjust_Capacity(Class->Capacity);
House->IsRecalcNeeded = true;
/* SPECIAL CASE:
** Tiberium Refineries get a free harvester. Add a harvester to the
** reinforcement list at this time.
*/
if (*this == STRUCT_REFINERY && !ScenarioInit && !captured && !Debug_Map && (!House->IsHuman || PurchasePrice == 0 || PurchasePrice > Class->Raw_Cost())) {
CELL cell = Coord_Cell(Adjacent_Cell(Center_Coord(), DIR_S));
UnitClass * unit = new UnitClass(UNIT_HARVESTER, House->Class->House);
if (unit != NULL) {
/*
** Try to place down the harvesters. If it could not be placed, then try
** to place it in a nearby location.
*/
if (!unit->Unlimbo(Cell_Coord(cell), DIR_W)) {
/*
** Check multiple times for clear locations.
*/
for (int i = 0; i < 10; i++) {
cell = unit->Nearby_Location(this, i);
if (unit->Unlimbo(Cell_Coord(cell), DIR_SW)) {
break;
}
}
/*
** If the harvester could still not be placed, then refund the money
** to the owner and then bail.
*/
if (unit->IsInLimbo) {
House->Refund_Money(unit->Class->Cost_Of());
delete unit;
}
}
} else {
/*
** If the harvester could not be created in the first place, then give
** the full refund price to the owning player.
*/
House->Refund_Money(UnitTypeClass::As_Reference(UNIT_HARVESTER).Cost_Of());
}
}
/*
** Helicopter pads get a free attack helicopter.
*/
if (!Rule.IsSeparate && *this == STRUCT_HELIPAD && !captured) {
ScenarioInit++;
AircraftClass * air = 0;
if (House->ActLike == HOUSE_USSR || House->ActLike == HOUSE_BAD || House->ActLike == HOUSE_UKRAINE) {
air = new AircraftClass(AIRCRAFT_HIND, House->Class->House);
} else {
air = new AircraftClass(AIRCRAFT_LONGBOW, House->Class->House);
}
if (air) {
air->Height = 0;
if (air->Unlimbo(Docking_Coord(), air->Pose_Dir())) {
air->Assign_Mission(MISSION_GUARD);
air->Transmit_Message(RADIO_HELLO, this);
Transmit_Message(RADIO_TETHER);
}
}
ScenarioInit--;
}
}
}
/***********************************************************************************************
* BuildingClass::Repair -- Initiates or terminates the repair process. *
* *
* This routine will start, stop, or toggle the repair process. When a building repairs, it *
* occurs incrementally over time. *
* *
* INPUT: control -- Determines how to control the repair process. *
* 0: Turns repair process off (if it was on). *
* 1: Turns repair process on (if it was off). *
* -1:Toggles repair process to other state. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 01/08/1995 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Repair(int control)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
switch (control) {
case -1:
IsRepairing = (IsRepairing == false);
break;
case 1:
if (IsRepairing) return;
IsRepairing = true;
break;
case 0:
if (!IsRepairing) return;
IsRepairing = false;
break;
default:
break;
}
/*
** At this point, we know that the repair state has changed. Perform
** appropriate action.
*/
VocType soundid = VOC_NONE;
if (IsRepairing) {
if (Strength == Class->MaxStrength) {
soundid = VOC_SCOLD;
} else {
soundid = VOC_CLICK;
if (House->IsPlayerControl) {
Clicked_As_Target(PlayerPtr->Class->House); // 2019/09/20 JAS - Added record of who clicked on the object
}
IsWrenchVisible = true;
}
} else {
soundid = VOC_CLICK;
}
if (House->IsPlayerControl) {
Sound_Effect(soundid, Coord);
}
}
/***********************************************************************************************
* BuildingClass::Sell_Back -- Controls the sell back (demolish) operation. *
* *
* This routine will initiate or stop the sell back process for a building. It is called *
* when the player clicks on a building when the sell mode is active. *
* *
* INPUT: control -- The action to perform. 0 = turn deconstruction off, 1 = deconstruct, *
* -1 = toggle deconstruction state. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Sell_Back(int control)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (Class->Get_Buildup_Data()) {
bool decon = false;
switch (control) {
case -1:
decon = (Mission != MISSION_DECONSTRUCTION);
break;
case 1:
if (Mission == MISSION_DECONSTRUCTION) return;
if (IsGoingToBlow) return;
decon = true;
break;
case 0:
if (Mission != MISSION_DECONSTRUCTION) return;
decon = false;
break;
default:
break;
}
/*
** At this point, we know that the repair state has changed. Perform
** appropriate action.
*/
if (decon) {
Assign_Mission(MISSION_DECONSTRUCTION);
Commence();
if (House->IsPlayerControl) {
Clicked_As_Target(PlayerPtr->Class->House);
}
}
if (House->IsPlayerControl) {
Sound_Effect(VOC_CLICK);
}
}
}
/***********************************************************************************************
* BuildingClass::What_Action -- Determines action to perform if click on specified object. *
* *
* This routine will determine what action to perform if the mouse was clicked on the *
* object specified. This determination is used to control the mouse imagery and the *
* function process when the mouse button is pressed. *
* *
* INPUT: object -- Pointer to the object that, if clicked on, will control what action *
* is to be performed. *
* *
* OUTPUT: Returns with the ActionType that will occur if the mouse is clicked over the *
* object specified while the building is currently selected. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 01/18/1995 JLB : Created. *
*=============================================================================================*/
ActionType BuildingClass::What_Action(ObjectClass const * object) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
ActionType action = TechnoClass::What_Action(object);
if (action == ACTION_SELF) {
int index;
if (Class->Is_Factory() && PlayerPtr == House && *House->Factory_Counter(Class->ToBuild) > 1) {
switch (Class->ToBuild) {
case RTTI_INFANTRYTYPE:
case RTTI_INFANTRY:
action = ACTION_NONE;
if (*this == STRUCT_KENNEL) {
for (index = 0; index < Buildings.Count(); index++) {
BuildingClass *bldg = Buildings.Ptr(index);
if (bldg != this && bldg->Owner() == Owner() && *bldg == STRUCT_KENNEL) {
action = ACTION_TOGGLE_PRIMARY;
break;
}
}
} else {
for (index = 0; index < Buildings.Count(); index++) {
BuildingClass *bldg = Buildings.Ptr(index);
if (bldg != this && bldg->Owner() == Owner() && bldg->Class->ToBuild == RTTI_INFANTRYTYPE && *bldg != STRUCT_KENNEL) {
action = ACTION_TOGGLE_PRIMARY;
break;
}
}
}
break;
case RTTI_AIRCRAFTTYPE:
case RTTI_AIRCRAFT:
action = ACTION_NONE;
if (*this == STRUCT_AIRSTRIP) {
for (index = 0; index < Buildings.Count(); index++) {
BuildingClass *bldg = Buildings.Ptr(index);
if (bldg != this && bldg->Owner() == Owner() && *bldg == STRUCT_AIRSTRIP) {
action = ACTION_TOGGLE_PRIMARY;
break;
}
}
}
else {
for (index = 0; index < Buildings.Count(); index++) {
BuildingClass *bldg = Buildings.Ptr(index);
if (bldg != this && bldg->Owner() == Owner() && bldg->Class->ToBuild == RTTI_AIRCRAFTTYPE && *bldg != STRUCT_AIRSTRIP) {
action = ACTION_TOGGLE_PRIMARY;
break;
}
}
}
break;
case RTTI_UNITTYPE:
case RTTI_UNIT:
case RTTI_VESSELTYPE:
case RTTI_VESSEL:
action = ACTION_TOGGLE_PRIMARY;
break;
case RTTI_NONE:
action = ACTION_NONE;
break;
default:
break;
}
} else {
action = ACTION_NONE;
}
}
/*
** Don't allow targeting of SAM sites, even if the CTRL key
** is held down. Also don't allow targeting if the object is too
** far away.
*/
if (action == ACTION_ATTACK && (*this == STRUCT_SAM || *this == STRUCT_AAGUN || !In_Range(object, 0))) {
action = ACTION_NONE;
}
if (action == ACTION_MOVE) {
action = ACTION_NONE;
}
return(action);
}
/***********************************************************************************************
* BuildingClass::What_Action -- Determines what action will occur. *
* *
* This routine examines the cell specified and returns with the action that will be *
* performed if that cell were clicked upon while the building is selected. *
* *
* INPUT: cell -- The cell to examine. *
* *
* OUTPUT: Returns the ActionType that indicates what should occur if the mouse is clicked *
* on this cell. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 01/18/1995 JLB : Created. *
*=============================================================================================*/
ActionType BuildingClass::What_Action(CELL cell) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
ActionType action = TechnoClass::What_Action(cell);
if (action == ACTION_MOVE && (*this != STRUCT_CONST || !Is_MCV_Deploy())) {
action = ACTION_NONE;
}
/*
** Don't allow targeting of SAM sites, even if the CTRL key
** is held down.
*/
if (action == ACTION_ATTACK && Class->PrimaryWeapon != NULL && !Class->PrimaryWeapon->Bullet->IsAntiGround) {
// if (action == ACTION_ATTACK && (*this == STRUCT_SAM || *this == STRUCT_AAGUN)) {
action = ACTION_NONE;
}
return(action);
}
/***********************************************************************************************
* BuildingClass::Begin_Mode -- Begins an animation mode for the building. *
* *
* This routine will start the building animating. This animation will loop indefinitely *
* until explicitly stopped. *
* *
* INPUT: bstate -- The animation state to initiate. *
* *
* OUTPUT: none *
* *
* WARNINGS: The building graphic state will reflect the first stage of this animation the *
* very next time it is rendered. *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
* 07/02/1995 JLB : Uses normalize animation rate where applicable. *
*=============================================================================================*/
void BuildingClass::Begin_Mode(BStateType bstate)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
QueueBState = bstate;
if (BState == BSTATE_NONE || bstate == BSTATE_CONSTRUCTION || ScenarioInit) {
BState = bstate;
QueueBState = BSTATE_NONE;
BuildingTypeClass::AnimControlType const * ctrl = Fetch_Anim_Control();
int rate = ctrl->Rate;
if (Class->IsRegulated && bstate != BSTATE_CONSTRUCTION) {
rate = Options.Normalize_Delay(rate);
}
Set_Rate(rate);
Set_Stage(ctrl->Start);
}
}
/***********************************************************************************************
* BuildingClass::Center_Coord -- Fetches the center coordinate for the building. *
* *
* This routine is used to set the center coordinate for this building. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the coordinate for the center location for the building. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/10/1995 JLB : Created. *
*=============================================================================================*/
COORDINATE BuildingClass::Center_Coord(void) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
return(Coord_Add(Coord, CenterOffset[Class->Size]));
}
/***********************************************************************************************
* BuildingClass::Docking_Coord -- Fetches the coordinate to use for docking. *
* *
* This routine will return the coordinate to use when an object wishes to dock with this *
* building. Normally the docking coordinate would be the center of the building. *
* Exceptions to this would be the airfield and helipad. Their docking coordinates are *
* offset to match the building artwork. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the coordinate to head to when trying to dock with this building. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 09/21/1995 JLB : Created. *
*=============================================================================================*/
COORDINATE BuildingClass::Docking_Coord(void) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (*this == STRUCT_HELIPAD) {
return(Coord_Add(Coord, XYP_COORD(24, 18)));
}
if (*this == STRUCT_AIRSTRIP) {
return(Coord_Add(Coord, XYP_COORD(ICON_PIXEL_W + ICON_PIXEL_W/2, 28)));
}
return(TechnoClass::Docking_Coord());
}
/***********************************************************************************************
* BuildingClass::Can_Fire -- Determines if this building can fire. *
* *
* Use this routine to see if the building can fire its weapon. *
* *
* *
* INPUT: target -- The target that firing upon is desired. *
* *
* which -- Which weapon to use when firing. 0=primary, 1=secondary. *
* *
* OUTPUT: Returns with the fire possibility code. If firing is allowed, then FIRE_OK is *
* returned. Other cases will result in appropriate fire code value that indicates *
* why firing is not allowed. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/03/1995 JLB : Created. *
*=============================================================================================*/
FireErrorType BuildingClass::Can_Fire(TARGET target, int which) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
FireErrorType canfire = TechnoClass::Can_Fire(target, which);
if (canfire == FIRE_OK) {
/*
** Double check to make sure that the facing is roughly toward
** the target. If the difference is too great, then firing is
** temporarily postponed.
*/
if (Class->IsTurretEquipped) {
int diff = PrimaryFacing.Difference(Direction(TarCom));
diff = abs(diff);
if (ABS(diff) > (*this == STRUCT_SAM ? 64 : 8)) {
// if (ABS(diff) > 8) {
return(FIRE_FACING);
}
/*
** If the turret is rotating then firing must be delayed.
*/
// if (PrimaryFacing.Is_Rotating()) {
// return(FIRE_ROTATING);
// }
}
/*
** Certain buildings cannot fire if there is insufficient power.
*/
if (Class->IsPowered && House->Power_Fraction() < 1) {
return(FIRE_BUSY);
}
/*
** If an obelisk can fire, check the state of charge.
*/
if (Class->PrimaryWeapon != NULL && Class->PrimaryWeapon->IsElectric && !IsCharged) {
return(FIRE_BUSY);
}
}
return(canfire);
}
/***********************************************************************************************
* BuildingClass::Toggle_Primary -- Toggles the primary factory state. *
* *
* This routine will change the primary factory state of this building. The primary *
* factory is the one that units will be produced from (by default). *
* *
* INPUT: none *
* *
* OUTPUT: Is this building NOW the primary factory? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/03/1995 JLB : Created. *
*=============================================================================================*/
bool BuildingClass::Toggle_Primary(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (IsLeader) {
IsLeader = false;
} else {
for (int index = 0; index < Buildings.Count(); index++) {
BuildingClass * building = Buildings.Ptr(index);
if (!building->IsInLimbo && building->Owner() == Owner() && building->Class->ToBuild == Class->ToBuild) {
if (Class->ToBuild == RTTI_INFANTRYTYPE) {
if (*building == STRUCT_KENNEL && *this == STRUCT_KENNEL) {
building->IsLeader = false;
} else {
if (*building != STRUCT_KENNEL && *this != STRUCT_KENNEL) {
building->IsLeader = false;
}
}
} else if (Class->ToBuild == RTTI_AIRCRAFTTYPE) {
if (*building == STRUCT_AIRSTRIP && *this == STRUCT_AIRSTRIP) {
building->IsLeader = false;
} else {
if (*building != STRUCT_AIRSTRIP && *this != STRUCT_AIRSTRIP) {
building->IsLeader = false;
}
}
} else {
building->IsLeader = false;
}
}
}
IsLeader = true;
//
// MBL 04.20.2020 - Update so that each player in multiplayer will properly hear this when it applies to them
//
// if ((HouseClass *)House == PlayerPtr) {
// Speak(VOX_PRIMARY_SELECTED);
// }
if ((HouseClass *)House->IsHuman) {
Speak(VOX_PRIMARY_SELECTED, House);
}
}
Mark(MARK_CHANGE);
return(IsLeader);
}
/***********************************************************************************************
* BuildingClass::Captured -- Captures the building. *
* *
* This routine will change the owner of the building. It handles updating any related *
* game systems as a result. Factories are the most prone to have great game related *
* consequences when captured. This could also affect the sidebar and building ownership. *
* *
* INPUT: newowner -- Pointer to the house that is now the new owner. *
* *
* OUTPUT: Was the capture attempt successful? *
* *
* WARNINGS: Capturing could fail if the house is already owned by the one specified or *
* the building isn't allowed to be captured. *
* *
* HISTORY: *
* 05/03/1995 JLB : Created. *
* 07/05/1995 JLB : Fixed production problem with capturing enemy buildings. *
*=============================================================================================*/
bool BuildingClass::Captured(HouseClass * newowner)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (Can_Capture() && newowner != House) {
#ifdef TOFIX
switch (Owner()) {
case HOUSE_GOOD:
Speak(VOX_GDI_CAPTURED);
break;
case HOUSE_BAD:
Speak(VOX_NOD_CAPTURED);
break;
}
#endif
/*
** Maybe trigger an achivement. ST - 11/14/2019 1:53PM
*/
if (newowner->IsHuman) {
TechnoTypeClass const *object_type = Techno_Type_Class();
if (object_type) {
if (newowner->ActLike != House->ActLike) {
On_Achievement_Event(newowner, "OPPOSING_BUILDING_CAPTURED", object_type->IniName);
} else {
On_Achievement_Event(newowner, "BUILDING_CAPTURED", object_type->IniName);
}
}
}
/*
** Make sure the capturer isn't spying on his own building, and if
** it was a radar facility, update the target house's RadarSpied field.
*/
if (SpiedBy & (1<<(newowner->Class->House)) ) {
SpiedBy -= (1<<(newowner->Class->House));
if (*this == STRUCT_RADAR) {
Update_Radar_Spied();
}
}
if (House == PlayerPtr) {
Map.PowerClass::IsToRedraw = true;
Map.Flag_To_Redraw(false);
}
if (*this == STRUCT_GAP) {
Remove_Gap_Effect();
IsJamming = false;
Arm = 0;
}
/*
** Add this building to the list of buildings captured this game. For internet stats purposes.
*/
if (Session.Type == GAME_INTERNET) {
newowner->CapturedBuildings->Increment_Unit_Total (Class->Type);
}
House->Adjust_Power(-Power_Output());
LastStrength = 0;
House->Adjust_Drain(-Class->Drain);
int booty = House->Adjust_Capacity(-Class->Capacity, true);
/*
** If there is something loaded, then it gets captured as well.
*/
TechnoClass * tech = Attached_Object();
if (tech) tech->Captured(newowner);
/*
** If something isn't technically attached, but is sitting on this
** building for another reason (e.g., helicopter on helipad), then it
** gets captured as well.
*/
tech = Contact_With_Whom();
if (tech) {
if (Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER && (::Distance(tech->Center_Coord(), Docking_Coord()) < 0x0040 ||
(tech->What_Am_I() == RTTI_AIRCRAFT && ((AircraftClass *)tech)->Class->IsFixedWing && ((AircraftClass *)tech)->In_Which_Layer() == LAYER_GROUND)) ) {
tech->Captured(newowner);
} else {
Transmit_Message(RADIO_RUN_AWAY);
Transmit_Message(RADIO_OVER_OUT);
}
}
/*
** Abort any computer production in progress.
*/
if (Factory) {
delete (FactoryClass *)Factory;
Factory = 0;
}
/*
** Decrement the factory counter for the original owner.
*/
House->Active_Remove(this);
/*
** Flag that both owners now need to update their buildable lists.
*/
House->IsRecalcNeeded = true;
newowner->IsRecalcNeeded = true;
HouseClass * oldowner = House;
TARGET tocap = As_Target();
IsCaptured = true;
TechnoClass::Captured(newowner);
oldowner->ToCapture = tocap;
oldowner->Recalc_Center();
House->Recalc_Center();
if (House->ToCapture == As_Target()) {
House->ToCapture = TARGET_NONE;
}
SmudgeType bib;
CELL cell = Coord_Cell(Coord);
if (Class->Bib_And_Offset(bib, cell)) {
SmudgeClass * smudge = new SmudgeClass(bib);
if (smudge) {
smudge->Disown(cell);
delete smudge;
}
#ifdef FIXIT_CAPTURE_BIB
if (Session.Type == GAME_NORMAL) {
new SmudgeClass(bib, Cell_Coord(cell), Class->IsBase ? House->Class->House : HOUSE_NONE);
} else {
new SmudgeClass(bib, Cell_Coord(cell), House->Class->House);
}
#else
new SmudgeClass(bib, Cell_Coord(cell), House->Class->House);
#endif
}
House->Stole(Refund_Amount());
/*
** Increment the factory count for the new owner.
*/
House->Active_Add(this);
IsRepairing = false;
Grand_Opening(true);
House->Harvested(booty);
Mark(MARK_CHANGE);
/*
** Perform a look operation when captured if it was the player
** that performed the capture.
*/
if (Session.Type == GAME_GLYPHX_MULTIPLAYER && House->IsHuman) {
Look(false);
} else {
if (House == PlayerPtr) {
Look(false);
}
}
/*
** If it was spied upon by the player who just captured it, clear the
** spiedby flag for that house.
*/
if (SpiedBy & (1 << (newowner->Class->House))) {
SpiedBy &= ~(1 << (newowner->Class->House));
}
/*
** Update the new building's colors on the radar map.
*/
short const * offset = Occupy_List();
while (*offset != REFRESH_EOL) {
CELL cell = Coord_Cell(Coord) + *offset++;
Map.Radar_Pixel(cell);
}
if (oldowner) {
oldowner->Check_Pertinent_Structures();
}
return(true);
}
return(false);
}
/***********************************************************************************************
* BuildingClass::Sort_Y -- Returns the building coordinate used for sorting. *
* *
* The coordinate value returned from this function should be used for sorting purposes. *
* It has special offset adjustment applied so that vehicles don't overlap (as much). *
* *
* INPUT: none *
* *
* OUTPUT: Returns with a coordinate value suitable to be used for sorting. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/23/1995 JLB : Created. *
* 06/19/1995 JLB : Handles buildings that come with bibs built-in. *
*=============================================================================================*/
COORDINATE BuildingClass::Sort_Y(void) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (*this == STRUCT_REPAIR) {
return(Coord);
}
if (*this == STRUCT_HELIPAD) {
return(Center_Coord());
}
if (*this == STRUCT_AIRSTRIP) {
return(Center_Coord());
}
if (*this == STRUCT_BARRACKS /*|| *this == STRUCT_POWER*/) {
return(Center_Coord());
}
if (*this == STRUCT_REFINERY) {
return(Center_Coord());
}
/*
** Mines need to bias their sort location such that they are typically drawn
** before any objects that might overlap them.
*/
if (*this == STRUCT_AVMINE || *this == STRUCT_APMINE) {
return(Coord_Move(Center_Coord(), DIR_N, CELL_LEPTON_H));
}
return(Coord_Add(Center_Coord(), XY_Coord(0, (Class->Height()*256)/3)));
}
/***********************************************************************************************
* BuildingClass::Can_Enter_Cell -- Determines if building can be placed down. *
* *
* This routine will determine if the building can be placed down at the location *
* specified. *
* *
* INPUT: cell -- The cell to examine. This is usually the cell of the upper left corner *
* of the building if it were to be placed down. *
* *
* OUTPUT: Returns with the move legality value for placement at the location specified. This *
* will either be MOVE_OK or MOVE_NO. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
*=============================================================================================*/
MoveType BuildingClass::Can_Enter_Cell(CELL cell, FacingType) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (*this == STRUCT_CONST && IsDown) {
return(Map[cell].Is_Clear_To_Build(Class->Speed) ? MOVE_OK : MOVE_NO);
}
if (!Debug_Map && ScenarioInit == 0 && Session.Type == GAME_NORMAL && House->IsPlayerControl && !Map[cell].IsMapped) {
return(MOVE_NO);
}
return(Class->Legal_Placement(cell) ? MOVE_OK : MOVE_NO);
}
/***********************************************************************************************
* BuildingClass::Can_Demolish -- Can the player demolish (sell back) the building? *
* *
* Determines if the player can sell this building. Selling is possible if the building *
* is not currently in construction or deconstruction animation. *
* *
* INPUT: none *
* *
* OUTPUT: Can the building be demolished at this time? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
* 07/01/1995 JLB : If there is no buildup data, then the building can't be sold. *
* 07/17/1995 JLB : Cannot sell a refinery that has a harvester attached. *
*=============================================================================================*/
bool BuildingClass::Can_Demolish(void) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (Class->IsUnsellable) return(false);
if (Class->Get_Buildup_Data() && BState != BSTATE_CONSTRUCTION && Mission != MISSION_DECONSTRUCTION && Mission != MISSION_CONSTRUCTION) {
if (*this == STRUCT_REFINERY && Is_Something_Attached()) return(false);
return(true);
}
return(false);
}
bool BuildingClass::Can_Demolish_Unit(void) const
{
return((*this == STRUCT_REPAIR || *this == STRUCT_AIRSTRIP) && In_Radio_Contact() && Distance(Contact_With_Whom()) < 0x0080);
}
bool BuildingClass::Can_Capture(void) const
{
bool can_capture = Class->IsCaptureable && Mission != MISSION_DECONSTRUCTION;
// Only allow capturing of multiplayer-owned structures
if (Session.Type != GAME_NORMAL) {
if (*this == STRUCT_V01) { // Check to fix exploit in specific map 'Tournament Ore Rift'
can_capture = false;
}
}
return(can_capture);
}
/***********************************************************************************************
* BuildingClass::Mission_Guard -- Handles guard mission for combat buildings. *
* *
* Buildings that can attack are given this mission. They will wait until a suitable target *
* comes within range and then launch into the attack mission. Buildings that have no *
* weaponry will just sit in this routine forever. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the number of game frames to delay before this routine will be called *
* again. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
*=============================================================================================*/
int BuildingClass::Mission_Guard(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
/*
** If this building has a weapon, then search for a target to attack. When
** a target is found, switch into attack mode to deal with the threat.
*/
if (Is_Weapon_Equipped()) {
/*
** Weapon equipped buildings are ALWAYS ready to launch into another mission if
** they are sitting around in guard mode.
*/
IsReadyToCommence = true;
/*
** If there is no target available, then search for one.
*/
if (!Target_Legal(TarCom)) {
ThreatType threat = THREAT_NORMAL;
Assign_Target(Greatest_Threat(threat));
}
/*
** There is a valid target. Switch into attack mode right away.
*/
if (Target_Legal(TarCom)) {
Assign_Mission(MISSION_ATTACK);
Commence();
return(1);
}
} else {
/*
** This is the very simple state machine that basically does
** nothing. This is the mode that non weapon equipped buildings
** are normally in.
*/
enum {
INITIAL_ENTRY,
IDLE
};
switch (Status) {
case INITIAL_ENTRY:
Begin_Mode(BSTATE_IDLE);
Status = IDLE;
break;
case IDLE:
/*
** Special case to break out of guard mode if this is a repair
** facility and there is a customer waiting at the grease pit.
*/
if (*this == STRUCT_REPAIR &&
In_Radio_Contact() &&
Contact_With_Whom()->Is_Techno() &&
((TechnoClass *)Contact_With_Whom())->Mission == MISSION_ENTER &&
Distance(Contact_With_Whom()) < 0x0040 &&
Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER) {
Assign_Mission(MISSION_REPAIR);
return(1);
}
break;
default:
break;
}
if (*this == STRUCT_REPAIR) {
return(MissionControl[Mission].Normal_Delay() + Random_Pick(0, 2));
} else {
return(MissionControl[Mission].Normal_Delay() * 3 + Random_Pick(0, 2));
}
}
return(MissionControl[Mission].AA_Delay() + Random_Pick(0, 2));
}
/***********************************************************************************************
* BuildingClass::Mission_Construction -- Handles mission construction. *
* *
* This routine will handle mission construction. When this mission is complete, the *
* building will begin normal operation. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the number of game frames to delay before calling this routine *
* again. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
*=============================================================================================*/
int BuildingClass::Mission_Construction(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
enum {
INITIAL,
DURING
};
switch (Status) {
case INITIAL:
Begin_Mode(BSTATE_CONSTRUCTION);
Transmit_Message(RADIO_BUILDING);
if (House->IsPlayerControl) {
Sound_Effect(VOC_CONSTRUCTION, Coord);
}
Status = DURING;
break;
case DURING:
if (IsReadyToCommence) {
/*
** When construction is complete, then transmit this
** to the construction yard so that it can stop its
** construction animation.
*/
Transmit_Message(RADIO_COMPLETE); // "I'm finished."
Transmit_Message(RADIO_OVER_OUT); // "You're free."
Begin_Mode(BSTATE_IDLE);
Grand_Opening();
Assign_Mission(MISSION_GUARD);
PrimaryFacing = Class->StartFace;
}
break;
default:
break;
}
return(1);
}
/***********************************************************************************************
* BuildingClass::Mission_Deconstruction -- Handles building deconstruction. *
* *
* This state machine is only used when the building is deconstructing as a result of *
* selling. When this mission is finished, the building will no longer exist. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the number of game frames to delay before calling this routine again. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
* 08/13/1995 JLB : Enable selling of units on a repair bay. *
* 08/20/1995 JLB : Scatters infantry from scattered starting points. *
*=============================================================================================*/
int BuildingClass::Mission_Deconstruction(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
/*
** Always force repair off.
*/
Repair(0);
enum {
INITIAL,
HOLDING,
DURING
};
switch (Status) {
case INITIAL:
/*
** Special check for the repair bay which has the ability to sell
** whatever is on it. If there is something on the repair bay, then
** it will be sold. If there is nothing on the repair bay, then
** the repair bay itself will be sold.
*/
if (Can_Demolish_Unit() && Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER) {
TechnoClass * tech = Contact_With_Whom();
Transmit_Message(RADIO_OVER_OUT);
if (IsOwnedByPlayer) Speak(VOX_UNIT_SOLD);
tech->Sell_Back(1);
Assign_Mission(MISSION_GUARD);
return(1);
}
/*
** Selling off a shipyard or sub pen may cause attached ships
** who are repairing themselves to discontinue repairs.
*/
if (*this == STRUCT_SHIP_YARD || *this == STRUCT_SUB_PEN) {
for (int index = 0; index < Vessels.Count(); index++) {
VesselClass * obj = Vessels.Ptr(index);
if (obj && !obj->IsInLimbo && obj->House == House) {
if (obj->IsSelfRepairing) {
if (::Distance(Center_Coord(), obj->Center_Coord()) < 0x0200) {
obj->IsSelfRepairing = false;
obj->IsToSelfRepair = false;
}
}
}
}
}
IsReadyToCommence = false;
Transmit_Message(RADIO_RUN_AWAY);
Status = HOLDING;
break;
case HOLDING:
if (!IsTethered) {
/*
** The crew will evacuate from the building. The number of crew
** members leaving is equal to the unrecovered cost of the building
** divided by 100 (the typical cost of a minigunner infantryman).
*/
if (!Target_Legal(ArchiveTarget) || !Is_MCV_Deploy() || *this != STRUCT_CONST) {
int count = How_Many_Survivors();
bool engine = false;
while (count) {
/*
** Ensure that the player only gets ONE engineer and not from a captured
** construction yard.
*/
InfantryType typ = Crew_Type();
while (typ == INFANTRY_RENOVATOR && engine) {
typ = Crew_Type();
}
if (typ == INFANTRY_RENOVATOR) engine = true;
InfantryClass * infantry = 0;
if (typ != INFANTRY_NONE) infantry = new InfantryClass(typ, House->Class->House);
if (infantry != NULL) {
ScenarioInit++;
COORDINATE coord = Coord_Add(Center_Coord(), XYP_COORD(0, -12));
coord = Map[coord].Closest_Free_Spot(coord, false);
if (infantry->Unlimbo(coord, DIR_N)) {
infantry->IsZoneCheat = infantry->Can_Enter_Cell(Coord_Cell(infantry->Center_Coord())) != MOVE_OK;
if (infantry->Class->IsNominal) infantry->IsTechnician = true;
ScenarioInit--;
infantry->Scatter(0, true);
ScenarioInit++;
infantry->Assign_Mission(MISSION_GUARD_AREA);
} else {
delete infantry;
}
ScenarioInit--;
}
count--;
}
}
// MBL 07.10.2020 - In 1v1, sometimes both players will hear this SFX, or neither player will hear it
// Making it so all players hear it positionally in the map; Per thread discussion in https://jaas.ea.com/browse/TDRA-7245
//
#if 0
if (House->IsPlayerControl) {
Sound_Effect(VOC_CASHTURN, Coord);
}
#else
Sound_Effect(VOC_CASHTURN, Coord);
#endif
/*
** Destroy all attached objects. ST - 4/24/2020 9:38PM
*/
while (Attached_Object()) {
FootClass * obj = Detach_Object();
Detach_All(true);
delete obj;
}
Transmit_Message(RADIO_OVER_OUT);
Status = DURING;
Begin_Mode(BSTATE_CONSTRUCTION);
IsReadyToCommence = false;
IsSurvivorless = true;
break;
}
Transmit_Message(RADIO_RUN_AWAY);
break;
case DURING:
if (IsReadyToCommence) {
House->IsRecalcNeeded = true;
// MBL 05.06.2020 - "Structure Sold" is being heard when selecting/moving a redepolyable Con Yard to turn back into an MCV (RA only), so moving below
#if 0
// MBL 04.06.2020: Fix being heard by wrong player
// if (IsOwnedByPlayer) Speak(VOX_STRUCTURE_SOLD);
if (IsOwnedByPlayer) {
if ((HouseClass *)House == PlayerPtr) {
Speak(VOX_STRUCTURE_SOLD);
}
}
#endif
bool mcv_redeployed = false;
/*
** Construction yards that deconstruct, really just revert back
** to an MCV.
*/
if (Target_Legal(ArchiveTarget) && *this == STRUCT_CONST && House->IsHuman && Strength > 0) {
ScenarioInit++;
UnitClass * unit = new UnitClass(UNIT_MCV, House->Class->House);
ScenarioInit--;
if (unit != NULL) {
/*
** Unlimbo the MCV onto the map. The MCV should start in the same
** health condition that the construction yard was in.
*/
fixed ratio = Health_Ratio();
int money = Refund_Amount();
TARGET arch = ArchiveTarget;
COORDINATE place = Coord_Snap(Adjacent_Cell(Coord, DIR_SE));
delete this;
if (unit->Unlimbo(place, DIR_SW)) {
unit->Strength = (int)unit->Class_Of().MaxStrength * ratio; // Cast to (int). ST - 5/8/2019
/*
** Lift the move destination from the building and assign
** it to the unit.
*/
if (Target_Legal(arch)) {
unit->Assign_Destination(arch);
unit->Assign_Mission(MISSION_MOVE);
}
mcv_redeployed = true;
} else {
/*
** If, for some strange reason, the MCV could not be placed on the
** map, then give the player some money to compensate.
*/
House->Refund_Money(money);
}
} else {
House->Refund_Money(Refund_Amount());
delete this;
}
} else {
/*
** Selling off a gap generator will cause the cells it affects
** to stop being jammed.
*/
if (*this == STRUCT_GAP) {
Remove_Gap_Effect();
}
/*
** A sold building still counts as a kill, but it just isn't directly
** attributed to the enemy.
*/
WhoLastHurtMe = HOUSE_NONE;
Record_The_Kill(NULL);
/*
** The player gets part of the money back for the sell.
*/
House->Refund_Money(Refund_Amount());
House->Stole(-Refund_Amount());
Limbo();
if (House) {
House->Check_Pertinent_Structures();
}
/*
** Finally, delete the building from the game.
*/
delete this;
}
// MBL 05.06.2020 - "Structure Sold" was being heard when selecting/moving a redepolyable Con Yard to turn back into an MCV (RA only) above, so moved here
#if 1
if (!mcv_redeployed)
{
if (IsOwnedByPlayer) {
if ((HouseClass *)House == PlayerPtr) {
Speak(VOX_STRUCTURE_SOLD);
}
}
}
#endif
}
break;
default:
break;
}
return(1);
}
/***********************************************************************************************
* BuildingClass::Mission_Attack -- Handles attack mission for building. *
* *
* Buildings that can attack are processed by this attack mission state machine. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the number of game frames to delay before calling this routine *
* again. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
* 02/22/1996 JLB : SAM doesn't lower back into ground. *
*=============================================================================================*/
int BuildingClass::Mission_Attack(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (*this == STRUCT_SAM) {
switch (Status) {
/*
** This is the target tracking state of the launcher. It will rotate
** to face the current TarCom of the launcher.
*/
case SAM_READY:
if ((Class->IsPowered && House->Power_Fraction() < 1) || IsJammed) {
return(1);
}
if (!Target_Legal(TarCom) || !Is_Target_Aircraft(TarCom) || As_Aircraft(TarCom)->Height == 0) {
Assign_Target(TARGET_NONE);
Status = SAM_READY;
Assign_Mission(MISSION_GUARD);
Commence();
return(1);
} else {
if (!PrimaryFacing.Is_Rotating()) {
DirType facing = Direction(TarCom);
if (PrimaryFacing.Difference(facing)) {
PrimaryFacing.Set_Desired(facing);
} else {
Status = SAM_FIRING;
}
}
}
return(1);
/*
** The launcher is in the process of firing.
*/
case SAM_FIRING:
if (!Target_Legal(TarCom) || !Is_Target_Aircraft(TarCom) || As_Aircraft(TarCom)->Height == 0) {
Assign_Target(TARGET_NONE);
Status = SAM_READY;
} else {
FireErrorType error = Can_Fire(TarCom, 0);
if (error == FIRE_ILLEGAL || error == FIRE_CANT || error == FIRE_RANGE) {
Assign_Target(TARGET_NONE);
Status = SAM_READY;
} else {
if (error == FIRE_FACING) {
Status = SAM_READY;
} else {
if (error == FIRE_OK) {
Fire_At(TarCom, 0);
Fire_At(TarCom, 1);
Status = SAM_READY;
}
}
}
}
return(1);
default:
break;
}
return(MissionControl[Mission].AA_Delay() + Random_Pick(0, 2));
}
if (!Target_Legal(TarCom)) {
Assign_Target(TARGET_NONE);
Assign_Mission(MISSION_GUARD);
Commence();
return(1);
}
int primary = What_Weapon_Should_I_Use(TarCom);
IsReadyToCommence = true;
switch (Can_Fire(TarCom, primary)) {
case FIRE_ILLEGAL:
case FIRE_CANT:
case FIRE_RANGE:
case FIRE_AMMO:
Assign_Target(TARGET_NONE);
Assign_Mission(MISSION_GUARD);
Commence();
break;
case FIRE_FACING:
PrimaryFacing.Set_Desired(Direction(TarCom));
return(2);
case FIRE_REARM:
PrimaryFacing.Set_Desired(Direction(TarCom));
return(Arm);
case FIRE_BUSY:
return(1);
case FIRE_CLOAKED:
Do_Uncloak();
break;
case FIRE_OK:
Fire_At(TarCom, primary);
return(1);
default:
break;
}
PrimaryFacing.Set_Desired(Direction(TarCom));
return(1);
// return(MissionControl[Mission].Normal_Delay() + Random_Pick(0, 2));
}
/***********************************************************************************************
* BuildingClass::Mission_Harvest -- Handles refinery unloading harvesters. *
* *
* This state machine handles the refinery when it unloads the harvester. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the number of game frames to delay before calling this routine *
* again. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
*=============================================================================================*/
int BuildingClass::Mission_Harvest(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
enum {
INITIAL, // Dock the Tiberium cannister.
WAIT_FOR_DOCK, // Waiting for docking to complete.
MIDDLE, // Offload "bails" of tiberium.
WAIT_FOR_UNDOCK // Waiting for undocking to complete.
};
switch (Status) {
case INITIAL:
Status = WAIT_FOR_DOCK;
break;
case WAIT_FOR_DOCK:
if (IsReadyToCommence) {
IsReadyToCommence = false;
Status = MIDDLE;
}
break;
case MIDDLE:
if (IsReadyToCommence) {
IsReadyToCommence = false;
/*
** Force any bib squatters to scatter.
*/
Map[Adjacent_Cell(Coord_Cell(Center_Coord()), DIR_S)].Incoming(0, true, true);
FootClass * techno = Attached_Object();
if (techno) {
int bail = techno->Offload_Tiberium_Bail();
if (bail) {
House->Harvested(bail);
if (techno->Tiberium_Load() > 0) {
return(1);
}
}
}
Status = WAIT_FOR_UNDOCK;
}
break;
case WAIT_FOR_UNDOCK:
if (IsReadyToCommence) {
/*
** Detach harvester and go back into idle state.
*/
Assign_Mission(MISSION_GUARD);
}
break;
default:
break;
}
return(1);
}
/***********************************************************************************************
* BuildingClass::Mission_Repair -- Handles the repair (active) state for building. *
* *
* This state machine is used when the building is active in some sort of repair or *
* construction mode. The construction yard will animate. The repair facility will repair *
* anything that it docked on it. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the number of game frames to delay before calling this routine again. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
* 06/25/1995 JLB : Handles repair facility *
* 07/29/1995 JLB : Repair rate is controlled by power rating. *
*=============================================================================================*/
int BuildingClass::Mission_Repair(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (*this == STRUCT_CONST) {
enum {
INITIAL,
DURING
};
switch (Status) {
case INITIAL:
Begin_Mode(BSTATE_ACTIVE);
Status = DURING;
break;
case DURING:
if (!In_Radio_Contact()) {
Assign_Mission(MISSION_GUARD);
}
break;
default:
break;
}
return(1);
}
if (*this == STRUCT_REPAIR) {
enum {
INITIAL,
IDLE,
DURING
};
switch (Status) {
case INITIAL:
{
if (!In_Radio_Contact()) {
Begin_Mode(BSTATE_IDLE);
Assign_Mission(MISSION_GUARD);
return(1);
}
IsReadyToCommence = false;
int distance = 0x10;
TechnoClass *tech = Contact_With_Whom();
/*
** BG: If the unit to repair is an aircraft, and the aircraft is
** fixed-wing, and it's landed, be much more liberal with the
** distance check. Fixed-wing aircraft are very inaccurate with
** their landings.
*/
if (tech->What_Am_I() == RTTI_AIRCRAFT) {
if ( ((AircraftClass *)tech)->Class->IsFixedWing &&
((AircraftClass *)tech)->In_Which_Layer() == LAYER_GROUND) {
distance = 0x80;
}
}
if (Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER && Distance(Contact_With_Whom()) < distance) {
Status = IDLE;
return(TICKS_PER_SECOND/4);
}
break;
}
case IDLE:
if (!In_Radio_Contact()) {
Assign_Mission(MISSION_GUARD);
return(1);
}
if (Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER) {
TechnoClass * radio = Contact_With_Whom();
if ( ((radio->Health_Ratio() < Rule.ConditionGreen) ||
(radio->What_Am_I() == RTTI_UNIT && *(UnitClass *)radio == UNIT_MINELAYER))
&& Transmit_Message(RADIO_REPAIR) == RADIO_ROGER) {
/*
** If the object over the repair bay is marked as useless, then
** sell it back to get some money.
*/
if (radio->IsUseless) {
if (!radio->House->IsHuman) {
radio->Sell_Back(1);
}
Status = INITIAL;
IsReadyToCommence = true;
} else {
//
// MBL 04.27.2020: Legacy VOX_REPAIRING seems to be never called in TD, but only in RA.
// It is currently supported as a client GUI event when standard repairing begins, with "REPAIR1" on both TD and RA
//
// This repairing is in reference to the repair bay
// There is a newer bug (https://jaas.ea.com/browse/TDRA-6224) reporting that it is heard in multiplayer by
// other players, from this call, so modifiying the original call here:
//
// if (IsOwnedByPlayer) Speak(VOX_REPAIRING);
if (IsOwnedByPlayer) Speak(VOX_REPAIRING, House);
Status = DURING;
Begin_Mode(BSTATE_ACTIVE);
IsReadyToCommence = false;
}
} else {
// Transmit_Message(RADIO_RUN_AWAY);
///*BG*/ if(radio->Health_Ratio() >= Rule.ConditionGreen) {
// Transmit_Message(RADIO_RUN_AWAY);
// }
}
}
break;
case DURING:
if (!In_Radio_Contact()) {
Begin_Mode(BSTATE_IDLE);
Status = IDLE;
return(1);
}
/*
** Check to see if the repair light blink has completed and the attached
** unit is not doing something else. If these conditions are favorable,
** the repair can proceed another step.
*/
if (IsReadyToCommence && Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER) {
IsReadyToCommence = false;
/*
** Tell the attached unit to repair one step. It will respond with how
** it fared.
*/
switch (Transmit_Message(RADIO_REPAIR)) {
/*
** The repair step proceeded smoothly. Proceed normally with the
** repair process.
*/
case RADIO_ROGER:
break;
/*
** The repair operation was aborted because of some reason. Presume
** that the reason is because of low cash.
*/
case RADIO_CANT:
if (IsOwnedByPlayer) Speak(VOX_NO_CASH);
Begin_Mode(BSTATE_IDLE);
Status = IDLE;
break;
/*
** The repair step resulted in a completely repaired unit.
*/
case RADIO_ALL_DONE:
// MBL 04.27.2020: Make only audible to the correct player
// if (IsOwnedByPlayer) Speak(VOX_UNIT_REPAIRED);
if (IsOwnedByPlayer) Speak(VOX_UNIT_REPAIRED, House);
// Transmit_Message(RADIO_RUN_AWAY);
Begin_Mode(BSTATE_IDLE);
Status = IDLE;
break;
/*
** The repair step could not be completed because this unit is already
** at full strength.
*/
case RADIO_NEGATIVE:
default:
// Transmit_Message(RADIO_RUN_AWAY);
Begin_Mode(BSTATE_IDLE);
Status = IDLE;
break;
}
}
return(1);
default:
break;
}
return(MissionControl[Mission].Normal_Delay());
}
if (*this == STRUCT_HELIPAD || *this == STRUCT_AIRSTRIP) {
enum {
INITIAL,
DURING
};
switch (Status) {
case INITIAL:
if (Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER && Transmit_Message(RADIO_PREPARED) == RADIO_NEGATIVE) {
Begin_Mode(BSTATE_ACTIVE);
Contact_With_Whom()->Assign_Mission(MISSION_SLEEP);
Status = DURING;
return(1);
}
Assign_Mission(MISSION_GUARD);
break;
case DURING:
if (IsReadyToCommence) {
if (!In_Radio_Contact() || Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_NEGATIVE) {
Assign_Mission(MISSION_GUARD);
return(1);
}
if (Transmit_Message(RADIO_PREPARED) == RADIO_ROGER) {
Contact_With_Whom()->Assign_Mission(MISSION_GUARD);
Assign_Mission(MISSION_GUARD);
return(1);
}
if (Transmit_Message(RADIO_RELOAD) != RADIO_ROGER) {
Assign_Mission(MISSION_GUARD);
Contact_With_Whom()->Assign_Mission(MISSION_GUARD);
return(1);
} else {
fixed pfrac = Saturate(House->Power_Fraction(), 1);
if (pfrac < fixed::_1_2) pfrac = fixed::_1_2;
int time = Inverse(pfrac) * Rule.ReloadRate * TICKS_PER_MINUTE;
// int time = Bound((int)(TICKS_PER_SECOND * Saturate(House->Power_Fraction(), 1)), 0, TICKS_PER_SECOND);
// time = (TICKS_PER_SECOND*3) - time;
IsReadyToCommence = false;
return(time);
}
}
break;
default:
break;
}
return(3);
}
return(TICKS_PER_SECOND);
}
/***********************************************************************************************
* BuildingClass::Mission_Missile -- State machine for nuclear missile launch. *
* *
* This handles the Temple of Nod launching its nuclear missile. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the number of frames to delay before calling this routine again. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/04/1995 JLB : Commented. *
*=============================================================================================*/
int BuildingClass::Mission_Missile(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (*this == STRUCT_ADVANCED_TECH) {
enum {
DOOR_OPENING,
LAUNCH_UP,
SATELLITE_DEPLOY,
DONE_LAUNCH
};
switch (Status) {
/*
** The initial case is responsible for starting the door
** opening on the building, the missile rising, and smoke broiling.
*/
case DOOR_OPENING:
{
#ifdef FIXIT_VERSION_3
COORDINATE door = Coord_Move(Center_Coord(), (DirType)0xC0, 0x30);
AnimClass * sput = new AnimClass(ANIM_SPUTDOOR, door);
if (sput) {
IsReadyToCommence = false;
Status = LAUNCH_UP;
AnimToTrack = sput->As_Target();
}
#else
IsReadyToCommence = false;
COORDINATE door = Coord_Move(Center_Coord(), (DirType)0xC0, 0x30);
AnimClass * sput = new AnimClass(ANIM_SPUTDOOR, door);
Status = LAUNCH_UP;
AnimToTrack = sput->As_Target();
return(1);
#endif
}
/*
** Once the smoke has been going for a little while this
** actually handles launching the missile into the air.
*/
case LAUNCH_UP:
{
AnimClass * sput = As_Animation(AnimToTrack);
if (sput) {
if (sput->Fetch_Stage() >= 19) {
CELL center = Coord_Cell(Center_Coord());
CELL cell = XY_Cell( Cell_X(center), 1);
TARGET targ = ::As_Target(cell);
BulletClass * bullet = new BulletClass(BULLET_GPS_SATELLITE, targ, this, 200, WARHEAD_FIRE, MPH_ROCKET);
if (bullet) {
COORDINATE launch = Coord_Move(Center_Coord(), (DirType)0xC0, 0x30);
if (!bullet->Unlimbo(launch, DIR_N)) {
delete bullet;
bullet = NULL;
}
}
if (bullet) {
Assign_Mission(MISSION_GUARD);
}
}
}
}
return(1);
}
}
if (*this == STRUCT_MSLO) {
enum {
INITIAL,
DOOR_OPENING,
LAUNCH_UP,
LAUNCH_DOWN,
DONE_LAUNCH
};
switch (Status) {
/*
** The initial case is responsible for starting the door
** opening on the building.
*/
case INITIAL:
IsReadyToCommence = false;
Begin_Mode(BSTATE_ACTIVE); // open the door
Status = DOOR_OPENING;
return(1);
/*
** This polls for the case when the door is actually open and
** then kicks off the missile smoke.
*/
case DOOR_OPENING:
if (IsReadyToCommence) {
Begin_Mode(BSTATE_AUX1); // hold the door open
Status = LAUNCH_UP;
return(14);
}
return(1);
/*
** Once the smoke has been going for a little while this
** actually handles launching the missile into the air.
*/
case LAUNCH_UP:
{
CELL center = Coord_Cell(Center_Coord());
CELL cell = XY_Cell( Cell_X(center), 1);
TARGET targ = ::As_Target(cell);
BulletClass * bullet = new BulletClass(BULLET_NUKE_UP, targ, this, 200, WARHEAD_HE, MPH_VERY_FAST);
if (bullet) {
COORDINATE launch = Coord_Move(Center_Coord(), (DirType)28, 0xA0);
if (!bullet->Unlimbo(launch, DIR_N)) {
delete bullet;
bullet = NULL;
}
}
if (bullet) {
Speak(VOX_ABOMB_LAUNCH);
Status = LAUNCH_DOWN;
/*
** Hack: If it's the artificial nukes, don't let the bullets come down (as
** they're the only ones that blow up). We know it's artificial if you're
** at tech level 10 or below, because you can't build the nuclear silo until
** tech level 15 or so.
*/
if (House->Control.TechLevel <= 10) {
return(6);
}
bullet = new BulletClass(BULLET_NUKE_DOWN, ::As_Target(House->NukeDest), this, 200, WARHEAD_NUKE, MPH_VERY_FAST);
if (bullet) {
int celly = Cell_Y(House->NukeDest);
celly -= 64;
if (celly < 1) celly = 1;
COORDINATE start = Cell_Coord(XY_Cell(Cell_X(House->NukeDest), celly));
if (!bullet->Unlimbo(start, DIR_S)) {
delete bullet;
}
}
return(8 * TICKS_PER_SECOND);
}
}
return(1);
/*
** Once the missile is in the air, this handles waiting for
** the missile to be off the screen and then launching one down
** over the target.
*/
case LAUNCH_DOWN:
{
Begin_Mode(BSTATE_AUX2); // start the door closing
#ifdef OBSOLETE
/*
** Hack: If it's the artificial nukes, don't let the bullets come down (as
** they're the only ones that blow up). We know it's artificial if you're
** at tech level 10 or below, because you can't build the nuclear silo until
** tech level 15 or so.
*/
if (House->Control.TechLevel <= 10) {
Status = DONE_LAUNCH;
return(6);
}
BulletClass * bullet = new BulletClass(BULLET_NUKE_DOWN, ::As_Target(House->NukeDest), this, 200, WARHEAD_NUKE, MPH_VERY_FAST);
if (bullet) {
int celly = Cell_Y(House->NukeDest);
celly -= 15;
if (celly < 1) celly = 1;
COORDINATE start = Cell_Coord(XY_Cell(Cell_X(House->NukeDest), celly));
if (!bullet->Unlimbo(start, DIR_S)) {
delete bullet;
}
}
if (bullet) {
#endif
Status = DONE_LAUNCH;
return(6);
}
#ifdef OBSOLETE
}
return(1);
#endif
/*
** Once the missile is done launching this handles allowing
** the building to sit there with its door closed.
*/
case DONE_LAUNCH:
Begin_Mode(BSTATE_IDLE); // keep the door closed.
Assign_Mission(MISSION_GUARD);
return(60);
}
}
return(MissionControl[Mission].Normal_Delay());
}
/***********************************************************************************************
* BuildingClass::Revealed -- Reveals the building to the specified house. *
* *
* This routine will reveal the building to the specified house. It will handle updating *
* the sidebar for player owned buildings. A player owned building that hasn't been *
* revealed, is in a state of pseudo-limbo. It cannot be used for any of its special *
* abilities even though it exists on the map for all other purposes. *
* *
* INPUT: house -- The house that this building is being revealed to. *
* *
* OUTPUT: Was this building revealed by this procedure? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
*=============================================================================================*/
bool BuildingClass::Revealed(HouseClass * house)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (TechnoClass::Revealed(house)) {
if (!ScenarioInit) {
House->JustBuiltStructure = Class->Type;
House->IsBuiltSomething = true;
}
House->IsRecalcNeeded = true;
/*
** Perform any grand opening here so that in the scenarios where a player
** owned house is not yet revealed, it won't be reflected in the sidebar
** selection icons.
*/
if (!In_Radio_Contact() && House->IsHuman && Mission != MISSION_CONSTRUCTION) {
Grand_Opening();
} else {
if (!In_Radio_Contact() && !House->IsHuman && house == House && Mission != MISSION_CONSTRUCTION) {
Grand_Opening();
}
}
return(true);
}
return(false);
}
/***********************************************************************************************
* BuildingClass::Enter_Idle_Mode -- The building will enter its idle mode. *
* *
* This routine is called when the exact mode of the building isn't known. By examining *
* the building's condition, this routine will assign an appropriate mission. *
* *
* INPUT: initial -- This this being called during scenario init? *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/25/1995 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Enter_Idle_Mode(bool initial)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
/*
** Assign an appropriate mission for the building. If the ScenarioInit flag is true, then
** this must be an initial building. Start such buildings in idle state. For other buildings
** it indicates that it is being placed during game play and thus it must start in
** the "construction" mission.
*/
MissionType mission = MISSION_GUARD;
if (!initial || ScenarioInit || Debug_Map) {
Begin_Mode(BSTATE_IDLE);
mission = MISSION_GUARD;
} else {
Begin_Mode(BSTATE_CONSTRUCTION);
mission = MISSION_CONSTRUCTION;
}
Assign_Mission(mission);
}
/***********************************************************************************************
* BuildingClass::Pip_Count -- Determines "full" pips to display for building. *
* *
* This routine will determine the number of pips that should be filled in when rendering *
* the building. *
* *
* INPUT: none *
* *
* OUTPUT: Returns the number of pips to display as filled in. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 06/28/1995 JLB : Created. *
*=============================================================================================*/
int BuildingClass::Pip_Count(void) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
return(Class->Max_Pips() * House->Tiberium_Fraction());
}
/***********************************************************************************************
* BuildingClass::Death_Announcement -- Announce the death of this building. *
* *
* This routine is called when the building is destroyed by "unnatural" means. Typically *
* as a result of combat. If the building is known to the player, then it should be *
* announced. *
* *
* INPUT: source -- The object most directly responsible for the building's death. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/04/1995 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Death_Announcement(TechnoClass const * source) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (source != NULL && House->IsPlayerControl) {
Speak(VOX_STRUCTURE_DESTROYED);
}
}
/***********************************************************************************************
* BuildingClass::Fire_Direction -- Fetches the direction of firing. *
* *
* This routine will return with the default direction to use when firing from this *
* building. This is the facing of the turret except for the case of non-turret equipped *
* buildings that have a weapon (e.g., guard tower). *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the default firing direction for this building. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/04/1995 JLB : Created. *
*=============================================================================================*/
DirType BuildingClass::Fire_Direction(void) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (Class->IsTurretEquipped) {
return(PrimaryFacing.Current());
}
return(Direction(TarCom));
}
/***********************************************************************************************
* BuildingClass::Remap_Table -- Fetches the remap table to use for this building. *
* *
* Use this routine to fetch the remap table to use. This override function is needed *
* because the default remap table for techno objects presumes the object is a unit. *
* Buildings aren't units. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the proper remap table to use for this building. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/08/1995 JLB : Created. *
*=============================================================================================*/
void const * BuildingClass::Remap_Table(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
return(House->Remap_Table(IsBlushing, Class->Remap));
}
/***********************************************************************************************
* BuildingClass::Mission_Unload -- Handles the unload mission for a building. *
* *
* This is the unload mission for a building. This really only applies to the weapon's *
* factory, since it needs the sophistication of an unload mission due to the door *
* animation. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the number of game frames to delay before calling this routine *
* again. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/29/1995 JLB : Created. *
*=============================================================================================*/
int BuildingClass::Mission_Unload(void)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (*this == STRUCT_WEAP) {
CELL cell = Coord_Cell(Coord) + Class->ExitList[0];
COORDINATE coord = Cell_Coord(cell);
CellClass * cellptr = &Map[cell];
enum {
INITIAL,
CLEAR_BIB,
OPEN,
LEAVE,
CLOSE
};
enum {
DOOR_STAGES = 5,
DOOR_RATE = 8
};
UnitClass * unit;
switch (Status) {
/*
** Start the door opening.
*/
case INITIAL:
// if (cellptr->Cell_Techno()) {
// cellptr->Incoming(0, true);
// }
unit = (UnitClass *)Contact_With_Whom();
if (unit) {
unit->Assign_Mission(MISSION_GUARD);
unit->Commence();
}
Open_Door(DOOR_RATE, DOOR_STAGES);
Status = CLEAR_BIB;
break;
/*
** Now that the occupants can peek out the door, they will tell
** everyone that could be blocking the way, that they should
** scatter away.
*/
case CLEAR_BIB:
if (cellptr->Cell_Techno()) {
cellptr->Incoming(0, true, true);
/*
** Scatter everything around the weapon's factory door.
*/
for (FacingType f = FACING_FIRST; f < FACING_COUNT; f++) {
Command & Conquer Remastered post-launch patch Improvements to harvester resource finding logic. Don't allow the Advanced Comm Center to be capturable in skirmish or multiplayer. Increased failed pathfinding fudge factor. Buildings accept the Guard command if they can attack. Don't allow force capturing of ally structures. Fixes for laser Orcas in S3cr3t M1ss10n. Properly restore them after save. Reset Orcas after loads. Fixed flag animation rendering in CTF. Potentially fix a crash if aircraft are destroyed outside the map bounds. Fixed legacy Obelisk line rendering. Fix out-of-bounds crash in TD; issue was already fixed in RA. Disable capture flag on Commandos. Drop the flag when entering the limbo state. Fixed end game race condition, winning team ID is always sent before individual player win/lose messages. Fixed Chan spawn on SCB10EA. Don't show enter cursor for enemy units on refineries and repair pads. Changing right-click support for first put building on hold, and then subsequenct right-clicks to decrement that queue count for 1x or 5x; Then, 1x or 5x Left click will resume from hold. Don't debug reveal legacy rendering when a player is defeated. Fixed crash when loading saves of custom campaign maps. Reset harvester archived target when given a direct harvest order. Prevent NOD cargo planes from being force attacked. Fixed unit selection on load. Migrated queued repair pad functionality from RA to TD. Randomly animate infantry in area guard mode. Fixed crash accessing inactive objects. Added some walls in SCG08EB to prevent civilians from killing themselves. TD + RA: Audio: Overiding "Our base is under attack" cooldown timing from legacy from 2 minutes to 30 seconds, so it will be heard 4x as often. Fixed adjacent cell out-of-bounds crash issues. Kill player on disconnect Fixed and improved build time calculations to be consistent between TD and RA. Don't show health bars for cloaked Stealth Tanks in the legacy renderer. Fix selection of individual control groups after mixed selection. More adjustments to SCG08EB; switch C7 to C5, and add civilian AI to avoid Tiberium. Extra safety checks for units that have no weapons and aircraft that can't hunt. Fix loading of multiple infantry onto an APC. Additional safety checks for invalid coordinates. Prevent units from being instantly repaired. Fix map passability. Fail Allied mission 5B if the spy re-boards the starting transport (matches 5A and 5C behavior). Fixed multiplayer formation move causing units to move at light speed. Ignore movement destination checks if a unit is part of a mission-driven team. Fix buffer overrun crash. Ignore mines when determining win conditions. Fixed river passability in Blue Lakes.
2020-06-23 04:43:21 +12:00
CellClass * cptr = cellptr->Adjacent_Cell(f);
if (cptr && cptr->Cell_Building() == NULL) {
cptr->Incoming(coord, true, true);
}
}
} else {
Status = OPEN;
}
break;
/*
** When the door is finally open and the way is clear, tell the
** unit to drive out.
*/
case OPEN:
if (Is_Door_Open()) {
unit = (UnitClass *)Contact_With_Whom();
if (unit) {
unit->Assign_Mission(MISSION_MOVE);
if (House->IQ >= Rule.IQGuardArea) {
unit->Assign_Mission(MISSION_GUARD_AREA);
unit->ArchiveTarget = ::As_Target(House->Where_To_Go(unit));
}
unit->Force_Track(DriveClass::OUT_OF_WEAPON_FACTORY, coord);
// unit->Force_Track(DriveClass::OUT_OF_WEAPON_FACTORY, Adjacent_Cell(Adjacent_Cell(Center_Coord(), FACING_S), FACING_S));
unit->Set_Speed(128);
Status = LEAVE;
} else {
Close_Door(DOOR_RATE, DOOR_STAGES);
Status = CLOSE;
}
}
break;
/*
** Wait until the unit has completely left the building.
*/
case LEAVE:
if (!IsTethered) {
Close_Door(DOOR_RATE, DOOR_STAGES);
Status = CLOSE;
} else {
// if (In_Radio_Contact() && !((FootClass *)Contact_With_Whom())->IsDriving) {
// Transmit_Message(RADIO_OVER_OUT);
// }
}
break;
/*
** Wait while the door closes.
*/
case CLOSE:
if (Is_Door_Closed()) {
Enter_Idle_Mode();
}
break;
default:
break;
}
return(MissionControl[Mission].Normal_Delay() + Random_Pick(0, 2));
}
Assign_Mission(MISSION_GUARD);
return(1);
}
/***********************************************************************************************
* BuildingClass::Power_Output -- Fetches the current power output from this building. *
* *
* This routine will return the current power output for this building. The power output *
* is adjusted according to the damage level of the building. *
* *
* INPUT: none *
* *
* OUTPUT: Returns the current power output for this building. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/29/1995 JLB : Created. *
*=============================================================================================*/
int BuildingClass::Power_Output(void) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (Class->Power) {
return(Class->Power * fixed(LastStrength, Class->MaxStrength));
}
return(0);
}
/***********************************************************************************************
* BuildingClass::Detach -- Handles target removal from the game system. *
* *
* This routine is called when the specified target is about to be removed from the game *
* system. *
* *
* INPUT: target -- The target to be removed from this building's targeting computer. *
* *
* all -- Is the target about to be completely eliminated? *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/29/1995 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Detach(TARGET target, bool all)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
TechnoClass::Detach(target, all);
if (target == WhomToRepay) {
WhomToRepay = TARGET_NONE;
}
if (target == AnimToTrack) {
AnimToTrack = TARGET_NONE;
}
}
/***********************************************************************************************
* BuildingClass::Crew_Type -- This determines the crew that this object generates. *
* *
* When selling very cheap buildings (such as the silo), a technician will pop out since *
* generating minigunners would be overkill -- the player could use this loophole to *
* gain an advantage. *
* *
* INPUT: none *
* *
* OUTPUT: Returns the infantry type that this building will generate as a survivor. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/05/1995 JLB : Created. *
*=============================================================================================*/
InfantryType BuildingClass::Crew_Type(void) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
switch (Class->Type) {
case STRUCT_STORAGE:
if (Percent_Chance(50)) {
return(INFANTRY_C1);
} else {
return(INFANTRY_C7);
}
case STRUCT_CONST:
if (!IsCaptured && House->IsHuman && Percent_Chance(25)) {
return(INFANTRY_RENOVATOR);
}
break;
case STRUCT_KENNEL:
if (Percent_Chance(50)) {
return(INFANTRY_DOG);
} else {
return(INFANTRY_NONE);
}
case STRUCT_TENT:
case STRUCT_BARRACKS:
return(INFANTRY_E1);
default:
break;
}
return(TechnoClass::Crew_Type());
}
/***********************************************************************************************
* BuildingClass::Detach_All -- Possibly abandons production according to factory type. *
* *
* When this routine is called, it indicates that the building is about to be destroyed *
* or captured. In such a case any production it may be doing, must be abandoned. *
* *
* INPUT: all -- Is the object about the be completely destroyed? *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/05/1995 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Detach_All(bool all)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
/*
** If it is producing something, then it must be abandoned.
*/
if (Factory) {
Factory->Abandon();
delete (FactoryClass *)Factory;
Factory = 0;
}
/*
** If the owner HouseClass is building something, and this building can
** build that thing, we may be the last building for that house that can
** build that thing; if so, abandon production of it.
*/
if (House) {
FactoryClass * factory = House->Fetch_Factory(Class->ToBuild);
/*
** If a factory was found, then temporarily disable this building and then
** determine if any object that is being produced can still be produced. If
** not, then the object being produced must be abandoned.
*/
if (factory) {
TechnoClass * object = factory->Get_Object();
IsInLimbo = true;
if (object && !object->Techno_Type_Class()->Who_Can_Build_Me(true, false, House->Class->House)) {
House->Abandon_Production(Class->ToBuild);
}
IsInLimbo = false;
}
}
TechnoClass::Detach_All(all);
}
/***********************************************************************************************
* BuildingClass::Flush_For_Placement -- Handles clearing a zone for object placement. *
* *
* This routine is used to clear the way for placement of the specified object (usually *
* a building). If there are friendly units blocking the placement area, they are told *
* to scatter. Enemy blocking units are attacked. *
* *
* INPUT: techno -- Pointer to the object that is desired to be placed. *
* *
* cell -- The cell that placement wants to occur at. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/06/1995 JLB : Created. *
* 09/27/1995 JLB : Revised to use type class function. *
*=============================================================================================*/
bool BuildingClass::Flush_For_Placement(TechnoClass * techno, CELL cell)
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (techno) {
return (((BuildingTypeClass const &)techno->Class_Of()).Flush_For_Placement(cell, House));
}
return(false);
}
/***********************************************************************************************
* BuildingClass::Find_Exit_Cell -- Find a clear location to exit an object from this building *
* *
* This routine is called when the building needs to discharge a unit. It will find a *
* nearby (adjacent) cell that is clear enough for the specified object to enter. Typical *
* use of this routine is when the airfield disgorges its cargo. *
* *
* INPUT: techno -- Pointer to the object that wishes to exit this building. *
* *
* OUTPUT: Returns with the cell number to use for object placement. If no free location *
* could be found, then zero (0) is returned. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 09/21/1995 JLB : Created. *
* 02/20/1996 JLB : Added default case for exit cell calculation. *
*=============================================================================================*/
CELL BuildingClass::Find_Exit_Cell(TechnoClass const * techno) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
CELL const * ptr;
CELL origin = Coord_Cell(Coord);
ptr = Class->ExitList;
if (ptr != NULL) {
while (*ptr != REFRESH_EOL) {
CELL cell = origin + *ptr++;
if (Map.In_Radar(cell) && techno->Can_Enter_Cell(cell) == MOVE_OK) {
return(cell);
}
}
} else {
int x1, x2;
int y1, y2;
CELL cell;
y1 = -1;
y2 = Class->Height();
for (x1 = -1; x1 <= Class->Width(); x1++) {
cell = origin + x1 + (y1 * MAP_CELL_W);
if (Map.In_Radar(cell) && techno->Can_Enter_Cell(cell) == MOVE_OK) {
return(cell);
}
cell = origin + x1 + (y2 * MAP_CELL_W);
if (Map.In_Radar(cell) && techno->Can_Enter_Cell(cell) == MOVE_OK) {
return(cell);
}
}
x1 = -1;
x2 = Class->Width();
for (y1 = -1; y1 <= Class->Height(); y1++) {
cell = origin + (y1 * MAP_CELL_W) + x1;
if (Map.In_Radar(cell) && techno->Can_Enter_Cell(cell) == MOVE_OK) {
return(cell);
}
cell = origin + (y1 * MAP_CELL_W) + x2;
if (Map.In_Radar(cell) && techno->Can_Enter_Cell(cell) == MOVE_OK) {
return(cell);
}
}
}
return(0);
}
/***********************************************************************************************
* BuildingClass::Can_Player_Move -- Can this building be moved? *
* *
* This routine answers the question 'can this building be moved?' Typically, only the *
* construction yard can be moved and it does this by undeploying back into a MCV. *
* *
* INPUT: none *
* *
* OUTPUT: Can the building move to a new location under player control? *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 10/04/1995 JLB : Created. *
*=============================================================================================*/
bool BuildingClass::Can_Player_Move(void) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
return(*this == STRUCT_CONST && (Mission == MISSION_GUARD) && Special.IsMCVDeploy);
}
/***********************************************************************************************
* BuildingClass::Exit_Coord -- Determines location where object will leave it. *
* *
* This routine will return the coordinate where an object that wishes to leave the *
* building will exit at. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the coordinate that the object should be created at. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 02/20/1996 JLB : Created. *
*=============================================================================================*/
COORDINATE BuildingClass::Exit_Coord(void) const
{
assert(Buildings.ID(this) == ID);
assert(IsActive);
if (Class->ExitCoordinate) {
return(Coord_Add(Coord, Class->ExitCoordinate));
}
return(TechnoClass::Exit_Coord());
}
/***********************************************************************************************
* BuildingClass::Check_Point -- Fetches the landing checkpoint for the given flight pattern. *
* *
* Use this routine to coordinate a landing operation. The specified checkpoint is *
* converted into a cell number. The landing aircraft should fly over that cell and then *
* request the next check point. *
* *
* INPUT: cp -- The check point to convert to a cell number. *
* *
* OUTPUT: Returns with the cell that the aircraft should fly over in order to complete *
* that portion of the landing pattern. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/06/1996 JLB : Created. *
*=============================================================================================*/
CELL BuildingClass::Check_Point(CheckPointType cp) const
{
CELL xoffset = 6; // Downwind offset.
CELL yoffset = 5; // Crosswind offset.
CELL cell = Coord_Cell(Center_Coord());
switch (cp) {
case CHECK_STACK:
xoffset = 0;
break;
case CHECK_CROSSWIND:
yoffset = 0;
break;
case CHECK_DOWNWIND:
default:
break;
}
if ((Cell_X(cell) - Map.MapCellX) > Map.MapCellWidth/2) {
xoffset = -xoffset;
}
if ((Cell_Y(cell) - Map.MapCellY) > Map.MapCellHeight/2) {
yoffset = -yoffset;
}
return(XY_Cell(Cell_X(cell)+xoffset, Cell_Y(cell)+yoffset));
}
/***********************************************************************************************
* BuildingClass::Update_Radar_Spied - set house's RadarSpied field appropriately. *
* *
* This routine is called when a radar facility is captured or destroyed. It fills in the *
* RadarSpied field of the house based on whether there's a spied-upon radar facility or not*
* *
* INPUT: none *
* *
* OUTPUT: House->RadarSpied field gets set appropriately. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 03/22/1996 BWG : Created. *
*=============================================================================================*/
void BuildingClass::Update_Radar_Spied(void)
{
House->RadarSpied = 0;
for (int index = 0; index < Buildings.Count(); index++) {
BuildingClass * obj = Buildings.Ptr(index);
if (obj && !obj->IsInLimbo && obj->House == House) {
if (*obj == STRUCT_RADAR /* || *obj == STRUCT_EYE */) {
House->RadarSpied |= obj->Spied_By();
}
}
}
Map.RadarClass::Flag_To_Redraw(true);
}
/***********************************************************************************************
* BuildingClass::Read_INI -- Reads buildings from INI file. *
* *
* This is the basic scenario initialization of building function. It *
* is called when reading the scenario startup INI file and it handles *
* creation of all specified buildings. *
* *
* INI entry format: *
* Housename, Typename, Strength, Cell, Facing, Triggername *
* *
* INPUT: buffer -- Pointer to the loaded INI file data. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 05/24/1994 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Read_INI(CCINIClass & ini)
{
BuildingClass * b; // Working unit pointer.
HousesType bhouse; // Building house.
StructType classid; // Building type.
CELL cell; // Cell of building.
char buf[128];
char * trigname; // building's trigger's name
int len = ini.Entry_Count(INI_Name());
for (int index = 0; index < len; index++) {
char const * entry = ini.Get_Entry(INI_Name(), index);
/*
** Get a building entry.
*/
ini.Get_String(INI_Name(), entry, NULL, buf, sizeof(buf));
/*
** 1st token: house name.
*/
bhouse = HouseTypeClass::From_Name(strtok(buf, ","));
/*
** 2nd token: building name.
*/
classid = BuildingTypeClass::From_Name(strtok(NULL, ","));
if (bhouse != HOUSE_NONE && classid != STRUCT_NONE) {
int strength;
DirType facing;
/*
** 3rd token: strength.
*/
strength = atoi(strtok(NULL, ","));
/*
** 4th token: cell #.
*/
cell = atoi(strtok(NULL, ","));
/*
** 5th token: facing.
*/
facing = (DirType)atoi(strtok(NULL, ","));
/*
** 6th token: triggername (can be NULL).
*/
trigname = strtok(NULL, ",");
bool sellable = false;
char * token_pointer = strtok(NULL, ",");
if (token_pointer) {
sellable = atoi(token_pointer);
}
bool rebuild = false;
token_pointer = strtok(NULL, ",");
if (token_pointer) {
rebuild = atoi(token_pointer);
}
if (HouseClass::As_Pointer(bhouse) != NULL) {
b = new BuildingClass(classid, bhouse);
if (b) {
TriggerTypeClass * tp = TriggerTypeClass::From_Name(trigname);
if (tp) {
TriggerClass * tt = Find_Or_Make(tp);
if (tt) {
tt->AttachCount++;
b->Trigger = tt;
}
}
b->IsAllowedToSell = sellable;
b->IsToRebuild = rebuild;
b->IsToRepair = rebuild || *b == STRUCT_CONST;
if (b->Unlimbo(Cell_Coord(cell), facing)) {
strength = min(strength, 0x100);
strength = (int)b->Class->MaxStrength * fixed(strength, 256); // Cast to (int). ST - 5/8/2019
b->Strength = strength;
if (b->Strength > b->Class->MaxStrength-3) b->Strength = b->Class->MaxStrength;
b->IsALemon = false;
} else {
/*
** If the building could not be unlimboed on the map, then this indicates
** a serious error. Delete the building.
*/
delete b;
}
}
}
}
}
}
/***********************************************************************************************
* BuildingClass::Write_INI -- Write out the building data to the INI file specified. *
* *
* This will store the building data (as it relates to scenario initialization) to the *
* INI database specified. *
* *
* INPUT: ini -- Reference to the INI database that the building data will be stored to. *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/06/1996 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Write_INI(CCINIClass & ini)
{
/*
** First, clear out all existing building data from the ini file.
*/
ini.Clear(INI_Name());
/*
** Write the data out.
*/
for (int index = 0; index < Buildings.Count(); index++) {
BuildingClass * building = Buildings.Ptr(index);
if (!building->IsInLimbo) {
char uname[10];
char buf[127];
sprintf(uname, "%d", index);
sprintf(buf, "%s,%s,%d,%u,%d,%s,%d,%d",
building->House->Class->IniName,
building->Class->IniName,
building->Health_Ratio()*256,
Coord_Cell(building->Coord),
building->PrimaryFacing.Current(),
building->Trigger.Is_Valid() ? building->Trigger->Class->IniName : "None",
building->IsAllowedToSell,
building->IsToRebuild
);
ini.Put_String(INI_Name(), uname, buf);
}
}
}
/***********************************************************************************************
* BuildingClass::Target_Coord -- Return the coordinate to use when firing on this building. *
* *
* This routine will determine the "center" location of this building for purposes of *
* targeting. Usually, this location is somewhere near the foundation of the building. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the coordinate to use when firing upon this building (or trying to *
* walk onto it). *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/19/1996 JLB : Created. *
*=============================================================================================*/
COORDINATE BuildingClass::Target_Coord(void) const
{
COORDINATE coord = Center_Coord();
if (Class->FoundationFace != FACING_NONE) {
return(Adjacent_Cell(coord, Class->FoundationFace));
}
return(coord);
}
/***********************************************************************************************
* BuildingClass::Factory_AI -- Handle factory production and initiation. *
* *
* Some building (notably the computer controlled ones) can have a factory object attached. *
* This routine handles processing of that factory and also detecting when production *
* should begin in order to initiate production. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: Only call this routine once per building per game logic loop. *
* *
* HISTORY: *
* 07/29/1996 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Factory_AI(void)
{
/*
** Handle any production tied to this building. Only computer controlled buildings have
** production attached to the building itself. The player uses the sidebar interface for
** all production control.
*/
if (Factory.Is_Valid() && Factory->Has_Completed() && PlacementDelay == 0) {
TechnoClass * product = Factory->Get_Object();
// FactoryClass * fact = Factory;
switch (Exit_Object(product)) {
/*
** If the object could not leave the factory, then either request
** a transport, place the (what must be a) building using another method, or
** abort the production and refund money.
*/
case 0:
Factory->Abandon();
delete (FactoryClass *)Factory;
Factory = 0;
break;
/*
** Exiting this building is prevented by some temporary blockage. Wait
** a bit before trying again.
*/
case 1:
PlacementDelay = TICKS_PER_SECOND*3;
break;
/*
** The object was successfully sent from this factory. Inform the house
** tracking logic that the requested object has been produced.
*/
case 2:
switch (product->What_Am_I()) {
case RTTI_VESSEL:
House->JustBuiltVessel = ((VesselClass*)product)->Class->Type;
House->IsBuiltSomething = true;
break;
case RTTI_UNIT:
House->JustBuiltUnit = ((UnitClass*)product)->Class->Type;
House->IsBuiltSomething = true;
break;
case RTTI_INFANTRY:
House->JustBuiltInfantry = ((InfantryClass*)product)->Class->Type;
House->IsBuiltSomething = true;
break;
case RTTI_BUILDING:
House->JustBuiltStructure = ((BuildingClass*)product)->Class->Type;
House->IsBuiltSomething = true;
break;
case RTTI_AIRCRAFT:
House->JustBuiltAircraft = ((AircraftClass*)product)->Class->Type;
House->IsBuiltSomething = true;
break;
default:
break;
}
// fact->Completed();
Factory->Completed();
// delete fact;
delete (FactoryClass *)Factory;
Factory = 0;
break;
default:
break;
}
}
/*
** Pick something to create for this factory.
*/
if (House->IsStarted && Mission != MISSION_CONSTRUCTION && Mission != MISSION_DECONSTRUCTION) {
/*
** Buildings that produce other objects have special factory logic handled here.
*/
if (Class->ToBuild != RTTI_NONE) {
if (Factory.Is_Valid()) {
/*
** If production has halted, then just abort production and make the
** funds available for something else.
*/
if (PlacementDelay == 0 && !Factory->Is_Building()) {
Factory->Abandon();
delete (FactoryClass *)Factory;
Factory = 0;
}
} else {
/*
** Only look to start production if there is at least a small amount of
** money available. In cases where there is no practical money left, then
** production can never complete -- don't bother starting it.
*/
if (House->IsStarted && House->Available_Money() > 10) {
TechnoTypeClass const * techno = House->Suggest_New_Object(Class->ToBuild, *this == STRUCT_KENNEL);
/*
** If a suitable object type was selected for production, then start
** producing it now.
*/
if (techno != NULL) {
Factory = new FactoryClass;
if (Factory.Is_Valid()) {
if (!Factory->Set(*techno, *House)) {
delete (FactoryClass *)Factory;
Factory = 0;
} else {
House->Production_Begun(Factory->Get_Object());
Factory->Start();
}
}
}
}
}
}
}
}
/***********************************************************************************************
* BuildingClass::Rotation_AI -- Process any turret rotation required of this building. *
* *
* Some buildings have a turret and this routine handles processing the turret rotation. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: Only call this routine once per building per game logic loop. *
* *
* HISTORY: *
* 07/29/1996 JLB : Created. *
* 10/27/1996 JLB : Rotation does not occur if power and no power avail. *
*=============================================================================================*/
void BuildingClass::Rotation_AI(void)
{
if (Class->IsTurretEquipped &&
Mission != MISSION_CONSTRUCTION &&
Mission != MISSION_DECONSTRUCTION &&
(!Class->IsPowered || House->Power_Fraction() >= 1)) {
/*
** Rotate turret to match desired facing.
*/
if (PrimaryFacing.Is_Rotating()) {
if (PrimaryFacing.Rotation_Adjust(Class->ROT)) {
Mark(MARK_CHANGE);
}
}
}
}
/***********************************************************************************************
* BuildingClass::Charging_AI -- Handles the special charging logic for Tesla coils. *
* *
* This handles the special logic required of the charging tesla coil. It requires special *
* processing since its charge up is dependant upon the target and power surplus of the *
* owning house. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 07/29/1996 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Charging_AI(void)
{
if (Class->PrimaryWeapon != NULL && Class->PrimaryWeapon->IsElectric && BState != BSTATE_CONSTRUCTION) {
if (Target_Legal(TarCom) && House->Power_Fraction() >= 1) {
if (!IsCharged) {
if (IsCharging) {
// if (stagechange) {
Mark(MARK_CHANGE);
if (Fetch_Stage() >= 9) {
IsCharged = true;
IsCharging = false;
Set_Rate(0);
}
// }
} else if (!Arm) {
IsCharged = false;
IsCharging = true;
Set_Stage(0);
Set_Rate(3);
Sound_Effect(VOC_TESLA_POWER_UP, Coord);
}
}
} else {
if (IsCharging || IsCharged) {
Mark(MARK_CHANGE);
IsCharging = false;
IsCharged = false;
Set_Stage(0);
Set_Rate(0);
}
}
}
}
/***********************************************************************************************
* BuildingClass::Repair_AI -- Handle the repair (and sell) logic for the building. *
* *
* This routine handle the repair animation and healing logic. It also detects when the *
* (computer controlled) building should begin repair or sell itself. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: Only call this routine once per building per game logic loop. *
* *
* HISTORY: *
* 07/29/1996 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Repair_AI(void)
{
if (House->IQ >= Rule.IQRepairSell && Mission != MISSION_CONSTRUCTION && Mission != MISSION_DECONSTRUCTION) {
/*
** Possibly start repair process if the building is below half strength.
*/
// unsigned ratio = MIN(House->Smartness, 0x00F0);
if (Can_Repair()) {
if (House->Available_Money() >= Rule.RepairThreshhold) {
if (!House->DidRepair) {
if (!IsRepairing && (IsCaptured || IsToRepair || House->IsHuman || Session.Type != GAME_NORMAL)) {
House->DidRepair = true; // flag that this house did its repair allocation for this frame
Repair(1);
if (!House->IsHuman) {
House->RepairTimer = Random_Pick((int)(House->RepairDelay * (TICKS_PER_MINUTE/4)), (int)(House->RepairDelay * TICKS_PER_MINUTE * 2));
}
}
}
} else {
if ((Session.Type != GAME_NORMAL || IsAllowedToSell) && IsTickedOff && House->Control.TechLevel >= Rule.IQSellBack && Random_Pick(0, 50) < House->Control.TechLevel && !Trigger.Is_Valid() && *this != STRUCT_CONST && Health_Ratio() < Rule.ConditionRed) {
Sell_Back(1);
}
}
}
}
/*
** If it is repairing, then apply any repair effects as necessary.
*/
if (IsRepairing && (Frame % (Rule.RepairRate * TICKS_PER_MINUTE)) == 0) {
IsWrenchVisible = (IsWrenchVisible == false);
Mark(MARK_CHANGE);
int cost = Class->Repair_Cost();
int step = Class->Repair_Step();
/*
** Check for and expend any necessary monies to continue the repair.
*/
if (House->Available_Money() >= cost) {
House->Spend_Money(cost);
Strength += step;
if (Strength >= Class->MaxStrength) {
Strength = Class->MaxStrength;
IsRepairing = false;
}
} else {
IsRepairing = false;
}
}
}
/***********************************************************************************************
* BuildingClass::Animation_AI -- Handles normal building animation processing. *
* *
* This will process the general building animation mechanism. It detects when the *
* building animation sequence has completed and flags the building to perform mission *
* changes as a result. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: Call this routine only once per building per game logic loop. *
* *
* HISTORY: *
* 07/29/1996 JLB : Created. *
*=============================================================================================*/
void BuildingClass::Animation_AI(void)
{
bool stagechange = Graphic_Logic();
bool toloop = false;
/*
** Always refresh the SAM site if it has an animation change.
*/
if (*this == STRUCT_SAM && stagechange) Mark(MARK_CHANGE);
if ((!Class->IsTurretEquipped && *this != STRUCT_TESLA) || Mission == MISSION_CONSTRUCTION || Mission == MISSION_DECONSTRUCTION) {
if (stagechange) {
/*
** Check for animation end or if special case of MCV deconstructing when it is allowed
** to convert back into an MCV.
*/
BuildingTypeClass::AnimControlType const * ctrl = Fetch_Anim_Control();
/*
** When the last frame of the current animation sequence is reached, flag that
** a new mission may be started. This must occur before the animation actually
** loops so that if a mission change does occur, it will have a chance to change
** the building graphic before the last frame is replaced by the first frame of
** the loop.
*/
if (Fetch_Stage() == ctrl->Start+ctrl->Count-1 || (!Target_Legal(ArchiveTarget) /*Is_MCV_Deploy()*/ && *this == STRUCT_CONST && Mission == MISSION_DECONSTRUCTION && Fetch_Stage() == (42-19))) {
IsReadyToCommence = true;
}
/*
** If the animation advances beyond the last frame, then start the animation
** sequence over from the beginning.
*/
if (Fetch_Stage() >= ctrl->Start+ctrl->Count) {
toloop = true;
}
Mark(MARK_CHANGE);
} else {
if (BState == BSTATE_NONE || Fetch_Rate() == 0) {
IsReadyToCommence = true;
}
}
}
/*
** If there is a door that is animating, then it might cause this building
** to be redrawn. Check for and flag to redraw as necessary.
*/
if (Time_To_Redraw()) {
Clear_Redraw_Flag();
Mark(MARK_CHANGE);
}
/*
** The animation sequence has looped. Restart it and flag this loop condition.
** This is used to tell the mission system that the animation has completed. It
** also signals that now is a good time to act on any pending mission.
*/
if (toloop) {
BuildingTypeClass::AnimControlType const * ctrl = Fetch_Anim_Control();
if (BState == BSTATE_CONSTRUCTION || BState == BSTATE_IDLE) {
Set_Rate(Options.Normalize_Delay(ctrl->Rate));
} else {
Set_Rate(ctrl->Rate);
}
Set_Stage(ctrl->Start);
Mark(MARK_CHANGE);
}
}
/***********************************************************************************************
* BuildingClass::How_Many_Survivors -- This determine the maximum number of survivors. *
* *
* This routine is called to determine how many survivors should run from this building *
* when it is either sold or destroyed. Buildings that are captured have fewer survivors. *
* The number of survivors is a portion of the cost of the building divided by the cost *
* of a minigunner. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the number of soldiers that should run from this building. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/04/1996 JLB : Created. *
*=============================================================================================*/
int BuildingClass::How_Many_Survivors(void) const
{
if (IsSurvivorless || !Class->IsCrew) return(0);
int divisor = InfantryTypeClass::As_Reference(INFANTRY_E1).Raw_Cost();
if (divisor == 0) return(0);
if (IsCaptured) divisor *= 2;
int count = (Class->Raw_Cost() * Rule.SurvivorFraction) / divisor;
return(Bound(count, 1, 5));
}
/***********************************************************************************************
* BuildingClass::Get_Image_Data -- Fetch the image pointer for the building. *
* *
* This routine will return with a pointer to the shape data for the building. The shape *
* data is different than normal when the building is undergoing construction and *
* disassembly. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with a pointer to the shape data for this building. *
* *
* WARNINGS: none *
* *
* HISTORY: *
* 08/06/1996 JLB : Created. *
*=============================================================================================*/
void const * BuildingClass::Get_Image_Data(void) const
{
if (BState == BSTATE_CONSTRUCTION) {
return(Class->Get_Buildup_Data());
}
return(TechnoClass::Get_Image_Data());
}
/***********************************************************************************************
* BuildingClass::Value -- Determine the value of this building. *
* *
* The value of the building is normally just its ordinary assigned value. However, in the *
* case of fakes, the value is artificially enhanced to match the structure that is *
* being faked. *
* *
* INPUT: none *
* *
* OUTPUT: Returns with the point value of the building type. *
* *
* WARNINGS: The point value returned should not be used for scoring, only for target *
* scanning. *
* *
* HISTORY: *
* 09/16/1996 JLB : Created. *
*=============================================================================================*/
int BuildingClass::Value(void) const
{
if (Class->IsFake) {
switch (Class->Type) {
case STRUCT_FAKEWEAP:
return(BuildingTypeClass::As_Reference(STRUCT_WEAP).Reward + BuildingTypeClass::As_Reference(STRUCT_WEAP).Risk);
case STRUCT_FAKECONST:
return(BuildingTypeClass::As_Reference(STRUCT_CONST).Reward + BuildingTypeClass::As_Reference(STRUCT_CONST).Risk);
case STRUCT_FAKE_YARD:
return(BuildingTypeClass::As_Reference(STRUCT_SHIP_YARD).Reward + BuildingTypeClass::As_Reference(STRUCT_SHIP_YARD).Risk);
case STRUCT_FAKE_PEN:
return(BuildingTypeClass::As_Reference(STRUCT_SUB_PEN).Reward + BuildingTypeClass::As_Reference(STRUCT_SUB_PEN).Risk);
case STRUCT_FAKE_RADAR:
return(BuildingTypeClass::As_Reference(STRUCT_RADAR).Reward + BuildingTypeClass::As_Reference(STRUCT_RADAR).Risk);
default:
break;
}
}
return(TechnoClass::Value());
}
/***********************************************************************************************
* BuildingClass::Remove_Gap_Effect -- Stop a gap generator from jamming cells. *
* *
* INPUT: none *
* *
* OUTPUT: none *
* *
* WARNINGS: *
* *
* HISTORY: *
* 09/20/1996 BWG : Created. *
*=============================================================================================*/
void BuildingClass::Remove_Gap_Effect(void)
{
// unjam this one's field...
Map.UnJam_From(Coord_Cell(Center_Coord()), Rule.GapShroudRadius, House);
/*
** Updated for client/server multiplayer. ST - 8/12/2019 11:14AM
*/
if (Session.Type != GAME_GLYPHX_MULTIPLAYER) {
if (!House->IsPlayerControl && PlayerPtr->IsGPSActive) {
Map.Sight_From(Coord_Cell(Center_Coord()), Rule.GapShroudRadius, PlayerPtr);
}
} else {
for (int i = 0; i < Session.Players.Count(); i++) {
HouseClass *player_house = HouseClass::As_Pointer(Session.Players[i]->Player.ID);
if (player_house->IsGPSActive && player_house != House) {
Map.Sight_From(Coord_Cell(Center_Coord()), Rule.GapShroudRadius, player_house);
}
}
}
// and rejam any overlapping buildings' fields
for (int index = 0; index < Buildings.Count(); index++) {
BuildingClass *obj = Buildings.Ptr(index);
if (obj && !obj->IsInLimbo && obj->House == House && *obj == STRUCT_GAP && obj!=this) {
obj->IsJamming = false;
obj->Arm = 0;
// Map.Jam_From(Coord_Cell(obj->Center_Coord()), Rule.GapShroudRadius, PlayerPtr);
}
}
}
short const * BuildingClass::Overlap_List(bool redraw) const
{
if ((Spied_By() & (1 << PlayerPtr->Class->House)) != 0 && Is_Selected_By_Player()) {
if (*this == STRUCT_BARRACKS || *this == STRUCT_TENT) {
static short const _list[] = {
-1, 2, (MAP_CELL_W*1)-1, (MAP_CELL_W*1)+2, REFRESH_EOL
};
return(_list);
} else if (*this == STRUCT_REFINERY) {
static short const _list[] = {
0, 2, (MAP_CELL_W*2)+0, (MAP_CELL_W*2)+1, (MAP_CELL_W*2)+2, REFRESH_EOL
};
return(_list);
}
}
return(TechnoClass::Overlap_List(redraw));
}
unsigned BuildingClass::Spied_By() const
{
unsigned spiedby = TechnoClass::Spied_By();
/*
** If it's an ore refinery or other such storage-capable building,
** loop thru all of their buildings to see if ANY of them are spied
** upon, 'cause once you spy any money, you've spied all of it.
*/
if (Class->Capacity) {
for (int index = 0; index < Buildings.Count(); index++) {
BuildingClass * building = Buildings.Ptr(index);
if (building->House == House && building->Class->Capacity) {
spiedby |= building->SpiedBy;
}
}
}
return spiedby;
}