diff --git a/Generals/Code/GameEngine/Include/GameLogic/FiringTracker.h b/Generals/Code/GameEngine/Include/GameLogic/FiringTracker.h index 46637984e5..5c2c4f483d 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/FiringTracker.h +++ b/Generals/Code/GameEngine/Include/GameLogic/FiringTracker.h @@ -55,9 +55,21 @@ class FiringTracker : public UpdateModule void shotFired(const Weapon* weaponFired, ObjectID victimID ); ///< Owner just fired this weapon at this Object ObjectID getLastShotVictim() const { return m_victimID; } ///< get the last victim ID that was shot at Int getNumConsecutiveShotsAtVictim( const Object *victim ) const; + void forceCoolDown(); ///< Force immediate cooldown, stopping all continuous fire states +#if !RETAIL_COMPATIBLE_CRC + /// Exclude power-related disable types so update() doesn't restart barrel animations. + /// forceCoolDown() in setDisabledUntil() handles immediate cooldown. + virtual DisabledMaskType getDisabledTypesToProcess() const + { + DisabledMaskType mask = DISABLEDMASK_ALL; + mask.clear(MAKE_DISABLED_MASK3(DISABLED_HACKED, DISABLED_EMP, DISABLED_UNDERPOWERED)); + return mask; + } +#else /// this is never disabled, since we want disabled things to continue to slowly "spin down"... (srj) virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; } +#endif virtual UpdateSleepTime update(); ///< See if spin down is needed because we haven't shot in a while diff --git a/Generals/Code/GameEngine/Include/GameLogic/Object.h b/Generals/Code/GameEngine/Include/GameLogic/Object.h index 9f0f0869b3..23da00757b 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Object.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Object.h @@ -549,6 +549,7 @@ class Object : public Thing, public Snapshot void setDisabled( DisabledType type ); void setDisabledUntil( DisabledType type, UnsignedInt frame ); Bool isDisabledByType( DisabledType type ) const { return TEST_DISABLEDMASK( m_disabledMask, type ); } + Bool isUnderpoweredForAttack() const; ///< Returns true if powered-type and underpowered void pauseAllSpecialPowers( const Bool disabling ) const; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/FiringTracker.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/FiringTracker.cpp index 78bc4fced5..3778856db5 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/FiringTracker.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/FiringTracker.cpp @@ -282,6 +282,13 @@ void FiringTracker::speedUp() } +//------------------------------------------------------------------------------------------------- +void FiringTracker::forceCoolDown() +{ + m_frameToStartCooldown = 0; + coolDown(); +} + //------------------------------------------------------------------------------------------------- void FiringTracker::coolDown() { diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 49d49491de..ef0a1df0c8 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -1569,6 +1569,16 @@ Bool Object::isLogicallyVisible() const //============================================================================= // Object::isLocallyControlled //============================================================================= +Bool Object::isUnderpoweredForAttack() const +{ +#if !RETAIL_COMPATIBLE_CRC + return isKindOf( KINDOF_POWERED ) && isDisabledByType( DISABLED_UNDERPOWERED ); +#else + return false; +#endif +} + +//------------------------------------------------------------------------------------------------- Bool Object::isLocallyControlled() const { return getControllingPlayer() == ThePlayerList->getLocalPlayer(); @@ -2015,6 +2025,20 @@ void Object::setDisabledUntil( DisabledType type, UnsignedInt frame ) } +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix bobtista 21/12/2025 Fix Gatling Cannon barrels rotating despite insufficient energy. + // When power is lost (UNDERPOWERED, EMP, HACKED), immediately force FiringTracker cooldown to stop barrel animations. + // getDisabledTypesToProcess() prevents update() from restarting animations, and isUnderpoweredForAttack() prevents cursor/attack logic. + if (m_firingTracker) + { + Bool isPowerDisableType = (type == DISABLED_UNDERPOWERED || type == DISABLED_EMP || type == DISABLED_HACKED); + if (isPowerDisableType) + { + m_firingTracker->forceCoolDown(); + } + } +#endif + // This will only be called if we were NOT disabled before coming into this function. if (edgeCase) { onDisabledEdge(true); @@ -2918,6 +2942,11 @@ Bool Object::isAbleToAttack() const if( testStatus(OBJECT_STATUS_SOLD) ) return false; +#if !RETAIL_COMPATIBLE_CRC + if (isUnderpoweredForAttack()) + return false; +#endif + //We can't fire if we, as a portable structure, are aptly disabled if ( isKindOf( KINDOF_PORTABLE_STRUCTURE ) || isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS )) { @@ -4187,6 +4216,12 @@ void Object::adjustModelConditionForWeaponStatus() // we really don't care, so we just force the issue here. (This might still need tweaking for the pursue state.) conditionToSet = WSF_NONE; } +#if !RETAIL_COMPATIBLE_CRC + else if (isUnderpoweredForAttack()) + { + conditionToSet = WSF_NONE; + } +#endif else { WeaponStatus newStatus = w->getStatus(); @@ -4210,7 +4245,14 @@ void Object::adjustModelConditionForWeaponStatus() if (newStatus == READY_TO_FIRE && conditionToSet == WSF_NONE && testStatus( OBJECT_STATUS_IS_ATTACKING ) && (testStatus( OBJECT_STATUS_IS_AIMING_WEAPON ) || testStatus( OBJECT_STATUS_IS_FIRING_WEAPON ))) { +#if !RETAIL_COMPATIBLE_CRC + if (!isUnderpoweredForAttack()) + { + conditionToSet = WSF_BETWEEN; + } +#else conditionToSet = WSF_BETWEEN; +#endif } } diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/FiringTracker.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/FiringTracker.h index 237b009bb3..49185ca819 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/FiringTracker.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/FiringTracker.h @@ -55,9 +55,21 @@ class FiringTracker : public UpdateModule void shotFired(const Weapon* weaponFired, ObjectID victimID ); ///< Owner just fired this weapon at this Object ObjectID getLastShotVictim() const { return m_victimID; } ///< get the last victim ID that was shot at Int getNumConsecutiveShotsAtVictim( const Object *victim ) const; + void forceCoolDown(); ///< Force immediate cooldown, stopping all continuous fire states +#if !RETAIL_COMPATIBLE_CRC + /// Exclude power-related disable types so update() doesn't restart barrel animations. + /// forceCoolDown() in setDisabledUntil() handles immediate cooldown. + virtual DisabledMaskType getDisabledTypesToProcess() const + { + DisabledMaskType mask = DISABLEDMASK_ALL; + mask.clear(MAKE_DISABLED_MASK4(DISABLED_HACKED, DISABLED_EMP, DISABLED_UNDERPOWERED, DISABLED_SUBDUED)); + return mask; + } +#else /// this is never disabled, since we want disabled things to continue to slowly "spin down"... (srj) virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; } +#endif virtual UpdateSleepTime update(); ///< See if spin down is needed because we haven't shot in a while diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index 9c601848f6..c90bd580af 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -586,6 +586,7 @@ class Object : public Thing, public Snapshot void setDisabled( DisabledType type ); void setDisabledUntil( DisabledType type, UnsignedInt frame ); Bool isDisabledByType( DisabledType type ) const { return TEST_DISABLEDMASK( m_disabledMask, type ); } + Bool isUnderpoweredForAttack() const; ///< Returns true if powered-type and underpowered UnsignedInt getDisabledUntil( DisabledType type = DISABLED_ANY ) const; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/FiringTracker.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/FiringTracker.cpp index 61969926d6..f3a2418fc5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/FiringTracker.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/FiringTracker.cpp @@ -301,6 +301,13 @@ void FiringTracker::speedUp() } +//------------------------------------------------------------------------------------------------- +void FiringTracker::forceCoolDown() +{ + m_frameToStartCooldown = 0; + coolDown(); +} + //------------------------------------------------------------------------------------------------- void FiringTracker::coolDown() { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index bca7c39b87..8bc539cd39 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -1724,6 +1724,16 @@ Bool Object::isLogicallyVisible() const //============================================================================= // Object::isLocallyControlled //============================================================================= +Bool Object::isUnderpoweredForAttack() const +{ +#if !RETAIL_COMPATIBLE_CRC + return isKindOf( KINDOF_POWERED ) && isDisabledByType( DISABLED_UNDERPOWERED ); +#else + return false; +#endif +} + +//------------------------------------------------------------------------------------------------- Bool Object::isLocallyControlled() const { return getControllingPlayer() == ThePlayerList->getLocalPlayer(); @@ -2247,6 +2257,20 @@ void Object::setDisabledUntil( DisabledType type, UnsignedInt frame ) } +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix bobtista 21/12/2025 Fix Gatling Cannon barrels rotating despite insufficient energy. + // When power is lost (UNDERPOWERED, EMP, SUBDUED, HACKED), immediately force FiringTracker cooldown to stop barrel animations. + // getDisabledTypesToProcess() prevents update() from restarting animations, and isUnderpoweredForAttack() prevents cursor/attack logic. + if (m_firingTracker) + { + Bool isPowerDisableType = (type == DISABLED_UNDERPOWERED || type == DISABLED_EMP || type == DISABLED_SUBDUED || type == DISABLED_HACKED); + if (isPowerDisableType) + { + m_firingTracker->forceCoolDown(); + } + } +#endif + // This will only be called if we were NOT disabled before coming into this function. if (edgeCase) { onDisabledEdge(true); @@ -3236,6 +3260,11 @@ Bool Object::isAbleToAttack() const if ( isDisabledByType( DISABLED_SUBDUED ) ) return FALSE; // A Microwave Tank is cooking me +#if !RETAIL_COMPATIBLE_CRC + if (isUnderpoweredForAttack()) + return false; +#endif + //We can't fire if we, as a portable structure, are aptly disabled if ( isKindOf( KINDOF_PORTABLE_STRUCTURE ) || isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS )) { @@ -4751,6 +4780,12 @@ void Object::adjustModelConditionForWeaponStatus() // we really don't care, so we just force the issue here. (This might still need tweaking for the pursue state.) conditionToSet = WSF_NONE; } +#if !RETAIL_COMPATIBLE_CRC + else if (isUnderpoweredForAttack()) + { + conditionToSet = WSF_NONE; + } +#endif else { WeaponStatus newStatus = w->getStatus(); @@ -4774,7 +4809,14 @@ void Object::adjustModelConditionForWeaponStatus() if (newStatus == READY_TO_FIRE && conditionToSet == WSF_NONE && testStatus( OBJECT_STATUS_IS_ATTACKING ) && (testStatus( OBJECT_STATUS_IS_AIMING_WEAPON ) || testStatus( OBJECT_STATUS_IS_FIRING_WEAPON ))) { +#if !RETAIL_COMPATIBLE_CRC + if (!isUnderpoweredForAttack()) + { + conditionToSet = WSF_BETWEEN; + } +#else conditionToSet = WSF_BETWEEN; +#endif } }