/* ** 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 . */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : Combat * * * * $Archive:: /Commando/Code/Combat/damageablestaticphys.cpp $* * * * Original Author:: Greg Hjelstrom * * * * $Author:: Greg_h $* * * * $Modtime:: 8/17/01 7:15p $* * * * $Revision:: 19 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "damageablestaticphys.h" #include "simpledefinitionfactory.h" #include "persistfactory.h" #include "wwphysids.h" #include "hanim.h" #include "wwdebug.h" #include "wwhack.h" #include "combat.h" #include "explosion.h" DECLARE_FORCE_LINK( damageablestaticphys ); /* ** Chunk Id's used by DamageableStaticPhysDefClass */ enum { DAMAGEABLESTATICPHYSDEF_CHUNK_STATICANIMPHYSDEF = 7311734, DAMAGEABLESTATICPHYSDEF_CHUNK_VARIABLES, DAMAGEABLESTATICPHYSDEF_CHUNK_DEFENSEOBJECTDEF, DAMAGEABLESTATICPHYSDEF_VARIABLE_KILLEDEXPLOSION = 0x00, DAMAGEABLESTATICPHYSDEF_VARIABLE_RESETAFTERANIM, DAMAGEABLESTATICPHYSDEF_VARIABLE_LIVELOOPSTART, DAMAGEABLESTATICPHYSDEF_VARIABLE_LIVELOOPEND, DAMAGEABLESTATICPHYSDEF_VARIABLE_LIVETWITCHSTART, DAMAGEABLESTATICPHYSDEF_VARIABLE_LIVETWITCHEND, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEATHTRANSITIONSTART, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEATHTRANSITIONEND, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEADLOOPSTART, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEADLOOPEND, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEADTWITCHSTART, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEADTWITCHEND, DAMAGEABLESTATICPHYSDEF_VARIABLE_PLAYTWITCHESTOCOMPLETION, }; /* ** Chunk Id's used by DamageableStaticPhysClass */ enum { DAMAGEABLESTATICPHYS_CHUNK_STATICANIMPHYS = 7311740, DAMAGEABLESTATICPHYS_CHUNK_DEFENSEOBJECT, DAMAGEABLESTATICPHYS_CHUNK_VARIABLES, DAMAGEABLESTATICPHYS_VARIABLE_CURSTATE = 0x00, }; /************************************************************************************************ ** ** DamageableStaticPhysDefClass Implementation ** ************************************************************************************************/ SimplePersistFactoryClass _DamageableStaticPhysDefPersistFactory; DECLARE_DEFINITION_FACTORY(DamageableStaticPhysDefClass, CLASSID_DAMAGEABLESTATICPHYSDEF, "DamageableStaticPhys") _DamageableStaticPhysDefDefFactory; DamageableStaticPhysDefClass::DamageableStaticPhysDefClass(void) : StaticAnimPhysDefClass(), KilledExplosion(0), LiveLoopStart(0), LiveLoopEnd(0), LiveTwitchStart(0), LiveTwitchEnd(0), DeathTransitionStart(0), DeathTransitionEnd(0), DeadLoopStart(0), DeadLoopEnd(0), DeadTwitchStart(0), DeadTwitchEnd(0), PlayTwitchesToCompletion(false) { PARAM_SEPARATOR(DamageableStaticPhysDefClass, "Damage Behavior Controls"); EDITABLE_PARAM(DamageableStaticPhysDefClass, ParameterClass::TYPE_EXPLOSIONDEFINITIONID, KilledExplosion); INT_EDITABLE_PARAM(DamageableStaticPhysDefClass, LiveLoopStart, 0, 999); INT_EDITABLE_PARAM(DamageableStaticPhysDefClass, LiveLoopEnd, 0, 999); INT_EDITABLE_PARAM(DamageableStaticPhysDefClass, LiveTwitchStart, 0, 999); INT_EDITABLE_PARAM(DamageableStaticPhysDefClass, LiveTwitchEnd,0, 999); INT_EDITABLE_PARAM(DamageableStaticPhysDefClass, DeathTransitionStart,0, 999); INT_EDITABLE_PARAM(DamageableStaticPhysDefClass, DeathTransitionEnd,0, 999); INT_EDITABLE_PARAM(DamageableStaticPhysDefClass, DeadLoopStart,0, 999); INT_EDITABLE_PARAM(DamageableStaticPhysDefClass, DeadLoopEnd,0, 999); INT_EDITABLE_PARAM(DamageableStaticPhysDefClass, DeadTwitchStart,0, 999); INT_EDITABLE_PARAM(DamageableStaticPhysDefClass, DeadTwitchEnd,0, 999); EDITABLE_PARAM(DamageableStaticPhysDefClass,ParameterClass::TYPE_BOOL,PlayTwitchesToCompletion); DEFENSEOBJECTDEF_EDITABLE_PARAMS(DamageableStaticPhysDefClass, DefenseObjectDef); } uint32 DamageableStaticPhysDefClass::Get_Class_ID(void) const { return CLASSID_DAMAGEABLESTATICPHYSDEF; } bool DamageableStaticPhysDefClass::Is_Type(const char * type_name) { if (stricmp(type_name,DamageableStaticPhysDefClass::Get_Type_Name()) == 0) { return true; } else { return StaticAnimPhysDefClass::Is_Type(type_name); } } PersistClass * DamageableStaticPhysDefClass::Create(void) const { DamageableStaticPhysClass * obj = new DamageableStaticPhysClass; obj->Init(*this); return obj; } bool DamageableStaticPhysDefClass::Save(ChunkSaveClass & csave) { csave.Begin_Chunk(DAMAGEABLESTATICPHYSDEF_CHUNK_STATICANIMPHYSDEF); StaticAnimPhysDefClass::Save(csave); csave.End_Chunk(); csave.Begin_Chunk(DAMAGEABLESTATICPHYSDEF_CHUNK_VARIABLES); WRITE_MICRO_CHUNK(csave, DAMAGEABLESTATICPHYSDEF_VARIABLE_KILLEDEXPLOSION, KilledExplosion); WRITE_MICRO_CHUNK(csave, DAMAGEABLESTATICPHYSDEF_VARIABLE_LIVELOOPSTART,LiveLoopStart); WRITE_MICRO_CHUNK(csave, DAMAGEABLESTATICPHYSDEF_VARIABLE_LIVELOOPEND,LiveLoopEnd); WRITE_MICRO_CHUNK(csave, DAMAGEABLESTATICPHYSDEF_VARIABLE_LIVETWITCHSTART,LiveTwitchStart); WRITE_MICRO_CHUNK(csave, DAMAGEABLESTATICPHYSDEF_VARIABLE_LIVETWITCHEND,LiveTwitchEnd); WRITE_MICRO_CHUNK(csave, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEATHTRANSITIONSTART,DeathTransitionStart); WRITE_MICRO_CHUNK(csave, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEATHTRANSITIONEND,DeathTransitionEnd); WRITE_MICRO_CHUNK(csave, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEADLOOPSTART,DeadLoopStart); WRITE_MICRO_CHUNK(csave, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEADLOOPEND,DeadLoopEnd); WRITE_MICRO_CHUNK(csave, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEADTWITCHSTART,DeadTwitchStart); WRITE_MICRO_CHUNK(csave, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEADTWITCHEND,DeadTwitchEnd); WRITE_MICRO_CHUNK(csave, DAMAGEABLESTATICPHYSDEF_VARIABLE_PLAYTWITCHESTOCOMPLETION,PlayTwitchesToCompletion); csave.End_Chunk(); csave.Begin_Chunk(DAMAGEABLESTATICPHYSDEF_CHUNK_DEFENSEOBJECTDEF); DefenseObjectDef.Save(csave); csave.End_Chunk(); return true; } bool DamageableStaticPhysDefClass::Load( ChunkLoadClass &cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case DAMAGEABLESTATICPHYSDEF_CHUNK_STATICANIMPHYSDEF: StaticAnimPhysDefClass::Load( cload ); break; case DAMAGEABLESTATICPHYSDEF_CHUNK_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK(cload, DAMAGEABLESTATICPHYSDEF_VARIABLE_KILLEDEXPLOSION, KilledExplosion); READ_MICRO_CHUNK(cload, DAMAGEABLESTATICPHYSDEF_VARIABLE_LIVELOOPSTART,LiveLoopStart); READ_MICRO_CHUNK(cload, DAMAGEABLESTATICPHYSDEF_VARIABLE_LIVELOOPEND,LiveLoopEnd); READ_MICRO_CHUNK(cload, DAMAGEABLESTATICPHYSDEF_VARIABLE_LIVETWITCHSTART,LiveTwitchStart); READ_MICRO_CHUNK(cload, DAMAGEABLESTATICPHYSDEF_VARIABLE_LIVETWITCHEND,LiveTwitchEnd); READ_MICRO_CHUNK(cload, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEATHTRANSITIONSTART,DeathTransitionStart); READ_MICRO_CHUNK(cload, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEATHTRANSITIONEND,DeathTransitionEnd); READ_MICRO_CHUNK(cload, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEADLOOPSTART,DeadLoopStart); READ_MICRO_CHUNK(cload, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEADLOOPEND,DeadLoopEnd); READ_MICRO_CHUNK(cload, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEADTWITCHSTART,DeadTwitchStart); READ_MICRO_CHUNK(cload, DAMAGEABLESTATICPHYSDEF_VARIABLE_DEADTWITCHEND,DeadTwitchEnd); READ_MICRO_CHUNK(cload, DAMAGEABLESTATICPHYSDEF_VARIABLE_PLAYTWITCHESTOCOMPLETION,PlayTwitchesToCompletion); default: WWDEBUG_SAY(( "Unrecognized DamageableStaticPhysDef Variable chunkID\n" )); break; } cload.Close_Micro_Chunk(); } break; case DAMAGEABLESTATICPHYSDEF_CHUNK_DEFENSEOBJECTDEF: DefenseObjectDef.Load(cload); break; default: WWDEBUG_SAY(( "Unrecognized DamageableStaticPhysDef chunkID\n" )); break; } cload.Close_Chunk(); } return true; } const PersistFactoryClass & DamageableStaticPhysDefClass::Get_Factory (void) const { return _DamageableStaticPhysDefPersistFactory; } /************************************************************************************************ ** ** DamageableStaticPhysClass Implementation ** ************************************************************************************************/ SimplePersistFactoryClass _DamageableStaticPhysPersistFactory; const PersistFactoryClass & DamageableStaticPhysClass::Get_Factory (void) const { return _DamageableStaticPhysPersistFactory; } DamageableStaticPhysClass::DamageableStaticPhysClass(void) : CurState(STATE_LIVE_LOOP) { } DamageableStaticPhysClass::~DamageableStaticPhysClass( void ) { } void DamageableStaticPhysClass::Init( const DamageableStaticPhysDefClass & def ) { // DamageableStaticPhys are not PhysicalGameObjs, so pass in a NULL owner DefenseObject.Init(def.DefenseObjectDef, NULL); StaticAnimPhysClass::Init(def); Start_Loop(); } void DamageableStaticPhysClass::Timestep(float dt) { StaticAnimPhysClass::Timestep(dt); /* ** If we just reached a target, then transition into the loop for our current health state */ if ( (Get_Animation_Manager().Get_Animation_Mode() == AnimCollisionManagerClass::ANIMATE_TARGET) && (Get_Animation_Manager().Is_At_Target()) ) { Start_Loop(); } } void DamageableStaticPhysClass::Apply_Damage_Static( const OffenseObjectClass & offense ) { /* ** If we're already completely damaged, possibly start a twitch and then return */ if (DefenseObject.Get_Health() <= 0) { Play_Twitch(); return; } /* ** Apply the damage */ float new_health = DefenseObject.Apply_Damage(offense); /* ** If we just lost all of our health, implement all of the ** destruction effects. */ if (new_health <= 0) { /* ** Play our animation to the last frame */ Play_Death_Transition(); /* ** Set off an explosion if the user defined one */ const DamageableStaticPhysDefClass * def = Get_DamageableStaticPhysDef(); if (def->KilledExplosion != 0) { ExplosionManager::Create_Explosion_At(def->KilledExplosion,Get_Transform(),NULL); } } else { /* ** We were hit but didn't lose all of our health, so play a twitch */ Play_Twitch(); } } void DamageableStaticPhysClass::Reset_Health(void) { DefenseObject.Set_Health(DefenseObject.Get_Health_Max()); Start_Loop(); } void DamageableStaticPhysClass::Start_Loop(void) { const DamageableStaticPhysDefClass * def = Get_DamageableStaticPhysDef(); if (def != NULL) { int frame0,frame1; if (DefenseObject.Get_Health() > 0) { frame0 = def->LiveLoopStart; frame1 = def->LiveLoopEnd; CurState = STATE_LIVE_LOOP; } else { frame0 = def->DeadLoopStart; frame1 = def->DeadLoopEnd; CurState = STATE_DEAD_LOOP; } if (frame0 != frame1) { Get_Animation_Manager().Set_Animation_Mode(AnimCollisionManagerClass::ANIMATE_LOOP); Get_Animation_Manager().Set_Current_Frame(frame0); Get_Animation_Manager().Set_Loop_Start(frame0); Get_Animation_Manager().Set_Loop_End(frame1); } else { Get_Animation_Manager().Set_Animation_Mode(AnimCollisionManagerClass::ANIMATE_MANUAL); Get_Animation_Manager().Set_Current_Frame(frame0); } } else { WWDEBUG_SAY(("ERROR: Missing definition for damageable object: %s\n",Model->Get_Name())); } } void DamageableStaticPhysClass::Play_Twitch(void) { /* ** Never interrupt death transitions. */ if (CurState == STATE_DEATH_TRANSITION) { return; } /* ** Play the appropriate twitch depending on whether we are alive or dead */ const DamageableStaticPhysDefClass * def = Get_DamageableStaticPhysDef(); if (def != NULL) { /* ** Don't interrupt other twitches if the designer doesn't want us to */ if ((def->PlayTwitchesToCompletion == false) || ((CurState != STATE_LIVE_TWITCH) && (CurState != STATE_DEAD_TWITCH))) { int frame0,frame1; if (DefenseObject.Get_Health() > 0) { frame0 = def->LiveTwitchStart; frame1 = def->LiveTwitchEnd; CurState = STATE_LIVE_TWITCH; } else { frame0 = def->DeadTwitchStart; frame1 = def->DeadTwitchEnd; CurState = STATE_DEAD_TWITCH; } if (frame0 != frame1) { Get_Animation_Manager().Set_Animation_Mode(AnimCollisionManagerClass::ANIMATE_TARGET); Get_Animation_Manager().Set_Current_Frame(frame0); Get_Animation_Manager().Set_Target_Frame(frame1); } } } else { WWDEBUG_SAY(("ERROR: Missing definition for damageable object: %s\n",Model->Get_Name())); } } void DamageableStaticPhysClass::Play_Death_Transition(void) { const DamageableStaticPhysDefClass * def = Get_DamageableStaticPhysDef(); if (def != NULL) { if (def->DeathTransitionStart != def->DeathTransitionEnd) { CurState = STATE_DEATH_TRANSITION; Get_Animation_Manager().Set_Animation_Mode(AnimCollisionManagerClass::ANIMATE_TARGET); Get_Animation_Manager().Set_Current_Frame(def->DeathTransitionStart); Get_Animation_Manager().Set_Target_Frame(def->DeathTransitionEnd); } else { Start_Loop(); } } else { WWDEBUG_SAY(("ERROR: Missing definition for damageable object: %s\n",Model->Get_Name())); } } bool DamageableStaticPhysClass::Save( ChunkSaveClass & csave ) { /* ** Save the parent class data */ csave.Begin_Chunk(DAMAGEABLESTATICPHYS_CHUNK_STATICANIMPHYS); StaticAnimPhysClass::Save( csave ); csave.End_Chunk(); /* ** Save the contents of our damage object */ csave.Begin_Chunk(DAMAGEABLESTATICPHYS_CHUNK_DEFENSEOBJECT); DefenseObject.Save(csave); csave.End_Chunk(); /* ** Save our variables */ csave.Begin_Chunk(DAMAGEABLESTATICPHYS_CHUNK_VARIABLES); WRITE_MICRO_CHUNK(csave, DAMAGEABLESTATICPHYS_VARIABLE_CURSTATE, CurState); csave.End_Chunk(); return true; } bool DamageableStaticPhysClass::Load(ChunkLoadClass & cload) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case DAMAGEABLESTATICPHYS_CHUNK_STATICANIMPHYS: StaticAnimPhysClass::Load( cload ); break; case DAMAGEABLESTATICPHYS_CHUNK_DEFENSEOBJECT: DefenseObject.Load(cload); break; case DAMAGEABLESTATICPHYS_CHUNK_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK(cload, DAMAGEABLESTATICPHYS_VARIABLE_CURSTATE, CurState); default: WWDEBUG_SAY(( "Unrecognized DamageableStaticPhys Variable chunkID\n" )); break; } cload.Close_Micro_Chunk(); } break; default: WWDEBUG_SAY(( "Unrecognized DamageableStaticPhys chunkID\n" )); break; } cload.Close_Chunk(); } Start_Loop(); return true; }