Initial commit of Command & Conquer Generals and Command & Conquer Generals Zero Hour source code.

This commit is contained in:
LFeenanEA 2025-02-27 17:34:39 +00:00
parent 2e338c00cb
commit 3d0ee53a05
No known key found for this signature in database
GPG key ID: C6EBE8C2EA08F7E0
6072 changed files with 2283311 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,819 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// AIDock.cpp
// Implementation of docking behavior
// Author: Michael S. Booth, February 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Module.h"
#include "Common/Player.h"
#include "GameLogic/Object.h"
#include "GameLogic/AIDock.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/SupplyTruckAIUpdate.h"
#include "GameLogic/Module/UpdateModule.h"
//----------------------------------------------------------------------------------------------------------
/**
* Create an AI state machine. Define all of the states the machine
* can possibly be in, and set the initial (default) state.
*/
AIDockMachine::AIDockMachine( Object *obj ) : StateMachine( obj, "AIDockMachine" )
{
static const StateConditionInfo waitForClearanceConditions[] =
{
StateConditionInfo(ableToAdvance, AI_DOCK_ADVANCE_POSITION, NULL),
StateConditionInfo(NULL, NULL, NULL) // keep last
};
// order matters: first state is the default state.
defineState( AI_DOCK_APPROACH, newInstance(AIDockApproachState)( this ), AI_DOCK_WAIT_FOR_CLEARANCE, EXIT_MACHINE_WITH_FAILURE );
defineState( AI_DOCK_WAIT_FOR_CLEARANCE, newInstance(AIDockWaitForClearanceState)( this ), AI_DOCK_MOVE_TO_ENTRY, EXIT_MACHINE_WITH_FAILURE, waitForClearanceConditions );
defineState( AI_DOCK_ADVANCE_POSITION, newInstance(AIDockAdvancePositionState)( this ), AI_DOCK_WAIT_FOR_CLEARANCE, EXIT_MACHINE_WITH_FAILURE );
defineState( AI_DOCK_MOVE_TO_ENTRY, newInstance(AIDockMoveToEntryState)( this ), AI_DOCK_MOVE_TO_DOCK, AI_DOCK_MOVE_TO_EXIT );
defineState( AI_DOCK_MOVE_TO_DOCK, newInstance(AIDockMoveToDockState)( this ), AI_DOCK_PROCESS_DOCK, AI_DOCK_MOVE_TO_EXIT );
defineState( AI_DOCK_PROCESS_DOCK, newInstance(AIDockProcessDockState)( this ), AI_DOCK_MOVE_TO_EXIT, AI_DOCK_MOVE_TO_EXIT );
defineState( AI_DOCK_MOVE_TO_EXIT, newInstance(AIDockMoveToExitState)( this ), AI_DOCK_MOVE_TO_RALLY, EXIT_MACHINE_WITH_FAILURE );
defineState( AI_DOCK_MOVE_TO_RALLY, newInstance(AIDockMoveToRallyState)( this ), EXIT_MACHINE_WITH_SUCCESS, EXIT_MACHINE_WITH_FAILURE );
m_approachPosition = -1;
}
AIDockMachine::~AIDockMachine()
{
}
//-----------------------------------------------------------------------------
void AIDockMachine::halt()
{
Object *goalObject = getGoalObject();
// sanity
if( goalObject != NULL )
{
// get dock update interface
DockUpdateInterface *dock = goalObject->getDockUpdateInterface();
// We need to say goodbye, or we will leave our spot taken forever.
if( dock != NULL )
dock->cancelDock( getOwner() );
}
StateMachine::halt();
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIDockMachine::crc( Xfer *xfer )
{
StateMachine::crc(xfer);
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIDockMachine::xfer( Xfer *xfer )
{
XferVersion cv = 1;
XferVersion v = cv;
xfer->xferVersion( &v, cv );
StateMachine::xfer(xfer);
xfer->xferInt(&m_approachPosition);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIDockMachine::loadPostProcess( void )
{
StateMachine::loadPostProcess();
} // end loadPostProcess
// State transition conditions ----------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/* static */ Bool AIDockMachine::ableToAdvance( State *thisState, void* userData )
{
Object *goalObject = thisState->getMachineGoalObject();
AIDockMachine *myMachine = (AIDockMachine *)thisState->getMachine();
if( goalObject == NULL )
return FALSE;
DockUpdateInterface *dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if( dock == NULL )
return FALSE;
// if the dock says we can advance, then sidetrack to the scoot forward state
if( dock->isClearToAdvance( thisState->getMachineOwner(), myMachine->m_approachPosition ) )
return TRUE;
// continue to wait
return FALSE;
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIDockApproachState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
if (version>=2) {
AIInternalMoveToState::xfer(xfer);
}
} // end xfer
//----------------------------------------------------------------------------------------------
/**
* Approach our waiting spot next to the dock.
*/
StateReturnType AIDockApproachState::onEnter( void )
{
Object *goalObject = getMachineGoalObject();
// sanity
if( goalObject == NULL )
return STATE_FAILURE;
// get dock update interface
DockUpdateInterface *dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// fail if the dock is closed
if( dock->isDockOpen() == FALSE )
{
dock->cancelDock( getMachineOwner() );
return STATE_FAILURE;
}
// get a good place to wait from the dock
Bool reserved = dock->reserveApproachPosition( getMachineOwner(), &m_goalPosition, &(( (AIDockMachine*)getMachine() )->m_approachPosition) );
if( reserved == FALSE )
{
// dock is full
return STATE_FAILURE;
}
AIUpdateInterface *ai = getMachineOwner()->getAIUpdateInterface();
if (ai) {
ai->ignoreObstacle( NULL );
}
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::onEnter();
}
//----------------------------------------------------------------------------------------------
StateReturnType AIDockApproachState::update( void )
{
Object *goalObject = getMachineGoalObject();
// if we have nothing to dock with, fail
if (goalObject == NULL)
return STATE_FAILURE;
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::update();
}
//----------------------------------------------------------------------------------------------
void AIDockApproachState::onExit( StateExitType status )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// tell the dock we have approached
if (dock)
{
// if we were interrupted, let the dock know we're not coming
if (status == EXIT_RESET || dock->isDockOpen() == FALSE)
dock->cancelDock( getMachineOwner() );
else
dock->onApproachReached( getMachineOwner() );
}
// this behavior is an extention of basic MoveTo
AIInternalMoveToState::onExit( status );
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/**
* We have approached, now wait at our queue position until the dock says we can enter.
*/
StateReturnType AIDockWaitForClearanceState::onEnter( void )
{
m_enterFrame = TheGameLogic->getFrame();
return STATE_CONTINUE;
}
/**
* We have approached, now wait at our queue position until the dock says we can enter.
* @todo What if we are pushed off of our queue spot? We need to move back on... (MSB)
*/
StateReturnType AIDockWaitForClearanceState::update( void )
{
Object *goalObject = getMachineGoalObject();
if( goalObject == NULL )
return STATE_FAILURE;
DockUpdateInterface *dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// fail if the dock is closed
if( dock->isDockOpen() == FALSE )
{
dock->cancelDock( getMachineOwner() );
return STATE_FAILURE;
}
// if the dock says we can enter, our wait is over
if (dock->isClearToEnter( getMachineOwner() ))
return STATE_SUCCESS;
if (m_enterFrame + 30*LOGICFRAMES_PER_SECOND < TheGameLogic->getFrame()) {
return STATE_FAILURE;
}
// continue to wait
return STATE_CONTINUE;
}
//----------------------------------------------------------------------------------------------
void AIDockWaitForClearanceState::onExit( StateExitType status )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// if we were interrupted, let the dock know we're not coming
if (dock && (dock->isDockOpen() == FALSE || status == EXIT_RESET))
dock->cancelDock( getMachineOwner() );
}
//----------------------------------------------------------------------------------------------
void AIDockWaitForClearanceState::xfer(Xfer *xfer )
{
XferVersion cv = 2;
XferVersion v = cv;
xfer->xferVersion( &v, cv );
if (v >= 2) {
xfer->xferUnsignedInt(&m_enterFrame);
} else {
m_enterFrame = TheGameLogic->getFrame();
}
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/**
* Advance to our next waiting spot next to the dock.
*/
StateReturnType AIDockAdvancePositionState::onEnter( void )
{
Object *goalObject = getMachineGoalObject();
// sanity
if( goalObject == NULL )
return STATE_FAILURE;
// get dock update interface
DockUpdateInterface *dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// fail if the dock is closed
if( dock->isDockOpen() == FALSE )
{
dock->cancelDock( getMachineOwner() );
return STATE_FAILURE;
}
// get a good place to wait from the dock
Bool reserved = dock->advanceApproachPosition( getMachineOwner(), &m_goalPosition, &(( (AIDockMachine*)getMachine() )->m_approachPosition) );
if( reserved == FALSE )
{
// dock is full
return STATE_FAILURE;
}
AIUpdateInterface *ai = getMachineOwner()->getAIUpdateInterface();
if (ai) {
ai->ignoreObstacle( NULL );
}
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::onEnter();
}
//----------------------------------------------------------------------------------------------
StateReturnType AIDockAdvancePositionState::update( void )
{
Object *goalObject = getMachineGoalObject();
// if we have nothing to dock with, fail
if (goalObject == NULL)
return STATE_FAILURE;
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::update();
}
//----------------------------------------------------------------------------------------------
void AIDockAdvancePositionState::onExit( StateExitType status )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// tell the dock we have approached
if (dock)
{
// if we were interrupted, let the dock know we're not coming
if (status == EXIT_RESET || dock->isDockOpen() == FALSE)
dock->cancelDock( getMachineOwner() );
else
dock->onApproachReached( getMachineOwner() );
}
// this behavior is an extention of basic MoveTo
AIInternalMoveToState::onExit( status );
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/**
* Move to the dock's entry position.
*/
StateReturnType AIDockMoveToEntryState::onEnter( void )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// fail if the dock is closed
if( dock->isDockOpen() == FALSE )
{
dock->cancelDock( getMachineOwner() );
return STATE_FAILURE;
}
AIUpdateInterface *ai = getMachineOwner()->getAIUpdateInterface();
if( ai && dock->isAllowPassthroughType() )
{
ai->ignoreObstacle( getMachineGoalObject() );
}
// get the enter position and set as our goal position
dock->getEnterPosition( getMachineOwner(), &m_goalPosition );
( (AIDockMachine*)getMachine() )->m_approachPosition = -1;
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::onEnter();
}
//----------------------------------------------------------------------------------------------
StateReturnType AIDockMoveToEntryState::update( void )
{
// if we have nothing to dock with, fail
if (getMachineGoalObject() == NULL)
return STATE_FAILURE;
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::update();
}
//----------------------------------------------------------------------------------------------
void AIDockMoveToEntryState::onExit( StateExitType status )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
if (dock)
{
if (dock->isDockOpen() == FALSE || status == EXIT_RESET)
{
// if we were interrupted, let the dock know we're not coming
dock->cancelDock( getMachineOwner() );
}
else
{
// tell the dock we are at the entrance
dock->onEnterReached( getMachineOwner() );
}
}
// this behavior is an extention of basic MoveTo
AIInternalMoveToState::onExit( status );
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/**
* Move to the dock's docking position.
*/
StateReturnType AIDockMoveToDockState::onEnter( void )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// fail if the dock is closed
if( dock->isDockOpen() == FALSE )
{
dock->cancelDock( getMachineOwner() );
return STATE_FAILURE;
}
// get the docking position
dock->getDockPosition( getMachineOwner(), &m_goalPosition );
AIUpdateInterface *ai = getMachineOwner()->getAIUpdateInterface();
if( ai && dock->isAllowPassthroughType() )
{
ai->ignoreObstacle( getMachineGoalObject() );
setAdjustsDestination(false);
}
// since we are moving inside the dock, disallow interruptions
getMachine()->lock("AIDockMoveToDockState::onEnter");
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::onEnter();
}
//----------------------------------------------------------------------------------------------
StateReturnType AIDockMoveToDockState::update( void )
{
Object *goalObject = getMachineGoalObject();
// if we have nothing to dock with, fail
if (goalObject == NULL)
return STATE_FAILURE;
DockUpdateInterface *dock = goalObject->getDockUpdateInterface();
if( dock->isDockOpen() == FALSE )
return STATE_FAILURE;
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::update();
}
//----------------------------------------------------------------------------------------------
void AIDockMoveToDockState::onExit( StateExitType status )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// tell the dock we are at the docking point
if (dock)
{
// if we were interrupted, let the dock know we're not coming
if (status == EXIT_RESET || dock->isDockOpen() == FALSE )
dock->cancelDock( getMachineOwner() );
else
dock->onDockReached( getMachineOwner() );
}
// unlock the machine
getMachine()->unlock();
// this behavior is an extention of basic MoveTo
AIInternalMoveToState::onExit( status );
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
AIDockProcessDockState::AIDockProcessDockState( StateMachine *machine ) : State( machine, "AIDockProcessDockState" )
{
m_nextDockActionFrame = 0;
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
void AIDockProcessDockState::setNextDockActionFrame()
{
// If we have a SupplyTruck Interface, then we will ask for our specific delay time
SupplyTruckAIInterface *supplyTruck = getMachineOwner()->getAI()->getSupplyTruckAIInterface();
if( supplyTruck )
{
m_nextDockActionFrame = TheGameLogic->getFrame() + supplyTruck->getActionDelayForDock( getMachineGoalObject() );
return;
}
// The default is that it is simply okay to Action right away
m_nextDockActionFrame = TheGameLogic->getFrame();
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
StateReturnType AIDockProcessDockState::onEnter( void )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
setNextDockActionFrame();
return STATE_CONTINUE;
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/**
* We are now docked. Invoke the dock's action() method until it returns false.
*/
StateReturnType AIDockProcessDockState::update( void )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// Some dockers can have a delay built in
if( TheGameLogic->getFrame() < m_nextDockActionFrame )
return STATE_CONTINUE;
setNextDockActionFrame();
Object *drone = findMyDrone();
// invoke the dock's action until it tells us it is done or the dock becomes closed
if( dock->isDockOpen() == false || dock->action( getMachineOwner(), drone ) == false )
return STATE_SUCCESS;
return STATE_CONTINUE;
}
//----------------------------------------------------------------------------------------------
struct DroneInfo
{
Object *owner;
Object *drone;
Bool found;
};
void findDrone( Object *obj, void *droneInfo )
{
DroneInfo *dInfo = (DroneInfo*)droneInfo;
if( !dInfo->found && obj )
{
if( obj->isKindOf( KINDOF_DRONE ) && obj->getProducerID() == dInfo->owner->getID() )
{
dInfo->found = TRUE;
dInfo->drone = obj;
}
}
}
//----------------------------------------------------------------------------------------------
Object* AIDockProcessDockState::findMyDrone()
{
//First do the fast cached check.
Object *drone = TheGameLogic->findObjectByID( m_droneID );
if( drone )
{
return drone;
}
//Nope... look for a drone (perhaps we just finished building one after docking?)
Object *self = getMachineOwner();
Player *player = self->getControllingPlayer();
DroneInfo dInfo;
dInfo.found = FALSE;
dInfo.drone = NULL;
dInfo.owner = self;
//Iterate the objects in search for a drone with a producer ID of me.
if( player )
{
player->iterateObjects( findDrone, &dInfo );
}
//If we found a drone, store it's ID as cached.
if( dInfo.drone )
{
m_droneID = dInfo.drone->getID();
}
return dInfo.drone;
}
//----------------------------------------------------------------------------------------------
void AIDockProcessDockState::onExit( StateExitType status )
{
// unlock the machine
getMachine()->unlock();
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/**
* Move to the dock's exit position.
*/
StateReturnType AIDockMoveToExitState::onEnter( void )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// get the exit position
dock->getExitPosition( getMachineOwner(), &m_goalPosition );
AIUpdateInterface *ai = getMachineOwner()->getAIUpdateInterface();
if( ai && dock->isAllowPassthroughType() )
{
ai->ignoreObstacle( getMachineGoalObject() );
setAdjustsDestination(false);
}
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::onEnter();
}
//----------------------------------------------------------------------------------------------
StateReturnType AIDockMoveToExitState::update( void )
{
// if we have nothing to dock with, fail
if (getMachineGoalObject() == NULL)
return STATE_FAILURE;
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::update();
}
//----------------------------------------------------------------------------------------------
void AIDockMoveToExitState::onExit( StateExitType status )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// tell the dock we have exited
if (dock)
dock->onExitReached( getMachineOwner() );
// unlock the machine
getMachine()->unlock();
// this behavior is an extention of basic MoveTo
AIInternalMoveToState::onExit( status );
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/**
* Move to the dock's rally position, if he wants me to.
*/
StateReturnType AIDockMoveToRallyState::onEnter( void )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// if they don't have anywhere to send us, then we are good
if( ! dock->isRallyPointAfterDockType() //Chooses not to
|| goalObject->getObjectExitInterface() == NULL //or can't
|| goalObject->getObjectExitInterface()->getRallyPoint() == NULL //or can't right now.
)
{
return STATE_SUCCESS; // Success in an Enter is like success in an update. We're all fine here
}
// get the rally point and set as our goal position
m_goalPosition = *goalObject->getObjectExitInterface()->getRallyPoint();
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::onEnter();
}
//----------------------------------------------------------------------------------------------
StateReturnType AIDockMoveToRallyState::update( void )
{
// This state is fine with the loss of the goal object after the move starts
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::update();
}
//----------------------------------------------------------------------------------------------
void AIDockMoveToRallyState::onExit( StateExitType status )
{
// This state is fine with the loss of the goal object after the move starts
// this behavior is an extention of basic MoveTo
AIInternalMoveToState::onExit( status );
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,833 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: AIGuard.cpp
/*---------------------------------------------------------------------------*/
/* EA Pacific */
/* Confidential Information */
/* Copyright (C) 2001 - All Rights Reserved */
/* DO NOT DISTRIBUTE */
/*---------------------------------------------------------------------------*/
/* Project: RTS3 */
/* File name: AIGuard.cpp */
/* Created: John K. McDonald, Jr., 3/29/2002 */
/* Desc: // Set up guard states for AI */
/* Revision History: */
/* 3/29/2002 : Initial creation */
/*---------------------------------------------------------------------------*/
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/PerfTimer.h"
#include "Common/Team.h"
#include "Common/Xfer.h"
#include "GameLogic/AI.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/AIGuard.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/CollideModule.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/PolygonTrigger.h"
const Real CLOSE_ENOUGH = (25.0f);
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
static Bool hasAttackedMeAndICanReturnFire( State *thisState, void* /*userData*/ )
{
Object *obj = thisState->getMachineOwner();
BodyModuleInterface *bmi = obj ? obj->getBodyModule() : NULL;
if (!(obj && bmi)) {
return FALSE;
}
if (bmi->getClearableLastAttacker() == INVALID_ID) {
return FALSE;
}
// K. It appears we have a valid aggressor. Find it, and determine if we can attack it, etc.
Object *target = TheGameLogic->findObjectByID(bmi->getClearableLastAttacker());
bmi->clearLastAttacker();
// We use the clearable last attacker because we should continue attacking the guy. But if he
// stops attacking us, then we want our timer to kick us off of him and make us go attack
// other units instead.
if (!target) {
return FALSE;
}
if (obj->getRelationship(target) != ENEMIES) {
return FALSE;
}
// This is a quick test on the target. It will be duplicated in getAbleToAttackSpecificObject,
// but the payoff is worth the duplication.
if (target->isEffectivelyDead()) {
return FALSE;
}
//@todo: Get this out of here. Move it into the declaration of calling this function, or figure
// out some way to call it less often.
if (!obj->isAbleToAttack()) {
return FALSE;
}
CanAttackResult result = obj->getAbleToAttackSpecificObject(ATTACK_NEW_TARGET, target, CMD_FROM_AI);
if( result == ATTACKRESULT_POSSIBLE || result == ATTACKRESULT_POSSIBLE_AFTER_MOVING )
{
return TRUE;
}
return FALSE;
}
//-- ExitConditions -------------------------------------------------------------------------------
/**
* This returns true if the conditions specified have been met, false otherwise.
*/
Bool ExitConditions::shouldExit(const StateMachine* machine) const
{
if (!machine->getGoalObject())
{
if (m_conditionsToConsider & ATTACK_ExitIfNoUnitFound)
{
return true;
}
else
{
return false;
}
}
if (m_conditionsToConsider & ATTACK_ExitIfExpiredDuration)
{
if (TheGameLogic->getFrame() >= m_attackGiveUpFrame)
{
return true;
}
}
if (m_conditionsToConsider & ATTACK_ExitIfOutsideRadius)
{
Coord3D deltaAggressor;
Coord3D objPos = *machine->getGoalObject()->getPosition();
deltaAggressor.x = objPos.x - m_center.x;
deltaAggressor.y = objPos.y - m_center.y;
// deltaAggressor.z = objPos.z - m_center.z;
deltaAggressor.z = 0; // BGC - when we search for a target we don't account for Z, so why should we here?
// changing this fixed a crash where a GLARebelInfantry would be in GuardReturnState, find
// a target that is within range, then not be able to attack because its actually out of range.
// then it would look for a new target, get the same one, and proceed in an infinite recursive
// loop that eventually blew the stack.
if (deltaAggressor.lengthSqr() > m_radiusSqr)
{
return true;
}
}
return false;
}
//-- AIGuardMachine -------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
AIGuardMachine::AIGuardMachine( Object *owner ) :
StateMachine(owner, "AIGuardMachine"),
m_targetToGuard(INVALID_ID),
m_areaToGuard(NULL),
m_nemesisToAttack(INVALID_ID),
m_guardMode(GUARDMODE_NORMAL)
{
m_positionToGuard.zero();
static const StateConditionInfo attackAggressors[] =
{
StateConditionInfo(hasAttackedMeAndICanReturnFire, AI_GUARD_ATTACK_AGGRESSOR, NULL),
StateConditionInfo(NULL, NULL, NULL) // keep last
};
// order matters: first state is the default state.
// srj sez: I made "return" the start state, so that if ordered to guard a position
// that isn't the unit's current position, it moves to that position first.
defineState( AI_GUARD_RETURN, newInstance(AIGuardReturnState)( this ), AI_GUARD_IDLE, AI_GUARD_INNER, attackAggressors );
defineState( AI_GUARD_IDLE, newInstance(AIGuardIdleState)( this ), AI_GUARD_INNER, AI_GUARD_RETURN, attackAggressors );
defineState( AI_GUARD_INNER, newInstance(AIGuardInnerState)( this ), AI_GUARD_OUTER, AI_GUARD_OUTER );
defineState( AI_GUARD_OUTER, newInstance(AIGuardOuterState)( this ), AI_GUARD_GET_CRATE, AI_GUARD_GET_CRATE );
defineState( AI_GUARD_GET_CRATE, newInstance(AIGuardPickUpCrateState)( this ), AI_GUARD_RETURN, AI_GUARD_RETURN );
defineState( AI_GUARD_ATTACK_AGGRESSOR, newInstance(AIGuardAttackAggressorState)( this ), AI_GUARD_RETURN, AI_GUARD_RETURN );
}
//--------------------------------------------------------------------------------------
AIGuardMachine::~AIGuardMachine()
{
}
//--------------------------------------------------------------------------------------
/*static*/ Real AIGuardMachine::getStdGuardRange(const Object* obj)
{
Real visionRange = TheAI->getAdjustedVisionRangeForObject(obj,
AI_VISIONFACTOR_OWNERTYPE | AI_VISIONFACTOR_MOOD | AI_VISIONFACTOR_GUARDINNER);
return visionRange;
}
//--------------------------------------------------------------------------------------
Bool AIGuardMachine::lookForInnerTarget(void)
{
Object* owner = getOwner();
if (!owner->isAbleToAttack())
{
return false; // my, that was easy
}
// Check if team auto targets same victim.
Object *teamVictim = NULL;
if (owner->getTeam()->getPrototype()->getTemplateInfo()->m_attackCommonTarget)
{
teamVictim = owner->getTeam()->getTeamTargetObject();
if (teamVictim)
{
setNemesisID(teamVictim->getID());
return true; // Transitions to AIGuardInnerState.
}
}
Object* targetToGuard = findTargetToGuardByID();
Coord3D pos = targetToGuard ? *targetToGuard->getPosition() : *getPositionToGuard();
const PolygonTrigger* area = getAreaToGuard();
PartitionFilterRelationship f1(owner, PartitionFilterRelationship::ALLOW_ENEMIES);
PartitionFilterPossibleToAttack f2(ATTACK_NEW_TARGET, owner, CMD_FROM_AI);
PartitionFilterSameMapStatus filterMapStatus(owner);
PartitionFilterPolygonTrigger f3(area);
PartitionFilterIsFlying f4;
PartitionFilter *filters[16];
Int count = 0;
filters[count++] = &f1;
filters[count++] = &f2;
filters[count++] = &filterMapStatus;
Real visionRange = AIGuardMachine::getStdGuardRange(owner);
if (area)
{
UnsignedInt checkFrame = TheGameLogic->getFrameObjectsChangedTriggerAreas()+TheAI->getAiData()->m_guardEnemyScanRate;
if (TheGameLogic->getFrame()>checkFrame) {
return false;
}
filters[count++] = &f3;
visionRange = area->getRadius();
area->getCenterPoint(&pos);
}
if (getGuardMode() == GUARDMODE_GUARD_FLYING_UNITS_ONLY)
{
// only consider flying targets
filters[count++] = &f4;
}
filters[count++] = NULL;
// SimpleObjectIterator* iter = ThePartitionManager->iterateObjectsInRange(
// &pos, visionRange, FROM_CENTER_2D, filters, ITER_SORTED_NEAR_TO_FAR);
// MemoryPoolObjectHolder hold(iter);
// Object* target = iter->first();
//
// srj sez: the above code is stupid and slow. since we only want the closest object,
// just ask for that; the above has to find ALL objects in range, but we ignore all
// but the first (closest).
//
Object* target = ThePartitionManager->getClosestObject(&pos, visionRange, FROM_CENTER_2D, filters);
if (target)
{
setNemesisID(target->getID());
return true; // Transitions to AIGuardInnerState.
}
else
{
return false;
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardMachine::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardMachine::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
if (version>=2) {
StateMachine::xfer(xfer); // Forgot this in initial implementation. jba.
}
xfer->xferObjectID(&m_targetToGuard);
xfer->xferObjectID(&m_nemesisToAttack);
xfer->xferCoord3D(&m_positionToGuard);
AsciiString triggerName;
if (m_areaToGuard) triggerName = m_areaToGuard->getTriggerName();
xfer->xferAsciiString(&triggerName);
if (xfer->getXferMode() == XFER_LOAD)
{
if (triggerName.isNotEmpty()) {
m_areaToGuard = TheTerrainLogic->getTriggerAreaByName(triggerName);
}
}
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardMachine::loadPostProcess( void )
{
} // end loadPostProcess
//-- AIGuardInnerState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardInnerState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardInnerState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardInnerState::loadPostProcess( void )
{
onEnter();
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AIGuardInnerState::onEnter( void )
{
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
Coord3D pos = targetToGuard ? *targetToGuard->getPosition() : *getGuardMachine()->getPositionToGuard();
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AIGuardInnerState.\n"));
return STATE_SUCCESS;
}
m_exitConditions.m_center = pos;
m_exitConditions.m_radiusSqr = sqr(AIGuardMachine::getStdGuardRange(getMachineOwner()));
m_exitConditions.m_conditionsToConsider = (ExitConditions::ATTACK_ExitIfOutsideRadius |
ExitConditions::ATTACK_ExitIfNoUnitFound);
m_attackState = newInstance(AIAttackState)(getMachine(), false, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardInnerState::update( void )
{
if (m_attackState==NULL) return STATE_SUCCESS;
// if the position has moved (IE we're guarding an object), move with it.
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
if (targetToGuard)
{
m_exitConditions.m_center = *targetToGuard->getPosition();
}
return m_attackState->update();
}
//--------------------------------------------------------------------------------------
void AIGuardInnerState::onExit( StateExitType status )
{
Object *obj = getMachineOwner();
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
if (obj->getTeam())
{
obj->getTeam()->setTeamTargetObject(NULL); // clear the target.
}
}
//-- AIGuardOuterState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardOuterState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardOuterState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardOuterState::loadPostProcess( void )
{ AIGuardOuterState
onEnter();
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AIGuardOuterState::onEnter( void )
{
if (getGuardMachine()->getGuardMode() == GUARDMODE_GUARD_WITHOUT_PURSUIT)
{
// "patrol" mode does not follow targets outside the guard area.
return STATE_SUCCESS;
}
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
Coord3D pos = targetToGuard ? *targetToGuard->getPosition() : *getGuardMachine()->getPositionToGuard();
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AIGuardInnerState.\n"));
return STATE_SUCCESS;
}
Object *obj = getMachineOwner();
Real range = TheAI->getAdjustedVisionRangeForObject(obj, AI_VISIONFACTOR_OWNERTYPE | AI_VISIONFACTOR_MOOD);
const PolygonTrigger *area = getGuardMachine()->getAreaToGuard();
if (area)
{
if (range < area->getRadius())
range = area->getRadius();
area->getCenterPoint(&pos);
}
m_exitConditions.m_center = pos;
m_exitConditions.m_radiusSqr = sqr(range);
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
m_exitConditions.m_conditionsToConsider = (ExitConditions::ATTACK_ExitIfExpiredDuration |
ExitConditions::ATTACK_ExitIfOutsideRadius |
ExitConditions::ATTACK_ExitIfNoUnitFound);
m_attackState = newInstance(AIAttackState)(getMachine(), false, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardOuterState::update( void )
{
if (m_attackState==NULL) return STATE_SUCCESS;
// if the position has moved (IE we're guarding an object), move with it.
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
if (targetToGuard)
{
m_exitConditions.m_center = *targetToGuard->getPosition();
}
Object* goalObj = m_attackState->getMachineGoalObject();
if (goalObj)
{
Coord3D deltaAggr;
deltaAggr.x = m_exitConditions.m_center.x - goalObj->getPosition()->x;
deltaAggr.y = m_exitConditions.m_center.y - goalObj->getPosition()->y;
deltaAggr.z = m_exitConditions.m_center.z - goalObj->getPosition()->z;
Real visionSqr = sqr(AIGuardMachine::getStdGuardRange(getMachineOwner()));
if (deltaAggr.lengthSqr() <= visionSqr)
{
// reset the counter
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
}
}
return m_attackState->update();
}
//--------------------------------------------------------------------------------------
void AIGuardOuterState::onExit( StateExitType status )
{
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
}
//-- AIGuardReturnState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardReturnState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardReturnState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
xfer->xferUnsignedInt(&m_nextReturnScanTime);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardReturnState::loadPostProcess( void )
{
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AIGuardReturnState::onEnter( void )
{
UnsignedInt now = TheGameLogic->getFrame();
m_nextReturnScanTime = now + GameLogicRandomValue(0, TheAI->getAiData()->m_guardEnemyReturnScanRate);
// no, no, no, don't do this in onEnter, unless you like really slow maps. (srj)
// if (getGuardMachine()->lookForInnerTarget())
// return STATE_FAILURE; // early termination because we found a target.
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
m_goalPosition = targetToGuard ? *targetToGuard->getPosition() : *getGuardMachine()->getPositionToGuard();
const PolygonTrigger *area = getGuardMachine()->getAreaToGuard();
if (area)
{
area->getCenterPoint(&m_goalPosition);
}
AIUpdateInterface *ai = getMachineOwner()->getAIUpdateInterface();
if (ai && ai->isDoingGroundMovement())
{
TheAI->pathfinder()->adjustDestination(getMachineOwner(), ai->getLocomotorSet(), &m_goalPosition);
}
setAdjustsDestination(true);
return AIInternalMoveToState::onEnter();
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardReturnState::update( void )
{
UnsignedInt now = TheGameLogic->getFrame();
if (now >= m_nextReturnScanTime)
{
m_nextReturnScanTime = now + TheAI->getAiData()->m_guardEnemyReturnScanRate;
if (getGuardMachine()->lookForInnerTarget())
return STATE_FAILURE; // early termination because we found a target.
}
// Just let the return movement finish.
return AIInternalMoveToState::update();
}
//--------------------------------------------------------------------------------------
void AIGuardReturnState::onExit( StateExitType status )
{
AIInternalMoveToState::onExit( status );
}
//-- AIGuardIdleState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardIdleState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardIdleState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
xfer->xferUnsignedInt(&m_nextEnemyScanTime);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardIdleState::loadPostProcess( void )
{
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AIGuardIdleState::onEnter( void )
{
// first time thru, use a random amount so that everyone doesn't scan on the same frame,
// to avoid "spikes".
UnsignedInt now = TheGameLogic->getFrame();
m_nextEnemyScanTime = now + GameLogicRandomValue(0, TheAI->getAiData()->m_guardEnemyScanRate);
return STATE_CONTINUE;
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardIdleState::update( void )
{
//DEBUG_LOG(("AIGuardIdleState frame %d: %08lx\n",TheGameLogic->getFrame(),getMachineOwner()));
UnsignedInt now = TheGameLogic->getFrame();
if (now < m_nextEnemyScanTime)
return STATE_SLEEP(m_nextEnemyScanTime - now);
m_nextEnemyScanTime = now + TheAI->getAiData()->m_guardEnemyScanRate;
#ifdef STATE_MACHINE_DEBUG
//getMachine()->setDebugOutput(true);
#endif
Object *owner = getMachineOwner();
AIUpdateInterface *ai = owner->getAIUpdateInterface();
// Check to see if we have created a crate we need to pick up.
if (ai->getCrateID() != INVALID_ID)
{
getMachine()->setState(AI_GUARD_GET_CRATE);
return STATE_SLEEP(m_nextEnemyScanTime - now);
}
// if anyone is in the inner area, return success.
if (getGuardMachine()->lookForInnerTarget())
{
return STATE_SUCCESS; // Transitions to AIGuardInnerState.
}
// See if the object we are guarding moved.
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
if (targetToGuard)
{
Coord3D pos = *targetToGuard->getPosition();
Real delta = m_guardeePos.x-pos.x;
if (delta*delta > 4*PATHFIND_CELL_SIZE_F*PATHFIND_CELL_SIZE_F) {
m_guardeePos = pos;
return STATE_FAILURE; // goes to AIGuardReturnState.
}
delta = m_guardeePos.y-pos.y;
if (delta*delta > 4*PATHFIND_CELL_SIZE_F*PATHFIND_CELL_SIZE_F) {
m_guardeePos = pos;
return STATE_FAILURE; // goes to AIGuardReturnState.
}
}
return STATE_SLEEP(m_nextEnemyScanTime - now);
}
//--------------------------------------------------------------------------------------
void AIGuardIdleState::onExit( StateExitType status )
{
}
//-- AIGuardPickUpCrateState ----------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AIGuardPickUpCrateState::AIGuardPickUpCrateState( StateMachine *machine ) : AIPickUpCrateState(machine)
{
#ifdef STATE_MACHINE_DEBUG
setName("AIGuardPickUpCrateState");
#endif
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardPickUpCrateState::onEnter( void )
{
Object *owner = getMachineOwner();
AIUpdateInterface *ai = owner->getAIUpdateInterface();
// Check to see if we have created a crate we need to pick up.
Object* crate = ai->checkForCrateToPickup();
if (crate)
{
getMachine()->setGoalObject(crate);
return AIPickUpCrateState::onEnter();
}
return STATE_SUCCESS; // no crate, so we're done.
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardPickUpCrateState::update( void )
{
return AIPickUpCrateState::update();
}
//--------------------------------------------------------------------------------------
void AIGuardPickUpCrateState::onExit( StateExitType status )
{
}
//-- AIGuardAttackAggressorState ------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AIGuardAttackAggressorState::AIGuardAttackAggressorState( StateMachine *machine ) :
State( machine, "AIGuardAttackAggressorState" )
{
m_attackState = NULL;
}
//-------------------------------------------------------------------------------------------------
StateReturnType AIGuardAttackAggressorState::onEnter( void )
{
Object *obj = getMachineOwner();
ObjectID nemID = INVALID_ID;
if (obj->getBodyModule() && obj->getBodyModule()->getLastDamageInfo()->in.m_sourceID) {
nemID = obj->getBodyModule()->getLastDamageInfo()->in.m_sourceID;
getGuardMachine()->setNemesisID(nemID);
}
Object *nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID());
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AIGuardAttackAggressorState.\n"));
return STATE_SUCCESS;
}
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
m_exitConditions.m_conditionsToConsider = (ExitConditions::ATTACK_ExitIfExpiredDuration |
ExitConditions::ATTACK_ExitIfNoUnitFound);
m_attackState = newInstance(AIAttackState)(getMachine(), true, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
//-------------------------------------------------------------------------------------------------
StateReturnType AIGuardAttackAggressorState::update( void )
{
if (m_attackState==NULL) return STATE_SUCCESS;
// if the position has moved (IE we're guarding an object), move with it.
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
if (targetToGuard)
{
m_exitConditions.m_center = *targetToGuard->getPosition();
}
return m_attackState->update();
}
//-------------------------------------------------------------------------------------------------
void AIGuardAttackAggressorState::onExit( StateExitType status )
{
Object *obj = getMachineOwner();
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
if (obj->getTeam())
{
obj->getTeam()->setTeamTargetObject(NULL); // clear the target.
}
}
//-------------------------------------------------------------------------------------------------
void AIGuardAttackAggressorState::crc( Xfer *xfer )
{
}
//-------------------------------------------------------------------------------------------------
void AIGuardAttackAggressorState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
}
//-------------------------------------------------------------------------------------------------
void AIGuardAttackAggressorState::loadPostProcess()
{
onEnter();
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,866 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: AITNGuard.cpp
/*---------------------------------------------------------------------------*/
/* EA Pacific */
/* Confidential Information */
/* Copyright (C) 2001 - All Rights Reserved */
/* DO NOT DISTRIBUTE */
/*---------------------------------------------------------------------------*/
/* Project: RTS3 */
/* File name: AITNGuard.cpp */
/* Created: John Ahlquist., 12/20/2002 */
/* Desc: // Set up guard tunnel network states for AI */
/* Revision History: */
/* 12/20/2002 : Initial creation - modified from AIGuard.cpp */
/*---------------------------------------------------------------------------*/
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/PerfTimer.h"
#include "Common/Team.h"
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/AI.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/AITNGuard.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/CollideModule.h"
#include "Common/TunnelTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/PolygonTrigger.h"
const Real CLOSE_ENOUGH = (25.0f);
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
static Bool hasAttackedMeAndICanReturnFire( State *thisState, void* /*userData*/ )
{
Object *obj = thisState->getMachineOwner();
BodyModuleInterface *bmi = obj ? obj->getBodyModule() : NULL;
if (!(obj && bmi)) {
return FALSE;
}
if (bmi->getClearableLastAttacker() == INVALID_ID) {
return FALSE;
}
// K. It appears we have a valid aggressor. Find it, and determine if we can attack it, etc.
Object *target = TheGameLogic->findObjectByID(bmi->getClearableLastAttacker());
bmi->clearLastAttacker();
// We use the clearable last attacker because we should continue attacking the guy. But if he
// stops attacking us, then we want our timer to kick us off of him and make us go attack
// other units instead.
if (!target) {
return FALSE;
}
if (obj->getRelationship(target) != ENEMIES) {
return FALSE;
}
// This is a quick test on the target. It will be duplicated in getAbleToAttackSpecificObject,
// but the payoff is worth the duplication.
if (target->isEffectivelyDead()) {
return FALSE;
}
CanAttackResult result = obj->getAbleToAttackSpecificObject(ATTACK_NEW_TARGET, target, CMD_FROM_AI);
if( result == ATTACKRESULT_POSSIBLE || result == ATTACKRESULT_POSSIBLE_AFTER_MOVING )
{
return TRUE;
}
return FALSE;
}
static Object *findBestTunnel(Player *ownerPlayer, const Coord3D *pos)
{
if (!ownerPlayer) return NULL; // should never happen, but hey. jba.
TunnelTracker *tunnels = ownerPlayer->getTunnelSystem();
Object *bestTunnel = NULL;
Real bestDistSqr = 0;
const std::list<ObjectID> *allTunnels = tunnels->getContainerList();
for( std::list<ObjectID>::const_iterator iter = allTunnels->begin(); iter != allTunnels->end(); iter++ ) {
// For each ID, look it up and change its team. We all get captured together.
Object *currentTunnel = TheGameLogic->findObjectByID( *iter );
if( currentTunnel ) {
Real dx = currentTunnel->getPosition()->x-pos->x;
Real dy = currentTunnel->getPosition()->y-pos->y;
Real distSqr = dx*dx+dy*dy;
if (bestTunnel==NULL || distSqr<bestDistSqr) {
bestDistSqr = distSqr;
bestTunnel = currentTunnel;
}
}
}
return bestTunnel;
}
//-- ExitConditions -------------------------------------------------------------------------------
/**
* This returns true if the conditions specified have been met, false otherwise.
*/
Bool TunnelNetworkExitConditions::shouldExit(const StateMachine* machine) const
{
if (TheGameLogic->getFrame() >= m_attackGiveUpFrame)
{
return true;
}
return false;
}
//-- AITNGuardMachine -------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
AITNGuardMachine::AITNGuardMachine( Object *owner ) :
StateMachine(owner, "AITNGuardMachine"),
m_nemesisToAttack(INVALID_ID),
m_guardMode(GUARDMODE_NORMAL)
{
m_positionToGuard.zero();
static const StateConditionInfo attackAggressors[] =
{
StateConditionInfo(hasAttackedMeAndICanReturnFire, AI_TN_GUARD_ATTACK_AGGRESSOR, NULL),
StateConditionInfo(NULL, NULL, NULL) // keep last
};
// order matters: first state is the default state.
// srj sez: I made "return" the start state, so that if ordered to guard a position
// that isn't the unit's current position, it moves to that position first.
defineState( AI_TN_GUARD_RETURN, newInstance(AITNGuardReturnState)( this ), AI_TN_GUARD_IDLE, AI_TN_GUARD_INNER, attackAggressors );
defineState( AI_TN_GUARD_IDLE, newInstance(AITNGuardIdleState)( this ), AI_TN_GUARD_INNER, AI_TN_GUARD_RETURN );
defineState( AI_TN_GUARD_INNER, newInstance(AITNGuardInnerState)( this ), AI_TN_GUARD_OUTER, AI_TN_GUARD_OUTER , attackAggressors);
defineState( AI_TN_GUARD_OUTER, newInstance(AITNGuardOuterState)( this ), AI_TN_GUARD_GET_CRATE, AI_TN_GUARD_GET_CRATE );
defineState( AI_TN_GUARD_GET_CRATE, newInstance(AITNGuardPickUpCrateState)( this ), AI_TN_GUARD_RETURN, AI_TN_GUARD_RETURN );
defineState( AI_TN_GUARD_ATTACK_AGGRESSOR, newInstance(AITNGuardAttackAggressorState)( this ), AI_TN_GUARD_RETURN, AI_TN_GUARD_RETURN );
#ifdef STATE_MACHINE_DEBUG
setDebugOutput(true);
#endif
}
//--------------------------------------------------------------------------------------
AITNGuardMachine::~AITNGuardMachine()
{
}
//--------------------------------------------------------------------------------------
/*static*/ Real AITNGuardMachine::getStdGuardRange(const Object* obj)
{
Real visionRange = TheAI->getAdjustedVisionRangeForObject(obj,
AI_VISIONFACTOR_OWNERTYPE | AI_VISIONFACTOR_MOOD | AI_VISIONFACTOR_GUARDINNER);
return visionRange;
}
//--------------------------------------------------------------------------------------
Bool AITNGuardMachine::lookForInnerTarget(void)
{
Object* owner = getOwner();
// Check if team auto targets same victim.
Object *teamVictim = NULL;
if (owner->getTeam()->getPrototype()->getTemplateInfo()->m_attackCommonTarget)
{
teamVictim = owner->getTeam()->getTeamTargetObject();
if (teamVictim)
{
setNemesisID(teamVictim->getID());
return true; // Transitions to AITNGuardInnerState.
}
}
// Find tunnel network to defend.
// Scan my tunnels.
Player *ownerPlayer = getOwner()->getControllingPlayer();
if (!ownerPlayer) return false; // should never happen, but hey. jba.
TunnelTracker *tunnels = ownerPlayer->getTunnelSystem();
if (tunnels==NULL) return false;
if (tunnels->getCurNemesis()) {
setNemesisID(tunnels->getCurNemesis()->getID());
return true; // Transitions to AITNGuardInnerState.
}
const std::list<ObjectID> *allTunnels = tunnels->getContainerList();
for( std::list<ObjectID>::const_iterator iter = allTunnels->begin(); iter != allTunnels->end(); iter++ ) {
Object *currentTunnel = TheGameLogic->findObjectByID( *iter );
if( currentTunnel ) {
// Check for attacking.
if (currentTunnel->getAI()) {
Object *victim = currentTunnel->getAI()->getGoalObject();
if (owner->getRelationship(victim) == ENEMIES) {
setNemesisID(victim->getID());
return true;
}
}
// check for attacked.
BodyModuleInterface *body = currentTunnel->getBodyModule();
if (body) {
const DamageInfo *info = body->getLastDamageInfo();
if (info) {
if (info->out.m_noEffect) {
continue;
}
if (body->getLastDamageTimestamp() + TheAI->getAiData()->m_guardEnemyScanRate > TheGameLogic->getFrame()) {
// winner.
ObjectID attackerID = info->in.m_sourceID;
Object *attacker = TheGameLogic->findObjectByID(attackerID);
if( attacker )
{
if (owner->getRelationship(attacker) != ENEMIES) {
continue;
}
CanAttackResult result = getOwner()->getAbleToAttackSpecificObject(ATTACK_TUNNEL_NETWORK_GUARD, attacker, CMD_FROM_AI);
if( result == ATTACKRESULT_POSSIBLE || result == ATTACKRESULT_POSSIBLE_AFTER_MOVING )
{
setNemesisID(attackerID);
owner->getTeam()->setTeamTargetObject(attacker);
tunnels->updateNemesis(attacker);
return true; // Transitions to AITNGuardInnerState.
}
}
}
}
}
}
}
return false;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AITNGuardMachine::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AITNGuardMachine::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
if (version>=2) {
StateMachine::xfer(xfer); // Forgot this in initial implementation. jba.
}
xfer->xferObjectID(&m_nemesisToAttack);
xfer->xferCoord3D(&m_positionToGuard);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AITNGuardMachine::loadPostProcess( void )
{
} // end loadPostProcess
//-- AITNGuardInnerState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AITNGuardInnerState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AITNGuardInnerState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AITNGuardInnerState::loadPostProcess( void )
{
onEnter();
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardInnerState::onEnter( void )
{
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AITNGuardInnerState.\n"));
return STATE_SUCCESS;
}
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
m_attackState = newInstance(AIAttackState)(getMachine(), false, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
static Object *TunnelNetworkScan(Object *owner)
{
PartitionFilterRelationship f1(owner, PartitionFilterRelationship::ALLOW_ENEMIES);
PartitionFilterPossibleToAttack f2(ATTACK_NEW_TARGET, owner, CMD_FROM_AI);
PartitionFilterSameMapStatus filterMapStatus(owner);
PartitionFilter *filters[16];
Int count = 0;
filters[count++] = &f1;
filters[count++] = &f2;
filters[count++] = &filterMapStatus;
Real visionRange = AITNGuardMachine::getStdGuardRange(owner);
filters[count++] = NULL;
Object* target = ThePartitionManager->getClosestObject(owner->getPosition(), visionRange, FROM_CENTER_2D, filters);
return target;
}
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardInnerState::update( void )
{
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
Player *ownerPlayer = getMachineOwner()->getControllingPlayer();
TunnelTracker *tunnels = NULL;
if (ownerPlayer) {
tunnels = ownerPlayer->getTunnelSystem();
}
Object* owner = getMachineOwner();
// killed him.
Object *teamVictim = owner->getTeam()->getTeamTargetObject();
if (nemesis == NULL)
{
if (teamVictim)
{
getGuardMachine()->setNemesisID(teamVictim->getID());
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
return STATE_CONTINUE;
}
// Check tunnel.
if (tunnels) {
nemesis = tunnels->getCurNemesis();
if (nemesis) {
getGuardMachine()->setNemesisID(nemesis->getID());
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
return STATE_CONTINUE;
}
}
if (m_scanForEnemy) {
m_scanForEnemy = false; // we just do 1 scan.
nemesis = TunnelNetworkScan(owner);
if (nemesis) {
m_attackState->onExit(EXIT_RESET);
m_attackState->getMachine()->setGoalObject(nemesis);
if (tunnels) {
tunnels->updateNemesis(nemesis);
}
StateReturnType returnVal = m_attackState->onEnter();
return returnVal;
}
}
} else {
if (nemesis != teamVictim && teamVictim != NULL) {
tunnels->updateNemesis(nemesis);
getGuardMachine()->setNemesisID(teamVictim->getID());
}
}
return m_attackState->update();
}
//--------------------------------------------------------------------------------------
void AITNGuardInnerState::onExit( StateExitType status )
{
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
}
//-- AITNGuardOuterState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AITNGuardOuterState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AITNGuardOuterState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AITNGuardOuterState::loadPostProcess( void )
{ AITNGuardOuterState
onEnter();
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardOuterState::onEnter( void )
{
if (getGuardMachine()->getGuardMode() == GUARDMODE_GUARD_WITHOUT_PURSUIT)
{
// "patrol" mode does not follow targets outside the guard area.
return STATE_SUCCESS;
}
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AITNGuardOuterState.\n"));
return STATE_SUCCESS;
}
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
m_attackState = newInstance(AIAttackState)(getMachine(), false, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardOuterState::update( void )
{
Object *owner = getMachineOwner();
Object* goalObj = m_attackState->getMachineGoalObject();
if (goalObj)
{
} else {
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
if (nemesis) {
goalObj = nemesis;
}
// Check if team auto targets same victim.
Object *teamVictim = NULL;
if (goalObj == NULL && owner->getTeam()->getPrototype()->getTemplateInfo()->m_attackCommonTarget)
{
teamVictim = owner->getTeam()->getTeamTargetObject();
if (teamVictim)
{
goalObj = teamVictim;
}
m_attackState->getMachine()->setGoalObject(goalObj);
return m_attackState->onEnter();
}
}
return m_attackState->update();
}
//--------------------------------------------------------------------------------------
void AITNGuardOuterState::onExit( StateExitType status )
{
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
}
//-- AITNGuardReturnState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AITNGuardReturnState::crc( Xfer *xfer )
{
AIEnterState::crc(xfer);
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AITNGuardReturnState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
AIEnterState::xfer(xfer);
xfer->xferUnsignedInt(&m_nextReturnScanTime);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AITNGuardReturnState::loadPostProcess( void )
{
AIEnterState::loadPostProcess();
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardReturnState::onEnter( void )
{
UnsignedInt now = TheGameLogic->getFrame();
m_nextReturnScanTime = now + GameLogicRandomValue(0, TheAI->getAiData()->m_guardEnemyReturnScanRate);
// no, no, no, don't do this in onEnter, unless you like really slow maps. (srj)
// if (getGuardMachine()->lookForInnerTarget())
// return STATE_FAILURE; // early termination because we found a target.
// Find tunnel network to enter.
// Scan my tunnels.
Object *bestTunnel = findBestTunnel(getMachineOwner()->getControllingPlayer(), getMachineOwner()->getPosition());
if (bestTunnel==NULL) return STATE_FAILURE;
getMachine()->setGoalObject(bestTunnel);
getMachineOwner()->getAI()->friend_setGoalObject(bestTunnel);
return AIEnterState::onEnter();
}
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardReturnState::update( void )
{
Player *ownerPlayer = getMachineOwner()->getControllingPlayer();
if (getMachineOwner()->getTeam()) {
Object *teamVictim = getMachineOwner()->getTeam()->getTeamTargetObject();
if (teamVictim) {
getGuardMachine()->setNemesisID(teamVictim->getID());
return STATE_FAILURE; // Fail to return goes to inner attack state.
}
}
// Check tunnel for target.
TunnelTracker *tunnels = NULL;
if (ownerPlayer) {
tunnels = ownerPlayer->getTunnelSystem();
}
if (tunnels) {
Object *nemesis = tunnels->getCurNemesis();
if (nemesis) {
// Check distance.
//Coord3D dist;
//Coord3D curPos;
//dist.set()
getGuardMachine()->setNemesisID(nemesis->getID());
return STATE_FAILURE; // Fail to return goes to inner attack state.
}
}
// Just let the return movement finish.
StateReturnType ret = AIEnterState::update();
if (ret==STATE_CONTINUE) return STATE_CONTINUE;
return STATE_SUCCESS;
}
//--------------------------------------------------------------------------------------
void AITNGuardReturnState::onExit( StateExitType status )
{
AIEnterState::onExit( status );
}
//-- AITNGuardIdleState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AITNGuardIdleState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AITNGuardIdleState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
xfer->xferUnsignedInt(&m_nextEnemyScanTime);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AITNGuardIdleState::loadPostProcess( void )
{
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardIdleState::onEnter( void )
{
// first time thru, use a random amount so that everyone doesn't scan on the same frame,
// to avoid "spikes".
UnsignedInt now = TheGameLogic->getFrame();
m_nextEnemyScanTime = now + GameLogicRandomValue(0, TheAI->getAiData()->m_guardEnemyScanRate);
getMachineOwner()->getAI()->friend_setGoalObject(NULL);
return STATE_CONTINUE;
}
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardIdleState::update( void )
{
//DEBUG_LOG(("AITNGuardIdleState frame %d: %08lx\n",TheGameLogic->getFrame(),getMachineOwner()));
UnsignedInt now = TheGameLogic->getFrame();
if (now < m_nextEnemyScanTime)
return STATE_SLEEP(m_nextEnemyScanTime - now);
m_nextEnemyScanTime = now + TheAI->getAiData()->m_guardEnemyScanRate;
getMachineOwner()->getAI()->friend_setGoalObject(NULL);
#ifdef STATE_MACHINE_DEBUG
//getMachine()->setDebugOutput(true);
#endif
Object *owner = getMachineOwner();
AIUpdateInterface *ai = owner->getAIUpdateInterface();
// Check to see if we have created a crate we need to pick up.
if (ai->getCrateID() != INVALID_ID)
{
getMachine()->setState(AI_TN_GUARD_GET_CRATE);
return STATE_SLEEP(m_nextEnemyScanTime - now);
}
// if anyone is in the inner area, return success.
if (getGuardMachine()->lookForInnerTarget())
{
Object *nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID());
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AITNGuardAttackAggressorState.\n"));
return STATE_SLEEP(0);
}
if (getMachineOwner()->getContainedBy()) {
Object *bestTunnel = findBestTunnel(owner->getControllingPlayer(), nemesis->getPosition());
ExitInterface* goalExitInterface = bestTunnel->getContain() ? bestTunnel->getContain()->getContainExitInterface() : NULL;
if( goalExitInterface == NULL )
return STATE_FAILURE;
if( goalExitInterface->isExitBusy() )
return STATE_SLEEP(0);// Just wait a sec.
goalExitInterface->exitObjectInAHurry(getMachineOwner());
return STATE_SLEEP(0);
}
return STATE_SUCCESS; // Transitions to AITNGuardInnerState.
}
if (!owner->getContainedBy() && findBestTunnel(owner->getControllingPlayer(), owner->getPosition())) {
return STATE_FAILURE; // go to AITNGuardReturnState, & enter a tunnel.
}
return STATE_SLEEP(m_nextEnemyScanTime - now);
}
//--------------------------------------------------------------------------------------
void AITNGuardIdleState::onExit( StateExitType status )
{
}
//-- AITNGuardPickUpCrateState ----------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AITNGuardPickUpCrateState::AITNGuardPickUpCrateState( StateMachine *machine ) : AIPickUpCrateState(machine)
{
#ifdef STATE_MACHINE_DEBUG
setName("AITNGuardPickUpCrateState");
#endif
}
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardPickUpCrateState::onEnter( void )
{
Object *owner = getMachineOwner();
AIUpdateInterface *ai = owner->getAIUpdateInterface();
// Check to see if we have created a crate we need to pick up.
Object* crate = ai->checkForCrateToPickup();
if (crate)
{
getMachine()->setGoalObject(crate);
return AIPickUpCrateState::onEnter();
}
return STATE_SUCCESS; // no crate, so we're done.
}
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardPickUpCrateState::update( void )
{
return AIPickUpCrateState::update();
}
//--------------------------------------------------------------------------------------
void AITNGuardPickUpCrateState::onExit( StateExitType status )
{
}
//-- AITNGuardAttackAggressorState ------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AITNGuardAttackAggressorState::AITNGuardAttackAggressorState( StateMachine *machine ) :
State( machine, "AITNGuardAttackAggressorState" )
{
m_attackState = NULL;
}
//-------------------------------------------------------------------------------------------------
StateReturnType AITNGuardAttackAggressorState::onEnter( void )
{
Object *obj = getMachineOwner();
ObjectID nemID = INVALID_ID;
if (obj->getBodyModule() && obj->getBodyModule()->getLastDamageInfo()->in.m_sourceID) {
nemID = obj->getBodyModule()->getLastDamageInfo()->in.m_sourceID;
getGuardMachine()->setNemesisID(nemID);
}
Object *nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID());
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AITNGuardAttackAggressorState.\n"));
return STATE_SUCCESS;
}
Player *ownerPlayer = getMachineOwner()->getControllingPlayer();
TunnelTracker *tunnels = NULL;
if (ownerPlayer) {
tunnels = ownerPlayer->getTunnelSystem();
}
if (tunnels) tunnels->updateNemesis(nemesis);
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
m_attackState = newInstance(AIAttackState)(getMachine(), true, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
//-------------------------------------------------------------------------------------------------
StateReturnType AITNGuardAttackAggressorState::update( void )
{
if (m_attackState->getMachine()->getCurrentStateID() == AttackStateMachine::FIRE_WEAPON) {
Object *nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID());
Player *ownerPlayer = getMachineOwner()->getControllingPlayer();
TunnelTracker *tunnels = NULL;
if (ownerPlayer) {
tunnels = ownerPlayer->getTunnelSystem();
}
if (tunnels) tunnels->updateNemesis(nemesis);
}
return m_attackState->update();
}
//-------------------------------------------------------------------------------------------------
void AITNGuardAttackAggressorState::onExit( StateExitType status )
{
Object *obj = getMachineOwner();
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
if (obj->getTeam())
{
obj->getTeam()->setTeamTargetObject(NULL); // clear the target.
}
}
//-------------------------------------------------------------------------------------------------
void AITNGuardAttackAggressorState::crc( Xfer *xfer )
{
}
//-------------------------------------------------------------------------------------------------
void AITNGuardAttackAggressorState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
}
//-------------------------------------------------------------------------------------------------
void AITNGuardAttackAggressorState::loadPostProcess()
{
onEnter();
}

View file

@ -0,0 +1,271 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: Squad.cpp
/*---------------------------------------------------------------------------*/
/* EA Pacific */
/* Confidential Information */
/* Copyright (C) 2001 - All Rights Reserved */
/* DO NOT DISTRIBUTE */
/*---------------------------------------------------------------------------*/
/* Project: RTS3 */
/* File name: Squad.cpp */
/* Created: John K. McDonald, Jr., 4/19/2002 */
/* Desc: // @todo */
/* Revision History: */
/* 4/19/2002 : Initial creation */
/*---------------------------------------------------------------------------*/
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameLogic/Squad.h"
#include "Common/GameState.h"
#include "Common/Team.h"
#include "Common/Xfer.h"
#include "GameLogic/AI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// addObject //////////////////////////////////////////////////////////////////////////////////////
void Squad::addObject(Object *objectToAdd)
{
if (objectToAdd) {
m_objectIDs.push_back(objectToAdd->getID());
}
}
// addObjectID ////////////////////////////////////////////////////////////////////////////////////
void Squad::addObjectID(ObjectID objectID) {
m_objectIDs.push_back(objectID);
}
// removeObject ///////////////////////////////////////////////////////////////////////////////////
void Squad::removeObject(Object *objectToRemove)
{
if (objectToRemove) {
ObjectID objID;
objID = objectToRemove->getID();
VecObjectIDIt it = std::find(m_objectIDs.begin(), m_objectIDs.end(), objID);
if (it != m_objectIDs.end()) {
m_objectIDs.erase(it);
}
}
}
// clearSquad /////////////////////////////////////////////////////////////////////////////////////
void Squad::clearSquad() {
m_objectIDs.clear();
m_objectsCached.clear();
}
// getAllObjects //////////////////////////////////////////////////////////////////////////////////
const VecObjectPtr& Squad::getAllObjects(void) // Not a const function cause we clear away dead object here too
{
// prunes all NULL objects
m_objectsCached.clear();
for (VecObjectIDIt it = m_objectIDs.begin(); it != m_objectIDs.end(); ) {
Object *obj = TheGameLogic->findObjectByID(*it);
if (obj) {
m_objectsCached.push_back(obj);
++it;
} else {
it = m_objectIDs.erase(it);
}
}
return m_objectsCached;
}
// getLiveObjects /////////////////////////////////////////////////////////////////////////////////
const VecObjectPtr& Squad::getLiveObjects(void)
{
// first get all the objects.
// cheat, since we are a member function, and just use m_objectsCached
getAllObjects();
for (VecObjectPtrIt it = m_objectsCached.begin(); it != m_objectsCached.end(); ) {
if (!(*it)->isSelectable()) {
it = m_objectsCached.erase(it);
} else {
++it;
}
}
return m_objectsCached;
}
// getSizeOfGroup /////////////////////////////////////////////////////////////////////////////////
Int Squad::getSizeOfGroup(void) const
{
return m_objectIDs.size();
}
// isOnSquad //////////////////////////////////////////////////////////////////////////////////////
Bool Squad::isOnSquad(const Object *objToTest) const
{
// @todo need a faster way to do this. Perhaps a more efficient data structure?
ObjectID objID = objToTest->getID();
for (VecObjectID::const_iterator cit = m_objectIDs.begin(); cit != m_objectIDs.end(); ++cit) {
if (objID == (*cit)) {
return true;
}
}
return false;
}
/**
* There should never be a TeamFromSqaud as Teams are entirely a construct to work with the AI.
* Since things can only be on one Team at a time, creating a Team from an arbitrary Squad will
* cause weird, difficult to reproduce bugs. Please don't do it.
*/
// squadFromTeam //////////////////////////////////////////////////////////////////////////////////
void Squad::squadFromTeam(const Team* fromTeam, Bool clearSquadFirst)
{
if (!fromTeam) {
return;
}
if (clearSquadFirst) {
m_objectIDs.clear();
}
for (DLINK_ITERATOR<Object> iter = fromTeam->iterate_TeamMemberList(); !iter.done(); iter.advance()) {
Object *obj = iter.cur();
m_objectIDs.push_back(obj->getID());
}
}
// squadFromAIGroup ///////////////////////////////////////////////////////////////////////////////
void Squad::squadFromAIGroup(const AIGroup* fromAIGroup, Bool clearSquadFirst)
{
if (!fromAIGroup) {
return;
}
if (clearSquadFirst) {
m_objectIDs.clear();
}
m_objectIDs = fromAIGroup->getAllIDs();
}
// aiGroupFromSquad ///////////////////////////////////////////////////////////////////////////////
void Squad::aiGroupFromSquad(AIGroup* aiGroupToFill)
{
if (!aiGroupToFill) {
return;
}
// cheat, since we are a member function, and just use m_objectsCached
getLiveObjects();
for (VecObjectPtr::iterator it = m_objectsCached.begin(); it != m_objectsCached.end(); ++it) {
aiGroupToFill->add((*it));
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void Squad::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void Squad::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// length of object ID list
UnsignedShort objectCount = m_objectIDs.size();
xfer->xferUnsignedShort( &objectCount );
// object id elements
ObjectID objectID;
if( xfer->getXferMode() == XFER_SAVE )
{
// save each object id
VecObjectIDIt it;
for( it = m_objectIDs.begin(); it != m_objectIDs.end(); ++it )
{
// save object ID
objectID = *it;
xfer->xferObjectID( &objectID );
} // end for, it
} // end if, save
else
{
// the cached objects list should be empty
if( m_objectsCached.size() != 0 )
{
DEBUG_CRASH(( "Squad::xfer - m_objectsCached should be emtpy, but is not\n" ));
throw SC_INVALID_DATA;
} // end of
// read all items
for( UnsignedShort i = 0; i < objectCount; ++i )
{
// read id
xfer->xferObjectID( &objectID );
// put on list
m_objectIDs.push_back( objectID );
} // end for, i
} // end else, load
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void Squad::loadPostProcess( void )
{
} // end loadPostProcess

File diff suppressed because it is too large Load diff