/*
** 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 .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: WorkerAIUpdate.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, June 2002
// Desc: A Worker is a unit that is both a Dozer and a Supply Truck.
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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/GameState.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/GlobalData.h"
#include "Common/ResourceGatheringManager.h"
#include "GameClient/Drawable.h"
#include "GameClient/GameText.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/Locomotor.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/BridgeBehavior.h"
#include "GameLogic/Module/BridgeTowerBehavior.h"
#include "GameLogic/Module/CreateModule.h"
#include "GameLogic/Module/SupplyTruckAIUpdate.h"
#include "GameLogic/Module/SupplyCenterDockUpdate.h"
#include "GameLogic/Module/SupplyWarehouseDockUpdate.h"
#include "GameLogic/Module/WorkerAIUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// FORWARD DECLARATIONS ///////////////////////////////////////////////////////////////////////////
enum
{
AS_DOZER, ///< When not actively Gathering, or when actively building, I am a dozer
AS_SUPPLY_TRUCK ///< When told explicitly by player or other object, I become a supply truck
};
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
WorkerAIUpdate::WorkerAIUpdate( Thing *thing, const ModuleData* moduleData ) :
AIUpdateInterface( thing, moduleData )
{
//
// 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
//
//Added By Sadullah Nader
//Initialization(s) inserted
m_isRebuild = FALSE;
//
m_dozerMachine = NULL;
for( Int i = 0; i < DOZER_NUM_TASKS; i++ )
{
m_task[ i ].m_targetObjectID = INVALID_ID;
m_task[ i ].m_taskOrderFrame = 0;
for( Int j = 0; j < DOZER_NUM_DOCK_POINTS; j++ )
{
m_dockPoint[ i ][ j ].valid = FALSE;
m_dockPoint[ i ][ j ].location.zero();
}
}
m_currentTask = DOZER_TASK_INVALID;
m_buildSubTask = DOZER_SELECT_BUILD_DOCK_LOCATION; // irrelavant, but I want non-garbage value
m_supplyTruckStateMachine = NULL;
m_numberBoxes = 0;
m_forcePending = FALSE;
m_forcedBusyPending = FALSE;
m_workerMachine = NULL;
m_suppliesDepletedVoice = getWorkerAIUpdateModuleData()->m_suppliesDepletedVoice;
createMachines();
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
WorkerAIUpdate::~WorkerAIUpdate( void )
{
// delete our behavior state machine
if( m_dozerMachine )
m_dozerMachine->deleteInstance();
if( m_supplyTruckStateMachine )
m_supplyTruckStateMachine->deleteInstance();
if( m_workerMachine )
m_workerMachine->deleteInstance();
}
//-------------------------------------------------------------------------------------------------
Bool WorkerAIUpdate::isCurrentlyFerryingSupplies() const
{
if (m_supplyTruckStateMachine)
{
switch (m_supplyTruckStateMachine->getCurrentStateID())
{
case ST_IDLE:
case ST_BUSY:
case ST_REGROUPING:
return false;
case ST_WANTING:
case ST_DOCKING:
return true;
}
}
return false;
}
//-------------------------------------------------------------------------------------------------
Bool WorkerAIUpdate::isAvailableForSupplying() const
{
return true;
}
// ------------------------------------------------------------------------------------------------
Real WorkerAIUpdate::getRepairHealthPerSecond( void ) const
{
return getWorkerAIUpdateModuleData()->m_repairHealthPercentPerSecond;
}
// ------------------------------------------------------------------------------------------------
Real WorkerAIUpdate::getBoredTime( void ) const
{
return getWorkerAIUpdateModuleData()->m_boredTime;
}
// ------------------------------------------------------------------------------------------------
Real WorkerAIUpdate::getBoredRange( void ) const
{
return getWorkerAIUpdateModuleData()->m_boredRange;
}
// ------------------------------------------------------------------------------------------------
void WorkerAIUpdate::createMachines( void )
{
if( m_workerMachine == NULL )
{
m_workerMachine = newInstance(WorkerStateMachine)( getObject() );
if( m_dozerMachine == NULL )
{
m_dozerMachine = newInstance(DozerPrimaryStateMachine)( getObject() );
m_dozerMachine->initDefaultState();
}
if( m_supplyTruckStateMachine == NULL )
{
m_supplyTruckStateMachine = newInstance(SupplyTruckStateMachine)( getObject() );
m_supplyTruckStateMachine->initDefaultState();
}
m_workerMachine->initDefaultState();// this has to wait until all three are in place since
// an immediate transition check will ask questions of the machines.
//#ifdef _DEBUG
// m_workerMachine->setDebugOutput(TRUE);
// m_dozerMachine->setDebugOutput(TRUE);
// m_supplyTruckStateMachine->setDebugOutput(TRUE);
//#endif
}
}
//-------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------
UnsignedInt WorkerAIUpdate::getActionDelayForDock( Object *dock )
{
// Decide whether to use my Center or Warehouse delay time
static const NameKeyType key_warehouseUpdate = NAMEKEY("SupplyWarehouseDockUpdate");
SupplyWarehouseDockUpdate *warehouseModule = (SupplyWarehouseDockUpdate*) dock->findUpdateModule( key_warehouseUpdate );
if (warehouseModule) {
return getWorkerAIUpdateModuleData()->m_warehouseDelay;
}
static const NameKeyType key_centerUpdate = NAMEKEY("SupplyCenterDockUpdate");
SupplyCenterDockUpdate *centerModule = (SupplyCenterDockUpdate*) dock->findUpdateModule( key_centerUpdate );
if (centerModule) {
return getWorkerAIUpdateModuleData()->m_centerDelay;
}
return 0;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Real WorkerAIUpdate::getWarehouseScanDistance() const
{
// Ai players get larger scan range. jba.
if (getObject()->getControllingPlayer()->getPlayerType() == PLAYER_COMPUTER) {
return 2 * getWorkerAIUpdateModuleData()->m_warehouseScanDistance;
}
return getWorkerAIUpdateModuleData()->m_warehouseScanDistance;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpdateSleepTime WorkerAIUpdate::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
//
// create all the machines if they don't yet exist
createMachines();
// DO NOT set us as being to able to move with super precision off grid locations
// Causes workers to get stuck. jba.
//if( getCurLocomotor() )
//getCurLocomotor()->setUltraAccurate( TRUE );
// extend the normal AI system
AIUpdateInterface::update();
// do nothing if we're dead
///@todo shouldn't this be at a higher level?
if( getObject()->isEffectivelyDead() )
return UPDATE_SLEEP_NONE;
// run our own state machine, and the appropriate sub machine
m_workerMachine->updateStateMachine();
if( m_workerMachine->getCurrentStateID() == AS_DOZER )
{
// 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
// update dozer behavior
m_dozerMachine->updateStateMachine();
} // end if
else
{
m_supplyTruckStateMachine->updateStateMachine();
// If we are harvesting, we can be diverted to clear mines. jba.
getObject()->setWeaponSetFlag(WEAPONSET_MINE_CLEARING_DETAIL);//maybe go clear some mines, if I feel like it
}
return UPDATE_SLEEP_NONE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/** The entry point of a construct command to the Dozer */
//-------------------------------------------------------------------------------------------------
Object *WorkerAIUpdate::construct( const ThingTemplate *what,
const Coord3D *pos,
Real angle,
Player *owningPlayer,
Bool isRebuild )
{
// !!! NOTE: If you modify this you must modify the dozer too !!!
// !!! Graham: Please please please have inspiration for how to *not* duplicate this code
// GS - Construct needs to be an AI primitive. Inheriting off of AIUpdate means you are writing a
// master brain that will call AI primitives on the object, not something that does stuff itself.
// SupplyTruckAI decides who to call AIDock on. Worker should decide to AIDock or AIConstruct
// or AIRepair. Dozer should just use the latter two. No construction logic should be in
// the inherited AIUpdates at all.
// 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 )
{
// validate the the position to build at is valid
if( TheBuildAssistant->isLocationLegalToBuild( pos, what, angle,
BuildAssistant::CLEAR_PATH |
BuildAssistant::NO_OBJECT_OVERLAP,
getObject(), NULL ) != LBC_OK )
return NULL;
} // 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
UnsignedInt statusBits = OBJECT_STATUS_UNDER_CONSTRUCTION;
if( isRebuild )
BitSet( statusBits, OBJECT_STATUS_RECONSTRUCTING );
// create an object at the destination location
Object *obj = TheThingFactory->newObject( what, owningPlayer->getDefaultTeam(),
(ObjectStatusBits)statusBits );
// even though we haven't actually built anything yet, this keeps things tidy
obj->setProducer( getObject() );
obj->setBuilder( getObject() );
// leave the supply truck state and now behave like a dozer.
exitingSupplyTruckState();
// take the required money away from the player
if( isRebuild == FALSE )
{
Money *money = owningPlayer->getMoney();
money->withdraw( what->calcCostToBuild( owningPlayer ) );
} // end if
//
// set a bit that this object is under construction, it is important to do this early
// before the hooks add/subtract power from a player are executed
//
obj->setStatus( OBJECT_STATUS_UNDER_CONSTRUCTION );
// 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;
}
// ------------------------------------------------------------------------------------------------
/** We just exited from a supply truck task and are now idle, we should go back to Dozer idle
mode */
// ------------------------------------------------------------------------------------------------
void WorkerAIUpdate::exitingSupplyTruckState()
{
if( m_workerMachine->getCurrentStateID() == AS_SUPPLY_TRUCK )
{
// We've been given a Dozer specific order that the Supply Truck machine doesn't recognize
// as BUSY (because this command also recognizes its own busy and is likewise waiting).
// Explicitly slap it upside the head.
if( getObject()->getAIUpdateInterface() )
{
getObject()->getAIUpdateInterface()->aiIdle(CMD_FROM_AI);
}
m_workerMachine->setState( AS_DOZER );
// To clarify, I leave supply truck mode when I notice I am doing something not supply
// truck related. When given a construct command, I wait to do anything until I notice
// I'm not busy. Both states are being polite, so I must force the switch.
}
}
// ------------------------------------------------------------------------------------------------
/** Given our current task and repair target, can we accept this as a new repair target */
// ------------------------------------------------------------------------------------------------
Bool WorkerAIUpdate::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
//----------------------------------------------------------------------------------------
void WorkerAIUpdate::privateIdle(CommandSourceType cmdSource)
{
// Leaving this commented out to show that although the regular supply truck does this, the
// worker's dozer brain will get completely screwed.
// If the user gives a stop command, I have to turn off autopilot
// if( cmdSource == CMD_FROM_PLAYER )
// setForceBusyState(TRUE);
AIUpdateInterface::privateIdle(cmdSource);
}
//----------------------------------------------------------------------------------------
void WorkerAIUpdate::privateDock( Object *dock, CommandSourceType cmdSource )
{
AIUpdateInterface::privateDock( dock, cmdSource );
// If this is a command from a player, I will remember this as my favorite dock to override
// ResourceManager searches.
if ((cmdSource == CMD_FROM_PLAYER) && dock)
{
// Please note, there is not a separate Warehouse and Center memory by Design. Because
// we lack a UI way to click Warehouse and drag to center to set up a specific path, the
// practical realization has been made that you do not want separate memory.
m_preferredDock = dock->getID();
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void WorkerAIUpdate::privateRepair( Object *obj, CommandSourceType cmdSource )
{
Object *dozer = getObject();
// sanity, if we can't repair the object then get out of there
if( TheActionManager->canRepairObject( dozer, obj, cmdSource ) == FALSE )
return;
// if we are already repairing this target do nothing
if( canAcceptNewRepair( obj ) == FALSE )
return;
//
// if this object has already been targeted for repair by an object we won't also try to
// go repair it
ObjectID currentRepairer = obj->getSoleHealingBenefactor();
if( currentRepairer != INVALID_ID && currentRepairer != dozer->getID() )
return;
// start the new task
newTask( DOZER_TASK_REPAIR, obj );
} // end privateRepair
// ------------------------------------------------------------------------------------------------
/** Resume construction on a building */
// ------------------------------------------------------------------------------------------------
void WorkerAIUpdate::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
//-------------------------------------------------------------------------------------------------
/** Issue and order to the dozer */
//-------------------------------------------------------------------------------------------------
void WorkerAIUpdate::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;
m_preferredDock = INVALID_ID; // If we are dozing, we don't want any supply truck stuff going on. jba.
//
// 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 = DozerAIUpdate::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;
m_dockPoint[ task ][ DOZER_DOCK_POINT_END ].valid = TRUE;
m_dockPoint[ task ][ DOZER_DOCK_POINT_END ].location = position;
} // end if, build task
// 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();
// reset the workermachine, if we've been acting like a supply truck
if( m_workerMachine->getCurrentStateID() == AS_SUPPLY_TRUCK )
{
// We've been given a Dozer specific order that the Supply Truck machine doesn't recognize
// as BUSY (because this command also recognizes its own busy and is likewise waiting).
// Explicitly slap it upside the head.
if( getObject()->getAIUpdateInterface() )
{
getObject()->getAIUpdateInterface()->aiIdle(CMD_FROM_AI);
}
m_workerMachine->setState( AS_DOZER );
// To clarify, I leave supply truck mode when I notice I am doing something not supply
// truck related. When given a construct command, I wait to do anything until I notice
// I'm not busy. Both states are being polite, so I must force the switch.
}
}
//-------------------------------------------------------------------------------------------------
/** 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 WorkerAIUpdate::cancelTask( DozerTask task )
{
// clear the order
internalCancelTask( task );
// reset the machine to we can re-evaluate what we want to do
m_dozerMachine->resetToDefaultState();
}
//-------------------------------------------------------------------------------------------------
/** Is there a given task waiting to be done */
//-------------------------------------------------------------------------------------------------
Bool WorkerAIUpdate::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;
}
//-------------------------------------------------------------------------------------------------
/** Is there any task pending */
//-------------------------------------------------------------------------------------------------
Bool WorkerAIUpdate::isAnyTaskPending( void )
{
for( Int i = 0; i < DOZER_NUM_TASKS; i++ )
if( isTaskPending( (DozerTask)i ) )
return TRUE;
return FALSE;
}
//-------------------------------------------------------------------------------------------------
/** Get the target object of a given task */
//-------------------------------------------------------------------------------------------------
ObjectID WorkerAIUpdate::getTaskTarget( DozerTask task )
{
// sanity
DEBUG_ASSERTCRASH( task >= 0 && task < DOZER_NUM_TASKS, ("Illegal dozer task '%d'\n", task) );
return m_task[ task ].m_targetObjectID;
}
//-------------------------------------------------------------------------------------------------
/** Set a task as successfully completed */
//-------------------------------------------------------------------------------------------------
void WorkerAIUpdate::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;
}
//-------------------------------------------------------------------------------------------------
/** Clear a task from the Dozer for consideration, we can use this when a goal object becomes
* invalid/destroyed etc. */
//-------------------------------------------------------------------------------------------------
void WorkerAIUpdate::internalCancelTask( 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;
// stop the dozer from moving
AIUpdateInterface *ai = getObject()->getAIUpdateInterface();
if( !ai )
{
return;
}
/// @todo we really need a stop command instead of making it move to it's current location
ai->aiMoveToPosition( getObject()->getPosition(), CMD_FROM_AI );
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void WorkerAIUpdate::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:
{
Object *obj = NULL;
// the builder is no longer actively repairing something
getObject()->clearModelConditionState( MODELCONDITION_ACTIVELY_CONSTRUCTING );
// 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
if( obj->isKindOf( KINDOF_BRIDGE_TOWER ) )
removeBridgeScaffolding( obj );
} // 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 )
}
//-------------------------------------------------------------------------------------------------
/** If we were building something, kill the active-construction flag on it */
//-------------------------------------------------------------------------------------------------
void WorkerAIUpdate::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 WorkerAIUpdate::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;
}
}
}
return mostRecentTask;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
const Coord3D* WorkerAIUpdate::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
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void WorkerAIUpdate::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;
// create our machines if they don't yet exist
createMachines();
switch( parms->m_cmd )
{
// --------------------------------------------------------------------------------------------
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
if (isClearingMines() && m_numberBoxes > 0 )
{
// if clearing mines, we drop any boxes we were carrying
m_numberBoxes = 0;
Drawable *draw = getObject()->getDrawable();
if( draw )
{
draw->updateDrawableSupplyStatus( getWorkerAIUpdateModuleData()->m_maxBoxesData, m_numberBoxes );
}
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// SupplyTruck stuff
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool WorkerAIUpdate::loseOneBox()
{
if( m_numberBoxes == 0 )
return FALSE;
m_numberBoxes--;
Drawable *draw = getObject()->getDrawable();
if( draw )
{
draw->updateDrawableSupplyStatus( getWorkerAIUpdateModuleData()->m_maxBoxesData, m_numberBoxes );
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool WorkerAIUpdate::gainOneBox( Int remainingStock )
{
if( getWorkerAIUpdateModuleData() && m_numberBoxes >= getWorkerAIUpdateModuleData()->m_maxBoxesData )
return FALSE;
++m_numberBoxes;
//if I just took the last box,
//i will announce that this supply source is now empty
if (remainingStock == 0)
{
Object* bestWarehouse = getObject()->getControllingPlayer()->getResourceGatheringManager()->findBestSupplyWarehouse( getObject() );
Bool playDepleted = FALSE;
if ( bestWarehouse )
{
//figure out whether the best one is considerably far from the previous one (current position)
Coord3D delta = *getObject()->getPosition();
delta.sub( bestWarehouse->getPosition() );
if ( delta.length() > getWarehouseScanDistance()/4)
playDepleted = TRUE;
}
else
playDepleted = TRUE;
if (playDepleted && m_suppliesDepletedVoice.getEventName().isEmpty() == false)
{
m_suppliesDepletedVoice.setObjectID(getObject()->getID());
m_suppliesDepletedVoice.setPlayingHandle(TheAudio->addAudioEvent(&m_suppliesDepletedVoice));
}
}
Drawable *draw = getObject()->getDrawable();
if( draw )
{
draw->updateDrawableSupplyStatus( getWorkerAIUpdateModuleData()->m_maxBoxesData, m_numberBoxes );
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool WorkerAIUpdate::isSupplyTruckBrainActiveAndBusy()
{
return (m_workerMachine->getCurrentStateID() == AS_SUPPLY_TRUCK)
&& (m_supplyTruckStateMachine->getCurrentStateID() == ST_BUSY);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void WorkerAIUpdate::resetSupplyTruckBrain()
{
m_supplyTruckStateMachine->resetToDefaultState();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void WorkerAIUpdate::resetDozerBrain()
{
m_dozerMachine->resetToDefaultState();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// Worker's master state machine, that controls Dozerness or Supplytruckness
class ActAsDozerState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ActAsDozerState, "ActAsDozerState")
protected:
// snapshot interface STUBBED.
virtual void crc( Xfer *xfer ){};
virtual void xfer( Xfer *xfer ){};
virtual void loadPostProcess(){};
public:
ActAsDozerState( StateMachine *machine ) :State( machine, "ActAsDozerState" ){}
virtual StateReturnType onEnter();
virtual StateReturnType update();
virtual StateReturnType onExit();
};
EMPTY_DTOR(ActAsDozerState)
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
class ActAsSupplyTruckState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ActAsSupplyTruckState, "ActAsSupplyTruckState")
protected:
// snapshot interface STUBBED.
virtual void crc( Xfer *xfer ){};
virtual void xfer( Xfer *xfer ){};
virtual void loadPostProcess(){};
public:
ActAsSupplyTruckState( StateMachine *machine ) :State( machine, "ActAsSupplyTruckState" ){}
virtual StateReturnType onEnter();
virtual StateReturnType update();
virtual StateReturnType onExit();
};
EMPTY_DTOR(ActAsSupplyTruckState)
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
WorkerStateMachine::WorkerStateMachine( Object *owner ) : StateMachine( owner, "WorkerStateMachine" )
{
static const StateConditionInfo asDozerConditions[] =
{
StateConditionInfo(supplyTruckSubMachineWantsToEnter, AS_SUPPLY_TRUCK, NULL),
StateConditionInfo(NULL, NULL, NULL) // keep last
};
static const StateConditionInfo asTruckConditions[] =
{
StateConditionInfo(supplyTruckSubMachineReadyToLeave, AS_DOZER, NULL),
StateConditionInfo(NULL, NULL, NULL) // keep last
};
// order matters: first state is the default state.
defineState( AS_DOZER, newInstance(ActAsDozerState)( this ), INVALID_STATE_ID, INVALID_STATE_ID, asDozerConditions );
defineState( AS_SUPPLY_TRUCK, newInstance(ActAsSupplyTruckState)( this ), INVALID_STATE_ID, INVALID_STATE_ID, asTruckConditions );
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
WorkerStateMachine::~WorkerStateMachine()
{
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void WorkerStateMachine::crc( Xfer *xfer )
{
StateMachine::crc(xfer);
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void WorkerStateMachine::xfer( Xfer *xfer )
{
XferVersion cv = 1;
XferVersion v = cv;
xfer->xferVersion( &v, cv );
StateMachine::xfer(xfer);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void WorkerStateMachine::loadPostProcess( void )
{
StateMachine::loadPostProcess();
} // end loadPostProcess
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool WorkerStateMachine::supplyTruckSubMachineWantsToEnter( State *thisState, void* userData )
{
Object *owner = thisState->getMachineOwner();
WorkerAIUpdate *update = (WorkerAIUpdate*)owner->getAIUpdateInterface();
if( !update )
{
return false;
}
AIStateType masterState = update->getAIStateType();
//If I detect a Supply force message, or if I have been put straight in dock,
//then the worker master part of me wants to switch to the Supply sub-brain
return update->isForcedIntoWantingState() || (masterState == AI_DOCK);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool WorkerStateMachine::supplyTruckSubMachineReadyToLeave( State *thisState, void* userData )
{
Object *owner = thisState->getMachineOwner();
WorkerAIUpdate *update = (WorkerAIUpdate*)owner->getAIUpdateInterface();
if( !update )
{
return false;
}
// AIStateType masterState = update->getAIStateType();
// It isn't ready to leave if it is on its way in. The first clause
// allow a latch for a moment
// so there is no transition out on the way in. Active and Busy means it isn't doing
// anything Supply related.
return !supplyTruckSubMachineWantsToEnter( thisState, NULL )
&& update->isSupplyTruckBrainActiveAndBusy();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
StateReturnType ActAsDozerState::onEnter()
{
return STATE_CONTINUE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
StateReturnType ActAsDozerState::update()
{
return STATE_CONTINUE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
StateReturnType ActAsDozerState::onExit()
{
Object *owner = getMachineOwner();
WorkerAIUpdate *update = (WorkerAIUpdate*)owner->getAIUpdateInterface();
if( !update )
{
return STATE_FAILURE;
}
update->resetDozerBrain();
return STATE_CONTINUE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
StateReturnType ActAsSupplyTruckState::onEnter()
{
return STATE_CONTINUE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
StateReturnType ActAsSupplyTruckState::update()
{
return STATE_CONTINUE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
StateReturnType ActAsSupplyTruckState::onExit()
{
Object *owner = getMachineOwner();
WorkerAIUpdate *update = (WorkerAIUpdate*)owner->getAIUpdateInterface();
if( !update )
{
return STATE_FAILURE;
}
update->resetSupplyTruckBrain();
return STATE_CONTINUE;
}
// ------------------------------------------------------------------------------------------------
/** Create the bridge scaffolding if necessary for the bridge that is attached to this tower */
// ------------------------------------------------------------------------------------------------
void WorkerAIUpdate::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 WorkerAIUpdate::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
//------------------------------------------------------------------------------------------------
void WorkerAIUpdate::startBuildingSound( const AudioEventRTS *sound, ObjectID constructionSiteID )
{
m_buildingSound = *sound;
m_buildingSound.setObjectID( constructionSiteID );
m_buildingSound.setPlayingHandle( TheAudio->addAudioEvent( &m_buildingSound ) );
}
//------------------------------------------------------------------------------------------------
void WorkerAIUpdate::finishBuildingSound()
{
TheAudio->removeAudioEvent( m_buildingSound.getPlayingHandle() );
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void WorkerAIUpdate::crc( Xfer *xfer )
{
// extend base class
AIUpdateInterface::crc(xfer);
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void WorkerAIUpdate::xfer( Xfer *xfer )
{
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
AIUpdateInterface::xfer(xfer);
//------------------------- xfer Dozer info
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));
//------------------------- xfer Supply Truck info
xfer->xferSnapshot(m_supplyTruckStateMachine);
xfer->xferObjectID(&m_preferredDock);
xfer->xferInt(&m_numberBoxes);
xfer->xferBool(&m_forcePending);
//-------------------------- xfer Worker info
xfer->xferSnapshot(m_workerMachine);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void WorkerAIUpdate::loadPostProcess( void )
{
// extend base class
AIUpdateInterface::loadPostProcess();
} // end loadPostProcess