/* ** 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. // // // //////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // MobMemberSlavedUpdate.cpp /////////////////////////////////////////////////////////////////////////// // Will obey spawner... or die trying // Author: Mark Lorenzen, August 2002 // Desc: Slaved unit(s) remain close to their master. Used by angry Mob members (various) /////////////////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "GameClient/InGameUI.h"// selection logic #include "GameClient/Drawable.h" #include "Common/RandomValue.h" #include "Common/Xfer.h" #include "GameClient/Drawable.h" #include "GameClient/ParticleSys.h" #include "GameLogic/AIPathfind.h" #include "GameLogic/Damage.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Locomotor.h" #include "GameLogic/Object.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/TerrainLogic.h" #include "GameLogic/Module/AIUpdate.h" #include "GameLogic/Module/BodyModule.h" #include "GameLogic/Module/MobMemberSlavedUpdate.h" #include "GameLogic/Module/SpawnBehavior.h" #include "GameClient/InGameUI.h"// selection logic #include "GameClient/Drawable.h" #include "Common/ThingFactory.h" #include "Common/ThingTemplate.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif #define STRAY_MULTIPLIER 2.0f // Multiplier from stating diestance from tunnel, to max distance from const Real CLOSE_ENOUGH = 15; // Our moveTo commands and pathfinding can't handle people in the way, so quit trying to hump someone on your spot const Real CLOSE_ENOUGH_SQR = (CLOSE_ENOUGH * CLOSE_ENOUGH); //------------------------------------------------------------------------------------------------- MobMemberSlavedUpdate::MobMemberSlavedUpdate( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ) { m_slaver = INVALID_ID; m_framesToWait = GameLogicRandomValue(0,20); // MDC: moving to GameLogicRandomValue. This does not need to be synced, but having it so makes searches *so* much nicer. m_personalColor.red = GameLogicRandomValueReal( 0.2f, 0.4f ); m_personalColor.green = GameLogicRandomValueReal( 0.2f, 0.4f ); m_personalColor.blue = GameLogicRandomValueReal( 0.2f, 0.4f ); // Drawable *myDraw = getObject()->getDrawable(); // if ( myDraw ) // myDraw->colorTint( &m_personalColor ); m_mobState = MOB_STATE_NONE; m_primaryVictimID = INVALID_ID; m_squirrellinessRatio = 0; m_isSelfTasking = FALSE; m_catchUpCrisisTimer = 0; // MDC: moving to GameLogicRandomValue. This does not need to be synced, but having it so makes searches *so* much nicer. //getObject()->getDrawable()->setInstanceScale(GameLogicRandomValueReal( 5.0f, 1.5f )); } //------------------------------------------------------------------------------------------------- MobMemberSlavedUpdate::~MobMemberSlavedUpdate( void ) { } //------------------------------------------------------------------------------------------------- void MobMemberSlavedUpdate::onObjectCreated() { const MobMemberSlavedUpdateModuleData* data = getMobMemberSlavedUpdateModuleData(); m_squirrellinessRatio = MIN(MAX_SQUIRRELLINESS, MAX(0, data->m_squirrellinessRatio)); } //------------------------------------------------------------------------------------------------- void MobMemberSlavedUpdate::onEnslave( const Object *slaver ) { startSlavedEffects( slaver ); } //------------------------------------------------------------------------------------------------- void MobMemberSlavedUpdate::onSlaverDie( const DamageInfo *info ) { stopSlavedEffects(); } //------------------------------------------------------------------------------------------------- void MobMemberSlavedUpdate::onSlaverDamage( const DamageInfo *info ) { // Only slaves with a ProneUpdate will even care. AIUpdateInterface *ai = getObject()->getAIUpdateInterface(); if( ai ) ai->aiGoProne( info, CMD_FROM_AI ); } //------------------------------------------------------------------------------------------------- UpdateSleepTime MobMemberSlavedUpdate::update( void ) { /// @todo srj use SLEEPY_UPDATE here const MobMemberSlavedUpdateModuleData* data = getMobMemberSlavedUpdateModuleData(); Object *me = getObject(); if( !me ) { return UPDATE_SLEEP_NONE; } Object *master = TheGameLogic->findObjectByID( m_slaver ); if( master == NULL ) { stopSlavedEffects(); //TheGameLogic->destroyObject( me ); me->kill(); return UPDATE_SLEEP_NONE; // you cannot return SLEEP_FOREVER unless you make yourself sleepy... } AIUpdateInterface *myAI = me->getAIUpdateInterface(); AIUpdateInterface *masterAI = master->getAIUpdateInterface(); if( ! myAI || ! masterAI) { return UPDATE_SLEEP_NONE; } Drawable *myDraw = me->getDrawable(); Drawable *masterDraw = master->getDrawable(); if ( ! myDraw || ! masterDraw) { return UPDATE_SLEEP_NONE; } // myDraw->colorTint( &m_personalColor ); const ModelConditionFlags flags = myDraw->getModelConditionFlags(); if (flags.anyIntersectionWith(MAKE_MODELCONDITION_MASK(MODELCONDITION_WEAPONSET_PLAYER_UPGRADE))) { ModelConditionFlags clearFlags; clearFlags.clear(); clearFlags.set( MODELCONDITION_RELOADING_A ); clearFlags.set( MODELCONDITION_BETWEEN_FIRING_SHOTS_A ); clearFlags.set( MODELCONDITION_PREATTACK_A ); clearFlags.set( MODELCONDITION_FIRING_A ); clearFlags.set( MODELCONDITION_USING_WEAPON_A ); myDraw->clearModelConditionFlags( clearFlags ); } if ( ++m_framesToWait < 16) return UPDATE_SLEEP_NONE; m_framesToWait = 0; Locomotor *locomotor = myAI->getCurLocomotor(); if( !locomotor ) { return UPDATE_SLEEP_NONE; } Object *victim = getObject()->getAIUpdateInterface()->getCurrentVictim(); Object *masterVictim = master->getAIUpdateInterface()->getCurrentVictim(); if (masterVictim) { m_primaryVictimID = masterVictim->getID(); } Object *primaryVictim = TheGameLogic->findObjectByID(m_primaryVictimID); //now, we don't know if master is standing still or going somewhere, so Real masterPathDistToGoal = masterAI->getLocomotorDistanceToGoal(); Real myPathDistToGoal = myAI->getLocomotorDistanceToGoal(); Real catchUpRadiusSquared = ThePartitionManager->getDistanceSquared( me, master, FROM_CENTER_3D ); // I'm too far from the nexus... I need to catch up now! if( catchUpRadiusSquared > sqr( data->m_mustCatchUpRadius ) ) { if ( masterAI->isMoving() )// master is on the move { if ( masterPathDistToGoal > myPathDistToGoal ) // I'm getting ahead of master, need to slow down myAI->chooseLocomotorSet(LOCOMOTORSET_WANDER); else myAI->chooseLocomotorSet(LOCOMOTORSET_PANIC); // I'm lagging, so I need to snap to it! Coord3D nuPos = *masterAI->getGoalPosition(); if ( nuPos.length() < 1.0f ) //if a nasty error has sent me to map origin { myAI->aiMoveToPosition( master->getPosition(), CMD_FROM_AI ); // NASTY BEEHIVE EFFECT } else { Coord3D goalDelta = *myAI->getGoalPosition(); goalDelta.sub( &nuPos ); if ( goalDelta.length() > 5.0f * PATHFIND_CELL_SIZE_F )// only if I am not headed there already { myAI->aiMoveToPosition( &nuPos, CMD_FROM_AI ); // Whither thou goest... THis causes the mob to reconverge } } // on the fly, instead of doubling back to reconverge } else // master is still, so let's re group in a hurry { myAI->chooseLocomotorSet(LOCOMOTORSET_PANIC); myAI->aiMoveToPosition( master->getPosition(), CMD_FROM_AI ); } if (catchUpRadiusSquared > sqr( data->m_mustCatchUpRadius * 3))// I am critically far, now! { ++ m_catchUpCrisisTimer; // I'm way too far from the nexus this frame if ( m_catchUpCrisisTimer > data->m_catchUpCrisisBailTime) { me->kill(); return UPDATE_SLEEP_NONE; // Here is the rethink: // If the nexus has outrun me to the target by so much, // // lets make the nexus come to me, and try to return to where I find it // Coord3D masterPosition = *master->getPosition(); // master->setPosition( me->getPosition() ); // if ( ! masterAI->isMoving() ) // { // masterAI->aiMoveToPosition( &masterPosition, CMD_FROM_AI ); // } // m_catchUpCrisisTimer = 0; } else if ( m_catchUpCrisisTimer > data->m_catchUpCrisisBailTime/3 ) { myAI->aiMoveToPosition( master->getPosition(), CMD_FROM_AI ); // NASTY BEEHIVE EFFECT } } } else if ( myAI->isMoving() ) // we're all on a trip, together { m_catchUpCrisisTimer = 0; // I'm not too far from the nexus this frame Int seed = GameLogicRandomValue( 0, 10 ); if ( seed == 1 ) myAI->chooseLocomotorSet(LOCOMOTORSET_WANDER); else if ( seed == 2 ) myAI->chooseLocomotorSet(LOCOMOTORSET_PANIC); else if ( seed == 3 ) myAI->chooseLocomotorSet(LOCOMOTORSET_NORMAL); // else if ( seed >= 5 ) // go towards mommy's goal // { // Coord3D destination = *me->getPosition(); // TheAI->pathfinder()->adjustToPossibleDestination(me, myAI->getLocomotorSet(), &destination); // myAI->aiMoveToPosition( &destination, CMD_FROM_AI ); // reconverge // } } else // give me something to do while I'm standing here... { m_catchUpCrisisTimer = 0; // I'm not too far from the nexus this frame SpawnBehaviorInterface *spawnerBehavior = master->getSpawnBehaviorInterface(); if ( spawnerBehavior ) // if I have a mommy { if ( masterAI->isIdle() ) // if controlling player has pressed stop, we stop! That's it! { myAI->aiIdle(CMD_FROM_AI); primaryVictim = NULL; m_primaryVictimID = INVALID_ID; return UPDATE_SLEEP_NONE; } if ( spawnerBehavior->maySpawnSelfTaskAI( m_squirrellinessRatio ) ) // if mommy says it is okay { if ( myAI->getLastCommandSource() != CMD_FROM_AI ) // I may have been told to attack directly more recently { Object *newTarget = myAI->getNextMoodTarget( FALSE, FALSE ); if ( newTarget && ( newTarget != victim) ) // if there is someone else around to attack { victim = newTarget; myAI->aiAttackObject( newTarget, 999, CMD_FROM_AI ); // go ahead and do it m_isSelfTasking = TRUE; } } } if ( ! victim ) // If I still don't have anyone to shoot at { if ( primaryVictim ) // I remember the last target { myAI->aiAttackObject( primaryVictim, 999, CMD_FROM_AI ); } else if( ! masterAI->isAttacking())// there Is no previous target and master isn't attacking { /// myAI->aiIdle( CMD_FROM_AI );// auto acquire mode } m_isSelfTasking = FALSE; } } else { DEBUG_ASSERTCRASH(( spawnerBehavior != NULL ),("Hey!, why for this mob member got no spawner? MLorenzen")); } } return UPDATE_SLEEP_NONE; } //------------------------------------------------------------------------------------------------- // We are too far from nexus, so we need to catch-up //------------------------------------------------------------------------------------------------- void MobMemberSlavedUpdate::doCatchUpLogic( Coord3D *pos ) { Coord3D nuPos; const MobMemberSlavedUpdateModuleData* data = getMobMemberSlavedUpdateModuleData(); // recalc where we want to be if we wander around Real randomDirection = GameLogicRandomValue( 0, 2*PI ); Real randomRadius = GameLogicRandomValue( 0, data->m_noNeedToCatchUpRadius ); nuPos.set(pos); nuPos.x += randomRadius * Cos( randomDirection ); nuPos.y += randomRadius * Sin( randomDirection ); nuPos.z = TheTerrainLogic->getGroundHeight( nuPos.x, nuPos.y ); AIUpdateInterface *ai = getObject()->getAIUpdateInterface(); if( ai ) { ai->aiMoveToPosition( &nuPos, CMD_FROM_AI ); } setMobState(MOB_STATE_CATCHING_UP); } //------------------------------------------------------------------------------------------------- void MobMemberSlavedUpdate::startSlavedEffects( const Object *slaver ) { if( slaver == NULL ) return; m_slaver = slaver->getID(); // mark selves as not selectable //getObject()->setStatus( OBJECT_STATUS_UNSELECTABLE ); } //------------------------------------------------------------------------------------------------- void MobMemberSlavedUpdate::stopSlavedEffects() { m_slaver = INVALID_ID; /// @todo Just a thought. Our Status bits on objects really need to be reference counts so you don't clear someone else's flag getObject()->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNSELECTABLE ) ); getObject()->clearDisabled( DISABLED_HELD ); } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void MobMemberSlavedUpdate::crc( Xfer *xfer ) { // extend base class UpdateModule::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void MobMemberSlavedUpdate::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class UpdateModule::xfer( xfer ); // slaves xfer->xferObjectID( &m_slaver ); // frames to wait xfer->xferInt( &m_framesToWait ); // mob state xfer->xferUser( &m_mobState, sizeof( MobStates ) ); // personal color xfer->xferRGBColor( &m_personalColor ); // primary victim xfer->xferObjectID( &m_primaryVictimID ); // squirrelliness ration xfer->xferReal( &m_squirrellinessRatio ); // is self tasking xfer->xferBool( &m_isSelfTasking ); // catch up crisis timer xfer->xferUnsignedInt( &m_catchUpCrisisTimer ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void MobMemberSlavedUpdate::loadPostProcess( void ) { // extend base class UpdateModule::loadPostProcess(); } // end loadPostProcess