// // 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); 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++) { 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; }