mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-10 20:07:37 +00:00
feat: engine pieces for retail's ScrollBox
What the ScrollBox port needs from the DLL, and three engine bugs it hit. Fixes: - GetWidth / GetHeight answer a rect-derived size over the layout scale, as 4.3.4 does. 1.12's getters leave the division out, so an anchored region under a UI scale read scaled (a 160-high track at 0.91 answered 145.6, and the scroll bar thumb stopped short). GetRect and GetScaledRect use them. - A <Size>, <Anchor> or <Offset> with only `x` applies. The engine's dimension reader decides "no attributes" from `y` alone; a co-hook answers as 4.3.4 does. Xml::Anchors' "0" attribute workaround goes. - KeyValue type="global" walks a dotted path, as retail's own templates need (ScrollControllerMixin.Directions.Decrease). New: - <Texture atlas="..." useAtlasSize="...">, applied as SetAtlas. - securecall / securecallfunction / secureexecuterange, as probed on retail: results returned, errors to the error handler, the range walk goes on after one. - AddSourceLocationExclude(fileName): GetSourceLocation steps over that file's levels. - region:AdjustPointsOffset(x, y) / ClearPointsOffset(), re-setting each anchor through the engine's SetPoint. Retail, probed: Clear zeroes the offsets rather than undoing adjustments. - Enum.InputContext.
This commit is contained in:
+111
-3
@@ -272,9 +272,11 @@ build instructions.
|
||||
- [`region:GetPoint([index])`](#regiongetpointindex)
|
||||
- [`region:GetPointByName(point)`](#regiongetpointbynamepoint)
|
||||
- [`region:ClearPoint(point)`](#regionclearpointpoint)
|
||||
- [`region:AdjustPointsOffset(x, y)` / `region:ClearPointsOffset()`](#regionadjustpointsoffsetx-y--regionclearpointsoffset)
|
||||
- [`region:IsDragging()`](#regionisdragging)
|
||||
- [`region:GetDebugName([preferParentKey])`](#regiongetdebugnamepreferparentkey)
|
||||
- [`region:GetSourceLocation()`](#regiongetsourcelocation)
|
||||
- [`AddSourceLocationExclude(fileName)`](#addsourcelocationexcludefilename)
|
||||
- [`GetMouseFoci()`](#getmousefoci)
|
||||
- [`CreateFrame(frameType [, name, parent, template, id])`](#createframeframetype--name-parent-template-id)
|
||||
- [`CreateFrame("Cooldown" [, name, parent, template])`](#createframecooldown--name-parent-template)
|
||||
@@ -450,6 +452,7 @@ build instructions.
|
||||
|
||||
- [Hooks](#hooks)
|
||||
- [`hooksecurefunc(name, callback)` / `hooksecurefunc(table, name, callback)`](#hooksecurefuncname-callback--hooksecurefunctable-name-callback)
|
||||
- [`securecall(func, ...)` / `securecallfunction(func, ...)` / `secureexecuterange(tbl, func, ...)`](#securecallfunc---securecallfunctionfunc---secureexecuterangetbl-func-)
|
||||
|
||||
- [Input](#input)
|
||||
- [`IsLeftShiftKeyDown()` / `IsRightShiftKeyDown()`](#isleftshiftkeydown--isrightshiftkeydown)
|
||||
@@ -964,6 +967,8 @@ build instructions.
|
||||
- [`KeyValues` in XML](#keyvalues-in-xml)
|
||||
- [`mixin` in XML](#mixin-in-xml)
|
||||
- [`intrinsic` frame types in XML](#intrinsic-frame-types-in-xml)
|
||||
- [ScrollBox and scroll bars](#scrollbox-and-scroll-bars)
|
||||
- [Atlases in XML](#atlases-in-xml)
|
||||
- [Masks in XML](#masks-in-xml)
|
||||
- [`function` and `method` on XML script handlers](#function-and-method-on-xml-script-handlers)
|
||||
- [`style` on button font elements](#style-on-button-font-elements)
|
||||
@@ -6608,8 +6613,9 @@ effect as on `GetWidth` and `GetHeight`.
|
||||
### `region:GetWidth([ignoreRect])` / `region:GetHeight([ignoreRect])`
|
||||
|
||||
Returns the width or the height of the region. A region with no set size
|
||||
returns the size that its anchors give it. If `ignoreRect` is true, the
|
||||
method returns only the set size, which is 0 for a region with no set size.
|
||||
returns the size that its anchors give it, in the same units as `GetTop`
|
||||
and `GetBottom`, at every UI scale. If `ignoreRect` is true, the method
|
||||
returns only the set size, which is 0 for a region with no set size.
|
||||
|
||||
```lua
|
||||
local f = CreateFrame("Frame", nil, UIParent)
|
||||
@@ -6703,6 +6709,21 @@ anchor at that point.
|
||||
local point, relativeTo, relativePoint, x, y = MyFrame:GetPointByName("TOPLEFT")
|
||||
```
|
||||
|
||||
### `region:AdjustPointsOffset(x, y)` / `region:ClearPointsOffset()`
|
||||
|
||||
`AdjustPointsOffset` adds `x` and `y` to the offset of each anchor of the
|
||||
region. `ClearPointsOffset` sets the offset of each anchor to 0. Each
|
||||
anchor keeps its point, its relative region and its relative point.
|
||||
|
||||
```lua
|
||||
t:SetPoint("CENTER", 5, 7)
|
||||
t:AdjustPointsOffset(1, -1) -- the anchor is now ("CENTER", 6, 6)
|
||||
t:ClearPointsOffset() -- the anchor is now ("CENTER", 0, 0)
|
||||
```
|
||||
|
||||
`ClearPointsOffset` does not undo `AdjustPointsOffset`. It removes all of
|
||||
the offset, also the offset that `SetPoint` gave.
|
||||
|
||||
### `region:ClearPoint(point)`
|
||||
|
||||
Removes the anchor of the region at `point`. The other anchors stay. The
|
||||
@@ -6772,6 +6793,19 @@ If `CreateFrame` runs inside `pcall`, the location is the script that called
|
||||
`pcall`. Child frames that a template adds to a `CreateFrame` frame return
|
||||
the location of that `CreateFrame` call.
|
||||
|
||||
### `AddSourceLocationExclude(fileName)`
|
||||
|
||||
Makes [`GetSourceLocation`](#regiongetsourcelocation) skip a script file.
|
||||
A frame that code in that file creates returns the location of the code
|
||||
that called into the file. Use it in a file that creates frames for other
|
||||
code, such as a pool or a factory:
|
||||
|
||||
```lua
|
||||
AddSourceLocationExclude("MyFramePool.lua")
|
||||
```
|
||||
|
||||
`fileName` is the name of the file without its folder. Case is ignored.
|
||||
|
||||
### `GetMouseFoci()`
|
||||
|
||||
Returns a table of the mouse-enabled frames under the cursor, from top
|
||||
@@ -10396,6 +10430,27 @@ but they only report "any shift/ctrl/alt" — there's no built-in way to
|
||||
tell left from right. These seven functions add the missing distinction
|
||||
plus an `IsModifierKeyDown()` rollup.
|
||||
|
||||
### `securecall(func, ...)` / `securecallfunction(func, ...)` / `secureexecuterange(tbl, func, ...)`
|
||||
|
||||
Call a function so that an error in it does not stop the code that called
|
||||
it. The error goes to the error handler (`geterrorhandler()`).
|
||||
|
||||
- `securecall(func, ...)` calls `func(...)` and returns its results.
|
||||
`func` can also be the name of a global function. After an error,
|
||||
`securecall` returns nothing.
|
||||
- `securecallfunction(func, ...)` is the same, but `func` must be a
|
||||
function.
|
||||
- `secureexecuterange(tbl, func, ...)` calls `func(key, value, ...)` for
|
||||
each entry of `tbl`. After an error in one call, the other entries still
|
||||
run. It returns nothing.
|
||||
|
||||
```lua
|
||||
print(securecall(math.max, 1, 9)) -- 9
|
||||
secureexecuterange({10, 20}, function(key, value, extra)
|
||||
print(key, value, extra)
|
||||
end, "x")
|
||||
```
|
||||
|
||||
### `IsLeftShiftKeyDown()` / `IsRightShiftKeyDown()`
|
||||
### `IsLeftControlKeyDown()` / `IsRightControlKeyDown()`
|
||||
### `IsLeftAltKeyDown()` / `IsRightAltKeyDown()`
|
||||
@@ -22584,7 +22639,7 @@ are `string` if you leave them out:
|
||||
| `number` | The text as a number. |
|
||||
| `boolean` | `true` if the text starts with `t`, `y` or a digit from `1` to `9` (any case), or is `on` or `enabled`. Otherwise `false`. |
|
||||
| `nil` | `nil`. This type clears a field that a template set. |
|
||||
| `global` | The global variable with that name, `_G[value]`. |
|
||||
| `global` | The global variable with that name, `_G[value]`. A name with dots, such as `ScrollControllerMixin.Directions.Decrease`, reads one field at each dot. |
|
||||
|
||||
Numbers keep about seven significant digits, so integers up to 16777216
|
||||
are exact.
|
||||
@@ -22676,6 +22731,59 @@ local f = CreateFrame("EventFrame", nil, UIParent)
|
||||
f:RegisterCallback("OnShow", function() print("shown") end, f)
|
||||
```
|
||||
|
||||
|
||||
### ScrollBox and scroll bars
|
||||
|
||||
ClassicAPI has the ScrollBox system of the later clients: scroll boxes,
|
||||
their views, data providers, `ScrollUtil` and scroll bars. A scroll box
|
||||
shows its frames only inside its own area.
|
||||
|
||||
| Template | Use |
|
||||
|---|---|
|
||||
| `WowScrollBoxList` | A list or a grid of frames from a data provider. |
|
||||
| `WowScrollBox` | A scroll box with one frame that you make, such as a long text. |
|
||||
| `WowClassicScrollBar` | A vertical scroll bar with the client's own scroll bar art. |
|
||||
| `WowTrimScrollBar`, `MinimalScrollBar` | The same as `WowClassicScrollBar`. |
|
||||
| `ScrollingEditBoxTemplate`, `ScrollingFontTemplate` | An edit box or a font string in a scroll box. |
|
||||
|
||||
```lua
|
||||
local box = CreateFrame("Frame", nil, UIParent, "WowScrollBoxList")
|
||||
box:SetSize(200, 200)
|
||||
box:SetPoint("CENTER")
|
||||
local bar = CreateFrame("EventFrame", nil, UIParent, "WowClassicScrollBar")
|
||||
bar:SetPoint("TOPLEFT", box, "TOPRIGHT")
|
||||
bar:SetPoint("BOTTOMLEFT", box, "BOTTOMRIGHT")
|
||||
|
||||
local view = CreateScrollBoxListLinearView()
|
||||
view:SetElementExtent(20)
|
||||
view:SetElementInitializer("Button", function(button, data)
|
||||
if not button.text then
|
||||
button.text = button:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||
button.text:SetAllPoints()
|
||||
end
|
||||
button.text:SetText(data)
|
||||
end)
|
||||
ScrollUtil.InitScrollBoxListWithScrollBar(box, bar, view)
|
||||
box:SetDataProvider(CreateDataProvider({"one", "two", "three"}))
|
||||
```
|
||||
|
||||
The scroll bar arrows do not play a click sound.
|
||||
### Atlases in XML
|
||||
|
||||
A `<Texture>` element takes an `atlas` attribute and a `useAtlasSize`
|
||||
attribute. They are the same as
|
||||
`texture:SetAtlas(atlas, useAtlasSize)`:
|
||||
|
||||
```xml
|
||||
<Texture parentKey="Middle" atlas="UI-ClassicScrollBar-Center" useAtlasSize="true"/>
|
||||
```
|
||||
|
||||
The atlas applies after the other content of the element. An atlas that
|
||||
ClassicAPI does not know keeps the texture that the element set.
|
||||
|
||||
A `<Size>` element, an `<Anchor>` or an `<Offset>` can give only `x` or
|
||||
only `y`. The value that it does not give is 0.
|
||||
|
||||
### Masks in XML
|
||||
|
||||
A `<Texture>` element takes a `mask` attribute. It is the same as
|
||||
|
||||
+3
-1
@@ -11195,7 +11195,9 @@ enum Offsets {
|
||||
// <AbsDimension> (or <RelDimension>) child, converted to layout units the
|
||||
// way every XML size and offset is. With neither attribute and no child it
|
||||
// reports the node to `status` and returns 0. The anchor loader reads each
|
||||
// <Offset> with it (0x00767800 calls it, benilla-re ui/anchors).
|
||||
// <Offset> with it (0x00767800 calls it, benilla-re ui/anchors). It
|
||||
// decides "no attributes" from `y` alone, so a node with only `x` reports
|
||||
// and answers 0; Xml::Dimension co-hooks it to answer 1 there, as 4.3.4.
|
||||
FUN_XML_READ_DIMENSION = 0x006F1EB0,
|
||||
// XMLTree_Load — `XMLTree *__fastcall(const char *data, uint32_t size)`,
|
||||
// null when the text does not parse; the root node is `*(XMLNode **)tree`.
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
// 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/>.
|
||||
|
||||
// `securecall(func or name, ...)`, `securecallfunction(func, ...)` and
|
||||
// `secureexecuterange(tbl, func, ...)` — the later clients' protected calls,
|
||||
// less taint (1.12 has none). Retail, probed:
|
||||
// - securecall returns the function's results; a string names a global
|
||||
// function. An error goes to the error handler and the call returns
|
||||
// nothing.
|
||||
// - securecallfunction is securecall without the name lookup: a string is
|
||||
// a value that cannot be called, so it errors and returns nothing.
|
||||
// - secureexecuterange calls func(key, value, ...) for each entry of tbl in
|
||||
// `next` order; an error in one goes to the error handler and the walk
|
||||
// goes on. It returns nothing.
|
||||
// The error handler is the one every engine script run passes
|
||||
// (VAR_FRAMESCRIPT_ERROR_HANDLER_REF), as HookScript's wrapper uses.
|
||||
//
|
||||
// Registered on both Lua states, like the base-library helpers.
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
|
||||
namespace BaseLib::SecureCall {
|
||||
|
||||
namespace {
|
||||
|
||||
// Pushes the engine's script error handler; false (nothing pushed) before
|
||||
// the engine has set one.
|
||||
bool PushErrorHandler(void *L) {
|
||||
const int ref = Game::Read<int>(static_cast<uintptr_t>(Offsets::VAR_FRAMESCRIPT_ERROR_HANDLER_REF));
|
||||
if (ref <= 0)
|
||||
return false;
|
||||
Game::Lua::RawGetI(L, Game::Lua::REGISTRY_INDEX, ref);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Calls the function at stack index 1 with the values above it, under the
|
||||
// error handler. Returns the result count, 0 after an error.
|
||||
int CallProtected(void *L) {
|
||||
const int nargs = Game::Lua::GetTop(L) - 1;
|
||||
int errIdx = 0;
|
||||
if (PushErrorHandler(L)) {
|
||||
Game::Lua::Insert(L, 1); // [handler, func, args...]
|
||||
errIdx = 1;
|
||||
}
|
||||
if (Game::Lua::PCall(L, nargs, Game::Lua::MULTRET, errIdx) != 0)
|
||||
return 0;
|
||||
return Game::Lua::GetTop(L) - errIdx;
|
||||
}
|
||||
|
||||
int __fastcall Script_securecall(void *L) {
|
||||
if (Game::Lua::Type(L, 1) == Game::Lua::TYPE_STRING) {
|
||||
Game::Lua::PushValue(L, 1);
|
||||
Game::Lua::GetTable(L, Game::Lua::GLOBALS_INDEX);
|
||||
Game::Lua::Remove(L, 1);
|
||||
Game::Lua::Insert(L, 1);
|
||||
}
|
||||
return CallProtected(L);
|
||||
}
|
||||
|
||||
int __fastcall Script_securecallfunction(void *L) { return CallProtected(L); }
|
||||
|
||||
int __fastcall Script_secureexecuterange(void *L) {
|
||||
const int n = Game::Lua::GetTop(L); // tbl, func, extra args 3..n
|
||||
if (Game::Lua::Type(L, 1) != Game::Lua::TYPE_TABLE)
|
||||
return 0;
|
||||
Game::Lua::PushNil(L);
|
||||
while (Game::Lua::Next(L, 1) != 0) { // [.., key, value]
|
||||
int errIdx = 0;
|
||||
if (PushErrorHandler(L))
|
||||
errIdx = n + 3;
|
||||
Game::Lua::PushValue(L, 2);
|
||||
Game::Lua::PushValue(L, n + 1);
|
||||
Game::Lua::PushValue(L, n + 2);
|
||||
for (int i = 3; i <= n; ++i)
|
||||
Game::Lua::PushValue(L, i);
|
||||
Game::Lua::PCall(L, n, 0, errIdx); // key, value and the n - 2 extras
|
||||
Game::Lua::SetTop(L, n + 1); // the key, for the next step
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Game::Doc::Field kCallArgs[] = {
|
||||
Game::Doc::Req("func", "function", "The function to call, or for securecall the name of a global function."),
|
||||
Game::Doc::Opt("...", "any", nullptr, "The arguments for the function."),
|
||||
};
|
||||
const Game::Doc::Field kCallRets[] = {
|
||||
Game::Doc::Opt("...", "any", nullptr, "The function's results, or nothing after an error."),
|
||||
};
|
||||
const Game::Doc::Field kRangeArgs[] = {
|
||||
Game::Doc::Req("tbl", "table", "The table whose entries are visited."),
|
||||
Game::Doc::Req("func", "function", "Called as func(key, value, ...) for each entry."),
|
||||
Game::Doc::Opt("...", "any", nullptr, "Extra arguments for each call."),
|
||||
};
|
||||
const Game::Doc::Function kSecureCall{
|
||||
"Calls a function and returns its results. An error goes to the error handler and the call returns nothing.",
|
||||
kCallArgs, kCallRets, "LuaGlobals"};
|
||||
const Game::Doc::Function kSecureCallFunction{
|
||||
"Calls a function and returns its results. An error goes to the error handler and the call returns nothing.",
|
||||
kCallArgs, kCallRets, "LuaGlobals"};
|
||||
const Game::Doc::Function kSecureExecuteRange{
|
||||
"Calls a function for each entry of a table. An error goes to the error handler and the other entries still run.",
|
||||
kRangeArgs, {}, "LuaGlobals"};
|
||||
|
||||
void RegisterInGame() {
|
||||
Game::Lua::RegisterGlobalFunction("securecall", &Script_securecall, &kSecureCall);
|
||||
Game::Lua::RegisterGlobalFunction("securecallfunction", &Script_securecallfunction, &kSecureCallFunction);
|
||||
Game::Lua::RegisterGlobalFunction("secureexecuterange", &Script_secureexecuterange, &kSecureExecuteRange);
|
||||
}
|
||||
|
||||
void RegisterGlue() {
|
||||
Game::Lua::RegisterGlueFunction("securecall", &Script_securecall, &kSecureCall);
|
||||
Game::Lua::RegisterGlueFunction("securecallfunction", &Script_securecallfunction, &kSecureCallFunction);
|
||||
Game::Lua::RegisterGlueFunction("secureexecuterange", &Script_secureexecuterange, &kSecureExecuteRange);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterInGame};
|
||||
const Game::GlueModuleAutoRegister _autoregGlue{&RegisterGlue};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace BaseLib::SecureCall
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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/>.
|
||||
|
||||
|
||||
// `Enum.InputContext` — retail's InputConstants enumeration: the input an
|
||||
// edit box's OnCursorChanged names as its `context`. ScrollingEditBox
|
||||
// (Blizzard_SharedXML) compares it with Enum.InputContext.Keyboard.
|
||||
|
||||
#include "Game.h"
|
||||
|
||||
namespace EditBox::InputContext {
|
||||
|
||||
namespace {
|
||||
|
||||
const Game::Lua::EnumIntegerEntry kEntries[] = {
|
||||
{"None", 0},
|
||||
{"Keyboard", 1},
|
||||
{"Mouse", 2},
|
||||
{"GamePad", 3},
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "InputContext", kEntries,
|
||||
sizeof(kEntries) / sizeof(kEntries[0]), "InputConstants");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace EditBox::InputContext
|
||||
+38
-32
@@ -27,9 +27,9 @@
|
||||
// on the REGION base method registry, so they resolve on frames,
|
||||
// buttons, textures, fontstrings — everything.
|
||||
// - `region:GetWidth([ignoreRect])` / `region:GetHeight([ignoreRect])` —
|
||||
// the engine's getters with the later argument. The engine answers the
|
||||
// explicit size, and the rect's extent only when that size is 0; with
|
||||
// ignoreRect a 0 stays 0.
|
||||
// 4.3.4's getters: the explicit size, and the rect's extent over the
|
||||
// layout scale only when that size is 0; with ignoreRect a 0 stays 0.
|
||||
// 1.12's own leave out the division (see PushExtent).
|
||||
// - `region:IsMouseOver([top, bottom, left, right])` — cursor-in-rect
|
||||
// test with optional per-edge offsets, also on the REGION registry.
|
||||
// - `region:GetRect()` — left, bottom, width, height, composed from the
|
||||
@@ -98,41 +98,47 @@ int __fastcall Script_SetSize(void *L) {
|
||||
}
|
||||
|
||||
using LayoutExtent_t = float(__thiscall *)(void *layout);
|
||||
using LayoutDirty_t = int(__fastcall *)(void *layout);
|
||||
using LayoutResize_t = void(__fastcall *)(void *layout, void *edx, int force);
|
||||
using LayoutGetRect_t = int(__fastcall *)(void *layout, void *edx, float *out);
|
||||
|
||||
// Pushes the engine getter's answer for `self` (index 1), or 0 when
|
||||
// ignoreRect is set and the explicit size in `slot` is 0 — the one case the
|
||||
// engine answers from the rect instead.
|
||||
void PushExtent(void *L, bool ignoreRect, int slot, uintptr_t getter) {
|
||||
if (ignoreRect) {
|
||||
void *self = Game::Lua::ResolveRegion(L);
|
||||
if (self == nullptr)
|
||||
return;
|
||||
void *layout = static_cast<uint8_t *>(self) + Offsets::OFF_REGION_ANCHOR;
|
||||
auto *vtable = Game::Read<uintptr_t *>(layout, 0);
|
||||
if (reinterpret_cast<LayoutExtent_t>(vtable[slot / sizeof(uintptr_t)])(layout) == 0.0f) {
|
||||
Game::Lua::PushNumber(L, 0.0);
|
||||
return;
|
||||
}
|
||||
// 4.3.4's Script_GetWidth / Script_GetHeight for `self` (index 1): the
|
||||
// explicit size from layout `slot`, or when it is 0 and ignoreRect is not set,
|
||||
// the rect's span over the layout scale; pushed in script units. 1.12's own
|
||||
// getters (FUN_SCRIPT_REGION_GETWIDTH / _GETHEIGHT) leave out the division,
|
||||
// so an anchored region under a UI scale reads scaled: a 160-high track at
|
||||
// scale 0.91 answered 145.6 while its GetTop - GetBottom was 160. Their other
|
||||
// steps, and the edge getters' division, are the engine's.
|
||||
void PushExtent(void *L, bool ignoreRect, int slot, bool vertical) {
|
||||
void *self = Game::Lua::ResolveRegion(L);
|
||||
if (self == nullptr)
|
||||
return;
|
||||
void *layout = static_cast<uint8_t *>(self) + Offsets::OFF_REGION_ANCHOR;
|
||||
auto *vtable = Game::Read<uintptr_t *>(layout, 0);
|
||||
float extent = reinterpret_cast<LayoutExtent_t>(vtable[slot / sizeof(uintptr_t)])(layout);
|
||||
if (extent == 0.0f && !ignoreRect) {
|
||||
if (reinterpret_cast<LayoutDirty_t>(Offsets::FUN_REGION_LAYOUT_DIRTY)(layout) != 0)
|
||||
reinterpret_cast<LayoutResize_t>(Offsets::FUN_LAYOUT_RESIZE)(layout, nullptr, 1);
|
||||
float rect[4] = {0.0f, 0.0f, 0.0f, 0.0f}; // {bottom, left, top, right}
|
||||
if (reinterpret_cast<LayoutGetRect_t>(Offsets::FUN_REGION_GET_RECT)(layout, nullptr, rect) != 0)
|
||||
extent = (vertical ? rect[2] - rect[0] : rect[3] - rect[1]) /
|
||||
Game::Read<float>(self, Offsets::OFF_REGION_EFFECTIVE_SCALE);
|
||||
}
|
||||
const int top = Game::Lua::GetTop(L);
|
||||
Game::Lua::PushValue(L, 1);
|
||||
CallScript(getter, L); // (.., self, value)
|
||||
Game::Lua::Remove(L, top + 1);
|
||||
Game::Lua::PushNumber(L, Game::InternalToPixel(extent));
|
||||
}
|
||||
|
||||
// `region:GetWidth([ignoreRect])` / `region:GetHeight([ignoreRect])`.
|
||||
int __fastcall Script_GetWidth(void *L) {
|
||||
const bool ignoreRect = Game::Lua::ToBoolean(L, 2) != 0;
|
||||
Game::Lua::SetTop(L, 1);
|
||||
PushExtent(L, ignoreRect, Offsets::VTBL_LAYOUT_GET_WIDTH, Offsets::FUN_SCRIPT_REGION_GETWIDTH);
|
||||
PushExtent(L, ignoreRect, Offsets::VTBL_LAYOUT_GET_WIDTH, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int __fastcall Script_GetHeight(void *L) {
|
||||
const bool ignoreRect = Game::Lua::ToBoolean(L, 2) != 0;
|
||||
Game::Lua::SetTop(L, 1);
|
||||
PushExtent(L, ignoreRect, Offsets::VTBL_LAYOUT_GET_HEIGHT,
|
||||
Offsets::FUN_SCRIPT_REGION_GETHEIGHT);
|
||||
PushExtent(L, ignoreRect, Offsets::VTBL_LAYOUT_GET_HEIGHT, true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -140,9 +146,8 @@ int __fastcall Script_GetHeight(void *L) {
|
||||
int __fastcall Script_GetSize(void *L) {
|
||||
const bool ignoreRect = Game::Lua::ToBoolean(L, 2) != 0;
|
||||
Game::Lua::SetTop(L, 1); // (self)
|
||||
PushExtent(L, ignoreRect, Offsets::VTBL_LAYOUT_GET_WIDTH, Offsets::FUN_SCRIPT_REGION_GETWIDTH);
|
||||
PushExtent(L, ignoreRect, Offsets::VTBL_LAYOUT_GET_HEIGHT,
|
||||
Offsets::FUN_SCRIPT_REGION_GETHEIGHT);
|
||||
PushExtent(L, ignoreRect, Offsets::VTBL_LAYOUT_GET_WIDTH, false);
|
||||
PushExtent(L, ignoreRect, Offsets::VTBL_LAYOUT_GET_HEIGHT, true);
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -234,7 +239,8 @@ int __fastcall Script_IsMouseOver(void *L) {
|
||||
// ---- GetRect (Region base — all region types) ------------------------------
|
||||
|
||||
// `region:GetRect()` → left, bottom, width, height. Composed from the
|
||||
// engine's own Region getters (so all UI-scale conversion is its code).
|
||||
// engine's edge getters and this file's GetWidth / GetHeight (the engine's
|
||||
// leave the layout scale in a rect-derived size).
|
||||
// Matches retail: returns nothing for a region with no resolved rect —
|
||||
// gated on GetLeft, which pushes nil when the position isn't resolved (a
|
||||
// size-only region with no anchor has width/height but no rect).
|
||||
@@ -243,8 +249,8 @@ int __fastcall Script_GetRect(void *L) {
|
||||
if (!CallRegionNumber(L, Offsets::FUN_SCRIPT_REGION_GETLEFT, &left))
|
||||
return 0;
|
||||
CallRegionNumber(L, Offsets::FUN_SCRIPT_REGION_GETBOTTOM, &bottom);
|
||||
CallRegionNumber(L, Offsets::FUN_SCRIPT_REGION_GETWIDTH, &width);
|
||||
CallRegionNumber(L, Offsets::FUN_SCRIPT_REGION_GETHEIGHT, &height);
|
||||
CallRegionNumber(L, reinterpret_cast<uintptr_t>(&Script_GetWidth), &width);
|
||||
CallRegionNumber(L, reinterpret_cast<uintptr_t>(&Script_GetHeight), &height);
|
||||
Game::Lua::SetTop(L, 1);
|
||||
Game::Lua::PushNumber(L, left);
|
||||
Game::Lua::PushNumber(L, bottom);
|
||||
@@ -263,8 +269,8 @@ int __fastcall Script_GetScaledRect(void *L) {
|
||||
if (!CallRegionNumber(L, Offsets::FUN_SCRIPT_REGION_GETLEFT, &left))
|
||||
return 0;
|
||||
CallRegionNumber(L, Offsets::FUN_SCRIPT_REGION_GETBOTTOM, &bottom);
|
||||
CallRegionNumber(L, Offsets::FUN_SCRIPT_REGION_GETWIDTH, &width);
|
||||
CallRegionNumber(L, Offsets::FUN_SCRIPT_REGION_GETHEIGHT, &height);
|
||||
CallRegionNumber(L, reinterpret_cast<uintptr_t>(&Script_GetWidth), &width);
|
||||
CallRegionNumber(L, reinterpret_cast<uintptr_t>(&Script_GetHeight), &height);
|
||||
Game::Lua::SetTop(L, 1);
|
||||
Game::Lua::PushNumber(L, left * scale);
|
||||
Game::Lua::PushNumber(L, bottom * scale);
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
// `point`, nothing when that slot is empty
|
||||
// region:ClearPoint(point) removes the anchor at `point`
|
||||
// region:IsRectValid() true when the region has a rect
|
||||
// region:AdjustPointsOffset(x, y) adds (x, y) to every anchor's offset
|
||||
// region:ClearPointsOffset() sets every anchor's offset to 0
|
||||
//
|
||||
// A layout keeps one anchor per point, in the slot of that point's index
|
||||
// (benilla-re ui/anchors "Point slots"), so a point name selects a slot
|
||||
@@ -28,6 +30,13 @@
|
||||
// with 1, and NULL the slot. Like ClearAllPoints it does not lay the region
|
||||
// out; the next anchor or size change does.
|
||||
//
|
||||
// AdjustPointsOffset and ClearPointsOffset re-set each anchor through the
|
||||
// engine's SetPoint (FUN_REGION_SET_POINT) with the anchor's own relative
|
||||
// frame and point and the new offset, so the region lays out as a script
|
||||
// SetPoint would. Retail, probed: SetPoint("CENTER", 5, 7), then
|
||||
// AdjustPointsOffset(1, -1) reads back (6, 6), and ClearPointsOffset then
|
||||
// reads back (0, 0) - it zeroes the offsets, it does not undo adjustments.
|
||||
//
|
||||
// IsRectValid takes the steps of the stock edge getters (Script GetLeft):
|
||||
// lay out a pending region now, then answer the rect reader's valid bit, so
|
||||
// it is true exactly when GetLeft answers a number.
|
||||
@@ -49,6 +58,8 @@ using DeleteNode_t = void(__thiscall *)(void *node, int flags);
|
||||
using LayoutDirty_t = int(__fastcall *)(void *layout);
|
||||
using Resize_t = void(__fastcall *)(void *layout, void *edx, int force);
|
||||
using GetRect_t = int(__fastcall *)(void *layout, void *edx, float *out);
|
||||
using SetPoint_t = void(__thiscall *)(void *anchor, int point, void *relAnchor, int relPoint,
|
||||
float x, float y, int flag);
|
||||
|
||||
// The point named by argument 2, or -1 after raising the usage error.
|
||||
int PointArg(void *L, void *region, const char *usage) {
|
||||
@@ -98,6 +109,47 @@ int __fastcall Script_ClearPoint(void *L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Re-sets every anchor with its offset moved by (dx, dy) in layout units, or
|
||||
// with no offset when `zero`.
|
||||
void ShiftPoints(void *region, float dx, float dy, bool zero) {
|
||||
Frame::GetPointGuard::CleanDanglingAnchors(region);
|
||||
for (int point = 0; point < Offsets::REGION_ANCHOR_SLOT_COUNT; ++point) {
|
||||
uint8_t *node = Game::Read<uint8_t *>(region, Offsets::OFF_REGION_ANCHOR_ARRAY + point * 4);
|
||||
if (node == nullptr)
|
||||
continue;
|
||||
auto *vtable = Game::Read<uintptr_t *>(node, 0);
|
||||
void *relative = reinterpret_cast<GetRelative_t>(
|
||||
vtable[Offsets::VTBL_ANCHOR_NODE_GET_RELATIVE / sizeof(uintptr_t)])(node);
|
||||
const int relativePoint = Game::Read<int>(node, Offsets::OFF_ANCHOR_NODE_RELATIVE_POINT);
|
||||
const float x = zero ? 0.0f : Game::Read<float>(node, Offsets::OFF_ANCHOR_NODE_X) + dx;
|
||||
const float y = zero ? 0.0f : Game::Read<float>(node, Offsets::OFF_ANCHOR_NODE_Y) + dy;
|
||||
reinterpret_cast<SetPoint_t>(Offsets::FUN_REGION_SET_POINT)(
|
||||
static_cast<uint8_t *>(region) + Offsets::OFF_REGION_ANCHOR, point, relative,
|
||||
relativePoint, x, y, 1);
|
||||
}
|
||||
}
|
||||
|
||||
int __fastcall Script_AdjustPointsOffset(void *L) {
|
||||
void *region = Game::Lua::ResolveRegion(L);
|
||||
if (region == nullptr)
|
||||
return 0;
|
||||
if (!Game::Lua::IsNumber(L, 2) || !Game::Lua::IsNumber(L, 3)) {
|
||||
Game::Lua::Error(L, "Usage: %s:AdjustPointsOffset(x, y)", Game::Lua::ObjectDisplayName(region));
|
||||
return 0;
|
||||
}
|
||||
ShiftPoints(region, Game::PixelToInternal(static_cast<float>(Game::Lua::ToNumber(L, 2))),
|
||||
Game::PixelToInternal(static_cast<float>(Game::Lua::ToNumber(L, 3))), false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __fastcall Script_ClearPointsOffset(void *L) {
|
||||
void *region = Game::Lua::ResolveRegion(L);
|
||||
if (region == nullptr)
|
||||
return 0;
|
||||
ShiftPoints(region, 0.0f, 0.0f, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __fastcall Script_IsRectValid(void *L) {
|
||||
void *region = Game::Lua::ResolveRegion(L);
|
||||
if (region == nullptr)
|
||||
@@ -115,6 +167,8 @@ const Game::Lua::FrameMethodEntry g_methods[] = {
|
||||
{"GetPointByName", &Script_GetPointByName},
|
||||
{"ClearPoint", &Script_ClearPoint},
|
||||
{"IsRectValid", &Script_IsRectValid},
|
||||
{"AdjustPointsOffset", &Script_AdjustPointsOffset},
|
||||
{"ClearPointsOffset", &Script_ClearPointsOffset},
|
||||
};
|
||||
|
||||
const Game::Doc::Field kPointArg[] = {
|
||||
@@ -138,7 +192,16 @@ const Game::Doc::Function kClearPoint{"Removes the region's anchor at a point.",
|
||||
const Game::Doc::Function kIsRectValid{
|
||||
"Returns true when the region's position and size are resolved from its anchors.",
|
||||
{}, kValidRets};
|
||||
const Game::Doc::Field kOffsetArgs[] = {
|
||||
Game::Doc::Req("x", "uiUnit"),
|
||||
Game::Doc::Req("y", "uiUnit"),
|
||||
};
|
||||
const Game::Doc::Function kAdjustPointsOffset{"Adds an offset to every anchor of the region.", kOffsetArgs,
|
||||
{}};
|
||||
const Game::Doc::Function kClearPointsOffset{"Sets the offset of every anchor of the region to 0.", {}, {}};
|
||||
const Game::Doc::Method g_methodDocs[] = {
|
||||
{"AdjustPointsOffset", &kAdjustPointsOffset},
|
||||
{"ClearPointsOffset", &kClearPointsOffset},
|
||||
{"GetPointByName", &kGetPointByName},
|
||||
{"ClearPoint", &kClearPoint},
|
||||
{"IsRectValid", &kIsRectValid},
|
||||
|
||||
@@ -40,6 +40,12 @@
|
||||
// Textures and font strings have no record of their own; Get walks to their
|
||||
// parent frame — retail's behavior for script-created regions.
|
||||
//
|
||||
// AddSourceLocationExclude(fileName) names a script file whose levels the
|
||||
// walk steps over, so a frame a factory file makes (retail's FrameFactory.lua
|
||||
// and Pools.lua exclude themselves) is the caller's. A level matches when its
|
||||
// file name, the part of its path after the last separator, is fileName,
|
||||
// ignoring case. The names are dropped with the Lua state that added them.
|
||||
//
|
||||
// Always on: retail gates the file-name lookup behind the
|
||||
// enableSourceLocationLookup CVar ("UnknownFile" otherwise), a cost its Lua
|
||||
// has and 5.0's two field reads do not.
|
||||
@@ -56,6 +62,7 @@
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Frame::SourceLocation {
|
||||
|
||||
@@ -78,8 +85,23 @@ constexpr int kMaxScopes = 32; // CreateFrame -> OnLoad -> CreateFrame nests sha
|
||||
Scope g_scopes[kMaxScopes];
|
||||
int g_scopeDepth = 0;
|
||||
|
||||
std::vector<std::string> g_excludes;
|
||||
|
||||
bool Excluded(const char *source) {
|
||||
if (g_excludes.empty())
|
||||
return false;
|
||||
const char *name = source;
|
||||
for (const char *p = source; *p != '\0'; ++p)
|
||||
if (*p == '\\' || *p == '/')
|
||||
name = p + 1;
|
||||
for (const std::string &e : g_excludes)
|
||||
if (Game::StrCmpI(name, e.c_str()) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// "file:line" of the nearest Lua function above the running C function on
|
||||
// L's call stack. False when there is none.
|
||||
// L's call stack that is not in an excluded file. False when there is none.
|
||||
bool CaptureCaller(void *L, char *out, size_t size) {
|
||||
auto getStack = reinterpret_cast<GetStack_t>(Offsets::LUA_GET_STACK);
|
||||
auto getInfo = reinterpret_cast<GetInfo_t>(Offsets::LUA_GET_INFO);
|
||||
@@ -93,6 +115,8 @@ bool CaptureCaller(void *L, char *out, size_t size) {
|
||||
const char *source = Game::Read<const char *>(ar, Offsets::OFF_LUA_DEBUG_SOURCE);
|
||||
if (source == nullptr)
|
||||
return false;
|
||||
if (Excluded(source))
|
||||
continue;
|
||||
char name[256];
|
||||
reinterpret_cast<ChunkId_t>(Offsets::LUAO_CHUNKID)(name, source, sizeof(name));
|
||||
std::snprintf(out, size, "%s:%d", name, line);
|
||||
@@ -138,10 +162,34 @@ int __fastcall Script_GetSourceLocation(void *L) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int __fastcall Script_AddSourceLocationExclude(void *L) {
|
||||
if (!Game::Lua::IsString(L, 1)) {
|
||||
Game::Lua::Error(L, "Usage: AddSourceLocationExclude(fileName)");
|
||||
return 0;
|
||||
}
|
||||
const char *name = Game::Lua::ToString(L, 1);
|
||||
for (const std::string &e : g_excludes)
|
||||
if (Game::StrCmpI(name, e.c_str()) == 0)
|
||||
return 0;
|
||||
g_excludes.emplace_back(name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PrepareForReload() { g_excludes.clear(); }
|
||||
|
||||
const Game::ReloadAutoRegister _reload{&PrepareForReload};
|
||||
|
||||
const Game::Lua::FrameMethodEntry g_methods[] = {
|
||||
{"GetSourceLocation", &Script_GetSourceLocation},
|
||||
};
|
||||
|
||||
const Game::Doc::Field kExcludeArgs[] = {
|
||||
Game::Doc::Req("fileName", "string", "A script file name, such as \"FrameFactory.lua\"."),
|
||||
};
|
||||
const Game::Doc::Function kAddSourceLocationExclude{
|
||||
"Makes GetSourceLocation skip the named script file and report the code that called into it.",
|
||||
kExcludeArgs, {}, "FrameScriptGlobals"};
|
||||
|
||||
const Game::Doc::Field kRets[] = {
|
||||
Game::Doc::Opt("location", "string", nullptr,
|
||||
"\"file:line\" of the CreateFrame call that made the frame, or the XML file "
|
||||
@@ -156,6 +204,8 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterGlobalFunction("AddSourceLocationExclude", &Script_AddSourceLocationExclude,
|
||||
&kAddSourceLocationExclude);
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
|
||||
@@ -567,4 +567,17 @@ void PrepareForReload() {
|
||||
g_misses.clear();
|
||||
}
|
||||
|
||||
void SetFromXml(void *region, const char *name, bool useAtlasSize) {
|
||||
void *L = Game::Lua::State();
|
||||
if (L == nullptr || region == nullptr || name == nullptr || *name == '\0')
|
||||
return;
|
||||
const int top = Game::Lua::GetTop(L);
|
||||
Game::Lua::PushCClosure(L, &Script_SetAtlas, 0);
|
||||
Game::Lua::PushObject(L, region);
|
||||
Game::Lua::PushString(L, name);
|
||||
Game::Lua::PushBool(L, useAtlasSize);
|
||||
Game::Lua::PCall(L, 3, 0, 0);
|
||||
Game::Lua::SetTop(L, top);
|
||||
}
|
||||
|
||||
} // namespace Texture::Atlas
|
||||
|
||||
@@ -62,4 +62,9 @@ void RecordMiss(const char *name);
|
||||
// across the gap.
|
||||
void PrepareForReload();
|
||||
|
||||
// The XML `<Texture atlas="name" useAtlasSize="true">`: texture:SetAtlas(name,
|
||||
// useAtlasSize) on `region`, run as its own Lua call so the caller's stack is
|
||||
// left alone.
|
||||
void SetFromXml(void *region, const char *name, bool useAtlasSize);
|
||||
|
||||
} // namespace Texture::Atlas
|
||||
|
||||
+2
-12
@@ -53,7 +53,6 @@ namespace {
|
||||
|
||||
using StringToPoint_t = int(__fastcall *)(const char *name, int *point);
|
||||
using ReadDimension_t = int(__fastcall *)(const void *node, float *x, float *y, void *status);
|
||||
using SetAttribute_t = void(__thiscall *)(void *node, const char *name, const char *value);
|
||||
using GetRelative_t = void *(__thiscall *)(void *anchorNode);
|
||||
using SetPoint_t = void(__thiscall *)(void *anchor, int point, void *relAnchor, int relPoint,
|
||||
float x, float y, int flag);
|
||||
@@ -151,20 +150,11 @@ bool ApplyAnchor(void *object, const void *anchor, void *status) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// An attribute the anchor leaves out is 0; giving it "0" keeps the reader
|
||||
// from reporting a missing dimension when the other one is present.
|
||||
// An attribute the anchor leaves out is 0 (Xml::Dimension reads one alone).
|
||||
float x = 0.0f, y = 0.0f;
|
||||
const void *dimension = inlineOffset ? anchor : FindChild(anchor, "Offset");
|
||||
if (dimension != nullptr) {
|
||||
if (inlineOffset) {
|
||||
auto setAttr = reinterpret_cast<SetAttribute_t>(Offsets::FUN_XML_NODE_SET_ATTRIBUTE);
|
||||
if (!HasValue(xs))
|
||||
setAttr(const_cast<void *>(anchor), "x", "0");
|
||||
if (!HasValue(ys))
|
||||
setAttr(const_cast<void *>(anchor), "y", "0");
|
||||
}
|
||||
if (dimension != nullptr)
|
||||
reinterpret_cast<ReadDimension_t>(Offsets::FUN_XML_READ_DIMENSION)(dimension, &x, &y, status);
|
||||
}
|
||||
|
||||
reinterpret_cast<SetPoint_t>(Offsets::FUN_REGION_SET_POINT)(
|
||||
Game::Ptr<void>(object, Offsets::OFF_REGION_ANCHOR), point, relAnchor, relativePoint, x,
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// 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/>.
|
||||
|
||||
// A dimension element with only `x`: `<Size x="18"/>`, `<Anchor point="LEFT"
|
||||
// x="2"/>`.
|
||||
//
|
||||
// The engine's reader (FUN_XML_READ_DIMENSION) stores `x` and `y` from the
|
||||
// node's attributes, then decides whether it found any by looking at the `y`
|
||||
// attribute alone: with no `y` and no child element it reports "No x or y
|
||||
// attributes in element" and answers 0, so the frame loader never applies the
|
||||
// size it just read. 4.3.4's Dimension::Load answers true when either
|
||||
// attribute is set. The hook makes that call for a node with `x`, no `y` and
|
||||
// no child: the engine's reader runs as before, writing the converted `x` and
|
||||
// a 0 `y`, with its warning sent to a sink that drops it, and the answer is 1.
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "xml/FrameLoad.h"
|
||||
|
||||
namespace Xml::Dimension {
|
||||
|
||||
namespace {
|
||||
|
||||
using ReadDimension_t = int(__fastcall *)(const void *node, float *x, float *y, void *status);
|
||||
using StatusAdd_t = void(__cdecl *)(void *status, int level, const char *fmt, ...);
|
||||
|
||||
ReadDimension_t g_orig = nullptr;
|
||||
|
||||
void __cdecl IgnoreStatus(void * /*status*/, int /*level*/, const char * /*fmt*/, ...) {}
|
||||
|
||||
// A status object the reader can report to: VTBL_XML_STATUS_ADD is the only
|
||||
// slot it calls.
|
||||
const StatusAdd_t kSilentVtable[Offsets::VTBL_XML_STATUS_ADD / sizeof(void *) + 1] = {
|
||||
nullptr, nullptr, nullptr, &IgnoreStatus};
|
||||
static_assert(Offsets::VTBL_XML_STATUS_ADD / sizeof(void *) == 3, "the status Add slot");
|
||||
struct SilentStatus {
|
||||
const StatusAdd_t *vtable = kSilentVtable;
|
||||
};
|
||||
|
||||
bool HasValue(const char *s) { return s != nullptr && *s != '\0'; }
|
||||
|
||||
int __fastcall ReadDimension_h(const void *node, float *x, float *y, void *status) {
|
||||
if (node != nullptr && HasValue(Attr(node, "x")) && Attr(node, "y") == nullptr &&
|
||||
FirstChild(node) == nullptr) {
|
||||
SilentStatus silent;
|
||||
g_orig(node, x, y, &silent);
|
||||
return 1;
|
||||
}
|
||||
return g_orig(node, x, y, status);
|
||||
}
|
||||
|
||||
const Game::HookAutoRegister _hook{Offsets::FUN_XML_READ_DIMENSION, reinterpret_cast<void *>(&ReadDimension_h),
|
||||
reinterpret_cast<void **>(&g_orig)};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace Xml::Dimension
|
||||
+27
-4
@@ -23,7 +23,10 @@
|
||||
//
|
||||
// Each entry is `object[key] = value`. `type` and `keyType` (default
|
||||
// "string") are one of nil / boolean / number / string / global, the later
|
||||
// clients' KEYVALUETYPE; "global" is `_G[text]`. Booleans and numbers go
|
||||
// clients' KEYVALUETYPE; "global" is `_G[text]`, walking a dotted path a
|
||||
// field at a time: retail's own templates use
|
||||
// "ScrollControllerMixin.Directions.Decrease". A step that is not a table
|
||||
// gives nil. Booleans and numbers go
|
||||
// through the same loader helpers as `<Attributes>` (FUN_XML_ATTR_TO_BOOL,
|
||||
// FUN_SSTR_TO_FLOAT).
|
||||
//
|
||||
@@ -42,6 +45,8 @@
|
||||
#include "baselib/Ascii.h"
|
||||
#include "xml/FrameLoad.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace Xml::KeyValues {
|
||||
|
||||
namespace {
|
||||
@@ -75,10 +80,28 @@ void Push(void *L, Kind kind, const char *text) {
|
||||
case Kind::String:
|
||||
Game::Lua::PushString(L, text);
|
||||
break;
|
||||
case Kind::Global:
|
||||
Game::Lua::PushString(L, text);
|
||||
Game::Lua::GetTable(L, Game::Lua::GLOBALS_INDEX);
|
||||
case Kind::Global: {
|
||||
Game::Lua::PushValue(L, Game::Lua::GLOBALS_INDEX);
|
||||
const char *start = text;
|
||||
for (;;) {
|
||||
const char *dot = std::strchr(start, '.');
|
||||
if (Game::Lua::Type(L, -1) != Game::Lua::TYPE_TABLE) {
|
||||
Game::Lua::SetTop(L, -2);
|
||||
Game::Lua::PushNil(L);
|
||||
break;
|
||||
}
|
||||
if (dot == nullptr)
|
||||
Game::Lua::PushString(L, start);
|
||||
else
|
||||
Game::Lua::PushLString(L, start, static_cast<unsigned>(dot - start));
|
||||
Game::Lua::GetTable(L, -2);
|
||||
Game::Lua::Remove(L, -2);
|
||||
if (dot == nullptr)
|
||||
break;
|
||||
start = dot + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Game::Lua::PushNil(L);
|
||||
break;
|
||||
|
||||
@@ -87,13 +87,16 @@
|
||||
// builder finalizes, so OnLoad sees the groups.
|
||||
//
|
||||
// The texture loader also reads `mask="path"` (`<Texture file=".." mask=".."/>`,
|
||||
// the later-client attribute) and applies it as texture:SetMask(path).
|
||||
// the later-client attribute) and applies it as texture:SetMask(path), and
|
||||
// `atlas="name"` with `useAtlasSize`, applied as texture:SetAtlas(name,
|
||||
// useAtlasSize).
|
||||
|
||||
#include "xml/FrameLoad.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "animation/Xml.h"
|
||||
#include "texture/Atlas.h"
|
||||
#include "texture/Mask.h"
|
||||
#include "xml/Anchors.h"
|
||||
#include "xml/KeyValues.h"
|
||||
@@ -329,6 +332,11 @@ uintptr_t __fastcall TextureLoad_h(void *self, void *edx, const void *node, void
|
||||
Animation::Xml::LoadAnimations(RegionObject(self), node, status);
|
||||
if (const char *mask = Xml::Attr(node, "mask"); mask != nullptr && *mask != '\0')
|
||||
Texture::Mask::SetPath(RegionObject(self), mask);
|
||||
if (const char *atlas = Xml::Attr(node, "atlas"); atlas != nullptr && *atlas != '\0') {
|
||||
const char *useSize = Xml::Attr(node, "useAtlasSize");
|
||||
Texture::Atlas::SetFromXml(RegionObject(self), atlas,
|
||||
useSize != nullptr && Game::StringToFlag(useSize));
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user