/* ** 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