/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: DozerAIUpdate.cpp ////////////////////////////////////////////////////////////////////////
// Author: Colin Day, February 2002
// Desc: Dozer AI behavior
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/ActionManager.h"
#include "Common/Team.h"
#include "Common/StateMachine.h"
#include "Common/BuildAssistant.h"
#include "Common/ThingTemplate.h"
#include "Common/ThingFactory.h"
#include "Common/Player.h"
#include "Common/Money.h"
#include "Common/Radar.h"
#include "Common/RandomValue.h"
#include "Common/GameState.h"
#include "Common/GlobalData.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/GameText.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Locomotor.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/BridgeBehavior.h"
#include "GameLogic/Module/BridgeTowerBehavior.h"
#include "GameLogic/Module/CreateModule.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#include "GameClient/InGameUI.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// FORWARD DECLARATIONS ///////////////////////////////////////////////////////////////////////////
class DozerPrimaryStateMachine;
class DozerActionStateMachine;
static const Real MIN_ACTION_TOLERANCE = 70.0f;
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
/** Available Dozer actions */
//-------------------------------------------------------------------------------------------------
enum DozerActionType
{
DOZER_ACTION_PICK_ACTION_POS, ///< pick a location "around" the target to do our action
DOZER_ACTION_MOVE_TO_ACTION_POS,///< move to our action pos we've picked
DOZER_ACTION_DO_ACTION, ///< do our action at the position, build/repair/etc ...
};
//-------------------------------------------------------------------------------------------------
/** Dozer picks a position around the target to do the action */
//-------------------------------------------------------------------------------------------------
class DozerActionPickActionPosState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(DozerActionPickActionPosState, "DozerActionPickActionPosState")
public:
DozerActionPickActionPosState( StateMachine *machine, DozerTask task );
virtual StateReturnType update( void );
protected:
// snapshot interface
virtual void crc( Xfer *xfer );
virtual void xfer( Xfer *xfer );
virtual void loadPostProcess();
protected:
DozerTask m_task; ///< our task
Int m_failedAttempts; /**< counter for successive unsuccessfull attempts to pick
and move to an action position */
};
EMPTY_DTOR(DozerActionPickActionPosState)
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
DozerActionPickActionPosState::DozerActionPickActionPosState( StateMachine *machine,
DozerTask task ) :
State( machine, "DozerActionPickActionPosState" )
{
m_task = task;
m_failedAttempts = 0;
} // end DozerActionPickActionPosState
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DozerActionPickActionPosState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void DozerActionPickActionPosState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
xfer->xferUser(&m_task, sizeof(m_task));
xfer->xferInt(&m_failedAttempts);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DozerActionPickActionPosState::loadPostProcess( void )
{
} // end loadPostProcess
//-------------------------------------------------------------------------------------------------
/** Pick a position around the target */
//-------------------------------------------------------------------------------------------------
StateReturnType DozerActionPickActionPosState::update( void )
{
StateMachine *machine = getMachine();
Object *dozer = machine->getOwner();
// get dozer ai update interface
AIUpdateInterface *ai = dozer->getAIUpdateInterface();
if( !ai )
{
return STATE_FAILURE;
}
DozerAIInterface *dozerAI = ai->getDozerAIInterface();
if( !dozerAI )
{
return STATE_FAILURE;
}
// get the object we're concerned with for our task
Object *goalObject = TheGameLogic->findObjectByID( dozerAI->getTaskTarget( m_task ) );
// if there is no goal, get out of this machine with a failure code (success is done in the action state )
if( goalObject == NULL )
{
// to be clean get rid of the goal object we set
getMachine()->setGoalObject( NULL );
// cancel our task
dozerAI->cancelTask( m_task );
// exit with failure
return STATE_FAILURE;
} // end if
// pick a location to move to
Coord3D goalPos;
const Coord3D *pos = dozerAI->getDockPoint( m_task, DOZER_DOCK_POINT_START );
if( pos )
goalPos = *pos;
else
{
// pick a spot to use
//
// find the vector from goal object to us ... we will start our search on this angle so
// that we "approach" a closer point rather than a point on a random side
//
Coord2D v;
v.x = dozer->getPosition()->x - goalObject->getPosition()->x;
v.y = dozer->getPosition()->y - goalObject->getPosition()->y;
Real radius = goalObject->getGeometryInfo().getBoundingSphereRadius();
FindPositionOptions fpOptions;
fpOptions.minRadius = radius;
fpOptions.maxRadius = radius;
fpOptions.startAngle = v.toAngle();
if( ThePartitionManager->findPositionAround( goalObject->getPosition(),
&fpOptions,
&goalPos ) == FALSE )
{
// return STATE_FAILURE; no, we don't ever want dozers to fail, particularly
// if ai.
goalPos = *goalObject->getPosition();
} // end if
//
// we only ignore the goal object when we did the point selection in this more
// "random" method cause we could pick a place inside the object
//
ai->ignoreObstacle( goalObject );
} // end if
// set goal position and object
machine->setGoalObject( goalObject );
machine->setGoalPosition( &goalPos );
ai->ignoreObstacle(goalObject);
ai->aiMoveToPosition( &goalPos, CMD_FROM_AI );
return STATE_SUCCESS;
} // end update
//-------------------------------------------------------------------------------------------------
/** Dozer moves to the action position */
//-------------------------------------------------------------------------------------------------
class DozerActionMoveToActionPosState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(DozerActionMoveToActionPosState, "DozerActionMoveToActionPosState")
public:
DozerActionMoveToActionPosState( StateMachine *machine, DozerTask task ) : State( machine, "DozerActionMoveToActionPosState" ) { m_task = task; }
virtual StateReturnType update( void );
protected:
// snapshot interface
virtual void crc( Xfer *xfer );
virtual void xfer( Xfer *xfer );
virtual void loadPostProcess();
protected:
DozerTask m_task; ///< our task
};
EMPTY_DTOR(DozerActionMoveToActionPosState)
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DozerActionMoveToActionPosState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void DozerActionMoveToActionPosState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
xfer->xferUser(&m_task, sizeof(m_task));
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DozerActionMoveToActionPosState::loadPostProcess( void )
{
} // end loadPostProcess
//-------------------------------------------------------------------------------------------------
/** We are supposed to be on route to our action position now, see when we get there or
* detect that we have encountered a problem that's going to cause it to give up */
//-------------------------------------------------------------------------------------------------
StateReturnType DozerActionMoveToActionPosState::update( void )
{
Object *goalObject = getMachineGoalObject();
Object *dozer = getMachineOwner();
// sanity
if( goalObject == NULL || dozer == NULL )
return STATE_FAILURE;
AIUpdateInterface *ai = dozer->getAIUpdateInterface();
ObjectID currentRepairer = goalObject->getSoleHealingBenefactor();
if( m_task==DOZER_TASK_REPAIR && currentRepairer != INVALID_ID && currentRepairer != dozer->getID() )//oops I guess someone beat me to it!
{
if ( ai )
{
DozerAIInterface *dozerAI = ai->getDozerAIInterface();
if ( dozerAI )
dozerAI->internalTaskComplete( m_task );
}
getMachine()->setGoalObject( NULL );
return STATE_FAILURE;
}
// Double oops, someone else has taken over the build responsibility for this building. We can't double up.
// This can happen because a guy who is moving to build who is told to build will idle for a frame after registering.
// Two workers, two started buildings.
// Grab both workers, resume on one building
// First will register, second will see first as active and say no
// Grab both, click on other building
// First will register, and idle for a frame, second will see first as not active and say yes
// Next frame, first will start the build task, without reasking validity
// Infinite number of workers can be told to build something with just two in progress buildings
if( (m_task == DOZER_TASK_BUILD) && goalObject && (goalObject->getBuilderID() != dozer->getID()) )
{
// Geebus. Returning failure is ignored, so you have to explicitly make the ai stop trying to restart the machine
if ( ai )
{
DozerAIInterface *dozerAI = ai->getDozerAIInterface();
if ( dozerAI )
dozerAI->internalTaskComplete( m_task );
}
getMachine()->setGoalObject( NULL );
return STATE_FAILURE;
}
// if distance between us and our goal position is close enough
const Coord3D *goalPos = getMachine()->getGoalPosition();
Real distSqr = ThePartitionManager->getDistanceSquared( dozer, goalPos, FROM_BOUNDINGSPHERE_2D );
const Real SLOP = 15.0f;
Real allowableDistanceSqr = sqr(max( MIN_ACTION_TOLERANCE, dozer->getGeometryInfo().getBoundingSphereRadius() + SLOP ));
if( distSqr <= allowableDistanceSqr )
{
if( m_task == DOZER_TASK_BUILD )
{
//
// the object is now no longer awaiting construction, it is being constructed ... note
// that we might possibly be here multiple times for a single object being built
// but the setting of the same model condition doesn't have any adverse effects
//
goalObject->clearAndSetModelConditionFlags(
MAKE_MODELCONDITION_MASK(MODELCONDITION_AWAITING_CONSTRUCTION),
MAKE_MODELCONDITION_MASK2(MODELCONDITION_PARTIALLY_CONSTRUCTED, MODELCONDITION_ACTIVELY_BEING_CONSTRUCTED));
} // end if
return STATE_SUCCESS;
} // end if
// if we're in the idle state fail our move
// Failure transition is back to DOZER_ACTION_PICK_ACTION_POS, so
// it is ok to fail. jba.
if( ai && ai->isIdle() )
return STATE_FAILURE;
return STATE_CONTINUE;
} // end update
//-------------------------------------------------------------------------------------------------
/** Dozer does the "action" */
//-------------------------------------------------------------------------------------------------
class DozerActionDoActionState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(DozerActionDoActionState, "DozerActionDoActionState")
public:
DozerActionDoActionState( StateMachine *machine, DozerTask task ) : State( machine, "DozerActionDoActionState" )
{
m_task = task;
//Added By Sadullah Nader
// Initializations missing and needed
m_enterFrame = 0;
}
virtual StateReturnType update( void );
virtual StateReturnType onEnter( void );
virtual void onExit( StateExitType status ) { }
protected:
// snapshot interface
virtual void crc( Xfer *xfer );
virtual void xfer( Xfer *xfer );
virtual void loadPostProcess();
protected:
DozerTask m_task; ///< our task
UnsignedInt m_enterFrame; ///< frame we entered this state on
};
EMPTY_DTOR(DozerActionDoActionState)
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DozerActionDoActionState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void DozerActionDoActionState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
xfer->xferUser(&m_task, sizeof(m_task));
xfer->xferUnsignedInt(&m_enterFrame);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DozerActionDoActionState::loadPostProcess( void )
{
} // end loadPostProcess
//-------------------------------------------------------------------------------------------------
/** Entering the do action state */
//-------------------------------------------------------------------------------------------------
StateReturnType DozerActionDoActionState::onEnter( void )
{
Object *dozer = getMachineOwner();
DozerAIInterface *dozerAI = dozer->getAIUpdateInterface()->getDozerAIInterface();
if( !dozerAI )
{
return STATE_FAILURE;
}
// DozerAIUpdate *dozerAI = static_cast(dozer->getAIUpdateInterface());
// record the frame we came in on
m_enterFrame = TheGameLogic->getFrame();
// when building, we have additional movement that we will for docking
if( m_task == DOZER_TASK_BUILD )
dozerAI->setBuildSubTask( DOZER_SELECT_BUILD_DOCK_LOCATION );
return STATE_CONTINUE;
} // end onEnter
//-------------------------------------------------------------------------------------------------
/** Do the action */
//-------------------------------------------------------------------------------------------------
StateReturnType DozerActionDoActionState::update( void )
{
Object *goalObject = getMachineGoalObject();
Object *dozer = getMachineOwner();
AIUpdateInterface *ai = dozer->getAIUpdateInterface();
if( !ai )
{
return STATE_FAILURE;
}
DozerAIInterface *dozerAI = ai->getDozerAIInterface();
if( !dozerAI )
{
return STATE_FAILURE;
}
// DozerAIUpdate *dozerAI = static_cast(dozer->getAIUpdateInterface());
// const UnsignedInt ACTION_TIME = LOGICFRAMES_PER_SECOND * 4 ; // frames to spend here in this state doing the action
// check for object gone
if( goalObject == NULL )
return STATE_FAILURE;
if ( dozer->isDisabledByType( DISABLED_UNMANNED ) )// Yipes, I've been sniped!
return STATE_FAILURE;
// do the task
Bool complete = FALSE;
switch( m_task )
{
//---------------------------------------------------------------------------------------------
case DOZER_TASK_BUILD:
{
//GS Moved this inside Build, since you are allowed to Repair things that are not your player (canRepairObject handles it)
if (dozer->getControllingPlayer() != goalObject->getControllingPlayer())//Yipes, SOmehow I have changed sides in mid build!
return STATE_FAILURE;
// if we need to select the dock location and move there do so
if( dozerAI->getBuildSubTask() == DOZER_SELECT_BUILD_DOCK_LOCATION )
{
const Coord3D *dockLocation = dozerAI->getDockPoint( m_task, DOZER_DOCK_POINT_ACTION );
if( dockLocation )
ai->aiMoveToPosition( dockLocation, CMD_FROM_AI );
// we're now moving to the dock location
dozerAI->setBuildSubTask( DOZER_MOVING_TO_BUILD_DOCK_LOCATION );
} // end if
// if we're moving to the build dock location, when we become idle we are there
if( dozerAI->getBuildSubTask() == DOZER_MOVING_TO_BUILD_DOCK_LOCATION )
{
if( ai->isIdle() )
{
dozerAI->setBuildSubTask( DOZER_DO_BUILD_AT_DOCK );
// Get the audio sound and start playing the construction sound (get the sound
// from the building itself)
dozerAI->startBuildingSound( goalObject->getTemplate()->getPerUnitSound( "UnderConstruction" ), goalObject->getID() );
}
} // end if
// only do the build if we've moved into the dock position
if( dozerAI->getBuildSubTask() == DOZER_DO_BUILD_AT_DOCK )
{
// the builder is now actively constructing something
dozer->setModelConditionState( MODELCONDITION_ACTIVELY_CONSTRUCTING );
// increase the construction percent of the goal object
Int framesToBuild = goalObject->getTemplate()->calcTimeToBuild( dozer->getControllingPlayer() );
Real percentProgressThisFrame = 100.0f / framesToBuild;
goalObject->setConstructionPercent( goalObject->getConstructionPercent() +
percentProgressThisFrame );
//
// every time we construct a piece of the goal object, the goal object gets a little
// bit o health, note that we're bypassing the regular healing/damage methods
// here and going straight for the change of the health
//
BodyModuleInterface *body = goalObject->getBodyModule();
body->internalChangeHealth( body->getMaxHealth() / INT_TO_REAL( framesToBuild ) );
//
// since we've just actually contributed a "piece" to this building, we'll say that
// we are now the producer and the builder
//
goalObject->setProducer( dozer );
goalObject->setBuilder( dozer );
// check for construction complete
if( goalObject->getConstructionPercent() >= 100.0f )
{
// clear the under construction status
goalObject->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNDER_CONSTRUCTION ) );
goalObject->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_RECONSTRUCTING ) );
// stop playing the construction sound!
dozerAI->finishBuildingSound();
// object will now be idle instead of in one of the construction actions
goalObject->clearModelConditionFlags(
MAKE_MODELCONDITION_MASK3(MODELCONDITION_AWAITING_CONSTRUCTION, MODELCONDITION_PARTIALLY_CONSTRUCTED, MODELCONDITION_ACTIVELY_BEING_CONSTRUCTED));
// set the construction at the 100% enum value
goalObject->setConstructionPercent( CONSTRUCTION_COMPLETE );
//
// now that we're done with construction we evaluate the visual condition of
// the object since while under construction visual changes due to damage state
// do not occur. Note that is was important to call this after we cleared the
// model conditions involving construction because part of this evaluation
// is to auto populate the model with particle systems when special named
// bones for the current given state are provided
//
body->evaluateVisualCondition();
// this object now has energy influence in the player
Player *player = goalObject->getControllingPlayer();
if( player )
{
// notification for build completeion
player->onStructureConstructionComplete( dozer, goalObject, dozerAI->getIsRebuild() );
player->getAcademyStats()->recordProduction( goalObject, dozer );
//
// Now onCreates were called at construction start. Now at finish is when we
// want the Game side of OnCreate
//
for (BehaviorModule** m = goalObject->getBehaviorModules(); *m; ++m)
{
CreateModuleInterface* create = (*m)->getCreate();
if (!create)
continue;
create->onBuildComplete();
}
} // end if
// Creation is another valid and essential time to call this. This building now Looks.
goalObject->handlePartitionCellMaintenance();
// UnderConstruction just cleared, so update our upgrades
goalObject->updateUpgradeModules();
// this object how has influence in the controlling players' tech tree
/// @todo need to write this
// do some UI stuff for the constrolling player
if( dozer->isLocallyControlled() )
{
// message the the building player
UnicodeString format = TheGameText->fetch( "DOZER:ConstructionComplete" );
UnicodeString objectName = goalObject->getTemplate()->getDisplayName();
if( objectName.isEmpty() )
{
UnicodeString format = TheGameText->fetch( "INI:MissingDisplayName" );
objectName.format( format, goalObject->getTemplate()->getName().str() );
} // end if
UnicodeString msg;
msg.format( format.str(), objectName.str() );
TheInGameUI->message( msg );
AudioEventRTS audio = *dozer->getTemplate()->getVoiceTaskComplete();
audio.setObjectID(dozer->getID());
TheAudio->addAudioEvent(&audio);
/// make radar neat-o attention grabber event at build location
TheRadar->createEvent( goalObject->getPosition(), RADAR_EVENT_CONSTRUCTION );
} // end if
// this will allow us to exit the state machine with success
complete = TRUE;
// move off to the end dock position if present
const Coord3D *endPos = dozerAI->getDockPoint( m_task, DOZER_DOCK_POINT_END );
Coord3D pos = *dozer->getPosition();
if( endPos ) {
pos = *endPos;
}
// Our goal may be inside a building, if we are packing them in tight, so try adjusting. jba.
TheAI->pathfinder()->adjustToPossibleDestination(dozer, ai->getLocomotorSet(), &pos);
ai->aiMoveToPosition( &pos, CMD_FROM_AI );
} // end if
} // end if
break;
} // end build
//---------------------------------------------------------------------------------------------
case DOZER_TASK_REPAIR:
{
BodyModuleInterface *body = goalObject->getBodyModule();
// check for fully "repaired"
if( body->getHealth() == body->getMaxHealth() )
{
// issue repair complete message, we might want to remove this later cause it could be annoying
TheInGameUI->message( "DOZER:RepairComplete" );
// we're now complete
complete = TRUE;
} // end if
else
{
Bool canHeal = TRUE;
// if we are repairing a bridge, create scaffolding over the bridge if we need to
if( goalObject->isKindOf( KINDOF_BRIDGE_TOWER ) )
dozerAI->createBridgeScaffolding( goalObject );
// the builder is now actively repairing something, we'll borrow the constructing animation
dozer->setModelConditionState( MODELCONDITION_ACTIVELY_CONSTRUCTING );
//
// when repairing bridges, we cannot actually do any repairing until the
// scaffolding is extended and all the way complete
//
if( goalObject->isKindOf( KINDOF_BRIDGE_TOWER ) )
{
BridgeTowerBehaviorInterface *btbi = BridgeTowerBehavior::getBridgeTowerBehaviorInterfaceFromObject( goalObject );
DEBUG_ASSERTCRASH( btbi, ("Unable to find bridge tower interface\n") );
Object *bridgeObject = TheGameLogic->findObjectByID( btbi->getBridgeID() );
DEBUG_ASSERTCRASH( bridgeObject, ("Unable to find bridge center object\n") );
BridgeBehaviorInterface *bbi = BridgeBehavior::getBridgeBehaviorInterfaceFromObject( bridgeObject );
DEBUG_ASSERTCRASH( bbi, ("Unable to find bridge interface from tower goal object during repair\n") );
if( bbi->isScaffoldInMotion() == TRUE )
canHeal = FALSE;
} // end if
// do healing
if( canHeal )
{
// figure out how much health we will restore this frame
Real health = body->getMaxHealth() * dozerAI->getRepairHealthPerSecond() /
LOGICFRAMES_PER_SECOND;
// try to give it a little bit-o-health
if ( ! goalObject->attemptHealingFromSoleBenefactor(health, dozer, 2) )//this frame and the next
{
// goalObject->setStatus( OBJECT_STATUS_UNDERGOING_REPAIR );
// This bit used to be set way back in DozerAIUpdate::privateRepair(), but it has been outmoded
// so that several dozers/workers can target the same sick building for repair, but the first one to begin
// this is done by attemptHealingFromSoleBenefactor() which lets the beneficiary of the healing
// remember who has been healing it, and will return false to everybody else
//or if the goalObject is already receiving healing, I must stop, since my healing is getting rejected here
dozerAI->internalTaskComplete( m_task );
getMachine()->setGoalObject( NULL );
return STATE_FAILURE;
}
} // end if
} // end else
// play a repairing sound
break;
} // end repair
//---------------------------------------------------------------------------------------------
case DOZER_TASK_FORTIFY:
{
/// @todo write me
break;
} // end fortify
//---------------------------------------------------------------------------------------------
default:
{
DEBUG_CRASH(( "Unknown task for the dozer action do action state\n" ));
return STATE_FAILURE;
} // end default
} // end switch
// if we're complete with the task we exit success
if( complete == TRUE )
{
// this task is now complete, remove it from dozer consideration
dozerAI->internalTaskComplete( m_task );
// to be clean get rid of the goal object we set
getMachine()->setGoalObject( NULL );
getMachineOwner()->setWeaponSetFlag(WEAPONSET_MINE_CLEARING_DETAIL);//maybe go clear some mines, if I feel like it
// we're done
return STATE_SUCCESS;
} // end if
// just continue sitting here for a while
getMachineOwner()->clearWeaponSetFlag(WEAPONSET_MINE_CLEARING_DETAIL);// no mine clearing fun while I'm on the job
return STATE_CONTINUE;
} // end update
//-------------------------------------------------------------------------------------------------
/** The Dozer action state machine */
//-------------------------------------------------------------------------------------------------
class DozerActionStateMachine : public StateMachine
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( DozerActionStateMachine, "DozerActionStateMachine" );
public:
DozerActionStateMachine( Object *owner, DozerTask task );
// virtual destructor prototypes provided by memory pool object
protected:
// snapshot interface
virtual void crc( Xfer *xfer );
virtual void xfer( Xfer *xfer );
virtual void loadPostProcess();
protected:
DozerTask m_task; ///< the task of this action state machine
};
EMPTY_DTOR(DozerActionStateMachine)
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
DozerActionStateMachine::DozerActionStateMachine( Object *owner, DozerTask task ) :
StateMachine( owner, "DozerActionStateMachine" )
{
// initialize our task
m_task = task;
// order matters: first state is the default state.
defineState( DOZER_ACTION_PICK_ACTION_POS, newInstance(DozerActionPickActionPosState)( this, task ), DOZER_ACTION_MOVE_TO_ACTION_POS, EXIT_MACHINE_WITH_FAILURE );
defineState( DOZER_ACTION_MOVE_TO_ACTION_POS, newInstance(DozerActionMoveToActionPosState)( this, task ), DOZER_ACTION_DO_ACTION, DOZER_ACTION_PICK_ACTION_POS );
defineState( DOZER_ACTION_DO_ACTION, newInstance(DozerActionDoActionState)( this, task ), EXIT_MACHINE_WITH_SUCCESS, EXIT_MACHINE_WITH_FAILURE );
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DozerActionStateMachine::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void DozerActionStateMachine::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
xfer->xferUser(&m_task, sizeof(m_task));
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DozerActionStateMachine::loadPostProcess( void )
{
} // end loadPostProcess
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
static Object *findObjectToRepair( Object *dozer )
{
// sanity
if( dozer == NULL )
return NULL;
if( !dozer->getAIUpdateInterface() )
{
return NULL;
}
const DozerAIInterface *dozerAI = dozer->getAIUpdateInterface()->getDozerAIInterface();
PartitionFilterSamePlayer filter1( dozer->getControllingPlayer() );
PartitionFilterAcceptByKindOf filter2( MAKE_KINDOF_MASK( KINDOF_STRUCTURE ),
KINDOFMASK_NONE );
PartitionFilterSameMapStatus filterMapStatus(dozer);
PartitionFilter *filters[] = { &filter1, &filter2, &filterMapStatus, NULL };
ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( dozer->getPosition(),
dozerAI->getBoredRange(),
FROM_CENTER_2D,
filters );
MemoryPoolObjectHolder hold( iter );
Object *obj;
Object *closestRepairTarget = NULL;
Real closestRepairTargetDistSqr = 0.0f;
for( obj = iter->first(); obj; obj = iter->next() )
{
// ignore objects we cant repair
if( TheActionManager->canRepairObject( dozer, obj, CMD_FROM_AI ) == FALSE )
continue;
// target the closest valid repair target
if( closestRepairTarget == NULL )
{
closestRepairTarget = obj;
closestRepairTargetDistSqr = ThePartitionManager->getDistanceSquared( dozer, obj, FROM_CENTER_2D );
} // end if
else
{
// only use this command center if it's closer than the last one we found
Real distSqr = ThePartitionManager->getDistanceSquared( dozer, obj, FROM_CENTER_2D );
if( distSqr < closestRepairTargetDistSqr )
{
closestRepairTarget = obj;
closestRepairTargetDistSqr = distSqr;
} // end if
} // end else
} // end for obj
return closestRepairTarget;
} // end findObjectToRepair
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
static Object *findMine( Object *dozer )
{
// sanity
if( dozer == NULL )
return NULL;
if( !dozer->getAIUpdateInterface() )
{
return NULL;
}
const DozerAIInterface *dozerAI = dozer->getAIUpdateInterface()->getDozerAIInterface();
// srj sez: only clear enemy or neutal mines. clearing allied mines (ie, OURS) is really dumb.
// (and no, PossibleToAttack won't necessarily filter these out.)
PartitionFilterRelationship filterTeam(dozer, PartitionFilterRelationship::ALLOW_ENEMIES | PartitionFilterRelationship::ALLOW_NEUTRAL);
PartitionFilterPossibleToAttack filterAttack(ATTACK_NEW_TARGET, dozer, CMD_FROM_DOZER);
PartitionFilterSameMapStatus filterMapStatus(dozer);
PartitionFilter *filters[] = { &filterTeam, &filterAttack, &filterMapStatus, NULL };
Object* mine = ThePartitionManager->getClosestObject(dozer, dozerAI->getBoredRange(), FROM_CENTER_2D, filters);
return mine;
} // end findMine
//-------------------------------------------------------------------------------------------------
/** Available primary Dozer states */
//-------------------------------------------------------------------------------------------------
enum
{
DOZER_PRIMARY_IDLE, ///< dozer is idle
DOZER_PRIMARY_BUILD, ///< dozer is building a structure for the player
DOZER_PRIMARY_REPAIR, ///< dozer is repairing something
DOZER_PRIMARY_FORTIFY, ///< dozer is fortifying a civilian building, making it stronger
DOZER_PRIMARY_GO_HOME, ///< dozer has nothing else to do so it will return a command center
};
//-------------------------------------------------------------------------------------------------
/** Dozer primary idle state */
//-------------------------------------------------------------------------------------------------
class DozerPrimaryIdleState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(DozerPrimaryIdleState, "DozerPrimaryIdleState")
public:
DozerPrimaryIdleState( StateMachine *machine ) : State( machine, "DozerPrimaryIdleState" )
{
m_idleTooLongTimestamp = 0;
m_idlePlayerNumber = 0;
m_isMarkedAsIdle = FALSE;
}
virtual StateReturnType update( void );
virtual StateReturnType onEnter( void );
virtual void onExit( StateExitType status );
protected:
// snapshot interface
virtual void crc( Xfer *xfer );
virtual void xfer( Xfer *xfer );
virtual void loadPostProcess();
protected:
UnsignedInt m_idleTooLongTimestamp; ///< when this is more than our idle too long time we try to do something about it
Int m_idlePlayerNumber; ///< Remeber what list we were added to.
Bool m_isMarkedAsIdle;
};
EMPTY_DTOR(DozerPrimaryIdleState)
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DozerPrimaryIdleState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void DozerPrimaryIdleState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
xfer->xferUnsignedInt(&m_idleTooLongTimestamp);
xfer->xferInt(&m_idlePlayerNumber);
xfer->xferBool(&m_isMarkedAsIdle);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DozerPrimaryIdleState::loadPostProcess( void )
{
} // end loadPostProcess
//-------------------------------------------------------------------------------------------------
/** Upon entering the dozer primary idle state */
//-------------------------------------------------------------------------------------------------
StateReturnType DozerPrimaryIdleState::onEnter( void )
{
//
// upon entering this state we begin tracking how long we're idle for to try to do something
// about it every once in a while
//
m_idleTooLongTimestamp = TheGameLogic->getFrame();
//if(TheGameLogic->getFrame() > m_idleNotifyTimestamp + NOTIFY_AFTER_TIME)
m_isMarkedAsIdle = FALSE;
getMachineOwner()->setWeaponSetFlag(WEAPONSET_MINE_CLEARING_DETAIL);//maybe go clear some mines, if I feel like it
return STATE_CONTINUE;
} // end onEnter
//-------------------------------------------------------------------------------------------------
/** Upon exiting the dozer primary idle state */
//-------------------------------------------------------------------------------------------------
void DozerPrimaryIdleState::onExit( StateExitType status )
{
if(m_isMarkedAsIdle)
{
TheInGameUI->removeIdleWorker(getMachineOwner(), m_idlePlayerNumber);
m_idlePlayerNumber = -1;
m_isMarkedAsIdle = FALSE;
}
}
//-------------------------------------------------------------------------------------------------
/** Dozer idle behavior */
//-------------------------------------------------------------------------------------------------
StateReturnType DozerPrimaryIdleState::update( void )
{
Object *dozer = getMachineOwner();
AIUpdateInterface *ai = dozer->getAIUpdateInterface();
if( !ai )
{
return STATE_FAILURE;
}
DozerAIInterface *dozerAI = ai->getDozerAIInterface();
if( !dozerAI )
{
return STATE_FAILURE;
}
//
// These are to add into the IngameUI idle worker button thingy
// we don't want to add in if we're already in the list or if
// we're "Effectivly dead"
//
if( ai->isIdle() && !m_isMarkedAsIdle && !dozer->isEffectivelyDead())
{
m_idlePlayerNumber = dozer->getControllingPlayer()->getPlayerIndex();
TheInGameUI->addIdleWorker(getMachineOwner());
m_isMarkedAsIdle = TRUE;
getMachineOwner()->setWeaponSetFlag(WEAPONSET_MINE_CLEARING_DETAIL);//maybe go clear some mines, if I feel like it
}
if( m_isMarkedAsIdle && (!ai->isIdle() || dozer->isEffectivelyDead()))
{
TheInGameUI->removeIdleWorker(getMachineOwner(), m_idlePlayerNumber);
m_idlePlayerNumber = -1;
m_isMarkedAsIdle = FALSE;
}
//
// if our actual real AI state machine is not idle we're doing something so let's reset
// our idle too long timestamp
//
if( !ai->isIdle() )
m_idleTooLongTimestamp = TheGameLogic->getFrame();
//
// if we're just sitting here in idle, and there is no task pending ... after so long we'll
// try to go to the nearest command center
//
if( TheGameLogic->getFrame() - m_idleTooLongTimestamp > dozerAI->getBoredTime() &&
dozerAI->isAnyTaskPending() == FALSE )
{
//
// to prevent us from doing this potentially expensive logic to find objects every frame
// we'll only try it every once in a while so set our idle too long timestamp to the
// current frame
//
m_idleTooLongTimestamp = TheGameLogic->getFrame();
// try to find something around us that we can repair
Object *repairTarget = findObjectToRepair( dozer );
if( repairTarget )
{
// issue the command
ai->aiRepair( repairTarget, CMD_FROM_AI );
//
// in theory we would need to "interrupt" whatever it is the Dozer is doing now, but
// we know we're in the idle state doing nothing so we don't really need to
//
} else {
getMachineOwner()->setWeaponSetFlag(WEAPONSET_MINE_CLEARING_DETAIL);//maybe go clear some mines, if I feel like it
Object *mine = findMine(dozer);
if (mine!=NULL) {
ai->aiAttackObject( mine, 1, CMD_FROM_DOZER);
}
}
} // end if
return STATE_CONTINUE;
} // end update
//-------------------------------------------------------------------------------------------------
/** Dozer action state */
//-------------------------------------------------------------------------------------------------
class DozerActionState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(DozerActionState, "DozerActionState")
public:
DozerActionState( StateMachine *machine, DozerTask task ) : State( machine, "DozerActionState" )
{
m_task = task;
m_actionMachine = newInstance(DozerActionStateMachine)( getMachineOwner(), task );
m_actionMachine->initDefaultState();
}
/*
Note that we DON'T use CONVERT_SLEEP_TO_CONTINUE; since we're not doing anything else
interesting in update, we can sleep when this machine sleeps
*/
virtual StateReturnType update( void ) { return m_actionMachine->updateStateMachine(); }
virtual StateReturnType onEnter( void );
virtual void onExit( StateExitType status );
protected:
// snapshot interface
virtual void crc( Xfer *xfer );
virtual void xfer( Xfer *xfer );
virtual void loadPostProcess();
protected:
DozerTask m_task; // task for this state (and therefore sub-machine too)
StateMachine *m_actionMachine;
};
inline DozerActionState::~DozerActionState( void ) { if (m_actionMachine) m_actionMachine->deleteInstance(); }
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DozerActionState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void DozerActionState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
xfer->xferUser(&m_task, sizeof(m_task));
xfer->xferSnapshot(m_actionMachine);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DozerActionState::loadPostProcess( void )
{
} // end loadPostProcess
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
StateReturnType DozerActionState::onEnter( void )
{
// save this as the current action of the dozer
Object *dozer = getMachineOwner();
if( !dozer->getAIUpdateInterface() )
{
return STATE_FAILURE;
}
DozerAIInterface *dozerAI = dozer->getAIUpdateInterface()->getDozerAIInterface();
dozerAI->setCurrentTask( m_task );
//
// we have a machine within this state all it's own ...
// note that during an onEnter, since the action machine is persistent, anytime we transition
// into doing an action, we need to reset our state machine to do that action as it may
// have been left in any state from the previous time we ran it
//
m_actionMachine->resetToDefaultState();
return STATE_CONTINUE;
} // end onEnter
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void DozerActionState::onExit( StateExitType status )
{
// save the current action of the dozer as none
Object *dozer = getMachineOwner();
if( !dozer->getAIUpdateInterface() )
{
return;
}
DozerAIInterface *dozerAI = dozer->getAIUpdateInterface()->getDozerAIInterface();
dozerAI->setCurrentTask( DOZER_TASK_INVALID );
} // end onExit
//-------------------------------------------------------------------------------------------------
/** Dozer primary going home state */
//-------------------------------------------------------------------------------------------------
class DozerPrimaryGoingHomeState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(DozerPrimaryGoingHomeState, "DozerPrimaryGoingHomeState")
protected:
// snapshot interface STUBBED no member vars
virtual void crc( Xfer *xfer ){};
virtual void xfer( Xfer *xfer ){};
virtual void loadPostProcess(){};
public:
DozerPrimaryGoingHomeState( StateMachine *machine ) : State( machine, "DozerPrimaryGoingHomeState" ) { }
virtual StateReturnType update( void ) { return STATE_FAILURE; }
};
EMPTY_DTOR(DozerPrimaryGoingHomeState)
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
DozerPrimaryStateMachine::DozerPrimaryStateMachine( Object *owner ) : StateMachine( owner, "DozerPrimaryStateMachine" )
{
static const StateConditionInfo idleConditions[] =
{
StateConditionInfo(isBuildMostImportant, DOZER_PRIMARY_BUILD, NULL),
StateConditionInfo(isRepairMostImportant, DOZER_PRIMARY_REPAIR, NULL),
StateConditionInfo(isFortifyMostImportant, DOZER_PRIMARY_FORTIFY, NULL),
StateConditionInfo(NULL, NULL, NULL) // keep last
};
// order matters: first state is the default state.
defineState( DOZER_PRIMARY_IDLE, newInstance(DozerPrimaryIdleState)( this ), INVALID_STATE_ID, INVALID_STATE_ID, idleConditions );
defineState( DOZER_PRIMARY_BUILD, newInstance(DozerActionState)( this, DOZER_TASK_BUILD ), DOZER_PRIMARY_IDLE, DOZER_PRIMARY_IDLE );
defineState( DOZER_PRIMARY_REPAIR, newInstance(DozerActionState)( this, DOZER_TASK_REPAIR ), DOZER_PRIMARY_IDLE, DOZER_PRIMARY_IDLE );
defineState( DOZER_PRIMARY_FORTIFY, newInstance(DozerActionState)( this, DOZER_TASK_FORTIFY ), DOZER_PRIMARY_IDLE, DOZER_PRIMARY_IDLE );
defineState( DOZER_PRIMARY_GO_HOME, newInstance(DozerPrimaryGoingHomeState)( this ), DOZER_PRIMARY_IDLE, DOZER_PRIMARY_IDLE );
} // end DozerPrimaryStateMachine
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
DozerPrimaryStateMachine::~DozerPrimaryStateMachine( void )
{
} // end ~DozerPrimaryStateMachine
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DozerPrimaryStateMachine::crc( Xfer *xfer )
{
StateMachine::crc(xfer);
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void DozerPrimaryStateMachine::xfer( Xfer *xfer )
{
XferVersion cv = 1;
XferVersion v = cv;
xfer->xferVersion( &v, cv );
StateMachine::xfer(xfer);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DozerPrimaryStateMachine::loadPostProcess( void )
{
StateMachine::loadPostProcess();
} // end loadPostProcess
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool DozerPrimaryStateMachine::isBuildMostImportant( State *thisState, void* userData )
{
Object *dozer = thisState->getMachineOwner();
AIUpdateInterface *ai = dozer->getAIUpdateInterface();
if( !ai )
{
return FALSE;
}
DozerAIInterface *dozerAI = ai->getDozerAIInterface();
if( !dozerAI )
{
return FALSE;
}
if( !ai->isIdle() )
return FALSE; // busy doing something else
// if the most important task is us then return true
DozerTask task = dozerAI->getMostRecentCommand();
return task == DOZER_TASK_BUILD;
} // end isBuildMostImportant
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool DozerPrimaryStateMachine::isRepairMostImportant( State *thisState, void* userData )
{
Object *dozer = thisState->getMachineOwner();
AIUpdateInterface *ai = dozer->getAIUpdateInterface();
if( !ai )
{
return false;
}
DozerAIInterface *dozerAI = ai->getDozerAIInterface();
if( !dozerAI )
{
return false;
}
if( !ai->isIdle() )
return FALSE; // busy doing something else
// if the most important task is us then return true
DozerTask task = dozerAI->getMostRecentCommand();
return task == DOZER_TASK_REPAIR;
} // end isRepairMostImportant
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool DozerPrimaryStateMachine::isFortifyMostImportant( State *thisState, void* userData )
{
Object *dozer = thisState->getMachineOwner();
AIUpdateInterface *ai = dozer->getAIUpdateInterface();
if( !ai )
{
return false;
}
DozerAIInterface *dozerAI = ai->getDozerAIInterface();
if( !dozerAI )
{
return false;
}
if( !ai->isIdle() )
return FALSE; // busy doing something else
// if the most important task is us then return true
DozerTask task = dozerAI->getMostRecentCommand();
return task == DOZER_TASK_FORTIFY;
} // end isFortifyMostImportat
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DozerAIUpdateModuleData::DozerAIUpdateModuleData( void )
{
m_repairHealthPercentPerSecond = 0.0f;
m_boredTime = 0.0f;
m_boredRange = 0.0f;
} // end DozerAIUpdateModuleData
// ------------------------------------------------------------------------------------------------
void DozerAIUpdateModuleData::buildFieldParse( MultiIniFieldParse& p)
{
AIUpdateModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "RepairHealthPercentPerSecond", INI::parsePercentToReal, NULL, offsetof( DozerAIUpdateModuleData, m_repairHealthPercentPerSecond ) },
{ "BoredTime", INI::parseDurationReal, NULL, offsetof( DozerAIUpdateModuleData, m_boredTime ) },
{ "BoredRange", INI::parseReal, NULL, offsetof( DozerAIUpdateModuleData, m_boredRange ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
DozerAIUpdate::DozerAIUpdate( Thing *thing, const ModuleData* moduleData ) :
AIUpdateInterface( thing, moduleData )
{
Int i, j;
for( i = 0; i < DOZER_NUM_TASKS; i++ )
{
m_task[ i ].m_targetObjectID = INVALID_ID;
m_task[ i ].m_taskOrderFrame = 0;
for( j = 0; j < DOZER_NUM_DOCK_POINTS; j++ )
{
m_dockPoint[ i ][ j ].valid = FALSE;
m_dockPoint[ i ][ j ].location.zero();
} // end for j
} // end for i
m_currentTask = DOZER_TASK_INVALID;
m_buildSubTask = DOZER_SELECT_BUILD_DOCK_LOCATION; // irrelavant, but I want non-garbage value
//
// initialize the dozer machine to NULL, we want to do this and create it during the update
// implementation because at this point we don't have the object all setup
//
m_dozerMachine = NULL;
createMachines();
} // end DozerAIUpdate
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
DozerAIUpdate::~DozerAIUpdate( void )
{
// delete our behavior state machine
if( m_dozerMachine )
m_dozerMachine->deleteInstance();
// no orders
for( Int i = 0; i < DOZER_NUM_TASKS; i++ )
{
m_task[ i ].m_targetObjectID = INVALID_ID;
m_task[ i ].m_taskOrderFrame = 0;
} // end for i
} // end ~DozerAIUpdate
// ------------------------------------------------------------------------------------------------
/** Create any sub machines we need to do all our behavior */
// ------------------------------------------------------------------------------------------------
void DozerAIUpdate::createMachines( void )
{
if( m_dozerMachine == NULL )
{
m_dozerMachine = newInstance(DozerPrimaryStateMachine)( getObject() );
m_dozerMachine->initDefaultState();
} // end if
} // end createMachines
// ------------------------------------------------------------------------------------------------
/** Create the bridge scaffolding if necessary for the bridge that is attached to this tower */
// ------------------------------------------------------------------------------------------------
void DozerAIUpdate::createBridgeScaffolding( Object *bridgeTower )
{
// sanity
if( bridgeTower == NULL )
return;
// get the bridge behavior interface from the bridge object that this tower is a part of
BridgeTowerBehaviorInterface *btbi = BridgeTowerBehavior::getBridgeTowerBehaviorInterfaceFromObject( bridgeTower );
if( btbi == NULL )
return;
Object *bridgeObject = TheGameLogic->findObjectByID( btbi->getBridgeID() );
if( bridgeObject == NULL )
return;
BridgeBehaviorInterface *bbi = BridgeBehavior::getBridgeBehaviorInterfaceFromObject( bridgeObject );
if( bbi == NULL )
return;
// tell the bridge to create scaffolding if necessary
bbi->createScaffolding();
} // end createBridgeScaffolding
// ------------------------------------------------------------------------------------------------
/** Remove the bridge scaffolding from the bridge object that is attached to this tower */
// ------------------------------------------------------------------------------------------------
void DozerAIUpdate::removeBridgeScaffolding( Object *bridgeTower )
{
// sanity
if( bridgeTower == NULL )
return;
// get the bridge behavior interface from the bridge object that this tower is a part of
BridgeTowerBehaviorInterface *btbi = BridgeTowerBehavior::getBridgeTowerBehaviorInterfaceFromObject( bridgeTower );
if( btbi == NULL )
return;
Object *bridgeObject = TheGameLogic->findObjectByID( btbi->getBridgeID() );
if( bridgeObject == NULL )
return;
BridgeBehaviorInterface *bbi = BridgeBehavior::getBridgeBehaviorInterfaceFromObject( bridgeObject );
if( bbi == NULL )
return;
// tell the bridge to end any scaffolding from repairing
bbi->removeScaffolding();
} // end removeBridgeScaffolding
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpdateSleepTime DozerAIUpdate::update( void )
{
//
// NOTE: Any changes to DozerAIUpdate::* you probably want to reflect and copy into
// WorkerAIUPdate:* as well ... sigh
//
//
// now that we're really executing we have all the necessary object modules in place to
// correctly create a state machine and set the default state
//
createMachines();
// set us as being to able to move with super precision off grid locations
/*
if( getCurLocomotor() ) {
getCurLocomotor()->setUltraAccurate( TRUE );
getCurLocomotor()->setAllowInvalidPosition(TRUE);
}*/
// extend the normal AI system
UpdateSleepTime result;
result = AIUpdateInterface::update();
// do nothing if we're dead
///@todo shouldn't this be at a higher level?
if( getObject()->isEffectivelyDead() )
return UPDATE_SLEEP_NONE;
// get and validate our current task
DozerTask currentTask = getCurrentTask();
if( currentTask != DOZER_TASK_INVALID )
{
ObjectID taskTarget = getTaskTarget( currentTask );
Object *targetObject = TheGameLogic->findObjectByID( taskTarget );
Bool invalidTask = FALSE;
// validate the task and the target
if( currentTask == DOZER_TASK_REPAIR &&
TheActionManager->canRepairObject( getObject(), targetObject, getLastCommandSource() ) == FALSE )
invalidTask = TRUE;
// cancel the task if it's now invalid
if( invalidTask == TRUE )
cancelTask( currentTask );
} // end if
else
getObject()->setWeaponSetFlag(WEAPONSET_MINE_CLEARING_DETAIL);//maybe go clear some mines, if I feel like it
// run our own state machine
m_dozerMachine->updateStateMachine();
return UPDATE_SLEEP_NONE;
} // end update
//-------------------------------------------------------------------------------------------------
/** The entry point of a construct command to the Dozer */
//-------------------------------------------------------------------------------------------------
Object *DozerAIUpdate::construct( const ThingTemplate *what,
const Coord3D *pos,
Real angle,
Player *owningPlayer,
Bool isRebuild )
{
// !!! NOTE: If you modify this you must modify the worker too !!!
// !!! Graham: Please please please have inspiration for how to *not* duplicate this code
// create our machines if they don't yet exist
///@todo make 'construct' a real AI command and you won't need a special case
m_isRebuild = isRebuild;
createMachines();
// sanity
if( what == NULL || pos == NULL || owningPlayer == NULL )
return NULL;
// sanity
DEBUG_ASSERTCRASH( getObject()->getControllingPlayer() == owningPlayer,
("Dozer::Construct - The controlling player of the Dozer is not the owning player passed in\n") );
// if we're not rebuilding, we have a few checks to pass first for sanity
if( isRebuild == FALSE )
{
// AI has weaker restriction on building
Bool dozerIsAI = owningPlayer->getPlayerType() == PLAYER_COMPUTER;
if( dozerIsAI )
{
// Just build it. The ai will validate, or cheat. jba.
} // end if
else
{
// make sure the player is capable of building this
if( TheBuildAssistant->canMakeUnit( getObject(), what ) != CANMAKE_OK)
return NULL;
// validate the the position to build at is valid
if( TheBuildAssistant->isLocationLegalToBuild( pos, what, angle,
BuildAssistant::TERRAIN_RESTRICTIONS |
BuildAssistant::CLEAR_PATH |
BuildAssistant::NO_OBJECT_OVERLAP |
BuildAssistant::SHROUD_REVEALED,
getObject(), NULL ) != LBC_OK )
return NULL;
} // end else
} // end if
//
// what will our initial status bits be, it is important to do this early
// before the hooks add/subtract power from a player are executed
//
ObjectStatusMaskType statusBits = MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNDER_CONSTRUCTION );
if( isRebuild )
statusBits.set( OBJECT_STATUS_RECONSTRUCTING );
// create an object at the destination location
Object *obj = TheThingFactory->newObject( what, owningPlayer->getDefaultTeam(), statusBits );
// even though we haven't actually built anything yet, this keeps things tidy
obj->setProducer( getObject() );
obj->setBuilder( getObject() );
// take the required money away from the player
if( isRebuild == FALSE )
{
Money *money = owningPlayer->getMoney();
money->withdraw( what->calcCostToBuild( owningPlayer ) );
} // end if
// initialize object
obj->setPosition( pos );
obj->setOrientation( angle );
// Flatten the terrain underneath the object, then adjust to the flattened height. jba.
TheTerrainLogic->flattenTerrain(obj);
Coord3D adjustedPos = *pos;
adjustedPos.z = TheTerrainLogic->getGroundHeight(pos->x, pos->y);
obj->setPosition(&adjustedPos);
// Note - very important that we add to map AFTER we flatten terrain. jba.
TheAI->pathfinder()->addObjectToPathfindMap( obj );
// "callback" event for structure created (note that it's not yet "complete")
owningPlayer->onStructureCreated( getObject(), obj );
// set a construction percent for the new object to zero and a status for under construction
obj->setConstructionPercent( 0.0 );
// newly constructed objects start at one hit point
BodyModuleInterface *body = obj->getBodyModule();
body->internalChangeHealth( -body->getHealth() + 1.0f );
// set the model action state to awaiting construction
obj->clearAndSetModelConditionFlags(
MAKE_MODELCONDITION_MASK2(MODELCONDITION_PARTIALLY_CONSTRUCTED, MODELCONDITION_ACTIVELY_BEING_CONSTRUCTED),
MAKE_MODELCONDITION_MASK(MODELCONDITION_AWAITING_CONSTRUCTION)
);
// we have a construction pending
newTask( DOZER_TASK_BUILD, obj );
return obj;
} // end construct
// ------------------------------------------------------------------------------------------------
/** Given our current task and repair target, can we accept this as a new repair target */
// ------------------------------------------------------------------------------------------------
Bool DozerAIUpdate::canAcceptNewRepair( Object *obj )
{
// sanity
if( obj == NULL )
return FALSE;
// if we're not repairing right now, we don't have any accept restrictions
if( getCurrentTask() != DOZER_TASK_REPAIR )
return TRUE;
// get current repair target
Object *currentRepair = TheGameLogic->findObjectByID( m_task[ DOZER_TASK_REPAIR ].m_targetObjectID );
if( currentRepair )
{
// check for same object
if( currentRepair == obj )
return FALSE;
// check for repairing any tower on the same bridge
if( currentRepair->isKindOf( KINDOF_BRIDGE_TOWER ) &&
obj->isKindOf( KINDOF_BRIDGE_TOWER ) )
{
BridgeTowerBehaviorInterface *currentTowerInterface = NULL;
BridgeTowerBehaviorInterface *newTowerInterface = NULL;
currentTowerInterface = BridgeTowerBehavior::getBridgeTowerBehaviorInterfaceFromObject( currentRepair );
newTowerInterface = BridgeTowerBehavior::getBridgeTowerBehaviorInterfaceFromObject( obj );
// sanity
if( currentTowerInterface == NULL || newTowerInterface == NULL )
{
DEBUG_CRASH(( "Unable to find bridge tower interface on object\n" ));
return FALSE;
} // end if
// if they are part of the same bridge, ignore this repair command
if( currentTowerInterface->getBridgeID() == newTowerInterface->getBridgeID() )
return FALSE;
} // end if
} // end if, currentRepair object exists
// all is well
return TRUE;
} // end canAcceptNewRepair
// ------------------------------------------------------------------------------------------------
/** Issue an order for the Dozer to go repair the target 'obj' */
// ------------------------------------------------------------------------------------------------
void DozerAIUpdate::privateRepair( Object *obj, CommandSourceType cmdSource )
{
Object *dozer = getObject();
// if we are already repairing this target do nothing
if( canAcceptNewRepair( obj ) == FALSE )
return;
// sanity, if we can't repair the object then get out of there
if( TheActionManager->canRepairObject( dozer, obj, cmdSource ) == FALSE )
return;
// if this object is already actively being repaired by another dozertype we won't also try to go repair it
ObjectID currentRepairer = obj->getSoleHealingBenefactor();
if( currentRepairer != INVALID_ID && currentRepairer != dozer->getID() )
return;
// Bridges have been made indestructible, so these checks were in vain. -- ML
// if this object is a bridge tower, we need to check the status of the bridge in order
// to check for a 'duplicate repair'
//
//if( obj->isKindOf( KINDOF_BRIDGE_TOWER ) )
//{
// BridgeTowerBehaviorInterface *btbi = BridgeTowerBehavior::getBridgeTowerBehaviorInterfaceFromObject( obj );
// DEBUG_ASSERTCRASH( btbi, ("Unable to find bridge tower behavior interface\n") );
//
// Object *bridge = TheGameLogic->findObjectByID( btbi->getBridgeID() );
// DEBUG_ASSERTCRASH( bridge, ("Unable to find bridge object\n") );
// if( BitTest( bridge->getStatusBits(), OBJECT_STATUS_UNDERGOING_REPAIR ) == TRUE )
// return;
//
//} // end if
// for bridges, set the status for the bridge object
//if( obj->isKindOf( KINDOF_BRIDGE_TOWER ) )
//{
// BridgeTowerBehaviorInterface *btbi = BridgeTowerBehavior::getBridgeTowerBehaviorInterfaceFromObject( obj );
// DEBUG_ASSERTCRASH( btbi, ("Unable to find bridge tower behavior interface\n") );
//
// Object *bridge = TheGameLogic->findObjectByID( btbi->getBridgeID() );
// DEBUG_ASSERTCRASH( bridge, ("Unable to find bridge object\n") );
// bridge->setStatus( OBJECT_STATUS_UNDERGOING_REPAIR );
//
//} // end if
// start the new task
newTask( DOZER_TASK_REPAIR, obj );
} // end repair
// ------------------------------------------------------------------------------------------------
/** Resume construction on a building */
// ------------------------------------------------------------------------------------------------
void DozerAIUpdate::privateResumeConstruction( Object *obj, CommandSourceType cmdSource )
{
// sanity
if( obj == NULL )
return;
// make sure we can resume construction on this
if( TheActionManager->canResumeConstructionOf( getObject(), obj, cmdSource ) == FALSE )
return;
// start the new task for construction
newTask( DOZER_TASK_BUILD, obj );
} // end privateResumeConstruction
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/*static*/ Bool DozerAIUpdate::findGoodBuildOrRepairPosition(const Object* me, const Object* target, Coord3D& positionOut)
{
// The place we go to build or repair is the closest spot from us to them
Coord3D ourPosition = *me->getPosition();
Coord3D theirPosition = *target->getPosition();
Coord3D bestPosition = theirPosition;// This answer is the best, as it includes findPositionAround
Coord3D workingPosition = theirPosition;// But if findPositionAround fails, we need to say something.
Vector3 offset( ourPosition.x - theirPosition.x,
ourPosition.y - theirPosition.y,
ourPosition.z - theirPosition.z );
offset.Normalize();
// This scaler makes FindPositionAround bias towards our side
offset = offset * (target->getGeometryInfo().getMajorRadius() / 2);
workingPosition.x += offset.X;
workingPosition.y += offset.Y;
workingPosition.z += offset.Z;
// this is a little cheesy... the idea is that we can only choose a location that is pretty close
// in z to the desired one. this prevents us from choosing a space at the bottom of a cliff when
// the space we want is at the top of the cliff. ideally we should do a funky terrain-zone compare
// but that isn't well-exposed... (srj)
const Real MAX_Z_DELTA = 10.0f;
FindPositionOptions fpOptions;
fpOptions.minRadius = 0.0f;
fpOptions.maxRadius = 100.0f;
fpOptions.sourceToPathToDest = me;// This makes it find a place forWhom can get to.
if (!me->isUsingAirborneLocomotor())
fpOptions.maxZDelta = MAX_Z_DELTA;
if( me->isUsingAirborneLocomotor() )
fpOptions.ignoreObject = target;// Flyers can ignore stuff, so they can approach right over the target if they want.
Bool spotFound = ThePartitionManager->findPositionAround( &workingPosition, &fpOptions, &bestPosition );
positionOut = spotFound ? bestPosition : workingPosition;
return spotFound;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/*static*/ Object* DozerAIUpdate::findGoodBuildOrRepairPositionAndTarget(Object* me, Object* target, Coord3D& positionOut)
{
if (target->isKindOf(KINDOF_BRIDGE))
{
BridgeBehaviorInterface *bbi = BridgeBehavior::getBridgeBehaviorInterfaceFromObject(target);
if (bbi)
{
AIUpdateInterface* ai = me->getAI();
// have to repair at a tower.
Real bestDistSqr = 1e10f;
Object* bestTower = NULL;
for (Int i = 0; i < BRIDGE_MAX_TOWERS; ++i)
{
Object* tower = TheGameLogic->findObjectByID(bbi->getTowerID((BridgeTowerType)i));
if( tower )
{
Coord3D tmp;
Bool found = findGoodBuildOrRepairPosition(me, tower, tmp);
// do isPathAvail against the result of this, NOT the tower pos,
// since towers are often in cliff cells.
if (found && ai->isPathAvailable(&tmp))
{
Real thisDistSqr = sqr(me->getPosition()->x - tmp.x) + sqr(me->getPosition()->y - tmp.y);
if (thisDistSqr < bestDistSqr)
{
positionOut = tmp;
bestDistSqr = thisDistSqr;
bestTower = tower;
}
}
}
}
if (bestTower)
return bestTower;
DEBUG_CRASH(("should not happen, no reachable tower found"));
return NULL;
}
}
findGoodBuildOrRepairPosition(me, target, positionOut);
return target;
}
//-------------------------------------------------------------------------------------------------
/** Issue and order to the dozer */
//-------------------------------------------------------------------------------------------------
void DozerAIUpdate::newTask( DozerTask task, Object *target )
{
// sanity
DEBUG_ASSERTCRASH( task >= 0 && task < DOZER_NUM_TASKS, ("Illegal dozer task '%d'\n", task) );
// sanity
if( target == NULL )
return;
//
// special check for the build task, we should never be given more than one of them ...
// for the other tasks we just forget what we were doing and the new target takes
// precedence for the task
//
if( task == DOZER_TASK_BUILD || task == DOZER_TASK_REPAIR )
{
// handle getting two tasks
if( isTaskPending( task ) == TRUE )
cancelTask( task );
// get our object
Object *me = getObject();
Coord3D position;
target = findGoodBuildOrRepairPositionAndTarget(me, target, position);
if (target == NULL)
return; // could happen for some bridges
//
// for building, we say that even "thinking" about building or rebuilding an object
// sets us as the current builder of that object. this allows any dozers that are
// ordered later to resume construction on something to see that somebody is already taking
// care of it and then they won't be even try to resume a build since we don't allow
// multiple dozers/workers to double up on construction efforts
//
if( task == DOZER_TASK_BUILD )
target->setBuilder( me );
m_dockPoint[ task ][ DOZER_DOCK_POINT_START ].valid = TRUE;
m_dockPoint[ task ][ DOZER_DOCK_POINT_START ].location = position;
m_dockPoint[ task ][ DOZER_DOCK_POINT_ACTION ].valid = TRUE;
m_dockPoint[ task ][ DOZER_DOCK_POINT_ACTION ].location = position;
Coord3D offset;
offset.set(position.x-target->getPosition()->x, position.y-target->getPosition()->y, 0);
offset.normalize();
offset.scale(5*PATHFIND_CELL_SIZE_F);
position.add(&offset); // move away from the dock point at the end of build.
m_dockPoint[ task ][ DOZER_DOCK_POINT_END ].valid = TRUE;
m_dockPoint[ task ][ DOZER_DOCK_POINT_END ].location = position;
} // end if
// set the new task target and the frame in which we got this order
m_task[ task ].m_targetObjectID = target->getID();
m_task[ task ].m_taskOrderFrame = TheGameLogic->getFrame();
// reset the dozer behavior so that it can re-evluate which task to continue working on
m_dozerMachine->resetToDefaultState();
} // end newTask
//-------------------------------------------------------------------------------------------------
/** Cancel a task and reset the dozer behavior state machine so that it can
* re-evaluate what it wants to do if it was working on the task being
* cancelled */
//-------------------------------------------------------------------------------------------------
void DozerAIUpdate::cancelTask( DozerTask task )
{
// clear the order
internalCancelTask( task );
// reset the machine to we can re-evaluate what we want to do
m_dozerMachine->resetToDefaultState();
} // end cancelTask
//-------------------------------------------------------------------------------------------------
/** Is there a given task waiting to be done */
//-------------------------------------------------------------------------------------------------
Bool DozerAIUpdate::isTaskPending( DozerTask task )
{
// sanity
DEBUG_ASSERTCRASH( task >= 0 && task < DOZER_NUM_TASKS, ("Illegal dozer task '%d'\n", task) );
return m_task[ task ].m_targetObjectID != 0 ? TRUE : FALSE;
} // end isTaskPending
//-------------------------------------------------------------------------------------------------
/** Is there any task pending */
//-------------------------------------------------------------------------------------------------
Bool DozerAIUpdate::isAnyTaskPending( void )
{
for( Int i = 0; i < DOZER_NUM_TASKS; i++ )
if( isTaskPending( (DozerTask)i ) )
return TRUE;
return FALSE;
} // end isAnyTaskPending
//-------------------------------------------------------------------------------------------------
/** Get the target object of a given task */
//-------------------------------------------------------------------------------------------------
ObjectID DozerAIUpdate::getTaskTarget( DozerTask task )
{
// sanity
DEBUG_ASSERTCRASH( task >= 0 && task < DOZER_NUM_TASKS, ("Illegal dozer task '%d'\n", task) );
return m_task[ task ].m_targetObjectID;
} // end getTaskTarget
//-------------------------------------------------------------------------------------------------
/** Set a task as successfully completed */
//-------------------------------------------------------------------------------------------------
void DozerAIUpdate::internalTaskComplete( DozerTask task )
{
// sanity
DEBUG_ASSERTCRASH( task >= 0 && task < DOZER_NUM_TASKS, ("Illegal dozer task '%d'\n", task) );
// call the single method that gets called for completing and canceling tasks
internalTaskCompleteOrCancelled( task );
// remove the info for this task
m_task[ task ].m_targetObjectID = INVALID_ID;
m_task[ task ].m_taskOrderFrame = 0;
// remove dock point info for this task
for( Int i = 0; i < DOZER_NUM_DOCK_POINTS; i++ )
m_dockPoint[ task ][ i ].valid = FALSE;
} // end internalTaskComplete
//-------------------------------------------------------------------------------------------------
/** Clear a task from the Dozer for consideration, we can use this when a goal object becomes
* invalid/destroyed etc. */
//-------------------------------------------------------------------------------------------------
void DozerAIUpdate::internalCancelTask( DozerTask task )
{
// sanity
DEBUG_ASSERTCRASH( task >= 0 && task < DOZER_NUM_TASKS, ("Illegal dozer task '%d'\n", task) );
if(task < 0 || task >= DOZER_NUM_TASKS)
return; //DAMNIT! You CANNOT assert and then not handle the damn error! The. Code. Must. Not. Crash.
// call the single method that gets called for completing and canceling tasks
internalTaskCompleteOrCancelled( task );
// remove the info for this task
m_task[ task ].m_targetObjectID = INVALID_ID;
m_task[ task ].m_taskOrderFrame = 0;
// remove dock point info for this task
for( Int i = 0; i < DOZER_NUM_DOCK_POINTS; i++ )
m_dockPoint[ task ][ i ].valid = FALSE;
// stop the dozer from moving
AIUpdateInterface *ai = getObject()->getAIUpdateInterface();
ai->aiIdle( CMD_FROM_AI );
} // end internalCancelTask
// ------------------------------------------------------------------------------------------------
/** This method is called whenever a task is completed *OR* cancelled */
// ------------------------------------------------------------------------------------------------
void DozerAIUpdate::internalTaskCompleteOrCancelled( DozerTask task )
{
switch( task )
{
// --------------------------------------------------------------------------------------------
case DOZER_TASK_INVALID:
{
break; // do nothing, this is really no task
} // end invalid
// --------------------------------------------------------------------------------------------
case DOZER_TASK_BUILD:
{
// the builder is no longer actively building something
getObject()->clearModelConditionState( MODELCONDITION_ACTIVELY_CONSTRUCTING );
// And the thing we were working on is no longer being actively built
///@todo This would be correct except that we don't have idle crane animations and it is December.
// Object* goalObject = TheGameLogic->findObjectByID(m_task[task].m_targetObjectID);
// if (goalObject != NULL)
// {
// goalObject->clearModelConditionState(MODELCONDITION_ACTIVELY_BEING_CONSTRUCTED);
// }
break;
} // end build
// --------------------------------------------------------------------------------------------
case DOZER_TASK_REPAIR:
{
// the builder is no longer actively repairing something
getObject()->clearModelConditionState( MODELCONDITION_ACTIVELY_CONSTRUCTING );
// Bridges have been made indestructible, so the code below was meaningless. -- ML
// Object *obj = NULL;
// get object to reapir (if present)
//obj = TheGameLogic->findObjectByID( m_task[ task ].m_targetObjectID );
//
//if( obj )
//{
//
//
// when we're done repairing bridges, tell the scaffolding to go away and also remove
// the undergoing repair status from the bridge object itself
//
// if( obj->isKindOf( KINDOF_BRIDGE_TOWER ) )
// {
// // remove the bridge scaffold
// removeBridgeScaffolding( obj );
//
// // clear the repair bit from the bridge
// BridgeTowerBehaviorInterface *btbi = BridgeTowerBehavior::getBridgeTowerBehaviorInterfaceFromObject( obj );
// DEBUG_ASSERTCRASH( btbi, ("Unable to find bridge tower behavior interface\n") );
// Object *bridge = TheGameLogic->findObjectByID( btbi->getBridgeID() );
// DEBUG_ASSERTCRASH( bridge, ("Unable to find bridge object\n") );
// bridge->clearStatus( OBJECT_STATUS_UNDERGOING_REPAIR );
//
// } // end if
//
//} // end if
break;
} // end repair
// --------------------------------------------------------------------------------------------
case DOZER_TASK_FORTIFY:
{
break;
} // end fortify
// --------------------------------------------------------------------------------------------
default:
{
DEBUG_CRASH(( "internalTaskCompleteOrCancelled: Unknown Dozer task '%d'\n", task ));
break;
} // end default
} // end switch( task )
} // end internalTaskCompleteOrCancelled
//-------------------------------------------------------------------------------------------------
/** If we were building something, kill the active-construction flag on it */
//-------------------------------------------------------------------------------------------------
void DozerAIUpdate::onDelete( void )
{
Int i;
// cancel any of the tasks we had queued up
for( i = DOZER_TASK_FIRST; i < DOZER_NUM_TASKS; ++i )
{
if( isTaskPending( (DozerTask)i ) )
cancelTask( (DozerTask)i );
} // end for i
for( i = 0; i < DOZER_NUM_TASKS; i++ )
{
Object* goalObject = TheGameLogic->findObjectByID(m_task[i].m_targetObjectID);
if (goalObject != NULL)
{
goalObject->clearModelConditionState(MODELCONDITION_ACTIVELY_BEING_CONSTRUCTED);
}
}
}
//-------------------------------------------------------------------------------------------------
/** Get the most recently issued task */
//-------------------------------------------------------------------------------------------------
DozerTask DozerAIUpdate::getMostRecentCommand( void )
{
Int i;
DozerTask mostRecentTask = DOZER_TASK_INVALID;
UnsignedInt mostRecentFrame = 0;
for( i = 0; i < DOZER_NUM_TASKS; i++ )
{
if( isTaskPending( (DozerTask)i ) )
{
if( m_task[ i ].m_taskOrderFrame > mostRecentFrame )
{
mostRecentTask = (DozerTask)i;
mostRecentFrame = m_task[ i ].m_taskOrderFrame;
} // end if
} // end if
} // end for i
return mostRecentTask;
} // end getMostRecentCommand
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
const Coord3D* DozerAIUpdate::getDockPoint( DozerTask task, DozerDockPoint point )
{
// sanity
if( task < 0 || task >= DOZER_NUM_TASKS )
return NULL;
// sanity
if( point < 0 || point >= DOZER_NUM_DOCK_POINTS )
return NULL;
// if the point has been set (is valid) then return it
if( m_dockPoint[ task ][ point ].valid )
return &m_dockPoint[ task ][ point ].location;
// no valid point has been set for this dock point on this task
return NULL;
} // end getDockPoint
// ------------------------------------------------------------------------------------------------
Real DozerAIUpdate::getRepairHealthPerSecond( void ) const
{
return getDozerAIUpdateModuleData()->m_repairHealthPercentPerSecond;
}
// ------------------------------------------------------------------------------------------------
Real DozerAIUpdate::getBoredTime( void ) const
{
return getDozerAIUpdateModuleData()->m_boredTime;
}
// ------------------------------------------------------------------------------------------------
Real DozerAIUpdate::getBoredRange( void ) const
{
if (getObject()->getControllingPlayer() &&
getObject()->getControllingPlayer()->getPlayerType() == PLAYER_COMPUTER) {
return TheAI->getAiData()->m_aiDozerBoredRadiusModifier*getDozerAIUpdateModuleData()->m_boredRange;
}
return getDozerAIUpdateModuleData()->m_boredRange;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
void DozerAIUpdate::aiDoCommand(const AICommandParms* parms)
{
//
// anytime we get a command, just remove any model condition that has us actively building
// if we need to show that, that bit will be set anyway again during the build process
//
getObject()->clearModelConditionState( MODELCONDITION_ACTIVELY_CONSTRUCTING );
if (!isAllowedToRespondToAiCommands(parms))
return;
// if we haven't made the dozer machine yet, do so now
createMachines();
switch( parms->m_cmd )
{
case AICMD_MOVE_AWAY_FROM_UNIT:
{
Object *otherObj = parms->m_obj;
Bool otherIsDozer = false;
if (otherObj) {
otherIsDozer = otherObj->isKindOf(KINDOF_DOZER);
}
// We only want to do this if we aren't busy doing dozer things. jba.
// Or if the other guy is a dozer too.
if( !otherIsDozer && getCurrentTask() != DOZER_TASK_INVALID ) {
return; // just ignore it. jba.
}
// issue the command
AIUpdateInterface::aiDoCommand(parms);
break;
}
// --------------------------------------------------------------------------------------------
case AICMD_REPAIR:
{
// if we have no task right now, go idle so we can immediately respond to this
if( getCurrentTask() == DOZER_TASK_INVALID )
aiIdle( CMD_FROM_AI );
// do the repair
privateRepair(parms->m_obj, parms->m_cmdSource);
break;
} // end repair
// --------------------------------------------------------------------------------------------
case AICMD_RESUME_CONSTRUCTION:
{
// if we have no task right now, go idle so we can immediately respond to this
if( getCurrentTask() == DOZER_TASK_INVALID )
aiIdle( CMD_FROM_AI );
// do the command
privateResumeConstruction( parms->m_obj, parms->m_cmdSource );
break;
} // end resume construction
// --------------------------------------------------------------------------------------------
default:
{
// if this is from the player, cancel our current task
if( parms->m_cmdSource == CMD_FROM_PLAYER && getCurrentTask() != DOZER_TASK_INVALID )
cancelTask( getCurrentTask() );
// issue the command
AIUpdateInterface::aiDoCommand(parms);
// when a player issues commands, this will cause the dozer to re-evaluate what it's doing
if( parms->m_cmdSource == CMD_FROM_PLAYER )
m_dozerMachine->resetToDefaultState();
break;
} // end default
} // end switch
} // end aiDoCommand
//------------------------------------------------------------------------------------------------
void DozerAIUpdate::startBuildingSound( const AudioEventRTS *sound, ObjectID constructionSiteID )
{
m_buildingSound = *sound;
m_buildingSound.setObjectID( constructionSiteID );
m_buildingSound.setPlayingHandle( TheAudio->addAudioEvent( &m_buildingSound ) );
}
//------------------------------------------------------------------------------------------------
void DozerAIUpdate::finishBuildingSound()
{
TheAudio->removeAudioEvent( m_buildingSound.getPlayingHandle() );
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DozerAIUpdate::crc( Xfer *xfer )
{
// extend base class
AIUpdateInterface::crc(xfer);
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void DozerAIUpdate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
AIUpdateInterface::xfer(xfer);
Int numTasks = DOZER_NUM_TASKS;
xfer->xferInt(&numTasks);
if (numTasks != DOZER_NUM_TASKS) {
DEBUG_CRASH(("DOZER_NUM_TASKS changed unexpectedly."));
throw SC_INVALID_DATA;
}
Int i, j;
for (i=0; ixferObjectID(&m_task[i].m_targetObjectID);
xfer->xferUnsignedInt(&m_task[i].m_taskOrderFrame);
}
xfer->xferSnapshot(m_dozerMachine);
xfer->xferUser(&m_currentTask, sizeof(m_currentTask));
Int dockPoints = DOZER_NUM_DOCK_POINTS;
xfer->xferInt(&dockPoints);
if (dockPoints!=DOZER_NUM_DOCK_POINTS) {
DEBUG_CRASH(("DOZER_NUM_DOCK_POINTS changed unexpectedly."));
throw SC_INVALID_DATA;
}
for (i=0; ixferBool(&m_dockPoint[i][j].valid);
xfer->xferCoord3D(&m_dockPoint[i][j].location);
}
}
xfer->xferUser(&m_buildSubTask, sizeof(m_buildSubTask));
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DozerAIUpdate::loadPostProcess( void )
{
// extend base class
AIUpdateInterface::loadPostProcess();
} // end loadPostProcess