Skip to content

Commit c80d1b3

Browse files
Zephkekclaude
andcommitted
Fix #4803 building light flicker via pre-CWeather::Update hook
PR #4870 hooks at CWorld::Process (0x53C095) to re-apply MTA's blended weather before CTimeCycle::CalcColoursForPoint reads the weather globals at 0x53C0DA. That is in time for the sky/ambient colour set, but it is too late for CWeather::Update itself: when setTime() jumps the game clock past CWeather::InterpolationValue, CWeather::Update (called at 0x53BFC2, earlier in CGame::Process) takes its clock-wrap branch and re-derives Rain, Foggyness, CloudCoverage, ExtraSunnyness, SunGlare, HeatHaze, Sandstorm, WetRoads, Wind and Rainbow from a freshly-picked weather pair. Those globals drive cloud, fog and night-time building light rendering for the rest of the frame, so building lights still flickered after PR #4870. Add a HookInstallCall over the call site at 0x53BFC2 and route a new PreWeatherUpdateHandler through CMultiplayerSA -> CClientGame, calling CBlendedWeather::DoPulse before CWeather::Update. DoPulse re-applies Old/New via Set and re-syncs InterpolationValue with the game clock, so CWeather::Update takes the non-wrap branch and derives every weather global from MTA's intended pair. The PR #4870 PreWorldProcessHandler hook is kept as a defensive idempotent re-apply. CBlendedWeather::DoPulse now also calls CWeather::ResyncInterpolation WithGameClock after Set so the InterpolationValue used by CalcColoursFor Point matches the clock — fixes glass/reflective material flicker that the sky-only fix in #4870 left behind. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent bbf2fad commit c80d1b3

10 files changed

Lines changed: 127 additions & 2 deletions

File tree

Client/game_sa/CWeatherSA.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ void CWeatherSA::Set(unsigned char primary, unsigned char secondary)
2525
MemPutFast<unsigned char>(0xC8131C, secondary); // CWeather::NewWeatherType
2626
}
2727

28+
void CWeatherSA::ResyncInterpolationWithGameClock(unsigned char primary, unsigned char secondary)
29+
{
30+
// CWeather::InterpolationValue — see plugin_sa CWeather.cpp (0xC8130C)
31+
constexpr DWORD VAR_InterpolationValue = 0xC8130C;
32+
constexpr DWORD VAR_TimeMinutes = 0xB70152;
33+
34+
if (primary == secondary)
35+
MemPutFast<float>(VAR_InterpolationValue, 0.0f);
36+
else
37+
{
38+
const auto ucMinute = *reinterpret_cast<unsigned char*>(VAR_TimeMinutes);
39+
const float fInterp = std::min(1.f, static_cast<float>(ucMinute) / 60.f);
40+
MemPutFast<float>(VAR_InterpolationValue, fInterp);
41+
}
42+
}
43+
2844
void CWeatherSA::Release()
2945
{
3046
MemPutFast<unsigned char>(0xC81318, 0xFF); // CWeather::ForcedWeatherType

Client/game_sa/CWeatherSA.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class CWeatherSA : public CWeather
2020
public:
2121
unsigned char Get();
2222
void Set(unsigned char primary, unsigned char secondary);
23+
void ResyncInterpolationWithGameClock(unsigned char primary, unsigned char secondary);
2324

2425
void Release();
2526

Client/mods/deathmatch/logic/CBlendedWeather.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,14 @@ void CBlendedWeather::DoPulse()
5353
}
5454
}
5555

56-
// Force the weather
57-
m_pWeather->Set(static_cast<unsigned char>(m_ucPrimaryWeather), static_cast<unsigned char>(m_ucSecondaryWeather));
56+
const auto ucPrimary = static_cast<unsigned char>(m_ucPrimaryWeather);
57+
const auto ucSecondary = static_cast<unsigned char>(m_ucSecondaryWeather);
58+
59+
m_pWeather->Set(ucPrimary, ucSecondary);
60+
// CWeather::Update (before this pulse) advances InterpolationValue for its own Old/New pair.
61+
// After we overwrite Old/New with MTA's state, keep the blend weight aligned with the game
62+
// clock so reflective/translucent materials (e.g. glass windows) match the sky (#4803).
63+
m_pWeather->ResyncInterpolationWithGameClock(ucPrimary, ucSecondary);
5864
}
5965

