mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-15 15:40:37 +00:00
Vanilla 1.12 only stores the single active tracking spell
(VAR_ACTIVE_TRACKING_SPELL, 0xBC6378) and never enumerates the tracking
abilities the player knows. Add GetNumTrackingTypes/GetTrackingInfo/
SetTracking, which rebuild that list by scanning the player spellbook for
spells whose EffectApplyAuraName is Track Creatures/Resources/Stealthed
(44/45/151) -- the same discriminator the engine's own active-tracker
updater (FUN_004E4170) uses. This reproduces pfUI's hardcoded tracking
table with no table to maintain and auto-covers server-custom trackers
(e.g. Turtle's Find Trees, 52917).
- GetTrackingInfo returns name, texture, active, category ("spell"),
spellID; active is read from VAR_ACTIVE_TRACKING_SPELL.
- SetTracking selects a tracker by tail-calling the engine's
Script_CastSpell(slot, "spell").
- Add offsets VAR_ACTIVE_TRACKING_SPELL and FUN_SCRIPT_CAST_SPELL
(0x4B42F0), both with RE-provenance comments.
- Document under docs/API.md (Tracking section).
Verified in-game on a Hunter (8 trackers) and a Dwarf racial (1):
active flips on SetTracking, texture matches native GetTrackingTexture,
out-of-range returns nil.
188 lines
8.1 KiB
C++
188 lines
8.1 KiB
C++
// 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/>.
|
|
|
|
#include "Game.h"
|
|
#include "Offsets.h"
|
|
#include "dbc/Lookup.h"
|
|
#include "spell/Lookup.h"
|
|
|
|
#include <cstdint>
|
|
|
|
// `GetNumTrackingTypes()` / `GetTrackingInfo(index)` / `SetTracking(index)` —
|
|
// backport of the minimap-tracking enumeration the modern client exposes.
|
|
//
|
|
// Vanilla 1.12 never enumerates the tracking abilities the player KNOWS — it
|
|
// only stores the single currently-active tracker as a plain spellID in
|
|
// `VAR_ACTIVE_TRACKING_SPELL`, surfaced through `GetTrackingTexture()` /
|
|
// `CancelTrackingBuff()` / `GameTooltip:SetTrackingSpell()`. 3.3.5's
|
|
// `GetTrackingInfo` iterates a spellbook-derived list of tracking spells and
|
|
// reports name/texture/active/category per entry; we rebuild that list here.
|
|
//
|
|
// A tracking spell is identified exactly the way the engine's own active-
|
|
// tracker updater `FUN_004E4170` identifies one: a Spell.dbc record with an
|
|
// `EffectApplyAuraName` of Track Creatures (44), Track Resources (45), or
|
|
// Track Stealthed (151). That effect-based test reproduces pfUI's hardcoded
|
|
// spellID/icon table (Find Herbs/Minerals/Treasure/Trees, every Hunter
|
|
// Track*, Sense Undead/Demons, Track Humanoids) with no table to maintain,
|
|
// and auto-covers server-custom trackers such as Turtle's Find Trees (52917).
|
|
namespace Spell::Tracking {
|
|
|
|
// Tracking aura effect types — SPELL_AURA_TRACK_* ApplyAura names, the same
|
|
// values `FUN_004E4170` scans a spell's three effects for.
|
|
static constexpr int32_t AURA_TRACK_CREATURES = 44;
|
|
static constexpr int32_t AURA_TRACK_RESOURCES = 45;
|
|
static constexpr int32_t AURA_TRACK_STEALTHED = 151;
|
|
|
|
// True iff `spellID`'s record has any of the three Track* effects.
|
|
static bool IsTrackingSpell(int spellID) {
|
|
const uint8_t *record = Spell::Lookup::RecordForID(spellID);
|
|
if (record == nullptr)
|
|
return false;
|
|
auto *aura = Game::Ptr<const int32_t>(
|
|
record, Offsets::OFF_SPELL_RECORD_EFFECT_APPLY_AURA_NAME);
|
|
for (int i = 0; i < Offsets::SPELL_RECORD_EFFECT_COUNT; ++i) {
|
|
if (aura[i] == AURA_TRACK_CREATURES || aura[i] == AURA_TRACK_RESOURCES ||
|
|
aura[i] == AURA_TRACK_STEALTHED)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Walks the player spellbook in slot order counting tracking spells. If
|
|
// `wantNth >= 1` and an Nth tracking spell exists, writes its spellID and
|
|
// 1-based spellbook slot to `*outSpellID` / `*outSlot`. Returns the total
|
|
// number of tracking spells found (so callers detect out-of-range by
|
|
// comparing the return against `wantNth`).
|
|
//
|
|
// The spellbook array is zero-padded past the populated count (see
|
|
// `Spell::Lookup::FindSpellbookSlot`), so a 0 entry is just an empty slot —
|
|
// skip it and keep scanning the full range rather than break, matching the
|
|
// existing spellbook walkers.
|
|
static int EnumerateTracking(int wantNth, int *outSpellID, int *outSlot) {
|
|
auto *book = reinterpret_cast<const int *>(
|
|
static_cast<uintptr_t>(Offsets::VAR_PLAYER_SPELLBOOK));
|
|
int found = 0;
|
|
for (int i = 0; i < Offsets::SPELLBOOK_MAX_SLOTS; ++i) {
|
|
const int spellID = book[i];
|
|
if (spellID <= 0 || !IsTrackingSpell(spellID))
|
|
continue;
|
|
++found;
|
|
if (found == wantNth) {
|
|
if (outSpellID != nullptr)
|
|
*outSpellID = spellID;
|
|
if (outSlot != nullptr)
|
|
*outSlot = i + 1;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
// `GetNumTrackingTypes()` -> number of tracking spells in the player's book.
|
|
static int __fastcall Script_GetNumTrackingTypes(void *L) {
|
|
Game::Lua::PushNumber(L,
|
|
static_cast<double>(EnumerateTracking(0, nullptr, nullptr)));
|
|
return 1;
|
|
}
|
|
|
|
// `GetTrackingInfo(index)` -> name, texture, active, category, spellID.
|
|
// - index is 1-based into the tracking list (spellbook order).
|
|
// - texture is the SpellIcon.dbc path (nil if the icon is missing).
|
|
// - active is a boolean: the tracker currently in effect.
|
|
// - category is always "spell" — vanilla has no non-spell ("other")
|
|
// trackers, unlike 3.3.5's class-masked table.
|
|
// - spellID is a ClassicAPI extension (the modern 5th return is `nested`,
|
|
// which has no meaning here); handy since selecting/among trackers is
|
|
// spellID-driven.
|
|
// Returns nil for an out-of-range index.
|
|
static int __fastcall Script_GetTrackingInfo(void *L) {
|
|
if (!Game::Lua::IsNumber(L, 1)) {
|
|
Game::Lua::Error(L, "Usage: GetTrackingInfo(index)");
|
|
return 0;
|
|
}
|
|
const int index = static_cast<int>(Game::Lua::ToNumber(L, 1));
|
|
if (index < 1)
|
|
return 0;
|
|
|
|
int spellID = 0;
|
|
int slot = 0;
|
|
if (EnumerateTracking(index, &spellID, &slot) < index)
|
|
return 0; // index past the number of tracking types -> nil
|
|
|
|
const uint8_t *record = Spell::Lookup::RecordForID(spellID);
|
|
const int locale = Game::Read<int>(static_cast<uintptr_t>(Offsets::VAR_LOCALE_INDEX));
|
|
const char *name =
|
|
Game::Read<const char *>(record, Offsets::OFF_SPELL_NAMES + locale * 4);
|
|
|
|
const char *texture = nullptr;
|
|
const int iconID = Game::Read<int>(record, Offsets::OFF_SPELL_RECORD_ICON_ID);
|
|
if (auto *iconRec = DBC::Record(Offsets::VAR_SPELL_ICON_RECORDS,
|
|
Offsets::VAR_SPELL_ICON_COUNT,
|
|
static_cast<uint32_t>(iconID))) {
|
|
texture = Game::Read<const char *>(iconRec, Offsets::OFF_SPELLICON_PATH);
|
|
}
|
|
|
|
const bool active =
|
|
Game::Read<int>(static_cast<uintptr_t>(Offsets::VAR_ACTIVE_TRACKING_SPELL)) ==
|
|
spellID;
|
|
|
|
Game::Lua::PushString(L, name); // 1. name (PushString handles NULL -> nil)
|
|
Game::Lua::PushString(L, texture); // 2. texture (icon path) or nil
|
|
Game::Lua::PushBool(L, active); // 3. active
|
|
Game::Lua::PushString(L, "spell"); // 4. category
|
|
Game::Lua::PushNumber(L, static_cast<double>(spellID)); // 5. spellID (extension)
|
|
return 5;
|
|
}
|
|
|
|
// `SetTracking(index)` — selects (activates) the tracking spell at `index`.
|
|
// Vanilla has no "set tracking" primitive: choosing a tracker IS casting its
|
|
// spell (the tracking dropdown does exactly this). So we resolve the entry's
|
|
// spellbook slot and tail-call the engine's own `Script_CastSpell(slot,
|
|
// "spell")` — reusing its slot bounds-check, target resolution, and error
|
|
// handling rather than reconstructing the cast path. Returns nil for an
|
|
// out-of-range index.
|
|
static int __fastcall Script_SetTracking(void *L) {
|
|
if (!Game::Lua::IsNumber(L, 1)) {
|
|
Game::Lua::Error(L, "Usage: SetTracking(index)");
|
|
return 0;
|
|
}
|
|
const int index = static_cast<int>(Game::Lua::ToNumber(L, 1));
|
|
if (index < 1)
|
|
return 0;
|
|
|
|
int spellID = 0;
|
|
int slot = 0;
|
|
if (EnumerateTracking(index, &spellID, &slot) < index)
|
|
return 0;
|
|
|
|
// Rebuild the stack as CastSpell(slot, "spell") and hand off to the
|
|
// engine handler. `slot` is 1-based, matching what a Lua `CastSpell`
|
|
// caller passes.
|
|
Game::Lua::SetTop(L, 0);
|
|
Game::Lua::PushNumber(L, static_cast<double>(slot));
|
|
Game::Lua::PushString(L, "spell");
|
|
using CastSpell_t = int(__fastcall *)(void *);
|
|
auto castSpell = reinterpret_cast<CastSpell_t>(Offsets::FUN_SCRIPT_CAST_SPELL);
|
|
return castSpell(L);
|
|
}
|
|
|
|
static void RegisterLuaFunctions() {
|
|
Game::Lua::RegisterGlobalFunction("GetNumTrackingTypes",
|
|
&Script_GetNumTrackingTypes);
|
|
Game::Lua::RegisterGlobalFunction("GetTrackingInfo", &Script_GetTrackingInfo);
|
|
Game::Lua::RegisterGlobalFunction("SetTracking", &Script_SetTracking);
|
|
}
|
|
|
|
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
|
|
|
} // namespace Spell::Tracking
|