/* ** Command & Conquer Generals(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: StructureCollapseUpdate.cpp /////////////////////////////////////////////////////////////////////// // Author: // Desc: /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/Thing.h" #include "Common/ThingTemplate.h" #include "Common/INI.h" #include "Common/RandomValue.h" #include "Common/GlobalData.h" #include "Common/Xfer.h" #include "GameClient/FXList.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Module/BoneFXUpdate.h" #include "GameLogic/Module/StructureCollapseUpdate.h" #include "GameLogic/Module/AIUpdate.h" #include "GameLogic/Object.h" #include "GameLogic/ObjectCreationList.h" #include "GameClient/Drawable.h" #include "GameClient/InGameUI.h" const Int MAX_IDX = 32; //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- static const char *TheStructureCollapsePhaseNames[] = { "INITIAL", "DELAY", "BURST", "FINAL", NULL }; //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- StructureCollapseUpdate::StructureCollapseUpdate( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ) { m_collapseFrame = 0; m_collapseState = COLLAPSESTATE_STANDING; m_collapseVelocity = 0.0f; //Added By Sadullah Nader //Initialization(s) inserted m_burstFrame = 0; m_currentHeight = 0.0f; // setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- StructureCollapseUpdate::~StructureCollapseUpdate( void ) { } //------------------------------------------------------------------------------------------------- static void parseFX( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ ) { StructureCollapseUpdateModuleData* self = (StructureCollapseUpdateModuleData*)instance; StructureCollapsePhaseType scphase = (StructureCollapsePhaseType)INI::scanIndexList(ini->getNextToken(), TheStructureCollapsePhaseNames); 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_fxs[scphase].push_back(fxl); } } //------------------------------------------------------------------------------------------------- static void parseOCL( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ ) { StructureCollapseUpdateModuleData* self = (StructureCollapseUpdateModuleData*)instance; StructureCollapsePhaseType stphase = (StructureCollapsePhaseType)INI::scanIndexList(ini->getNextToken(), TheStructureCollapsePhaseNames); 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[stphase].push_back(ocl); } } //------------------------------------------------------------------------------------------------- /*static*/ void StructureCollapseUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) { UpdateModuleData::buildFieldParse(p); static const FieldParse dataFieldParse[] = { { "MinCollapseDelay", INI::parseDurationUnsignedInt, NULL, offsetof( StructureCollapseUpdateModuleData, m_minCollapseDelay ) }, { "MaxCollapseDelay", INI::parseDurationUnsignedInt, NULL, offsetof( StructureCollapseUpdateModuleData, m_maxCollapseDelay ) }, { "MinBurstDelay", INI::parseDurationUnsignedInt, NULL, offsetof( StructureCollapseUpdateModuleData, m_minBurstDelay ) }, { "MaxBurstDelay", INI::parseDurationUnsignedInt, NULL, offsetof( StructureCollapseUpdateModuleData, m_maxBurstDelay ) }, { "CollapseDamping", INI::parseReal, NULL, offsetof( StructureCollapseUpdateModuleData, m_collapseDamping ) }, { "MaxShudder", INI::parseReal, NULL, offsetof( StructureCollapseUpdateModuleData, m_maxShudder ) }, { "BigBurstFrequency", INI::parseInt, NULL, offsetof( StructureCollapseUpdateModuleData, m_bigBurstFrequency ) }, { "OCL", parseOCL, NULL, 0 }, { "FXList", parseFX, NULL, 0 }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); p.add(DieMuxData::getFieldParse(), offsetof( StructureCollapseUpdateModuleData, m_dieMuxData )); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void StructureCollapseUpdate::beginStructureCollapse(const DamageInfo *damageInfo) { const StructureCollapseUpdateModuleData *d = getStructureCollapseUpdateModuleData(); Object *building = getObject(); UnsignedInt now = TheGameLogic->getFrame(); // This has to use a game logic random value since the bursts can spawn debris, and debris is sync'd. m_collapseFrame = now + GameLogicRandomValue(d->m_minCollapseDelay, d->m_maxCollapseDelay); doPhaseStuff(SCPHASE_INITIAL, building->getPosition()); m_collapseState = COLLAPSESTATE_WAITINGFORCOLLAPSESTART; m_currentHeight = 0.0f; setWakeFrame(getObject(), UPDATE_SLEEP_NONE); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void StructureCollapseUpdate::onDie( const DamageInfo *damageInfo ) { const StructureCollapseUpdateModuleData* d = getStructureCollapseUpdateModuleData(); if (!d->m_dieMuxData.isDieApplicable(getObject(), damageInfo)) return; AIUpdateInterface *ai = getObject()->getAIUpdateInterface(); if (ai) ai->markAsDead(); // deselect this object for all players. TheGameLogic->deselectObject(getObject(), PLAYERMASK_ALL, TRUE); beginStructureCollapse(damageInfo); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- UpdateSleepTime StructureCollapseUpdate::update( void ) { static const Real COLLAPSE_ACCELERATION_FACTOR = 0.02f; const StructureCollapseUpdateModuleData *d = getStructureCollapseUpdateModuleData(); if (m_collapseState == COLLAPSESTATE_STANDING) { DEBUG_CRASH(("hmm, what?")); return UPDATE_SLEEP_FOREVER; } // We are in the dramatic pause between when the building has lost all its hit points and // when it starts toppling over. if (m_collapseState == COLLAPSESTATE_WAITINGFORCOLLAPSESTART) { UnsignedInt now = TheGameLogic->getFrame(); Object *building = getObject(); const Coord3D *currentPosition = building->getPosition(); Vector3 shudder; shudder.Set(GameClientRandomValueReal(-(d->m_maxShudder), d->m_maxShudder), GameClientRandomValueReal(-(d->m_maxShudder), d->m_maxShudder), 0); const Matrix3D *instMatrix = building->getDrawable()->getInstanceMatrix(); Matrix3D newInstMatrix; newInstMatrix = *instMatrix; newInstMatrix.Set_Translation(shudder); building->getDrawable()->setInstanceMatrix(&newInstMatrix); if (now >= m_collapseFrame) { m_collapseState = COLLAPSESTATE_COLLAPSING; doPhaseStuff(SCPHASE_BURST, currentPosition); // This has to use a game logic random value since the bursts can spawn debris, and debris is sync'd. m_burstFrame = now + GameLogicRandomValue(d->m_minBurstDelay, d->m_maxBurstDelay); } } // The building is in the process of falling over. if (m_collapseState == COLLAPSESTATE_COLLAPSING) { Object *building = getObject(); UnsignedInt now = TheGameLogic->getFrame(); m_currentHeight -= m_collapseVelocity; m_collapseVelocity -= TheGlobalData->m_gravity * (1.0 - d->m_collapseDamping); const Coord3D *currentPosition = building->getPosition(); Vector3 shudder; shudder.Set(GameClientRandomValueReal(-(d->m_maxShudder), d->m_maxShudder), GameClientRandomValueReal(-(d->m_maxShudder), d->m_maxShudder), m_currentHeight); const Matrix3D *instMatrix = building->getDrawable()->getInstanceMatrix(); Matrix3D newInstMatrix; newInstMatrix = *instMatrix; newInstMatrix.Set_Translation(shudder); building->getDrawable()->setInstanceMatrix(&newInstMatrix); if (now >= m_burstFrame) { if (GameLogicRandomValue(1, d->m_bigBurstFrequency) == 1) { doPhaseStuff(SCPHASE_BURST, currentPosition); } else { doPhaseStuff(SCPHASE_DELAY, currentPosition); } // This has to use a game logic random value since the bursts can spawn debris, and debris is sync'd. m_burstFrame += GameLogicRandomValue(d->m_minBurstDelay, d->m_maxBurstDelay); } // if ((m_currentHeight + building->getGeometryInfo().getMaxHeightAbovePosition()) <= 0) if ((m_currentHeight + building->getTemplate()->getTemplateGeometryInfo().getMaxHeightAbovePosition()) <= 0) { m_collapseState = COLLAPSESTATE_DONE; doPhaseStuff(SCPHASE_FINAL, building->getPosition()); Drawable *drawable = building->getDrawable(); doCollapseDoneStuff(); drawable->clearModelConditionState(MODELCONDITION_RUBBLE); drawable->setModelConditionState(MODELCONDITION_POST_COLLAPSE); building->setOrientation(building->getOrientation()); // Need to update body particle systems, now BodyModuleInterface *body = building->getBodyModule(); body->updateBodyParticleSystems(); Vector3 shudder; shudder.Set(0, 0, 0); const Matrix3D *instMatrix = building->getDrawable()->getInstanceMatrix(); Matrix3D newInstMatrix; newInstMatrix = *instMatrix; newInstMatrix.Set_Translation(shudder); building->getDrawable()->setInstanceMatrix(&newInstMatrix); return UPDATE_SLEEP_FOREVER; } } return UPDATE_SLEEP_NONE; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- inline Bool inList(Int value, Int count, const Int idxList[]) { for (Int j = 0; j < count; ++j) { if (idxList[j] == value) return true; } return false; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- static void buildNonDupRandomIndexList(Int range, Int count, Int idxList[]) { for (Int i = 0; i < count; ++i) { Int idx; do { idx = GameLogicRandomValue(0, range-1); } while (inList(idx, i, idxList)); idxList[i] = idx; } } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void StructureCollapseUpdate::doPhaseStuff(StructureCollapsePhaseType scphase, const Coord3D *target) { DEBUG_LOG(("Firing phase %d on frame %d\n", scphase, TheGameLogic->getFrame())); const StructureCollapseUpdateModuleData* d = getStructureCollapseUpdateModuleData(); Int i, idx, count, listSize; Int idxList[MAX_IDX]; listSize = d->m_fxs[scphase].size(); if (listSize > 0) { count = d->m_fxCount[scphase]; buildNonDupRandomIndexList(listSize, count, idxList); for (i = 0; i < count; ++i) { idx = idxList[i]; const FXVec& v = d->m_fxs[scphase]; DEBUG_ASSERTCRASH(idx>=0&&idxm_ocls[scphase].size(); if (listSize > 0) { count = d->m_oclCount[scphase]; buildNonDupRandomIndexList(listSize, count, idxList); for (i = 0; i < count; ++i) { idx = idxList[i]; const OCLVec& v = d->m_ocls[scphase]; DEBUG_ASSERTCRASH(idx>=0&&idxfindUpdateModule(key_BoneFXUpdate); if (bfxu != NULL) { bfxu->stopAllBoneFX(); } } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void StructureCollapseUpdate::crc( Xfer *xfer ) { // extend base class UpdateModule::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void StructureCollapseUpdate::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class UpdateModule::xfer( xfer ); // collapse frame xfer->xferUnsignedInt( &m_collapseFrame ); // burst frame xfer->xferUnsignedInt( &m_burstFrame ); // collapse state xfer->xferUser( &m_collapseState, sizeof( StructureCollapseStateType ) ); // collapse velocity xfer->xferReal( &m_collapseVelocity ); // current height xfer->xferReal( &m_currentHeight ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void StructureCollapseUpdate::loadPostProcess( void ) { // extend base class UpdateModule::loadPostProcess(); } // end loadPostProcess