Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Generals/Code/GameEngine/Include/GameLogic/FiringTracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions Generals/Code/GameEngine/Include/GameLogic/Object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,13 @@ void FiringTracker::speedUp()

}

//-------------------------------------------------------------------------------------------------
void FiringTracker::forceCoolDown()
{
m_frameToStartCooldown = 0;
coolDown();
}

//-------------------------------------------------------------------------------------------------
void FiringTracker::coolDown()
{
Expand Down
42 changes: 42 additions & 0 deletions Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 ))
{
Expand Down Expand Up @@ -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();
Expand All @@ -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
}

}
Expand Down
12 changes: 12 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/GameLogic/FiringTracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,13 @@ void FiringTracker::speedUp()

}

//-------------------------------------------------------------------------------------------------
void FiringTracker::forceCoolDown()
{
m_frameToStartCooldown = 0;
coolDown();
}

//-------------------------------------------------------------------------------------------------
void FiringTracker::coolDown()
{
Expand Down
42 changes: 42 additions & 0 deletions GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 ))
{
Expand Down Expand Up @@ -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();
Expand All @@ -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
}

}
Expand Down
Loading