/* ** 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. // // // //////////////////////////////////////////////////////////////////////////////// // DeployStyleAIUpdate.cpp //////////// // Author: Kris Morness, August 2002 // Desc: A standard ai update that also handles units that must deploy to attack and pack before moving. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/Player.h" #include "Common/ThingFactory.h" #include "Common/ThingTemplate.h" #include "GameClient/Drawable.h" #include "GameClient/InGameUI.h" #include "GameLogic/ExperienceTracker.h" #include "GameLogic/Locomotor.h" #include "GameLogic/Object.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/Weapon.h" #include "GameLogic/Module/BodyModule.h" #include "GameLogic/Module/ContainModule.h" #include "GameLogic/Module/DeployStyleAIUpdate.h" #include "GameLogic/Module/PhysicsUpdate.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- DeployStyleAIUpdate::DeployStyleAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData ) { m_state = READY_TO_MOVE; m_frameToWaitForDeploy = 0; } //------------------------------------------------------------------------------------------------- DeployStyleAIUpdate::~DeployStyleAIUpdate( void ) { } //------------------------------------------------------------------------------------------------- Bool DeployStyleAIUpdate::isIdle() const { return AIUpdateInterface::isIdle(); } //------------------------------------------------------------------------------------------------- void DeployStyleAIUpdate::aiDoCommand( const AICommandParms* parms ) { if (!isAllowedToRespondToAiCommands(parms)) return; /* //Hack code to allow follow waypoint scripts to be converted to attack follow waypoint scripts //for simple-test-map-purposes. switch( parms->m_cmd ) { case AICMD_FOLLOW_WAYPOINT_PATH: ((AICommandParms*)parms)->m_cmd = AICMD_ATTACKFOLLOW_WAYPOINT_PATH; break; case AICMD_FOLLOW_WAYPOINT_PATH_AS_TEAM: ((AICommandParms*)parms)->m_cmd = AICMD_ATTACKFOLLOW_WAYPOINT_PATH_AS_TEAM; break; } */ AIUpdateInterface::aiDoCommand( parms ); } //------------------------------------------------------------------------------------------------- UpdateSleepTime DeployStyleAIUpdate::update( void ) { // have to call our parent's isIdle, because we override it to never return true // when we have a pending command... Object *self = getObject(); Weapon *weapon = self->getCurrentWeapon(); UnsignedInt now = TheGameLogic->getFrame(); const DeployStyleAIUpdateModuleData *data = getDeployStyleAIUpdateModuleData(); //Are we attempting to move? If so we can't do it unless we are undeployed. Bool isTryingToMove = isWaitingForPath() || getPath(); //Are we trying to attack something. If so, we need to be in range before we can do so. Bool isTryingToAttack = getStateMachine()->isInAttackState(); Bool isInRange = FALSE; //Are we in guard mode? If so, are we idle... idle guarders deploy for fastest response against attackers. Bool isInGuardIdleState = getStateMachine()->isInGuardIdleState(); AIUpdateInterface *ai = self->getAI(); if( isTryingToAttack && weapon ) { Object *victim = ai->getCurrentVictim(); if( victim ) { isInRange = weapon->isWithinAttackRange( self, victim ); } else { const Coord3D *pos = ai->getCurrentVictimPos(); if( pos ) { isInRange = weapon->isWithinAttackRange( self, pos ); } } } if( m_frameToWaitForDeploy != 0 && now >= m_frameToWaitForDeploy) { switch( m_state ) { case DEPLOY: setMyState( READY_TO_ATTACK ); break; case UNDEPLOY: setMyState( READY_TO_MOVE ); break; } } if( isInRange || isInGuardIdleState ) { switch( m_state ) { case READY_TO_MOVE: //We're need to deploy before we can attack. setMyState( DEPLOY ); break; case READY_TO_ATTACK: //Let the AI handle attacking. break; case DEPLOY: //We can't start attacking until we are fully deployed. break; case UNDEPLOY: if( m_frameToWaitForDeploy != 0 ) { //Reverse the undeploy at it's current frame! setMyState( DEPLOY, TRUE ); } break; case ALIGNING_TURRETS: //If turrets are aligning, we are able to attack right now. setMyState( READY_TO_ATTACK ); break; } } else if( isTryingToMove ) { switch( m_state ) { case READY_TO_MOVE: //We're ready... ai will handle moving. break; case READY_TO_ATTACK: { WhichTurretType tur = getWhichTurretForCurWeapon(); if( tur != TURRET_INVALID ) { if( doTurretsHaveToCenterBeforePacking() ) { //Turrets need to center before we can undeploy. setMyState( ALIGNING_TURRETS ); break; } } //Start undeploying. setMyState( UNDEPLOY ); break; } case DEPLOY: if( m_frameToWaitForDeploy != 0 ) { //Reverse the deploy at it's current frame! setMyState( UNDEPLOY, TRUE ); } break; case UNDEPLOY: break; case ALIGNING_TURRETS: { WhichTurretType tur = getWhichTurretForCurWeapon(); if( tur != TURRET_INVALID ) { if( isTurretInNaturalPosition( tur ) ) { //Turrets are centers, so now we can undeploy. setMyState( UNDEPLOY ); } } break; } } } switch( m_state ) { case READY_TO_MOVE: if( isTryingToMove ) { self->setModelConditionState( MODELCONDITION_MOVING ); } break; case READY_TO_ATTACK: break; case DEPLOY: if( data->m_manualDeployAnimations ) { UnsignedInt totalFrames = getPackTime(); UnsignedInt framesLeft = m_frameToWaitForDeploy - now; Drawable *draw = self->getDrawable(); if( draw ) { draw->setAnimationFrame( totalFrames - framesLeft ); } } getStateMachine()->setTemporaryState( AI_BUSY, UPDATE_SLEEP_NONE ); setLocomotorGoalNone(); break; case UNDEPLOY: if( data->m_manualDeployAnimations ) { UnsignedInt framesLeft = m_frameToWaitForDeploy - now; Drawable *draw = self->getDrawable(); if( draw ) { draw->setAnimationFrame( framesLeft ); } } getStateMachine()->setTemporaryState( AI_BUSY, UPDATE_SLEEP_NONE ); setLocomotorGoalNone(); break; case ALIGNING_TURRETS: getStateMachine()->setTemporaryState( AI_BUSY, UPDATE_SLEEP_NONE ); setLocomotorGoalNone(); break; } AIUpdateInterface::update(); //We can't sleep the deploy AI because any new commands need to be caught and sent //into busy state during the update. return UPDATE_SLEEP_NONE; } //------------------------------------------------------------------------------------------------- void DeployStyleAIUpdate::setMyState( DeployStateTypes stateID, Bool reverseDeploy ) { m_state = stateID; Object *self = getObject(); UnsignedInt now = TheGameLogic->getFrame(); const DeployStyleAIUpdateModuleData *data = getDeployStyleAIUpdateModuleData(); switch( stateID ) { case DEPLOY: { //Tell our object to deploy (so it can continue the same attack later). self->clearAndSetModelConditionFlags( MAKE_MODELCONDITION_MASK( MODELCONDITION_PACKING ), MAKE_MODELCONDITION_MASK( MODELCONDITION_UNPACKING ) ); if( reverseDeploy ) { //This is a zero to max animation. UnsignedInt totalFrames = getUnpackTime(); UnsignedInt framesLeft = m_frameToWaitForDeploy - now; m_frameToWaitForDeploy = now + totalFrames - framesLeft; if( data->m_manualDeployAnimations ) { Drawable *draw = self->getDrawable(); if( draw ) { draw->setAnimationFrame( totalFrames - framesLeft ); } } } else { m_frameToWaitForDeploy = getUnpackTime() + now; } //Play deploy sound const ThingTemplate *thing = self->getTemplate(); const AudioEventRTS* soundToPlayPtr = thing->getPerUnitSound( "Deploy" ); if( soundToPlayPtr ) { AudioEventRTS soundToPlay = *soundToPlayPtr; soundToPlay.setObjectID( self->getID() ); TheAudio->addAudioEvent( &soundToPlay ); } break; } case UNDEPLOY: { //This status will tell the approach AI state to succeed automatically. This prevents //twitching on deploy. Make sure we clear it now so when it goes into that state, it'll //actually move. self->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_DEPLOYED ) ); //Tell our object to pack up (so it can continue the same move later). self->clearAndSetModelConditionFlags( MAKE_MODELCONDITION_MASK2( MODELCONDITION_UNPACKING, MODELCONDITION_DEPLOYED ), MAKE_MODELCONDITION_MASK( MODELCONDITION_PACKING ) ); if( reverseDeploy ) { //This is a max to zero animation. UnsignedInt totalFrames = getUnpackTime(); UnsignedInt framesLeft = m_frameToWaitForDeploy - now; m_frameToWaitForDeploy = now + totalFrames - framesLeft; if( data->m_manualDeployAnimations ) { Drawable *draw = self->getDrawable(); if( draw ) { draw->setAnimationFrame( framesLeft ); } } } else { m_frameToWaitForDeploy = getPackTime() + now; } if( doTurretsFunctionOnlyWhenDeployed() ) { WhichTurretType tur = getWhichTurretForCurWeapon(); if( tur != TURRET_INVALID ) { setTurretEnabled( tur, false ); } } //Play undeploy sound const ThingTemplate *thing = self->getTemplate(); const AudioEventRTS* soundToPlayPtr = thing->getPerUnitSound( "Undeploy" ); if( soundToPlayPtr ) { AudioEventRTS soundToPlay = *soundToPlayPtr; soundToPlay.setObjectID( self->getID() ); TheAudio->addAudioEvent( &soundToPlay ); } break; } case READY_TO_MOVE: { m_frameToWaitForDeploy = 0; self->clearModelConditionFlags( MAKE_MODELCONDITION_MASK( MODELCONDITION_PACKING ) ); break; } case READY_TO_ATTACK: { //This status will tell the approach AI state to succeed automatically. This prevents //twitching on deploy. self->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_DEPLOYED ) ); m_frameToWaitForDeploy = 0; self->clearAndSetModelConditionFlags( MAKE_MODELCONDITION_MASK( MODELCONDITION_UNPACKING ), MAKE_MODELCONDITION_MASK( MODELCONDITION_DEPLOYED) ); if( doTurretsFunctionOnlyWhenDeployed() ) { WhichTurretType tur = getWhichTurretForCurWeapon(); if( tur != TURRET_INVALID ) { setTurretEnabled( tur, true ); } } break; } case ALIGNING_TURRETS: { m_frameToWaitForDeploy = 0; WhichTurretType tur = getWhichTurretForCurWeapon(); if( tur != TURRET_INVALID ) { recenterTurret( tur ); } break; } } } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void DeployStyleAIUpdate::crc( Xfer *xfer ) { // extend base class AIUpdateInterface::crc(xfer); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version * 2: Added support for attack move * 3: Added improved support for guard, and support for hunt AI **/ // ------------------------------------------------------------------------------------------------ void DeployStyleAIUpdate::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 4; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class AIUpdateInterface::xfer(xfer); if( version >= 4 ) { xfer->xferUser(&m_state, sizeof(m_state)); xfer->xferUnsignedInt(&m_frameToWaitForDeploy); } else if( xfer->getXferMode() == XFER_LOAD ) { //Good riddance!!! Bool obsoleteBool; UnsignedInt obsoleteUnsignedInt; ObjectID obsoleteObjectID; Coord3D obsoleteCoord3D; AICommandParmsStorage obsoleteAICommandParmsStorage; xfer->xferBool( &obsoleteBool ); xfer->xferUnsignedInt( &obsoleteUnsignedInt ); xfer->xferUser(&m_state, sizeof(m_state)); if( version >= 2 ) { xfer->xferObjectID( &obsoleteObjectID ); xfer->xferObjectID( &obsoleteObjectID ); xfer->xferCoord3D( &obsoleteCoord3D ); xfer->xferBool( &obsoleteBool ); xfer->xferBool( &obsoleteBool ); xfer->xferBool( &obsoleteBool ); } if( version >= 3 ) { xfer->xferBool( &obsoleteBool ); xfer->xferBool( &obsoleteBool ); } obsoleteAICommandParmsStorage.doXfer( xfer ); if( version < 2 ) { obsoleteAICommandParmsStorage.doXfer(xfer); } //Initialize unit to able to move. m_state = READY_TO_MOVE; } } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void DeployStyleAIUpdate::loadPostProcess( void ) { // extend base class AIUpdateInterface::loadPostProcess(); } // end loadPostProcess