/* ** Command & Conquer Generals Zero Hour(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) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// // FILE: SlowDeathBehavior.cpp /////////////////////////////////////////////////////////////////////// // Author: // Desc: /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #define DEFINE_SLOWDEATHPHASE_NAMES #include "Common/GameLOD.h" #include "Common/INI.h" #include "Common/RandomValue.h" #include "Common/Thing.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "GameClient/Drawable.h" #include "GameClient/FXList.h" #include "GameClient/InGameUI.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Module/BodyModule.h" #include "GameLogic/Module/PhysicsUpdate.h" #include "GameLogic/Module/SlowDeathBehavior.h" #include "GameLogic/Module/AIUpdate.h" #include "GameLogic/Module/SlavedUpdate.h" #include "GameLogic/Object.h" #include "GameLogic/ObjectCreationList.h" #include "GameLogic/Weapon.h" #include "GameClient/Drawable.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif const Real BEGIN_MIDPOINT_RATIO = 0.35f; const Real END_MIDPOINT_RATIO = 0.65f; //------------------------------------------------------------------------------------------------- SlowDeathBehaviorModuleData::SlowDeathBehaviorModuleData() { m_sinkRate = 0; m_probabilityModifier = 10; m_modifierBonusPerOverkillPercent = 0; m_sinkDelay = 0; m_sinkDelayVariance = 0; m_destructionDelay = 0; m_destructionDelayVariance = 0; m_destructionAltitude = -10; m_maskOfLoadedEffects = 0; //assume no ocl, fx, or weapons. m_flingForce = 0; m_flingForceVariance = 0; m_flingPitch = 0; m_flingPitchVariance = 0; // redundant. //m_fx.clear(); //m_ocls.clear(); //m_weapons.clear(); } //------------------------------------------------------------------------------------------------- static void parseFX( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ ) { SlowDeathBehaviorModuleData* self = (SlowDeathBehaviorModuleData*)instance; SlowDeathPhaseType sdphase = (SlowDeathPhaseType)INI::scanIndexList(ini->getNextToken(), TheSlowDeathPhaseNames); for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull()) { const FXList *fxl = TheFXListStore->findFXList((token)); // could be null! this is OK! self->m_fx[sdphase].push_back(fxl); if (fxl) self->m_maskOfLoadedEffects |= SlowDeathBehaviorModuleData::HAS_FX; } } //------------------------------------------------------------------------------------------------- static void parseOCL( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ ) { SlowDeathBehaviorModuleData* self = (SlowDeathBehaviorModuleData*)instance; SlowDeathPhaseType sdphase = (SlowDeathPhaseType)INI::scanIndexList(ini->getNextToken(), TheSlowDeathPhaseNames); for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull()) { const ObjectCreationList *ocl = TheObjectCreationListStore->findObjectCreationList(token); // could be null! this is OK! self->m_ocls[sdphase].push_back(ocl); if (ocl) self->m_maskOfLoadedEffects |= SlowDeathBehaviorModuleData::HAS_OCL; } } //------------------------------------------------------------------------------------------------- static void parseWeapon( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ ) { SlowDeathBehaviorModuleData* self = (SlowDeathBehaviorModuleData*)instance; SlowDeathPhaseType sdphase = (SlowDeathPhaseType)INI::scanIndexList(ini->getNextToken(), TheSlowDeathPhaseNames); for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull()) { const WeaponTemplate *wt = TheWeaponStore->findWeaponTemplate(token); // could be null! this is OK! self->m_weapons[sdphase].push_back(wt); if (wt) self->m_maskOfLoadedEffects |= SlowDeathBehaviorModuleData::HAS_WEAPON; } } //------------------------------------------------------------------------------------------------- /*static*/ void SlowDeathBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p) { UpdateModuleData::buildFieldParse(p); static const FieldParse dataFieldParse[] = { { "SinkRate", INI::parseVelocityReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_sinkRate ) }, { "ProbabilityModifier", INI::parseInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_probabilityModifier ) }, { "ModifierBonusPerOverkillPercent", INI::parsePercentToReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_modifierBonusPerOverkillPercent ) }, { "SinkDelay", INI::parseDurationUnsignedInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_sinkDelay ) }, { "SinkDelayVariance", INI::parseDurationUnsignedInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_sinkDelayVariance ) }, { "DestructionDelay", INI::parseDurationUnsignedInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_destructionDelay ) }, { "DestructionDelayVariance", INI::parseDurationUnsignedInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_destructionDelayVariance ) }, { "DestructionAltitude", INI::parseReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_destructionAltitude ) }, { "FX", parseFX, NULL, 0 }, { "OCL", parseOCL, NULL, 0 }, { "Weapon", parseWeapon, NULL, 0 }, { "FlingForce", INI::parseReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_flingForce) }, { "FlingForceVariance", INI::parseReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_flingForceVariance) }, { "FlingPitch", INI::parseAngleReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_flingPitch) }, { "FlingPitchVariance", INI::parseAngleReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_flingPitchVariance) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); p.add(DieMuxData::getFieldParse(), offsetof( SlowDeathBehaviorModuleData, m_dieMuxData )); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- SlowDeathBehavior::SlowDeathBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ) { m_flags = 0; m_sinkFrame = 0; m_midpointFrame = 0; m_destructionFrame = 0; m_acceleratedTimeScale = 1.0f; if (getSlowDeathBehaviorModuleData()->m_probabilityModifier < 1) { DEBUG_CRASH(("ProbabilityModifer must be >= 1.\n")); throw INI_INVALID_DATA; } setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- SlowDeathBehavior::~SlowDeathBehavior( void ) { } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Int SlowDeathBehavior::getProbabilityModifier( const DamageInfo *damageInfo ) const { // Calculating how far past dead we were allows us to pick more spectacular deaths when // severly killed, and more sedate ones when only slightly killed. // eg ( 200 hp max, had 10 left, took 50 damage, 40 overkill, (40/200) * 100 = 20 overkill %) Int overkillDamage = damageInfo->out.m_actualDamageDealt - damageInfo->out.m_actualDamageClipped; Real overkillPercent = (float)overkillDamage / (float)getObject()->getBodyModule()->getMaxHealth(); Int overkillModifier = overkillPercent * getSlowDeathBehaviorModuleData()->m_modifierBonusPerOverkillPercent; return max( getSlowDeathBehaviorModuleData()->m_probabilityModifier + overkillModifier, 1 ); } //------------------------------------------------------------------------------------------------- static void calcRandomForce(Real minMag, Real maxMag, Real minPitch, Real maxPitch, Coord3D& force) { Real angle = GameLogicRandomValueReal(-PI, PI); Real pitch = GameLogicRandomValueReal(minPitch, maxPitch); Real mag = GameLogicRandomValueReal(minMag, maxMag); Matrix3D mtx(1); mtx.Scale(mag); mtx.Rotate_Z(angle); mtx.Rotate_Y(-pitch); Vector3 v = mtx.Get_X_Vector(); force.x = v.X; force.y = v.Y; force.z = v.Z; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void SlowDeathBehavior::beginSlowDeath(const DamageInfo *damageInfo) { if (!isSlowDeathActivated()) { const SlowDeathBehaviorModuleData* d = getSlowDeathBehaviorModuleData(); Object* obj = getObject(); if (d->m_sinkRate && obj->isKindOf(KINDOF_INFANTRY)) { Drawable *draw = getObject()->getDrawable(); if ( draw ) { // this object sinks slowly after it dies so don't draw a // floating shadow decal on the ground above it. obj->getDrawable()->setShadowsEnabled(false); draw->setTerrainDecalFadeTarget( 0.0f, -0.2f ); } } // Ask game detail manager if we need to speedup all deaths to improve performance Real timeScale = TheGameLODManager->getSlowDeathScale(); m_acceleratedTimeScale = 1.0f; // assume normal death speed. if (timeScale == 0.0f && !d->hasNonLodEffects()) { // Deaths happen instantly so just delete the object and return TheGameLogic->destroyObject(obj); return; } else { // timescale is some non-zero value so we may need to speed up death if( getObject()->isKindOf( KINDOF_HULK ) && TheGameLogic->getHulkMaxLifetimeOverride() != -1 ) { //Scripts don't want hulks around, so start sinking immediately! m_sinkFrame = 1; m_midpointFrame = (LOGICFRAMES_PER_SECOND/2) + 1; m_destructionFrame = LOGICFRAMES_PER_SECOND + 1; m_acceleratedTimeScale = 1.0f; } else { m_sinkFrame = timeScale * (d->m_sinkDelay + GameLogicRandomValue(0, d->m_sinkDelayVariance)); m_destructionFrame = timeScale * (d->m_destructionDelay + GameLogicRandomValue(0, d->m_destructionDelayVariance)); m_midpointFrame = GameLogicRandomValue( BEGIN_MIDPOINT_RATIO * m_destructionFrame, END_MIDPOINT_RATIO * m_destructionFrame ); m_acceleratedTimeScale = timeScale; } } UnsignedInt now = TheGameLogic->getFrame(); if (d->m_flingForce > 0) { //Just in case this is a stingersoldier or other HELD object, lets set them free so they will fly // with their own physics during slow death if( obj->isDisabledByType( DISABLED_HELD ) ) { static NameKeyType key_SlavedUpdate = NAMEKEY( "SlavedUpdate" ); SlavedUpdate* slave = (SlavedUpdate*)obj->findUpdateModule( key_SlavedUpdate ); if( slave ) { slave->onSlaverDie( NULL ); } } PhysicsBehavior* physics = obj->getPhysics(); if (physics) { // make sure we are at least a bit above the ground const Real MIN_ALTITUDE = 1.0f; Real altitude = obj->getHeightAboveTerrain(); if (altitude < MIN_ALTITUDE) { Coord3D pos = *obj->getPosition(); pos.z += MIN_ALTITUDE; obj->setPosition(&pos); } Coord3D force; calcRandomForce(d->m_flingForce, d->m_flingForce + d->m_flingForceVariance, d->m_flingPitch, d->m_flingPitch + d->m_flingPitchVariance, force); physics->setAllowToFall(true); physics->applyForce(&force); physics->setExtraBounciness(-1.0); // we don't want this guy to bounce at all physics->setExtraFriction(-3 * SECONDS_PER_LOGICFRAME_REAL); // reduce his ground friction a bit physics->setAllowBouncing(true); Real orientation = atan2(force.y, force.x); physics->setAngles(orientation, 0, 0); obj->getDrawable()->setModelConditionState(MODELCONDITION_EXPLODED_FLAILING); m_flags |= (1< m_destructionFrame) whenToWakeTime = m_destructionFrame; if (whenToWakeTime > m_midpointFrame) whenToWakeTime = m_midpointFrame; setWakeFrame(obj, UPDATE_SLEEP(whenToWakeTime)); } m_sinkFrame += now; m_destructionFrame += now; m_midpointFrame += now; m_flags |= (1<m_maskOfLoadedEffects) return; //has no ocl, fx, or weapons. listSize = d->m_fx[sdphase].size(); if (listSize > 0) { idx = GameLogicRandomValue(0, listSize-1); const FXListVec& v = d->m_fx[sdphase]; DEBUG_ASSERTCRASH(idx>=0&&idxm_ocls[sdphase].size(); if (listSize > 0) { idx = GameLogicRandomValue(0, listSize-1); const OCLVec& v = d->m_ocls[sdphase]; DEBUG_ASSERTCRASH(idx>=0&&idxm_weapons[sdphase].size(); if (listSize > 0) { idx = GameLogicRandomValue(0, listSize-1); const WeaponTemplateVec& v = d->m_weapons[sdphase]; DEBUG_ASSERTCRASH(idx>=0&&idxcreateAndFireTempWeapon(wt, getObject(), getObject()->getPosition()); } } } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- UpdateSleepTime SlowDeathBehavior::update() { //DEBUG_LOG(("updating SlowDeathBehavior %08lx\n",this)); DEBUG_ASSERTCRASH(isSlowDeathActivated(), ("hmm, this should not be possible")); const SlowDeathBehaviorModuleData* d = getSlowDeathBehaviorModuleData(); Object* obj = getObject(); Real timeScale = TheGameLODManager->getSlowDeathScale(); // Check if we have normal time scale but LODManager is requeseting acceleration if (timeScale != 1.0f && m_acceleratedTimeScale == 1.0f && !d->hasNonLodEffects()) { // speed of deaths has been increased since beginning of death // so adjust it to current levels. if (timeScale == 0) { // instant death TheGameLogic->destroyObject(obj); return UPDATE_SLEEP_NONE; } m_sinkFrame = (Real)m_sinkFrame * timeScale; m_midpointFrame = (Real)m_midpointFrame * timeScale; m_destructionFrame = (Real)m_destructionFrame * timeScale; m_acceleratedTimeScale = timeScale; }; UnsignedInt now = TheGameLogic->getFrame(); if ((m_flags & (1<isAboveTerrain()) { obj->clearAndSetModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_EXPLODED_FLAILING), MAKE_MODELCONDITION_MASK(MODELCONDITION_EXPLODED_BOUNCING)); m_flags |= (1<getPhysics(); if ( phys ) { ObjectID treeID = phys->getLastCollidee(); Object *tree = TheGameLogic->findObjectByID( treeID ); if ( tree ) { if (tree->isKindOf( KINDOF_SHRUBBERY ) ) { obj->setDisabled( DISABLED_HELD ); obj->clearModelConditionFlags( MAKE_MODELCONDITION_MASK(MODELCONDITION_EXPLODED_FLAILING) ); obj->clearModelConditionFlags( MAKE_MODELCONDITION_MASK(MODELCONDITION_EXPLODED_BOUNCING) ); obj->setModelConditionFlags( MAKE_MODELCONDITION_MASK(MODELCONDITION_PARACHUTING) ); //looks like he is snagged in a tree obj->setPositionZ( obj->getPosition()->z - (d->m_sinkRate * 50.0f) );// make him sink faster if ( !obj->isAboveTerrain() ) TheGameLogic->destroyObject(obj); } } } } } if ( (now >= m_sinkFrame && d->m_sinkRate > 0.0f) ) { // disable Physics (if any) so that we can control the sink... obj->setDisabled( DISABLED_HELD ); Coord3D pos = *obj->getPosition(); pos.z -= d->m_sinkRate / m_acceleratedTimeScale; obj->setPosition( &pos ); } if( now >= m_midpointFrame && (m_flags & (1<= m_destructionFrame) { doPhaseStuff(SDPHASE_FINAL); TheGameLogic->destroyObject(obj); } return UPDATE_SLEEP_NONE; } //------------------------------------------------------------------------------------------------- void SlowDeathBehavior::onDie( const DamageInfo *damageInfo ) { Object *obj = getObject(); if (!isDieApplicable(damageInfo)) return; AIUpdateInterface *ai = obj->getAIUpdateInterface(); if (ai) { // has another AI already handled us. (hopefully another SlowDeathBehavior) if (ai->isAiInDeadState()) return; ai->markAsDead(); } // deselect this unit for all players. TheGameLogic->deselectObject(obj, PLAYERMASK_ALL, TRUE); Int total = 0; for (BehaviorModule** update = obj->getBehaviorModules(); *update; ++update) { SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface(); if (sdu != NULL && sdu->isDieApplicable(damageInfo)) { total += sdu->getProbabilityModifier( damageInfo ); } } DEBUG_ASSERTCRASH(total > 0, ("Hmm, this is wrong")); // this returns a value from 1...total, inclusive Int roll = GameLogicRandomValue(1, total); for (/* UpdateModuleInterface** */ update = obj->getBehaviorModules(); *update; ++update) { SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface(); if (sdu != NULL && sdu->isDieApplicable(damageInfo)) { roll -= sdu->getProbabilityModifier( damageInfo ); if (roll <= 0) { sdu->beginSlowDeath(damageInfo); return; } } } DEBUG_CRASH(("We should never get here")); } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void SlowDeathBehavior::crc( Xfer *xfer ) { // extend base class UpdateModule::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void SlowDeathBehavior::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class UpdateModule::xfer( xfer ); // sink frame xfer->xferUnsignedInt( &m_sinkFrame ); // midpoint frame xfer->xferUnsignedInt( &m_midpointFrame ); // destruction frame xfer->xferUnsignedInt( &m_destructionFrame ); // accelerated time scale xfer->xferReal( &m_acceleratedTimeScale ); // flags xfer->xferUnsignedInt( &m_flags ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void SlowDeathBehavior::loadPostProcess( void ) { // extend base class UpdateModule::loadPostProcess(); } // end loadPostProcess