From 42691c014b6b09ceb438bb3aea81965bb9061550 Mon Sep 17 00:00:00 2001 From: andreasw Date: Wed, 24 Jun 2026 18:43:37 +0200 Subject: [PATCH] fix and safeguards for LocomotorWorksWhenDisabled --- .../Include/GameLogic/Module/AIUpdate.h | 16 ++++++--- .../GameLogic/Object/Update/AIUpdate.cpp | 34 +++++++++++++++++++ .../AIUpdate/AssaultTransportAIUpdate.cpp | 4 +++ .../Update/AIUpdate/CarrierDroneAIUpdate.cpp | 4 +++ .../AIUpdate/DeliverPayloadAIUpdate.cpp | 5 +++ .../Update/AIUpdate/DeployStyleAIUpdate.cpp | 4 +++ .../Object/Update/AIUpdate/DozerAIUpdate.cpp | 3 ++ .../Update/AIUpdate/DroneCarrierAIUpdate.cpp | 4 +++ .../Update/AIUpdate/HackInternetAIUpdate.cpp | 4 +++ .../Object/Update/AIUpdate/JetAIUpdate.cpp | 4 +++ .../Update/AIUpdate/POWTruckAIUpdate.cpp | 3 ++ .../AIUpdate/RailedTransportAIUpdate.cpp | 4 +++ .../Update/AIUpdate/SupplyTruckAIUpdate.cpp | 3 ++ .../Object/Update/AIUpdate/WanderAIUpdate.cpp | 4 +++ .../Object/Update/AIUpdate/WorkerAIUpdate.cpp | 3 ++ 15 files changed, 94 insertions(+), 5 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h index 8bd077b1423..4ab1019f68c 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h @@ -314,11 +314,11 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface virtual AIUpdateInterface* getAIUpdateInterface() { return this; } - // Disabled conditions to process (AI will still process held status) - //virtual DisabledMaskType getDisabledTypesToProcess() const { return MAKE_DISABLED_MASK( DISABLED_HELD ); } - - // We need to process all disabled types to allow Locomotor working while disabled - virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; } + // Disabled conditions to process. By default the AI only processes HELD (so a disabled + // unit's AI fully freezes, as it always did). We additionally process all disabled types + // only when the current locomotor must keep working while disabled, so it can maintain + // position. (Implemented in the .cpp because it needs the full Locomotor definition.) + virtual DisabledMaskType getDisabledTypesToProcess() const; // Some very specific, complex behaviors are used by more than one AIUpdate. Here are their interfaces. virtual DozerAIInterface* getDozerAIInterface() {return nullptr;} @@ -649,6 +649,12 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface virtual UpdateSleepTime doLocomotor(); // virtual so subclasses can override virtual void chooseGoodLocomotorFromCurrentSet(); + // True when a disable type should suspend AI logic. We still run while disabled + // (DISABLEDMASK_ALL) so the locomotor can maintain position, but all other AI logic + // (state machine, attacking, pathfinding, economy) must be frozen. HELD never suspends, + // and death is handled separately by the normal update path. + Bool isAiSuspendedByDisable() const; + void setLastCommandSource(CommandSourceType source); // subclasses may want to override this, to use a subclass of AIStateMachine. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index ecfe20c786a..2b988b36e16 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -1154,6 +1154,16 @@ UpdateSleepTime AIUpdateInterface::update( void ) m_isInUpdate = TRUE; + // While disabled (other than HELD/dead), suspend all AI logic and only let the locomotor + // run, so locos flagged LocomotorWorksWhenDisabled can maintain position. This restores the + // pre-DISABLEDMASK_ALL behavior where a disabled AI module's update() was not called at all. + if (isAiSuspendedByDisable()) + { + doLocomotor(); // self-gates: does nothing unless the loco works while disabled / maintains position + m_isInUpdate = FALSE; + return UPDATE_SLEEP_NONE; // poll each frame so we resume full AI when the disable clears + } + m_completedWaypoint = nullptr; // Reset so state machine update can set it if we just completed the path. // assume we can sleep forever, unless the state machine (or turret, etc) demand otherwise @@ -2263,6 +2273,30 @@ Bool AIUpdateInterface::isValidLocomotorPosition(const Coord3D* pos) const return TheAI->pathfinder()->validMovementPosition( getObject()->getCrusherLevel()>0, getObject()->getLayer(), m_locomotorSet, getObject()->getRequiredBridgeHeight(), pos ); } +//------------------------------------------------------------------------------------------------- +DisabledMaskType AIUpdateInterface::getDisabledTypesToProcess() const +{ + // Only keep ticking while disabled if the current locomotor must keep working while + // disabled (e.g. to maintain position). Otherwise process HELD only, so the AI fully + // freezes when disabled - matching the original behavior. Note that when no locomotor is + // chosen (buildings and other units that never move) we also freeze; the rare mobile unit + // flagged LocomotorWorksWhenDisabled will have already selected a locomotor before it can + // be disabled in flight. + if (m_curLocomotor != NULL && m_curLocomotor->getLocomotorWorksWhenDisabled()) + return DISABLEDMASK_ALL; + + return MAKE_DISABLED_MASK( DISABLED_HELD ); +} + +//------------------------------------------------------------------------------------------------- +Bool AIUpdateInterface::isAiSuspendedByDisable() const +{ + const Object* obj = getObject(); + return obj->isDisabled() + && !obj->isDisabledByType(DISABLED_HELD) + && !obj->isEffectivelyDead(); +} + //------------------------------------------------------------------------------------------------- DECLARE_PERF_TIMER(doLocomotor) /** diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/AssaultTransportAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/AssaultTransportAIUpdate.cpp index 4ada25186e2..3e1ded3afb3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/AssaultTransportAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/AssaultTransportAIUpdate.cpp @@ -154,6 +154,10 @@ UpdateSleepTime AssaultTransportAIUpdate::update( void ) //return UPDATE_SLEEP_FOREVER; } + // Suspend assault coordination while disabled; only the locomotor runs. + if (isAiSuspendedByDisable()) + return AIUpdateInterface::update(); + //First removing dead members or members that have been ordered to do something outside of this AI. if( m_currentMembers ) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/CarrierDroneAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/CarrierDroneAIUpdate.cpp index 57bfe03d885..bfd028071c8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/CarrierDroneAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/CarrierDroneAIUpdate.cpp @@ -93,6 +93,10 @@ CarrierDroneAIUpdate::~CarrierDroneAIUpdate(void) //------------------------------------------------------------------------------------------------- UpdateSleepTime CarrierDroneAIUpdate::update() { + // Suspend carrier-drone AI while disabled; only the locomotor runs. + if (isAiSuspendedByDisable()) + return AIUpdateInterface::update(); + Object* obj = getObject(); const CarrierDroneAIUpdateModuleData* data = getCarrierDroneAIUpdateModuleData(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp index 648af6297d7..ffb362a2ffb 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp @@ -172,6 +172,11 @@ Bool DeliverPayloadAIUpdate::isAllowedToRespondToAiCommands(const AICommandParms //------------------------------------------------------------------------------------------------- UpdateSleepTime DeliverPayloadAIUpdate::update( void ) { + // While disabled (e.g. DelayDeliveryFrames hold), suspend delivery logic so the + // approach/deliver state machine does not advance while we are frozen in place. + if (isAiSuspendedByDisable()) + return AIUpdateInterface::update(); + m_deliveryDecal.update(); if(!(isAiInDeadState()) && m_deliverPayloadStateMachine) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeployStyleAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeployStyleAIUpdate.cpp index decb0dd2620..e801b0a5c8d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeployStyleAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeployStyleAIUpdate.cpp @@ -94,6 +94,10 @@ void DeployStyleAIUpdate::aiDoCommand( const AICommandParms* parms ) //------------------------------------------------------------------------------------------------- UpdateSleepTime DeployStyleAIUpdate::update( void ) { + // Suspend deploy/undeploy timers while disabled; only the locomotor runs. + if (isAiSuspendedByDisable()) + return AIUpdateInterface::update(); + // have to call our parent's isIdle, because we override it to never return true // when we have a pending command... Object *self = getObject(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp index c9052dabc7e..45b8c0ef26b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp @@ -1561,6 +1561,9 @@ void DozerAIUpdate::removeBridgeScaffolding( Object *bridgeTower ) //------------------------------------------------------------------------------------------------- UpdateSleepTime DozerAIUpdate::update( void ) { + // Suspend dozer tasks (build/repair) while disabled; only the locomotor runs. + if (isAiSuspendedByDisable()) + return AIUpdateInterface::update(); // // NOTE: Any changes to DozerAIUpdate::* you probably want to reflect and copy into diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DroneCarrierAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DroneCarrierAIUpdate.cpp index f58f615db3a..4a1872a013d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DroneCarrierAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DroneCarrierAIUpdate.cpp @@ -655,6 +655,10 @@ void DroneCarrierAIUpdate::aiDoCommand(const AICommandParms* parms) UpdateSleepTime DroneCarrierAIUpdate::update() { + // Suspend drone-carrier management while disabled; only the locomotor runs. + if (isAiSuspendedByDisable()) + return AIUpdateInterface::update(); + if (!getObject()->isEffectivelyDead()) { const DroneCarrierAIUpdateModuleData* data = getDroneCarrierAIUpdateModuleData(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp index 97e28944458..4ddc74b580a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp @@ -100,6 +100,10 @@ Bool HackInternetAIUpdate::isHackingPackingOrUnpacking() const //------------------------------------------------------------------------------------------------- UpdateSleepTime HackInternetAIUpdate::update( void ) { + // Suspend hacking (and pending-command handling) while disabled; only the locomotor runs. + if (isAiSuspendedByDisable()) + return AIUpdateInterface::update(); + // have to call our parent's isIdle, because we override it to never return true // when we have a pending command... if( AIUpdateInterface::isIdle() ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp index a16bfa93540..29bae62973c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp @@ -2433,6 +2433,10 @@ void JetAIUpdate::getProducerLocation() //------------------------------------------------------------------------------------------------- UpdateSleepTime JetAIUpdate::update() { + // Suspend jet AI (taxi/takeoff/landing/ammo logic) while disabled; only the locomotor runs. + if (isAiSuspendedByDisable()) + return AIUpdateInterface::update(); + const JetAIUpdateModuleData* d = getJetAIUpdateModuleData(); getProducerLocation(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp index 6950ab00109..20985a604cc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp @@ -168,6 +168,9 @@ void POWTruckAIUpdate::aiDoCommand( const AICommandParms *parms ) //------------------------------------------------------------------------------------------------- UpdateSleepTime POWTruckAIUpdate::update( void ) { + // Suspend POW collection tasks while disabled; only the locomotor runs. + if (isAiSuspendedByDisable()) + return AIUpdateInterface::update(); // we are ultra accurate if( getCurLocomotor() ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/RailedTransportAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/RailedTransportAIUpdate.cpp index 86951f5455a..bac59485049 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/RailedTransportAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/RailedTransportAIUpdate.cpp @@ -191,6 +191,10 @@ UpdateSleepTime RailedTransportAIUpdate::update( void ) { Object *us = getObject(); + // Suspend railed-transport pathing while disabled; only the locomotor runs. + if (isAiSuspendedByDisable()) + return AIUpdateInterface::update(); + // load the waypoint data if not loaded if( m_waypointDataLoaded == FALSE ) loadWaypointData(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/SupplyTruckAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/SupplyTruckAIUpdate.cpp index ce7a8cc5af3..380ddb06ff1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/SupplyTruckAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/SupplyTruckAIUpdate.cpp @@ -96,6 +96,9 @@ SupplyTruckAIUpdate::~SupplyTruckAIUpdate( void ) //------------------------------------------------------------------------------------------------- UpdateSleepTime SupplyTruckAIUpdate::update( void ) { + // Suspend supply ferrying while disabled (e.g. EMP/hacked); only the locomotor runs. + if (isAiSuspendedByDisable()) + return AIUpdateInterface::update(); StateReturnType stRet = m_supplyTruckStateMachine->updateStateMachine(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/WanderAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/WanderAIUpdate.cpp index c27faef256d..030653907d3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/WanderAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/WanderAIUpdate.cpp @@ -52,6 +52,10 @@ WanderAIUpdate::~WanderAIUpdate( void ) //------------------------------------------------------------------------------------------------- UpdateSleepTime WanderAIUpdate::update( void ) { + // Suspend wandering while disabled; only the locomotor runs. + if (isAiSuspendedByDisable()) + return AIUpdateInterface::update(); + // If I'm standing still, move somewhere if (isIdle()) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/WorkerAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/WorkerAIUpdate.cpp index 4d0eb5cc2a9..45bb879462c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/WorkerAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/WorkerAIUpdate.cpp @@ -235,6 +235,9 @@ Real WorkerAIUpdate::getWarehouseScanDistance() const //------------------------------------------------------------------------------------------------- UpdateSleepTime WorkerAIUpdate::update( void ) { + // Suspend worker tasks (build/repair/supply) while disabled; only the locomotor runs. + if (isAiSuspendedByDisable()) + return AIUpdateInterface::update(); // // NOTE: Any changes to DozerAIUpdate::* you probably want to reflect and copy into