Skip to content

Commit e760d5f

Browse files
committed
Drop focus when out of render distance
1 parent d0b6a7c commit e760d5f

2 files changed

Lines changed: 37 additions & 0 deletions

File tree

docs/API.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2168,6 +2168,13 @@ loads that recreate Lua state. Modern Classic Era behaves the same;
21682168
addons that want persistence have to re-`FocusUnit` from `SavedVariables`
21692169
at `ADDON_LOADED`.
21702170

2171+
**Auto-clear on despawn.** When the focused unit leaves the client's
2172+
object table — out of rendering range, full despawn, dies and
2173+
decays — focus drops and [`PLAYER_FOCUS_CHANGED`](#player_focus_changed-event)
2174+
fires. The unit re-entering range does NOT auto-refocus. Matches
2175+
modern WoW's documented behavior. Implementation: per-tick
2176+
`ObjectByGUID(g_focusGUID)` probe; on null result, `Set(0)`.
2177+
21712178
### `FocusUnit(unit)`
21722179

21732180
Sets focus to the given unit. Argument is a unit token —

src/unit/Focus.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,21 @@
2525
// `UnitFlag bit 0x2000` ("focus glow" rendering hint, introduced in
2626
// TBC for the default focus frame). Vanilla addons (pfUI) render
2727
// their own focus indicator and don't need the hint.
28+
//
29+
// Auto-clear on despawn: modern WoW fires `PLAYER_FOCUS_CHANGED`
30+
// when the focused unit leaves the client's object table (out of
31+
// rendering range, despawn) and does NOT auto-refocus when they
32+
// come back. We mirror that by probing `ObjectByGUID(g_focusGUID)`
33+
// every world tick — when it returns null, we `Set(0)` which fires
34+
// the event. Cost is one hash-table lookup per tick while focus is
35+
// set; zero when no focus is active.
2836

2937
#include "Focus.h"
3038

3139
#include "Game.h"
3240
#include "Offsets.h"
3341
#include "event/Custom.h"
42+
#include "tick/WorldTick.h"
3443

3544
#include <cstdint>
3645

@@ -44,6 +53,9 @@ const Event::Custom::AutoReserve _reserve{kEventName};
4453
uint64_t g_focusGUID = 0;
4554

4655
using TokenToGUID_t = uint64_t(__fastcall *)(const char *token);
56+
using ResolveByGUID_t = void *(__fastcall *)(int type, const char *debugName,
57+
uint32_t guidLo, uint32_t guidHi,
58+
int priority);
4759

4860
uint64_t ResolveTokenGUID(const char *token) {
4961
if (token == nullptr || *token == '\0')
@@ -53,6 +65,23 @@ uint64_t ResolveTokenGUID(const char *token) {
5365
return fn(token);
5466
}
5567

68+
// Per-tick despawn watcher. Probes the engine's object table for
69+
// `g_focusGUID`; when it disappears (out of range, fully despawned),
70+
// clear focus and fire PLAYER_FOCUS_CHANGED — matches modern's
71+
// "leaves render distance → focus drops" behavior. Won't refocus
72+
// when the unit comes back, also matching modern.
73+
void OnWorldTick() {
74+
if (g_focusGUID == 0)
75+
return;
76+
auto resolve = reinterpret_cast<ResolveByGUID_t>(
77+
static_cast<uintptr_t>(Offsets::FUN_OBJECT_RESOLVE_BY_GUID));
78+
if (resolve(Offsets::OBJ_TYPE_UNIT, "Focus",
79+
static_cast<uint32_t>(g_focusGUID),
80+
static_cast<uint32_t>(g_focusGUID >> 32),
81+
0x172) == nullptr)
82+
Set(0);
83+
}
84+
5685
// `FocusUnit(unit)` — sets focus to whatever GUID `unit` currently
5786
// resolves to. Modern signature: `FocusUnit(unit)` with the token
5887
// argument. Modern also allows `FocusUnit()` (no arg) to mean
@@ -80,6 +109,7 @@ void RegisterLuaFunctions() {
80109
}
81110

82111
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
112+
const Tick::WorldTick::AutoSubscribe _tickSub{&OnWorldTick};
83113

84114
} // namespace
85115

0 commit comments

Comments
 (0)