mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-13 06:38:57 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5aed30a5d | ||
|
|
9755cd3e09 | ||
|
|
604d87b9fc | ||
|
|
6b0a74ec4b | ||
|
|
165ccf3e9c | ||
|
|
ce4f80518f |
@@ -69,7 +69,7 @@ In the Frame rows, a name that starts with `:` is a method of the widget that th
|
||||
| [Animation](docs/API.md#animation) | `region:CreateAnimationGroup`, `region:GetAnimationGroups`, `region:StopAnimating`, `SetMaxAnimFramerate`, `GetMaxAnimFramerate`, `AnimationGroup` and `Animation` / `Alpha` / `Translation` / `Scale` / `Rotation` / `Path` / `ControlPoint` methods |
|
||||
| [AuctionHouse](docs/API.md#auctionhouse) | `C_AuctionHouse.PostItem` |
|
||||
| [Bindings](docs/API.md#bindings) | `SetBindingSpell`, `SetBindingItem`, `SetBindingMacro`, `SetBindingClick`, `SetOverrideBinding`, `SetOverrideBindingSpell`, `SetOverrideBindingItem`, `SetOverrideBindingMacro`, `SetOverrideBindingClick`, `ClearOverrideBindings`, `GetBindingAction` (`checkOverride`), `GetBindingByKey`, `IsModifiedClick`, `GetModifiedClick`, `SetModifiedClick`, `GetNumModifiedClickActions`, `GetModifiedClickAction` |
|
||||
| [Chat](docs/API.md#chat) | `GetCurrentChatGUID` |
|
||||
| [Chat](docs/API.md#chat) | `GetCurrentChatEvent`, `GetCurrentChatGUID` |
|
||||
| [ChatBubbles](docs/API.md#chatbubbles) | `C_ChatBubbles.GetAllChatBubbles` |
|
||||
| [Class](docs/API.md#class) | `FillLocalizedClassList` |
|
||||
| [ClassColor](docs/API.md#classcolor) | `C_ClassColor.GetClassColor` |
|
||||
@@ -103,7 +103,7 @@ In the Frame rows, a name that starts with `:` is a method of the widget that th
|
||||
| [GameObject](docs/API.md#gameobject) | `C_GameObjectInfo.GetGameObjectInfoByID`, `C_GameObjectInfo.RequestLoadGameObjectByID`, `ClosestGameObjectPosition` |
|
||||
| [Glue](docs/API.md#glue) | `C_Glue.IsOnGlueScreen` |
|
||||
| [GameTooltip](docs/API.md#gametooltip) | `GameTooltip:AddSpellByID`, `GameTooltip:AddTexture`, `GameTooltip:GetGameObject`, `GameTooltip:GetItem`, `GameTooltip:GetMinimumWidth`, `GameTooltip:GetOwner`, `GameTooltip:GetPadding`, `GameTooltip:GetSpell`, `GameTooltip:GetUnit`, `GameTooltip:GetUnitGUID`, `GameTooltip:HasGameObject`, `GameTooltip:HasItem`, `GameTooltip:HasSpell`, `GameTooltip:HasUnit`, `GameTooltip:IsEquippedItem`, `GameTooltip:IsUnit`, `GameTooltip:SetAnchorType`, `GameTooltip:SetEquipmentSet`, `GameTooltip:SetFrameStack`, `GameTooltip:SetHyperlink` (full links, `spell:`, `quest:`), `GameTooltip:SetHyperlinkCompareItem`, `GameTooltip:SetInventoryItemByID`, `GameTooltip:SetItemByGUID`, `GameTooltip:SetItemByID`, `GameTooltip:SetSpellByID`, `GameTooltip:SetTalentByID`, `GameTooltip:SetTotem`, `GameTooltip:SetUnitAura`, `OnTooltipSetItem`, `OnTooltipSetSpell`, `OnTooltipSetUnit`, `OnTooltipSetGameObject`, `OnTooltipSetFrameStack` (scripts) |
|
||||
| [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`, `GetNumGossipActiveQuests`, `GetNumGossipAvailableQuests` |
|
||||
| [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.GetPoiForUiMapID`, `C_GossipInfo.GetPoiInfo`, `C_GossipInfo.GetText`, `C_GossipInfo.SelectActiveQuest`, `C_GossipInfo.SelectAvailableQuest`, `C_GossipInfo.SelectOption`, `C_GossipInfo.SelectOptionByIndex`, `GetNumGossipActiveQuests`, `GetNumGossipAvailableQuests` |
|
||||
| [Hooks](docs/API.md#hooks) | `hooksecurefunc` |
|
||||
| [Input](docs/API.md#input) | `GetMouseButtonClicked`, `IsLeftAltKeyDown`, `IsLeftControlKeyDown`, `IsLeftShiftKeyDown`, `IsModifierKeyDown`, `IsMouseButtonDown`, `IsRightAltKeyDown`, `IsRightControlKeyDown`, `IsRightShiftKeyDown` |
|
||||
| [Instance](docs/API.md#instance) | `GetInstanceInfo`, `IsActiveBattlefieldArena` |
|
||||
@@ -275,6 +275,7 @@ when launching with `-console`), not as Lua functions. See the
|
||||
| `COMBAT_LOG_EVENT` | `timestamp, subevent, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags, ...` ([details](docs/API.md#combat_log_event_unfiltered--combat_log_event-events)) |
|
||||
| `COMBAT_LOG_EVENT_UNFILTERED` | same as `COMBAT_LOG_EVENT` |
|
||||
| `CURSOR_CHANGED` | `isDefault, newCursorType, oldCursorType, oldCursorVirtualID` |
|
||||
| `DYNAMIC_GOSSIP_POI_UPDATED` | *(none)* |
|
||||
| `EQUIPMENT_SETS_CHANGED` | *(none)* |
|
||||
| `EQUIPMENT_SWAP_PENDING` | `setID` |
|
||||
| `EQUIPMENT_SWAP_FINISHED` | `success, setID` |
|
||||
|
||||
+111
-26
@@ -66,6 +66,7 @@ build instructions.
|
||||
- [`GetSavedCharacterOrder(realm)` / `SetSavedCharacterOrder(realm, order)` — GlueXML only](#getsavedcharacterorderrealm--setsavedcharacterorderrealm-order--gluexml-only)
|
||||
|
||||
- [Chat](#chat)
|
||||
- [`GetCurrentChatEvent()`](#getcurrentchatevent)
|
||||
- [`GetCurrentChatGUID()`](#getcurrentchatguid)
|
||||
|
||||
- [Class](#class)
|
||||
@@ -198,6 +199,7 @@ build instructions.
|
||||
- [`UPDATE_INVENTORY_DURABILITY` event](#update_inventory_durability-event)
|
||||
- [`PLAYER_AVG_ITEM_LEVEL_READY` event](#player_avg_item_level_ready-event)
|
||||
- [`HEARTHSTONE_BOUND` event](#hearthstone_bound-event)
|
||||
- [`DYNAMIC_GOSSIP_POI_UPDATED` event](#dynamic_gossip_poi_updated-event)
|
||||
- [Player input-state events (`PLAYER_STARTED_MOVING` / `LOOKING` / `TURNING` + `STOPPED_*`)](#player-input-state-events)
|
||||
- [`GLOBAL_MOUSE_DOWN` / `GLOBAL_MOUSE_UP` events](#global_mouse_down--global_mouse_up-events)
|
||||
- [`AUCTION_MULTISELL_START` / `AUCTION_MULTISELL_UPDATE` / `AUCTION_MULTISELL_FAILURE` events](#c_auctionhousepostitemitemlocation-duration-quantity-numstacks-bid-buyout)
|
||||
@@ -376,8 +378,8 @@ build instructions.
|
||||
|
||||
- [GameTooltip](#gametooltip)
|
||||
- [`GameTooltip:AddTexture(texture)`](#gametooltipaddtexturetexture)
|
||||
- [`GameTooltip:SetSpellByID(spellID)`](#gametooltipsetspellbyidspellid)
|
||||
- [`GameTooltip:AddSpellByID(spellID)`](#gametooltipaddspellbyidspellid)
|
||||
- [`GameTooltip:SetSpellByID(spellID [, isPet [, showSubtext]])`](#gametooltipsetspellbyidspellid--ispet--showsubtext)
|
||||
- [`GameTooltip:AddSpellByID(spellID [, isPet [, showSubtext]])`](#gametooltipaddspellbyidspellid--ispet--showsubtext)
|
||||
- [`GameTooltip:SetTalentByID(talentID)`](#gametooltipsettalentbyidtalentid)
|
||||
- [`GameTooltip:SetInventoryItemByID(itemID)`](#gametooltipsetinventoryitembyiditemid)
|
||||
- [`GameTooltip:SetHyperlink(link)`](#gametooltipsethyperlinklink)
|
||||
@@ -429,6 +431,8 @@ build instructions.
|
||||
- [`C_GossipInfo.SelectAvailableQuest(questID)`](#c_gossipinfoselectavailablequestquestid)
|
||||
- [`C_GossipInfo.SelectActiveQuest(questID)`](#c_gossipinfoselectactivequestquestid)
|
||||
- [`C_GossipInfo.CloseGossip()`](#c_gossipinfoclosegossip)
|
||||
- [`C_GossipInfo.GetPoiForUiMapID(uiMapID)`](#c_gossipinfogetpoiforuimapiduimapid)
|
||||
- [`C_GossipInfo.GetPoiInfo(uiMapID, gossipPoiID)`](#c_gossipinfogetpoiinfouimapid-gossippoiid)
|
||||
- [`GetNumGossipAvailableQuests()` / `GetNumGossipActiveQuests()`](#getnumgossipavailablequests--getnumgossipactivequests)
|
||||
|
||||
- [Hooks](#hooks)
|
||||
@@ -1934,6 +1938,40 @@ end
|
||||
|
||||
## Chat
|
||||
|
||||
### `GetCurrentChatEvent()`
|
||||
|
||||
Returns the name and the arguments of the `CHAT_MSG_*` event that is being
|
||||
handled now. It returns nothing when you call it outside a chat event.
|
||||
This is a ClassicAPI extension.
|
||||
|
||||
```lua
|
||||
event, arg1, arg2, ..., arg12 = GetCurrentChatEvent()
|
||||
```
|
||||
|
||||
The values are the values that the event gave to its handlers, in the same
|
||||
order: the event name, `arg1` to `arg10`, then the line ID (`arg11`) and the
|
||||
sender GUID (`arg12`) (see
|
||||
[`CHAT_MSG_*` arguments](#chat_msg_-arguments-lineid-arg11-and-guid-arg12)).
|
||||
|
||||
Use it when the `event` and `arg1` to `arg12` globals are not reliable. Other
|
||||
handlers and hooks can change these globals before your code reads them. This
|
||||
function always gives the values of the event that is in progress.
|
||||
|
||||
```lua
|
||||
local orig = ChatFrame_OnEvent
|
||||
function ChatFrame_OnEvent(event)
|
||||
local name, text, sender = GetCurrentChatEvent()
|
||||
if name then
|
||||
-- name, text and sender are the real values, even if a hook changed arg1/arg2
|
||||
end
|
||||
return orig(event)
|
||||
end
|
||||
```
|
||||
|
||||
If a chat event handler causes another chat event, the function gives the
|
||||
inner event while that handler runs. When the inner handler returns, it gives
|
||||
the outer event again.
|
||||
|
||||
### `GetCurrentChatGUID()`
|
||||
|
||||
Returns the sender's GUID for the `CHAT_MSG_*` event that is being handled
|
||||
@@ -4518,6 +4556,12 @@ Character switch is handled automatically: the character-entry init
|
||||
re-zeroes the flag, so each new character's first update is also
|
||||
treated as sync and suppressed.
|
||||
|
||||
### `DYNAMIC_GOSSIP_POI_UPDATED` event
|
||||
|
||||
Fires (with no payload) when a gossip point of interest is added to the map or
|
||||
removed from it. When it fires, read the marker again with
|
||||
[`C_GossipInfo.GetPoiForUiMapID`](#c_gossipinfogetpoiforuimapiduimapid).
|
||||
|
||||
### Player input-state events
|
||||
|
||||
Three pairs of zero-payload events fired from a single per-frame
|
||||
@@ -8644,44 +8688,51 @@ The call adds no texture and returns `false` in these cases:
|
||||
12x12. Do not resize or move a texture after you add it: the text of the
|
||||
line does not move with it.
|
||||
|
||||
### `GameTooltip:SetSpellByID(spellID)`
|
||||
### `GameTooltip:SetSpellByID(spellID [, isPet [, showSubtext]])`
|
||||
|
||||
Renders a spell tooltip for any `spellID`, including spells the player has
|
||||
not learned. The stock 1.12 `GameTooltip:SetSpell(slot, bookType)` only works
|
||||
for entries in the player's spellbook (or pet book). `SetSpellByID` bypasses
|
||||
the spellbook indirection and calls WoW's internal tooltip builder directly.
|
||||
Shows the tooltip of a spell. The spell can be any spell ID, also a spell
|
||||
that the player did not learn.
|
||||
|
||||
| Argument | Type | Description |
|
||||
|---|---|---|
|
||||
| `spellID` | number | The ID of the spell. |
|
||||
| `isPet` | boolean | If `true`, the cost, range, and cooldown are those of the pet. The default is `false`. |
|
||||
| `showSubtext` | boolean | If `true`, the rank shows to the right of the spell name. The default is `false`. |
|
||||
|
||||
Returns `1` when the tooltip shows a cooldown that counts down. Otherwise,
|
||||
returns `nil`. The tooltip shows the remaining cooldown only for a spell
|
||||
with a cooldown of 1 minute or more.
|
||||
|
||||
If `spellID` is not a number, or is less than 0, the function raises an
|
||||
error. If no spell has this ID, the tooltip clears and hides.
|
||||
|
||||
```lua
|
||||
GameTooltip:SetOwner(UIParent, "ANCHOR_CURSOR")
|
||||
GameTooltip:SetSpellByID(133) -- Fireball
|
||||
GameTooltip:SetSpellByID(133) -- Fireball
|
||||
GameTooltip:SetSpellByID(133, false, true) -- Fireball, with its rank
|
||||
GameTooltip:Show()
|
||||
```
|
||||
|
||||
### `GameTooltip:AddSpellByID(spellID)`
|
||||
### `GameTooltip:AddSpellByID(spellID [, isPet [, showSubtext]])`
|
||||
|
||||
The append counterpart to [`SetSpellByID`](#gametooltipsetspellbyidspellid):
|
||||
adds a spell's full tooltip (name, cast time, cost, range, description) to
|
||||
the *current* tooltip **without clearing** the existing lines. Works for any
|
||||
`spellID`, learned or not. Lets you compose tooltips — e.g. a header line
|
||||
plus a spell block.
|
||||
Adds the tooltip of a spell below the lines that the tooltip already shows.
|
||||
The tooltip does not clear first. The arguments and the return value are the
|
||||
same as for [`SetSpellByID`](#gametooltipsetspellbyidspellid--ispet--showsubtext).
|
||||
|
||||
Use this function to make one tooltip from more parts, for example a header
|
||||
line and a spell.
|
||||
|
||||
```lua
|
||||
GameTooltip:SetOwner(UIParent, "ANCHOR_CURSOR")
|
||||
GameTooltip:AddLine("Rank 3 grants:")
|
||||
GameTooltip:AddSpellByID(133) -- Fireball, appended below the header
|
||||
GameTooltip:AddSpellByID(133) -- Fireball, below the header
|
||||
GameTooltip:Show()
|
||||
```
|
||||
|
||||
Unlike `SetSpellByID`, this does **not** update what
|
||||
[`GetSpell`](#gametooltipgetspell) reports — the appended spell isn't the
|
||||
tooltip's "primary" spell, so `GetSpell` keeps reflecting whatever `SetX`
|
||||
call (if any) built the base tooltip. Silent no-op for `spellID <= 0` or an
|
||||
unknown spell.
|
||||
|
||||
> The engine's tooltip builder only exposes "append" via its internal talent
|
||||
> "next rank" preview, which emits a `Next rank:` header instead of the
|
||||
> spell name; we overwrite that header line with the real name so the
|
||||
> appended block reads normally.
|
||||
This function does not change the value of
|
||||
[`GetSpell`](#gametooltipgetspell). `GetSpell` continues to give the spell
|
||||
of the tooltip before the added lines. If no spell has this ID, the function
|
||||
adds no lines.
|
||||
|
||||
### `GameTooltip:GetItem()`
|
||||
|
||||
@@ -9003,7 +9054,7 @@ GameTooltip:SetHyperlink("quest:783")
|
||||
| `\|H<payload>\|h[text]\|h` | The tooltip for the payload, as in the rows below |
|
||||
| `item:` | The item tooltip |
|
||||
| `enchant:<spellID>` | The spell tooltip, for an Enchanting formula or a trade-skill recipe (such as a [`GetTradeSkillRecipeLink`](#gettradeskillrecipelinkindex) link) |
|
||||
| `spell:<spellID>` | The spell tooltip, the same as [`SetSpellByID`](#gametooltipsetspellbyidspellid) |
|
||||
| `spell:<spellID>` | The spell tooltip of [`SetSpellByID`](#gametooltipsetspellbyidspellid--ispet--showsubtext), with the rank and without the cooldown |
|
||||
| `quest:<questID>[:level]` | The quest title, then the objectives text. Tokens such as `$N` are expanded, as in the quest log. |
|
||||
|
||||
Other link types (for example `achievement:` and `talent:`) raise the error
|
||||
@@ -9772,6 +9823,40 @@ Picks an in-progress quest by `questID`. Same shape as
|
||||
Closes the gossip window. Direct passthrough to the engine's
|
||||
`CloseGossip`. Returns nothing.
|
||||
|
||||
### `C_GossipInfo.GetPoiForUiMapID(uiMapID)`
|
||||
|
||||
Returns the ID of the gossip point of interest on the map `uiMapID`, or nil.
|
||||
Some gossip options add this marker to the map, for example the directions
|
||||
that a city guard gives. There is only one marker at a time, and its ID is
|
||||
always `1`.
|
||||
|
||||
The function returns nil when there is no marker, or when the marker is not
|
||||
inside the map. The marker goes away when the player comes within 10 yards of
|
||||
it, or 8 minutes after it was added.
|
||||
|
||||
### `C_GossipInfo.GetPoiInfo(uiMapID, gossipPoiID)`
|
||||
|
||||
Returns a table that describes the gossip point of interest on the map
|
||||
`uiMapID`, or nil. It returns nil in the same conditions as
|
||||
`GetPoiForUiMapID`, and when `gossipPoiID` is not the ID that
|
||||
`GetPoiForUiMapID` returns.
|
||||
|
||||
| Field | Type | Description |
|
||||
|----------------|---------|-------------|
|
||||
| `name` | string | The name of the location, for example `"Stormwind Bank"`. |
|
||||
| `textureIndex` | number | The icon index. It is the same index that `GetMapLandmarkInfo` returns for this marker. |
|
||||
| `position` | vector2 | The position on the map, from 0 to 1 on each axis. |
|
||||
| `inBattleMap` | boolean | Always `false`. |
|
||||
|
||||
```lua
|
||||
local mapID = C_Map.GetBestMapForUnit("player")
|
||||
local poiID = C_GossipInfo.GetPoiForUiMapID(mapID)
|
||||
if poiID then
|
||||
local info = C_GossipInfo.GetPoiInfo(mapID, poiID)
|
||||
print(info.name, info.position:GetXY())
|
||||
end
|
||||
```
|
||||
|
||||
### `GetNumGossipAvailableQuests()` / `GetNumGossipActiveQuests()`
|
||||
|
||||
Returns the number of quests that `GetGossipAvailableQuests()` /
|
||||
|
||||
@@ -40,6 +40,11 @@ double InternalToPixel(float internal) {
|
||||
return static_cast<double>(internal) * div * Offsets::UI_COORD_SCALE_UNIT / mul;
|
||||
}
|
||||
|
||||
bool StringToFlag(const char *text) {
|
||||
using StringToFlag_t = int(__fastcall *)(const char *text);
|
||||
return reinterpret_cast<StringToFlag_t>(Offsets::FUN_XML_ATTR_TO_BOOL)(text) != 0;
|
||||
}
|
||||
|
||||
namespace Lua {
|
||||
// Each entry binds a typed function pointer in `Game::Lua::` to the
|
||||
// corresponding raw VA in `Offsets`. The X-macro keeps the column-aligned
|
||||
@@ -131,6 +136,13 @@ void PushObject(void *L, void *object) {
|
||||
RawGetI(L, REGISTRY_INDEX, ObjectLuaRef(object));
|
||||
}
|
||||
|
||||
bool ToFlag(void *L, int index, bool defaultValue) {
|
||||
// Returns a full int in EAX on every path (Ghidra's `bool` is wrong).
|
||||
using ToFlag_t = int(__fastcall *)(void *L, int index, int defaultValue);
|
||||
return reinterpret_cast<ToFlag_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE)(L, index,
|
||||
defaultValue ? 1 : 0) != 0;
|
||||
}
|
||||
|
||||
const char *ObjectDisplayName(void *object) {
|
||||
using GetName_t = const char *(__thiscall *)(void *);
|
||||
const void *vtable = Game::Read<const void *>(object, 0);
|
||||
|
||||
+13
@@ -104,6 +104,12 @@ using LoadScriptFunctions_t = void(__fastcall *)();
|
||||
// registering its 109 glue functions. See FUN_LOAD_GLUE_SCRIPT_FUNCTIONS.
|
||||
using LoadGlueScriptFunctions_t = void(__stdcall *)();
|
||||
|
||||
// The engine's text-to-flag reader (FUN_XML_ATTR_TO_BOOL, 4.3.4's
|
||||
// `StringToBOOL(const char *)`), which XML boolean attributes go through:
|
||||
// true for text starting 1-9/T/Y/t/y or equal to "on"/"enabled" (any case),
|
||||
// false for anything else, including null.
|
||||
bool StringToFlag(const char *text);
|
||||
|
||||
// --- API documentation descriptors -----------------------------------------
|
||||
//
|
||||
// Every Lua registration (see the `Register*` functions in `Game::Lua`) can
|
||||
@@ -312,6 +318,13 @@ extern const lua_pushboolean_t PushBoolean;
|
||||
// engine's `lua_pushboolean` ABI wants `int` (any non-zero = true), so
|
||||
// this just funnels through the raw binding with a single conversion.
|
||||
inline void PushBool(void *L, bool b) { PushBoolean(L, static_cast<int>(b)); }
|
||||
// The engine's script-flag argument reader (FUN_LUA_TO_BOOLEAN_LOOSE, 4.3.4's
|
||||
// `StringToBOOL(L, index, default)`), which every engine method taking an
|
||||
// optional flag uses: nil is false, a boolean is itself, a number is its
|
||||
// truncated int (so 0.5 is false), a string reads as StringToFlag except that
|
||||
// text it does not recognize is `defaultValue`, and any other type is
|
||||
// `defaultValue`.
|
||||
bool ToFlag(void *L, int index, bool defaultValue = false);
|
||||
extern const lua_pushstring_t PushString;
|
||||
extern const lua_pushlstring_t PushLString;
|
||||
extern const lua_pushvalue_t PushValue;
|
||||
|
||||
+49
-5
@@ -189,8 +189,16 @@ enum Offsets {
|
||||
// lookups (e.g. GameTooltip:GetOwner) subtract this back out.
|
||||
OFF_FRAME_LAYOUT_SUBOBJECT = 0x24,
|
||||
|
||||
// Inner spell-tooltip builder, called from Script_GameTooltip_SetSpell at 0x00532E92.
|
||||
// __thiscall(spellID, 0, 0, isPet, 0, 0, 0); we always pass isPet=0.
|
||||
// Inner spell-tooltip builder (CGTooltip::BuildSpell), called from
|
||||
// Script_GameTooltip_SetSpell at 0x00532E92. `uint __thiscall(spellID,
|
||||
// compact, cooldownMs, pet, showRank, talent, rebuild)`, RET 0x1C — the
|
||||
// first six of 4.3.4's CGTooltip::SetSpell(spellId, brief, cooldown,
|
||||
// isPet, showSubtext, …, talent, …, append, …) in order. `rebuild`
|
||||
// skips the clear and the spellID stash (+0x39C) and, unlike 4.3.4's
|
||||
// append, always heads the block with "\n" TOOLTIP_TALENT_NEXT_RANK
|
||||
// instead of the name line (talent or not). A TRADESPELL record builds
|
||||
// its created item's tooltip instead. Returns 1 when it added the
|
||||
// cooldown line (cooldownMs != 0, recovery >= 60s) or a chance-to line.
|
||||
FUN_GAMETOOLTIP_BUILD_SPELL_TOOLTIP = 0x0052E610,
|
||||
|
||||
// Existing GameTooltip method-table entries we dispatch to from
|
||||
@@ -3783,9 +3791,12 @@ enum Offsets {
|
||||
// The two default tooltip line colors, packed {b,g,r,a} like every
|
||||
// FUN_GAMETOOLTIP_ADD_LINE color: text 0xFFFFFFFF (static init 0x00528E90)
|
||||
// and title 0xFFFFD200 (static init 0x00528E50) — 4.3.4's
|
||||
// s_tooltipTextColor / s_tooltipTitleColor.
|
||||
// s_tooltipTextColor / s_tooltipTitleColor. The gray 0xFF808080 is the
|
||||
// right column of the spell builder's name line (benilla-re
|
||||
// s_tooltipGray, verified) — 4.3.4's s_tooltipGrayColor.
|
||||
VAR_TOOLTIP_TEXT_COLOR = 0x00C0CF60,
|
||||
VAR_TOOLTIP_TITLE_COLOR = 0x00C0D3E8,
|
||||
VAR_TOOLTIP_GRAY_COLOR = 0x00C0D3C4,
|
||||
FUN_SCRIPT_FRAME_SHOW = 0x00775750,
|
||||
FUN_SCRIPT_FRAME_HIDE = 0x00775810,
|
||||
FUN_SCRIPT_FRAME_SETMINRESIZE = 0x00776020,
|
||||
@@ -10376,6 +10387,37 @@ enum Offsets {
|
||||
// the previous NPC's stale arrays/text after close.
|
||||
VAR_GOSSIP_NPC_GUID = 0x00BC3F58,
|
||||
|
||||
// u32, 1 while the greeting is unresolved. The greeting builder
|
||||
// 0x004E2010 (its only writer) sets it on an NPC-text cache miss or
|
||||
// textID 0 and clears it once the text resolves, right before
|
||||
// GOSSIP_SHOW; the option selector 0x004E2320 refuses while it is
|
||||
// set. The GUID above is latched earlier, so GUID != 0 alone counts
|
||||
// a window that has not shown yet — with the previous NPC's text
|
||||
// still in VAR_GOSSIP_GREETING_TEXT. Verified via xrefs (1 read, 2
|
||||
// writes) and the 0x004E2010 decompile.
|
||||
VAR_GOSSIP_TEXT_PENDING = 0x00BBB670,
|
||||
|
||||
// --- Gossip point of interest (SMSG_GOSSIP_POI, opcode 0x224) ------
|
||||
// The handler 0x004E2840 stores the marker in static blip record 1
|
||||
// (records 0x74 apart from 0x00CEA760: 0 = selected quest, 1 = gossip,
|
||||
// 2 = corpse) through the setter 0x006DAC10, the record's only writer.
|
||||
// There is one marker at a time; a new packet overwrites it. It is
|
||||
// live while its position is nonzero: 0x006D93A0 zeroes x/y when the
|
||||
// player comes within 10 yards or 480 s after it was set. The world
|
||||
// map's landmark builder 0x004A67A0 reads it the same way and projects
|
||||
// it with 0x004A7360, which drops it unless the displayed map's row
|
||||
// has the record's map id and the point lands inside 0..1. Verified
|
||||
// via the 0x006DAC10 / 0x004A67A0 / 0x004A7360 decompiles.
|
||||
VAR_GOSSIP_POI_BLIP = 0x00CEA7D4,
|
||||
VAR_GOSSIP_POI_NAME = 0x00CEA6E0, // char[0x40], slot 1 of the names at 0x00CEA6A0
|
||||
// u32, wall-clock seconds (0x00429580) + 480 at each set; both clears
|
||||
// zero it with x/y (0x006D93FB, 0x006D9A3A). 0 = no marker.
|
||||
VAR_GOSSIP_POI_EXPIRY = 0x00CEAAA4,
|
||||
OFF_STATIC_BLIP_ICON = 0x08, // u32, POI icon index (the packet's icon)
|
||||
OFF_STATIC_BLIP_X = 0x10, // float, world X (north)
|
||||
OFF_STATIC_BLIP_Y = 0x14, // float, world Y (west)
|
||||
OFF_STATIC_BLIP_MAP = 0x1C, // i32, Map.dbc id: the minimap's map when set
|
||||
|
||||
// --- Quest-greeting state (SMSG_QUESTGIVER_QUEST_LIST) -------------
|
||||
// Vanilla's SECOND questgiver path: NPCs without a gossip menu send
|
||||
// SMSG_QUESTGIVER_QUEST_LIST, whose handler (FUN_005dbb10) fills a
|
||||
@@ -11214,7 +11256,7 @@ enum Offsets {
|
||||
GXSHADER_TYPE_PIXEL = 1,
|
||||
GXSHADER_CODE_D3D = 2,
|
||||
GXRS_PIXEL_SHADER = 0x3F, // pointer-form selector (0x40 = vertex shader); doc only — the engine's draw binds it
|
||||
FUN_LUA_TO_BOOLEAN_LOOSE = 0x006F1C10, // __fastcall(L, idx, default): nil→0, boolean, number→int, "1/t/y…"→1, "0/f/n…"→0, "enabled"→1, "disabled"→0, other→default
|
||||
FUN_LUA_TO_BOOLEAN_LOOSE = 0x006F1C10, // __fastcall(L, idx, default): nil→0, boolean, number→int, "1/t/y…"→1, "0/f/n…"→0, "enabled"→1, "disabled"→0, other→default. Called through Game::Lua::ToFlag (4.3.4 StringToBOOL(L, idx, default))
|
||||
|
||||
// --- Texture memory accounting: the zero-dimension hang -------------------
|
||||
// FUN_GX_TEXTURE_MIP_BYTES totals a texture's bytes across its mip chain:
|
||||
@@ -11885,7 +11927,9 @@ enum Offsets {
|
||||
// (0x00779FC6), then reads its own attributes: "letters" (0x0077A046),
|
||||
// "autoFocus" (0x0077A0A5), "multiLine", "numeric", "historyLines".
|
||||
// Booleans go through FUN_XML_ATTR_TO_BOOL - `int __fastcall(const char *)`,
|
||||
// 1 for text starting 1/t/y - as for autoFocus at 0x0077A0BC.
|
||||
// 4.3.4's StringToBOOL(const char *): 1 for text starting 1-9/T/Y or equal
|
||||
// to on/enabled, else 0 (null too) - as for autoFocus at 0x0077A0BC.
|
||||
// Called through Game::StringToFlag.
|
||||
FUN_XML_EDITBOX_LOAD = 0x00779FB0,
|
||||
FUN_XML_ATTR_TO_BOOL = 0x006F1B30,
|
||||
// Selection-highlight regions — three consecutive CSimpleTexture pointers
|
||||
|
||||
@@ -471,14 +471,10 @@ namespace {
|
||||
|
||||
Group *Self(void *L) { return static_cast<Group *>(Resolve(L, 1, Group::s_class)); }
|
||||
|
||||
using LooseBool_t = int(__fastcall *)(void *L, int idx, int defaultValue);
|
||||
bool LooseBool(void *L, int idx, int dflt) {
|
||||
return reinterpret_cast<LooseBool_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE)(L, idx, dflt) != 0;
|
||||
}
|
||||
|
||||
int __fastcall Script_Play(void *L) {
|
||||
if (Group *g = Self(L))
|
||||
g->Play(LooseBool(L, 2, 0));
|
||||
g->Play(Game::Lua::ToFlag(L, 2));
|
||||
return 0;
|
||||
}
|
||||
int __fastcall Script_Pause(void *L) {
|
||||
@@ -620,7 +616,7 @@ int __fastcall Script_SetIgnoreFramerateThrottle(void *L) {
|
||||
Group *g = Self(L);
|
||||
if (g == nullptr)
|
||||
return 0;
|
||||
if (LooseBool(L, 2, 0))
|
||||
if (Game::Lua::ToFlag(L, 2))
|
||||
g->flags |= GROUP_IGNORE_THROTTLE;
|
||||
else
|
||||
g->flags &= ~GROUP_IGNORE_THROTTLE;
|
||||
|
||||
@@ -63,7 +63,6 @@ using KeyDispatch_t = int(__fastcall *)(void *mgr, void *edx, const char *key, i
|
||||
using CommandExecute_t = int(__fastcall *)(void *mgr, void *edx, const char *cmd, int isDown);
|
||||
using GetCommandAction_t = const char *(__thiscall *)(void *mgr, const char *key);
|
||||
using ScriptFn_t = int(__fastcall *)(void *L);
|
||||
using ToBool_t = int(__fastcall *)(void *L, int idx, int dflt);
|
||||
|
||||
KeyDispatch_t g_keyDispatchOriginal = nullptr;
|
||||
CommandExecute_t g_commandExecuteOriginal = nullptr;
|
||||
@@ -260,9 +259,6 @@ int __fastcall CommandExecute_h(void *mgr, void *edx, const char *command, int i
|
||||
|
||||
// --- Lua ----------------------------------------------------------------------
|
||||
|
||||
bool LooseBool(void *L, int idx) {
|
||||
return reinterpret_cast<ToBool_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE)(L, idx, 0) != 0;
|
||||
}
|
||||
|
||||
const char *OptString(void *L, int idx) {
|
||||
return Game::Lua::IsString(L, idx) ? Game::Lua::ToString(L, idx) : nullptr;
|
||||
@@ -321,7 +317,7 @@ int __fastcall Script_SetOverrideBinding(void *L) {
|
||||
Game::Lua::Error(L, "Usage: SetOverrideBinding(owner, isPriority, \"KEY\"[, \"COMMAND\"])");
|
||||
return 0;
|
||||
}
|
||||
SetOverrideBinding(owner, LooseBool(L, 2), Game::Lua::ToString(L, 3), OptString(L, 4));
|
||||
SetOverrideBinding(owner, Game::Lua::ToFlag(L, 2), Game::Lua::ToString(L, 3), OptString(L, 4));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -332,7 +328,7 @@ int SetOverrideCommand(void *L, const char *prefix, const char *usage) {
|
||||
return 0;
|
||||
}
|
||||
const std::string command = std::string(prefix) + Game::Lua::ToString(L, 4);
|
||||
SetOverrideBinding(owner, LooseBool(L, 2), Game::Lua::ToString(L, 3), command.c_str());
|
||||
SetOverrideBinding(owner, Game::Lua::ToFlag(L, 2), Game::Lua::ToString(L, 3), command.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -358,7 +354,7 @@ int __fastcall Script_SetOverrideBindingClick(void *L) {
|
||||
return 0;
|
||||
}
|
||||
const std::string command = ClickCommand(Game::Lua::ToString(L, 4), OptString(L, 5));
|
||||
SetOverrideBinding(owner, LooseBool(L, 2), Game::Lua::ToString(L, 3), command.c_str());
|
||||
SetOverrideBinding(owner, Game::Lua::ToFlag(L, 2), Game::Lua::ToString(L, 3), command.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -376,7 +372,7 @@ int __fastcall Script_ClearOverrideBindings(void *L) {
|
||||
// GetBindingAction(key [, checkOverride]): 4.3.4's GetCommandAction checks the
|
||||
// override first when asked; the saved binding is the engine's own answer.
|
||||
int __fastcall Script_GetBindingAction(void *L) {
|
||||
if (Game::Lua::IsString(L, 1) && LooseBool(L, 2)) {
|
||||
if (Game::Lua::IsString(L, 1) && Game::Lua::ToFlag(L, 2)) {
|
||||
if (const char *command = ActiveOverride(Game::Lua::ToString(L, 1))) {
|
||||
Game::Lua::PushString(L, command);
|
||||
return 1;
|
||||
|
||||
+93
-2
@@ -11,8 +11,9 @@
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// CHAT_MSG_* arg11 (lineID) and arg12 (sender GUID), as 4.3.4 fires them, and
|
||||
// `GetCurrentChatGUID()`, which reads the same sender from inside the handler.
|
||||
// CHAT_MSG_* arg11 (lineID) and arg12 (sender GUID), as 4.3.4 fires them,
|
||||
// `GetCurrentChatGUID()`, which reads the same sender from inside the handler,
|
||||
// and `GetCurrentChatEvent()`, which reads the whole payload back.
|
||||
//
|
||||
// 4.3.4's SendChatEvent (ChatFrame.cpp, 0x008E82E0) fires every chat event
|
||||
// with "%s%s%s%s%s%s%d%d%s%d%d%s%u%b": the ten arguments 1.12 already passes,
|
||||
@@ -33,6 +34,13 @@
|
||||
// `GetCurrentChatGUID()` returns that same sender, as nil where arg12 is "",
|
||||
// so code written for the function and code reading arg12 always agree.
|
||||
//
|
||||
// `GetCurrentChatEvent()` (a ClassicAPI extension) returns the event name and
|
||||
// the arguments exactly as fired. 1.12's ChatFrame_OnEvent reads the `event`
|
||||
// and `arg1..` globals, which any handler or hook running before it may
|
||||
// overwrite; the payload read here is the one this fire passed. It lives only
|
||||
// for the fire, and a chat event fired from another's handler restores the
|
||||
// outer payload when it returns.
|
||||
//
|
||||
// The sender comes from a SenderScope. Chat::Dispatch opens one around the
|
||||
// chat-message dispatch with the GUID it picks for that chat type; the text
|
||||
// emote builder is co-hooked below because it fires without passing its GUID
|
||||
@@ -70,6 +78,33 @@ using TextEmote_t = void(__fastcall *)(const uint64_t *senderGuid, int emote, co
|
||||
|
||||
TextEmote_t g_textEmoteOriginal = nullptr;
|
||||
|
||||
// The chat event being fired, as pushed. `argc` is 12 for the stock format and
|
||||
// 10 when another loader's format was fired unextended.
|
||||
struct Payload {
|
||||
int eventId;
|
||||
const char *text, *sender, *language, *channelString, *target, *flags;
|
||||
int zoneChannelID, channelNumber;
|
||||
const char *channelName;
|
||||
int channelUnk;
|
||||
uint32_t lineID;
|
||||
const char *guid;
|
||||
int argc;
|
||||
};
|
||||
|
||||
const Payload *g_payload = nullptr;
|
||||
|
||||
// Points g_payload at a fire for its duration, restoring the outer one after.
|
||||
class PayloadScope {
|
||||
public:
|
||||
explicit PayloadScope(const Payload &p) : prev_(g_payload) { g_payload = &p; }
|
||||
~PayloadScope() { g_payload = prev_; }
|
||||
PayloadScope(const PayloadScope &) = delete;
|
||||
PayloadScope &operator=(const PayloadScope &) = delete;
|
||||
|
||||
private:
|
||||
const Payload *prev_;
|
||||
};
|
||||
|
||||
// 1.12's chat tags are "AFK", "DND" and "GM"; 4.3.4's IsStaffChatFlag also
|
||||
// matches "DEV", which this client never sends.
|
||||
bool IsStaffFlag(const char *flags) { return flags != nullptr && std::strcmp(flags, "GM") == 0; }
|
||||
@@ -88,7 +123,10 @@ void __cdecl FireSite(int eventId, const char *fmt, const char *text, const char
|
||||
text = Chat::RaidMarkers::ForChatEvent(text, markerBuf, sizeof markerBuf);
|
||||
// Another loader passing its own format: the arguments may not be ours to
|
||||
// extend, so fire exactly what was asked.
|
||||
Payload payload{eventId, text, sender, language, channelString, target, flags,
|
||||
zoneChannelID, channelNumber, channelName, channelUnk, 0, "", 10};
|
||||
if (fmt != reinterpret_cast<const char *>(Offsets::VAR_STR_CHAT_EVENT_FORMAT)) {
|
||||
PayloadScope scope(payload);
|
||||
fire(eventId, fmt, text, sender, language, channelString, target, flags, zoneChannelID,
|
||||
channelNumber, channelName, channelUnk);
|
||||
return;
|
||||
@@ -97,6 +135,10 @@ void __cdecl FireSite(int eventId, const char *fmt, const char *text, const char
|
||||
char guid[Guid::STRING_SIZE] = "";
|
||||
if (g_sender != 0)
|
||||
Guid::FormatAsString(g_sender, guid, sizeof guid);
|
||||
payload.lineID = lineID;
|
||||
payload.guid = guid;
|
||||
payload.argc = 12;
|
||||
PayloadScope scope(payload);
|
||||
fire(eventId, "%s%s%s%s%s%s%d%d%s%d%d%s", text, sender, language, channelString, target, flags,
|
||||
zoneChannelID, channelNumber, channelName, channelUnk, static_cast<int>(lineID), guid);
|
||||
}
|
||||
@@ -115,6 +157,53 @@ int __fastcall Script_GetCurrentChatGUID(void *L) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// event name, arg1..argN — the same values the handler received.
|
||||
int __fastcall Script_GetCurrentChatEvent(void *L) {
|
||||
using namespace Game::Lua;
|
||||
const Payload *p = g_payload;
|
||||
if (p == nullptr || CheckStack(L, p->argc + 1) == 0)
|
||||
return 0;
|
||||
const auto *entry = Game::Read<uint8_t *>(Offsets::VAR_EVENT_TABLE_BASE_PTR) +
|
||||
p->eventId * Offsets::EVENT_ENTRY_STRIDE;
|
||||
PushString(L, Game::Read<const char *>(entry, Offsets::OFF_EVENT_ENTRY_NAME));
|
||||
// A null string pushes nil, as it does through the engine's dispatcher.
|
||||
PushString(L, p->text);
|
||||
PushString(L, p->sender);
|
||||
PushString(L, p->language);
|
||||
PushString(L, p->channelString);
|
||||
PushString(L, p->target);
|
||||
PushString(L, p->flags);
|
||||
PushNumber(L, p->zoneChannelID);
|
||||
PushNumber(L, p->channelNumber);
|
||||
PushString(L, p->channelName);
|
||||
PushNumber(L, p->channelUnk);
|
||||
if (p->argc == 12) {
|
||||
PushNumber(L, p->lineID);
|
||||
PushString(L, p->guid);
|
||||
}
|
||||
return p->argc + 1;
|
||||
}
|
||||
|
||||
const Game::Doc::Field kChatEventRets[] = {
|
||||
Game::Doc::Req("event", "string", "The CHAT_MSG_* event name."),
|
||||
Game::Doc::Opt("text", "string"),
|
||||
Game::Doc::Opt("playerName", "string"),
|
||||
Game::Doc::Opt("languageName", "string"),
|
||||
Game::Doc::Opt("channelName", "string"),
|
||||
Game::Doc::Opt("playerName2", "string"),
|
||||
Game::Doc::Opt("specialFlags", "string"),
|
||||
Game::Doc::Opt("zoneChannelID", "number"),
|
||||
Game::Doc::Opt("channelIndex", "number"),
|
||||
Game::Doc::Opt("channelBaseName", "string"),
|
||||
Game::Doc::Opt("unused", "number"),
|
||||
Game::Doc::Opt("lineID", "number"),
|
||||
Game::Doc::Opt("guid", "string"),
|
||||
};
|
||||
const Game::Doc::Function kGetCurrentChatEvent{
|
||||
"Returns the name and arguments of the CHAT_MSG_* event being handled now, or nothing "
|
||||
"outside one.",
|
||||
{}, kChatEventRets, "ChatGlobals", true};
|
||||
|
||||
// Once per process; a patched site no longer reaches FUN_FIRE_EVENT, so a
|
||||
// second pass would refuse anyway.
|
||||
bool g_applied = false;
|
||||
@@ -139,6 +228,8 @@ void Apply() {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterGlobalFunction("GetCurrentChatGUID", &Script_GetCurrentChatGUID);
|
||||
Game::Lua::RegisterGlobalFunction("GetCurrentChatEvent", &Script_GetCurrentChatEvent,
|
||||
&kGetCurrentChatEvent);
|
||||
Apply();
|
||||
}
|
||||
|
||||
|
||||
@@ -82,17 +82,12 @@ constexpr int kMaxRetention = 0x7FFFFFFF / 1000;
|
||||
using SStrCmpI_t = int(__stdcall *)(const char *a, const char *b, int n);
|
||||
using Tokenize_t = void(__stdcall *)(char **cursor, char *out, unsigned outSize,
|
||||
const char *delims, int *outQuoted);
|
||||
using LooseBool_t = int(__fastcall *)(void *L, int idx, int dflt);
|
||||
|
||||
int StrCmpI(const char *a, const char *b) {
|
||||
return reinterpret_cast<SStrCmpI_t>(static_cast<uintptr_t>(Offsets::FUN_SSTR_CMP_I))(
|
||||
a, b, kStormMaxStr);
|
||||
}
|
||||
|
||||
bool ArgFlag(void *L, int idx) {
|
||||
return reinterpret_cast<LooseBool_t>(
|
||||
static_cast<uintptr_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE))(L, idx, 0) != 0;
|
||||
}
|
||||
|
||||
bool Complete(uint32_t matched) {
|
||||
return (matched & AFFILIATION_MASK) && (matched & REACTION_MASK) && (matched & CONTROL_MASK) &&
|
||||
@@ -269,7 +264,7 @@ int __fastcall Script_CombatLogSetRetentionTime(void *L) {
|
||||
}
|
||||
|
||||
int __fastcall Script_CombatLogGetNumEntries(void *L) {
|
||||
const bool all = ArgFlag(L, 1);
|
||||
const bool all = Game::Lua::ToFlag(L, 1);
|
||||
int count = 0;
|
||||
for (const Entry &e : g_entries) {
|
||||
if (Matches(e, all))
|
||||
@@ -286,7 +281,7 @@ int __fastcall Script_CombatLogSetCurrentEntry(void *L) {
|
||||
}
|
||||
g_current = -1;
|
||||
int index = static_cast<int>(Game::Lua::ToNumber(L, 1));
|
||||
const bool all = ArgFlag(L, 2);
|
||||
const bool all = Game::Lua::ToFlag(L, 2);
|
||||
if (index <= 0) {
|
||||
// 0 is the newest entry, -1 the one before it.
|
||||
for (int i = Count() - 1; i >= 0; --i) {
|
||||
@@ -320,7 +315,7 @@ int __fastcall Script_CombatLogAdvanceEntry(void *L) {
|
||||
return 0;
|
||||
}
|
||||
int count = static_cast<int>(Game::Lua::ToNumber(L, 1));
|
||||
const bool all = ArgFlag(L, 2);
|
||||
const bool all = Game::Lua::ToFlag(L, 2);
|
||||
if (count != 0) {
|
||||
// The walk starts at the current entry, which counts when it matches.
|
||||
int result = -1;
|
||||
|
||||
@@ -86,7 +86,6 @@ const Game::HookAutoRegister _countHook{Offsets::FUN_EDITBOX_COUNT_CHARS, reinte
|
||||
|
||||
using Load_t = uintptr_t(__thiscall *)(void *self, const void *node, void *status);
|
||||
using GetAttr_t = const char *(__thiscall *)(const void *node, const char *name);
|
||||
using AttrToBool_t = int(__fastcall *)(const char *text);
|
||||
Load_t g_origEditBoxLoad = nullptr;
|
||||
|
||||
// `self` is the loader sub-object inside the edit box.
|
||||
@@ -99,7 +98,7 @@ uintptr_t __fastcall EditBoxLoad_h(void *self, void * /*edx*/, const void *node,
|
||||
if (value == nullptr || *value == '\0')
|
||||
return result;
|
||||
void *editBox = static_cast<uint8_t *>(self) - Offsets::OFF_XML_LOADER_SUBOBJECT;
|
||||
if (reinterpret_cast<AttrToBool_t>(Offsets::FUN_XML_ATTR_TO_BOOL)(value))
|
||||
if (Game::StringToFlag(value))
|
||||
g_counting.insert(editBox);
|
||||
else
|
||||
g_counting.erase(editBox);
|
||||
|
||||
@@ -76,7 +76,6 @@ using Notify_t = void(__fastcall *)(uint32_t slot0, int sendToServer, int quiet)
|
||||
using Usable_t = uint32_t(__fastcall *)(uint32_t slot0, int *outNoMana);
|
||||
using Texture_t = const char *(__fastcall *)(uint32_t slot0);
|
||||
using Script_t = int(__fastcall *)(void *L);
|
||||
using LooseBool_t = int(__fastcall *)(void *L, int idx, int defaultValue);
|
||||
// `__stdcall(a, packet)`, RET 8 — declared `__fastcall` with the two
|
||||
// register slots passed through untouched, which is the same frame.
|
||||
using ActionButtons_t = int(__fastcall *)(void *ecx, void *edx, void *a, void *packet);
|
||||
@@ -193,7 +192,7 @@ int __fastcall UseAction_h(void *L) {
|
||||
if (slot < 0 || slot >= static_cast<int>(kSlots))
|
||||
return g_origUseAction(L);
|
||||
const uint32_t slot0 = static_cast<uint32_t>(slot);
|
||||
if (reinterpret_cast<LooseBool_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE)(L, 2, 0) != 0) {
|
||||
if (Game::Lua::ToFlag(L, 2)) {
|
||||
if (const uint32_t id = CursorSetID()) {
|
||||
PlaceSet(slot0, id);
|
||||
return 0;
|
||||
|
||||
@@ -34,16 +34,12 @@ namespace FontString::IndentedWordWrap {
|
||||
|
||||
namespace {
|
||||
|
||||
using LooseBool_t = int(__fastcall *)(void *L, int idx, int defaultValue);
|
||||
using SetStyleFlags_t = void(__fastcall *)(void *fontString, void *edx, uint32_t flags);
|
||||
using UpdateObjects_t = void(__fastcall *)(void *font);
|
||||
using ReadTextType_t = int(__fastcall *)(void *L);
|
||||
|
||||
constexpr uint32_t kIndented = Offsets::FONT_STYLE_INDENTED_WORD_WRAP;
|
||||
|
||||
bool ReadIndent(void *L) {
|
||||
return reinterpret_cast<LooseBool_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE)(L, 2, 1) != 0;
|
||||
}
|
||||
|
||||
int PushIndented(void *L, uint32_t styleFlags) {
|
||||
if (styleFlags & kIndented)
|
||||
@@ -93,7 +89,7 @@ void *HtmlFont(void *L) {
|
||||
template <void *(*Font)(void *)> int __fastcall Script_SetFontIndented(void *L) {
|
||||
void *font = Font(L);
|
||||
if (font != nullptr)
|
||||
SetFontIndented(font, ReadIndent(L));
|
||||
SetFontIndented(font, Game::Lua::ToFlag(L, 2, true));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -115,7 +111,7 @@ int __fastcall Script_SetFontStringIndented(void *L) {
|
||||
void *fontString = Game::Lua::ResolveFontString(L);
|
||||
if (fontString == nullptr)
|
||||
return 0;
|
||||
const bool indented = ReadIndent(L);
|
||||
const bool indented = Game::Lua::ToFlag(L, 2, true);
|
||||
Game::Ref<uint32_t>(fontString, Offsets::OFF_FONTSTRING_SETTABLE_STYLE) &= ~kIndented;
|
||||
const uint32_t flags = Game::Read<uint32_t>(fontString, Offsets::OFF_FONTSTRING_FLAGS);
|
||||
reinterpret_cast<SetStyleFlags_t>(Offsets::FUN_FONTSTRING_SET_STYLE_FLAGS)(
|
||||
|
||||
@@ -32,7 +32,6 @@ namespace FontString::WordWrap {
|
||||
|
||||
namespace {
|
||||
|
||||
using LooseBool_t = int(__fastcall *)(void *L, int idx, int defaultValue);
|
||||
|
||||
constexpr uint32_t kNoWrap = Offsets::FONT_STYLE_NO_WORD_WRAP;
|
||||
|
||||
@@ -40,7 +39,7 @@ int __fastcall Script_SetWordWrap(void *L) {
|
||||
void *fontString = Game::Lua::ResolveFontString(L);
|
||||
if (fontString == nullptr)
|
||||
return 0;
|
||||
const bool wrap = reinterpret_cast<LooseBool_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE)(L, 2, 1) != 0;
|
||||
const bool wrap = Game::Lua::ToFlag(L, 2, true);
|
||||
Game::Ref<uint32_t>(fontString, Offsets::OFF_FONTSTRING_SETTABLE_STYLE) &= ~kNoWrap;
|
||||
const uint32_t flags = Game::Read<uint32_t>(fontString, Offsets::OFF_FONTSTRING_FLAGS);
|
||||
const uint32_t wanted = wrap ? flags & ~kNoWrap : flags | kNoWrap;
|
||||
|
||||
@@ -1100,7 +1100,6 @@ int __fastcall Script_GetAttribute(void *L) {
|
||||
// inherited templates are loaded first, so the frame's own values win.
|
||||
// Problems are the engine's load warnings, with 4.3.4's texts.
|
||||
void LoadXmlAttributes(void *L, void *frame, const void *attributes, void *status) {
|
||||
using ToBool_t = int(__fastcall *)(const char *);
|
||||
using ToFloat_t = float(__stdcall *)(const char *);
|
||||
|
||||
const int top = Game::Lua::GetTop(L);
|
||||
@@ -1138,7 +1137,7 @@ void LoadXmlAttributes(void *L, void *frame, const void *attributes, void *statu
|
||||
if (isNil)
|
||||
Game::Lua::PushNil(L);
|
||||
else if (Ascii::EqualCI(type, "boolean"))
|
||||
Game::Lua::PushBool(L, reinterpret_cast<ToBool_t>(Offsets::FUN_XML_ATTR_TO_BOOL)(value) != 0);
|
||||
Game::Lua::PushBool(L, Game::StringToFlag(value));
|
||||
else if (Ascii::EqualCI(type, "number"))
|
||||
Game::Lua::PushNumber(L, reinterpret_cast<ToFloat_t>(Offsets::FUN_SSTR_TO_FLOAT)(value));
|
||||
else
|
||||
|
||||
+4
-44
@@ -655,8 +655,6 @@ void __fastcall OnFrameSizeChanged(void *layout, void *, const float *oldRect) {
|
||||
ResetVerts(FrameOfLayout(layout));
|
||||
}
|
||||
|
||||
bool StringToBool(const char *s);
|
||||
|
||||
// The stock CooldownFrameTemplate: a Model template (Cooldown.xml, 1.12).
|
||||
bool IsStockTemplate(const void *node) {
|
||||
if (!Xml::TagIs(node, "Model"))
|
||||
@@ -691,9 +689,9 @@ uintptr_t __fastcall LoadXML(void *layout, void *, const void *node, void *statu
|
||||
return result;
|
||||
Fields &f = F(FrameOfLayout(layout));
|
||||
if (const char *reverse = Xml::Attr(node, "reverse"))
|
||||
f.reverse = StringToBool(reverse);
|
||||
f.reverse = Game::StringToFlag(reverse);
|
||||
if (const char *drawEdge = Xml::Attr(node, "drawEdge"))
|
||||
f.drawEdge = StringToBool(drawEdge);
|
||||
f.drawEdge = Game::StringToFlag(drawEdge);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -743,44 +741,6 @@ void *__fastcall CreateCooldown(void *parent) {
|
||||
|
||||
void *ResolveCooldown(void *L) { return Game::Lua::ResolveTypedObject(L, 1, TypeIdVar()); }
|
||||
|
||||
// 4.3.4's StringToBOOL on a string (FrameXML/LoadXML.cpp 0x004EF0C0): the
|
||||
// first letter (0/F/N false, 1-9/T/Y true), then off/disabled/on/enabled,
|
||||
// and true for anything else.
|
||||
bool StringToBool(const char *s) {
|
||||
switch (*s) {
|
||||
case '0': case 'F': case 'N': case 'f': case 'n':
|
||||
return false;
|
||||
case '1': case '2': case '3': case '4': case '5':
|
||||
case '6': case '7': case '8': case '9':
|
||||
case 'T': case 'Y': case 't': case 'y':
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (_stricmp(s, "off") == 0 || _stricmp(s, "disabled") == 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// StringToBOOL(L, index, 1): nil is false, a boolean is itself, a number is
|
||||
// != 0, a string as above, and anything else the default.
|
||||
bool ArgToBool(void *L, int index) {
|
||||
switch (Game::Lua::Type(L, index)) {
|
||||
case Game::Lua::TYPE_NIL:
|
||||
return false;
|
||||
case Game::Lua::TYPE_BOOLEAN:
|
||||
return Game::Lua::ToBoolean(L, index) != 0;
|
||||
case Game::Lua::TYPE_NUMBER:
|
||||
return Game::Lua::ToNumber(L, index) != 0.0;
|
||||
case Game::Lua::TYPE_STRING: {
|
||||
const char *s = Game::Lua::ToString(L, index);
|
||||
return s == nullptr || StringToBool(s);
|
||||
}
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// OFFSET (4.3.4): 0x009FFBD0
|
||||
int __fastcall Script_SetCooldown(void *L) {
|
||||
void *frame = ResolveCooldown(L);
|
||||
@@ -802,7 +762,7 @@ int __fastcall Script_SetReverse(void *L) {
|
||||
void *frame = ResolveCooldown(L);
|
||||
if (frame == nullptr)
|
||||
return 0;
|
||||
F(frame).reverse = ArgToBool(L, 2);
|
||||
F(frame).reverse = Game::Lua::ToFlag(L, 2, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -821,7 +781,7 @@ int __fastcall Script_SetDrawEdge(void *L) {
|
||||
if (frame == nullptr)
|
||||
return 0;
|
||||
Fields &f = F(frame);
|
||||
const uint32_t drawEdge = ArgToBool(L, 2);
|
||||
const uint32_t drawEdge = Game::Lua::ToFlag(L, 2, true);
|
||||
if (drawEdge != f.drawEdge) {
|
||||
f.drawEdge = drawEdge;
|
||||
NotifyLayerChanged(frame, 3);
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace {
|
||||
|
||||
using SaveFrame_t = void(__fastcall *)(void *frame, void *file);
|
||||
using SetFrameFlag_t = void(__fastcall *)(void *frame, void *edx, uint32_t mask, int on);
|
||||
using LooseBool_t = int(__fastcall *)(void *L, int idx, int defaultValue);
|
||||
|
||||
SaveFrame_t g_origSaveFrame = nullptr;
|
||||
|
||||
@@ -58,9 +57,9 @@ int __fastcall Script_SetDontSavePosition(void *L) {
|
||||
void *frame = Game::Lua::ResolveFrame(L);
|
||||
if (frame == nullptr)
|
||||
return 0;
|
||||
const int on = reinterpret_cast<LooseBool_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE)(L, 2, 1);
|
||||
const bool on = Game::Lua::ToFlag(L, 2, true);
|
||||
reinterpret_cast<SetFrameFlag_t>(Offsets::FUN_FRAME_SET_FRAME_FLAG)(
|
||||
frame, nullptr, Offsets::FRAME_FLAG_DONT_SAVE_POSITION, on != 0);
|
||||
frame, nullptr, Offsets::FRAME_FLAG_DONT_SAVE_POSITION, on);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ namespace {
|
||||
// other things.
|
||||
std::unordered_set<void *> g_disabled;
|
||||
|
||||
using LooseBool_t = int(__fastcall *)(void *L, int idx, int defaultValue);
|
||||
|
||||
bool OnRun(void *frame, uint32_t *slotPtr, const char * /*fmt*/, void * /*varargs*/) {
|
||||
if (g_disabled.empty() || g_disabled.count(frame) == 0)
|
||||
@@ -71,7 +70,7 @@ int __fastcall Script_SetHyperlinksEnabled(void *L) {
|
||||
void *frame = Game::Lua::ResolveFrame(L);
|
||||
if (frame == nullptr)
|
||||
return 0;
|
||||
if (reinterpret_cast<LooseBool_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE)(L, 2, 1) != 0)
|
||||
if (Game::Lua::ToFlag(L, 2, true))
|
||||
g_disabled.erase(frame);
|
||||
else
|
||||
g_disabled.insert(frame);
|
||||
|
||||
@@ -47,7 +47,6 @@ namespace {
|
||||
|
||||
using KeySlot_t = int(__fastcall *)(void *frame, void *edx, void *event);
|
||||
using SetFrameFlag_t = void(__fastcall *)(void *frame, void *edx, uint32_t mask, int on);
|
||||
using LooseBool_t = int(__fastcall *)(void *L, int idx, int defaultValue);
|
||||
|
||||
KeySlot_t g_origFrameOnChar = nullptr;
|
||||
KeySlot_t g_origFrameOnKeyDown = nullptr;
|
||||
@@ -86,9 +85,9 @@ int __fastcall Script_SetPropagateKeyboardInput(void *L) {
|
||||
void *frame = Game::Lua::ResolveFrame(L);
|
||||
if (frame == nullptr)
|
||||
return 0;
|
||||
const int on = reinterpret_cast<LooseBool_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE)(L, 2, 0);
|
||||
const bool on = Game::Lua::ToFlag(L, 2);
|
||||
reinterpret_cast<SetFrameFlag_t>(Offsets::FUN_FRAME_SET_FRAME_FLAG)(
|
||||
frame, nullptr, Offsets::FRAME_FLAG_PROPAGATE_KEYBOARD, on != 0);
|
||||
frame, nullptr, Offsets::FRAME_FLAG_PROPAGATE_KEYBOARD, on);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,6 @@ namespace {
|
||||
using OnEnter_t = void(__fastcall *)(void *button, void *edx);
|
||||
using OnLeave_t = void(__fastcall *)(void *button, void *edx, int removed);
|
||||
using FireScript_t = void(__fastcall *)(void *frame, void *edx, int *slot);
|
||||
using LooseBool_t = int(__fastcall *)(void *L, int idx, int defaultValue);
|
||||
using AttrToBool_t = int(__fastcall *)(const char *text);
|
||||
|
||||
std::unordered_set<const void *> g_enabled;
|
||||
|
||||
@@ -99,7 +97,7 @@ void OnFrameNode(void *object, const void *node, void * /*status*/) {
|
||||
const char *value = Xml::Attr(node, "motionScriptsWhileDisabled");
|
||||
if (value == nullptr || *value == '\0' || !Game::Lua::IsA(object, Offsets::VAR_BUTTON_LUA_TYPE_ID))
|
||||
return;
|
||||
Set(object, reinterpret_cast<AttrToBool_t>(Offsets::FUN_XML_ATTR_TO_BOOL)(value) != 0);
|
||||
Set(object, Game::StringToFlag(value));
|
||||
}
|
||||
|
||||
const Xml::FrameLoad::AutoSubscribe _xmlSub{&OnFrameNode};
|
||||
@@ -115,7 +113,7 @@ int __fastcall Script_SetMotionScriptsWhileDisabled(void *L) {
|
||||
void *button = ResolveButton(L);
|
||||
if (button == nullptr)
|
||||
return 0;
|
||||
Set(button, reinterpret_cast<LooseBool_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE)(L, 2, 0) != 0);
|
||||
Set(button, Game::Lua::ToFlag(L, 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+25
-9
@@ -30,7 +30,8 @@
|
||||
// block in Offsets.h). Retail keeps the two APIs disjoint, but for
|
||||
// backporting it's far more useful for `C_GossipInfo` to serve
|
||||
// whichever questgiver session is actually live:
|
||||
// - gossip session live (VAR_GOSSIP_NPC_GUID != 0) → gossip arrays
|
||||
// - gossip session live (VAR_GOSSIP_NPC_GUID != 0
|
||||
// and greeting not pending) → gossip arrays
|
||||
// - greeting session live (VAR_QUESTGIVER_GUID != 0
|
||||
// and == VAR_GREETING_NPC_GUID) → greeting arrays
|
||||
// - neither → empty / ""
|
||||
@@ -68,10 +69,15 @@ uint64_t ReadU64(uintptr_t va) {
|
||||
return *reinterpret_cast<const uint64_t *>(va);
|
||||
}
|
||||
|
||||
// A gossip session is live from SMSG_GOSSIP_MESSAGE until the close
|
||||
// worker (0x004E1FA0) zeroes the GUID and fires GOSSIP_CLOSED.
|
||||
// A gossip session is live from GOSSIP_SHOW until the close worker
|
||||
// (0x004E1FA0) zeroes the GUID and fires GOSSIP_CLOSED. The GUID is
|
||||
// latched on SMSG_GOSSIP_MESSAGE, before the greeting resolves, so the
|
||||
// engine's own pending flag (what its option selector checks) keeps the
|
||||
// not-yet-shown window — and the previous NPC's text — out.
|
||||
bool GossipSessionActive() {
|
||||
return ReadU64(Offsets::VAR_GOSSIP_NPC_GUID) != 0;
|
||||
return ReadU64(Offsets::VAR_GOSSIP_NPC_GUID) != 0 &&
|
||||
*reinterpret_cast<const uint32_t *>(
|
||||
Offsets::VAR_GOSSIP_TEXT_PENDING) == 0;
|
||||
}
|
||||
|
||||
// A quest-greeting session is live while the quest-session GUID is set
|
||||
@@ -427,18 +433,28 @@ int __fastcall Script_C_GossipInfo_SelectOption(void *L) {
|
||||
|
||||
// `C_GossipInfo.SelectOptionByIndex(orderIndex)` — `orderIndex` is
|
||||
// 1-based in display order (matches what `GetOptions()` puts on each
|
||||
// entry). The engine helper takes the 0-based array slot.
|
||||
// entry). Slots with a negative index are hidden and skipped, so the
|
||||
// display position is walked back to the 0-based array slot the engine
|
||||
// helper takes, the same way `GetOptions()` numbered it.
|
||||
int __fastcall Script_C_GossipInfo_SelectOptionByIndex(void *L) {
|
||||
if (!Game::Lua::IsNumber(L, 1)) {
|
||||
Game::Lua::Error(L,
|
||||
"Usage: C_GossipInfo.SelectOptionByIndex(orderIndex)");
|
||||
return 0;
|
||||
}
|
||||
const int slot0Based = static_cast<int>(Game::Lua::ToNumber(L, 1)) - 1;
|
||||
if (slot0Based < 0 || slot0Based >= Offsets::GOSSIP_OPTIONS_MAX ||
|
||||
!GossipSessionActive())
|
||||
const int target = static_cast<int>(Game::Lua::ToNumber(L, 1));
|
||||
if (!GossipSessionActive())
|
||||
return 0;
|
||||
EngineSelectOption(slot0Based, nullptr);
|
||||
|
||||
int orderIndex = 0;
|
||||
for (int slot = 0; slot < Offsets::GOSSIP_OPTIONS_MAX; ++slot) {
|
||||
if (OptionIndex(OptionEntry(slot)) < 0)
|
||||
continue;
|
||||
if (++orderIndex == target) {
|
||||
EngineSelectOption(slot, nullptr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
// 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/>.
|
||||
|
||||
// `C_GossipInfo` point of interest — the map marker a gossip option such as
|
||||
// a city guard's directions places (SMSG_GOSSIP_POI):
|
||||
//
|
||||
// GetPoiForUiMapID(uiMapID) -> gossipPoiID or nil
|
||||
// GetPoiInfo(uiMapID, gossipPoiID) -> GossipPoiInfo or nil
|
||||
//
|
||||
// The engine keeps the marker in static blip record 1 (see
|
||||
// `VAR_GOSSIP_POI_BLIP` in Offsets.h): one marker at a time, live while its
|
||||
// position is nonzero. Both functions read that record and apply the world
|
||||
// map's own placement test (0x004A7360): the map's WorldMapArea row must
|
||||
// carry the marker's map id and the projected point must land inside 0..1.
|
||||
//
|
||||
// The packet carries no POI id, and there is only ever one marker, so its
|
||||
// `gossipPoiID` is the engine's slot number, 1.
|
||||
//
|
||||
// `DYNAMIC_GOSSIP_POI_UPDATED` fires when the marker is set or cleared. Every
|
||||
// change runs the world map rebuild 0x004A67A0 (the setter after a packet, the
|
||||
// 480 s expiry and the 10-yard clear in 0x006D93A0), but that rebuild also
|
||||
// runs for every map view change, so the event comes from a once-per-frame
|
||||
// diff of the record instead: x, y and the expiry, which each set rewrites
|
||||
// and each clear zeroes.
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "event/Custom.h"
|
||||
#include "map/Area.h"
|
||||
#include "map/Vector2.h"
|
||||
#include "tick/WorldTick.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Gossip::Poi {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kGossipPoiID = 1;
|
||||
|
||||
template <typename T> T BlipField(uintptr_t off) {
|
||||
return *reinterpret_cast<const T *>(Offsets::VAR_GOSSIP_POI_BLIP + off);
|
||||
}
|
||||
|
||||
const Game::Doc::Event kPoiUpdatedDoc{
|
||||
"GossipInfo", "Fires when a gossip point of interest is added to or removed from the map.",
|
||||
{}};
|
||||
const Event::Custom::AutoReserve _evtPoiUpdated{"DYNAMIC_GOSSIP_POI_UPDATED",
|
||||
&kPoiUpdatedDoc};
|
||||
|
||||
struct Snapshot {
|
||||
uint32_t x, y, expiry;
|
||||
};
|
||||
|
||||
// The record as of the last fire. DLL-lived like the record itself, so a
|
||||
// /reload with a marker up does not fire a phantom change.
|
||||
Snapshot g_last{};
|
||||
|
||||
void OnTick() {
|
||||
const Snapshot now{BlipField<uint32_t>(Offsets::OFF_STATIC_BLIP_X),
|
||||
BlipField<uint32_t>(Offsets::OFF_STATIC_BLIP_Y),
|
||||
*reinterpret_cast<const uint32_t *>(Offsets::VAR_GOSSIP_POI_EXPIRY)};
|
||||
if (now.x == g_last.x && now.y == g_last.y && now.expiry == g_last.expiry)
|
||||
return;
|
||||
g_last = now;
|
||||
Event::Custom::Fire(_evtPoiUpdated.Slot(), "");
|
||||
}
|
||||
|
||||
const Tick::WorldTick::AutoSubscribe _tick{&OnTick};
|
||||
|
||||
// Projects the live marker onto `uiMapID`. False when there is no marker or
|
||||
// it isn't on that map.
|
||||
bool ProjectOnMap(int uiMapID, double *outPx, double *outPy) {
|
||||
const float x = BlipField<float>(Offsets::OFF_STATIC_BLIP_X);
|
||||
const float y = BlipField<float>(Offsets::OFF_STATIC_BLIP_Y);
|
||||
if (x == 0.0f && y == 0.0f)
|
||||
return false;
|
||||
|
||||
const int row = Map::Area::RowForUiMapID(uiMapID);
|
||||
int rowMapID = 0;
|
||||
return row > 0 &&
|
||||
Map::Area::RowRect(row, nullptr, nullptr, nullptr, nullptr, &rowMapID) &&
|
||||
rowMapID == BlipField<int32_t>(Offsets::OFF_STATIC_BLIP_MAP) &&
|
||||
Map::Area::PercentInRow(row, x, y, outPx, outPy) &&
|
||||
*outPx >= 0.0 && *outPx <= 1.0 && *outPy >= 0.0 && *outPy <= 1.0;
|
||||
}
|
||||
|
||||
// `C_GossipInfo.GetPoiForUiMapID(uiMapID)` — the id of the gossip marker
|
||||
// shown on that map, or nil.
|
||||
int __fastcall Script_GetPoiForUiMapID(void *L) {
|
||||
const int uiMapID =
|
||||
Game::Lua::IsNumber(L, 1) ? static_cast<int>(Game::Lua::ToNumber(L, 1)) : 0;
|
||||
double px = 0.0, py = 0.0;
|
||||
const bool have = uiMapID != 0 && ProjectOnMap(uiMapID, &px, &py);
|
||||
|
||||
Game::Lua::SetTop(L, 0);
|
||||
if (!have)
|
||||
Game::Lua::PushNil(L);
|
||||
else
|
||||
Game::Lua::PushNumber(L, static_cast<double>(kGossipPoiID));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// `C_GossipInfo.GetPoiInfo(uiMapID, gossipPoiID)` — the marker's name, icon
|
||||
// index and 0..1 position on that map, or nil.
|
||||
int __fastcall Script_GetPoiInfo(void *L) {
|
||||
const bool ok = Game::Lua::IsNumber(L, 1) && Game::Lua::IsNumber(L, 2);
|
||||
const int uiMapID = ok ? static_cast<int>(Game::Lua::ToNumber(L, 1)) : 0;
|
||||
const int poiID = ok ? static_cast<int>(Game::Lua::ToNumber(L, 2)) : 0;
|
||||
double px = 0.0, py = 0.0;
|
||||
const bool have = poiID == kGossipPoiID && ProjectOnMap(uiMapID, &px, &py);
|
||||
|
||||
Game::Lua::SetTop(L, 0);
|
||||
if (!have) {
|
||||
Game::Lua::PushNil(L);
|
||||
return 1;
|
||||
}
|
||||
Game::Lua::NewTable(L);
|
||||
Game::Lua::SetFieldString(
|
||||
L, "name", reinterpret_cast<const char *>(Offsets::VAR_GOSSIP_POI_NAME));
|
||||
Game::Lua::SetFieldNumber(
|
||||
L, "textureIndex",
|
||||
static_cast<double>(BlipField<uint32_t>(Offsets::OFF_STATIC_BLIP_ICON)));
|
||||
Game::Lua::PushString(L, "position");
|
||||
Map::PushVector2D(L, px, py);
|
||||
Game::Lua::SetTable(L, -3);
|
||||
Game::Lua::SetFieldBool(L, "inBattleMap", false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// --- Documentation ----------------------------------------------------------
|
||||
|
||||
const Game::Doc::Field kForMapArgs[] = {
|
||||
Game::Doc::Req("uiMapID", "number"),
|
||||
};
|
||||
const Game::Doc::Field kForMapRets[] = {
|
||||
Game::Doc::Opt("gossipPoiID", "number"),
|
||||
};
|
||||
const Game::Doc::Function kGetPoiForUiMapID{
|
||||
"Returns the id of the gossip point of interest shown on the map, if any.",
|
||||
kForMapArgs, kForMapRets};
|
||||
|
||||
const Game::Doc::Field kPoiInfoFields[] = {
|
||||
Game::Doc::Req("name", "string"),
|
||||
Game::Doc::Req("textureIndex", "number", "The icon's index in the map POI icon texture."),
|
||||
Game::Doc::Req("position", "vector2", "The point on the map, from 0 to 1 on each axis."),
|
||||
Game::Doc::Req("inBattleMap", "bool"),
|
||||
};
|
||||
const Game::Doc::Structure kGossipPoiInfo{
|
||||
"GossipPoiInfo", "GossipInfo", kPoiInfoFields,
|
||||
"A point of interest that a gossip option marked on the map."};
|
||||
|
||||
const Game::Doc::Field kInfoArgs[] = {
|
||||
Game::Doc::Req("uiMapID", "number"),
|
||||
Game::Doc::Req("gossipPoiID", "number"),
|
||||
};
|
||||
const Game::Doc::Field kInfoRets[] = {
|
||||
Game::Doc::Opt("gossipPoiInfo", "GossipPoiInfo"),
|
||||
};
|
||||
const Game::Doc::Function kGetPoiInfo{
|
||||
"Returns the gossip point of interest as it appears on the map.", kInfoArgs,
|
||||
kInfoRets};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterTableFunction("C_GossipInfo", "GetPoiForUiMapID",
|
||||
&Script_GetPoiForUiMapID, &kGetPoiForUiMapID);
|
||||
Game::Lua::RegisterTableFunction("C_GossipInfo", "GetPoiInfo", &Script_GetPoiInfo,
|
||||
&kGetPoiInfo);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace Gossip::Poi
|
||||
+2
-4
@@ -46,7 +46,6 @@ namespace Item::Count {
|
||||
namespace {
|
||||
|
||||
using CountByID_t = int(__thiscall *)(const void *invMgr, int itemID, uint32_t flags);
|
||||
using LooseBool_t = int(__fastcall *)(void *L, int idx, int defaultValue);
|
||||
|
||||
int CountByID(uintptr_t fn, int itemID, uint32_t flags) {
|
||||
const uint8_t *invMgr = Unit::Identity::PlayerInventoryManager();
|
||||
@@ -86,9 +85,8 @@ int CountUses(int itemID, int total) {
|
||||
}
|
||||
|
||||
int __fastcall Script_C_Item_GetItemCount(void *L) {
|
||||
const auto looseBool = reinterpret_cast<LooseBool_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE);
|
||||
const bool includeBank = looseBool(L, 2, 0) != 0;
|
||||
const bool includeUses = looseBool(L, 3, 0) != 0;
|
||||
const bool includeBank = Game::Lua::ToFlag(L, 2);
|
||||
const bool includeUses = Game::Lua::ToFlag(L, 3);
|
||||
const uint32_t flags = includeBank ? Offsets::INVMGR_SEARCH_BANK : 0;
|
||||
|
||||
const Item::Arg::Resolved arg = Item::Arg::Resolve(L, 1);
|
||||
|
||||
@@ -98,12 +98,17 @@ const Game::Doc::Function kGetSpellCooldown{
|
||||
|
||||
bool Query(int spellID, uint32_t *startMs, uint32_t *durationMs,
|
||||
bool *enabled) {
|
||||
int pet = 0;
|
||||
Spell::Lookup::FindSpellbookSlot(spellID, &pet);
|
||||
return Query(spellID, pet != 0, startMs, durationMs, enabled);
|
||||
}
|
||||
|
||||
bool Query(int spellID, bool pet, uint32_t *startMs, uint32_t *durationMs,
|
||||
bool *enabled) {
|
||||
if (spellID <= 0 || Spell::Lookup::RecordForID(spellID) == nullptr)
|
||||
return false;
|
||||
auto fn = reinterpret_cast<QueryCooldown_t>(
|
||||
static_cast<uintptr_t>(Offsets::FUN_SPELL_QUERY_COOLDOWN));
|
||||
int pet = 0;
|
||||
Spell::Lookup::FindSpellbookSlot(spellID, &pet);
|
||||
uint32_t duration = 0, start = 0, enable = 0;
|
||||
fn(spellID, pet, &duration, &start, &enable);
|
||||
*startMs = start;
|
||||
|
||||
@@ -28,4 +28,10 @@ namespace Spell::Cooldown {
|
||||
bool Query(int spellID, uint32_t *startMs, uint32_t *durationMs,
|
||||
bool *enabled);
|
||||
|
||||
// Same, read from the history the caller names (`pet` = the pet's) — the
|
||||
// shape 4.3.4's `Spell_C_GetSpellCooldown(spellId, isPet, …)` takes, for
|
||||
// callers that are handed the history instead of deriving it.
|
||||
bool Query(int spellID, bool pet, uint32_t *startMs, uint32_t *durationMs,
|
||||
bool *enabled);
|
||||
|
||||
} // namespace Spell::Cooldown
|
||||
|
||||
+103
-91
@@ -13,20 +13,106 @@
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "spell/Cooldown.h"
|
||||
#include "spell/Lookup.h"
|
||||
#include "spell/Tooltip.h"
|
||||
#include "time/Clock.h"
|
||||
#include "tooltip/Lines.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Spell::Tooltip {
|
||||
|
||||
// Spell.dbc record offsets used by GetSpell. Kept local rather than
|
||||
// pulled from Spell::Info — that module keeps these as `static
|
||||
// constexpr` privates and exposing them just for two readers here
|
||||
// would force a header change.
|
||||
namespace {
|
||||
|
||||
using BuildSpellTooltip_t = void(__thiscall *)(void *thisObj, int spellID, int arg2, int arg3,
|
||||
int isPet, int showRank, int arg6, int arg7);
|
||||
// CGTooltip::BuildSpell — see FUN_GAMETOOLTIP_BUILD_SPELL_TOOLTIP.
|
||||
using BuildSpell_t = uint32_t(__thiscall *)(void *tooltip, int spellID, int compact,
|
||||
uint32_t cooldownMs, int pet, int showRank,
|
||||
const void *talent, int rebuild);
|
||||
|
||||
uint32_t BuildSpell(void *tooltip, int spellID, uint32_t cooldownMs, bool pet, bool showRank,
|
||||
bool rebuild) {
|
||||
return reinterpret_cast<BuildSpell_t>(Offsets::FUN_GAMETOOLTIP_BUILD_SPELL_TOOLTIP)(
|
||||
tooltip, spellID, 0, cooldownMs, pet ? 1 : 0, showRank ? 1 : 0, nullptr, rebuild ? 1 : 0);
|
||||
}
|
||||
|
||||
// The cooldown argument 4.3.4's Script_SetSpellByID computes: the time left
|
||||
// when the spell's cooldown is enabled, running and started, else 0.
|
||||
uint32_t CooldownRemainingMs(int spellID, bool pet) {
|
||||
uint32_t start = 0, duration = 0;
|
||||
bool enabled = false;
|
||||
if (!Spell::Cooldown::Query(spellID, pet, &start, &duration, &enabled) || !enabled ||
|
||||
duration == 0 || start == 0)
|
||||
return 0;
|
||||
return Time::Clock::Remaining(start + duration);
|
||||
}
|
||||
|
||||
// The builder's append mode heads the block with its talent "Next rank:"
|
||||
// line, where 4.3.4's append (no talent) adds the ordinary name line. Rewrite
|
||||
// that line in place with the engine's own add-line — the same
|
||||
// `AddDoubleLine(name, subtext, text, gray)` the builder's non-append path
|
||||
// makes — by pointing the line cursor back at it for the one call.
|
||||
void RewriteAppendHeader(void *tooltip, int headerLine, int spellID, bool showSubtext) {
|
||||
const uint8_t *record = Spell::Lookup::RecordForID(spellID);
|
||||
if (record == nullptr ||
|
||||
(Game::Read<uint32_t>(record, Offsets::OFF_SPELL_RECORD_ATTRIBUTES) &
|
||||
Offsets::SPELL_ATTR_TRADESPELL) != 0)
|
||||
return; // a recipe appends its created item's tooltip, which has no header
|
||||
const int locale = Game::Read<int>(Offsets::VAR_LOCALE_INDEX);
|
||||
const char *name = Game::Read<const char *>(record, Offsets::OFF_SPELL_NAMES + locale * 4);
|
||||
if (name == nullptr || name[0] == '\0')
|
||||
return;
|
||||
const char *subtext =
|
||||
showSubtext ? Game::Read<const char *>(record, Offsets::OFF_SPELL_RECORD_RANK + locale * 4)
|
||||
: nullptr;
|
||||
|
||||
const uint32_t textColor = Game::Read<uint32_t>(Offsets::VAR_TOOLTIP_TEXT_COLOR);
|
||||
const uint32_t grayColor = Game::Read<uint32_t>(Offsets::VAR_TOOLTIP_GRAY_COLOR);
|
||||
int *numLines = Game::Ptr<int>(tooltip, Offsets::OFF_GAMETOOLTIP_NUM_LINES);
|
||||
const int after = *numLines;
|
||||
*numLines = headerLine;
|
||||
::Tooltip::Lines::AddDouble(tooltip, name, subtext, textColor, grayColor);
|
||||
*numLines = after;
|
||||
}
|
||||
|
||||
|
||||
// 4.3.4's Script_SetSpellByID / Script_AddSpellByID (Tooltip_Scripts.cpp
|
||||
// 0x004733C0 / 0x00473530), which differ only in `append`: (spellID
|
||||
// [, isPet [, showSubtext]]), the spell's cooldown passed to the builder,
|
||||
// 1 when it added the cooldown line (FrameXML's refresh cue), else nil.
|
||||
// Both raise the same message, SetSpellByID's.
|
||||
int SetOrAddSpellByID(void *L, bool append) {
|
||||
void *tooltip = Game::Lua::ResolveTooltip(L);
|
||||
if (tooltip == nullptr)
|
||||
return 0;
|
||||
int spellID = 0;
|
||||
if (!Game::Lua::IsNumber(L, 2) ||
|
||||
(spellID = static_cast<int>(Game::Lua::ToNumber(L, 2))) < 0) {
|
||||
Game::Lua::Error(L, "Invalid spell ID in %s:SetSpellByID",
|
||||
Game::Lua::ObjectDisplayName(tooltip));
|
||||
return 0;
|
||||
}
|
||||
const bool isPet = Game::Lua::ToFlag(L, 3);
|
||||
const bool showSubtext = Game::Lua::ToFlag(L, 4);
|
||||
|
||||
const int before = Game::Read<int>(tooltip, Offsets::OFF_GAMETOOLTIP_NUM_LINES);
|
||||
const uint32_t result = BuildSpell(tooltip, spellID, CooldownRemainingMs(spellID, isPet),
|
||||
isPet, showSubtext, append);
|
||||
if (append && Game::Read<int>(tooltip, Offsets::OFF_GAMETOOLTIP_NUM_LINES) > before)
|
||||
RewriteAppendHeader(tooltip, before, spellID, showSubtext);
|
||||
|
||||
if (result != 0)
|
||||
Game::Lua::PushNumber(L, 1.0);
|
||||
else
|
||||
Game::Lua::PushNil(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int __fastcall Script_GameTooltipSetSpellByID(void *L) { return SetOrAddSpellByID(L, false); }
|
||||
|
||||
int __fastcall Script_GameTooltipAddSpellByID(void *L) { return SetOrAddSpellByID(L, true); }
|
||||
|
||||
} // namespace
|
||||
|
||||
void ShowByID(void *L, int spellID) {
|
||||
if (spellID <= 0)
|
||||
@@ -34,88 +120,7 @@ void ShowByID(void *L, int spellID) {
|
||||
void *tooltipObj = Game::Lua::ResolveTooltip(L);
|
||||
if (tooltipObj == nullptr)
|
||||
return;
|
||||
auto BuildSpellTooltip =
|
||||
reinterpret_cast<BuildSpellTooltip_t>(Offsets::FUN_GAMETOOLTIP_BUILD_SPELL_TOOLTIP);
|
||||
BuildSpellTooltip(tooltipObj, spellID, 0, 0, 0, /*showRank=*/1, 0, 0);
|
||||
}
|
||||
|
||||
static int __fastcall Script_GameTooltipSetSpellByID(void *L) {
|
||||
if (Game::Lua::Type(L, 1) != Game::Lua::TYPE_TABLE) {
|
||||
Game::Lua::Error(L, "Usage: GameTooltipSetSpellByID(self, spellID)");
|
||||
return 0;
|
||||
}
|
||||
if (!Game::Lua::IsNumber(L, 2)) {
|
||||
Game::Lua::Error(L, "Usage: GameTooltipSetSpellByID(self, spellID)");
|
||||
return 0;
|
||||
}
|
||||
const int spellID = static_cast<int>(Game::Lua::ToNumber(L, 2));
|
||||
ShowByID(L, spellID);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// `GameTooltip:AddSpellByID(spellID)` — APPENDS a spell's tooltip to the
|
||||
// current one instead of clearing+rebuilding (the `SetSpellByID` behavior).
|
||||
// The natural pair to `SetSpellByID`; lets callers compose tooltips like
|
||||
// `AddLine(talentName) + AddSpellByID(rankSpellID)`.
|
||||
//
|
||||
// `BuildSpellTooltip`'s last arg (`param_7`) gates the clear: `0` calls the
|
||||
// per-tooltip Clear (`FUN_00530050`) first — that's `SetSpellByID`; non-zero
|
||||
// SKIPS the clear and appends. The append path is the engine's talent
|
||||
// "next rank" preview, so it (a) emits a `"\nNext rank:"` header line
|
||||
// instead of the spell name, and (b) does NOT stash the displayed spellID at
|
||||
// `+0x39C` (so `GetSpell` keeps reflecting the base tooltip — correct for an
|
||||
// append). We fix (a) by overwriting that header line — which lands at index
|
||||
// `numLines`-before-the-build (0-based) — with the real spell name via the
|
||||
// line pool's left-text FontString. Verified against `FUN_0052E610`.
|
||||
void AppendByID(void *L, int spellID) {
|
||||
if (spellID <= 0)
|
||||
return;
|
||||
void *tooltipObj = Game::Lua::ResolveTooltip(L);
|
||||
if (tooltipObj == nullptr)
|
||||
return;
|
||||
auto *tt = static_cast<uint8_t *>(tooltipObj);
|
||||
|
||||
const int before = Game::Read<int>(
|
||||
tt, Offsets::OFF_GAMETOOLTIP_NUM_LINES);
|
||||
|
||||
auto BuildSpellTooltip =
|
||||
reinterpret_cast<BuildSpellTooltip_t>(Offsets::FUN_GAMETOOLTIP_BUILD_SPELL_TOOLTIP);
|
||||
BuildSpellTooltip(tooltipObj, spellID, 0, 0, 0, 0, 0, /*param_7 (append)=*/1);
|
||||
|
||||
const int after = Game::Read<int>(
|
||||
tt, Offsets::OFF_GAMETOOLTIP_NUM_LINES);
|
||||
if (after <= before)
|
||||
return; // nothing appended (bad spellID / missing Spell.dbc record)
|
||||
|
||||
const uint8_t *record = Spell::Lookup::RecordForID(spellID);
|
||||
if (record == nullptr)
|
||||
return;
|
||||
const int locale = Game::Read<int>(Offsets::VAR_LOCALE_INDEX);
|
||||
const char *name = Game::Read<const char *>(
|
||||
record, Offsets::OFF_SPELL_NAMES + locale * 4);
|
||||
if (name == nullptr || name[0] == '\0')
|
||||
return;
|
||||
|
||||
// Left-text FontString array: descriptor at +0x324, data ptr at +0x8.
|
||||
auto **textLeft = Game::Read<void **>(
|
||||
tt, Offsets::OFF_GAMETOOLTIP_TEXTLEFT_DESC + 8);
|
||||
if (textLeft == nullptr)
|
||||
return;
|
||||
void *fs = textLeft[before];
|
||||
if (fs == nullptr)
|
||||
return;
|
||||
using SetText_t = void(__thiscall *)(void *fs, const char *text, int flag);
|
||||
reinterpret_cast<SetText_t>(Offsets::FUN_FONTSTRING_SET_TEXT)(fs, name, 0);
|
||||
}
|
||||
|
||||
static int __fastcall Script_GameTooltipAddSpellByID(void *L) {
|
||||
if (Game::Lua::Type(L, 1) != Game::Lua::TYPE_TABLE ||
|
||||
!Game::Lua::IsNumber(L, 2)) {
|
||||
Game::Lua::Error(L, "Usage: GameTooltip:AddSpellByID(spellID)");
|
||||
return 0;
|
||||
}
|
||||
AppendByID(L, static_cast<int>(Game::Lua::ToNumber(L, 2)));
|
||||
return 0;
|
||||
BuildSpell(tooltipObj, spellID, 0, false, /*showRank=*/true, false);
|
||||
}
|
||||
|
||||
// `GameTooltip:GetSpell()` → (name, rank, spellID) for whichever spell
|
||||
@@ -196,13 +201,20 @@ static const Game::Lua::FrameMethodEntry g_methods[] = {
|
||||
|
||||
// --- Documentation ----------------------------------------------------------
|
||||
|
||||
static const Game::Doc::Field kSpellIDArg[] = {
|
||||
static const Game::Doc::Field kSpellByIDArgs[] = {
|
||||
Game::Doc::Req("spellID", "number", "Any spell ID, learned or not."),
|
||||
Game::Doc::Opt("isPet", "boolean", "false", "Read cost, range and cooldown as the pet's spell."),
|
||||
Game::Doc::Opt("showSubtext", "boolean", "false", "Show the spell's rank to the right of its name."),
|
||||
};
|
||||
static const Game::Doc::Field kSpellByIDRets[] = {
|
||||
Game::Doc::Opt("hasCooldown", "number", nullptr,
|
||||
"1 when the tooltip shows a cooldown that is counting down, else nil."),
|
||||
};
|
||||
static const Game::Doc::Function kSetSpellByID{
|
||||
"Fills the tooltip with a spell's tooltip.", kSpellIDArg, {}};
|
||||
"Fills the tooltip with a spell's tooltip.", kSpellByIDArgs, kSpellByIDRets};
|
||||
static const Game::Doc::Function kAddSpellByID{
|
||||
"Appends a spell's tooltip to what the tooltip already shows.", kSpellIDArg, {}};
|
||||
"Appends a spell's tooltip to what the tooltip already shows.", kSpellByIDArgs,
|
||||
kSpellByIDRets};
|
||||
|
||||
static const Game::Doc::Field kGetSpellRets[] = {
|
||||
Game::Doc::Opt("name", "string", nullptr, "Nil when the tooltip shows no spell."),
|
||||
|
||||
+8
-19
@@ -15,26 +15,15 @@
|
||||
|
||||
namespace Spell::Tooltip {
|
||||
|
||||
// Resolves the `GameTooltip` self at Lua stack[1] (via the
|
||||
// engine's `FrameScript_PushObject`/`FrameScript_GetObject` chain)
|
||||
// and dispatches the engine's spell-tooltip builder for `spellID`,
|
||||
// with the spell's rank shown in the top-right corner of the title
|
||||
// line (matching `SetAction`/`SetPetAction`; native `SetSpell` omits it).
|
||||
// Silent no-op if `spellID <= 0` or stack[1] isn't a frame object.
|
||||
// Resolves the `GameTooltip` self at Lua stack[1] and runs the engine's
|
||||
// spell-tooltip builder for `spellID` with the rank shown in the top-right
|
||||
// corner of the name line (as `SetAction` shows it) and no cooldown line.
|
||||
// Silent no-op if `spellID <= 0` or stack[1] isn't a tooltip.
|
||||
//
|
||||
// Used by `GameTooltip:SetSpellByID` directly, and by
|
||||
// `GameTooltip:SetTalentByID` as a fallback for cross-class talents
|
||||
// (where the engine's per-player TalentEntry tables don't apply,
|
||||
// so we render the rank-1 spell tooltip instead).
|
||||
// Used by `GameTooltip:SetTalentByID` as a fallback for cross-class talents
|
||||
// (where the engine's per-player TalentEntry tables don't apply, so we
|
||||
// render the rank-1 spell tooltip instead) and by `spell:` hyperlinks.
|
||||
// `GameTooltip:SetSpellByID` has its own 4.3.4-shaped arguments.
|
||||
void ShowByID(void *L, int spellID);
|
||||
|
||||
// Like `ShowByID` but APPENDS the spell's tooltip to the current one
|
||||
// (no clear), and overwrites the engine's talent "next rank" header with
|
||||
// the real spell name. Backs `GameTooltip:AddSpellByID`. Silent no-op if
|
||||
// `spellID <= 0`, stack[1] isn't a frame object, or the build added no
|
||||
// lines. Does not stash the displayed spellID (so `GetSpell` still
|
||||
// reflects the base tooltip). Useful for cross-class talent tooltips
|
||||
// (`AddLine(talentName) + AppendByID(rankSpellID)`).
|
||||
void AppendByID(void *L, int spellID);
|
||||
|
||||
} // namespace Spell::Tooltip
|
||||
|
||||
@@ -264,17 +264,13 @@ const State *Find(void *bar) {
|
||||
|
||||
void *ResolveBar(void *L) { return Game::Lua::ResolveTypedObject(L, 1, Offsets::VAR_STATUSBAR_LUA_TYPE_ID); }
|
||||
|
||||
using LooseBool_t = int(__fastcall *)(void *L, int idx, int defaultValue);
|
||||
|
||||
bool ArgBool(void *L, int idx) {
|
||||
return reinterpret_cast<LooseBool_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE)(L, idx, 0) != 0;
|
||||
}
|
||||
|
||||
int __fastcall Script_SetReverseFill(void *L) {
|
||||
void *bar = ResolveBar(L);
|
||||
if (bar == nullptr)
|
||||
return 0;
|
||||
const bool reverse = ArgBool(L, 2);
|
||||
const bool reverse = Game::Lua::ToFlag(L, 2);
|
||||
Modify(bar, [reverse](State &s) { s.reverseFill = reverse; });
|
||||
return 0;
|
||||
}
|
||||
@@ -292,7 +288,7 @@ int __fastcall Script_SetRotatesTexture(void *L) {
|
||||
void *bar = ResolveBar(L);
|
||||
if (bar == nullptr)
|
||||
return 0;
|
||||
const bool rotates = ArgBool(L, 2);
|
||||
const bool rotates = Game::Lua::ToFlag(L, 2);
|
||||
Modify(bar, [rotates](State &s) { s.rotatesTexture = rotates; });
|
||||
// 4.3.4 sets the full coordinates at once, so a bar with no value yet
|
||||
// shows its image turned, or not, before the first layout. Full
|
||||
|
||||
@@ -67,7 +67,6 @@ namespace {
|
||||
|
||||
using Cycle_t = void(__fastcall *)(int reverse, int mode);
|
||||
using Valid_t = int(__fastcall *)(void *player, void *cand, int mode);
|
||||
using ToBool_t = int(__fastcall *)(void *L, int idx, int dflt);
|
||||
using SetSelection_t = void(__stdcall *)(uint32_t guidLo, uint32_t guidHi);
|
||||
using EnumCallback_t = int(__fastcall *)(void *ctx, void *unusedEdx, uint64_t guid);
|
||||
using EnumVisibleObjects_t = int(__fastcall *)(EnumCallback_t cb, void *ctx);
|
||||
@@ -110,9 +109,7 @@ const Game::HookAutoRegister _validHook{Offsets::FUN_TARGET_CANDIDATE_VALID,
|
||||
reinterpret_cast<void **>(&g_validOriginal)};
|
||||
|
||||
void Cycle(void *L, int mode) {
|
||||
const int reverse =
|
||||
reinterpret_cast<ToBool_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE)(L, 1, 0);
|
||||
reinterpret_cast<Cycle_t>(Offsets::FUN_TARGET_NEAREST_CYCLE)(reverse, mode);
|
||||
reinterpret_cast<Cycle_t>(Offsets::FUN_TARGET_NEAREST_CYCLE)(Game::Lua::ToFlag(L, 1), mode);
|
||||
}
|
||||
|
||||
void SetSelection(uint64_t guid) {
|
||||
|
||||
@@ -66,7 +66,6 @@ using ShaderCreate_t = void(__fastcall *)(int type, void **outSlot, const char *
|
||||
using ArrayGrow_t = void(__thiscall *)(void *desc, uint32_t newCap);
|
||||
using CompilePs_t = void(__thiscall *)(void *device, void *shader);
|
||||
using SetShader_t = void(__thiscall *)(void *region, void *shader);
|
||||
using LooseBool_t = int(__fastcall *)(void *L, int idx, int defaultValue);
|
||||
using ScriptFn_t = int(__fastcall *)(void *L);
|
||||
using GetUiPixelShader_t = void *(__fastcall *)(int index);
|
||||
|
||||
@@ -380,8 +379,7 @@ int __fastcall Script_SetDesaturated(void *L) {
|
||||
Game::Lua::PushNil(L);
|
||||
return 1;
|
||||
}
|
||||
const int flag = reinterpret_cast<LooseBool_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE)(L, 2, 1);
|
||||
if (!Apply(region, flag != 0 ? kFull : 0)) {
|
||||
if (!Apply(region, Game::Lua::ToFlag(L, 2, true) ? kFull : 0)) {
|
||||
Game::Lua::PushNil(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -57,15 +57,14 @@ namespace {
|
||||
|
||||
using OnSpriteRightClick_t = int(__stdcall *)(uint32_t guidLo, uint32_t guidHi);
|
||||
using TokenToGuid_t = uint64_t(__fastcall *)(const char *token);
|
||||
using ToBool_t = int(__fastcall *)(void *L, int idx, int dflt);
|
||||
|
||||
uint64_t ResolveGuid(void *L, const char *token) {
|
||||
if (_stricmp(token, "mouseover") == 0)
|
||||
return Game::Read<uint64_t>(Offsets::VAR_MOUSEOVER_GUID_LO);
|
||||
if (Unit::TokenResolve::IsUnitToken(token))
|
||||
return reinterpret_cast<TokenToGuid_t>(Offsets::FUN_TOKEN_TO_GUID)(token);
|
||||
const int exact = reinterpret_cast<ToBool_t>(Offsets::FUN_LUA_TO_BOOLEAN_LOOSE)(L, 2, 0);
|
||||
return Unit::Identity::FindGuidByName(token, Offsets::TYPEMASK_OBJECT, exact != 0);
|
||||
return Unit::Identity::FindGuidByName(token, Offsets::TYPEMASK_OBJECT,
|
||||
Game::Lua::ToFlag(L, 2));
|
||||
}
|
||||
|
||||
bool Interact(void *L) {
|
||||
|
||||
@@ -64,11 +64,10 @@ Kind KindOf(const char *type) {
|
||||
}
|
||||
|
||||
void Push(void *L, Kind kind, const char *text) {
|
||||
using ToBool_t = int(__fastcall *)(const char *);
|
||||
using ToFloat_t = float(__stdcall *)(const char *);
|
||||
switch (kind) {
|
||||
case Kind::Boolean:
|
||||
Game::Lua::PushBool(L, reinterpret_cast<ToBool_t>(Offsets::FUN_XML_ATTR_TO_BOOL)(text) != 0);
|
||||
Game::Lua::PushBool(L, Game::StringToFlag(text));
|
||||
break;
|
||||
case Kind::Number:
|
||||
Game::Lua::PushNumber(L, reinterpret_cast<ToFloat_t>(Offsets::FUN_SSTR_TO_FLOAT)(text));
|
||||
|
||||
Reference in New Issue
Block a user