/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /*********************************************************************************************** *** Confidential - Westwood Studios *** *********************************************************************************************** * * * Project Name : Commando * * * * $Archive:: /Commando/Code/Combat/building.cpp $* * * * $Author:: Greg_h $* * * * $Modtime:: 6/20/02 3:09p $* * * * $Revision:: 68 $* * * *---------------------------------------------------------------------------------------------* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "building.h" #include "simpledefinitionfactory.h" #include "persistfactory.h" #include "combatchunkid.h" #include "debug.h" #include "wwphysids.h" #include "meshmdl.h" #include "gameobjmanager.h" #include "wwaudio.h" #include "audiblesound.h" #include "sound3d.h" #include "translateobj.h" #include "translatedb.h" #include "combat.h" #include "messagewindow.h" #include "buildingmonitor.h" #include "basecontroller.h" #include "bitpackids.h" #include "soldier.h" #include "playerdata.h" #include "encyclopediamgr.h" #include "apppackettypes.h" #include "wwprofile.h" ///////////////////////////////////////////////////////////////////////////// // Namespaces ///////////////////////////////////////////////////////////////////////////// using namespace BuildingConstants; ///////////////////////////////////////////////////////////////////////////// // // Constants // ///////////////////////////////////////////////////////////////////////////// static const char * BULDING_TYPE_NAMES[TYPE_COUNT + 1] = { "", "Power Plant", "Soldier Factory", "Vehicle Factory", "Refinery", "Com Center", "Repair Bay", "Shrine", "Helipad", "Conyard", "Base Defense", }; static const char * BULDING_TEAM_NAMES[BASE_COUNT] = { "GDI", "NOD" }; // // Class statics // bool BuildingGameObj::CanRepairBuildings = true; /************************************************************************************************ ** ** BuildingGameObjDef ** ************************************************************************************************/ SimplePersistFactoryClass _BuildingGameObjDefPersistFactory; DECLARE_DEFINITION_FACTORY(BuildingGameObjDef, CLASSID_GAME_OBJECT_DEF_BUILDING, "") _BuildingGameObjDefDefFactory; ///////////////////////////////////////////////////////////////////////////// // // BuildingGameObjDef // ///////////////////////////////////////////////////////////////////////////// BuildingGameObjDef::BuildingGameObjDef (void) : MCTSkin(0), Type(TYPE_NONE), GDIDamageReportID(0), NodDamageReportID(0), GDIDestroyReportID(0), NodDestroyReportID(0) { EDITABLE_PARAM( BuildingGameObjDef, ParameterClass::TYPE_STRING, MeshPrefix ); #ifdef PARAM_EDITING_ON int skin_type_counter; EnumParameterClass * mct_skin_param = new EnumParameterClass( (int*)&MCTSkin ); mct_skin_param->Set_Name("MCTSkin"); for ( skin_type_counter = 0; skin_type_counter < ArmorWarheadManager::Get_Num_Armor_Types(); skin_type_counter++ ) { mct_skin_param->Add_Value(ArmorWarheadManager::Get_Armor_Name(skin_type_counter), skin_type_counter); } GENERIC_EDITABLE_PARAM(BuildingGameObjDef,mct_skin_param); #endif // // Configure the building type parameter // #ifdef PARAM_EDITING_ON EnumParameterClass *building_type_param = new EnumParameterClass( (int*)&Type ); building_type_param->Set_Name("Building Type"); for ( int index = TYPE_NONE; index < TYPE_COUNT; index++ ) { building_type_param->Add_Value(BULDING_TYPE_NAMES[index+1], index); } GENERIC_EDITABLE_PARAM(BuildingGameObjDef,building_type_param); #endif EDITABLE_PARAM(BuildingGameObjDef, ParameterClass::TYPE_STRINGSDB_ID, GDIDamageReportID); EDITABLE_PARAM(BuildingGameObjDef, ParameterClass::TYPE_STRINGSDB_ID, NodDamageReportID); EDITABLE_PARAM(BuildingGameObjDef, ParameterClass::TYPE_STRINGSDB_ID, GDIDestroyReportID); EDITABLE_PARAM(BuildingGameObjDef, ParameterClass::TYPE_STRINGSDB_ID, NodDestroyReportID); } ///////////////////////////////////////////////////////////////////////////// // // Get_Class_ID // ///////////////////////////////////////////////////////////////////////////// uint32 BuildingGameObjDef::Get_Class_ID (void) const { return CLASSID_GAME_OBJECT_DEF_BUILDING; } ///////////////////////////////////////////////////////////////////////////// // // Create // ///////////////////////////////////////////////////////////////////////////// PersistClass * BuildingGameObjDef::Create (void) const { BuildingGameObj * obj = new BuildingGameObj; obj->Init( *this ); return obj; } ///////////////////////////////////////////////////////////////////////////// // Save/Load constants ///////////////////////////////////////////////////////////////////////////// enum { CHUNKID_DEF_PARENT = 207011030, CHUNKID_DEF_VARIABLES, MICROCHUNKID_DEF_MESHPREFIX = 1, MICROCHUNKID_DEF_MCTSKIN, MICROCHUNKID_DEF_BUILDING_TYPE, LEGACY_MICROCHUNKID_DEF_BUILDING_TEAM, MICROCHUNKID_DEF_GDI_DAMAGE_REPORT_ID, MICROCHUNKID_DEF_NOD_DAMAGE_REPORT_ID, MICROCHUNKID_DEF_GDI_DESTROY_REPORT_ID, MICROCHUNKID_DEF_NOD_DESTROY_REPORT_ID, }; ///////////////////////////////////////////////////////////////////////////// // // Save // ///////////////////////////////////////////////////////////////////////////// bool BuildingGameObjDef::Save (ChunkSaveClass &csave) { csave.Begin_Chunk( CHUNKID_DEF_PARENT ); DamageableGameObjDef::Save( csave ); csave.End_Chunk(); csave.Begin_Chunk( CHUNKID_DEF_VARIABLES ); WRITE_MICRO_CHUNK_WWSTRING( csave, MICROCHUNKID_DEF_MESHPREFIX, MeshPrefix ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_MCTSKIN, MCTSkin ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_BUILDING_TYPE, Type ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_GDI_DAMAGE_REPORT_ID, GDIDamageReportID ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_NOD_DAMAGE_REPORT_ID, NodDamageReportID ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_GDI_DESTROY_REPORT_ID, GDIDestroyReportID ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_NOD_DESTROY_REPORT_ID, NodDestroyReportID ); csave.End_Chunk(); return true; } ///////////////////////////////////////////////////////////////////////////// // // Load // ///////////////////////////////////////////////////////////////////////////// bool BuildingGameObjDef::Load (ChunkLoadClass &cload) { int legacy_team = -1; while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_DEF_PARENT: DamageableGameObjDef::Load( cload ); break; case CHUNKID_DEF_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK_WWSTRING( cload, MICROCHUNKID_DEF_MESHPREFIX, MeshPrefix ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_MCTSKIN, MCTSkin ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_BUILDING_TYPE, Type ); READ_MICRO_CHUNK( cload, LEGACY_MICROCHUNKID_DEF_BUILDING_TEAM, legacy_team ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_GDI_DAMAGE_REPORT_ID, GDIDamageReportID ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_NOD_DAMAGE_REPORT_ID, NodDamageReportID ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_GDI_DESTROY_REPORT_ID, GDIDestroyReportID ); READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_NOD_DESTROY_REPORT_ID, NodDestroyReportID ); default: Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Micro_Chunk(); } break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk(); } if ( legacy_team != -1 ) { DefaultPlayerType = PLAYERTYPE_GDI; if ( legacy_team == BuildingConstants::LEGACY_TEAM_NOD ) { DefaultPlayerType = PLAYERTYPE_NOD; } } return true; } ///////////////////////////////////////////////////////////////////////////// // // Get_Factory // ///////////////////////////////////////////////////////////////////////////// const PersistFactoryClass & BuildingGameObjDef::Get_Factory (void) const { return _BuildingGameObjDefPersistFactory; } int BuildingGameObjDef::Get_Damage_Report(int team) const { if (PLAYERTYPE_GDI == team) { return GDIDamageReportID; } else if (PLAYERTYPE_NOD == team) { return NodDamageReportID; } return 0; } int BuildingGameObjDef::Get_Destroy_Report(int team) const { if (PLAYERTYPE_GDI == team) { return GDIDestroyReportID; } else if (PLAYERTYPE_NOD == team) { return NodDestroyReportID; } return 0; } /************************************************************************************************ ** ** BuildingGameObj ** ************************************************************************************************/ SimplePersistFactoryClass _BuildingGameObjPersistFactory; ///////////////////////////////////////////////////////////////////////////// // // Get_Factory // ///////////////////////////////////////////////////////////////////////////// const PersistFactoryClass & BuildingGameObj::Get_Factory (void) const { return _BuildingGameObjPersistFactory; } ///////////////////////////////////////////////////////////////////////////// // // BuildingGameObj // ///////////////////////////////////////////////////////////////////////////// BuildingGameObj::BuildingGameObj (void) : Position (0, 0, 0), IsPowerOn (true), CurrentState (-1), AnnouncementSphere (Vector3 (0, 0, 0), 1.0F), CollectionSphere (Vector3 (0, 0, 0), 50.0F), CurrentAnnouncement (NULL), IsDestroyed (false), BuildingMonitor (NULL), BaseController (NULL) { GameObjManager::Add_Building (this); Update_State (); Set_App_Packet_Type(APPPACKETTYPE_BUILDING); } ///////////////////////////////////////////////////////////////////////////// // // ~BuildingGameObj // ///////////////////////////////////////////////////////////////////////////// BuildingGameObj::~BuildingGameObj (void) { Stop_Current_Announcement(); // // Free the building monitor (if we have one) // if (BuildingMonitor != NULL) { Remove_Observer (BuildingMonitor); delete BuildingMonitor; BuildingMonitor = NULL; } GameObjManager::Remove_Building( this ); Reset_Components(); return ; } ///////////////////////////////////////////////////////////////////////////// // // Init // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Init( void ) { Init( Get_Definition() ); } ///////////////////////////////////////////////////////////////////////////// // // Init // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Init (const BuildingGameObjDef &definition) { DamageableGameObj::Init (definition); return ; } ///////////////////////////////////////////////////////////////////////////// // // Get_Definition // ///////////////////////////////////////////////////////////////////////////// const BuildingGameObjDef & BuildingGameObj::Get_Definition (void) const { return (const BuildingGameObjDef &)BaseGameObj::Get_Definition(); } ///////////////////////////////////////////////////////////////////////////// // Save/Load constants ///////////////////////////////////////////////////////////////////////////// enum { CHUNKID_PARENT = 207011120, CHUNKID_VARIABLES, MICROCHUNKID_POSITION = 1, MICROCHUNKID_ISPOWERON, MICROCHUNKID_COLLECTION_SPHERE }; ///////////////////////////////////////////////////////////////////////////// // // Save // ///////////////////////////////////////////////////////////////////////////// bool BuildingGameObj::Save (ChunkSaveClass &csave) { csave.Begin_Chunk (CHUNKID_PARENT); DamageableGameObj::Save (csave); csave.End_Chunk (); csave.Begin_Chunk (CHUNKID_VARIABLES); WRITE_MICRO_CHUNK (csave, MICROCHUNKID_POSITION, Position); WRITE_MICRO_CHUNK (csave, MICROCHUNKID_ISPOWERON, IsPowerOn); WRITE_MICRO_CHUNK (csave, MICROCHUNKID_COLLECTION_SPHERE, CollectionSphere); csave.End_Chunk (); return true; } ///////////////////////////////////////////////////////////////////////////// // // Load // ///////////////////////////////////////////////////////////////////////////// bool BuildingGameObj::Load (ChunkLoadClass &cload) { while (cload.Open_Chunk ()) { switch (cload.Cur_Chunk_ID ()) { case CHUNKID_PARENT: DamageableGameObj::Load (cload); break; case CHUNKID_VARIABLES: while (cload.Open_Micro_Chunk ()) { switch (cload.Cur_Micro_Chunk_ID ()) { READ_MICRO_CHUNK (cload, MICROCHUNKID_POSITION, Position); READ_MICRO_CHUNK (cload, MICROCHUNKID_ISPOWERON, IsPowerOn); READ_MICRO_CHUNK (cload, MICROCHUNKID_COLLECTION_SPHERE, CollectionSphere); default: Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Micro_Chunk(); } break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk (); } // Fix player_type (legacy) Set_Player_Type( Get_Definition().DefaultPlayerType ); // // Hack to get legacy code to work... // if (CollectionSphere.Center.X == 0 && CollectionSphere.Center.Y == 0 && CollectionSphere.Center.Z == 0) { CollectionSphere.Center = Position; } return true; } ///////////////////////////////////////////////////////////////////////////// // // Enable_Power // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Enable_Power (bool onoff) { if ( IsPowerOn != onoff ) { // Notify the observers const GameObjObserverList & observer_list = Get_Observers(); for( int index = 0; index < observer_list.Count(); index++ ) { observer_list[ index ]->Custom( this, CUSTOM_EVENT_BUILDING_POWER_CHANGED, onoff, NULL ); } } IsPowerOn = onoff; Update_State (); // // Mark the object as dirty so it's state will be mirrored on the client // if (CombatManager::I_Am_Server ()) { Set_Object_Dirty_Bit (NetworkObjectClass::BIT_RARE, true); } return ; } void BuildingGameObj::Apply_Damage( const OffenseObjectClass & damager, float scale, int alternate_skin ) { /* ** If we're already completely damaged, just return */ if ( DefenseObject.Get_Health() <= 0 ) { return; } // if gameplay not permitted, skip if ( !CombatManager::Is_Gameplay_Permitted() ) { return; } // // TSS102501 // if ( !CanRepairBuildings && DefenseObject.Is_Repair(damager) ) { return; } float old_health = Get_Defense_Object()->Get_Health(); DamageableGameObj::Apply_Damage( damager, scale, alternate_skin ); if ( old_health != Get_Defense_Object()->Get_Health() ) { Set_Object_Dirty_Bit( NetworkObjectClass::BIT_OCCASIONAL, true ); } // WWDEBUG_SAY(("Applied damage to a building (prefix=%s) new health = %f\r\n",Get_Definition().MeshPrefix,DefenseObject.Get_Health())); // Stats if ( DefenseObject.Get_Health() <= 0 ) { if ( damager.Get_Owner() != NULL && damager.Get_Owner()->As_SoldierGameObj() != NULL && damager.Get_Owner()->As_SoldierGameObj()->Get_Player_Data() != NULL ) { damager.Get_Owner()->As_SoldierGameObj()->Get_Player_Data()->Stats_Add_Building_Destroyed(); } // // Reveal this building to the encyclopedia // EncyclopediaMgrClass::Reveal_Object (this); } /* ** If our health status changed states, switch all of our aggregates */ Update_State(); } void BuildingGameObj::Apply_Damage_Building(const OffenseObjectClass & offense, StaticPhysClass * component ) { /* ** If the MCT is being damaged, use an alternate skin type */ ArmorType skin = DefenseObject.Get_Skin(); if (component->Get_Factory().Chunk_ID() == PHYSICS_CHUNKID_BUILDINGAGGREGATE) { if (((BuildingAggregateClass *)component)->Is_MCT()) { skin = Get_Definition().MCTSkin; } } /* ** Apply the damage */ Apply_Damage( offense, 1.0, skin ); } void BuildingGameObj::Apply_Damage_Building( const OffenseObjectClass & offense, bool mct_damage ) { /* ** If the MCT is being damaged, use an alternate skin type */ ArmorType skin = DefenseObject.Get_Skin(); if ( mct_damage ) { skin = Get_Definition().MCTSkin; } /* ** Apply the damage */ Apply_Damage( offense, 1.0, skin ); } ///////////////////////////////////////////////////////////////////////////// // // Is_Interior_Mesh_Name // ///////////////////////////////////////////////////////////////////////////// bool BuildingGameObj::Is_Interior_Mesh_Name (const char * name) { // (gth) Renegade day 120 patch, accept tiles as building "parts" #if 0 char * meshname = strchr(name,'.'); if (meshname != NULL) { return strchr(meshname,'#') != NULL; } else { return false; } #else return strchr(name,'#') != NULL; #endif } ///////////////////////////////////////////////////////////////////////////// // // Is_Exterior_Mesh_Name // ///////////////////////////////////////////////////////////////////////////// bool BuildingGameObj::Is_Exterior_Mesh_Name (const char * name) { // (gth) Renegade day 120 patch, accept tiles as building "parts" #if 0 char * meshname = strchr(name,'.'); if (meshname != NULL) { return strchr(meshname,'^') != NULL; } else { return false; } #else return strchr(name,'^') != NULL; #endif } ///////////////////////////////////////////////////////////////////////////// // // Name_Prefix_Matches_This_Building // ///////////////////////////////////////////////////////////////////////////// bool BuildingGameObj::Name_Prefix_Matches_This_Building (const char * name) { bool retval = false; if (name != NULL) { StringClass prefex(Get_Definition().MeshPrefix,true); char * meshname = strchr(name,'.'); if (meshname != NULL) { meshname++; retval = (strnicmp(meshname,prefex,strlen(prefex)) == 0); } else { retval = (strnicmp(name,prefex,strlen(prefex)) == 0); } } return retval; } ///////////////////////////////////////////////////////////////////////////// // // Set_Normalized_Health // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Set_Normalized_Health (float health) { // // Note: We convert from normalized to absolute values // DefenseObject.Set_Health (health * DefenseObject.Get_Health_Max ()); Update_State (); WWDEBUG_SAY(("Building State Set:\r\n")); WWDEBUG_SAY((" prefix = %s mesh count = %d aggregate count = %d light count = %d\r\n", Get_Definition().MeshPrefix, InteriorMeshes.Count() + ExteriorMeshes.Count(), Aggregates.Count(), PowerOnLights.Count() + PowerOffLights.Count())); } ///////////////////////////////////////////////////////////////////////////// // // Update_State // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Update_State (bool force_update) { /* ** Evaluate our current state */ float health_percentage = 100.0f * DefenseObject.Get_Health()/DefenseObject.Get_Health_Max(); int health_state = BuildingStateClass::Percentage_To_Health_State(health_percentage); int new_state = BuildingStateClass::Compose_State(health_state,IsPowerOn); /* ** If the state has changed, inform all of our components */ if ((new_state != CurrentState) || (force_update == true)) { int old_health_state = BuildingStateClass::Get_Health_State( CurrentState ); if (health_state != old_health_state) { int event = CUSTOM_EVENT_BUILDING_DAMAGED; if (health_state < old_health_state) { event = CUSTOM_EVENT_BUILDING_REPAIRED; } const GameObjObserverList & observer_list = Get_Observers(); for( int index = 0; index < observer_list.Count(); index++ ) { observer_list[ index ]->Custom( this, event, health_percentage, NULL ); } } CurrentState = new_state; // // Mark the object as dirty so it's state will be mirrored on the client // if (CombatManager::I_Am_Server ()) { Set_Object_Dirty_Bit (NetworkObjectClass::BIT_RARE, true); } /* ** Aggregates: inform all aggregates of the new state */ RefMultiListIterator aggregate_iterator(&Aggregates); for (aggregate_iterator.First(); !aggregate_iterator.Is_Done(); aggregate_iterator.Next()) { aggregate_iterator.Peek_Obj()->Set_Current_State(CurrentState,force_update); } /* ** Interior meshes: switch to alternate materials if either the power is out or ** the building is at zero health */ Enable_Alternate_Materials(InteriorMeshes,((health_state == BuildingStateClass::HEALTH_0) || (!IsPowerOn))); /* ** Exterior meshes: use alternate materials if the building is destroyed */ Enable_Alternate_Materials(ExteriorMeshes,(health_state == BuildingStateClass::HEALTH_0)); /* ** Lights: enable the lights which match the current power status, invalidate all ** lighting caches within the bounds of the lights that changed. */ bool disable = ((IsPowerOn == false) || (health_state == BuildingStateClass::HEALTH_0)); disable &= (!PowerOffLights.Is_Empty()); // for lights, power is not disabled if there are not power-off lights AABoxClass light_bounds(Position,Vector3(1,1,1)); RefMultiListIterator light_iterator(&PowerOnLights); for (light_iterator.First(); !light_iterator.Is_Done(); light_iterator.Next()) { light_iterator.Peek_Obj()->Set_Disabled(disable); light_bounds.Add_Box(light_iterator.Peek_Obj()->Peek_Model()->Get_Bounding_Box()); } disable = !disable; for (light_iterator.First(&PowerOffLights); !light_iterator.Is_Done(); light_iterator.Next()) { light_iterator.Peek_Obj()->Set_Disabled(disable); light_bounds.Add_Box(light_iterator.Peek_Obj()->Peek_Model()->Get_Bounding_Box()); } PhysicsSceneClass::Get_Instance()->Invalidate_Lighting_Caches(light_bounds); } return ; } ///////////////////////////////////////////////////////////////////////////// // // Play_Announcement // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Play_Announcement(int text_id, bool broadcast) { Stop_Current_Announcement(); // Lookup the translation object from the strings database TDBObjClass* translate_obj = TranslateDBClass::Find_Object(text_id); if (translate_obj) { const WCHAR* string = translate_obj->Get_String(); int sound_def_id = (int)translate_obj->Get_Sound_ID(); float duration = 2.0F; // Play the sound effect bool display_text = true; if (sound_def_id > 0) { // Create the sound object CurrentAnnouncement = WWAudioClass::Get_Instance()->Create_Sound(sound_def_id); if (CurrentAnnouncement) { duration = (CurrentAnnouncement->Get_Duration() / 1000.0F); if (broadcast == false) { // Tweak the radii of the sound so it can be heard throughout the building CurrentAnnouncement->Set_DropOff_Radius(AnnouncementSphere.Radius); Sound3DClass* sound3D = CurrentAnnouncement->As_Sound3DClass(); if (sound3D) { sound3D->Set_Max_Vol_Radius(AnnouncementSphere.Radius * 0.9F); } // Play the sound effect at the annoucement position for the building CurrentAnnouncement->Set_Transform(Matrix3D(AnnouncementSphere.Center)); display_text = (CurrentAnnouncement->Is_Sound_Culled() == false); } CurrentAnnouncement->Add_To_Scene(); } } // Display the text on the screen if (display_text && string) { float message_duration = max(duration, 5.0F); CombatManager::Get_Message_Window()->Add_Message(string, Vector3(1, 1, 1), NULL, message_duration); } } } ///////////////////////////////////////////////////////////////////////////// // // Stop_Current_Announcement // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Stop_Current_Announcement (void) { if (CurrentAnnouncement) { // Stop the sound object and remove it from the world CurrentAnnouncement->Stop(); CurrentAnnouncement->Remove_From_Scene(); CurrentAnnouncement->Release_Ref(); CurrentAnnouncement = NULL; } } ///////////////////////////////////////////////////////////////////////////// // // Initialize_Building // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Initialize_Building (void) { RefMultiListIterator mesh_iterator (&InteriorMeshes); // // Generate a bounding box that contains all the building interior meshes // AABoxClass bounding_box (Position, Vector3 (1, 1, 1)); for (mesh_iterator.First (); !mesh_iterator.Is_Done (); mesh_iterator.Next ()) { bounding_box.Add_Box (mesh_iterator.Peek_Obj ()->Peek_Model ()->Get_Bounding_Box()); } // // Turn the bounding box into a sphere that we can use to play building // announcement sound effects in // AnnouncementSphere.Center = bounding_box.Center; AnnouncementSphere.Radius = max (bounding_box.Extent.X, bounding_box.Extent.Y); AnnouncementSphere.Radius = max (AnnouncementSphere.Radius, bounding_box.Extent.Z); // // If we can find a base for our team, then add ourselves to it. // BaseControllerClass *base = BaseControllerClass::Find_Base ( Get_Player_Type() ); if (base != NULL) { base->Add_Building (this); } return ; } ///////////////////////////////////////////////////////////////////////////// // // Reset_Components // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Reset_Components (void) { RefMultiListIterator mesh_iterator(&InteriorMeshes); for (mesh_iterator.First(); !mesh_iterator.Is_Done(); mesh_iterator.Next()) { WWASSERT(mesh_iterator.Peek_Obj()->Get_Observer() == this); mesh_iterator.Peek_Obj()->Set_Observer(NULL); } for (mesh_iterator.First(&ExteriorMeshes); !mesh_iterator.Is_Done(); mesh_iterator.Next()) { WWASSERT(mesh_iterator.Peek_Obj()->Get_Observer() == this); mesh_iterator.Peek_Obj()->Set_Observer(NULL); } RefMultiListIterator agg_iterator(&Aggregates); for (agg_iterator.First(); !agg_iterator.Is_Done(); agg_iterator.Next()) { WWASSERT(agg_iterator.Peek_Obj()->Get_Observer() == this); agg_iterator.Peek_Obj()->Set_Observer(NULL); } InteriorMeshes.Reset_List(); ExteriorMeshes.Reset_List(); PowerOnLights.Reset_List(); PowerOffLights.Reset_List(); Aggregates.Reset_List(); } ///////////////////////////////////////////////////////////////////////////// // // Add_Mesh // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Add_Mesh (StaticPhysClass * terrain) { WWASSERT(terrain != NULL); if (Is_Interior_Mesh_Name(terrain->Peek_Model()->Get_Name())) { WWASSERT(terrain->Get_Observer() == NULL); InteriorMeshes.Add(terrain); terrain->Set_Observer(this); } else if (Is_Exterior_Mesh_Name(terrain->Peek_Model()->Get_Name())) { WWASSERT(terrain->Get_Observer() == NULL); ExteriorMeshes.Add(terrain); terrain->Set_Observer(this); } } ///////////////////////////////////////////////////////////////////////////// // // Remove_Mesh // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Remove_Mesh (StaticPhysClass * terrain) { WWASSERT(terrain != NULL); if (Is_Interior_Mesh_Name(terrain->Peek_Model()->Get_Name())) { WWASSERT(terrain->Get_Observer() == this); terrain->Set_Observer(NULL); InteriorMeshes.Remove(terrain); } else if (Is_Exterior_Mesh_Name(terrain->Peek_Model()->Get_Name())) { WWASSERT(terrain->Get_Observer() == this); terrain->Set_Observer(NULL); ExteriorMeshes.Remove(terrain); } } ///////////////////////////////////////////////////////////////////////////// // // Add_Aggregate // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Add_Aggregate (BuildingAggregateClass * aggregate) { WWASSERT(aggregate != NULL); Aggregates.Add(aggregate); WWASSERT(aggregate->Get_Observer() == NULL); aggregate->Set_Observer(this); } ///////////////////////////////////////////////////////////////////////////// // // Remove_Aggregate // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Remove_Aggregate (BuildingAggregateClass * aggregate) { WWASSERT(aggregate != NULL); WWASSERT(aggregate->Get_Observer() == this); aggregate->Set_Observer(NULL); Aggregates.Remove(aggregate); } ///////////////////////////////////////////////////////////////////////////// // // Add_Light // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Add_Light (LightPhysClass * light) { WWASSERT(light != NULL); if (light->Get_Group_ID() == 0) { PowerOnLights.Add(light); } else { PowerOffLights.Add(light); } } ///////////////////////////////////////////////////////////////////////////// // // Enable_Alternate_Materials // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Enable_Alternate_Materials (RefMultiListClass & models, bool onoff) { RefMultiListIterator it(&models); for (it.First(); !it.Is_Done(); it.Next()) { Enable_Alternate_Materials(it.Peek_Obj()->Peek_Model(),onoff); } } ///////////////////////////////////////////////////////////////////////////// // // Enable_Alternate_Materials // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Enable_Alternate_Materials (RenderObjClass * model,bool onoff) { if (model == NULL) return; for (int i=0; iGet_Num_Sub_Objects(); i++) { RenderObjClass * subobj = model->Get_Sub_Object(i); Enable_Alternate_Materials(subobj,onoff); REF_PTR_RELEASE(subobj); } if (model->Class_ID() == RenderObjClass::CLASSID_MESH) { MeshModelClass * mesh_model = ((MeshClass *)model)->Get_Model(); mesh_model->Enable_Alternate_Material_Description(onoff); REF_PTR_RELEASE(mesh_model); } } ///////////////////////////////////////////////////////////////////////////// // // CnC_Initialize // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::CnC_Initialize (BaseControllerClass *base) { BaseController = base; // // Create an observer that we can use to monitor the building's health // BuildingMonitor = new BuildingMonitorClass; BuildingMonitor->Set_Building (this); Add_Observer (BuildingMonitor); return ; } ///////////////////////////////////////////////////////////////////////////// // // On_Damaged // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::On_Damaged (void) { // // Notify the controller that the building has been damaged // if (BaseController != NULL) { BaseController->On_Building_Damaged (this); } return ; } ///////////////////////////////////////////////////////////////////////////// // // On_Destroyed // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::On_Destroyed (void) { IsDestroyed = true; // // Notify the controller that the building has been destroyed // if (BaseController != NULL) { BaseController->On_Building_Destroyed (this); } // // Mark the object as dirty so it's state will be mirrored on the client // if (CombatManager::I_Am_Server ()) { Set_Object_Dirty_Bit (NetworkObjectClass::BIT_RARE, true); } // // Reveal this building to the encyclopedia // EncyclopediaMgrClass::Reveal_Object (this); return ; } ///////////////////////////////////////////////////////////////////////////// // // Import_Rare // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Import_Rare (BitStreamClass &packet) { DamageableGameObj::Import_Rare (packet); // // Read the state information from the server // bool is_destroyed = IsDestroyed; bool is_power_on = IsPowerOn; int current_state = CurrentState; packet.Get (is_destroyed); packet.Get (is_power_on); packet.Get (current_state, BITPACK_BUILDING_STATE); // // Is the power state of the building changing? // if (is_power_on != IsPowerOn) { Enable_Power (is_power_on); } // // Update our health if we are destroyed // if (is_destroyed) { Get_Defense_Object()->Set_Health(0.0f); } // // Recalculate our state (if necessary) // if (current_state != CurrentState) { Update_State (); } // // Become destroyed (if necessary) // if (is_destroyed && IsDestroyed == false) { On_Destroyed (); } return ; } ///////////////////////////////////////////////////////////////////////////// // // Export_Rare // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Export_Rare (BitStreamClass &packet) { DamageableGameObj::Export_Rare (packet); // // Transmit the state information // packet.Add (IsDestroyed); packet.Add (IsPowerOn); packet.Add (CurrentState, BITPACK_BUILDING_STATE); return ; } ///////////////////////////////////////////////////////////////////////////// // // Set_Precision // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Set_Precision (void) { cEncoderList::Set_Precision (BITPACK_BUILDING_STATE, -1, (int)BuildingStateClass::STATE_COUNT); return ; } ///////////////////////////////////////////////////////////////////////////// // // Collect_Building_Components // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Collect_Building_Components (void) { Reset_Components (); // // Iterate through all static objects in the level, assigning the building components to // the appropriate building controller // RefPhysListIterator staticobj_iterator = PhysicsSceneClass::Get_Instance()->Get_Static_Object_Iterator(); for (staticobj_iterator.First(); !staticobj_iterator.Is_Done(); staticobj_iterator.Next()) { StaticPhysClass * obj = staticobj_iterator.Peek_Obj()->As_StaticPhysClass (); WWASSERT (obj != NULL); WWASSERT (obj->Peek_Model() != NULL); const char *obj_name = obj->Peek_Model()->Get_Name(); if (obj->Get_Factory().Chunk_ID() == PHYSICS_CHUNKID_BUILDINGAGGREGATE) { // // Does this aggregate match the prefix that this building is expecting? // if (Name_Prefix_Matches_This_Building (obj_name)) { // // Calculate how far the object is from the center of this building's // collection sphere // Vector3 obj_pos = obj->Peek_Model()->Get_Bounding_Box().Center; float dist2 = (CollectionSphere.Center - obj_pos).Length2 (); // // Is this object within the collection sphere? // if (dist2 <= (CollectionSphere.Radius * CollectionSphere.Radius)) { // Check for an existing building owner CombatPhysObserverClass * old_observer = (CombatPhysObserverClass *)obj->Get_Observer(); BuildingGameObj * existing_building = NULL; if ( old_observer != NULL ) { existing_building = old_observer->As_BuildingGameObj(); } if ( existing_building != NULL ) { // Another building already has this float existing_dist2 = (existing_building->CollectionSphere.Center - obj_pos).Length2 (); if ( dist2 < existing_dist2 ) { // But we are closer, so remove from the existing existing_building->Remove_Aggregate ((BuildingAggregateClass *)obj); // and Take him Add_Aggregate ((BuildingAggregateClass *)obj); } } else { // We are the first/only Add_Aggregate ((BuildingAggregateClass *)obj); } } } } else if (Is_Interior_Mesh_Name (obj_name) || Is_Exterior_Mesh_Name (obj_name)) { // // Does this mesh match the prefix that this building is expecting? // if (Name_Prefix_Matches_This_Building (obj_name)) { // // Calculate how far the object is from the center of this building's // collection sphere // Vector3 obj_pos = obj->Peek_Model()->Get_Bounding_Box().Center; float dist2 = (CollectionSphere.Center - obj_pos).Length2 (); // // Is this object within the collection sphere? // if (dist2 <= (CollectionSphere.Radius * CollectionSphere.Radius)) { // Check for an existing building owner CombatPhysObserverClass * old_observer = (CombatPhysObserverClass *)obj->Get_Observer(); BuildingGameObj * existing_building = NULL; if ( old_observer != NULL ) { existing_building = old_observer->As_BuildingGameObj(); } if ( existing_building != NULL ) { // Another building already has this float existing_dist2 = (existing_building->CollectionSphere.Center - obj_pos).Length2 (); if ( dist2 < existing_dist2 ) { // But we are closer, so remove from the existing existing_building->Remove_Mesh (obj); // and Take him Add_Mesh (obj); } } else { // We are the first/only Add_Mesh (obj); } } } } } // // Iterate through all static lights in the level, assigning each building light to // the appropriate building controller // RefPhysListIterator staticlight_iterator = PhysicsSceneClass::Get_Instance ()->Get_Static_Light_Iterator (); for (staticlight_iterator.First (); !staticlight_iterator.Is_Done (); staticlight_iterator.Next ()) { LightPhysClass * light = staticlight_iterator.Peek_Obj()->As_LightPhysClass(); WWASSERT(light != NULL); WWASSERT(light->Peek_Model() != NULL); // // Does this light match the prefix that this building is expecting? // if (Name_Prefix_Matches_This_Building (light->Get_Name ())) { // // Calculate how far the light is from the center of this building's // collection sphere // Vector3 light_pos; light->Get_Position (&light_pos); float dist2 = (CollectionSphere.Center - light_pos).Length2 (); // // Is this light within the collection sphere? // if (dist2 <= (CollectionSphere.Radius * CollectionSphere.Radius)) { Add_Light (light); } } } // // Update the building's state // Initialize_Building (); Update_State (true); return ; } ///////////////////////////////////////////////////////////////////////////// // // Export_Creation // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Export_Creation (BitStreamClass &packet) { DamageableGameObj::Export_Creation (packet); // // Send the object's position // Vector3 position (0, 0, 0); Get_Position (&position); packet.Add (position.X, BITPACK_WORLD_POSITION_X); packet.Add (position.Y, BITPACK_WORLD_POSITION_Y); packet.Add (position.Z, BITPACK_WORLD_POSITION_Z); // // Send the collection sphere's definition // packet.Add (CollectionSphere.Center.X, BITPACK_WORLD_POSITION_X); packet.Add (CollectionSphere.Center.Y, BITPACK_WORLD_POSITION_Y); packet.Add (CollectionSphere.Center.Z, BITPACK_WORLD_POSITION_Z); packet.Add (CollectionSphere.Radius, BITPACK_BUILDING_RADIUS); return ; } ///////////////////////////////////////////////////////////////////////////// // // Import_Creation // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Import_Creation (BitStreamClass &packet) { DamageableGameObj::Import_Creation (packet); // // Read the object's position // Vector3 position (0, 0, 0); packet.Get (position.X, BITPACK_WORLD_POSITION_X); packet.Get (position.Y, BITPACK_WORLD_POSITION_Y); packet.Get (position.Z, BITPACK_WORLD_POSITION_Z); Set_Position (position); // // Read the collection sphere's definition // packet.Get (CollectionSphere.Center.X, BITPACK_WORLD_POSITION_X); packet.Get (CollectionSphere.Center.Y, BITPACK_WORLD_POSITION_Y); packet.Get (CollectionSphere.Center.Z, BITPACK_WORLD_POSITION_Z); packet.Get (CollectionSphere.Radius, BITPACK_BUILDING_RADIUS); // // Now initialize the object // Collect_Building_Components (); Update_State (); Initialize_Building (); return ; } /* //////////////////////////////////////////////////////////////////////////// // // Compute_Object_Priority // ///////////////////////////////////////////////////////////////////////////// float BuildingGameObj::Compute_Object_Priority (int client_id, const Vector3 &client_pos) { float priority = 1.0F; if (Get_Object_Dirty_Bit (client_id, BIT_RARE) == false) { priority = DamageableGameObj::Compute_Object_Priority (client_id, client_pos); } // // Priority value is cached, update the cached value. // Set_Cached_Priority(priority); return priority; } */ //////////////////////////////////////////////////////////////////////////// // // Get_Description // ///////////////////////////////////////////////////////////////////////////// void //BuildingGameObj::Get_Extended_Information(StringClass & description) BuildingGameObj::Get_Description(StringClass & description) { // // Construct a diagnostic string // StringClass line(0,true); line.Format("ID: %d\n", Get_ID()); description += line; line.Format("Name: %s\n", Get_Definition().Get_Name()); description += line; line.Format("Team: %d\n", Get_Player_Type()); description += line; Vector3 position; Get_Position(&position); line.Format("POS: %-5.2f, %-5.2f, %-5.2f\n", position.X, position.Y, position.Z); description += line; if (Get_Defense_Object() != NULL) { line.Format("HLTH: %-5.2f\n", Get_Defense_Object()->Get_Health()); description += line; } line.Format("HIB: %d\n", Is_Hibernating()); description += line; line.Format("Destr: %d\n", IsDestroyed); description += line; line.Format("Power: %d\n", IsPowerOn); description += line; line.Format("ISC: %d\n", Get_Import_State_Count()); description += line; } //////////////////////////////////////////////////////////////////////////// // // Find_Closest_Poly_For_Model // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Find_Closest_Poly_For_Model ( RenderObjClass * model, const Vector3 & pos, float * distance2 ) { if (model == NULL) { return ; } // // Convert the point from world space to object space // Vector3 obj_space_point; Matrix3D::Inverse_Transform_Vector (model->Get_Transform (), pos, &obj_space_point); for (int index = 0; index < model->Get_Num_Sub_Objects (); index++) { RenderObjClass *sub_obj = model->Get_Sub_Object (index); // // Recurse into this sub-object // if (sub_obj != NULL) { Find_Closest_Poly_For_Model (sub_obj, pos, distance2); } REF_PTR_RELEASE (sub_obj); } // // Is this object a mesh? If so, then do a distance check against all its polygons // if (model->Class_ID () == RenderObjClass::CLASSID_MESH) { MeshModelClass *mesh_model = ((MeshClass *)model)->Peek_Model (); if (mesh_model != NULL) { const TriIndex *tri_array = mesh_model->Get_Polygon_Array (); Vector3 *vert_array = mesh_model->Get_Vertex_Array (); // // Check each polygon to see which is the closest // int poly_count = mesh_model->Get_Polygon_Count (); for (index = 0; index < poly_count; index ++) { int vert1 = tri_array[index][0]; int vert2 = tri_array[index][1]; int vert3 = tri_array[index][2]; // // Compute the distance from the center of this polygon to the // // Vector3 poly_center = (vert_array[vert1] + vert_array[vert2] + vert_array[vert3]) * 0.33F; Vector3 delta = (poly_center - obj_space_point); float dist2 = delta.Length2 (); if (dist2 < (*distance2)) { (*distance2) = dist2; } } } } return ; } //////////////////////////////////////////////////////////////////////////// // // Find_Closest_Poly // ///////////////////////////////////////////////////////////////////////////// void BuildingGameObj::Find_Closest_Poly (const Vector3 &pos, float *distance2) { (*distance2) = 9999.0F; RefMultiListIterator int_iterator (&InteriorMeshes); RefMultiListIterator ext_iterator (&ExteriorMeshes); // // Check all the interior meshes first // for (int_iterator.First (); !int_iterator.Is_Done (); int_iterator.Next ()) { Find_Closest_Poly_For_Model (int_iterator.Peek_Obj ()->Peek_Model (), pos, distance2); } // // Now, check all the exterior meshes // for (ext_iterator.First (); !ext_iterator.Is_Done (); ext_iterator.Next ()) { Find_Closest_Poly_For_Model (ext_iterator.Peek_Obj ()->Peek_Model (), pos, distance2); } return ; }