diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h b/Generals/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h index 9c80b361d93..fdffe305281 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h @@ -79,8 +79,8 @@ class ActiveBody : public BodyModule virtual void setAflame( Bool setting ) override;///< This is a major change like a damage state. virtual const DamageInfo *getLastDamageInfo() const override { return &m_lastDamageInfo; } ///< return info on last damage dealt to this object - virtual UnsignedInt getLastDamageTimestamp() const override { return m_lastDamageTimestamp; } ///< return frame of last damage dealt - virtual UnsignedInt getLastHealingTimestamp() const override { return m_lastHealingTimestamp; } ///< return frame of last damage dealt + virtual const UnsignedInt *getLastDamageTimestamp() const override; ///< return frame of last damage, or nullptr if none + virtual const UnsignedInt *getLastHealingTimestamp() const override; ///< return frame of last healing, or nullptr if none virtual ObjectID getClearableLastAttacker() const override { return (m_lastDamageCleared ? INVALID_ID : m_lastDamageInfo.in.m_sourceID); } virtual void clearLastAttacker() override { m_lastDamageCleared = true; } diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/BodyModule.h b/Generals/Code/GameEngine/Include/GameLogic/Module/BodyModule.h index f201ca1a777..d4e6fc5288b 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/BodyModule.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/BodyModule.h @@ -158,8 +158,8 @@ class BodyModuleInterface virtual void clearArmorSetFlag(ArmorSetType ast) = 0; virtual const DamageInfo *getLastDamageInfo() const = 0; - virtual UnsignedInt getLastDamageTimestamp() const = 0; - virtual UnsignedInt getLastHealingTimestamp() const = 0; + virtual const UnsignedInt *getLastDamageTimestamp() const = 0; + virtual const UnsignedInt *getLastHealingTimestamp() const = 0; virtual ObjectID getClearableLastAttacker() const = 0; virtual void clearLastAttacker() = 0; virtual Bool getFrontCrushed() const = 0; @@ -212,8 +212,24 @@ class BodyModule : public BehaviorModule, public BodyModuleInterface virtual Real getInitialHealth() const override {return 0.0f;} // return initial health virtual const DamageInfo *getLastDamageInfo() const override { return nullptr; } ///< return info on last damage dealt to this object - virtual UnsignedInt getLastDamageTimestamp() const override { return 0; } ///< return frame of last damage dealt - virtual UnsignedInt getLastHealingTimestamp() const override { return 0; } ///< return frame of last healing dealt + virtual const UnsignedInt *getLastDamageTimestamp() const override ///< return frame of last damage, or nullptr if none + { +#if RETAIL_COMPATIBLE_CRC + static const UnsignedInt timestamp = 0; + return ×tamp; +#else + return nullptr; +#endif + } + virtual const UnsignedInt *getLastHealingTimestamp() const override ///< return frame of last healing, or nullptr if none + { +#if RETAIL_COMPATIBLE_CRC + static const UnsignedInt timestamp = 0; + return ×tamp; +#else + return nullptr; +#endif + } virtual ObjectID getClearableLastAttacker() const override { return INVALID_ID; } virtual void clearLastAttacker() override { } virtual Bool getFrontCrushed() const override { return false; } diff --git a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp index 046c31ec844..97eb84504d8 100644 --- a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1812,7 +1812,8 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI const DamageInfo *damageInfo = obj->getBodyModule()->getLastDamageInfo(); if (damageInfo) { - if (obj->getBodyModule()->getLastDamageTimestamp() > m_lastDamageTimestamp && damageInfo->in.m_amount > RECOIL_DAMAGE) + const UnsignedInt *lastDamageTimestamp = obj->getBodyModule()->getLastDamageTimestamp(); + if (lastDamageTimestamp && *lastDamageTimestamp > m_lastDamageTimestamp && damageInfo->in.m_amount > RECOIL_DAMAGE) { Object *attacker = TheGameLogic->getObject( damageInfo->in.m_sourceID ); if (attacker) @@ -1831,7 +1832,7 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI m_locoInfo->m_accelerationRollRate -= recoil * lateral; } - m_lastDamageTimestamp = obj->getBodyModule()->getLastDamageTimestamp(); + m_lastDamageTimestamp = *lastDamageTimestamp; } } #endif @@ -3177,8 +3178,10 @@ void Drawable::drawHealing(const IRegion2D* healthBarRegion) // if( lastDamage != nullptr && lastDamage->in.m_damageType == DAMAGE_HEALING // &&(TheGameLogic->getFrame() - body->getLastHealingTimestamp()) <= HEALING_ICON_DISPLAY_TIME // ) + const UnsignedInt *lastHealingTimestamp = body->getLastHealingTimestamp(); if ( TheGameLogic->getFrame() > HEALING_ICON_DISPLAY_TIME && // because so many things init health early in game - (TheGameLogic->getFrame() - body->getLastHealingTimestamp() <= HEALING_ICON_DISPLAY_TIME) ) + lastHealingTimestamp != nullptr && + (TheGameLogic->getFrame() - *lastHealingTimestamp <= HEALING_ICON_DISPLAY_TIME) ) showHealing = TRUE; } diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp index fc9385a7b68..d7c97c00060 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp @@ -928,20 +928,27 @@ void AIPlayer::guardSupplyCenter( Team *team, Int minSupplies ) //------------------------------------------------------------------------------------------------- Bool AIPlayer::isSupplySourceAttacked() { - const Int SCAN_RATE = 10; // don't scan more often than every 10 seconds. + // TheSuperHackers @bugfix WebbontheWeb 27/09/2026 No longer scans for supply source attacks for just the last 10 frames. + // A prior EA comment indicated that the intent was to look for 10 seconds into the attack history. + const Int RefreshRate = 10; +#if RETAIL_COMPATIBLE_CRC + const Int ScanWindow = 10; +#else + const Int ScanWindow = 10 * LOGICFRAMES_PER_SECOND; +#endif UnsignedInt curFrame = TheGameLogic->getFrame(); if (curFrame==0) { - m_supplySourceAttackCheckFrame = curFrame+SCAN_RATE; + m_supplySourceAttackCheckFrame = curFrame + RefreshRate; return false; // can't be attacked on first frame. } m_attackedSupplyCenter = INVALID_ID; if (curFrame < m_supplySourceAttackCheckFrame) { return false; } - if (m_player->getAttackedFrame()+SCAN_RATE < curFrame) { + if (m_player->getAttackedFrame() + ScanWindow < curFrame) { return false; // haven't been attacked recently. } - m_supplySourceAttackCheckFrame = curFrame+SCAN_RATE; + m_supplySourceAttackCheckFrame = curFrame + RefreshRate; // Scan my units. Player::PlayerTeamList::const_iterator it; @@ -968,7 +975,8 @@ Bool AIPlayer::isSupplySourceAttacked() if (info->out.m_noEffect) { continue; } - if (body->getLastDamageTimestamp() + SCAN_RATE > curFrame) { + const UnsignedInt *lastDamageTimestamp = body->getLastDamageTimestamp(); + if (lastDamageTimestamp && *lastDamageTimestamp + ScanWindow > curFrame) { // winner. m_attackedSupplyCenter = obj->getID(); return true; diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AITNGuard.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AITNGuard.cpp index d1ec1c6fc3d..76d858f05b2 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AITNGuard.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AITNGuard.cpp @@ -230,7 +230,8 @@ Bool AITNGuardMachine::lookForInnerTarget() if (info->out.m_noEffect) { continue; } - if (body->getLastDamageTimestamp() + TheAI->getAiData()->m_guardEnemyScanRate > TheGameLogic->getFrame()) { + const UnsignedInt *lastDamageTimestamp = body->getLastDamageTimestamp(); + if (lastDamageTimestamp && *lastDamageTimestamp + TheAI->getAiData()->m_guardEnemyScanRate > TheGameLogic->getFrame()) { // winner. ObjectID attackerID = info->in.m_sourceID; Object *attacker = TheGameLogic->findObjectByID(attackerID); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp index bea29c00a96..292b60cc528 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp @@ -60,6 +60,8 @@ #define YELLOW_DAMAGE_PERCENT (0.25f) +constexpr const UnsignedInt InvalidBodyTimestamp = ~0u; ///< damage or healing timestamp that has not been recorded yet + // FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ @@ -149,8 +151,8 @@ ActiveBody::ActiveBody( Thing *thing, const ModuleData* moduleData ) : m_curArmorSet(nullptr), m_frontCrushed(false), m_backCrushed(false), - m_lastDamageTimestamp(0xffffffff),// So we don't think we just got damaged on the first frame - m_lastHealingTimestamp(0xffffffff),// So we don't think we just got healed on the first frame + m_lastDamageTimestamp(InvalidBodyTimestamp),// So we don't think we just got damaged on the first frame + m_lastHealingTimestamp(InvalidBodyTimestamp),// So we don't think we just got healed on the first frame m_curDamageState(BODY_PRISTINE), m_nextDamageFXTime(0), m_lastDamageFXDone((DamageType)-1), @@ -1002,6 +1004,30 @@ void ActiveBody::internalChangeHealth( Real delta ) } +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +const UnsignedInt *ActiveBody::getLastDamageTimestamp() const +{ +#if RETAIL_COMPATIBLE_CRC + return &m_lastDamageTimestamp; +#else + // TheSuperHackers @fix WebbontheWeb 03/10/2026 Avoid treating never-damaged sentinels as an attack timestamp. + return m_lastDamageTimestamp != InvalidBodyTimestamp ? &m_lastDamageTimestamp : nullptr; +#endif +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +const UnsignedInt *ActiveBody::getLastHealingTimestamp() const +{ +#if RETAIL_COMPATIBLE_CRC + return &m_lastHealingTimestamp; +#else + // TheSuperHackers @fix WebbontheWeb 04/10/2026 Avoid treating never-healed sentinels as a healing timestamp. + return m_lastHealingTimestamp != InvalidBodyTimestamp ? &m_lastHealingTimestamp : nullptr; +#endif +} + //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Real ActiveBody::getHealth() const diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp index c84d73a7caf..e1c19cbb237 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp @@ -464,7 +464,8 @@ UpdateSleepTime TunnelContain::update() if (body) { const DamageInfo *info = body->getLastDamageInfo(); if (info) { - if (body->getLastDamageTimestamp() + LOGICFRAMES_PER_SECOND > TheGameLogic->getFrame()) { + const UnsignedInt *lastDamageTimestamp = body->getLastDamageTimestamp(); + if (lastDamageTimestamp && *lastDamageTimestamp + LOGICFRAMES_PER_SECOND > TheGameLogic->getFrame()) { // winner. ObjectID attackerID = info->in.m_sourceID; Object *attacker = TheGameLogic->findObjectByID(attackerID); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h index f29567555f7..54434a6d3a8 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h @@ -87,8 +87,8 @@ class ActiveBody : public BodyModule virtual Real getCurrentSubdualDamageAmount() const override { return m_currentSubdualDamage; } virtual const DamageInfo *getLastDamageInfo() const override { return &m_lastDamageInfo; } ///< return info on last damage dealt to this object - virtual UnsignedInt getLastDamageTimestamp() const override { return m_lastDamageTimestamp; } ///< return frame of last damage dealt - virtual UnsignedInt getLastHealingTimestamp() const override { return m_lastHealingTimestamp; } ///< return frame of last damage dealt + virtual const UnsignedInt *getLastDamageTimestamp() const override; ///< return frame of last damage, or nullptr if none + virtual const UnsignedInt *getLastHealingTimestamp() const override; ///< return frame of last healing, or nullptr if none virtual ObjectID getClearableLastAttacker() const override { return (m_lastDamageCleared ? INVALID_ID : m_lastDamageInfo.in.m_sourceID); } virtual void clearLastAttacker() override { m_lastDamageCleared = true; } diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BodyModule.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BodyModule.h index 015b5e743ed..d9d1c2736b7 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BodyModule.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BodyModule.h @@ -168,8 +168,8 @@ class BodyModuleInterface virtual Bool testArmorSetFlag(ArmorSetType ast) = 0; virtual const DamageInfo *getLastDamageInfo() const = 0; - virtual UnsignedInt getLastDamageTimestamp() const = 0; - virtual UnsignedInt getLastHealingTimestamp() const = 0; + virtual const UnsignedInt *getLastDamageTimestamp() const = 0; + virtual const UnsignedInt *getLastHealingTimestamp() const = 0; virtual ObjectID getClearableLastAttacker() const = 0; virtual void clearLastAttacker() = 0; virtual Bool getFrontCrushed() const = 0; @@ -228,8 +228,24 @@ class BodyModule : public BehaviorModule, public BodyModuleInterface virtual Real getInitialHealth() const override {return 0.0f;} // return initial health virtual const DamageInfo *getLastDamageInfo() const override { return nullptr; } ///< return info on last damage dealt to this object - virtual UnsignedInt getLastDamageTimestamp() const override { return 0; } ///< return frame of last damage dealt - virtual UnsignedInt getLastHealingTimestamp() const override { return 0; } ///< return frame of last healing dealt + virtual const UnsignedInt *getLastDamageTimestamp() const override ///< return frame of last damage, or nullptr if none + { +#if RETAIL_COMPATIBLE_CRC + static const UnsignedInt timestamp = 0; + return ×tamp; +#else + return nullptr; +#endif + } + virtual const UnsignedInt *getLastHealingTimestamp() const override ///< return frame of last healing, or nullptr if none + { +#if RETAIL_COMPATIBLE_CRC + static const UnsignedInt timestamp = 0; + return ×tamp; +#else + return nullptr; +#endif + } virtual ObjectID getClearableLastAttacker() const override { return INVALID_ID; } virtual void clearLastAttacker() override { } virtual Bool getFrontCrushed() const override { return false; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 7ed234e99d8..13c758db7d5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1814,7 +1814,8 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI const DamageInfo *damageInfo = obj->getBodyModule()->getLastDamageInfo(); if (damageInfo) { - if (obj->getBodyModule()->getLastDamageTimestamp() > m_lastDamageTimestamp && damageInfo->in.m_amount > RECOIL_DAMAGE) + const UnsignedInt *lastDamageTimestamp = obj->getBodyModule()->getLastDamageTimestamp(); + if (lastDamageTimestamp && *lastDamageTimestamp > m_lastDamageTimestamp && damageInfo->in.m_amount > RECOIL_DAMAGE) { Object *attacker = TheGameLogic->getObject( damageInfo->in.m_sourceID ); if (attacker) @@ -1833,7 +1834,7 @@ void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformI m_locoInfo->m_accelerationRollRate -= recoil * lateral; } - m_lastDamageTimestamp = obj->getBodyModule()->getLastDamageTimestamp(); + m_lastDamageTimestamp = *lastDamageTimestamp; } } #endif @@ -3182,8 +3183,10 @@ void Drawable::drawHealing(const IRegion2D* healthBarRegion) // if( lastDamage != nullptr && lastDamage->in.m_damageType == DAMAGE_HEALING // &&(TheGameLogic->getFrame() - body->getLastHealingTimestamp()) <= HEALING_ICON_DISPLAY_TIME // ) + const UnsignedInt *lastHealingTimestamp = body->getLastHealingTimestamp(); if ( TheGameLogic->getFrame() > HEALING_ICON_DISPLAY_TIME && // because so many things init health early in game - (TheGameLogic->getFrame() - body->getLastHealingTimestamp() <= HEALING_ICON_DISPLAY_TIME) ) + lastHealingTimestamp != nullptr && + (TheGameLogic->getFrame() - *lastHealingTimestamp <= HEALING_ICON_DISPLAY_TIME) ) showHealing = TRUE; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp index 10e5faa96af..9108b9708c3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp @@ -935,20 +935,27 @@ void AIPlayer::guardSupplyCenter( Team *team, Int minSupplies ) //------------------------------------------------------------------------------------------------- Bool AIPlayer::isSupplySourceAttacked() { - const Int SCAN_RATE = 10; // don't scan more often than every 10 seconds. + // TheSuperHackers @bugfix WebbontheWeb 27/09/2026 No longer scans for supply source attacks for just the last 10 frames. + // A prior EA comment indicated that the intent was to look for 10 seconds into the attack history. + const Int RefreshRate = 10; +#if RETAIL_COMPATIBLE_CRC + const Int ScanWindow = 10; +#else + const Int ScanWindow = 10 * LOGICFRAMES_PER_SECOND; +#endif UnsignedInt curFrame = TheGameLogic->getFrame(); if (curFrame==0) { - m_supplySourceAttackCheckFrame = curFrame+SCAN_RATE; + m_supplySourceAttackCheckFrame = curFrame + RefreshRate; return false; // can't be attacked on first frame. } m_attackedSupplyCenter = INVALID_ID; if (curFrame < m_supplySourceAttackCheckFrame) { return false; } - if (m_player->getAttackedFrame()+SCAN_RATE < curFrame) { + if (m_player->getAttackedFrame() + ScanWindow < curFrame) { return false; // haven't been attacked recently. } - m_supplySourceAttackCheckFrame = curFrame+SCAN_RATE; + m_supplySourceAttackCheckFrame = curFrame + RefreshRate; // Scan my units. Player::PlayerTeamList::const_iterator it; @@ -975,7 +982,8 @@ Bool AIPlayer::isSupplySourceAttacked() if (info->out.m_noEffect) { continue; } - if (body->getLastDamageTimestamp() + SCAN_RATE > curFrame) { + const UnsignedInt *lastDamageTimestamp = body->getLastDamageTimestamp(); + if (lastDamageTimestamp && *lastDamageTimestamp + ScanWindow > curFrame) { // winner. m_attackedSupplyCenter = obj->getID(); return true; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AITNGuard.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AITNGuard.cpp index a475add6e1e..196f7c711d0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AITNGuard.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AITNGuard.cpp @@ -230,7 +230,8 @@ Bool AITNGuardMachine::lookForInnerTarget() if (info->out.m_noEffect) { continue; } - if (body->getLastDamageTimestamp() + TheAI->getAiData()->m_guardEnemyScanRate > TheGameLogic->getFrame()) { + const UnsignedInt *lastDamageTimestamp = body->getLastDamageTimestamp(); + if (lastDamageTimestamp && *lastDamageTimestamp + TheAI->getAiData()->m_guardEnemyScanRate > TheGameLogic->getFrame()) { // winner. ObjectID attackerID = info->in.m_sourceID; Object *attacker = TheGameLogic->findObjectByID(attackerID); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp index 21ce7bd81ae..9689ce9065a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp @@ -64,6 +64,8 @@ #define YELLOW_DAMAGE_PERCENT (0.25f) +constexpr const UnsignedInt InvalidBodyTimestamp = ~0u; ///< damage or healing timestamp that has not been recorded yet + // FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ @@ -160,8 +162,8 @@ ActiveBody::ActiveBody( Thing *thing, const ModuleData* moduleData ) : m_curArmorSet(nullptr), m_frontCrushed(false), m_backCrushed(false), - m_lastDamageTimestamp(0xffffffff),// So we don't think we just got damaged on the first frame - m_lastHealingTimestamp(0xffffffff),// So we don't think we just got healed on the first frame + m_lastDamageTimestamp(InvalidBodyTimestamp),// So we don't think we just got damaged on the first frame + m_lastHealingTimestamp(InvalidBodyTimestamp),// So we don't think we just got healed on the first frame m_curDamageState(BODY_PRISTINE), m_nextDamageFXTime(0), m_lastDamageFXDone((DamageType)-1), @@ -1338,6 +1340,30 @@ Bool ActiveBody::isSubdued() const #endif } +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +const UnsignedInt *ActiveBody::getLastDamageTimestamp() const +{ +#if RETAIL_COMPATIBLE_CRC + return &m_lastDamageTimestamp; +#else + // TheSuperHackers @fix WebbontheWeb 03/10/2026 Avoid treating never-damaged sentinels as an attack timestamp. + return m_lastDamageTimestamp != InvalidBodyTimestamp ? &m_lastDamageTimestamp : nullptr; +#endif +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +const UnsignedInt *ActiveBody::getLastHealingTimestamp() const +{ +#if RETAIL_COMPATIBLE_CRC + return &m_lastHealingTimestamp; +#else + // TheSuperHackers @fix WebbontheWeb 04/10/2026 Avoid treating never-healed sentinels as a healing timestamp. + return m_lastHealingTimestamp != InvalidBodyTimestamp ? &m_lastHealingTimestamp : nullptr; +#endif +} + //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Real ActiveBody::getHealth() const diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp index b2f2884bb6c..5c408b6524f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp @@ -576,7 +576,8 @@ UpdateSleepTime TunnelContain::update() if (body) { const DamageInfo *info = body->getLastDamageInfo(); if (info) { - if (body->getLastDamageTimestamp() + LOGICFRAMES_PER_SECOND > TheGameLogic->getFrame()) { + const UnsignedInt *lastDamageTimestamp = body->getLastDamageTimestamp(); + if (lastDamageTimestamp && *lastDamageTimestamp + LOGICFRAMES_PER_SECOND > TheGameLogic->getFrame()) { // winner. ObjectID attackerID = info->in.m_sourceID; Object *attacker = TheGameLogic->findObjectByID(attackerID); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp index b1a82a0ead8..bba50bcdc10 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp @@ -311,17 +311,20 @@ Bool StealthUpdate::allowedToStealth( Object *stealthOwner ) const return FALSE; } - if( flags & STEALTH_NOT_WHILE_TAKING_DAMAGE && self->getBodyModule()->getLastDamageTimestamp() >= now - 1 ) + const UnsignedInt *lastDamageTimestamp = self->getBodyModule()->getLastDamageTimestamp(); + if( flags & STEALTH_NOT_WHILE_TAKING_DAMAGE && lastDamageTimestamp && *lastDamageTimestamp >= now - 1 ) { #if RETAIL_COMPATIBLE_CRC || PRESERVE_STRUCTURE_STEALTH_DURING_REPAIR //Only if it's not healing damage. if( self->getBodyModule()->getLastDamageInfo()->in.m_damageType != DAMAGE_HEALING ) #endif { - //Can't stealth if we just took damage in the last frame or two. - if( self->getBodyModule()->getLastDamageTimestamp() != 0xffffffff ) +#if RETAIL_COMPATIBLE_CRC + //But it's initialized to 0xffffffff so we don't think we took damage on the first frame. + if( *lastDamageTimestamp != 0xffffffff ) +#endif { - //But it's initialized to 0xffffffff so we don't think we took damage on the first frame. + //Can't stealth if we just took damage in the last frame or two. return FALSE; } }