6066
void CBlendedWeather::SetWeather(unsigned char ucWeather)

Client/mods/deathmatch/logic/CClientGame.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ CClientGame::CClientGame(bool bLocalPlay) : m_ServerInfo(new CServerInfo())
285285
g_pMultiplayer->SetRenderHeliLightHandler(CClientGame::StaticRenderHeliLightHandler);
286286
g_pMultiplayer->SetRenderEverythingBarRoadsHandler(CClientGame::StaticRenderEverythingBarRoadsHandler);
287287
g_pMultiplayer->SetChokingHandler(CClientGame::StaticChokingHandler);
288+
g_pMultiplayer->SetPreWeatherUpdateHandler(CClientGame::StaticPreWeatherUpdateHandler);
288289
g_pMultiplayer->SetPreWorldProcessHandler(CClientGame::StaticPreWorldProcessHandler);
289290
g_pMultiplayer->SetPostWorldProcessHandler(CClientGame::StaticPostWorldProcessHandler);
290291
g_pMultiplayer->SetPostWorldProcessPedsAfterPreRenderHandler(CClientGame::StaticPostWorldProcessPedsAfterPreRenderHandler);
@@ -494,6 +495,7 @@ CClientGame::~CClientGame()
494495
g_pMultiplayer->SetRenderHeliLightHandler(nullptr);
495496
g_pMultiplayer->SetRenderEverythingBarRoadsHandler(nullptr);
496497
g_pMultiplayer->SetChokingHandler(NULL);
498+
g_pMultiplayer->SetPreWeatherUpdateHandler(NULL);
497499
g_pMultiplayer->SetPreWorldProcessHandler(NULL);
498500
g_pMultiplayer->SetPostWorldProcessHandler(NULL);
499501
g_pMultiplayer->SetPostWorldProcessPedsAfterPreRenderHandler(nullptr);
@@ -3629,6 +3631,11 @@ bool CClientGame::StaticBlendAnimationHierarchyHandler(CAnimBlendAssociationSAIn
36293631
return g_pClientGame->BlendAnimationHierarchyHandler(pAnimAssoc, pOutAnimHierarchy, pFlags, pClump);
36303632
}
36313633

3634+
void CClientGame::StaticPreWeatherUpdateHandler()
3635+
{
3636+
g_pClientGame->PreWeatherUpdateHandler();
3637+
}
3638+
36323639
void CClientGame::StaticPreWorldProcessHandler()
36333640
{
36343641
g_pClientGame->PreWorldProcessHandler();
@@ -3900,6 +3907,20 @@ void CClientGame::PreRenderSkyHandler()
39003907
g_pCore->GetGraphics()->GetRenderItemManager()->PreDrawWorld();
39013908
}
39023909

