This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/Combat/damageablestaticphys.cpp

494 lines
16 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/>.
*/
/***********************************************************************************************
*** 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<DamageableStaticPhysDefClass, PHYSICS_CHUNKID_DAMAGEABLESTATICPHYSDEF> _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<DamageableStaticPhysClass, PHYSICS_CHUNKID_DAMAGEABLESTATICPHYS> _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;
}