From d70074f29312c20818e118fb23a4a02e174213b0 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Fri, 2 Jan 2026 14:10:43 +0100 Subject: [PATCH 1/3] tweak(drawable): Decouple stealth opacity fade time step from render update --- .../GameEngine/Source/GameClient/Drawable.cpp | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 9f1f5f7eafe..749b3db6c26 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -2607,16 +2607,27 @@ void Drawable::setStealthLook(StealthLookType look) //------------------------------------------------------------------------------------------------- void Drawable::draw() { - if ( testTintStatus( TINT_STATUS_FRENZY ) == FALSE ) - { - if ( getObject() && getObject()->isEffectivelyDead() ) - m_secondMaterialPassOpacity = 0.0f;//dead folks don't stealth anyway - else if ( m_secondMaterialPassOpacity > VERY_TRANSPARENT_MATERIAL_PASS_OPACITY )// keep fading any add'l material unless something has set it to zero - m_secondMaterialPassOpacity *= MATERIAL_PASS_OPACITY_FADE_SCALAR; - else - m_secondMaterialPassOpacity = 0.0f; - } - + if ( testTintStatus( TINT_STATUS_FRENZY ) == FALSE ) + { + if ( getObject() && getObject()->isEffectivelyDead() ) + { + //dead folks don't stealth anyway + m_secondMaterialPassOpacity = 0.0f; + } + else if ( m_secondMaterialPassOpacity > VERY_TRANSPARENT_MATERIAL_PASS_OPACITY ) + { + // keep fading any added material unless something has set it to zero + // TheSuperHackers @tweak The stealth opacity fade time step is now decoupled from the render update. + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + const Real opacityFrame = m_secondMaterialPassOpacity * MATERIAL_PASS_OPACITY_FADE_SCALAR; + const Real opacityStep = (m_secondMaterialPassOpacity - opacityFrame) * timeScale; + m_secondMaterialPassOpacity -= opacityStep; + } + else + { + m_secondMaterialPassOpacity = 0.0f; + } + } if (m_hidden || m_hiddenByStealth || getFullyObscuredByShroud()) return; // my, that was easy From 6400232775fdcf04896bf9281bcda77239432a5e Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 3 Jan 2026 18:11:28 +0100 Subject: [PATCH 2/3] Tweak impl --- GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 749b3db6c26..bc21adb9308 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -2618,10 +2618,11 @@ void Drawable::draw() { // keep fading any added material unless something has set it to zero // TheSuperHackers @tweak The stealth opacity fade time step is now decoupled from the render update. + static_assert(MATERIAL_PASS_OPACITY_FADE_SCALAR > 0.0f && MATERIAL_PASS_OPACITY_FADE_SCALAR < 1.0f, "MATERIAL_PASS_OPACITY_FADE_SCALAR must be between 0 and 1"); + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); - const Real opacityFrame = m_secondMaterialPassOpacity * MATERIAL_PASS_OPACITY_FADE_SCALAR; - const Real opacityStep = (m_secondMaterialPassOpacity - opacityFrame) * timeScale; - m_secondMaterialPassOpacity -= opacityStep; + const Real fadeScalar = 1.0f - (1.0f - MATERIAL_PASS_OPACITY_FADE_SCALAR) * timeScale; + m_secondMaterialPassOpacity *= fadeScalar; } else { From 48c2153fef6d0d9169dc0ff9d41d2c0a2e9ee217 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 4 Jan 2026 10:33:16 +0100 Subject: [PATCH 3/3] Replicate in Generals --- .../GameEngine/Source/GameClient/Drawable.cpp | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp index 10e680001be..f1adbcc7f54 100644 --- a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -2163,15 +2163,25 @@ void Drawable::setStealthLook(StealthLookType look) //------------------------------------------------------------------------------------------------- void Drawable::draw() { - if ( getObject() && getObject()->isEffectivelyDead() ) - m_heatVisionOpacity = 0.0f;//dead folks don't stealth anyway - else if ( m_heatVisionOpacity > VERY_TRANSPARENT_HEATVISION )// keep fading any heatvision unless something has set it to zero - m_heatVisionOpacity *= HEATVISION_FADE_SCALAR; - else + { + //dead folks don't stealth anyway m_heatVisionOpacity = 0.0f; + } + else if ( m_heatVisionOpacity > VERY_TRANSPARENT_HEATVISION ) + { + // keep fading any added material unless something has set it to zero + // TheSuperHackers @tweak The stealth opacity fade time step is now decoupled from the render update. + static_assert(HEATVISION_FADE_SCALAR > 0.0f && HEATVISION_FADE_SCALAR < 1.0f, "HEATVISION_FADE_SCALAR must be between 0 and 1"); - + const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio(); + const Real fadeScalar = 1.0f - (1.0f - HEATVISION_FADE_SCALAR) * timeScale; + m_heatVisionOpacity *= fadeScalar; + } + else + { + m_heatVisionOpacity = 0.0f; + } if (m_hidden || m_hiddenByStealth || getFullyObscuredByShroud()) return; // my, that was easy