3910+
void CClientGame::PreWeatherUpdateHandler()
3911+
{
3912+
// Fix #4803 (building light flicker): Re-apply MTA's weather state BEFORE
3913+
// CWeather::Update runs. CWeather::Update detects a clock wrap when setTime()
3914+
// jumps the game clock past InterpolationValue and re-derives Rain, Foggyness,
3915+
// CloudCoverage, ExtraSunnyness, SunGlare, HeatHaze, etc. from a freshly-picked
3916+
// weather pair. Those globals drive cloud, fog and night-time building light
3917+
// rendering for the rest of the frame, and PreWorldProcessHandler runs too late
3918+
// to undo the damage. Pre-syncing Old/New/InterpolationValue here keeps the
3919+
// wrap branch from firing.
3920+
if (m_pManager->IsGameLoaded() && m_pBlendedWeather)
3921+
m_pBlendedWeather->DoPulse();
3922+
}
3923+
39033924
void CClientGame::PreWorldProcessHandler()
39043925
{
39053926
// Fix #4803: Re-apply MTA's weather state before CTimeCycle::CalcColoursForPoint()

Client/mods/deathmatch/logic/CClientGame.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ class CClientGame
577577
static void StaticRenderHeliLightHandler();
578578
static void StaticRenderEverythingBarRoadsHandler();
579579
static bool StaticChokingHandler(unsigned char ucWeaponType);
580+
static void StaticPreWeatherUpdateHandler();
580581
static void StaticPreWorldProcessHandler();
581582
static void StaticPostWorldProcessHandler();
582583
static void StaticPostWorldProcessPedsAfterPreRenderHandler();
@@ -626,6 +627,7 @@ class CClientGame
626627
void Render3DStuffHandler();
627628
void PreRenderSkyHandler();
628629
bool ChokingHandler(unsigned char ucWeaponType);
630+
void PreWeatherUpdateHandler();
629631
void PreWorldProcessHandler();
630632
void PostWorldProcessHandler();
631633
void PostWorldProcessPedsAfterPreRenderHandler();

Client/multiplayer_sa/CMultiplayerSA.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ DWORD RETURN_CTrafficLights_DisplayActualLight = 0x49E1FF;
8888
#define HOOKPOS_CGame_Process 0x53C095
8989
DWORD RETURN_CGame_Process = 0x53C09F;
9090

91+
#define CALL_CWeather_Update_FromCGameProcess 0x53BFC2
92+
DWORD FUNC_CWeather_Update = 0x72B850;
93+
9194
#define HOOKPOS_Idle 0x53E981
9295
DWORD RETURN_Idle = 0x53E98B;
9396

@@ -398,6 +401,7 @@ ExplosionHandler* m_pExplosionHandler = NULL;
398401
BreakTowLinkHandler* m_pBreakTowLinkHandler = NULL;
399402
DrawRadarAreasHandler* m_pDrawRadarAreasHandler = NULL;
400403
Render3DStuffHandler* m_pRender3DStuffHandler = NULL;
404+
PreWeatherUpdateHandler* m_pPreWeatherUpdateHandler = NULL;
401405
PreWorldProcessHandler* m_pPreWorldProcessHandler = NULL;
402406
PostWorldProcessHandler* m_pPostWorldProcessHandler = NULL;
403407
PostWorldProcessPedsAfterPreRenderHandler* m_postWorldProcessPedsAfterPreRenderHandler = nullptr;
@@ -461,6 +465,7 @@ void HOOK_CFire_ProcessFire();
461465
void HOOK_CExplosion_Update();
462466
void HOOK_CWeapon_FireAreaEffect();
463467
void HOOK_CGame_Process();
468+
void HOOK_CWeather_Update();
464469
void HOOK_Idle();
465470
void HOOK_RenderScene_Plants();
466471
void HOOK_RenderScene_end();
@@ -663,6 +668,7 @@ void CMultiplayerSA::InitHooks()
663668
HookInstall(HOOKPOS_CExplosion_Update, (DWORD)HOOK_CExplosion_Update, 5);
664669
HookInstall(HOOKPOS_CWeapon_FireAreaEffect, (DWORD)HOOK_CWeapon_FireAreaEffect, 5);
665670
HookInstall(HOOKPOS_CGame_Process, (DWORD)HOOK_CGame_Process, 10);
671+
HookInstallCall(CALL_CWeather_Update_FromCGameProcess, (DWORD)HOOK_CWeather_Update);
666672
HookInstall(HOOKPOS_Idle, (DWORD)HOOK_Idle, 10);
667673
HookInstall(HOOKPOS_CEventHandler_ComputeKnockOffBikeResponse, (DWORD)HOOK_CEventHandler_ComputeKnockOffBikeResponse, 7);
668674
HookInstall(HOOKPOS_CPed_GetWeaponSkill, (DWORD)HOOK_CPed_GetWeaponSkill, 8);
@@ -2652,6 +2658,11 @@ void CMultiplayerSA::SetChokingHandler(ChokingHandler* pChokingHandler)
26522658
m_pChokingHandler = pChokingHandler;
26532659
}
26542660

