bag: hold BAG_UPDATE_DELAYED until in-world

The per-frame world tick (FUN_0066FD50) runs behind the initial-login
loading screen, and the engine populates every bag slot during the
initial object burst -- setting g_pending while still loading. That made
OnWorldTick fire BAG_UPDATE_DELAYED repeatedly mid-load, so addons
rescanned bags for inventory that wasn't a real change yet.

Gate the drain on the engine's in-world flag (new VAR_IN_WORLD =
0x00B4E37C): hold g_pending -- without clearing -- until the flag is set.
The first in-world frame then emits a single DELAYED covering the settled
login inventory (matching retail's one BAG_UPDATE_DELAYED at login), and
a pending update suppressed during a later zone transition fires on
return. The three bag hooks are unchanged (still just set the flag).
This commit is contained in:
Brues
2026-07-30 11:33:55 -05:00
parent e84673dde5
commit 5d785ea0d3
2 changed files with 17 additions and 0 deletions
+6
View File
@@ -3825,6 +3825,12 @@ enum Offsets {
// `Script_IsInInstance`).
VAR_CURRENT_MAP_ID = 0x00B4E378,
// "In world" flag — non-zero when the player is in the world, zero while
// in a loading / transitioning state (initial-login load screen, zone /
// instance transitions). Sibling global to VAR_CURRENT_MAP_ID; used to
// gate work that shouldn't run behind a loading screen.
VAR_IN_WORLD = 0x00B4E37C,
// Local player's current AreaTable.dbc area ID (u32 storage; only
// the low 16 bits are used — the value is broadcast over the wire
+11
View File
@@ -127,6 +127,17 @@ unsigned __fastcall KeyringDescChange_h(void *bagDesc, void *edx,
void OnWorldTick() {
if (!g_pending)
return;
// Bag changes aren't meaningful until the player is actually in the world.
// During the initial-login object burst the engine populates every bag
// slot (setting g_pending) while still behind the loading screen; the
// per-frame world tick runs there too, so firing BAG_UPDATE_DELAYED would
// make addons rescan bags repeatedly mid-load for no real change. Hold the
// pending flag — do NOT clear it — until the engine's in-world flag is set;
// the first in-world frame then emits a single DELAYED covering the settled
// login inventory (matching retail's one BAG_UPDATE_DELAYED at login). A
// transient clear during a later zone transition behaves the same way.
if (*reinterpret_cast<const volatile uint32_t *>(Offsets::VAR_IN_WORLD) == 0)
return;
g_pending = false;
const int slot = Event::Custom::Lookup(kEventName);
if (slot >= 0)