mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-13 22:50:44 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb9579c398 | ||
|
|
5642644833 | ||
|
|
58e7e1a2c6 | ||
|
|
5339c97b1e | ||
|
|
7406ac5db1 | ||
|
|
1c263152e3 |
@@ -11,12 +11,51 @@
|
||||
-- knows (with an "X of Y" summary of the whole skill line), regardless of what
|
||||
-- the reader can craft themselves.
|
||||
|
||||
-- Frame geometry mirrors the real TradeSkillFrame (Blizzard_TradeSkillUI):
|
||||
-- 768x512, TW-TradeSkill quadrant art, 19 rows of 16px, list at (37,-98).
|
||||
local FRAME_W, FRAME_H = 768, 512;
|
||||
local NUM_ROWS = 19;
|
||||
-- Frame geometry mirrors the real TradeSkillFrame. Turtle ships an HD
|
||||
-- 768x512 two-column frame (TW-TradeSkill art); stock 1.12.1 only has the
|
||||
-- vanilla 384x512 single-column art, so on that client we fall back to the
|
||||
-- native Blizzard_TradeSkillUI geometry (list on top, detail below) rather
|
||||
-- than stretch the wide art. LO holds every layout value that differs.
|
||||
local TURTLE = (TURTLE_WOW_VERSION ~= nil);
|
||||
local FRAME_W = TURTLE and 768 or 384;
|
||||
local FRAME_H = 512;
|
||||
local NUM_ROWS = TURTLE and 19 or 8;
|
||||
local ROW_HEIGHT = 16;
|
||||
|
||||
local LO = TURTLE and {
|
||||
hit = { 2, 82, 2, 73 },
|
||||
quads = {
|
||||
{ "Interface\\TradeSkillFrame\\TW-TradeSkill-TopLeft", 512, 256, "TOPLEFT" },
|
||||
{ "Interface\\TradeSkillFrame\\TW-TradeSkill-TopRight", 256, 256, "TOPRIGHT" },
|
||||
{ "Interface\\TradeSkillFrame\\TW-TradeSkill-BotLeft2", 512, 256, "BOTTOMLEFT" },
|
||||
{ "Interface\\TradeSkillFrame\\TW-TradeSkill-BotRight2", 256, 256, "BOTTOMRIGHT" },
|
||||
},
|
||||
close = { -77, -8 },
|
||||
bar = { 596, 15, 76, -44 },
|
||||
barBdr = { 604, 22 },
|
||||
summary = { "TOPRIGHT", -110, -70 },
|
||||
drop = { 40, -60 },
|
||||
scroll = { 296, 330, 37, -98 },
|
||||
detail = { 379, -113 },
|
||||
exit = { "CENTER", "BOTTOMRIGHT", -128, 92 },
|
||||
} or {
|
||||
hit = { 0, 34, 0, 75 },
|
||||
quads = {
|
||||
{ "Interface\\ClassTrainerFrame\\UI-ClassTrainer-TopLeft", 256, 256, "TOPLEFT" },
|
||||
{ "Interface\\ClassTrainerFrame\\UI-ClassTrainer-TopRight", 128, 256, "TOPRIGHT" },
|
||||
{ "Interface\\TradeSkillFrame\\UI-TradeSkill-BotLeft", 256, 256, "BOTTOMLEFT" },
|
||||
{ "Interface\\ClassTrainerFrame\\UI-ClassTrainer-BotRight", 128, 256, "BOTTOMRIGHT" },
|
||||
},
|
||||
close = { -29, -8 },
|
||||
bar = { 268, 15, 73, -37 },
|
||||
barBdr = { 278, 22 },
|
||||
summary = { "TOPRIGHT", -40, -58 },
|
||||
drop = { 12, -62 },
|
||||
scroll = { 296, 130, 21, -96 },
|
||||
detail = { 28, -237 },
|
||||
exit = { "CENTER", "TOPLEFT", 305, -422 },
|
||||
};
|
||||
|
||||
local PROFESSION_ICONS = {
|
||||
[171] = "Trade_Alchemy",
|
||||
[164] = "Trade_BlackSmithing",
|
||||
@@ -32,7 +71,7 @@ local PROFESSION_ICONS = {
|
||||
[356] = "Trade_Fishing",
|
||||
};
|
||||
|
||||
if TURTLE_WOW_VERSION then
|
||||
if TURTLE then
|
||||
PROFESSION_ICONS[142] = "Trade_Survival"
|
||||
PROFESSION_ICONS[755] = "INV_Misc_Gem_01" -- Jewelcrafting
|
||||
end
|
||||
@@ -188,45 +227,43 @@ local function CreateFrame_TradeSkillLink()
|
||||
f:SetPoint("CENTER", 0, 0);
|
||||
f:SetMovable(true);
|
||||
f:EnableMouse(true);
|
||||
f:SetHitRectInsets(2, 82, 2, 73);
|
||||
f:SetHitRectInsets(LO.hit[1], LO.hit[2], LO.hit[3], LO.hit[4]);
|
||||
f:RegisterForDrag("LeftButton");
|
||||
f:SetScript("OnDragStart", function() this:StartMoving(); end);
|
||||
f:SetScript("OnDragStop", function() this:StopMovingOrSizing(); end);
|
||||
f:SetFrameStrata("DIALOG");
|
||||
f:Hide();
|
||||
tinsert(UISpecialFrames, f:GetName())
|
||||
|
||||
local portrait = f:CreateTexture(nil, "BACKGROUND");
|
||||
portrait:SetSize(60, 60);
|
||||
portrait:SetPoint("TOPLEFT", 7, -6);
|
||||
f.portrait = portrait;
|
||||
|
||||
local function Quad(file, w, h, point)
|
||||
for i = 1, table.getn(LO.quads) do
|
||||
local q = LO.quads[i];
|
||||
local tx = f:CreateTexture(nil, "BORDER");
|
||||
tx:SetTexture("Interface\\TradeSkillFrame\\" .. file);
|
||||
tx:SetSize(w, h);
|
||||
tx:SetPoint(point);
|
||||
tx:SetTexture(q[1]);
|
||||
tx:SetSize(q[2], q[3]);
|
||||
tx:SetPoint(q[4]);
|
||||
end
|
||||
Quad("TW-TradeSkill-TopLeft", 512, 256, "TOPLEFT");
|
||||
Quad("TW-TradeSkill-TopRight", 256, 256, "TOPRIGHT");
|
||||
Quad("TW-TradeSkill-BotLeft2", 512, 256, "BOTTOMLEFT");
|
||||
Quad("TW-TradeSkill-BotRight2", 256, 256, "BOTTOMRIGHT");
|
||||
|
||||
local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormal");
|
||||
title:SetPoint("TOP", 0, -18);
|
||||
f.title = title;
|
||||
|
||||
local close = CreateFrame("Button", nil, f, "UIPanelCloseButton");
|
||||
close:SetPoint("TOPRIGHT", -77, -8);
|
||||
close:SetPoint("TOPRIGHT", LO.close[1], LO.close[2]);
|
||||
|
||||
local exit = CreateFrame("Button", nil, f, "UIPanelButtonTemplate");
|
||||
exit:SetSize(80, 22);
|
||||
exit:SetPoint("CENTER", f, "BOTTOMRIGHT", -128, 92);
|
||||
exit:SetPoint(LO.exit[1], f, LO.exit[2], LO.exit[3], LO.exit[4]);
|
||||
exit:SetText(EXIT or "Exit");
|
||||
exit:SetScript("OnClick", function() f:Hide(); end);
|
||||
|
||||
local bar = CreateFrame("StatusBar", nil, f);
|
||||
bar:SetSize(596, 15);
|
||||
bar:SetPoint("TOPLEFT", 76, -44);
|
||||
bar:SetSize(LO.bar[1], LO.bar[2]);
|
||||
bar:SetPoint("TOPLEFT", LO.bar[3], LO.bar[4]);
|
||||
bar:SetStatusBarTexture("Interface\\PaperDollInfoFrame\\UI-Character-Skills-Bar");
|
||||
bar:SetStatusBarColor(0.0, 0.0, 1.0, 0.5);
|
||||
bar:SetMinMaxValues(0, 1);
|
||||
@@ -236,7 +273,7 @@ local function CreateFrame_TradeSkillLink()
|
||||
barBg:SetVertexColor(0.0, 0.0, 0.75, 0.5);
|
||||
-- Border: tooltip edge around the bar (TradeSkillRankFrameBorder).
|
||||
local border = CreateFrame("Frame", nil, bar);
|
||||
border:SetSize(604, 22);
|
||||
border:SetSize(LO.barBdr[1], LO.barBdr[2]);
|
||||
border:SetPoint("LEFT", -5, 1);
|
||||
border:SetBackdrop({
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 16,
|
||||
@@ -253,19 +290,19 @@ local function CreateFrame_TradeSkillLink()
|
||||
f.barRank = barRank;
|
||||
|
||||
local summary = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall");
|
||||
summary:SetPoint("TOPRIGHT", -110, -70);
|
||||
summary:SetPoint(LO.summary[1], LO.summary[2], LO.summary[3]);
|
||||
f.summary = summary;
|
||||
|
||||
local dropdown = CreateFrame("Frame", "ClassicAPITradeSkillLinkSubClass", f,
|
||||
"UIDropDownMenuTemplate");
|
||||
dropdown:SetPoint("TOPLEFT", 40, -60);
|
||||
dropdown:SetPoint("TOPLEFT", LO.drop[1], LO.drop[2]);
|
||||
f.dropdown = dropdown;
|
||||
f.subclassFilter = nil; -- nil = all subclasses
|
||||
|
||||
local scroll = CreateFrame("ScrollFrame", "ClassicAPITradeSkillLinkScroll",
|
||||
f, "FauxScrollFrameTemplate");
|
||||
scroll:SetSize(296, 330);
|
||||
scroll:SetPoint("TOPLEFT", 37, -98);
|
||||
scroll:SetSize(LO.scroll[1], LO.scroll[2]);
|
||||
scroll:SetPoint("TOPLEFT", LO.scroll[3], LO.scroll[4]);
|
||||
scroll:SetScript("OnVerticalScroll", function()
|
||||
FauxScrollFrame_OnVerticalScroll(ROW_HEIGHT, function() f:Refresh(); end)
|
||||
end)
|
||||
@@ -313,7 +350,7 @@ local function CreateFrame_TradeSkillLink()
|
||||
|
||||
local prodIcon = CreateFrame("Button", nil, f);
|
||||
prodIcon:SetSize(37, 37); -- TradeSkillSkillIcon is 37x37
|
||||
prodIcon:SetPoint("TOPLEFT", 379, -113);
|
||||
prodIcon:SetPoint("TOPLEFT", LO.detail[1], LO.detail[2]);
|
||||
prodIcon:SetNormalTexture("Interface\\Icons\\INV_Misc_QuestionMark");
|
||||
local hdrLeft = prodIcon:CreateTexture(nil, "BACKGROUND");
|
||||
hdrLeft:SetTexture("Interface\\ClassTrainerFrame\\UI-ClassTrainer-DetailHeaderLeft");
|
||||
@@ -620,3 +657,43 @@ function SetItemRef(link, text, button, chatFrame)
|
||||
end
|
||||
return originalSetItemRef(link, text, button, chatFrame);
|
||||
end
|
||||
|
||||
local function CreateProfessionLinkButton(parentName, getLink)
|
||||
local parent = _G[parentName];
|
||||
local btnName = parentName .. "LinkButton";
|
||||
if _G[btnName] or not parent then
|
||||
return;
|
||||
end
|
||||
local btn = CreateFrame("Button", btnName, parent);
|
||||
btn:SetSize(16, 16);
|
||||
btn:SetPoint("LEFT", _G[parentName .. "TitleText"], "RIGHT", 4, 0);
|
||||
|
||||
local icon = "Interface\\GossipFrame\\PetitionGossipIcon";
|
||||
btn:SetNormalTexture(icon);
|
||||
btn:SetHighlightTexture(icon, "ADD");
|
||||
|
||||
btn:SetScript("OnClick", function()
|
||||
local link = getLink();
|
||||
if not link then
|
||||
return;
|
||||
end
|
||||
if not ChatFrameEditBox:IsVisible() then
|
||||
ChatFrameEditBox:Show();
|
||||
end
|
||||
ChatFrameEditBox:Insert(link);
|
||||
end);
|
||||
btn:SetScript("OnEnter", function()
|
||||
GameTooltip:SetOwner(this, "ANCHOR_TOPLEFT");
|
||||
GameTooltip:SetText(LINK_TRADESKILL_TOOLTIP, nil, nil, nil, nil, 1);
|
||||
GameTooltip:Show();
|
||||
end);
|
||||
btn:SetScript("OnLeave", GameTooltip_Hide);
|
||||
end
|
||||
|
||||
EventUtil.ContinueOnAddOnLoaded("Blizzard_TradeSkillUI", function()
|
||||
CreateProfessionLinkButton("TradeSkillFrame", C_TradeSkillUI.GetTradeSkillListLink);
|
||||
end);
|
||||
|
||||
EventUtil.ContinueOnAddOnLoaded("Blizzard_CraftUI", function()
|
||||
CreateProfessionLinkButton("CraftFrame", C_TradeSkillUI.GetCraftListLink);
|
||||
end);
|
||||
@@ -24,3 +24,4 @@ SLASH_EQUIP_SET2 = "/equipset";
|
||||
SLASH_FOCUS1 = "/focus";
|
||||
SLASH_FOCUS2 = "/focus";
|
||||
TARGETFOCUS = "Target Focus";
|
||||
LINK_TRADESKILL_TOOLTIP = "Click here to create a link to your profession.";
|
||||
@@ -33,7 +33,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
|
||||
| [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` |
|
||||
| [Frame](docs/API.md#frame) | `region:SetPoint("point")` (one-arg form), `region:SetSize`, `region:GetSize`, `frame:SetShown`, `frame:SetResizeBounds`, `frame:HookScript` |
|
||||
| [Frame](docs/API.md#frame) | `region:SetPoint("point")` (one-arg form), `region:SetSize`, `region:GetSize`, `frame:SetShown`, `frame:SetResizeBounds`, `frame:HookScript`, `frame:IsEventRegistered` |
|
||||
| [FriendList](docs/API.md#friendlist) | `C_FriendList.IsWhoQueryPending`, `C_FriendList.SendWhoQueryByName` |
|
||||
| [GameObject](docs/API.md#gameobject) | `C_GameObjectInfo.GetGameObjectInfoByID`, `C_GameObjectInfo.RequestLoadGameObjectByID` |
|
||||
| [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` |
|
||||
|
||||
+17
@@ -137,6 +137,7 @@ build instructions.
|
||||
- [`frame:SetShown(shown)`](#framesetshownshown)
|
||||
- [`frame:SetResizeBounds(minWidth, minHeight [, maxWidth, maxHeight])`](#framesetresizeboundsminwidth-minheight--maxwidth-maxheight)
|
||||
- [`frame:HookScript(scriptType, handler)`](#framehookscriptscripttype-handler)
|
||||
- [`frame:IsEventRegistered(event)`](#frameiseventregisteredevent)
|
||||
|
||||
- [FriendList](#friendlist)
|
||||
- [`C_FriendList.SendWhoQueryByName(name)`](#c_friendlistsendwhoquerybynamename)
|
||||
@@ -3143,6 +3144,22 @@ button:HookScript("OnEnter", function(self)
|
||||
end)
|
||||
```
|
||||
|
||||
### `frame:IsEventRegistered(event)`
|
||||
|
||||
Vanilla ships `RegisterEvent` / `UnregisterEvent` but not the query.
|
||||
Returns `true` if the frame is currently registered for `event`,
|
||||
`false` otherwise (including for an unknown event name). Reuses the
|
||||
engine's own subscriber-chain membership check — the same walk
|
||||
`RegisterEvent` performs before appending — so the answer is exactly
|
||||
what the event dispatcher sees. Works on any frame.
|
||||
|
||||
```lua
|
||||
local f = CreateFrame("Frame")
|
||||
f:RegisterEvent("PLAYER_LOGIN")
|
||||
f:IsEventRegistered("PLAYER_LOGIN") -- true
|
||||
f:IsEventRegistered("PLAYER_LOGOUT") -- false
|
||||
```
|
||||
|
||||
## FriendList
|
||||
|
||||
### `C_FriendList.SendWhoQueryByName(name)`
|
||||
|
||||
@@ -3463,6 +3463,15 @@ enum Offsets {
|
||||
OFF_EVENT_ENTRY_NAME = 0x00,
|
||||
OFF_EVENT_ENTRY_HEAD = 0x0C,
|
||||
|
||||
// Subscriber-chain node layout — the linked list hanging off each
|
||||
// entry's +0x0C head, derived from Frame::RegisterEvent
|
||||
// (FUN_00702140): each node is `{ ?, next@+0x04, frame@+0x08 }`, where
|
||||
// `frame` is the CFrameScriptObject* subscriber. A node value with its
|
||||
// low bit set (or null) is the end/empty sentinel. Walked by
|
||||
// Frame:IsEventRegistered to test membership.
|
||||
OFF_EVENT_NODE_NEXT = 0x04,
|
||||
OFF_EVENT_NODE_FRAME = 0x08,
|
||||
|
||||
// `Frame::RegisterEvent` — the C++ helper called by the Lua
|
||||
// `frame:RegisterEvent(eventName)` method (`Script_RegisterEvent` at
|
||||
// `0x00774A40`). `__thiscall` with `(this=frame, eventName)`. Walks
|
||||
|
||||
@@ -150,10 +150,40 @@ void Evict(uint64_t targetGuid, uint32_t spellId) {
|
||||
}
|
||||
}
|
||||
|
||||
// Wipe the whole cache. Used on a map transition (see OnWorldTick).
|
||||
void FlushAll() {
|
||||
for (auto &e : g_cache)
|
||||
e.used = false;
|
||||
}
|
||||
|
||||
// -1 = no map seen yet (never a valid Map.dbc id), so the first tick just
|
||||
// records the current map without flushing.
|
||||
int g_lastMapId = -1;
|
||||
|
||||
// Drop entries whose timed aura has elapsed so the table doesn't fill with
|
||||
// stale combat debuffs. Infinite-duration entries (expirationMs == 0) stay
|
||||
// until overwritten.
|
||||
//
|
||||
// Also flush the entire cache on a map-ID change. A battleground/instance/
|
||||
// continent transition (including the teleport out of a BG via /afk) bulk-
|
||||
// clears the player's aura descriptor WITHOUT firing per-aura OnAuraRemoved,
|
||||
// so entries for auras that are now gone would linger and the
|
||||
// GetAuraDataByIndex fallback would keep surfacing them — the "buffs/debuffs
|
||||
// stuck after leaving a battleground" report. Every other unit from the old
|
||||
// map has despawned too, and the local player's real auras re-sync into the
|
||||
// descriptor / PLAYER_BUFF tables on entering the new world, so a full flush
|
||||
// loses nothing legitimate. Crucially, the descriptor drops the fallback
|
||||
// must survive — rogue stealth and party-range fluctuation — do NOT change
|
||||
// the map ID, so they're unaffected.
|
||||
void OnWorldTick() {
|
||||
const int mapId = *reinterpret_cast<const int *>(
|
||||
static_cast<uintptr_t>(Offsets::VAR_CURRENT_MAP_ID));
|
||||
if (mapId != g_lastMapId) {
|
||||
if (g_lastMapId != -1)
|
||||
FlushAll();
|
||||
g_lastMapId = mapId;
|
||||
}
|
||||
|
||||
const uint32_t now = NowMs();
|
||||
for (auto &e : g_cache) {
|
||||
if (e.used && e.expirationMs != 0 && now >= e.expirationMs)
|
||||
|
||||
@@ -186,6 +186,65 @@ int __fastcall Script_HookScript(void *L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- IsEventRegistered (Frame) ---------------------------------------------
|
||||
|
||||
// `frame:IsEventRegistered("event")` — vanilla has RegisterEvent /
|
||||
// UnregisterEvent but not the query. There's no engine Script_* to
|
||||
// delegate to, so we replicate the membership check that lives inside
|
||||
// Frame::RegisterEvent (FUN_00702140): find the event by name in the live
|
||||
// event table, then walk that entry's subscriber chain for this frame.
|
||||
// The frame pointer we compare against is the same CFrameScriptObject*
|
||||
// the engine stores (Game::Lua::ResolveObject resolves self exactly as
|
||||
// Script_RegisterEvent does).
|
||||
using SStrCmpI_t = int(__stdcall *)(const char *a, const char *b, int n);
|
||||
|
||||
bool FrameHasEvent(void *frame, const char *eventName) {
|
||||
const uint32_t count = *reinterpret_cast<const uint32_t *>(
|
||||
static_cast<uintptr_t>(Offsets::VAR_EVENT_TABLE_COUNT));
|
||||
auto *base = *reinterpret_cast<const uint8_t *const *>(
|
||||
static_cast<uintptr_t>(Offsets::VAR_EVENT_TABLE_BASE_PTR));
|
||||
if (count == 0 || base == nullptr)
|
||||
return false;
|
||||
|
||||
auto sstrcmpi = reinterpret_cast<SStrCmpI_t>(
|
||||
static_cast<uintptr_t>(Offsets::FUN_SSTR_CMP_I));
|
||||
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const uint8_t *entry = base + i * Offsets::EVENT_ENTRY_STRIDE;
|
||||
const char *name = *reinterpret_cast<const char *const *>(
|
||||
entry + Offsets::OFF_EVENT_ENTRY_NAME);
|
||||
if (name == nullptr)
|
||||
continue;
|
||||
if (sstrcmpi(name, eventName, 0x7FFFFFFF) != 0)
|
||||
continue;
|
||||
// Names are unique, so this is the one entry — walk its chain.
|
||||
uintptr_t node = *reinterpret_cast<const uintptr_t *>(
|
||||
entry + Offsets::OFF_EVENT_ENTRY_HEAD);
|
||||
while ((node & 1) == 0 && node != 0) {
|
||||
if (*reinterpret_cast<void *const *>(
|
||||
node + Offsets::OFF_EVENT_NODE_FRAME) == frame)
|
||||
return true;
|
||||
node = *reinterpret_cast<const uintptr_t *>(
|
||||
node + Offsets::OFF_EVENT_NODE_NEXT);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int __fastcall Script_IsEventRegistered(void *L) {
|
||||
if (!Game::Lua::IsString(L, 2)) {
|
||||
Game::Lua::Error(L, "Usage: frame:IsEventRegistered(\"event\")");
|
||||
return 0;
|
||||
}
|
||||
void *frame = Game::Lua::ResolveObject(L, 1);
|
||||
const char *eventName = Game::Lua::ToString(L, 2);
|
||||
const bool registered =
|
||||
frame != nullptr && eventName != nullptr && FrameHasEvent(frame, eventName);
|
||||
Game::Lua::PushBool(L, registered);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---- SetPoint one-arg form (Script_SetPoint co-hook) -----------------------
|
||||
|
||||
// Vanilla's Script_SetPoint accepts every modern form except the bare
|
||||
@@ -222,6 +281,7 @@ const Game::Lua::FrameMethodEntry g_frameMethods[] = {
|
||||
Offsets::FUN_SCRIPT_FRAME_HIDE>},
|
||||
{"SetResizeBounds", &Script_SetResizeBounds},
|
||||
{"HookScript", &Script_HookScript},
|
||||
{"IsEventRegistered", &Script_IsEventRegistered},
|
||||
};
|
||||
|
||||
const Game::Lua::FrameMethodEntry g_textureMethods[] = {
|
||||
|
||||
+24
-4
@@ -133,6 +133,26 @@ void StampChannel(int spellID, int startMs, int endMs) {
|
||||
|
||||
int NowMs() { return static_cast<int>(reinterpret_cast<TickMs_t>(Offsets::FUN_OS_TICKCOUNT_MS)()); }
|
||||
|
||||
// Pushes an engine-ms timestamp to Lua as an UNSIGNED 32-bit value.
|
||||
//
|
||||
// `FUN_OS_TICKCOUNT_MS` returns a 64-bit ms tick from one of two backends
|
||||
// (GetTickCount, or a scaled rdtsc — the latter survives warm reboots, so
|
||||
// the value can be well past 2^31 even right after a "restart"). We keep
|
||||
// ticks in a signed int internally, which is fine for the small deltas and
|
||||
// window compares (they cancel), but a direct int->double push surfaces a
|
||||
// NEGATIVE startTimeMs/endTimeMs once the tick's high bit is set.
|
||||
//
|
||||
// The contract is that these match `GetTime()*1000`. Script_GetTime
|
||||
// (0x00515EA0) does `(double)(tick & 0xFFFFFFFF) * 0.001` — i.e. it masks
|
||||
// the tick to the low 32 bits UNSIGNED, then scales. So reinterpreting our
|
||||
// stored tick as uint32 reproduces GetTime()'s exact value for the whole
|
||||
// range, including across the ~49.7-day 2^32 wrap (both wrap together).
|
||||
// Carrying 64-bit precision here would be WRONG: it would diverge from
|
||||
// GetTime() past 2^32 and break addons' (now-start)/(end-start) math.
|
||||
void PushMs(void *L, int ms) {
|
||||
Game::Lua::PushNumber(L, static_cast<double>(static_cast<uint32_t>(ms)));
|
||||
}
|
||||
|
||||
void *Resolve(const char *token) {
|
||||
return reinterpret_cast<ResolveUnitToken_t>(Offsets::FUN_RESOLVE_UNIT_TOKEN)(token);
|
||||
}
|
||||
@@ -200,8 +220,8 @@ int PushCastInfo(void *L, const TrackedSpell &c) {
|
||||
Game::Lua::PushString(L, name); // 1 name
|
||||
Game::Lua::PushString(L, name); // 2 displayName
|
||||
Game::Lua::PushString(L, SpellIconPath(rec)); // 3 textureID (path)
|
||||
Game::Lua::PushNumber(L, static_cast<double>(c.startMs)); // 4 startTimeMs
|
||||
Game::Lua::PushNumber(L, static_cast<double>(c.endMs)); // 5 endTimeMs
|
||||
PushMs(L, c.startMs); // 4 startTimeMs
|
||||
PushMs(L, c.endMs); // 5 endTimeMs
|
||||
Game::Lua::PushBool(L, IsTradeskill(rec)); // 6 isTradeskill
|
||||
Game::Lua::PushNil(L); // 7 castID
|
||||
Game::Lua::PushBool(L, false); // 8 notInterruptible
|
||||
@@ -230,8 +250,8 @@ int PushChannelInfo(void *L, int spellID, int startMs, int endMs, bool haveTimes
|
||||
Game::Lua::PushString(L, name); // 2 text / displayName
|
||||
Game::Lua::PushString(L, SpellIconPath(rec)); // 3 textureID (path)
|
||||
if (haveTimes) {
|
||||
Game::Lua::PushNumber(L, static_cast<double>(startMs)); // 4 startTimeMs
|
||||
Game::Lua::PushNumber(L, static_cast<double>(endMs)); // 5 endTimeMs
|
||||
PushMs(L, startMs); // 4 startTimeMs
|
||||
PushMs(L, endMs); // 5 endTimeMs
|
||||
} else {
|
||||
Game::Lua::PushNil(L);
|
||||
Game::Lua::PushNil(L);
|
||||
|
||||
Reference in New Issue
Block a user