mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-10 20:07:37 +00:00
bag: gate BAG_UPDATE_DELAYED on PLAYER_ENTERING_WORLD, not VAR_IN_WORLD
The in-world gate added in 5d785ea polled 0x00B4E37C, believed to be an
"in world" flag. It isn't: verified in the binary it's instance
grace-period state, written from a packet handler (FUN_0049e6c0) and
read only by the "recently left an instance" 90-second window check
(FUN_00495c90). It never reliably goes non-zero from being in the world,
so OnWorldTick's early-return held g_pending forever and
BAG_UPDATE_DELAYED stopped firing entirely. There is no clean pollable
in-world boolean at these offsets (0x00B4E378 is the map ID; map 0 is
valid).
Gate on the correct signal instead: PLAYER_ENTERING_WORLD, already owned
by Event::EnteringWorld. Expose a one-way HasEnteredWorld() latch flipped
the first time PEW fires (loading screen done, world live) and stays true
across /reload; UpdateDelayed holds g_pending until then, then emits one
DELAYED covering the settled login inventory. Correct the mislabeled
0x00B4E37C note in Offsets.h so the trap isn't repeated.
This commit is contained in:
@@ -3825,13 +3825,6 @@ 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
|
||||
// as a u16 in `SMSG_PARTY_MEMBER_STATS`). Written by the zone-change
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
#include "Offsets.h"
|
||||
#include "bag/UpdateDelayed.h"
|
||||
#include "event/Custom.h"
|
||||
#include "event/EnteringWorld.h"
|
||||
#include "tick/WorldTick.h"
|
||||
|
||||
namespace Bag::UpdateDelayed {
|
||||
@@ -132,11 +133,12 @@ void OnWorldTick() {
|
||||
// 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)
|
||||
// pending flag — do NOT clear it — until PLAYER_ENTERING_WORLD has fired
|
||||
// (the loading screen is done); the first in-world frame then emits a single
|
||||
// DELAYED covering the settled login inventory (matching retail's one
|
||||
// BAG_UPDATE_DELAYED at login). There is no reliable pollable in-world flag
|
||||
// in the binary — see Event::EnteringWorld::HasEnteredWorld.
|
||||
if (!Event::EnteringWorld::HasEnteredWorld())
|
||||
return;
|
||||
g_pending = false;
|
||||
const int slot = Event::Custom::Lookup(kEventName);
|
||||
|
||||
@@ -38,12 +38,19 @@
|
||||
|
||||
#include "Game.h"
|
||||
#include "event/Custom.h"
|
||||
#include "event/EnteringWorld.h"
|
||||
#include "event/SignalHook.h"
|
||||
|
||||
namespace Event::EnteringWorld {
|
||||
|
||||
namespace {
|
||||
|
||||
// One-way latch: set the first time PEW fires (see HasEnteredWorld). Stays true
|
||||
// across /reload — the DLL isn't reloaded, and once the world is live it stays
|
||||
// live for the session. Consumed by modules that must idle behind the initial-
|
||||
// login loading screen (e.g. Bag::UpdateDelayed).
|
||||
bool g_enteredWorld = false;
|
||||
|
||||
// Set on glue-screen boot; a pending latch means the next enter-world is a
|
||||
// fresh character login (from the login/char-select screen).
|
||||
bool g_atGlue = false;
|
||||
@@ -67,6 +74,8 @@ bool Intercept(int eventID) {
|
||||
if (pew < 0 || eventID != pew)
|
||||
return false;
|
||||
|
||||
g_enteredWorld = true; // the loading screen is done; the world is live
|
||||
|
||||
const bool isInitialLogin = g_atGlue;
|
||||
g_atGlue = false;
|
||||
const bool isReloadingUi = !isInitialLogin && g_scriptReloaded;
|
||||
@@ -106,4 +115,6 @@ const Game::GlueModuleAutoRegister _glue{&OnGlueInit};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool HasEnteredWorld() { return g_enteredWorld; }
|
||||
|
||||
} // namespace Event::EnteringWorld
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
// `Event::EnteringWorld` owns the single interception of PLAYER_ENTERING_WORLD
|
||||
// (see EnteringWorld.cpp). It also exposes the one durable fact other modules
|
||||
// need from that signal: whether the player has finished at least one
|
||||
// enter-world transition this session — i.e. the initial-login loading screen
|
||||
// is done and the world is live.
|
||||
//
|
||||
// This is the reliable "in world" gate. There is NO clean pollable in-world
|
||||
// boolean in the 1.12 binary: 0x00B4E378 is the current map ID (map 0 is
|
||||
// valid) and 0x00B4E37C is instance grace-period state written from a packet
|
||||
// handler, not a per-frame flag. PLAYER_ENTERING_WORLD, which fires once the
|
||||
// loading screen completes and the world is ready, is the correct edge.
|
||||
|
||||
namespace Event::EnteringWorld {
|
||||
|
||||
// True once PLAYER_ENTERING_WORLD has fired at least once this session (initial
|
||||
// login, /reload, or a zone/instance transition — whichever comes first).
|
||||
// One-way latch: false only during the initial-login loading screen, before the
|
||||
// world is live. Process-global, so it stays true across /reload.
|
||||
bool HasEnteredWorld();
|
||||
|
||||
} // namespace Event::EnteringWorld
|
||||
Reference in New Issue
Block a user