1636 lines
45 KiB
C++
1636 lines
45 KiB
C++
|
/*
|
||
|
** 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 <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
*** 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] =
|
||
|
{
|
||
|
"<None>",
|
||
|
"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<BuildingGameObjDef, CHUNKID_GAME_OBJECT_DEF_BUILDING> _BuildingGameObjDefPersistFactory;
|
||
|
|
||
|
DECLARE_DEFINITION_FACTORY(BuildingGameObjDef, CLASSID_GAME_OBJECT_DEF_BUILDING, "<Generic 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<BuildingGameObj, CHUNKID_GAME_OBJECT_BUILDING> _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<BuildingAggregateClass> 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<LightPhysClass> 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<StaticPhysClass> 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<StaticPhysClass> 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<BuildingAggregateClass> 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<StaticPhysClass> & models, bool onoff)
|
||
|
{
|
||
|
RefMultiListIterator<StaticPhysClass> 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; i<model->Get_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<StaticPhysClass> int_iterator (&InteriorMeshes);
|
||
|
RefMultiListIterator<StaticPhysClass> 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 ;
|
||
|
}
|