mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-12 21:07:36 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0332e49ac2 | ||
|
|
b1b564a533 | ||
|
|
444875ae38 |
@@ -45,7 +45,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
|
||||
| [Map](docs/API.md#map) | `C_Map.GetBestMapForUnit` |
|
||||
| [MerchantFrame](docs/API.md#merchantframe) | `C_MerchantFrame.GetBuybackItemID`, `C_MerchantFrame.GetItemInfo`, `C_MerchantFrame.GetNumJunkItems`, `C_MerchantFrame.IsMerchantItemRefundable`, `C_MerchantFrame.IsSellAllJunkEnabled`, `C_MerchantFrame.SellAllJunkItems` |
|
||||
| [NamePlate](docs/API.md#nameplate) | `C_NamePlate.GetNamePlateForGUID`, `C_NamePlate.GetNamePlateForUnit`, `C_NamePlate.GetNamePlateGUIDs`, `C_NamePlate.GetNamePlates` |
|
||||
| [NameCache](docs/API.md#namecache) | `C_CreatureInfo.GetCreatureID`, `C_PlayerCache.GetPlayerInfoByName`, `C_PlayerCache.IsEnabled`, `C_PlayerCache.IsScanEnabled`, `C_PlayerCache.RememberPlayer`, `C_PlayerCache.SetEnabled`, `C_PlayerCache.SetScanEnabled`, `C_PlayerInfo.GUIDIsCreature`, `C_PlayerInfo.GUIDIsGameObject`, `C_PlayerInfo.GUIDIsPet`, `C_PlayerInfo.GUIDIsPlayer`, `GetPlayerInfoByGUID` |
|
||||
| [NameCache](docs/API.md#namecache) | `C_CreatureInfo.GetCreatureID`, `C_PlayerCache.GetPlayerInfoByName`, `C_PlayerCache.IsEnabled`, `C_PlayerCache.IsScanEnabled`, `C_PlayerCache.RememberPlayer`, `C_PlayerCache.SetEnabled`, `C_PlayerCache.SetScanEnabled`, `C_PlayerInfo.GUIDIsCreature`, `C_PlayerInfo.GUIDIsGameObject`, `C_PlayerInfo.GUIDIsPet`, `C_PlayerInfo.GUIDIsPlayer`, `GetPlayerInfoByGUID`, `UnitNameFromGUID` |
|
||||
| [Quest](docs/API.md#quest) | `C_QuestLog.GetQuestDetails`, `C_QuestLog.GetQuestIDForLogIndex`, `C_QuestLog.GetTitleForQuestID`, `C_QuestLog.IsOnQuest`, `C_QuestLog.IsQuestDataCachedByID`, `C_QuestLog.IsUnitOnQuest`, `C_QuestLog.RequestLoadQuestByID`, `GetQuestLogLeaderBoardID` |
|
||||
| [Spell](docs/API.md#spell) | `C_Spell.CancelSpellByID`, `C_Spell.CastAtCursor`, `C_Spell.DoesSpellExist`, `C_Spell.GetSchoolString`, `C_Spell.GetSpellCooldown`, `C_Spell.GetSpellDescription`, `C_Spell.GetSpellInfo`, `C_Spell.GetSpellLink`, `C_Spell.GetSpellName`, `C_Spell.GetSpellReagents`, `C_Spell.GetSpellSubtext`, `C_Spell.GetSpellTexture`, `C_Spell.IsAutoAttackSpell`, `C_Spell.IsCurrentSpell`, `C_Spell.IsRangedAutoAttackSpell`, `C_Spell.IsSelfBuff`, `C_Spell.IsSpellHarmful`, `C_Spell.IsSpellHelpful`, `C_Spell.IsSpellPassive`, `C_Spell.IsSpellUsable`, `C_Spell.SpellHasRange`, `CancelSpellByName`, `CastSpellNoToggle`, `GetCraftSpellID`, `GetSpellInfo`, `GetSpellLink`, `GetSpellSchool`, `IsHarmfulSpell`, `IsHelpfulSpell`, `IsPassiveSpell`, `IsPlayerSpell`, `IsSpellKnown`, `IsUsableSpell`, `SpellHasRange` |
|
||||
| [SpellBook](docs/API.md#spellbook) | `C_SpellBook.GetCurrentLevelSpells`, `C_SpellBook.GetSpellLevelLearned`, `C_SpellBook.GetSpellSkillLine`, `C_SpellBook.IsAutoAttackSpellBookItem`, `C_SpellBook.IsRangedAutoAttackSpellBookItem`, `FindSpellBookSlotByID` |
|
||||
@@ -54,7 +54,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
|
||||
| [Talent](docs/API.md#talent) | `GetTalentIDByIndex`, `GetTalentSpellID` |
|
||||
| [Time](docs/API.md#time) | `C_DateAndTime.AdjustTimeByDays`, `C_DateAndTime.AdjustTimeByMinutes`, `C_DateAndTime.CompareCalendarTime`, `C_DateAndTime.GetCalendarTimeFromEpoch`, `C_DateAndTime.GetCurrentCalendarTime`, `C_DateAndTime.GetSecondsUntilDailyReset`, `C_DateAndTime.GetServerTimeLocal`, `C_Timer.After`, `C_Timer.NewTicker`, `C_Timer.NewTimer`, `GetServerTime` |
|
||||
| [UIColor](docs/API.md#uicolor) | `C_UIColor.GetColors` (backports `GlobalColor.dbc`; companion addon wraps each row with `ColorMixin` and assigns `_G[baseTag]` + `_G[baseTag.."_CODE"]`) |
|
||||
| [Unit](docs/API.md#unit) | `GetUnitSpeed`, `UnitClassBase`, `UnitGUID`, `UnitInRange`, `UnitIsAFK`, `UnitIsDND`, `UnitIsFeignDeath`, `UnitIsInMyGuild`, `UnitIsPossessed`, `UnitPower`, `UnitPowerMax`, `UnitPowerType`, `UnitRaceBase`, `UnitStandState`, `UnitTokenFromGUID` |
|
||||
| [Unit](docs/API.md#unit) | `GetUnitSpeed`, `UnitClassBase`, `UnitGUID`, `UnitInRange`, `UnitIsAFK`, `UnitIsDND`, `UnitIsFeignDeath`, `UnitIsInMyGuild`, `UnitIsPossessed`, `UnitPower`, `UnitPowerMax`, `UnitPowerType`, `UnitRaceBase`, `UnitStandState`, `UnitSubName`, `UnitTokenFromGUID` |
|
||||
| [UnitAuras](docs/API.md#unitauras) | `C_UnitAuras.GetAuraDataByIndex`, `C_UnitAuras.GetAuraDataBySpellName`, `C_UnitAuras.GetAuraDispelTypeColor`, `C_UnitAuras.GetBuffDataByIndex`, `C_UnitAuras.GetDebuffDataByIndex`, `C_UnitAuras.GetPlayerAuraBySpellID`, `C_UnitAuras.GetUnitAuraBySpellID`, `C_UnitAuras.GetUnitAuras` |
|
||||
|
||||
### GlueXML calls
|
||||
|
||||
+58
@@ -249,6 +249,7 @@ build instructions.
|
||||
|
||||
- [NameCache](#namecache)
|
||||
- [`GetPlayerInfoByGUID(guid)`](#getplayerinfobyguidguid)
|
||||
- [`UnitNameFromGUID(guid)`](#unitnamefromguidguid)
|
||||
- [`C_PlayerCache.GetPlayerInfoByName(name)`](#c_playercachegetplayerinfobynamename)
|
||||
- [`C_PlayerInfo.GUIDIsPlayer(guid)` / `GUIDIsCreature` / `GUIDIsPet` / `GUIDIsGameObject`](#c_playerinfoguidisplayerguid--guidiscreature--guidispet--guidisgameobject)
|
||||
- [`C_CreatureInfo.GetCreatureID(guid)`](#c_creatureinfogetcreatureidguid)
|
||||
@@ -346,6 +347,7 @@ build instructions.
|
||||
- [Unit](#unit)
|
||||
- [`UnitGUID(unit)`](#unitguidunit)
|
||||
- [`UnitTokenFromGUID(guid)`](#unittokenfromguidguid)
|
||||
- [`UnitSubName(unit)`](#unitsubnameunit)
|
||||
- [`GetUnitSpeed(unit)`](#getunitspeedunit)
|
||||
- [`UnitClassBase(unit)`](#unitclassbaseunit)
|
||||
- [`UnitRaceBase(unit)`](#unitracebaseunit)
|
||||
@@ -6003,6 +6005,35 @@ instance lives at `0x00C0E228`; entry layout (name, realm, race,
|
||||
sex, class) was reverse-engineered from the
|
||||
`SMSG_NAME_QUERY_RESPONSE` write path at `0x0055F310`.
|
||||
|
||||
### `UnitNameFromGUID(guid)`
|
||||
|
||||
Returns `name, realm` for the player identified by `guid`, or `nil`
|
||||
if no player with that GUID has been encountered yet. Same lookup
|
||||
chain as [`GetPlayerInfoByGUID`](#getplayerinfobyguidguid) (engine
|
||||
NameCache, persistent on-disk fallback when enabled) — just narrower
|
||||
return shape for callers that only need the name.
|
||||
|
||||
```lua
|
||||
local name, realm = UnitNameFromGUID(UnitGUID("target"))
|
||||
if name then
|
||||
ChatFrame1:AddMessage("Target: " .. name)
|
||||
end
|
||||
|
||||
-- Resolving a chat link
|
||||
local name, realm = UnitNameFromGUID("0x0000000000000777")
|
||||
```
|
||||
|
||||
`realm` is always `""` in vanilla — the engine doesn't populate
|
||||
per-player realm names and 1.12 has no cross-realm interaction.
|
||||
We push the empty string rather than `nil` to match the convention
|
||||
of the other player-info accessors; addons can gate on `realm == ""`.
|
||||
|
||||
Doesn't trigger a network query on miss. To populate an unknown
|
||||
GUID, either let the engine receive a `SMSG_NAME_QUERY_RESPONSE`
|
||||
through normal chat/target interaction, or call
|
||||
[`C_PlayerCache.RememberPlayer`](#c_playercacherememberplayerguid-name-classtoken)
|
||||
explicitly.
|
||||
|
||||
### `C_PlayerCache.GetPlayerInfoByName(name)`
|
||||
|
||||
Returns
|
||||
@@ -8251,6 +8282,33 @@ end
|
||||
> ex-targets, ex-mouseover units, distant players seen in the chat
|
||||
> log, etc. The engine simply doesn't address those by token.
|
||||
|
||||
### `UnitSubName(unit)`
|
||||
|
||||
Returns the NPC's subtitle / title string (the small italic text
|
||||
under their name in the tooltip): `"Innkeeper"`, `"Stable Master"`,
|
||||
`"<Master Engineer>"`, etc. Returns `nil` for players, unsynced
|
||||
NPCs, and NPCs with no subtitle.
|
||||
|
||||
```lua
|
||||
/dump UnitSubName("target")
|
||||
-- "Innkeeper" (Stormwind innkeep)
|
||||
-- "<Auction House>" (auctioneer)
|
||||
-- nil (a player, or a wolf in the woods)
|
||||
```
|
||||
|
||||
Modern WoW returns the subtitle as `UnitName`'s second return;
|
||||
vanilla 1.12's `UnitName` returns only one value, leaving no
|
||||
direct route to the subtitle. Addons that wanted it had to scrape
|
||||
`GameTooltip` text, which is fragile and required the unit to be
|
||||
hovered. `UnitSubName` reads it straight off the engine's creature
|
||||
cache (`[unit + 0xB30] → [+0x10]`), no tooltip round-trip.
|
||||
|
||||
Coverage caveat: the creature cache row is populated when an NPC
|
||||
becomes visible to the client AND the server has sent its
|
||||
`CMSG_CREATURE_QUERY_RESPONSE`. The first time a fresh NPC appears
|
||||
the row may be briefly NULL — usually only one or two frames.
|
||||
Subsequent calls succeed once the response lands.
|
||||
|
||||
### `GetUnitSpeed(unit)`
|
||||
|
||||
Returns `currentSpeed, runSpeed, flightSpeed, swimSpeed` — all four
|
||||
|
||||
@@ -405,6 +405,23 @@ enum Offsets {
|
||||
// descriptor at +0x114 — these are sibling classes under CGObject
|
||||
// with class-specific descriptor offsets.
|
||||
OFF_UNIT_DESCRIPTOR = 0x110,
|
||||
|
||||
// CGCreature client-side creature-data cache row. Populated by the
|
||||
// engine when an NPC GUID becomes visible (the same row the
|
||||
// `CreatureCache` SMSG fills with the templated NPC's name,
|
||||
// subname, etc.). NULL for unsynced units or for player CGUnits
|
||||
// (players don't have a row in this cache — their name comes from
|
||||
// the SMSG_NAME_QUERY_RESPONSE-fed name cache at `0x00C0E228`).
|
||||
//
|
||||
// Layout of the cache row:
|
||||
// +0x00 ?
|
||||
// +0x0C name C-string ptr (alias of UNIT_NAME for creatures)
|
||||
// +0x10 subName C-string ptr ("Innkeeper", "<Master Trainer>", etc.)
|
||||
//
|
||||
// Used by `Script_UnitName`'s creature path and by VanillaMinimapTracking's
|
||||
// subname-based blip filter (`src/Blips.cpp::ExtractUnitSubName`).
|
||||
OFF_UNIT_CREATURE_CACHE_ROW = 0xB30,
|
||||
OFF_CREATURE_CACHE_SUB_NAME = 0x10,
|
||||
// Pointer to the CGUnit's 8-byte GUID at `*(CGUnit + 0x08)`. Verified
|
||||
// in `Script_GetInventoryItemLink` at `0x004C8CB0`-`0x004C8CB5`:
|
||||
// `mov eax, [esi+8]; mov edi, [eax]; mov ecx, [eax+4]` reads the
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "guid/Guid.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace Player::Info {
|
||||
|
||||
@@ -182,6 +183,92 @@ int PushFromNameCacheEntry(void *L, const char *name,
|
||||
return 7;
|
||||
}
|
||||
|
||||
// `UnitNameFromGUID(guid)` → `(name, realm)`. Modern WoW backport;
|
||||
// vanilla 1.12 doesn't expose any GUID → name accessor at the Lua
|
||||
// surface, so addons that need to resolve a chat link or combat-log
|
||||
// GUID can't get a name without round-tripping through a unit token.
|
||||
//
|
||||
// Resolution order (matches vanilla's own `Script_UnitName` at
|
||||
// `0x00517020`):
|
||||
// 1. Resolve the GUID through the object manager. Any currently-
|
||||
// synced unit — player you can see, NPC in interact range,
|
||||
// target / mouseover / party / raid member — gets a `CGObject *`
|
||||
// back. Call the engine's canonical polymorphic name getter
|
||||
// `FUN_OBJECT_GET_NAME` on it; this routes through the player
|
||||
// NameCache for players AND the creature cache for NPCs, so it
|
||||
// works for both with a single call.
|
||||
// 2. If the unit isn't currently synced (ex-target who logged off,
|
||||
// player encountered earlier in chat but no longer visible),
|
||||
// fall back to the persistent NameCache (when enabled).
|
||||
//
|
||||
// `realm` is always `""` in vanilla — the engine doesn't populate
|
||||
// per-player realm names and 1.12 has no cross-realm interaction.
|
||||
// We push the empty string rather than nil so callers don't have to
|
||||
// special-case the second return; modern WoW returns nil, but
|
||||
// `realm == ""` is just as easy to gate on.
|
||||
//
|
||||
// Returns nothing on:
|
||||
// - Missing or non-string arg (raises an error rather than return).
|
||||
// - Unparseable GUID string.
|
||||
// - Zero / NULL GUID.
|
||||
// - GUID not resolvable in the object manager AND not in persistent
|
||||
// NameCache (i.e., we've never seen this unit).
|
||||
//
|
||||
// Doesn't trigger a network query on miss — passive read, same as
|
||||
// `GetPlayerInfoByGUID`. Use `C_PlayerCache.RememberPlayer` or let
|
||||
// the engine populate via normal interaction.
|
||||
int __fastcall Script_UnitNameFromGUID(void *L) {
|
||||
if (!Game::Lua::IsString(L, 1)) {
|
||||
Game::Lua::Error(L, "Usage: UnitNameFromGUID(\"0x...\")");
|
||||
return 0;
|
||||
}
|
||||
const char *guidStr = Game::Lua::ToString(L, 1);
|
||||
uint32_t hi, lo;
|
||||
if (!ParseGUID(guidStr, hi, lo))
|
||||
return 0;
|
||||
if (hi == 0 && lo == 0)
|
||||
return 0;
|
||||
|
||||
// Object-manager path — TYPEMASK_OBJECT (0x01) is permissive; the
|
||||
// name getter at `FUN_OBJECT_GET_NAME` does its own type check and
|
||||
// safely returns the "UNKNOWNOBJECT" sentinel for non-unit
|
||||
// objects (gameobjects etc.), so we just gate on the sentinel
|
||||
// rather than pre-filtering by typemask.
|
||||
using ObjectPtr_t = void *(__fastcall *)(uint32_t typeMask,
|
||||
const char *debugMsg,
|
||||
uint32_t guidLo,
|
||||
uint32_t guidHi,
|
||||
int debugCode);
|
||||
using GetName_t = const char *(__thiscall *)(void *obj, int *outFlags);
|
||||
|
||||
auto objectPtr = reinterpret_cast<ObjectPtr_t>(
|
||||
Offsets::FUN_CLNT_OBJ_MGR_OBJECT_PTR);
|
||||
void *obj = objectPtr(Offsets::TYPEMASK_OBJECT, nullptr, lo, hi, 0);
|
||||
if (obj != nullptr) {
|
||||
auto getName = reinterpret_cast<GetName_t>(Offsets::FUN_OBJECT_GET_NAME);
|
||||
const char *name = getName(obj, nullptr);
|
||||
if (name != nullptr && *name != '\0' &&
|
||||
std::strcmp(name, "UNKNOWNOBJECT") != 0 &&
|
||||
std::strcmp(name, "Unknown Being") != 0) {
|
||||
Game::Lua::PushString(L, name);
|
||||
Game::Lua::PushString(L, "");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Object-manager miss / sentinel — fall back to the persistent
|
||||
// NameCache (when enabled). Covers ex-units no longer in the
|
||||
// engine's sync window.
|
||||
const std::string *cachedName = nullptr;
|
||||
const NameCache::Entry *cached = NameCache::Lookup(
|
||||
(static_cast<uint64_t>(hi) << 32) | lo, &cachedName);
|
||||
if (cached == nullptr || cachedName == nullptr || cachedName->empty())
|
||||
return 0;
|
||||
Game::Lua::PushString(L, cachedName->c_str());
|
||||
Game::Lua::PushString(L, "");
|
||||
return 2;
|
||||
}
|
||||
|
||||
// `C_PlayerCache.GetPlayerInfoByName(name)` — name-keyed lookup over
|
||||
// the persistent NameCache. Returns `(localizedClass, englishClass,
|
||||
// localizedRace, englishRace, sex, name, realm, guid)` or nothing if
|
||||
@@ -212,6 +299,8 @@ int __fastcall Script_C_PlayerCache_GetPlayerInfoByName(void *L) {
|
||||
static void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterGlobalFunction("GetPlayerInfoByGUID",
|
||||
&Script_GetPlayerInfoByGUID);
|
||||
Game::Lua::RegisterGlobalFunction("UnitNameFromGUID",
|
||||
&Script_UnitNameFromGUID);
|
||||
Game::Lua::RegisterTableFunction("C_PlayerCache", "GetPlayerInfoByName",
|
||||
&Script_C_PlayerCache_GetPlayerInfoByName);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU Lesser 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// `UnitSubName(unit)` — returns the NPC's subname / title (the small
|
||||
// italic text under their name in the tooltip): "Innkeeper",
|
||||
// "Stable Master", "<Master Engineer>", etc. Modern WoW exposes this
|
||||
// via `UnitName`'s second return; vanilla 1.12's `UnitName` returns
|
||||
// only one value, so addons that want the subtitle have to roll their
|
||||
// own — until now.
|
||||
//
|
||||
// Returns `nil` for:
|
||||
// - Unresolvable unit token (`target` with nothing targeted, empty
|
||||
// `partyN` slot, etc.).
|
||||
// - Player units — they don't have a row in the creature cache.
|
||||
// - NPCs whose creature data hasn't synced yet (rare; happens when
|
||||
// the unit comes into view but the SMSG_CREATURE_QUERY_RESPONSE
|
||||
// hasn't arrived).
|
||||
// - NPCs with no subname (most non-vendor / non-trainer creatures).
|
||||
//
|
||||
// Raises a Lua error on missing / non-string `unit` argument — same
|
||||
// semantics as the engine's `UnitName`.
|
||||
//
|
||||
// Implementation: walks the creature cache row at `[unit + 0xB30]` →
|
||||
// `[+0x10]` (subname C string). Verified path; same one
|
||||
// VanillaMinimapTracking's blip subname filter uses.
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Unit::SubName {
|
||||
|
||||
namespace {
|
||||
|
||||
using ResolveUnitToken_t = void *(__fastcall *)(const char *token);
|
||||
|
||||
int __fastcall Script_UnitSubName(void *L) {
|
||||
if (!Game::Lua::IsString(L, 1)) {
|
||||
Game::Lua::Error(L, "Usage: UnitSubName(\"unit\")");
|
||||
return 0;
|
||||
}
|
||||
const char *token = Game::Lua::ToString(L, 1);
|
||||
if (token == nullptr)
|
||||
return 0;
|
||||
|
||||
auto resolve = reinterpret_cast<ResolveUnitToken_t>(
|
||||
Offsets::FUN_RESOLVE_UNIT_TOKEN);
|
||||
auto *unit = static_cast<const uint8_t *>(resolve(token));
|
||||
if (unit == nullptr)
|
||||
return 0;
|
||||
|
||||
auto *cacheRow = *reinterpret_cast<const uint8_t *const *>(
|
||||
unit + Offsets::OFF_UNIT_CREATURE_CACHE_ROW);
|
||||
if (cacheRow == nullptr)
|
||||
return 0;
|
||||
|
||||
auto *subName = *reinterpret_cast<const char *const *>(
|
||||
cacheRow + Offsets::OFF_CREATURE_CACHE_SUB_NAME);
|
||||
if (subName == nullptr || *subName == '\0')
|
||||
return 0;
|
||||
|
||||
Game::Lua::PushString(L, subName);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterGlobalFunction("UnitSubName", &Script_UnitSubName);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace Unit::SubName
|
||||
Reference in New Issue
Block a user