| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541 |
- /*
- ** 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 <http://www.gnu.org/licenses/>.
- */
- ////////////////////////////////////////////////////////////////////////////////
- // //
- // (c) 2001-2003 Electronic Arts Inc. //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- // AssaultTransportAIUpdate.cpp ////////////
- // Author: Kris Morness, December 2002
- // Desc: State machine that allows assault transports (troop crawler) to deploy
- // troops, order them to attack, then return. Can do extra things like ordering
- // injured troops to return to the transport for healing purposes.
- #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
- #include "Common/Player.h"
- #include "Common/ThingFactory.h"
- #include "GameClient/Drawable.h"
- #include "GameClient/InGameUI.h"
- #include "GameLogic/ExperienceTracker.h"
- #include "GameLogic/Module/BodyModule.h"
- #include "GameLogic/Module/ContainModule.h"
- #include "GameLogic/Module/AssaultTransportAIUpdate.h"
- #include "GameLogic/Module/PhysicsUpdate.h"
- #include "GameLogic/Object.h"
- #include "GameLogic/PartitionManager.h"
- #include "GameLogic/Weapon.h"
- #ifdef _INTERNAL
- // for occasional debugging...
- //#pragma optimize("", off)
- //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
- #endif
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- AssaultTransportAIUpdate::AssaultTransportAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData )
- {
- m_currentMembers = MAX_TRANSPORT_SLOTS; //First time, max it out, to ensure clearing arrays in reset.
- reset();
- }
- //-------------------------------------------------------------------------------------------------
- void AssaultTransportAIUpdate::reset()
- {
- for( int i = 0; i < m_currentMembers; i++ )
- {
- m_memberIDs[ i ] = INVALID_ID;
- m_memberHealing[ i ] = FALSE;
- m_newMember[ i ] = FALSE;
- }
- m_currentMembers = 0;
- m_attackMoveGoalPos.zero();
- m_designatedTarget = INVALID_ID;
- m_state = IDLE;
- m_framesRemaining = 0;
- m_isAttackMove = FALSE;
- m_isAttackObject = FALSE;
- m_newOccupantsAreNewMembers = FALSE;
- }
- //-------------------------------------------------------------------------------------------------
- AssaultTransportAIUpdate::~AssaultTransportAIUpdate( void )
- {
- }
- //-------------------------------------------------------------------------------------------------
- void AssaultTransportAIUpdate::aiDoCommand(const AICommandParms* parms)
- {
- //Inspect the command and reset everything when necessary.
- if( parms->m_cmdSource != CMD_FROM_AI )
- {
- //Now the only time we care about anything is if we were ordered to attack something or attack move.
- switch( parms->m_cmd )
- {
- case AICMD_ATTACKMOVE_TO_POSITION:
- //Reset because we have been ordered to do something.
- reset();
- m_attackMoveGoalPos = parms->m_pos;
- m_isAttackMove = TRUE;
- break;
- case AICMD_ATTACK_OBJECT:
- //Reset because we have been ordered to do something.
- reset();
- //m_designatedTarget = parms->m_obj ? parms->m_obj->getID() : INVALID_ID;
- m_isAttackObject = TRUE;
- break;
- case AICMD_IDLE:
- m_designatedTarget = INVALID_ID;
- //Order all outside members back inside!
- retrieveMembers();
- reset();
- break;
- default:
- //Reset because we have been ordered to do something we're not handling.
- reset();
- break;
- }
- }
- //Note, in both cases, the transport will fire a dummy DEPLOY weapon that will trigger the
- //evacuation of the troops.
- AIUpdateInterface::aiDoCommand( parms );
- }
- //-------------------------------------------------------------------------------------------------
- void AssaultTransportAIUpdate::beginAssault( const Object *designatedTarget ) const
- {
- //The transport has determined it is in range to begin the assault (via weapon system).
- //Now order the evacuation of healthy troops, and let the update handle moving them.
- if( designatedTarget )
- {
- m_designatedTarget = designatedTarget->getID();
- }
- }
- //-------------------------------------------------------------------------------------------------
- Bool AssaultTransportAIUpdate::isIdle() const
- {
- return AIUpdateInterface::isIdle();
- }
- //-------------------------------------------------------------------------------------------------
- UpdateSleepTime calcSleepTime()
- {
- return UPDATE_SLEEP_NONE;
- }
- //-------------------------------------------------------------------------------------------------
- UpdateSleepTime AssaultTransportAIUpdate::update( void )
- {
- Object *transport = getObject();
- //const AssaultTransportAIUpdateModuleData *data = getAssaultTransportAIUpdateModuleData();
- if( transport->isEffectivelyDead() )
- {
- giveFinalOrders();
- return UPDATE_SLEEP_FOREVER;
- }
- //First removing dead members or members that have been ordered to do something outside of this AI.
- if( m_currentMembers )
- {
- for( int i = 0; i < m_currentMembers; i++ )
- {
- Object *member = TheGameLogic->findObjectByID( m_memberIDs[ i ] );
- AIUpdateInterface *ai = member ? member->getAI() : NULL;
- if( !member || member->isEffectivelyDead() || ai->getLastCommandSource() != CMD_FROM_AI )
- {
- //Member is toast -- so remove him from our list!
- if( m_currentMembers - 1 > i )
- {
- //Move the last slot to this slot to keep array contiguous.
- m_memberIDs[ i ] = m_memberIDs[ m_currentMembers - 1 ];
- m_memberHealing[ i ] = m_memberHealing[ m_currentMembers - 1 ];
- m_newMember[ i ] = m_newMember[ m_currentMembers - 1 ];
- }
- else
- {
- //Just clean out last slot.
- m_memberIDs[ i ] = INVALID_ID;
- m_memberHealing[ i ] = FALSE;
- m_newMember[ i ] = FALSE;
- }
- if( ai )
- {
- //Important! Members of our assault transport must be allowed to chase down designated enemies.
- //Generally only player commands allow this, so this flag allows AI commands to do the same.
- //We need to turn this off though, because this ex-member is no longer under transport control.
- ai->setAllowedToChase( FALSE );
- }
- m_currentMembers--;
- }
- }
- }
- //Now add any potentially new members to the group.
- ContainModuleInterface *contain = transport->getContain();
- if( contain )
- {
- const ContainedItemsList *passengerList = contain->getContainedItemsList();
- ContainedItemsList::const_iterator passengerIterator;
- passengerIterator = passengerList->begin();
- while( passengerIterator != passengerList->end() )
- {
- Object *passenger = *passengerIterator;
- //Advance to the next iterator
- passengerIterator++;
- //Make sure it isn't in our list already.
- Bool found = FALSE;
- for( int i = 0; i < m_currentMembers; i++ )
- {
- if( passenger->getID() == m_memberIDs[ i ] )
- {
- //He is in the list... so skip him.
- found = TRUE;
- break;
- }
- }
- if( found )
- {
- //Get next passenger.
- continue;
- }
- //It's possible to add members manually -- but if we already have 10 members, then wait!
- if( m_currentMembers < MAX_TRANSPORT_SLOTS )
- {
- //Not in list, so add him!
- m_memberIDs[ m_currentMembers ] = passenger->getID();
- if( passenger->getAI() )
- {
- //Important! Members of our assault transport must be allowed to chase down designated enemies.
- //Generally only player commands allow this, so this flag allows AI commands to do the same.
- passenger->getAI()->setAllowedToChase( TRUE );
- }
-
- //Check if the passenger is wounded below threshhold (if so make sure we heal him before ordering him to fight!)
- if( isMemberWounded( passenger ) )
- {
- m_memberHealing[ m_currentMembers ] = TRUE;
- }
- if( m_newOccupantsAreNewMembers )
- {
- //New members won't eject out until a new attack order is issued.
- m_newMember[ m_currentMembers ] = TRUE;
- }
- m_currentMembers++;
- }
- }
- m_newOccupantsAreNewMembers = TRUE;
- }
- if( isAttackPointless() )
- {
- aiIdle( CMD_FROM_AI );
- return UPDATE_SLEEP_NONE;
- }
- //Keep track of the average position of all combat units assigned to me.
- Coord3D fighterCentroidPos;
- UnsignedInt fightingMembers = 0;
- fighterCentroidPos.zero();
- //If we're already in the process, reacquire the designated target again... see if
- //it's still alive.
- Object *designatedTarget = TheGameLogic->findObjectByID( m_designatedTarget );
- if( designatedTarget && designatedTarget->isEffectivelyDead() )
- {
- designatedTarget = NULL;
- }
- if( designatedTarget )
- {
- //Look for members not currently attacking this target.
- for( int i = 0; i < m_currentMembers; i++ )
- {
- Object *member = TheGameLogic->findObjectByID( m_memberIDs[ i ] );
- AIUpdateInterface *ai = member ? member->getAI() : NULL;
-
- if( member && ai )
- {
- Bool contained = member->isContained();
- Bool wounded = isMemberWounded( member );
- if( contained && isMemberHealthy( member ) && !m_newMember[ i ] )
- {
- //This contained member is healthy so order him to exit to start fighting!
- //New members are exempt!
- ai->aiExit( transport, CMD_FROM_AI );
- }
- if( !contained )
- {
- if( wounded )
- {
- if( ai->getAIStateType() != AI_ENTER )
- {
- //Order wounded members back to get healed.
- ai->aiEnter( transport, CMD_FROM_AI );
- }
- }
- else
- {
- //Increment the number of fighters and their position.
- fighterCentroidPos.add( member->getPosition() );
- fightingMembers++;
- if( !ai->isMoving() )
- {
- if( ai->getGoalObject() != designatedTarget )
- {
- //Okay, this dude is outside and waiting... order him to attack the designated target
- ai->aiAttackObject( designatedTarget, NO_MAX_SHOTS_LIMIT, CMD_FROM_AI );
- }
- }
- }
- }
- }
- }
- }
- else
- {
- if( m_isAttackMove && getAIStateType() != AI_ATTACK_MOVE_TO )
- {
- //Continue to move towards the attackmove area.
- aiAttackMoveToPosition( &m_attackMoveGoalPos, NO_MAX_SHOTS_LIMIT, CMD_FROM_AI );
- }
- else if( m_isAttackObject )
- {
- retrieveMembers();
- }
- }
- /*
- //Keep near the troops.
- if( !m_framesRemaining )
- {
- if( !isMoving() && fightingMembers && designatedTarget )
- {
- m_framesRemaining = 45;
- //Get centriod pos now that we know the number of fighting members.
- Real scale = 1.0f / (Real)fightingMembers;
- fighterCentroidPos.scale( scale );
- Coord3D designatedTargetPos = *designatedTarget->getPosition();
- //Calculate a vector from the target passed the fighters to be at a safe place
- //to be as a transport.
- Coord3D vector;
- vector.set( &fighterCentroidPos );
- vector.sub( &designatedTargetPos );
- vector.normalize();
- vector.scale( 150.0f );
- Coord3D transportGoalPos;
- transportGoalPos.set( &designatedTargetPos );
- transportGoalPos.add( &vector );
- Real distanceSqrd = ThePartitionManager->getDistanceSquared( transport, &transportGoalPos, FROM_CENTER_2D );
- if( distanceSqrd > 40.0f * 40.0f )
- {
- //Order the transport to move to the safer position
- //aiMoveToPosition( &transportGoalPos, CMD_FROM_AI );
- }
- }
- }
- else
- {
- m_framesRemaining--;
- }
- if( designatedTarget && !isMoving() )
- {
- //Order the transport to face the designated target!
- //aiFaceObject( designatedTarget, CMD_FROM_AI );
- }
- */
-
- /*UpdateSleepTime ret =*/ AIUpdateInterface::update();
- //return (mine < ret) ? mine : ret;
- /// @todo srj -- someday, make sleepy. for now, must not sleep.
- return UPDATE_SLEEP_NONE;
- }
- //-------------------------------------------------------------------------------------------------
- Bool AssaultTransportAIUpdate::isAttackPointless() const
- {
- //If all members are new members (thus can't attack), and the transport itself
- //is still attacking, stop!
- const Object *transport = getObject();
- if( transport->testStatus( OBJECT_STATUS_IS_ATTACKING ) )
- {
- for( int i = 0; i < m_currentMembers; i++ )
- {
- if( !m_newMember[ i ] )
- {
- //We have a non-new member, so attack is valid.
- return FALSE;
- }
- }
- //We are trying to attack, but can't because all our members are new.
- return TRUE;
- }
- //We aren't trying to attack, so everything is good.
- return FALSE;
- }
- //-------------------------------------------------------------------------------------------------
- Bool AssaultTransportAIUpdate::isMemberWounded( const Object *member ) const
- {
- const AssaultTransportAIUpdateModuleData *data = getAssaultTransportAIUpdateModuleData();
- BodyModuleInterface *body = member->getBodyModule();
- if( body )
- {
- Real ratio = body->getHealth() / body->getMaxHealth();
- if( ratio < data->m_membersGetHealedAtLifeRatio )
- {
- return TRUE;
- }
- }
- return FALSE;
- }
- //-------------------------------------------------------------------------------------------------
- Bool AssaultTransportAIUpdate::isMemberHealthy( const Object *member ) const
- {
- BodyModuleInterface *body = member->getBodyModule();
- if( body )
- {
- if( body->getHealth() == body->getMaxHealth() )
- {
- return TRUE;
- }
- }
- return FALSE;
- }
- //-------------------------------------------------------------------------------------------------
- void AssaultTransportAIUpdate::retrieveMembers()
- {
- //Order all outside members back inside!
- for( int i = 0; i < m_currentMembers; i++ )
- {
- Object *member = TheGameLogic->findObjectByID( m_memberIDs[ i ] );
- AIUpdateInterface *ai = member ? member->getAI() : NULL;
- if( member && ai )
- {
- Bool contained = member->isContained();
- if( !contained )
- {
- //This contained member is healthy so order him to exit to start fighting!
- if (ai->getAIStateType() != AI_ENTER) {
- ai->aiEnter( getObject(), CMD_FROM_AI );
- }
- }
- }
- }
- }
- //-------------------------------------------------------------------------------------------------
- void AssaultTransportAIUpdate::giveFinalOrders()
- {
- //All members have been ejected outside already -- transfer the original order to the troops
- for( int i = 0; i < m_currentMembers; i++ )
- {
- Object *member = TheGameLogic->findObjectByID( m_memberIDs[ i ] );
- AIUpdateInterface *ai = member ? member->getAI() : NULL;
- if( member && ai )
- {
- Object *designatedTarget = TheGameLogic->findObjectByID( m_designatedTarget );
- if( m_isAttackObject && designatedTarget )
- {
- ai->aiAttackObject( designatedTarget, NO_MAX_SHOTS_LIMIT, CMD_FROM_PLAYER );
- }
- else if( m_isAttackMove )
- {
- ai->aiAttackMoveToPosition( &m_attackMoveGoalPos, NO_MAX_SHOTS_LIMIT, CMD_FROM_PLAYER );
- }
- ai->setAllowedToChase( FALSE );
- }
- }
- }
- //-------------------------------------------------------------------------------------------------
- /** CRC */
- //-------------------------------------------------------------------------------------------------
- void AssaultTransportAIUpdate::crc( Xfer *xfer )
- {
- // extend base class
- AIUpdateInterface::crc(xfer);
- } // end crc
- //-------------------------------------------------------------------------------------------------
- /** Xfer method
- * Version Info:
- * 1: Initial version */
- //-------------------------------------------------------------------------------------------------
- void AssaultTransportAIUpdate::xfer( Xfer *xfer )
- {
- // version
- XferVersion currentVersion = 1;
- XferVersion version = currentVersion;
- xfer->xferVersion( &version, currentVersion );
-
- // extend base class
- AIUpdateInterface::xfer(xfer);
- xfer->xferInt( &m_currentMembers );
- for( int i = 0; i < m_currentMembers; i++ )
- {
- xfer->xferObjectID( &(m_memberIDs[ i ]) );
- xfer->xferBool( &(m_memberHealing[ i ]) );
- }
- xfer->xferCoord3D( &m_attackMoveGoalPos );
- xfer->xferObjectID( &m_designatedTarget );
-
- Int state = (Int)m_state;
- xfer->xferInt( &state );
- m_state = (AssaultStateTypes)state;
-
- xfer->xferUnsignedInt( &m_framesRemaining );
- xfer->xferBool( &m_isAttackMove );
- xfer->xferBool( &m_isAttackObject );
- } // end xfer
- //-------------------------------------------------------------------------------------------------
- /** Load post process */
- //-------------------------------------------------------------------------------------------------
- void AssaultTransportAIUpdate::loadPostProcess( void )
- {
- // extend base class
- AIUpdateInterface::loadPostProcess();
- } // end loadPostProcess
|