Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
24 changes: 20 additions & 4 deletions Generals/Code/GameEngine/Include/GameLogic/Module/BodyModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 &timestamp;
#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 &timestamp;
#else
return nullptr;
#endif
}
virtual ObjectID getClearableLastAttacker() const override { return INVALID_ID; }
virtual void clearLastAttacker() override { }
virtual Bool getFrontCrushed() const override { return false; }
Expand Down
9 changes: 6 additions & 3 deletions Generals/Code/GameEngine/Source/GameClient/Drawable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down
18 changes: 13 additions & 5 deletions Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion Generals/Code/GameEngine/Source/GameLogic/AI/AITNGuard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 /////////////////////////////////////////////////////////////////////////////

// ------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
24 changes: 20 additions & 4 deletions GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BodyModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 &timestamp;
#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 &timestamp;
#else
return nullptr;
#endif
}
virtual ObjectID getClearableLastAttacker() const override { return INVALID_ID; }
virtual void clearLastAttacker() override { }
virtual Bool getFrontCrushed() const override { return false; }
Expand Down
9 changes: 6 additions & 3 deletions GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down
18 changes: 13 additions & 5 deletions GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AITNGuard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 /////////////////////////////////////////////////////////////////////////////

// ------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading