mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-12 12:57:37 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e760d5f07e | ||
|
|
d0b6a7c0f3 | ||
|
|
96b0cf6075 |
@@ -23,12 +23,13 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
|
||||
| [Class](docs/API.md#class) | `FillLocalizedClassList` |
|
||||
| [Combat](docs/API.md#combat) | `InCombatLockdown` |
|
||||
| [CVar](docs/API.md#cvar) | `C_CVar.GetCVarBool` |
|
||||
| [Container](docs/API.md#container) | `C_Container.GetContainerItemDurability`, `C_Container.GetContainerItemID`, `C_Container.GetContainerItemRepairCost`, `C_Container.GetContainerNumFreeSlots`, `C_Container.GetItemCooldown`, `C_Container.MoveItem`, `C_Container.PlayerHasHearthstone`, `C_Container.SwapItems`, `C_Container.UseHearthstone`, `GetItemCooldown` |
|
||||
| [Container](docs/API.md#container) | `C_Container.GetContainerItemCharges`, `C_Container.GetContainerItemDurability`, `C_Container.GetContainerItemID`, `C_Container.GetContainerItemRepairCost`, `C_Container.GetContainerNumFreeSlots`, `C_Container.GetItemCooldown`, `C_Container.MoveItem`, `C_Container.PlayerHasHearthstone`, `C_Container.SwapItems`, `C_Container.UseHearthstone`, `GetItemCooldown` |
|
||||
| EncodingUtil | `C_EncodingUtil.CompressString`, `C_EncodingUtil.DecompressString`, `C_EncodingUtil.EncodeBase64`, `C_EncodingUtil.DecodeBase64`, `C_EncodingUtil.EncodeHex`, `C_EncodingUtil.DecodeHex`, `C_EncodingUtil.SerializeJSON`, `C_EncodingUtil.DeserializeJSON`, `C_EncodingUtil.SerializeCBOR`, `C_EncodingUtil.DeserializeCBOR` |
|
||||
| [EquipmentSet](docs/API.md#equipmentset) | `C_EquipmentSet.CanUseEquipmentSets`, `C_EquipmentSet.ClearIgnoredSlotsForSave`, `C_EquipmentSet.CreateEquipmentSet`, `C_EquipmentSet.DeleteEquipmentSet`, `C_EquipmentSet.EquipmentSetContainsLockedItems`, `C_EquipmentSet.GetEquipmentSetID`, `C_EquipmentSet.GetEquipmentSetIDs`, `C_EquipmentSet.GetEquipmentSetInfo`, `C_EquipmentSet.GetIgnoredSlots`, `C_EquipmentSet.GetItemIDs`, `C_EquipmentSet.GetItemLocations`, `C_EquipmentSet.GetNumEquipmentSets`, `C_EquipmentSet.IgnoreSlotForSave`, `C_EquipmentSet.IsSlotIgnoredForSave`, `C_EquipmentSet.ModifyEquipmentSet`, `C_EquipmentSet.SaveEquipmentSet`, `C_EquipmentSet.UnignoreSlotForSave`, `C_EquipmentSet.UseEquipmentSet` (sets persist to `WTF\Account\...\ClassicAPI_EquipmentSets.txt`) |
|
||||
| [Events](docs/API.md#events) | `C_EventUtils.IsEventValid` |
|
||||
| [Expansion](docs/API.md#expansion) | `ClassicExpansionAtLeast`, `ClassicExpansionAtMost`, `GetClassicExpansionLevel` |
|
||||
| [Faction](docs/API.md#faction) | `C_Reputation.GetFactionDataByIndex`, `C_Reputation.GetFactionStandings`, `C_Reputation.GetLastStandingChange`, `C_Reputation.GetWatchedFactionData`, `C_Reputation.SetWatchedFactionByID`, `GetFactionIDByIndex`, `GetFactionInfoByID`, `GetFactionParentID` |
|
||||
| [Focus](docs/API.md#focus) | `ClearFocus`, `FocusUnit` |
|
||||
| [FriendList](docs/API.md#friendlist) | `C_FriendList.IsWhoQueryPending`, `C_FriendList.SendWhoQueryByName` |
|
||||
| [Gossip](docs/API.md#gossip) | `C_GossipInfo.CloseGossip`, `C_GossipInfo.GetActiveQuests`, `C_GossipInfo.GetAvailableQuests`, `C_GossipInfo.GetNumActiveQuests`, `C_GossipInfo.GetNumAvailableQuests`, `C_GossipInfo.GetNumOptions`, `C_GossipInfo.GetOptions`, `C_GossipInfo.GetText`, `C_GossipInfo.SelectActiveQuest`, `C_GossipInfo.SelectAvailableQuest`, `C_GossipInfo.SelectOption`, `C_GossipInfo.SelectOptionByIndex` |
|
||||
| [GameTooltip](docs/API.md#gametooltip) | `GameTooltip:GetGameObject`, `GameTooltip:GetItem`, `GameTooltip:GetOwner`, `GameTooltip:GetSpell`, `GameTooltip:GetUnitGUID`, `GameTooltip:HasGameObject`, `GameTooltip:HasItem`, `GameTooltip:HasSpell`, `GameTooltip:HasUnit`, `GameTooltip:SetEquipmentSet`, `GameTooltip:SetInventoryItemByID`, `GameTooltip:SetItemByID`, `GameTooltip:SetSpellByID`, `GameTooltip:SetTalentByID`, `GameTooltip:SetUnitAura` |
|
||||
@@ -98,6 +99,7 @@ functions, just behavior the stock 1.12 engine didn't have. See the
|
||||
| `NAME_PLATE_CREATED` | `nameplateFrame` |
|
||||
| `NAME_PLATE_UNIT_ADDED` | `unitToken` ("nameplateN") |
|
||||
| `NAME_PLATE_UNIT_REMOVED` | `unitToken` ("nameplateN") |
|
||||
| `PLAYER_FOCUS_CHANGED` | *(none)* |
|
||||
| `PLAYER_STARTED_LOOKING` | *(none)* |
|
||||
| `PLAYER_STOPPED_LOOKING` | *(none)* |
|
||||
| `PLAYER_STARTED_MOVING` | *(none)* |
|
||||
@@ -125,6 +127,7 @@ functions, just behavior the stock 1.12 engine didn't have. See the
|
||||
| Token | Resolves to |
|
||||
|-------|-------------|
|
||||
| `nameplate1`..`nameplateN` | Unit behind the Nth visible nameplate, in creation-order. Works with every `UnitX` function — `UnitName`, `UnitGUID`, `UnitClass`, `UnitHealth`, etc. Suffix chains (`nameplate1target`, `nameplate1targettarget`) compose. See [NamePlate / Unit tokens](docs/API.md#unit-tokens-nameplaten). |
|
||||
| `focus` / `focustarget` | Sticky target set via [`FocusUnit`](docs/API.md#focusunitunit), cleared via [`ClearFocus`](docs/API.md#clearfocus). Same `UnitX` coverage as `nameplateN`. Fires [`PLAYER_FOCUS_CHANGED`](docs/API.md#player_focus_changed-event) on transition. See [Focus](docs/API.md#focus). |
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
+176
-10
@@ -34,6 +34,7 @@ build instructions.
|
||||
- [`GetItemCooldown(itemInfo)` / `C_Container.GetItemCooldown(itemID)`](#getitemcooldowniteminfo--c_containergetitemcooldownitemid)
|
||||
- [`C_Container.GetContainerItemDurability(containerIndex, slotIndex)`](#c_containergetcontaineritemdurabilitycontainerindex-slotindex)
|
||||
- [`C_Container.GetContainerItemRepairCost(containerIndex, slotIndex)`](#c_containergetcontaineritemrepaircostcontainerindex-slotindex)
|
||||
- [`C_Container.GetContainerItemCharges(containerIndex, slotIndex)`](#c_containergetcontaineritemchargescontainerindex-slotindex)
|
||||
- [`C_Container.GetContainerNumFreeSlots(bagID)`](#c_containergetcontainernumfreeslotsbagid)
|
||||
- [`C_Container.PlayerHasHearthstone()`](#c_containerplayerhashearthstone)
|
||||
- [`C_Container.UseHearthstone()`](#c_containerusehearthstone)
|
||||
@@ -74,6 +75,7 @@ build instructions.
|
||||
- [`FACTION_STANDING_CHANGED` event](#faction_standing_changed-event)
|
||||
- [`MODIFIER_STATE_CHANGED` event](#modifier_state_changed-event)
|
||||
- [`NAME_PLATE_CREATED` / `NAME_PLATE_UNIT_ADDED` / `NAME_PLATE_UNIT_REMOVED` events](#name_plate_created--name_plate_unit_added--name_plate_unit_removed-events)
|
||||
- [`PLAYER_FOCUS_CHANGED` event](#player_focus_changed-event)
|
||||
- [`QUEST_ACCEPTED` event](#quest_accepted-event)
|
||||
- [`QUEST_TURNED_IN` event](#quest_turned_in-event)
|
||||
- [`UPDATE_SHAPESHIFT_FORM` event](#update_shapeshift_form-event)
|
||||
@@ -93,6 +95,11 @@ build instructions.
|
||||
- [`C_Reputation.SetWatchedFactionByID(factionID)`](#c_reputationsetwatchedfactionbyidfactionid)
|
||||
- [`C_Reputation.GetLastStandingChange()`](#c_reputationgetlaststandingchange)
|
||||
|
||||
- [Focus](#focus)
|
||||
- [`FocusUnit(unit)`](#focusunitunit)
|
||||
- [`ClearFocus()`](#clearfocus)
|
||||
- [Unit token (`focus` / `focustarget`)](#unit-token-focus--focustarget)
|
||||
|
||||
- [FriendList](#friendlist)
|
||||
- [`C_FriendList.SendWhoQueryByName(name)`](#c_friendlistsendwhoquerybynamename)
|
||||
- [`C_FriendList.IsWhoQueryPending()`](#c_friendlistiswhoquerypending)
|
||||
@@ -764,6 +771,62 @@ Useful for "repair only items above N copper" smart-repair logic
|
||||
without scanning tooltips. ClassicAPI addition; modern WoW has no
|
||||
direct equivalent.
|
||||
|
||||
### `C_Container.GetContainerItemCharges(containerIndex, slotIndex)`
|
||||
|
||||
Per-slot equivalent of
|
||||
[`C_Item.GetItemCount`](#c_itemgetitemcountitem-includebank-includecharges)'s
|
||||
`includeCharges=true` mode — returns the total uses available in
|
||||
*this single slot*, where `GetItemCount` totals the same value
|
||||
across every matching slot.
|
||||
|
||||
```
|
||||
uses = C_Container.GetContainerItemCharges(containerIndex, slotIndex)
|
||||
```
|
||||
|
||||
`containerIndex` accepts the same values as
|
||||
[`C_Container.GetContainerItemDurability`](#c_containergetcontaineritemdurabilitycontainerindex-slotindex)
|
||||
(`0` = backpack, `1..4` = equipped bags, `-1`/`5..11` = bank, etc.).
|
||||
`slotIndex` is 1-based.
|
||||
|
||||
Math is `stack * usesPerItem`, where `usesPerItem` is
|
||||
`abs(SPELL_CHARGES[0])` for items with a negative charges field
|
||||
(consume-on-use: wands, healthstones, mana gems, sapper charges)
|
||||
and `1` for everything else. Worked examples:
|
||||
|
||||
| Slot contents | Stack | Raw charges | Returns |
|
||||
|---|---:|---:|---:|
|
||||
| Wand of Decay at 50 charges | 1 | -50 | **50** |
|
||||
| Wand at 1 charge | 1 | -1 | **1** |
|
||||
| Healthstone | 1 | -1 | **1** |
|
||||
| Stack of 20 water | 20 | -1 | **20** |
|
||||
| Stack of 5 mana potions | 5 | -1 | **5** |
|
||||
| Hearthstone (cooldown-only) | 1 | 0 | **1** |
|
||||
| Any other item in the slot | 1 | 0 | **1** |
|
||||
|
||||
Returns `nil` only when the slot is empty.
|
||||
|
||||
```lua
|
||||
-- "how many charges does my wand have left?"
|
||||
local charges = C_Container.GetContainerItemCharges(0, 1)
|
||||
print("Wand charges remaining: " .. charges)
|
||||
|
||||
-- "how many drinks across the bags?" — same as GetItemCount(water, false, true)
|
||||
local total = 0
|
||||
for bag = 0, 4 do
|
||||
for slot = 1, GetContainerNumSlots(bag) do
|
||||
if C_Container.GetContainerItemID(bag, slot) == waterID then
|
||||
total = total + C_Container.GetContainerItemCharges(bag, slot)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
ClassicAPI addition; modern WoW has no direct equivalent (modern
|
||||
addons read charges off tooltip text). Useful when you need a
|
||||
per-slot number rather than `GetItemCount`'s rollup — e.g. to
|
||||
display "X charges" on the slot UI for a wand without re-walking
|
||||
every other matching item.
|
||||
|
||||
### `C_Container.GetContainerNumFreeSlots(bagID)`
|
||||
|
||||
Returns the number of empty slots in the given bag, plus the bag's
|
||||
@@ -1649,6 +1712,33 @@ show path re-allocates from the pool. Those transient zeroes never
|
||||
become events because the unit appears in both the previous and
|
||||
current tick's snapshot.
|
||||
|
||||
### `PLAYER_FOCUS_CHANGED` event
|
||||
|
||||
Fires whenever the player's focus changes — assignment via
|
||||
[`FocusUnit`](#focusunitunit), clear via [`ClearFocus`](#clearfocus),
|
||||
or a `FocusUnit(token)` call where the token resolves to no GUID
|
||||
(implicit clear). No payload args; the new focus GUID is read off
|
||||
[`UnitGUID("focus")`](#unitguidunit) in the handler.
|
||||
|
||||
```lua
|
||||
local f = CreateFrame("Frame")
|
||||
f:RegisterEvent("PLAYER_FOCUS_CHANGED")
|
||||
f:SetScript("OnEvent", function()
|
||||
local guid = UnitGUID("focus")
|
||||
if guid then
|
||||
print("focusing", UnitName("focus"))
|
||||
else
|
||||
print("focus cleared")
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
||||
**Identity-checked**: assigning the same GUID twice is a no-op
|
||||
(no event refires), matching 3.3.5's `FUN_0051FF20` behavior.
|
||||
Mirrors modern's documented semantics — "fired whenever the
|
||||
player's focus target is changed, including when the focus target
|
||||
is lost or cleared".
|
||||
|
||||
### `QUEST_ACCEPTED` event
|
||||
|
||||
Fires once per quest the player just accepted, with two payload args:
|
||||
@@ -2065,6 +2155,82 @@ This is a ClassicAPI-only call; modern WoW has no equivalent (modern
|
||||
addons get the factionID from `FACTION_STANDING_CHANGED` directly, so
|
||||
they don't need a separate getter).
|
||||
|
||||
## Focus
|
||||
|
||||
Polyfills modern WoW's focus-target system: a single sticky GUID
|
||||
that addons can pin to a unit and address via the `"focus"` unit
|
||||
token across the entire `UnitX` API surface. Backed by a hook on
|
||||
`FUN_TOKEN_TO_GUID` (shared with the [`nameplateN`](#unit-tokens-nameplaten)
|
||||
tokens — see `unit/TokenExtensions.cpp`).
|
||||
|
||||
State is **session-only** — drops on `/reload`, `/logout`, and zone
|
||||
loads that recreate Lua state. Modern Classic Era behaves the same;
|
||||
addons that want persistence have to re-`FocusUnit` from `SavedVariables`
|
||||
at `ADDON_LOADED`.
|
||||
|
||||
**Auto-clear on despawn.** When the focused unit leaves the client's
|
||||
object table — out of rendering range, full despawn, dies and
|
||||
decays — focus drops and [`PLAYER_FOCUS_CHANGED`](#player_focus_changed-event)
|
||||
fires. The unit re-entering range does NOT auto-refocus. Matches
|
||||
modern WoW's documented behavior. Implementation: per-tick
|
||||
`ObjectByGUID(g_focusGUID)` probe; on null result, `Set(0)`.
|
||||
|
||||
### `FocusUnit(unit)`
|
||||
|
||||
Sets focus to the given unit. Argument is a unit token —
|
||||
`"target"`, `"mouseover"`, `"party1"`, `"nameplate1"`, anything the
|
||||
resolver accepts. Calling with no argument is shorthand for
|
||||
`FocusUnit("target")` (matches modern's `/focus` slash command
|
||||
default).
|
||||
|
||||
```lua
|
||||
FocusUnit("target") -- pin the current target
|
||||
FocusUnit("nameplate1") -- pin the unit behind nameplate1
|
||||
FocusUnit() -- same as FocusUnit("target")
|
||||
```
|
||||
|
||||
Fires [`PLAYER_FOCUS_CHANGED`](#player_focus_changed-event) if the
|
||||
resolved GUID differs from the current focus. No-op (no event) if
|
||||
the same unit is already focused — matches the
|
||||
identity-check-first behavior of 3.3.5's `FUN_0051FF20`.
|
||||
|
||||
Passing a token that doesn't resolve to a unit (e.g. `"target"`
|
||||
with nothing targeted, or `"party5"` solo) clears focus — same as
|
||||
calling `ClearFocus()`.
|
||||
|
||||
### `ClearFocus()`
|
||||
|
||||
Drops the focus. Fires `PLAYER_FOCUS_CHANGED` if there was one;
|
||||
no-op otherwise.
|
||||
|
||||
```lua
|
||||
ClearFocus()
|
||||
```
|
||||
|
||||
### Unit token (`focus` / `focustarget`)
|
||||
|
||||
`"focus"` resolves to whatever GUID `FocusUnit` last stashed.
|
||||
Accepted by every `UnitX` function:
|
||||
|
||||
```lua
|
||||
local name = UnitName("focus") -- nil if no focus
|
||||
local hp = UnitHealth("focus")
|
||||
local _, class = UnitClass("focus")
|
||||
```
|
||||
|
||||
Chains compose through the engine's standard suffix walker:
|
||||
`"focustarget"`, `"focustargettarget"`, etc. — same behavior as
|
||||
`"targettarget"`, mirrored instruction-for-instruction in our hook
|
||||
so addons get the engine semantics they expect.
|
||||
|
||||
Returns `nil` cleanly when no focus is set; doesn't raise the
|
||||
"Unknown unit name" error.
|
||||
|
||||
[`UnitTokenFromGUID`](#unittokenfromguidguid) scans `"focus"` right
|
||||
after `"target"` (matching retail order), so a focused unit's GUID
|
||||
reverse-resolves to `"focus"` only if it isn't already addressable
|
||||
as `"player"` / `"party*"` / `"raid*"` / `"nameplate*"` / `"target"`.
|
||||
|
||||
## FriendList
|
||||
|
||||
### `C_FriendList.SendWhoQueryByName(name)`
|
||||
@@ -6769,19 +6935,19 @@ known unit tokens and return the first one currently mapped to that
|
||||
GUID, or `nil` if none of them point at it.
|
||||
|
||||
The search order matches modern retail with post-1.12 tokens
|
||||
omitted (`vehicle`, `arenaN`, `arenapetN`, `bossN`, `focus`,
|
||||
`softenemy`, `softfriend`, `softinteract` all post-date vanilla and
|
||||
the engine's resolver doesn't recognize them). `nameplateN` is
|
||||
included between `raidpetN` and `target` — we hook the resolver so
|
||||
the engine recognizes the token form (see
|
||||
[Unit tokens](#unit-tokens-nameplaten)). As with the other
|
||||
positional tokens, the returned `"nameplate3"`-style result is only
|
||||
valid at that instant; the slot may shift to a different unit
|
||||
between calls, so re-verify with `UnitGUID(token)` before reusing.
|
||||
omitted (`vehicle`, `arenaN`, `arenapetN`, `bossN`, `softenemy`,
|
||||
`softfriend`, `softinteract` all post-date vanilla and the engine's
|
||||
resolver doesn't recognize them). `nameplateN` and `focus` are
|
||||
included — we hook the resolver so the engine recognizes both token
|
||||
forms. As with the other positional tokens, a returned
|
||||
`"nameplate3"` result is only valid at that instant; the slot may
|
||||
shift to a different unit between calls, so re-verify with
|
||||
`UnitGUID(token)` before reusing.
|
||||
|
||||
```
|
||||
player → pet → party1..4 → partypet1..4 → raid1..40
|
||||
→ raidpet1..40 → nameplate1..N → target → npc → mouseover
|
||||
→ raidpet1..40 → nameplate1..N → target → focus
|
||||
→ npc → mouseover
|
||||
```
|
||||
|
||||
```lua
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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/>.
|
||||
|
||||
// `C_Container.GetContainerItemCharges(containerIndex, slotIndex)` —
|
||||
// returns the `abs(SPELL_CHARGES[0])` value of the item in the
|
||||
// specified bag slot, or `nil` for empty slots and items that don't
|
||||
// carry a charge concept. Wands return their remaining cast count
|
||||
// (e.g. 50 → 49 → …); single-use items (Healthstone, Mana Gem,
|
||||
// Goblin Sapper Charge) return `1`.
|
||||
//
|
||||
// Modern equivalent: none — modern WoW exposes charges only through
|
||||
// tooltip text scanning. `C_Item.GetItemCount(includeBank, includeCharges)`
|
||||
// totals charges across every matching stack; this is the per-slot
|
||||
// reader.
|
||||
|
||||
#include "Game.h"
|
||||
#include "item/Charges.h"
|
||||
#include "item/Location.h"
|
||||
|
||||
namespace Container::Charges {
|
||||
|
||||
namespace {
|
||||
|
||||
int __fastcall Script_C_Container_GetContainerItemCharges(void *L) {
|
||||
if (!Game::Lua::IsNumber(L, 1) || !Game::Lua::IsNumber(L, 2)) {
|
||||
Game::Lua::Error(L,
|
||||
"Usage: C_Container.GetContainerItemCharges(containerIndex, slotIndex)");
|
||||
return 0;
|
||||
}
|
||||
const int bagID = static_cast<int>(Game::Lua::ToNumber(L, 1));
|
||||
const int slotIndex = static_cast<int>(Game::Lua::ToNumber(L, 2));
|
||||
return Item::Charges::PushChargesForItem(
|
||||
L, Item::Location::ResolveBag(L, bagID, slotIndex));
|
||||
}
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterTableFunction("C_Container", "GetContainerItemCharges",
|
||||
&Script_C_Container_GetContainerItemCharges);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace Container::Charges
|
||||
@@ -0,0 +1,39 @@
|
||||
// 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/>.
|
||||
|
||||
#include "Charges.h"
|
||||
|
||||
#include "../Game.h"
|
||||
#include "../Offsets.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
namespace Item::Charges {
|
||||
|
||||
int PushChargesForItem(void *L, const uint8_t *item) {
|
||||
if (item == nullptr)
|
||||
return 0;
|
||||
auto *descriptor = *reinterpret_cast<const uint8_t *const *>(
|
||||
item + Offsets::OFF_ITEM_DESCRIPTOR);
|
||||
if (descriptor == nullptr)
|
||||
return 0;
|
||||
const int32_t stack = static_cast<int32_t>(*reinterpret_cast<const uint32_t *>(
|
||||
descriptor + Offsets::OFF_DESCRIPTOR_STACK_COUNT));
|
||||
const int32_t rawCharges = *reinterpret_cast<const int32_t *>(
|
||||
descriptor + Offsets::OFF_DESCRIPTOR_SPELL_CHARGES_0);
|
||||
const int32_t usesPerItem = (rawCharges < 0) ? -rawCharges : 1;
|
||||
Game::Lua::PushNumber(L, static_cast<double>(stack * usesPerItem));
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace Item::Charges
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Item::Charges {
|
||||
|
||||
// Pushes the total uses available in this single slot — the
|
||||
// per-slot equivalent of `GetItemCount(_, _, includeCharges=true)`.
|
||||
// Returns `1` (pushed one value) on success, `0` (pushes nothing,
|
||||
// caller surfaces as `nil`) when the item is missing or the
|
||||
// descriptor is null.
|
||||
//
|
||||
// Math is `stack * usesPerItem` where `usesPerItem` follows
|
||||
// `Item::Count::GetUsesPerItem`:
|
||||
// - `SPELL_CHARGES[0] >= 0` → `1` (no destroy-on-zero behavior;
|
||||
// stack count alone reflects "how many uses").
|
||||
// - `SPELL_CHARGES[0] < 0` → `abs(value)` (consume-on-use items;
|
||||
// wand at 50 has `-50` so each item contributes 50).
|
||||
//
|
||||
// Worked examples:
|
||||
// - Wand at 50 charges (stack=1, raw=-50): `1 * 50 = 50`
|
||||
// - Hearthstone (stack=1, raw=0): `1 * 1 = 1` (cooldown-
|
||||
// only, no charges concept; we still surface "1 use").
|
||||
// - Water stack of 20 (stack=20, raw=-1): `20 * 1 = 20`
|
||||
// - Equipped sword (stack=1, raw=0): `1 * 1 = 1`
|
||||
//
|
||||
// Caller can `return PushChargesForItem(L, item);` to forward the
|
||||
// count directly.
|
||||
int PushChargesForItem(void *L, const uint8_t *item);
|
||||
|
||||
} // namespace Item::Charges
|
||||
@@ -32,7 +32,7 @@
|
||||
// - `NAME_PLATE_UNIT_ADDED` / `_REMOVED` — `arg1` is the
|
||||
// `"nameplateN"` unit token (formatted from the plate's index in
|
||||
// `g_orderedGUIDs` at fire time). The token resolves to the unit
|
||||
// via the `nameplateN`-aware token resolver in `TokenResolver.cpp`
|
||||
// via the `nameplateN`-aware token resolver in `unit/TokenExtensions.cpp`
|
||||
// — addons can pass it straight to `UnitName`, `UnitGUID`, etc.,
|
||||
// or to `GetNamePlateForUnit` for the frame.
|
||||
|
||||
@@ -86,7 +86,7 @@ std::unordered_set<const void *> g_seenPlates;
|
||||
|
||||
// Ordered list of currently-visible nameplate GUIDs, in
|
||||
// creation-order. Append on UNIT_ADDED, erase on UNIT_REMOVED. Backs
|
||||
// the `nameplateN` unit-token resolver in `TokenResolver.cpp`. Order
|
||||
// the `nameplateN` unit-token resolver in `unit/TokenExtensions.cpp`. Order
|
||||
// matches modern WoW semantics: stable for the lifetime of each
|
||||
// plate, gaps when middle plates vanish (until the next REMOVED
|
||||
// shifts later entries down).
|
||||
@@ -239,7 +239,7 @@ void PrepareForReload() {
|
||||
}
|
||||
|
||||
// Exposed via `nameplate/Walk.h` so the `nameplateN` token resolver
|
||||
// in `TokenResolver.cpp` can map an index to a GUID without seeing
|
||||
// in `unit/TokenExtensions.cpp` can map an index to a GUID without seeing
|
||||
// the internal vector.
|
||||
uint64_t GetGUIDByIndex(int oneBased) {
|
||||
if (oneBased <= 0)
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
// 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/>.
|
||||
|
||||
// `FocusUnit(unit)` / `ClearFocus()` / `focus` unit token. Polyfills
|
||||
// the post-vanilla focus-target system on top of the engine's
|
||||
// existing token resolver via the hook in
|
||||
// `unit/TokenExtensions.cpp`.
|
||||
//
|
||||
// Storage: a single 64-bit GUID held in-process. The 3.3.5 engine
|
||||
// keeps the same shape (two globals at `DAT_00BD07D0`/`D4`); we
|
||||
// don't share those because vanilla never reads them. The resolver
|
||||
// hook reads our `Unit::Focus::Get()` directly.
|
||||
//
|
||||
// Setter mirrors 3.3.5's `FUN_0051FF20` minus the post-vanilla
|
||||
// `UnitFlag bit 0x2000` ("focus glow" rendering hint, introduced in
|
||||
// TBC for the default focus frame). Vanilla addons (pfUI) render
|
||||
// their own focus indicator and don't need the hint.
|
||||
//
|
||||
// Auto-clear on despawn: modern WoW fires `PLAYER_FOCUS_CHANGED`
|
||||
// when the focused unit leaves the client's object table (out of
|
||||
// rendering range, despawn) and does NOT auto-refocus when they
|
||||
// come back. We mirror that by probing `ObjectByGUID(g_focusGUID)`
|
||||
// every world tick — when it returns null, we `Set(0)` which fires
|
||||
// the event. Cost is one hash-table lookup per tick while focus is
|
||||
// set; zero when no focus is active.
|
||||
|
||||
#include "Focus.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "event/Custom.h"
|
||||
#include "tick/WorldTick.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Unit::Focus {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char *kEventName = "PLAYER_FOCUS_CHANGED";
|
||||
const Event::Custom::AutoReserve _reserve{kEventName};
|
||||
|
||||
uint64_t g_focusGUID = 0;
|
||||
|
||||
using TokenToGUID_t = uint64_t(__fastcall *)(const char *token);
|
||||
using ResolveByGUID_t = void *(__fastcall *)(int type, const char *debugName,
|
||||
uint32_t guidLo, uint32_t guidHi,
|
||||
int priority);
|
||||
|
||||
uint64_t ResolveTokenGUID(const char *token) {
|
||||
if (token == nullptr || *token == '\0')
|
||||
return 0;
|
||||
auto fn = reinterpret_cast<TokenToGUID_t>(
|
||||
static_cast<uintptr_t>(Offsets::FUN_TOKEN_TO_GUID));
|
||||
return fn(token);
|
||||
}
|
||||
|
||||
// Per-tick despawn watcher. Probes the engine's object table for
|
||||
// `g_focusGUID`; when it disappears (out of range, fully despawned),
|
||||
// clear focus and fire PLAYER_FOCUS_CHANGED — matches modern's
|
||||
// "leaves render distance → focus drops" behavior. Won't refocus
|
||||
// when the unit comes back, also matching modern.
|
||||
void OnWorldTick() {
|
||||
if (g_focusGUID == 0)
|
||||
return;
|
||||
auto resolve = reinterpret_cast<ResolveByGUID_t>(
|
||||
static_cast<uintptr_t>(Offsets::FUN_OBJECT_RESOLVE_BY_GUID));
|
||||
if (resolve(Offsets::OBJ_TYPE_UNIT, "Focus",
|
||||
static_cast<uint32_t>(g_focusGUID),
|
||||
static_cast<uint32_t>(g_focusGUID >> 32),
|
||||
0x172) == nullptr)
|
||||
Set(0);
|
||||
}
|
||||
|
||||
// `FocusUnit(unit)` — sets focus to whatever GUID `unit` currently
|
||||
// resolves to. Modern signature: `FocusUnit(unit)` with the token
|
||||
// argument. Modern also allows `FocusUnit()` (no arg) to mean
|
||||
// `"target"`, matching the `/focus` slash command's default.
|
||||
int __fastcall Script_FocusUnit(void *L) {
|
||||
if (Game::Lua::IsString(L, 1)) {
|
||||
const char *token = Game::Lua::ToString(L, 1);
|
||||
Set(ResolveTokenGUID(token));
|
||||
} else {
|
||||
Set(ResolveTokenGUID("target"));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// `ClearFocus()` — drops the focus, fires PLAYER_FOCUS_CHANGED.
|
||||
int __fastcall Script_ClearFocus(void *L) {
|
||||
(void)L;
|
||||
Set(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterGlobalFunction("FocusUnit", &Script_FocusUnit);
|
||||
Game::Lua::RegisterGlobalFunction("ClearFocus", &Script_ClearFocus);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
const Tick::WorldTick::AutoSubscribe _tickSub{&OnWorldTick};
|
||||
|
||||
} // namespace
|
||||
|
||||
uint64_t Get() { return g_focusGUID; }
|
||||
|
||||
void Set(uint64_t guid) {
|
||||
if (guid == g_focusGUID)
|
||||
return; // no-op: same target → no event fire (matches 3.3.5)
|
||||
g_focusGUID = guid;
|
||||
const int slot = Event::Custom::Lookup(kEventName);
|
||||
if (slot >= 0)
|
||||
Event::Custom::Fire(slot, "");
|
||||
}
|
||||
|
||||
} // namespace Unit::Focus
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Unit::Focus {
|
||||
|
||||
// Current focus GUID, or `0` when nothing is focused. Read by the
|
||||
// `focus` / `focustarget*` branch of the token resolver in
|
||||
// `unit/TokenExtensions.cpp`.
|
||||
uint64_t Get();
|
||||
|
||||
// Sets focus to `guid` and fires `PLAYER_FOCUS_CHANGED` (no args).
|
||||
// Identity-checks first — assigning the same GUID is a no-op,
|
||||
// matching 3.3.5's `FUN_0051FF20` behavior so the event only fires
|
||||
// on a real transition. Pass `0` to clear focus.
|
||||
void Set(uint64_t guid);
|
||||
|
||||
} // namespace Unit::Focus
|
||||
@@ -107,14 +107,16 @@ static int __fastcall Script_UnitGUID(void *L) {
|
||||
//
|
||||
// The search walks the modern retail token order with post-vanilla
|
||||
// tokens dropped (no `vehicle`, `arenaN`, `arenapetN`, `bossN`,
|
||||
// `focus`, `softenemy`, `softfriend`, `softinteract` — those all
|
||||
// post-date 1.12 and `FUN_TOKEN_TO_GUID` would raise "Unknown unit
|
||||
// name" for them). `nameplateN` is included — we hook the token
|
||||
// resolver so the engine recognizes it (see `nameplate/TokenResolver.cpp`).
|
||||
// Order:
|
||||
// `softenemy`, `softfriend`, `softinteract` — those all post-date
|
||||
// 1.12 and `FUN_TOKEN_TO_GUID` would raise "Unknown unit name" for
|
||||
// them). `nameplateN` and `focus` are included — we hook the token
|
||||
// resolver so the engine recognizes them (see
|
||||
// `unit/TokenExtensions.cpp` and `unit/Focus.cpp`). Order matches
|
||||
// retail with the dropped tokens skipped in place:
|
||||
//
|
||||
// player → pet → party1..4 → partypet1..4 → raid1..40
|
||||
// → raidpet1..40 → nameplate1..N → target → npc → mouseover
|
||||
// → raidpet1..40 → nameplate1..N → target → focus
|
||||
// → npc → mouseover
|
||||
//
|
||||
// The result is inherently unstable — multiple tokens can map to the
|
||||
// same GUID at once (your target IS your mouseover IS your party1), and
|
||||
@@ -203,6 +205,7 @@ static int __fastcall Script_UnitTokenFromGUID(void *L) {
|
||||
}
|
||||
|
||||
if (check("target")) return 1;
|
||||
if (check("focus")) return 1;
|
||||
if (check("npc")) return 1;
|
||||
if (check("mouseover")) return 1;
|
||||
|
||||
|
||||
@@ -11,45 +11,41 @@
|
||||
// You should have received a copy of the GNU Lesser General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Extend `FUN_TOKEN_TO_GUID` (`0x00515970`) with modern WoW's
|
||||
// `nameplateN` unit-token family. Hooking the central token→GUID
|
||||
// resolver means every `Script_Unit*` (UnitName, UnitGUID, UnitHealth,
|
||||
// UnitClass, UnitExists, …) gains nameplate-token support for free,
|
||||
// Extend `FUN_TOKEN_TO_GUID` (`0x00515970`) with post-vanilla unit
|
||||
// tokens. Hooking the central token→GUID resolver means every
|
||||
// `Script_Unit*` (UnitName, UnitGUID, UnitHealth, UnitClass,
|
||||
// UnitExists, …) gains support for the new token families for free,
|
||||
// since they all funnel here.
|
||||
//
|
||||
// Resolution:
|
||||
// Currently extended families:
|
||||
//
|
||||
// 1. SStrCmpI the input against `"nameplate"` (9 chars). Non-match →
|
||||
// fall through to the original resolver and behave exactly as
|
||||
// before (engine handles `player` / `target` / `partyN` / etc.,
|
||||
// or raises "Unknown unit name").
|
||||
// 2. Parse trailing digits — `nameplate1`, `nameplate12`, ... — into
|
||||
// a 1-based index `N`.
|
||||
// 3. Look up `N` in `NamePlate::Events`' ordered GUID list. Out of
|
||||
// range returns 0 quietly, which the engine treats as "no unit"
|
||||
// and Lua callers see as `nil` — matches modern behavior, no
|
||||
// "Unknown unit name" error.
|
||||
// 4. If the token continues past the digits (`nameplate1target`,
|
||||
// `nameplate1targettarget`, …) mirror the engine's own suffix
|
||||
// walker at `LAB_005159d3`: each `"target"` chunk advances the
|
||||
// GUID by reading `m_objectFields + OFF_UNIT_FIELD_TARGET`
|
||||
// (UNIT_FIELD_TARGET) via `ObjectByGUID`.
|
||||
// - `nameplate1`..`nameplateN` — index into the ordered list of
|
||||
// visible nameplates maintained in `nameplate/Events.cpp`. See
|
||||
// `Unit tokens (nameplateN)` in docs/API.md.
|
||||
//
|
||||
// The engine's walker is **inside** the function we hook, so a hook
|
||||
// that returns early at the prefix-parse step would skip it
|
||||
// entirely. Replicating the walker here keeps `nameplate1target`
|
||||
// working without rewriting the resolver. The walker's logic is
|
||||
// short (one `SStrCmpI` + one `ObjectByGUID` + one field-read per
|
||||
// iteration) and matches the engine instruction-for-instruction —
|
||||
// verified against the disassembly of `0x005159D3..0x00515A2C`.
|
||||
// - `focus` — single-slot sticky target backed by `Unit::Focus`.
|
||||
// See `unit/Focus.cpp` for the storage + `PLAYER_FOCUS_CHANGED`
|
||||
// event firing.
|
||||
//
|
||||
// Both families compose with the engine's own `target`-suffix walker
|
||||
// (`focustarget`, `nameplate1targettarget`) via the shared
|
||||
// `WalkSuffix` helper, which mirrors the engine's
|
||||
// `LAB_005159d3..0x00515A2C` walker instruction-for-instruction
|
||||
// (SStrCmpI "target" + ObjectByGUID + read UNIT_FIELD_TARGET).
|
||||
//
|
||||
// Resolution order in the hook: focus check first (literal-equal,
|
||||
// short token), then nameplate prefix (longer, requires digit).
|
||||
// Both branches short-circuit; on no-match the original engine
|
||||
// resolver runs unchanged.
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "nameplate/Walk.h"
|
||||
#include "unit/Focus.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace NamePlate::TokenResolver {
|
||||
namespace Unit::TokenExtensions {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -64,8 +60,10 @@ using ResolveByGUID_t = void *(__fastcall *)(int type, const char *debugName,
|
||||
uint32_t guidLo, uint32_t guidHi,
|
||||
int priority);
|
||||
|
||||
constexpr const char kPrefix[] = "nameplate";
|
||||
constexpr int kPrefixLen = static_cast<int>(sizeof(kPrefix) - 1);
|
||||
constexpr const char kNamePlatePrefix[] = "nameplate";
|
||||
constexpr int kNamePlatePrefixLen = static_cast<int>(sizeof(kNamePlatePrefix) - 1);
|
||||
constexpr const char kFocusPrefix[] = "focus";
|
||||
constexpr int kFocusPrefixLen = static_cast<int>(sizeof(kFocusPrefix) - 1);
|
||||
constexpr const char kSuffixTarget[] = "target";
|
||||
constexpr int kSuffixTargetLen = static_cast<int>(sizeof(kSuffixTarget) - 1);
|
||||
|
||||
@@ -87,7 +85,7 @@ uint64_t WalkSuffix(uint64_t guid, const char *suffix) {
|
||||
return 0; // unknown suffix component — modern returns nil
|
||||
suffix += kSuffixTargetLen;
|
||||
auto *obj = static_cast<uint8_t *>(
|
||||
resolve(Offsets::OBJ_TYPE_UNIT, "nameplate",
|
||||
resolve(Offsets::OBJ_TYPE_UNIT, "ClassicAPI",
|
||||
static_cast<uint32_t>(guid),
|
||||
static_cast<uint32_t>(guid >> 32),
|
||||
kResolvePriority));
|
||||
@@ -113,27 +111,36 @@ uint64_t __fastcall Hook_h(const char *token) {
|
||||
return Original_o(token);
|
||||
|
||||
auto sstrcmpi = reinterpret_cast<SStrCmpI_t>(Offsets::FUN_SSTR_CMP_I);
|
||||
if (sstrcmpi(token, kPrefix, kPrefixLen) != 0)
|
||||
return Original_o(token);
|
||||
|
||||
// Must have at least one digit after `"nameplate"`; otherwise
|
||||
// it's `"nameplate"` (no number) or `"nameplateX"` — fall
|
||||
// through to the original so the engine raises its
|
||||
// "Unknown unit name" error consistently.
|
||||
const char *p = token + kPrefixLen;
|
||||
if (*p < '0' || *p > '9')
|
||||
return Original_o(token);
|
||||
|
||||
int index = 0;
|
||||
while (*p >= '0' && *p <= '9') {
|
||||
index = index * 10 + (*p - '0');
|
||||
++p;
|
||||
// `focus` / `focustarget*`. The 5-char prefix is unique among
|
||||
// unit tokens (no `focuscast` / `focused*` etc. live in vanilla
|
||||
// or modern resolvers), so any match continues as our token.
|
||||
if (sstrcmpi(token, kFocusPrefix, kFocusPrefixLen) == 0) {
|
||||
const uint64_t guid = Unit::Focus::Get();
|
||||
if (guid == 0)
|
||||
return 0;
|
||||
return WalkSuffix(guid, token + kFocusPrefixLen);
|
||||
}
|
||||
|
||||
const uint64_t guid = NamePlate::Events::GetGUIDByIndex(index);
|
||||
if (guid == 0)
|
||||
return 0; // OOB / no plate at index — modern returns nil
|
||||
return WalkSuffix(guid, p);
|
||||
// `nameplateN` / `nameplate1target*`. Requires a digit after the
|
||||
// prefix; anything else (`nameplate`, `nameplateX`) falls through
|
||||
// to the engine for its standard "Unknown unit name" error.
|
||||
if (sstrcmpi(token, kNamePlatePrefix, kNamePlatePrefixLen) == 0) {
|
||||
const char *p = token + kNamePlatePrefixLen;
|
||||
if (*p >= '0' && *p <= '9') {
|
||||
int index = 0;
|
||||
while (*p >= '0' && *p <= '9') {
|
||||
index = index * 10 + (*p - '0');
|
||||
++p;
|
||||
}
|
||||
const uint64_t guid = NamePlate::Events::GetGUIDByIndex(index);
|
||||
if (guid == 0)
|
||||
return 0;
|
||||
return WalkSuffix(guid, p);
|
||||
}
|
||||
}
|
||||
|
||||
return Original_o(token);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -143,4 +150,4 @@ static const Game::HookAutoRegister _hook{
|
||||
reinterpret_cast<void *>(&Hook_h),
|
||||
reinterpret_cast<void **>(&Original_o)};
|
||||
|
||||
} // namespace NamePlate::TokenResolver
|
||||
} // namespace Unit::TokenExtensions
|
||||
Reference in New Issue
Block a user