2661+
void CMultiplayerSA::SetPreWeatherUpdateHandler(PreWeatherUpdateHandler* pHandler)
2662+
{
2663+
m_pPreWeatherUpdateHandler = pHandler;
2664+
}
2665+
26552666
void CMultiplayerSA::SetPreWorldProcessHandler(PreWorldProcessHandler* pHandler)
26562667
{
26572668
m_pPreWorldProcessHandler = pHandler;
@@ -5338,6 +5349,36 @@ static void __declspec(naked) HOOK_ApplyCarBlowHop()
53385349

53395350
// ---------------------------------------------------
53405351

5352+
static void Pre_CWeather_Update()
5353+
{
5354+
if (m_pPreWeatherUpdateHandler)
5355+
m_pPreWeatherUpdateHandler();
5356+
}
5357+
5358+
// Replaces the `call CWeather::Update` site at 0x53BFC2 inside CGame::Process so that
5359+
// MTA can re-apply its blended weather state before the engine reads it. CWeather::Update
5360+
// detects a clock wrap when setTime() jumps the game clock past InterpolationValue and
5361+
// derives Rain/Foggyness/CloudCoverage/SunGlare/etc. from a freshly-picked weather pair —
5362+
// values that drive cloud, fog and night-time building light rendering. Restoring MTA's
5363+
// state here keeps that wrap branch from firing.
5364+
static void __declspec(naked) HOOK_CWeather_Update()
5365+
{
5366+
MTA_VERIFY_HOOK_LOCAL_SIZE;
5367+
5368+
// clang-format off
5369+
__asm
5370+
{
5371+
pushad
5372+
call Pre_CWeather_Update
5373+
popad
5374+
5375+
mov eax, FUNC_CWeather_Update
5376+
call eax
5377+
ret
5378+
}
5379+
// clang-format on
5380+
}
5381+
53415382
static void Pre_CGame_Process()
53425383
{
53435384
TIMING_CHECKPOINT("+CWorld_Process");

Client/multiplayer_sa/CMultiplayerSA.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class CMultiplayerSA : public CMultiplayer
118118
void SetBreakTowLinkHandler(BreakTowLinkHandler* pBreakTowLinkHandler);
119119
void SetProcessCamHandler(ProcessCamHandler* pProcessCamHandler);
120120
void SetChokingHandler(ChokingHandler* pChokingHandler);
121+
void SetPreWeatherUpdateHandler(PreWeatherUpdateHandler* pHandler);
121122
void SetPreWorldProcessHandler(PreWorldProcessHandler* pHandler);
122123
void SetPostWorldProcessHandler(PostWorldProcessHandler* pHandler);
123124
void SetPostWorldProcessPedsAfterPreRenderHandler(PostWorldProcessPedsAfterPreRenderHandler* pHandler);

Client/sdk/game/CWeather.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ class CWeather
1818
public:
1919
virtual unsigned char Get() = 0;
2020
virtual void Set(unsigned char primary, unsigned char secondary) = 0;
21+
// After Set(), if the engine had diverged from MTA's intended types (e.g. CWeather::Update
22+
// clock-wrap in #4803), realign InterpolationValue with the game clock so materials that blend
23+
// weather heavily (glass / reflections) match the sky.
24+
virtual void ResyncInterpolationWithGameClock(unsigned char primary, unsigned char secondary) = 0;
2125
virtual void Release() = 0;
2226

2327
virtual float GetAmountOfRain() = 0;

Client/sdk/multiplayer/CMultiplayer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ typedef void(Render3DStuffHandler)();
9797
typedef void(PreRenderSkyHandler)();
9898
typedef void(RenderHeliLightHandler)();
9999
typedef bool(ChokingHandler)(unsigned char ucWeaponType);
100+
typedef void(PreWeatherUpdateHandler)();
100101
typedef void(PreWorldProcessHandler)();
101102
typedef void(PostWorldProcessHandler)();
102103
typedef void(PostWorldProcessPedsAfterPreRenderHandler)();
@@ -228,6 +229,7 @@ class CMultiplayer
228229
virtual void SetChokingHandler(ChokingHandler* pChokingHandler) = 0;
229230
virtual void SetProjectileHandler(ProjectileHandler* pProjectileHandler) = 0;
230231
virtual void SetProjectileStopHandler(ProjectileStopHandler* pProjectileHandler) = 0;
232+
virtual void SetPreWeatherUpdateHandler(PreWeatherUpdateHandler* pHandler) = 0;
231233
virtual void SetPreWorldProcessHandler(PreWorldProcessHandler* pHandler) = 0;
232234
virtual void SetPostWorldProcessHandler(PostWorldProcessHandler* pHandler) = 0;
233235
virtual void SetPostWorldProcessPedsAfterPreRenderHandler(PostWorldProcessPedsAfterPreRenderHandler* pHandler) = 0;

PR_WEATHER_FLICKER_4803.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#### Summary
2+
3+
Re-apply MTA's blended weather state both **before** `CWeather::Update` and before `CTimeCycle::CalcColoursForPoint`, so neither the engine's clock-wrap branch nor the colour calculation ever observes a stale weather pair.
4+
5+
#### Motivation
6+
7+
Fixes #4803. When `setWeatherBlended`/`setTime` are used (e.g. race resource restart/respawn), the sky and ambient lighting flicker for 1-2 frames in fullscreen, and night-time building lights / cloud / fog also pop for the same frames.
8+
9+
The engine's `CWeather::Update` (called at `0x53BFC2` from `CGame::Process`) detects a clock wrap when `setTime` jumps the game clock past its internal `InterpolationValue`. It then overwrites `OldWeatherType`/`NewWeatherType` with its own weather-list pick **and** uses that wrong pair to derive `Rain`, `Foggyness`, `CloudCoverage`, `ExtraSunnyness`, `SunGlare`, `HeatHaze`, `Sandstorm`, `WetRoads`, `Wind` and `Rainbow` for the frame. `CalcColoursForPoint` (at `0x53C0DA`) then reads `OldWeatherType`/`NewWeatherType`/`InterpolationValue` to compute the sky/ambient colour set.
10+
11+
MTA's `CBlendedWeather::DoPulse` previously only ran in `DoPulses` during `Render2dStuff`, which is after the frame was already rendered with the wrong state.
12+
13+
Two hooks are now used:
14+
15+
1. **`HOOK_CWeather_Update`** — replaces the `call CWeather::Update` site at `0x53BFC2`. Fires `PreWeatherUpdateHandler` *before* the engine's update, so MTA's `Set` + `ResyncInterpolationWithGameClock` re-sync `OldWeatherType`/`NewWeatherType`/`InterpolationValue` with the new clock. `CWeather::Update` then takes the non-wrap branch and derives `Rain`/`Foggyness`/`CloudCoverage`/etc. from MTA's intended pair. This is what fixes the **building light / cloud / fog flicker**.
16+
2. **`PreWorldProcessHandler`** (the original hook from #4870, kept as a defensive idempotent re-apply) — runs at `CWorld::Process` (`0x53C095`), still ahead of `CalcColoursForPoint` at `0x53C0DA`, so the sky/ambient frame is also guaranteed to use MTA's weather pair even if the early hook is bypassed for any reason.
17+
18+
Fullscreen-only because exclusive mode bypasses DWM composition, so every frame including the glitch frames is presented directly. Windowed mode's DWM triple buffering absorbs them.
19+
20+
#### Test plan
21+
22+
1. Join a server running the default race resource
23+
2. Set fullscreen (any mode)
24+
3. Use `setWeatherBlended` + `setTime` via script, then restart the race resource to respawn
25+
4. Observe no sky/lighting flicker on respawn
26+
5. Repeat at night-time so building/streetlight glow and cloud cover are visible — they should not pop either
27+
28+
#### Checklist
29+
30+
* [x] Your code should follow the [coding guidelines](https://wiki.multitheftauto.com/index.php?title=Coding_guidelines).
31+
* [x] Smaller pull requests are easier to review. If your pull request is beefy, your pull request should be reviewable commit-by-commit.

0 commit comments

Comments
 (0)