From 3cd3f42990eda676b7cdbce552cf3b08d52c2939 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Thu, 18 Dec 2025 20:40:35 +0100 Subject: [PATCH 1/5] tweak(drawable): Decouple material opacity of detected stealth models from render update Scalar value is calculated ahead of rendering. --- .../GameEngine/Include/GameClient/Drawable.h | 9 ++-- .../GameEngine/Source/GameClient/Drawable.cpp | 42 ++++++++++++++----- .../Object/Update/StealthDetectorUpdate.cpp | 3 +- .../GameLogic/Object/Update/StealthUpdate.cpp | 3 +- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h index b66eb7dc96..181fc68cc5 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h @@ -542,8 +542,9 @@ class Drawable : public Thing, void setEffectiveOpacity( Real pulseFactor, Real explicitOpacity = -1.0f ); // this is for the add'l pass fx which operates completely independently of the stealth opacity effects. Draw() does the fading every frame. - Real getSecondMaterialPassOpacity() const { return m_secondMaterialPassOpacity; } ///< get alpha/opacity value used to render add'l rendering pass. - void setSecondMaterialPassOpacity( Real op ) { m_secondMaterialPassOpacity = op; }; ///< set alpha/opacity value used to render add'l rendering pass. + Real getSecondMaterialPassOpacity() const { return m_secondMaterialPassOpacity; } ///< get alpha/opacity value used to render add'l rendering pass. + void setSecondMaterialPassOpacity( Real op ) { m_secondMaterialPassOpacity = op; }; ///< set alpha/opacity value used to render add'l rendering pass. + void allowRefillSecondMaterialPassOpacity() { m_secondMaterialPassOpacityAllowRefill = TRUE; } ///< allow the second material opacity to be set to 1.0f // both of these assume that you are starting at one extreme 100% or 0% opacity and are trying to go to the other!! -- amit void fadeOut( UnsignedInt frames ); ///< fade object out...how gradually this is done is determined by frames @@ -723,7 +724,9 @@ class Drawable : public Thing, UnsignedInt m_expirationDate; ///< if nonzero, Drawable should destroy itself at this frame DrawableIconInfo* m_iconInfo; ///< lazily allocated! - Real m_secondMaterialPassOpacity; ///< drawable gets rendered again in hardware with an extra material layer + Real m_secondMaterialPassOpacity; ///< drawable gets rendered again in hardware with an extra material layer + Real m_secondMaterialPassOpacityScalar; ///< multiply opacity by scalar value; useful for non-default render framerates + Bool m_secondMaterialPassOpacityAllowRefill; ///< allow the second material opacity to be set to 1.0f // --------- BYTE-SIZED THINGS GO HERE Byte m_selected; ///< drawable is selected or not Bool m_hidden; ///< drawable is "hidden" or not (overrides stealth effects) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 9f1f5f7eaf..702122c502 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -87,8 +87,8 @@ #endif -#define VERY_TRANSPARENT_MATERIAL_PASS_OPACITY (0.001f) -#define MATERIAL_PASS_OPACITY_FADE_SCALAR (0.8f) +#define MATERIAL_PASS_OPACITY_MIN (0.001f) +#define MATERIAL_PASS_OPACITY_DEFAULT_FADE_SCALAR (0.8f) static const char *const TheDrawableIconNames[] = { @@ -433,6 +433,8 @@ Drawable::Drawable( const ThingTemplate *thingTemplate, DrawableStatusBits statu m_hidden = false; m_hiddenByStealth = false; m_secondMaterialPassOpacity = 0.0f; + m_secondMaterialPassOpacityScalar = MATERIAL_PASS_OPACITY_DEFAULT_FADE_SCALAR; + m_secondMaterialPassOpacityAllowRefill = false; m_drawableFullyObscuredByShroud = false; m_receivesDynamicLights = TRUE; // a good default... overridden by one of my draw modules if at all @@ -2607,17 +2609,37 @@ void Drawable::setStealthLook(StealthLookType look) //------------------------------------------------------------------------------------------------- void Drawable::draw() { - if ( testTintStatus( TINT_STATUS_FRENZY ) == FALSE ) + 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 (getObject() && getObject()->isEffectivelyDead()) + { + m_secondMaterialPassOpacity = 0.0f;//dead folks don't stealth anyway + } + else if (!TheFramePacer->isGameHalted()) + { + // TheSuperHackers @tweak The opacity step is now decoupled from the render update. + if (m_secondMaterialPassOpacity > MATERIAL_PASS_OPACITY_MIN) + { + m_secondMaterialPassOpacity *= m_secondMaterialPassOpacityScalar; + } + else if (m_secondMaterialPassOpacityAllowRefill) + { + // min opacity = (X ^ (framerate / updatesPerSec)) -> e.g. [ 0.05 = X ^ (100 / 2) ] -> [ X = 0.941845 ] -> [ 0.941845 ^ 50 = 0.05 ] + // changes to the updates per second value need to be tested with a single stealth detector, max 30 logic frames and unlimited render frames + const Real updatesPerSec = 2.0f; + const Real scalar = pow(MATERIAL_PASS_OPACITY_MIN, updatesPerSec / TheFramePacer->getUpdateFps()); + + m_secondMaterialPassOpacity = scalar; + m_secondMaterialPassOpacityScalar = scalar; + m_secondMaterialPassOpacityAllowRefill = FALSE; + } + else + { + m_secondMaterialPassOpacity = 0.0f; + } + } } - if (m_hidden || m_hiddenByStealth || getFullyObscuredByShroud()) return; // my, that was easy diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthDetectorUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthDetectorUpdate.cpp index 15eb2a74eb..199d0cc67d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthDetectorUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthDetectorUpdate.cpp @@ -306,7 +306,8 @@ UpdateSleepTime StealthDetectorUpdate::update( void ) Drawable *theirDraw = them->getDrawable(); if ( theirDraw && !them->isKindOf(KINDOF_MINE)) { - theirDraw->setSecondMaterialPassOpacity( 1.0f ); + // TheSuperHackers @tweak Don't set opacity here as it should be decoupled from the logic frame rate and not be set for every stealth detector. + theirDraw->allowRefillSecondMaterialPassOpacity(); } if (data->m_IRGridParticleSysTmpl) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp index a390bc943f..80022117be 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp @@ -431,7 +431,8 @@ void StealthUpdate::hintDetectableWhileUnstealthed() { Drawable *selfDraw = self->getDrawable(); if ( selfDraw ) - selfDraw->setSecondMaterialPassOpacity( 1.0f ); + // TheSuperHackers @tweak Don't set opacity here as it should be decoupled from the logic frame rate. + selfDraw->allowRefillSecondMaterialPassOpacity(); } } } From 79ca8b77bec6bafcecee469b3cdb36137b8a39af Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Thu, 18 Dec 2025 20:41:31 +0100 Subject: [PATCH 2/5] tweak(drawable): Decouple material opacity of detected stealth models from render update Scalar value is NOT calculated ahead of rendering. --- .../GameEngine/Include/GameClient/Drawable.h | 1 - .../GameEngine/Source/GameClient/Drawable.cpp | 28 ++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h index 181fc68cc5..14e5e4c972 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h @@ -725,7 +725,6 @@ class Drawable : public Thing, DrawableIconInfo* m_iconInfo; ///< lazily allocated! Real m_secondMaterialPassOpacity; ///< drawable gets rendered again in hardware with an extra material layer - Real m_secondMaterialPassOpacityScalar; ///< multiply opacity by scalar value; useful for non-default render framerates Bool m_secondMaterialPassOpacityAllowRefill; ///< allow the second material opacity to be set to 1.0f // --------- BYTE-SIZED THINGS GO HERE Byte m_selected; ///< drawable is selected or not diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 702122c502..643d0e6ea8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -88,7 +88,6 @@ #define MATERIAL_PASS_OPACITY_MIN (0.001f) -#define MATERIAL_PASS_OPACITY_DEFAULT_FADE_SCALAR (0.8f) static const char *const TheDrawableIconNames[] = { @@ -433,7 +432,6 @@ Drawable::Drawable( const ThingTemplate *thingTemplate, DrawableStatusBits statu m_hidden = false; m_hiddenByStealth = false; m_secondMaterialPassOpacity = 0.0f; - m_secondMaterialPassOpacityScalar = MATERIAL_PASS_OPACITY_DEFAULT_FADE_SCALAR; m_secondMaterialPassOpacityAllowRefill = false; m_drawableFullyObscuredByShroud = false; @@ -2617,21 +2615,25 @@ void Drawable::draw() } else if (!TheFramePacer->isGameHalted()) { - // TheSuperHackers @tweak The opacity step is now decoupled from the render update. - if (m_secondMaterialPassOpacity > MATERIAL_PASS_OPACITY_MIN) - { - m_secondMaterialPassOpacity *= m_secondMaterialPassOpacityScalar; - } - else if (m_secondMaterialPassOpacityAllowRefill) + // TheSuperHackers @tweak The opacity step is no longer a fixed value. + const Bool shouldFade = (m_secondMaterialPassOpacity > MATERIAL_PASS_OPACITY_MIN); + const Bool allowRefill = m_secondMaterialPassOpacityAllowRefill; + + if (shouldFade || allowRefill) { - // min opacity = (X ^ (framerate / updatesPerSec)) -> e.g. [ 0.05 = X ^ (100 / 2) ] -> [ X = 0.941845 ] -> [ 0.941845 ^ 50 = 0.05 ] - // changes to the updates per second value need to be tested with a single stealth detector, max 30 logic frames and unlimited render frames + // min opacity = (X ^ (frame rate / updates per second)) -> e.g. [ 0.05 = X ^ (100 / 2) ] -> [ X = 0.941845 ] -> [ 0.941845 ^ 50 = 0.05 ] const Real updatesPerSec = 2.0f; const Real scalar = pow(MATERIAL_PASS_OPACITY_MIN, updatesPerSec / TheFramePacer->getUpdateFps()); - m_secondMaterialPassOpacity = scalar; - m_secondMaterialPassOpacityScalar = scalar; - m_secondMaterialPassOpacityAllowRefill = FALSE; + if (!shouldFade && allowRefill) + { + m_secondMaterialPassOpacity = scalar; + m_secondMaterialPassOpacityAllowRefill = FALSE; + } + else + { + m_secondMaterialPassOpacity *= scalar; + } } else { From a56e83a7ebe15f23df572d909fbf49231ac625bf Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Thu, 18 Dec 2025 21:06:02 +0100 Subject: [PATCH 3/5] Changed scalar calculation logic. --- .../GameEngine/Include/GameClient/Drawable.h | 6 ++++-- .../GameEngine/Source/GameClient/Drawable.cpp | 20 +++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h index 14e5e4c972..a75e342390 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h @@ -545,6 +545,7 @@ class Drawable : public Thing, Real getSecondMaterialPassOpacity() const { return m_secondMaterialPassOpacity; } ///< get alpha/opacity value used to render add'l rendering pass. void setSecondMaterialPassOpacity( Real op ) { m_secondMaterialPassOpacity = op; }; ///< set alpha/opacity value used to render add'l rendering pass. void allowRefillSecondMaterialPassOpacity() { m_secondMaterialPassOpacityAllowRefill = TRUE; } ///< allow the second material opacity to be set to 1.0f + static void updateSecondMaterialPassOpacityScalar(); ///< update alpha/opacity scalar value used to render e.g. detected stealth units. // both of these assume that you are starting at one extreme 100% or 0% opacity and are trying to go to the other!! -- amit void fadeOut( UnsignedInt frames ); ///< fade object out...how gradually this is done is determined by frames @@ -724,8 +725,9 @@ class Drawable : public Thing, UnsignedInt m_expirationDate; ///< if nonzero, Drawable should destroy itself at this frame DrawableIconInfo* m_iconInfo; ///< lazily allocated! - Real m_secondMaterialPassOpacity; ///< drawable gets rendered again in hardware with an extra material layer - Bool m_secondMaterialPassOpacityAllowRefill; ///< allow the second material opacity to be set to 1.0f + static inline Real m_secondMaterialPassOpacityScalar; ///< multiply opacity by scalar value; used for non-default render framerates + Real m_secondMaterialPassOpacity; ///< drawable gets rendered again in hardware with an extra material layer + Bool m_secondMaterialPassOpacityAllowRefill; ///< allow the second material opacity to be set to 1.0f // --------- BYTE-SIZED THINGS GO HERE Byte m_selected; ///< drawable is selected or not Bool m_hidden; ///< drawable is "hidden" or not (overrides stealth effects) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 643d0e6ea8..1bf1fafb34 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -2601,6 +2601,15 @@ void Drawable::setStealthLook(StealthLookType look) } } +void Drawable::updateSecondMaterialPassOpacityScalar() +{ + // TheSuperHackers @tweak The opacity step is no longer a fixed value. + // min opacity = (X ^ (frame rate / updates per second)) -> e.g. [ 0.05 = X ^ (100 / 2) ] -> [ X = 0.941845 ] -> [ 0.941845 ^ 50 = 0.05 ] + const Real updatesPerSec = 2.0f; + const Real scalar = pow(MATERIAL_PASS_OPACITY_MIN, updatesPerSec / TheFramePacer->getUpdateFps()); + + m_secondMaterialPassOpacityScalar = scalar; +} //------------------------------------------------------------------------------------------------- /** default draw is to just call the database defined draw */ @@ -2615,24 +2624,19 @@ void Drawable::draw() } else if (!TheFramePacer->isGameHalted()) { - // TheSuperHackers @tweak The opacity step is no longer a fixed value. - const Bool shouldFade = (m_secondMaterialPassOpacity > MATERIAL_PASS_OPACITY_MIN); + const Bool shouldFade = m_secondMaterialPassOpacity > MATERIAL_PASS_OPACITY_MIN; const Bool allowRefill = m_secondMaterialPassOpacityAllowRefill; if (shouldFade || allowRefill) { - // min opacity = (X ^ (frame rate / updates per second)) -> e.g. [ 0.05 = X ^ (100 / 2) ] -> [ X = 0.941845 ] -> [ 0.941845 ^ 50 = 0.05 ] - const Real updatesPerSec = 2.0f; - const Real scalar = pow(MATERIAL_PASS_OPACITY_MIN, updatesPerSec / TheFramePacer->getUpdateFps()); - if (!shouldFade && allowRefill) { - m_secondMaterialPassOpacity = scalar; + m_secondMaterialPassOpacity = m_secondMaterialPassOpacityScalar; m_secondMaterialPassOpacityAllowRefill = FALSE; } else { - m_secondMaterialPassOpacity *= scalar; + m_secondMaterialPassOpacity *= m_secondMaterialPassOpacityScalar; } } else From 21dd3e37ef6092707eac69aa438a37f4aa331dcb Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Fri, 19 Dec 2025 23:59:37 +0100 Subject: [PATCH 4/5] Added call to `updateSecondMaterialPassOpacityScalar` from engine loop. --- GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 3c183794f8..3cfd5458fd 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -1003,6 +1003,7 @@ void GameEngine::execute( void ) } TheFramePacer->update(); + Drawable::updateSecondMaterialPassOpacityScalar(); } #ifdef PERF_TIMERS From 45d821ea19b5ca3d04facc4e0fcbc1f69258c8a9 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:10:19 +0100 Subject: [PATCH 5/5] Fix for 'static inline' class member for VC6. --- GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h | 6 +++--- GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h index a75e342390..35c8b4d4b0 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h @@ -725,9 +725,9 @@ class Drawable : public Thing, UnsignedInt m_expirationDate; ///< if nonzero, Drawable should destroy itself at this frame DrawableIconInfo* m_iconInfo; ///< lazily allocated! - static inline Real m_secondMaterialPassOpacityScalar; ///< multiply opacity by scalar value; used for non-default render framerates - Real m_secondMaterialPassOpacity; ///< drawable gets rendered again in hardware with an extra material layer - Bool m_secondMaterialPassOpacityAllowRefill; ///< allow the second material opacity to be set to 1.0f + static Real s_secondMaterialPassOpacityScalar; ///< multiply opacity by scalar value; used for non-default render framerates + Real m_secondMaterialPassOpacity; ///< drawable gets rendered again in hardware with an extra material layer + Bool m_secondMaterialPassOpacityAllowRefill; ///< allow the second material opacity to be set to 1.0f // --------- BYTE-SIZED THINGS GO HERE Byte m_selected; ///< drawable is selected or not Bool m_hidden; ///< drawable is "hidden" or not (overrides stealth effects) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 1bf1fafb34..32a3e9279a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -256,6 +256,8 @@ const Int MAX_ENABLED_MODULES = 16; // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ +/*static*/ Real Drawable::s_secondMaterialPassOpacityScalar = 1.0f; + /*static*/ Bool Drawable::s_staticImagesInited = false; /*static*/ const Image* Drawable::s_veterancyImage[LEVEL_COUNT] = { NULL }; /*static*/ const Image* Drawable::s_fullAmmo = NULL; @@ -2608,7 +2610,7 @@ void Drawable::updateSecondMaterialPassOpacityScalar() const Real updatesPerSec = 2.0f; const Real scalar = pow(MATERIAL_PASS_OPACITY_MIN, updatesPerSec / TheFramePacer->getUpdateFps()); - m_secondMaterialPassOpacityScalar = scalar; + s_secondMaterialPassOpacityScalar = scalar; } //------------------------------------------------------------------------------------------------- @@ -2631,12 +2633,12 @@ void Drawable::draw() { if (!shouldFade && allowRefill) { - m_secondMaterialPassOpacity = m_secondMaterialPassOpacityScalar; + m_secondMaterialPassOpacity = s_secondMaterialPassOpacityScalar; m_secondMaterialPassOpacityAllowRefill = FALSE; } else { - m_secondMaterialPassOpacity *= m_secondMaterialPassOpacityScalar; + m_secondMaterialPassOpacity *= s_secondMaterialPassOpacityScalar; } } else