/* ** 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: TensileFormationUpdate.cpp ////////////////////////////////////////////////////////////////////////// // Author: Mark Lorenzen, November 2002 // Desc: Springy formation movement like that of say, an avalanche /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/Xfer.h" #include "Common/QuickTrig.h" #include "Common/AudioEventRTS.h" #include "GameLogic/Object.h" #include "GameLogic/TerrainLogic.h" #include "GameLogic/Module/TensileFormationUpdate.h" #include "GameLogic/Module/BodyModule.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/GameLogic.h" #include "GameLogic/AI.h" #include "GameLogic/AIPathfind.h" #include "GameClient/TerrainVisual.h" #include "GameClient/Drawable.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif #define MAP_XY_FACTOR (10.0f) //How wide and tall each height map square is in world space. #define MAP_HEIGHT_SCALE (MAP_XY_FACTOR/16.0f) //divide all map heights by 8. //--------------------------------------------------------------------------------- TensileFormationUpdate* getTFU( Object *obj ) { static NameKeyType theTFUKey = TheNameKeyGenerator->nameToKey( "TensileFormationUpdate" ); TensileFormationUpdate* tfu = (TensileFormationUpdate *)obj->findUpdateModule( theTFUKey ); return tfu; } class PartitionFilterTensileFormationMember : public PartitionFilter { private: Object* m_obj; public: PartitionFilterTensileFormationMember( Object* obj ) : m_obj( obj ) { } #if defined(_DEBUG) || defined(_INTERNAL) virtual const char* debugGetName() { return "PartitionFilterTensileFormationMember"; } #endif virtual Bool allow( Object *objOther ) { return ( getTFU( objOther ) != NULL ); } }; // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ TensileFormationUpdateModuleData::TensileFormationUpdateModuleData( void ) { m_enabled = FALSE; } // end TensileFormationUpdateModuleData // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ /*static*/ void TensileFormationUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) { UpdateModuleData::buildFieldParse( p ); static const FieldParse dataFieldParse[] = { { "Enabled", INI::parseBool, NULL, offsetof( TensileFormationUpdateModuleData, m_enabled ) }, { "CrackSound", INI::parseAudioEventRTS, NULL, offsetof( TensileFormationUpdateModuleData, m_crackSound) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); } // end buildFieldParse /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ TensileFormationUpdate::TensileFormationUpdate( Thing *thing, const ModuleData *moduleData ) :UpdateModule( thing, moduleData ) { const TensileFormationUpdateModuleData *modData = getTensileFormationUpdateModuleData(); // save our initial enabled status based on INI settings m_enabled = ((TensileFormationUpdateModuleData *)moduleData)->m_enabled; m_inertia.set(0,0,0); m_linksInited = FALSE; m_motionlessCounter = 0; m_life = 0; m_lowestSlideElevation = 255.0f; for ( int t = 0; t < 4; ++t) { m_links[ t ].id = INVALID_ID; m_links[ t ].tensor.set(0,0,0); } m_crackSound = modData->m_crackSound; m_crackSound.setObjectID( getObject()->getID() ) ; TheAI->pathfinder()->createAWallFromMyFootprint( getObject() ); // Temporarily treat this object as an obstacle. //Coord3D pos = *getObject()->getPosition(); //if ( pos.z > 80) //{ // pos.z = 80 + ( ( pos.z - 80 ) * 2); // getObject()->setPosition( &pos ); //} } // end TensileFormationUpdate // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ TensileFormationUpdate::~TensileFormationUpdate( void ) { } // end ~TensileFormationUpdate void TensileFormationUpdate::initLinks( void ) { m_linksInited = TRUE; Object *obj = getObject(); if ( !obj ) return; PartitionFilterTensileFormationMember tfmFilter( getObject() ); PartitionFilter *filters[] = { &tfmFilter, NULL }; SimpleObjectIterator *iter = NULL; iter = ThePartitionManager->iterateObjectsInRange(getObject(), 1000.0f, FROM_BOUNDINGSPHERE_3D, filters); MemoryPoolObjectHolder hold(iter); Real closestDistance = 99999.9f; Real thisDistance = 99999.9f; const Coord3D *myPos = obj->getPosition(); for (Object* other = iter->first(); other; other = iter->next()) { const Coord3D *theirPos = other->getPosition(); Coord3D delta = { theirPos->x - myPos->x, theirPos->y - myPos->y, theirPos->z - myPos->z }; thisDistance = delta.length(); if ( closestDistance > thisDistance ) { closestDistance = thisDistance; //push a new link for ( int t = 3; t > 0; --t ) m_links[ t ] = m_links[ t-1 ]; m_links[ 0 ].id = other->getID(); m_links[ 0 ].tensor = delta; } } //ICoord2D gridPos; //gridPos.x = REAL_TO_INT_FLOOR(getObject()->getPosition()->x/MAP_XY_FACTOR); //gridPos.y = REAL_TO_INT_FLOOR(getObject()->getPosition()->y/MAP_XY_FACTOR); //TheTerrainVisual->setRawMapHeight(&gridPos, 500); getObject()->setOrientation(GameLogicRandomValueReal(-PI,PI)); } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ UpdateSleepTime TensileFormationUpdate::update( void ) { if ( ! m_linksInited ) initLinks(); if( m_enabled == FALSE ) { //We are all going to sit here idle and do not much of anything until one of us gets hurt BodyModuleInterface *body = getObject()->getBodyModule(); BodyDamageType bdt = body->getDamageState(); if ( bdt >= BODY_DAMAGED ) { //THen the hurt one (maybe me) will tell every other member of the formation to enable //So we will start moving setEnabled( TRUE ); TheAI->pathfinder()->removeWallFromMyFootprint( getObject() ); // Undo createAWallFromMyFootprint. //ALert players of disaster happening if ( ! m_crackSound.isCurrentlyPlaying()) m_crackSound.setPlayingHandle(TheAudio->addAudioEvent( &m_crackSound )); } else return UPDATE_SLEEP(30); } Drawable *draw = getObject()->getDrawable(); if ( ! draw ) return UPDATE_SLEEP_NONE; if (++m_life > 300) { draw->clearModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_MOVING)); draw->clearModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_FREEFALL)); draw->clearModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_POST_COLLAPSE)); BodyModuleInterface *body = getObject()->getBodyModule(); body->setDamageState( BODY_RUBBLE ); TheAI->pathfinder()->createAWallFromMyFootprint( getObject() ); // Temporarily treat this object as an obstacle. return UPDATE_SLEEP_FOREVER; } if ( m_life%30 == 29 ) propagateDislodgement( TRUE ); //APPLY PHYSICS=========================== const Coord3D *pos = getObject()->getPosition(); Coord3D normal = { 0.0f, 0.0f, 1.0f }; TheTerrainLogic->getGroundHeight(pos->x, pos->y, &normal); // which way does the ground slope? Coord3D slope = { normal.x, normal.y, 0.0f}; Real steepness = 1.0f - normal.z; slope.scale( 0.3f + steepness); m_inertia.add( &slope ); Real friction = 0.95f; m_inertia.scale( friction ); Coord3D newPos; newPos.x = pos->x + m_inertia.x;//flow down the slope newPos.y = pos->y + m_inertia.y; newPos.z = TheTerrainLogic->getGroundHeight(newPos.x, newPos.y);//rest on surface here Object *tree = ThePartitionManager->getClosestObject( &newPos, getObject()->getGeometryInfo().getMajorRadius(), FROM_CENTER_2D ); if (tree->isKindOf( KINDOF_SHRUBBERY )) tree->topple( &m_inertia, m_inertia.length(), 1 );//No Bounce //APPLY TENSORS=========================== Coord3D tensorSum = { 0, 0, 0 }; for ( int t = 0; t < 4; ++t ) { Object *other = TheGameLogic->findObjectByID( m_links[ t ].id ); if ( other ) { Coord3D desiredPos = *other->getPosition(); Coord3D tensor = m_links[ t ].tensor; desiredPos.sub( &tensor ); //Coord3D desiredPos = { theirPos->x - m_links[ t ].tensor.x, theirPos->y - m_links[ t ].tensor.y, theirPos->z - m_links[ t ].tensor.z }; newPos.x = newPos.x*0.93f + desiredPos.x*0.07f; newPos.y = newPos.y*0.93f + desiredPos.y*0.07f; newPos.z = MIN( m_lowestSlideElevation, TheTerrainLogic->getGroundHeight(newPos.x, newPos.y) );//rest on surface here tensor.normalize(); tensorSum.add( &tensor ); } } tensorSum.normalize(); Coord3D inertiaNormal = m_inertia; inertiaNormal.normalize(); draw->setModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_POST_COLLAPSE)); if ( m_life < 200 ) draw->setModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_MOVING)); else draw->clearModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_MOVING)); if ( fabs( pos->z - newPos.z ) > 0.2f && m_life < 100) draw->setModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_FREEFALL)); else draw->clearModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_FREEFALL)); // { // RGBColor cyan = {0,1,1}; // draw->flashAsSelected(&cyan); // } //else //m_motionlessCounter = 0; //if ( newPos.z > 80) // newPos.z = 80 + ( ( newPos.z - 80 ) * 2); m_lowestSlideElevation = newPos.z; getObject()->setPosition( &newPos ); return UPDATE_SLEEP_NONE; } // end update //------------------------------------------------------------------------------------- //------------------------------------------------------------------------------- void TensileFormationUpdate::propagateDislodgement ( Bool enabled ) { PartitionFilterTensileFormationMember tfmFilter( getObject() ); PartitionFilter *filters[] = { &tfmFilter, NULL }; SimpleObjectIterator *iter = NULL; iter = ThePartitionManager->iterateObjectsInRange(getObject(), 100.0f, FROM_BOUNDINGSPHERE_3D, filters); MemoryPoolObjectHolder hold(iter); for (Object* other = iter->first(); other; other = iter->next()) { //Object *other = TheGameLogic->findObjectByID( m_links[ t ].id ); if ( other ) { BodyModuleInterface *body = other->getBodyModule(); if ( body ) { body->setDamageState( BODY_DAMAGED ); } } //TensileFormationUpdate* tfu = getTFU(other); //if ( tfu != NULL ) //{ // tfu->setEnabled( enabled ); //} } for ( int t = 0; t < 4; ++t ) { Object *other = TheGameLogic->findObjectByID( m_links[ t ].id ); if ( other ) { BodyModuleInterface *body = other->getBodyModule(); if ( body ) { body->setDamageState( BODY_DAMAGED ); } } } } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void TensileFormationUpdate::crc( Xfer *xfer ) { // extend base class UpdateModule::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void TensileFormationUpdate::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class UpdateModule::xfer( xfer ); // enabled xfer->xferBool( &m_enabled ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void TensileFormationUpdate::loadPostProcess( void ) { // extend base class UpdateModule::loadPostProcess(); } // end loadPostProcess