Compare commits

...
7 Commits
Author SHA1 Message Date
Brues a106d86969 fix(unit): consult the name cache only when no unit object is held
UnitClassBase and UnitRaceBase now follow stock Script_UnitClass
(0x00518350) and Script_UnitRace (0x00518200) branch for branch.
"player" reads the selected-character record; any other token goes
through GetGUIDFromName + ClntObjMgrObjectPtr(TYPEMASK_UNIT) and reads
the held object's descriptor byte (+0x79 class, +0x78 race). The
NameCache record (+0x140 / +0x138, null callback) is peeked only when
no object is held.

Before, the NameCache was also peeked when a held object's byte was 0.
That never changed a result (creatures are not in the NameCache) but
resolved the token twice and did not match the engine's branch.

The comments and API.md also claimed creatures return nil, that class
IDs run 1-9, and that UnitClass has a third return. None of that is
true: creatures go through the same ChrClasses/ChrRaces lookup as in
UnitClass/UnitRace, Druid is 11, and stock returns exactly two values.
2026-11-06 11:52:58 -06:00
Brues fac3be3a0b refactor(lua): take integer enum and constants entries as arrays
RegisterIntegerEnum and RegisterConstants took their entries with a
count beside them, written as sizeof(t) / sizeof(t[0]), std::size, or
a literal. They now take Game::ArrayRef<EnumIntegerEntry>, as
RegisterFrameMethods does, so the count comes from the array's type.
The documentation recorders RecordEnum and RecordConstants take the
same view.

All 34 call sites pass only the array. The six literal counts were
checked against their arrays first: each was the full length, so every
Enum.* and Constants.* table gets the same entries as before. Five
files that included <iterator> only for std::size drop it.
2026-11-06 11:34:27 -06:00
Brues b49c107778 refactor(lua): take frame-method tables as arrays, not pointer and count
Every RegisterFrameMethods call passed its method table and doc table
with a count beside each: sizeof(t) / sizeof(t[0]), std::size, or a
literal. The literals had to be kept in step with the tables by hand.

Game::ArrayRef<T> is a view over a file-scope array that takes the
count from the array's type, the way Doc::FieldList already did for a
function's arguments and returns. FieldList is now ArrayRef<Field>,
Doc::MethodList is ArrayRef<Method>, and RegisterFrameMethods takes
(context, methods, docs). The table and count still go to the engine's
frame-method iterator (FUN_REGISTER_FRAME_METHODS) unchanged, and to
the documentation recorder.

All 84 call sites pass only the arrays. The 16 literal counts were
checked against their tables first: each was the full length, so every
registry gets the same methods as before.
2026-11-06 11:27:22 -06:00
Brues 53b7f79440 fix(model): release dress builders and stopped effects with their frame
/reload crashed with an access violation in the model release
(0x007103A3, inside FUN_MODEL_INSTANCE_RELEASE 0x007103A0) when
SetDisplayInfo had dressed a few panes.

1.12.1 reloads in two steps. CGGameUI::Shutdown (0x00490BD0) deletes
the CSimpleTop, which destroys every frame. A Model frame's teardown,
DestroyModelFrame (0x0076CA50, from the CSimpleModel and
CGCharacterModelBase destructors), releases the frame's M2 scene at
+0x314, and the scene frees every model in it, even one that something
else still holds a reference on. Only then does CGGameUI::Initialize
(0x0048FBF0) reset the Lua state, which runs FrameScript_Initialize
and our reload cleanups. DisplayInfo's cleanup destroyed each kept
compositor there (FUN_CHARCOMP_DESTROY 0x00476000), and the compositor
released its reference to a model that was already freed. VisualKit's
cleanup detached and released stopped one-shot effects that live in
the same freed scene.

Model::Teardown hooks DestroyModelFrame and runs its subscribers
before the original, while the frame's scene and models are intact,
which is where the engine frees what its own frames hold. DisplayInfo
drops the frame's dress job there, and VisualKit records each stopped
effect's pane (in the done callback's user data) and discards that
pane's effects there. Their reload cleanups keep only the
pointer-keyed state.

The ReloadAutoRegister doc now says that the cleanups run after the
frames are gone, so they may forget pointers but never release an
engine object tied to a frame.
2026-11-06 11:16:47 -06:00
Brues 7b21453469 refactor(model): share the engine model calls across the model modules
The model modules each declared their own pointers to the same 1.12.1
engine functions: the instance gates FUN_MODEL_INSTANCE_LOADED
(0x007103D0) and FUN_MODEL_INSTANCE_READY, AddRef / Release
(0x00710390 / 0x007103A0), FUN_M2SCENE_CREATE_MODEL,
FUN_M2MODEL_PLAY_ANIMATION, the done callback, the item visuals, the
render callback, the M2 data lookup FUN_M2_LOOKUP_INDEX (0x00710310),
and the pane's model, scene, SetModelInstance virtual (vtable +0x90)
and SetDisplay. model/M2.h now holds one typed wrapper for each, and
every module calls through it.

- Game::Lua::ResolvePlayerModel / ResolveDressUpModel join the
  per-type resolvers, replacing four ResolvePane copies and five
  inline ResolveTypedObject calls. Game::Lua::TYPE_NONE replaces the
  local kTypeNone in Rotation and frame/CreateFrame.
- Offsets.h has the full INVTYPE list, 0..28, named by the engine's
  own key table at 0x0083DDB0. It replaces the four scattered
  INVTYPE_* entries and the local copies in DressUp and ItemDisplay,
  and the try-on result switch reads by name.
- DisplayInfo keeps its dress jobs in a map by frame, like the other
  modules, in place of the fixed 8-slot pool and its eviction.
- DressUp names the held lane's bytes (item class, inventory type,
  sheathe type) and detaches a lane in one place.
- ItemDisplay::OwnVisual is the one test for a display's own
  ItemVisuals row (GetItemDescVisual 0x0062EC70), used by SetItem and
  by TryOn's enchant glow. Both own-record panes share
  Pane::DisplayRecord, and the SetDisplayInfo records are cleared on
  /reload as the SetItem records are.
2026-11-06 11:11:18 -06:00
shikuljaandBrues ebfd9d034d feat(localization): complete and correct the Russian locale (#130)
* feat(localization): complete and correct the Russian locale

The Russian locale was 99 keys behind enUS, so a Russian client kept the
English strings for ClassicAPI's own options: the AddOn Options title and
its game-menu button, every page name, header, control label and tooltip,
the AoE Loot binding, and the generic strings the options pages gained
(FOCUS_TOKEN_NOT_FOUND, LUA_ERROR, RELOADUI, PLAYER_LIST_DELIMITER,
SYMBOLIC_NEGATIVE_NUMBER, SYMBOLIC_POSITIVE_NUMBER and the money texture
formats). All 327 keys are now present, in enUS order, so the two files
diff key by key.

Focus follows the client's modern terms — «Взять в фокус», «Сбросить
фокус», «Выбрать фокус» and «Взять цель в фокус» — matching the existing
/фокус and /сбросфокуса aliases. The rest uses the 1.12 Russian client's
vocabulary where it has one (модификация, цель, панель команд, время
восстановления, табличка с именем) and «динамическое выделение» /
«мягкая цель» for action targeting. The money strings keep their |4
plural forms, which GetCoinText expects.

Fixes:

- LARGE_NUMBER_SEPERATOR was "", which turned digit grouping off. It is
  now a space, as in the tooltip's 1 234 567.
- FIRST/SECOND_NUMBER_CAP and their _NO_SPACE twins carry the leading
  space, because Abbreviate concatenates the number and the suffix
  directly.
- EQUIPMENT_MANAGER_ITEMS_MISSING_TOOLTIP printed a slot number where
  the missing count belongs; it now takes the missing count.
- SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL was missing a space before
  «или нажмите».
- SET_FRIENDNOTE_LABEL had a |3-5 declension token, which 1.12 renders
  literally.
- ITEM_SLOTS_IGNORED and ITEMS_NOT_IN_INVENTORY had wrong third plural
  forms, ITEM_MOD_HIT_MELEE_RATING_SHORT had a double space, and the flat
  stat labels (Защита, Уклонение, Парирование, Блок) read wrong after a
  signed value, so they use the same «к …» form as the other ITEM_MOD
  labels.

The TOC gains ## Notes-ruRU next to ## Notes.

* fix(localization): restore retail ruRU focus, number and log strings

Follow-up to the Russian locale completion, which had changed several
strings that the retail ruRU client already translates. Each is now
the retail text, or a correction where the new wording was wrong:

- Focus: the FOCUSTARGET / TARGETFOCUS binding labels were swapped
  (FOCUSTARGET runs FocusUnit("target"), TARGETFOCUS runs
  TargetUnit("focus")). All focus strings use retail ruRU's terms
  again: Запомнить цель, Восстановить цель, Отменить запоминание цели,
  Запомненная цель, <фокус отсутствует>.
- EVENTTRACE_LOG_DISCARD is the message logged after the log is
  discarded; it had become an option-style label. Retail: Журнал
  сброшен. EVENTTRACE_BUTTON_DISCARD_FILTER read "cancel all"; retail:
  Сбросить все.
- FIRST/SECOND_NUMBER_CAP and their _NO_SPACE variants go back to
  retail Т / М. AbbreviateNumbers appends the _NO_SPACE suffix
  directly by design ("1.2K"), so " тыс." widened compact numbers.
- LARGE_NUMBER_SEPERATOR goes back to retail "" (as frFR). A plain
  space also let a number wrap across lines. The separate-large-numbers
  tooltip drops its "1 234 567" example, which no longer applies.
- The loose-target option read "whom the soft target follows". The
  normal target is the one that follows, so it is now "обычная цель",
  and the choices name the soft enemy / soft friend.

* fix(localization): pick language rules by locale name, not 4.3.4's index

1.12.1's locale index (VAR_LOCALE_INDEX, 0xC0E080) is the Wow.ini
Language+Country tag's position in the eight-entry name table at
0x8558A4 (enUS koKR frFR deDE zhCN zhTW esES xxYY), stored once at
0x402562 and 0 when nothing matches. It is never 8. The Russian branches
ported from 4.3.4 switched on 4.3.4's own slot numbers (esMX 7, ruRU 8),
so they could never run: a client whose GetLocale() is "ruRU" fell
through to the default plural rule ("5 предмета") and got no ё/е folding
in bag search.

Localization::Locale::Client() reads the same entry Script_GetLocale
(0x48D8B0) pushes, table[index], and Is(code) compares against it. The
language-rule sites now go through it:

- Text::Grammar's plural index (|4 expansion) keys koKR/frFR/zhCN/zhTW
  and ruRU by name.
- Container::Search picks its accent-folding set by name, as a Folding
  value instead of an index switch.
- Localization::Numbers' ClientIsAsian reuses IsAsianLocale on the name.
- AddOns::TocRewrite drops its private copy of the reader, and the
  slot-8 clamp, which guarded an index that cannot occur.

The DBC column reads keep using the index, which is what it is for.

---------

Co-authored-by: Brues <5278969+brues-code@users.noreply.github.com>
2026-11-06 10:25:00 -06:00
Brues d825bcbdda fix(spell): stand, dismount and unshift only when the cast is sent
autoStand / autoDismount / autoUnshift ran their actions as soon as the
caster-state check (CheckCasterStateForSpell, 0x006094F0) passed. In the
1.12.1 cast request (TryCastSpell, 0x006E4B60) that check comes before
the target arm (0x006E5250), so a cast that then found no target still
dismounted the player and showed "You have no target" (Charge with
nothing targeted). A targeting cursor the player cancelled did the same.

The caster check now only keeps what its rungs noted, and the actions run
at the send. SendCast (0x006E54F0) builds the cast packet; its five
callers all pass the global pending cast: the arm's two immediate sends
and the object / location / trade-slot binders, which the targeting
cursor's click also runs. The arm's only caller is the cast request,
after the caster check, so every send follows a check of the same cast.
Each action runs only if its state still holds, since the player can
stand, dismount or leave the form while the cursor is up. The send
redirects must all apply before the rungs are patched.

Item use had the same ordering gap. Its form refusal (0x005D914D) comes
before the toggle test, which turns off a use aura that is already up
instead of casting (0x005D9157..0x005D9237). Re-using such an item while
shapeshifted dropped the form as well. The refusal now only notes the
unshift; it runs at the bind-on-use test (0x005D91D3), which every path
past the toggle reaches, and the toggle's cancel call clears it.

Fixes #132
2026-11-06 09:56:14 -06:00
124 changed files with 1391 additions and 1191 deletions
+35 -33
View File
@@ -22486,8 +22486,10 @@ the usual error.
| `autoDismount` | If you are mounted, you dismount. |
| `autoUnshift` | If the spell fails with "You are in shapeshift form", you leave the form. |
Each action occurs only when nothing else blocks the cast. `autoDismount` and
`autoUnshift` do nothing on a flight path.
Each action occurs only when nothing else blocks the cast. For example, if the
spell needs a target and you have none, you stay mounted. If the spell shows
the targeting cursor, the action occurs when you select the target. `autoDismount`
and `autoUnshift` do nothing on a flight path.
When `autoUnshift` is `1`, an action button stays usable in a form that the
cast can leave. For example, Healing Touch in Bear Form and Renew in
@@ -25597,10 +25599,9 @@ Notes and limits:
Returns `(classFile, classID)` — the locale-independent class
token plus the numeric classID. The token is one of
`"WARRIOR"`, `"PALADIN"`, `"HUNTER"`, `"ROGUE"`, `"PRIEST"`,
`"SHAMAN"`, `"MAGE"`, `"WARLOCK"`, `"DRUID"`; the classID matches
the integer `UnitClass`'s third return surfaces (1=Warrior,
2=Paladin, 3=Hunter, 4=Rogue, 5=Priest, 7=Shaman, 8=Mage,
9=Warlock, 11=Druid — 6 and 10 are unused here).
`"SHAMAN"`, `"MAGE"`, `"WARLOCK"`, `"DRUID"`. The classID is
1=Warrior, 2=Paladin, 3=Hunter, 4=Rogue, 5=Priest, 7=Shaman,
8=Mage, 9=Warlock, 11=Druid. IDs 6 and 10 are not used.
```lua
local token, id = UnitClassBase("player") -- "WARRIOR", 1
@@ -25608,29 +25609,27 @@ local token = UnitClassBase("target") -- works for any synced unit
local color = RAID_CLASS_COLORS[UnitClassBase("party1")]
```
Addons use the token for class detection because the
`UnitClass(unit)` returns a localized first return (e.g.
`"Krieger"` on a German client), which is fine for display but
breaks any addon code that keys on `if class == "WARRIOR"`. The
classID second return is a ClassicAPI extension — the standard
`UnitClassBase` returns the token only — but it saves callers
from chaining `UnitClass(unit)` just to get the integer.
Use the token for class checks. The first return of
`UnitClass(unit)` is localized (for example, `"Krieger"` on a
German client). It is good for display, but a check such as
`if class == "WARRIOR"` fails on other locales. The classID is a
ClassicAPI extension: the standard `UnitClassBase` returns only
the token.
Reads the class byte from `UNIT_FIELD_BYTES_0` (descriptor `+0x79`,
the same byte `UnitClass`'s general-token path reads) and looks up
the english filename at `ChrClasses.dbc::Filename` (record `+0x38`).
Both fields are broadcast / static, so remote players' class is
current regardless of distance.
`UnitClassBase` finds the unit and its class the same way as
`UnitClass`, so the two functions always agree:
- `"player"` gives a result before the player is in the world
(for example, during addon load).
- Party and raid members that are out of range give a result if
the client has their name data.
- Creatures give the same class as `UnitClass` gives for them.
Returns `(nil, nil)` for:
- `target` with no current target, empty `partyN` / `raidN` slots,
or units whose descriptor isn't loaded yet
- creatures and other non-player units (their class byte indexes a
different table; the class-byte lookup naturally returns nil for
out-of-range / zero values)
Returns `(nil, nil)` when `UnitClass` returns `(nil, nil)`. For
example, `"target"` with no target, or an empty `partyN` / `raidN`
slot.
Throws a Lua error on missing / non-string `unit` argument — same
shape as `UnitClass` itself.
Throws a Lua error if `unit` is missing or is not a string, the
same as `UnitClass`.
### `UnitRaceBase(unit)`
@@ -25644,14 +25643,17 @@ local token, id = UnitRaceBase("player") -- "Human", 1
local token = UnitRaceBase("target") -- works for any synced unit
```
Sibling to [`UnitClassBase`](#unitclassbaseunit). Same problem
(the `UnitRace(unit)` returns a localized name — `"Mensch"`,
`"Orc"`, etc.); same solution (locale-independent token straight
from the DBC). Reads byte 0 of `UNIT_FIELD_BYTES_0` (descriptor
`+0x78`) and looks up `ChrRaces.dbc::Filename` (`+0x3C`).
Use the token for race checks. The first return of
`UnitRace(unit)` is localized (for example, `"Mensch"` on a German
client).
Returns `(nil, nil)` for unresolvable units and non-player units
(creature race bytes don't index `ChrRaces.dbc`).
`UnitRaceBase` finds the unit and its race the same way as
`UnitRace`, so the two functions always agree. The notes for
[`UnitClassBase`](#unitclassbaseunit) about `"player"`,
out-of-range group members and creatures also apply here.
Returns `(nil, nil)` when `UnitRace` returns `(nil, nil)`. Throws a
Lua error if `unit` is missing or is not a string.
### `ClosestUnitPosition(creatureID)`
+16 -11
View File
@@ -252,6 +252,12 @@ void *ResolveEditBox(void *L, int idx, bool raiseError) {
void *ResolveModel(void *L, int idx, bool raiseError) {
return ResolveTypedObject(L, idx, Offsets::VAR_MODEL_LUA_TYPE_ID, raiseError);
}
void *ResolvePlayerModel(void *L, int idx, bool raiseError) {
return ResolveTypedObject(L, idx, Offsets::VAR_PLAYERMODEL_LUA_TYPE_ID, raiseError);
}
void *ResolveDressUpModel(void *L, int idx, bool raiseError) {
return ResolveTypedObject(L, idx, Offsets::VAR_DRESSUPMODEL_LUA_TYPE_ID, raiseError);
}
// Looks up `_G[name]`. If absent, creates a fresh table and binds it.
// Leaves the resulting table on top of the stack.
@@ -361,11 +367,11 @@ void RegisterGlueFunction(const char *name, CFunction func, const Doc::Function
Api::Documentation::RecordGlobal(name, doc);
}
void RegisterFrameMethods(void *context, const FrameMethodEntry *table, int count,
const Doc::Method *docs, int docCount) {
void RegisterFrameMethods(void *context, ArrayRef<FrameMethodEntry> methods,
Doc::MethodList docs) {
auto fn = reinterpret_cast<RegisterFrameMethods_t>(Offsets::FUN_REGISTER_FRAME_METHODS);
fn(table, count, context);
Api::Documentation::RecordFrameMethods(context, table, count, docs, docCount);
fn(methods.data, methods.count, context);
Api::Documentation::RecordFrameMethods(context, methods, docs);
}
// Registers `func` at `_G[tableName][methodName]`. If the namespace
@@ -431,17 +437,16 @@ static bool SetIntegerTable(const char *parent, const char *sub,
return true;
}
void RegisterIntegerEnum(const char *parent, const char *sub,
const EnumIntegerEntry *entries, int count,
void RegisterIntegerEnum(const char *parent, const char *sub, ArrayRef<EnumIntegerEntry> entries,
const char *docSystem) {
if (SetIntegerTable(parent, sub, entries, count))
Api::Documentation::RecordEnum(parent, sub, entries, count, docSystem);
if (SetIntegerTable(parent, sub, entries.data, entries.count))
Api::Documentation::RecordEnum(parent, sub, entries, docSystem);
}
void RegisterConstants(const char *sub, const EnumIntegerEntry *entries, int count,
void RegisterConstants(const char *sub, ArrayRef<EnumIntegerEntry> entries,
const char *docSystem) {
if (SetIntegerTable("Constants", sub, entries, count))
Api::Documentation::RecordConstants(sub, entries, count, docSystem);
if (SetIntegerTable("Constants", sub, entries.data, entries.count))
Api::Documentation::RecordConstants(sub, entries, docSystem);
}
void SetFieldNumber(void *L, const char *key, double value) {
+33 -16
View File
@@ -147,6 +147,18 @@ int StrCmpI(const char *a, const char *b, int n = 0x7FFFFFFF);
// literals + file-scope arrays): no heap, no dynamic init.
// - Summaries are user-facing text: one sentence, plain words, present
// tense, no patch versions.
// A view over a file-scope `const T[]` that takes its count from the array's
// type, so no call site passes one; `{}` means none.
template <typename T>
struct ArrayRef {
const T *data = nullptr;
int count = 0;
constexpr ArrayRef() = default;
template <size_t N>
constexpr ArrayRef(const T (&a)[N]) : data(a), count(static_cast<int>(N)) {}
};
namespace Doc {
struct Field {
@@ -172,14 +184,7 @@ constexpr Field Vararg(const char *type, const char *doc = nullptr) {
return {"...", type, true, nullptr, doc};
}
// A view over a file-scope `const Field[]`; `{}` means none.
struct FieldList {
const Field *data = nullptr;
int count = 0;
constexpr FieldList() = default;
template <size_t N>
constexpr FieldList(const Field (&a)[N]) : data(a), count(static_cast<int>(N)) {}
};
using FieldList = ArrayRef<Field>;
// A registered function. Positional init: `{summary, args, rets}` for a
// table function, `{summary, args, rets, "XGlobals"}` for a global, and
@@ -200,6 +205,7 @@ struct Method {
const char *name;
const Function *doc;
};
using MethodList = ArrayRef<Method>;
// A table shape a function returns or accepts → `Tables{Type="Structure"}`
// in `system`. Self-registering: nothing binds a structure to Lua, so it
@@ -240,7 +246,9 @@ constexpr int UpvalueIndex(int i) { return GLOBALS_INDEX - i; }
// `lua_call` / `lua_pcall` nresults sentinel meaning "all".
constexpr int MULTRET = -1;
// Type tag values returned by `Type()` (lua_type).
// Type tag values returned by `Type()` (lua_type). TYPE_NONE is an absent
// argument (past the top of the stack).
constexpr int TYPE_NONE = -1;
constexpr int TYPE_NIL = 0;
constexpr int TYPE_BOOLEAN = 1;
constexpr int TYPE_LIGHTUSERDATA = 2;
@@ -479,6 +487,8 @@ void *ResolveTexture(void *L, int idx = 1, bool raiseError = true);
void *ResolveFontString(void *L, int idx = 1, bool raiseError = true);
void *ResolveEditBox(void *L, int idx = 1, bool raiseError = true);
void *ResolveModel(void *L, int idx = 1, bool raiseError = true);
void *ResolvePlayerModel(void *L, int idx = 1, bool raiseError = true);
void *ResolveDressUpModel(void *L, int idx = 1, bool raiseError = true);
// Every name these registrars bind is ALSO bound under `_G.ClassicAPI` —
// `ClassicAPI.GetSpellInfo`, `ClassicAPI.C_Item.IsBound` — as an escape
@@ -523,10 +533,11 @@ struct FrameMethodEntry {
// Registers a batch of methods on a per-frame-type registry (e.g.
// GameTooltipMethodRegistry for `tooltip:Foo()` calls). `context` is the
// registry address — see Offsets::VAR_*_METHOD_REGISTRY. `docs` pairs
// descriptors to the entries by NAME (any order, any subset).
void RegisterFrameMethods(void *context, const FrameMethodEntry *table, int count,
const Doc::Method *docs = nullptr, int docCount = 0);
// registry address — see Offsets::VAR_*_METHOD_REGISTRY. `methods` and
// `docs` are file-scope arrays; `docs` pairs descriptors to the entries by
// NAME (any order, any subset).
void RegisterFrameMethods(void *context, ArrayRef<FrameMethodEntry> methods,
Doc::MethodList docs = {});
// Registers `func` at `_G[tableName][methodName]`, creating the namespace
// table if it doesn't already exist. This is how modern WoW C_*-style APIs
@@ -557,14 +568,14 @@ struct EnumIntegerEntry {
// `Enum.AddOnSecurityStatus = { Secure=0, Insecure=1, ... }` shapes.
// `docSystem` is the documentation System the enumeration belongs to
// ("SpellBook", "Item", …); the entries themselves are the documentation.
void RegisterIntegerEnum(const char *parent, const char *sub,
const EnumIntegerEntry *entries, int count,
// `entries` is a file-scope array.
void RegisterIntegerEnum(const char *parent, const char *sub, ArrayRef<EnumIntegerEntry> entries,
const char *docSystem = nullptr);
// Registers `Constants[sub] = { entries }`, Blizzard's engine constant
// tables (`Constants.MoneyFormattingConstants`). Documented as a "Constants"
// table of `docSystem`.
void RegisterConstants(const char *sub, const EnumIntegerEntry *entries, int count,
void RegisterConstants(const char *sub, ArrayRef<EnumIntegerEntry> entries,
const char *docSystem);
// Set `t[key] = value` on the table currently at stack[-1] (the most
@@ -777,6 +788,12 @@ bool RunHookRegistrations();
// issue #33, Frame::Attributes). A pure-C++ cache keyed by spellID/itemID is
// NOT fragile and must not register.
//
// It runs AFTER the frames are gone: CGGameUI::Shutdown destroys every frame
// (and each Model frame's scene with every model in it) before
// CGGameUI::Initialize resets the Lua state. So a cleanup here only FORGETS
// pointers; it never releases an engine object tied to a frame. Release those
// when the frame dies (Model::Teardown for Model frames).
//
// Same lifetime + ordering rules as ModuleAutoRegister: static-init chaining,
// undefined cross-TU order — fine, because each callback only clears its OWN
// state. This is the "clear stale state" category only; a save-before-teardown
+69 -18
View File
@@ -3345,6 +3345,19 @@ enum Offsets {
CALL_CAST_CHECK_TARGET_RANGE = 0x006E4F26,
FUN_SPELL_TARGET_ARM = 0x006E5250,
CALL_CAST_TARGET_ARM = 0x006E5038,
// SendCast — `void __fastcall(void *pendingCast /*ecx*/, uint8_t flag
// /*dl*/)`, plain ret: builds and sends CMSG_CAST_SPELL / CMSG_USE_ITEM /
// CMSG_PET_CAST_SPELL (4.3.4's Spell_C_SendPendingCast). Its five callers
// (xrefs) all pass the global pending cast (`mov ecx,0xCEAC48`): the
// target arm's two immediate sends, and the object / location / trade-
// slot binders the targeting cursor's click also runs. The arm's one
// caller is the cast request, after the caster check.
FUN_SPELL_SEND_CAST = 0x006E54F0,
CALL_TARGET_ARM_SEND_CAST = 0x006E5349,
CALL_TARGET_ARM_SEND_CAST_GUID = 0x006E5467,
CALL_BIND_TARGET_OBJECT_SEND_CAST = 0x006E60D7,
CALL_BIND_TARGET_LOCATION_SEND_CAST = 0x006E6177,
CALL_BIND_TARGET_TRADE_SLOT_SEND_CAST = 0x006E694F,
// GetSpellHostility — `int __thiscall(const uint8_t *record)`: 2 harmful,
// 1 helpful, 0 neither, from the Targets word and the implicit targets
// (benilla-re spell/handlers#hostility-classifier).
@@ -3445,6 +3458,16 @@ enum Offsets {
// 0x005D9139); the stock 1.12.1 exe reaches it.
SITE_ITEM_USE_FORM_REFUSAL = 0x005D914D,
ITEM_USE_FORM_PASSED = 0x005D9157,
// Past the form test, the item use's toggle test (4.3.4 runs it before
// the form cancel): a use spell with an activeIconID whose aura is on
// the player is cancelled instead of cast, `mov ecx,[ebp-0xC]` (spell)
// then CALL_ITEM_USE_TOGGLE_CANCEL → FUN_CANCEL_AURA_SEND, return 0.
// Every other path joins at SITE_ITEM_USE_BIND_TEST, the bind-on-use
// test: `mov ecx,[ebp-8]; cmp dword [ecx+0x194],3` (8B 4D F8 83 B9 94
// 01 00 00 03), then `jne`. There EDI = the spell (read after), EBX and
// EBP live; EAX, ECX, EDX are reloaded before use (disassembled).
CALL_ITEM_USE_TOGGLE_CANCEL = 0x005D9237,
SITE_ITEM_USE_BIND_TEST = 0x005D91D3,
// The mount reads in 4.3.4's CGUnit_C::CanAttackNow, inlined in 1.12
// (each followed by `test; jg refuse`):
// the unit right-click's attack step (8B 82 FC 01 00 00), player ESI,
@@ -4127,6 +4150,13 @@ enum Offsets {
// Slot 36 of the Model and pane vtables; DressUpModel's own slot 36
// (0x00504350) calls it first.
FUN_SIMPLEMODEL_SET_MODEL_INSTANCE = 0x0076CD30,
// Every Model frame's member teardown (fastcall frame), called by the
// CSimpleModel deleting dtor 0x0076CA20 and CGCharacterModelBase's dtor
// 0x00505870 (PlayerModel, DressUpModel). It releases the frame's M2
// scene (+0x314), which frees the models in it even when something else
// still holds a reference (seen live: a kept compositor's release faulted
// on the freed model after /reload).
FUN_SIMPLEMODEL_DESTROY = 0x0076CA50,
// The Model frame's per-paint update — `__thiscall(frame)`, slot 38.
FUN_SIMPLEMODEL_UPDATE_MODEL = 0x0076D1A0,
@@ -6145,15 +6175,37 @@ enum Offsets {
// (21) set only the main-hand bit, off-hand-only (22) only the off-hand.
VAR_INVTYPE_SLOT_MASKS = 0x00809200,
// The two `m_inventoryType` values that mean "consumed when used", so
// both the item-consumable test (`C_Item.IsConsumableItem`) and the
// action-slot one (`IsConsumableAction`) single them out. Indices into
// the table above: 24 = `INVTYPE_AMMO`, 25 = `INVTYPE_THROWN`.
// `m_inventoryType` (`OFF_ITEMSTATS_INVENTORY_TYPE`) values, named by
// their key in the string table above (read from WoW.exe).
INVTYPE_NON_EQUIP = 0,
INVTYPE_HEAD = 1,
INVTYPE_NECK = 2,
INVTYPE_SHOULDER = 3,
INVTYPE_BODY = 4,
INVTYPE_CHEST = 5,
INVTYPE_WAIST = 6,
INVTYPE_LEGS = 7,
INVTYPE_FEET = 8,
INVTYPE_WRIST = 9,
INVTYPE_HAND = 10,
INVTYPE_FINGER = 11,
INVTYPE_TRINKET = 12,
INVTYPE_WEAPON = 13,
INVTYPE_SHIELD = 14,
INVTYPE_RANGED = 15,
INVTYPE_CLOAK = 16,
INVTYPE_2HWEAPON = 17,
INVTYPE_BAG = 18,
INVTYPE_TABARD = 19,
INVTYPE_ROBE = 20,
INVTYPE_WEAPONMAINHAND = 21,
INVTYPE_WEAPONOFFHAND = 22,
INVTYPE_HOLDABLE = 23,
INVTYPE_AMMO = 24,
INVTYPE_THROWN = 25,
// Index 16 of the table above (read from WoW.exe). The bag search skips
// a cloak's subclass name ("Cloth"), as 4.3.4's does.
INVTYPE_CLOAK = 16,
INVTYPE_RANGEDRIGHT = 26,
INVTYPE_QUIVER = 27,
INVTYPE_RELIC = 28,
// Faction "displayed list" — the engine maintains a sorted/visible list
// of factions the player has rep with. `Script_GetNumFactions` (at
@@ -8627,17 +8679,20 @@ enum Offsets {
// unit-event observers. Matches nampower's `CGRaidInfo_m_raidtargets`.
VAR_RAID_TARGET_GUIDS = 0x00B71368,
VAR_LOCALE_INDEX = 0x00C0E080, // 0..8, picks one of the 9 localized strings
// 0..7: start-up stores the Wow.ini Language+Country tag's position in
// VAR_LOCALE_NAME_TABLE (0x402562), 0 when it matches none (enGB).
// Picks a DBC localized-string column. NOT 4.3.4's nine-slot locale
// order — language rules go by name (Localization::Locale).
VAR_LOCALE_INDEX = 0x00C0E080,
// Locale-name string table — a parallel `const char *[8]` indexed by
// VAR_LOCALE_INDEX, holding the exact codes `GetLocale()` returns:
// enUS / koKR / frFR / deDE / zhCN / zhTW / esES / xxYY. This is the
// source `Script_GetLocale` (FUN_0048d8b0) reads:
// enUS / koKR / frFR / deDE / zhCN / zhTW / esES / xxYY on a stock
// client. This is the source `Script_GetLocale` (FUN_0048d8b0) reads:
// lua_pushstring(L, (&PTR_DAT_008558a4)[DAT_00c0e080]);
// Verified by resolving all 8 entries from the binary. Index range is
// 0..7 (VAR_LOCALE_INDEX is documented 0..8, but slot 8 overreads into
// the string pool — clamp to 0..7 before reading). Used by the TOC
// `[TextLocale]` / `[AllowLoadTextLocale]` directives (AddOns::TocRewrite).
// and the list SetAddOnMetadata (0x51D580) matches a TOC key's
// `-xxXX` suffix against. Verified by resolving all 8 entries from the
// binary. Read through Localization::Locale::Client().
VAR_LOCALE_NAME_TABLE = 0x008558A4,
LUA_IS_NUMBER = 0x6F34D0,
@@ -10912,10 +10967,6 @@ enum Offsets {
INVSLOT_OFFHAND = 17,
INVSLOT_MAINHAND = 16,
// INVTYPE (`OFF_ITEMSTATS_INVENTORY_TYPE`) of a two-handed weapon,
// which occupies the main-hand slot and blocks the off-hand.
INVTYPE_2HWEAPON = 17,
// Character-pane equipment slot range: 1..19 (head=1, neck=2, …,
// tabard=19). Used by `C_Item.IsEquippedItem` to walk every slot
// looking for a matching item.
+1 -3
View File
@@ -170,9 +170,7 @@ static void RegisterLuaFunctions() {
&Script_HasSpellActionButtons, &kHasSpellActionButtons);
Game::Lua::RegisterTableFunction("C_ActionBar", "IsOnBarOrSpecialBar",
&Script_IsOnBarOrSpecialBar, &kIsOnBarOrSpecialBar);
Game::Lua::RegisterIntegerEnum("Enum", "ActionBarSet", kActionBarSet,
static_cast<int>(sizeof(kActionBarSet) / sizeof(kActionBarSet[0])),
"ActionBar");
Game::Lua::RegisterIntegerEnum("Enum", "ActionBarSet", kActionBarSet, "ActionBar");
}
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -5
View File
@@ -408,11 +408,7 @@ static void RegisterLuaFunctions() {
Game::Lua::RegisterTableFunction("C_AddOns", "GetAddOnInterfaceVersion",
&Script_GetAddOnInterfaceVersion,
&kGetAddOnInterfaceVersion);
Game::Lua::RegisterIntegerEnum(
"Enum", "AddOnSecurityStatus",
kAddOnSecurityStatusEntries,
sizeof(kAddOnSecurityStatusEntries) /
sizeof(kAddOnSecurityStatusEntries[0]));
Game::Lua::RegisterIntegerEnum("Enum", "AddOnSecurityStatus", kAddOnSecurityStatusEntries);
}
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+4 -13
View File
@@ -14,6 +14,7 @@
#include "addons/EngineIO.h"
#include "addons/TocRewrite.h"
#include "addons/Toc.h"
#include "localization/Locale.h"
#include "Offsets.h"
#include "turtle/Detect.h"
@@ -34,17 +35,7 @@ using AddOns::Toc::Lower;
bool IsSpace(char c) { return c == ' ' || c == '\t'; }
// The client locale code ("enUS", "frFR", …) — the exact string
// GetLocale() returns (Offsets.h VAR_LOCALE_NAME_TABLE derivation).
const char *ClientLocale() {
uint32_t idx = *reinterpret_cast<const uint32_t *>(
static_cast<uintptr_t>(Offsets::VAR_LOCALE_INDEX));
if (idx > 7) idx = 0; // slot 8 overreads the string pool — clamp
const char *const *table = reinterpret_cast<const char *const *>(
static_cast<uintptr_t>(Offsets::VAR_LOCALE_NAME_TABLE));
const char *s = table[idx];
return (s != nullptr) ? s : "enUS";
}
using Localization::Locale::Client;
// True iff the comma-separated `list` (spaces allowed) contains `token` (CI).
bool ListContains(const char *list, size_t listLen, const char *token) {
@@ -66,7 +57,7 @@ bool ListContains(const char *list, size_t listLen, const char *token) {
const char *VariableValue(const char *head, size_t headLen) {
if (EqCI(head, headLen, "Family")) return "Classic";
if (EqCI(head, headLen, "Game")) return "Vanilla";
if (EqCI(head, headLen, "TextLocale")) return ClientLocale();
if (EqCI(head, headLen, "TextLocale")) return Client();
return nullptr;
}
@@ -158,7 +149,7 @@ bool EvalCondition(const char *head, size_t headLen, const char *args,
if (EqCI(head, headLen, "ExcludeLoadGameType"))
return !ListContainsGameType(args, argsLen);
if (EqCI(head, headLen, "AllowLoadTextLocale"))
return ListContains(args, argsLen, ClientLocale());
return ListContains(args, argsLen, Client());
if (EqCI(head, headLen, "AllowLoad"))
return ListContains(args, argsLen, "game"); // addon files: in-game only
return false;
+2 -3
View File
@@ -560,9 +560,8 @@ void RegisterLuaFunctions() {
RegisterClass(Anim::s_rotationClass);
RegisterClass(Anim::s_pathClass);
RegisterClass(ControlPoint::s_class);
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), kRegionMethods,
static_cast<int>(sizeof(kRegionMethods) / sizeof(kRegionMethods[0])),
kRegionDocs, static_cast<int>(sizeof(kRegionDocs) / sizeof(kRegionDocs[0])));
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), kRegionMethods, kRegionDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+12 -7
View File
@@ -665,10 +665,15 @@ void RecordTable(const char *table, const char *name, const Function *doc) {
Record(KIND_TABLE, table, name, nullptr, doc);
}
void RecordFrameMethods(const void *registry, const Game::Lua::FrameMethodEntry *table,
int count, const Method *docs, int docCount) {
void RecordFrameMethods(const void *registry,
Game::ArrayRef<Game::Lua::FrameMethodEntry> methods,
Game::Doc::MethodList docList) {
if (g_pass == ENV_NONE)
return;
const Game::Lua::FrameMethodEntry *table = methods.data;
const int count = methods.count;
const Method *docs = docList.data;
const int docCount = docList.count;
for (int i = 0; i < count; ++i) {
const Function *doc = nullptr;
for (int d = 0; d < docCount; ++d) {
@@ -720,14 +725,14 @@ void RecordValueTable(Kind kind, const char *parent, const char *sub,
} // namespace
void RecordEnum(const char *parent, const char *sub, const Game::Lua::EnumIntegerEntry *entries,
int count, const char *system) {
RecordValueTable(KIND_ENUM, parent, sub, entries, count, system);
void RecordEnum(const char *parent, const char *sub,
Game::ArrayRef<Game::Lua::EnumIntegerEntry> entries, const char *system) {
RecordValueTable(KIND_ENUM, parent, sub, entries.data, entries.count, system);
}
void RecordConstants(const char *sub, const Game::Lua::EnumIntegerEntry *entries, int count,
void RecordConstants(const char *sub, Game::ArrayRef<Game::Lua::EnumIntegerEntry> entries,
const char *system) {
RecordValueTable(KIND_CONSTANTS, "Constants", sub, entries, count, system);
RecordValueTable(KIND_CONSTANTS, "Constants", sub, entries.data, entries.count, system);
}
} // namespace Api::Documentation
+6 -5
View File
@@ -32,14 +32,15 @@ void EndPass();
void RecordGlobal(const char *name, const Game::Doc::Function *doc);
void RecordTable(const char *table, const char *name, const Game::Doc::Function *doc);
void RecordFrameMethods(const void *registry, const Game::Lua::FrameMethodEntry *table,
int count, const Game::Doc::Method *docs, int docCount);
void RecordFrameMethods(const void *registry,
Game::ArrayRef<Game::Lua::FrameMethodEntry> methods,
Game::Doc::MethodList docs);
// Names the widget system of a method hash this DLL owns (a frame class it
// adds), alongside the engine registries the exporter already knows.
void NameWidgetSystem(const void *registry, const char *system);
void RecordEnum(const char *parent, const char *sub, const Game::Lua::EnumIntegerEntry *entries,
int count, const char *system);
void RecordConstants(const char *sub, const Game::Lua::EnumIntegerEntry *entries, int count,
void RecordEnum(const char *parent, const char *sub,
Game::ArrayRef<Game::Lua::EnumIntegerEntry> entries, const char *system);
void RecordConstants(const char *sub, Game::ArrayRef<Game::Lua::EnumIntegerEntry> entries,
const char *system);
} // namespace Api::Documentation
+4 -6
View File
@@ -457,12 +457,10 @@ static void RegisterLuaFunctions() {
&Script_GetAutoCompleteResults, &kGetAutoCompleteResults);
Game::Lua::RegisterTableFunction("C_AutoComplete", "IsRecognizedName", &Script_IsRecognizedName,
&kIsRecognizedName);
Game::Lua::RegisterIntegerEnum("Enum", "AutoCompleteEntryFlag", kEntryFlagEntries,
sizeof(kEntryFlagEntries) / sizeof(kEntryFlagEntries[0]),
"AutoComplete");
Game::Lua::RegisterIntegerEnum("Enum", "AutoCompletePriority", kPriorityEntries,
sizeof(kPriorityEntries) / sizeof(kPriorityEntries[0]),
"AutoComplete");
Game::Lua::RegisterIntegerEnum(
"Enum", "AutoCompleteEntryFlag", kEntryFlagEntries, "AutoComplete");
Game::Lua::RegisterIntegerEnum(
"Enum", "AutoCompletePriority", kPriorityEntries, "AutoComplete");
}
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -497,9 +497,7 @@ const Game::Lua::EnumIntegerEntry kSwingTypeEntries[] = {
};
void RegisterLuaFunctions() {
Game::Lua::RegisterIntegerEnum(
"Enum", "PlayerSwingType", kSwingTypeEntries,
sizeof(kSwingTypeEntries) / sizeof(kSwingTypeEntries[0]), "SwingTimer");
Game::Lua::RegisterIntegerEnum("Enum", "PlayerSwingType", kSwingTypeEntries, "SwingTimer");
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+2 -6
View File
@@ -145,12 +145,8 @@ void RegisterLuaFunctions() {
Game::Lua::RegisterGlobalFunction("ConsoleGetAllCommands", &Script_GetAllCommands);
Game::Lua::RegisterGlobalFunction("ConsolePrintAllMatchingCommands",
&Script_ConsolePrintAllMatchingCommands);
Game::Lua::RegisterIntegerEnum("Enum", "ConsoleCommandType", kCommandTypeEntries,
sizeof(kCommandTypeEntries) /
sizeof(kCommandTypeEntries[0]));
Game::Lua::RegisterIntegerEnum("Enum", "ConsoleCategory", kCategoryEntries,
sizeof(kCategoryEntries) /
sizeof(kCategoryEntries[0]));
Game::Lua::RegisterIntegerEnum("Enum", "ConsoleCommandType", kCommandTypeEntries);
Game::Lua::RegisterIntegerEnum("Enum", "ConsoleCategory", kCategoryEntries);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -2
View File
@@ -42,8 +42,7 @@ const Game::Lua::EnumIntegerEntry kEntries[] = {
};
void RegisterLuaFunctions() {
Game::Lua::RegisterIntegerEnum("Enum", "BagIndex", kEntries,
sizeof(kEntries) / sizeof(kEntries[0]), "BagIndexConstants");
Game::Lua::RegisterIntegerEnum("Enum", "BagIndex", kEntries, "BagIndexConstants");
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+31 -13
View File
@@ -55,6 +55,7 @@
#include "item/Location.h"
#include "item/Record.h"
#include "item/TypeInfo.h"
#include "localization/Locale.h"
#include "unit/Identity.h"
#include <algorithm>
@@ -86,12 +87,29 @@ char16_t ToLower(char16_t c) {
return c;
}
// Appends the lowercased `c` with 4.3.4's accent folding for `locale`
// (frFR 2, deDE 3, esES/esMX 6/7, ruRU 8); æ, œ and ß become two letters.
// Returns the new length.
size_t AppendFolded(int locale, char16_t c, char16_t *out, size_t n) {
switch (locale) {
case 2: // frFR
// 4.3.4's accent-folding sets. 4.3.4 picks one by its locale index (frFR 2,
// deDE 3, esES/esMX 6/7, ruRU 8); 1.12's index has other slots, so the set
// is picked by locale name.
enum class Folding { None, French, German, Spanish, Russian };
Folding ClientFolding() {
using Localization::Locale::Is;
if (Is("frFR"))
return Folding::French;
if (Is("deDE"))
return Folding::German;
if (Is("esES") || Is("esMX"))
return Folding::Spanish;
if (Is("ruRU"))
return Folding::Russian;
return Folding::None;
}
// Appends the lowercased `c` with `folding`'s accent folding; æ, œ and ß
// become two letters. Returns the new length.
size_t AppendFolded(Folding folding, char16_t c, char16_t *out, size_t n) {
switch (folding) {
case Folding::French:
switch (c) {
case 0xE0: case 0xE2: case 0xE4: c = u'a'; break;
case 0xE6: out[n++] = u'a'; c = u'e'; break;
@@ -103,14 +121,13 @@ size_t AppendFolded(int locale, char16_t c, char16_t *out, size_t n) {
case 0xF9: case 0xFA: case 0xFB: case 0xFC: c = u'u'; break;
}
break;
case 3: // deDE
case Folding::German:
if (c == 0xDF) {
out[n++] = u's';
c = u's';
}
break;
case 6: // esES
case 7: // esMX
case Folding::Spanish:
switch (c) {
case 0xE1: c = u'a'; break;
case 0xE9: c = u'e'; break;
@@ -120,10 +137,12 @@ size_t AppendFolded(int locale, char16_t c, char16_t *out, size_t n) {
case 0xF1: c = u'n'; break;
}
break;
case 8: // ruRU
case Folding::Russian:
if (c == 0x451)
c = 0x435;
break;
case Folding::None:
break;
}
out[n++] = c;
return n;
@@ -141,11 +160,10 @@ size_t Fold(const char *text, char16_t (&out)[kFoldCap]) {
// One unit short of the buffer: a full conversion stores no terminator.
reinterpret_cast<Convert_t>(Offsets::FUN_SUNI_CONVERT_UTF8_TO_16)(
wide, kFoldCap - 1, text, 0x7FFFFFFF, nullptr, nullptr);
const int locale =
*reinterpret_cast<const int *>(static_cast<uintptr_t>(Offsets::VAR_LOCALE_INDEX));
const Folding folding = ClientFolding();
size_t n = 0;
for (size_t i = 0; wide[i] != 0 && n + 2 < kFoldCap; ++i)
n = AppendFolded(locale, ToLower(wide[i]), out, n);
n = AppendFolded(folding, ToLower(wide[i]), out, n);
out[n] = 0;
return n;
}
+2 -4
View File
@@ -27,10 +27,8 @@ const Game::Lua::EnumIntegerEntry kMoneyFormattingConstants[] = {
};
void RegisterLuaFunctions() {
Game::Lua::RegisterConstants("MoneyFormattingConstants", kMoneyFormattingConstants,
sizeof(kMoneyFormattingConstants) /
sizeof(kMoneyFormattingConstants[0]),
"MoneyConstants");
Game::Lua::RegisterConstants(
"MoneyFormattingConstants", kMoneyFormattingConstants, "MoneyConstants");
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -119,9 +119,7 @@ void OnTick() {
const Tick::WorldTick::AutoSubscribe _tick{&OnTick};
void RegisterLuaFunctions() {
Game::Lua::RegisterIntegerEnum(
"Enum", "UICursorType", kCursorTypeEntries,
sizeof(kCursorTypeEntries) / sizeof(kCursorTypeEntries[0]));
Game::Lua::RegisterIntegerEnum("Enum", "UICursorType", kCursorTypeEntries);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+2 -3
View File
@@ -206,9 +206,8 @@ void RegisterLuaFunctions() {
&Script_GetSelfResurrectOptions, &kGetSelfResurrectOptions);
Game::Lua::RegisterTableFunction("C_DeathInfo", "UseSelfResurrectOption",
&Script_UseSelfResurrectOption, &kUseSelfResurrectOption);
Game::Lua::RegisterIntegerEnum("Enum", "SelfResurrectOptionType", kOptionTypeEntries,
sizeof(kOptionTypeEntries) / sizeof(kOptionTypeEntries[0]),
"DeathInfo");
Game::Lua::RegisterIntegerEnum(
"Enum", "SelfResurrectOptionType", kOptionTypeEntries, "DeathInfo");
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -47,7 +47,6 @@
#include <cmath>
#include <cstdio>
#include <iterator>
namespace Duration::Object {
@@ -451,8 +450,7 @@ const Game::Doc::Function kCreateDuration{"Creates a zero duration object that c
void RegisterLuaFunctions() {
CreateMetatable(Lua::State());
Lua::RegisterTableFunction("C_DurationUtil", "CreateDuration", &Script_CreateDuration, &kCreateDuration);
Lua::RegisterIntegerEnum("Enum", "DurationTimeModifier", kModifiers, static_cast<int>(std::size(kModifiers)),
"LuaDurationObjectShared");
Lua::RegisterIntegerEnum("Enum", "DurationTimeModifier", kModifiers, "LuaDurationObjectShared");
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+2 -3
View File
@@ -58,7 +58,6 @@
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iterator>
#include <string>
#include <vector>
@@ -780,8 +779,8 @@ void RegisterLuaFunctions() {
CreateMetatable(Lua::State());
Lua::RegisterTableFunction("C_DurationUtil", "CreateDurationTextBinding", &Script_CreateDurationTextBinding,
&kCreateDurationTextBinding);
Lua::RegisterIntegerEnum("Enum", "DurationTextBindingProperty", kProperties,
static_cast<int>(std::size(kProperties)), "DurationTextBindingShared");
Lua::RegisterIntegerEnum(
"Enum", "DurationTextBindingProperty", kProperties, "DurationTextBindingShared");
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+2 -2
View File
@@ -125,8 +125,8 @@ const Game::Lua::FrameMethodEntry g_methods[] = {
};
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -83,9 +83,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -86,9 +86,7 @@ void RegisterLuaFunctions() {
Common::RedirectCall(Offsets::CALL_EDITBOX_IME_COMPOSITION_INSERT, Offsets::FUN_EDITBOX_INSERT,
reinterpret_cast<const void *>(&CompositionInsert));
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -2
View File
@@ -30,8 +30,7 @@ const Game::Lua::EnumIntegerEntry kEntries[] = {
};
void RegisterLuaFunctions() {
Game::Lua::RegisterIntegerEnum("Enum", "InputContext", kEntries,
sizeof(kEntries) / sizeof(kEntries[0]), "InputConstants");
Game::Lua::RegisterIntegerEnum("Enum", "InputContext", kEntries, "InputConstants");
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -234,9 +234,7 @@ const Game::Lua::FrameMethodEntry g_methods[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY),
g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
@@ -1,6 +1,7 @@
## Interface: 11200
## Title: !!!ClassicAPI
## Notes: Library to emulate aspects of the modern WoW API on 1.12.1 / Lua 5.0.
## Notes-ruRU: Библиотека, эмулирующая аспекты современного API WoW на 1.12.1 / Lua 5.0.
## Version: DEV
## Author: Brues
## X-Website: https://github.com/brues-code/ClassicAPI
+150 -52
View File
@@ -4,17 +4,109 @@ ACTION_BUTTON_USE_KEY_DOWN = "Способности при нажатии кл
ACTION_TARGETING_OPTION = "Разрешить динамическое выделение";
ADDON_MEM_KB_ABBR = "(%.0f КБ) %s";
ADDON_MEM_MB_ABBR = "(%.2f МБ) %s";
ADDON_OPTIONS = "Параметры модификаций";
BINDING_NAME_AOELOOT = "Сбор добычи по области";
BINDING_NAME_FOCUSTARGET = "Запомненная цель";
BINDING_NAME_INTERACTMOUSEOVER = "Взаимодействовать указателем мыши";
BINDING_NAME_INTERACTMOUSEOVER = "Взаимодействовать с объектом под курсором";
BINDING_NAME_INTERACTTARGET = "Взаимодействовать с целью";
BINDING_NAME_TARGETFOCUS = "Восстановить цель";
CLASSICAPI_ACTION_BARS = "Панели команд";
CLASSICAPI_ACTION_TARGETING = "Динамическое выделение";
CLASSICAPI_ADVANCED = "Дополнительно";
CLASSICAPI_ALL_SOFT_TARGET_ICONS = "Значки для всех мягких целей";
CLASSICAPI_AUTO_DISMOUNT = "Автоматически спешиваться";
CLASSICAPI_AUTO_DISMOUNT_TOOLTIP = "Спешиваться при применении заклинания, использовании объекта, сборе добычи, атаке или открытии карты полетов, если вы едете верхом.";
CLASSICAPI_AUTO_STAND = "Автоматически вставать";
CLASSICAPI_AUTO_STAND_TOOLTIP = "Вставать при применении заклинания, сборе добычи или открытии карты полетов, если вы сидите.";
CLASSICAPI_AUTO_UNSHIFT = "Автоматически снимать облик";
CLASSICAPI_AUTO_UNSHIFT_TOOLTIP = "Покидать свой облик при применении заклинания, которое в нем недоступно, разговоре с NPC, использовании объекта или предмета, а также при открытии карты полетов.";
CLASSICAPI_BAGS_AND_ITEMS = "Сумки и предметы";
CLASSICAPI_CASTING_AND_ATTACKING = "Заклинания и атаки";
CLASSICAPI_COMBAT_LOG_RETENTION = "Хранение журнала боя";
CLASSICAPI_COMBAT_LOG_RETENTION_TOOLTIP = "Сколько секунд журнал боя хранит записи для модификаций.";
CLASSICAPI_CURSOR = "Курсор";
CLASSICAPI_DIRECTION_ANY = "Любое направление";
CLASSICAPI_DIRECTION_CONE = "В пределах 30 градусов";
CLASSICAPI_DIRECTION_FRONT = "Прямо перед собой";
CLASSICAPI_ENEMIES = "Противники";
CLASSICAPI_ENEMY_DIRECTION = "Направление на противника";
CLASSICAPI_ENEMY_ICON = "Показывать значок противника";
CLASSICAPI_ENEMY_ICON_TOOLTIP = "Показывает значок над противником, которого выбирает динамическое выделение.";
CLASSICAPI_ENEMY_RANGE = "Дистанция до противников";
CLASSICAPI_EXPANDED_CURSORS = "Показывать расширенные курсоры";
CLASSICAPI_EXPANDED_CURSORS_TOOLTIP = "При наведении на NPC или объект курсор показывает, что с ним можно сделать. Например, наковальню для ремонта, ! или ? для заданий и крючок для вашего поплавка.";
CLASSICAPI_FRIEND_DIRECTION = "Направление на союзника";
CLASSICAPI_FRIEND_ICON = "Показывать значок союзника";
CLASSICAPI_FRIEND_ICON_TOOLTIP = "Показывает значок над дружественной целью, которую выбирает дружественное динамическое выделение.";
CLASSICAPI_FRIEND_RANGE = "Дистанция до союзников";
CLASSICAPI_FRIEND_TARGETING = "Включить дружественное динамическое выделение";
CLASSICAPI_FRIEND_TARGETING_TOOLTIP = "Выбирает ближайшую дружественную цель в поле зрения. Если у вас нет цели, полезные заклинания применяются к ней.";
CLASSICAPI_FRIENDS = "Союзники";
CLASSICAPI_GAME_OBJECT_ICON = "Показывать значок взаимодействия на объектах";
CLASSICAPI_GAME_OBJECT_ICON_TOOLTIP = "Также показывает значок взаимодействия над объектами, которые нельзя выбрать целью, например почтовыми ящиками и сундуками.";
CLASSICAPI_HIDE_EQUIPMENT_SETS = "Скрывать комплекты экипировки на предметах";
CLASSICAPI_HIDE_EQUIPMENT_SETS_TOOLTIP = "Убирает строку с комплектами экипировки из подсказок предметов.";
CLASSICAPI_ICON_SIZE = "Размер значков";
CLASSICAPI_ICON_SIZE_TOOLTIP = "Размер значков взаимодействия, противников и союзников.";
CLASSICAPI_IGNORE_BACKPACK = "Не сортировать рюкзак";
CLASSICAPI_IGNORE_BACKPACK_TOOLTIP = "При сортировке сумок предметы в рюкзаке остаются на своих местах.";
CLASSICAPI_IGNORE_BANK = "Не сортировать основной банк";
CLASSICAPI_IGNORE_BANK_TOOLTIP = "При сортировке банка предметы в основных ячейках банка остаются на своих местах.";
CLASSICAPI_INTERACT_DIRECTION = "Направление взаимодействия";
CLASSICAPI_INTERACT_DIRECTION_TOOLTIP = "Где должен находиться NPC или объект, чтобы его выбрала клавиша взаимодействия. Направление определяется камерой, а не персонажем.";
CLASSICAPI_INTERACT_ICON = "Показывать значок взаимодействия";
CLASSICAPI_INTERACT_ICON_TOOLTIP = "Показывает значок над NPC или объектом, которые использует клавиша взаимодействия.";
CLASSICAPI_INTERACT_KEY = "Клавиша взаимодействия";
CLASSICAPI_INTERACT_RANGE = "Дистанция взаимодействия";
CLASSICAPI_INTERFACE = "Интерфейс";
CLASSICAPI_KEEP_ACTION_TARGETING = "Сохранять динамическое выделение";
CLASSICAPI_KEEP_ACTION_TARGETING_TOOLTIP = "Динамическое выделение продолжает выбирать противников и союзников, пока у вас есть цель, выбранная вручную.";
CLASSICAPI_LOOSE_TARGET_FOLLOWS = "За кем следует обычная цель";
CLASSICAPI_LOOSE_TARGET_FOLLOWS_ENEMY = "За мягкой целью противника";
CLASSICAPI_LOOSE_TARGET_FOLLOWS_FRIEND = "За мягкой целью союзника";
CLASSICAPI_LOOSE_TARGET_FOLLOWS_NONE = "Ни за кем";
CLASSICAPI_LOOSE_TARGET_FOLLOWS_TOOLTIP = "Цель, выбранная динамическим выделением, меняется вместе с мягкой целью противника или союзника. Цель, выбранная вручную, сохраняется.";
CLASSICAPI_LOW_PRIORITY_ICONS = "Показывать значки низкого приоритета";
CLASSICAPI_LOW_PRIORITY_ICONS_TOOLTIP = "Также показывает значок над целью, с которой нельзя ничего сделать, например над членом группы. Значок выглядит как обычный указатель.";
CLASSICAPI_MATCH_INSIDE_NAMES = "Учитывать правки внутри имени";
CLASSICAPI_MATCH_INSIDE_NAMES_TOOLTIP = "Когда вы вводите текст в середине имени, предлагаемые имена также учитывают буквы после курсора.";
CLASSICAPI_MATCH_THE_COMMAND = "Учитывать команду";
CLASSICAPI_MATCH_THE_COMMAND_TOOLTIP = "Предлагает только имена, подходящие для команды, например участников гильдии для /gpromote. Если выключено, предлагаются все известные вам имена.";
CLASSICAPI_MOTION = "Движение";
CLASSICAPI_NAME_SUGGESTIONS = "Подсказки имен";
CLASSICAPI_NAMEPLATES = "Таблички с именами";
CLASSICAPI_NUMBERS = "Числа";
CLASSICAPI_OPTIONS_ABOUT = "Настройки возможностей, которые ClassicAPI добавляет в игру. Каждый параметр меняет одну настройку, и изменение применяется сразу.";
CLASSICAPI_OTHER_NAMEPLATE_ALPHA = "Непрозрачность чужих табличек";
CLASSICAPI_OTHER_NAMEPLATE_ALPHA_TOOLTIP = "Непрозрачность табличек с именами целей, которые не являются вашей целью.";
CLASSICAPI_PREFER_YOUR_TARGET = "Отдавать предпочтение вашей цели";
CLASSICAPI_PREFER_YOUR_TARGET_TOOLTIP = "Если выбранную вами цель можно взять, динамическое выделение выбирает ее. Если параметр выключен, всегда выбирается ближайшая цель.";
CLASSICAPI_RANGE_TOOLTIP = "Максимальное расстояние в ярдах. Оно не может превышать значение параметра targetNearestDistance.";
CLASSICAPI_RECENT_WHISPERS_FIRST = "Сначала недавние шепоты";
CLASSICAPI_RECENT_WHISPERS_FIRST_TOOLTIP = "Предлагаемые имена начинаются с игроков, которым вы шептали совсем недавно.";
CLASSICAPI_REMEMBER_PLAYERS = "Запоминать игроков";
CLASSICAPI_REMEMBER_PLAYERS_TOOLTIP = "Сохраняет имя, класс, расу и пол встречавшихся вам игроков, чтобы модификации могли показывать их в следующих сеансах. Данные общие для всех ваших персонажей на этом сервере.";
CLASSICAPI_SCRIPT_PROFILE = "Профилирование скриптов";
CLASSICAPI_SCRIPT_PROFILE_TOOLTIP = "Измеряет время процессора, которое используют модификации. Изменение вступает в силу после перезагрузки интерфейса.";
CLASSICAPI_SEPARATE_LARGE_NUMBERS = "Разделять большие числа";
CLASSICAPI_SEPARATE_LARGE_NUMBERS_TOOLTIP = "Ставит разделитель между группами из трех цифр, когда модификации форматируют числа с помощью BreakUpLargeNumbers.";
CLASSICAPI_SORT_BAGS_RIGHT_TO_LEFT = "Сортировать сумки справа налево";
CLASSICAPI_SORT_BAGS_RIGHT_TO_LEFT_TOOLTIP = "При сортировке предметы перемещаются в конец последней сумки, а хлам — в начало рюкзака.";
CLASSICAPI_STRICT_INTERACT_RANGE = "Строгая дистанция взаимодействия";
CLASSICAPI_STRICT_INTERACT_RANGE_TOOLTIP = "Никогда не выбирает NPC или объект за пределами дистанции взаимодействия. Если параметр выключен, объект, доступный для использования сейчас, выбирается даже на большем расстоянии.";
CLASSICAPI_TARGET_NAMEPLATE_ALPHA = "Непрозрачность таблички цели";
CLASSICAPI_TARGET_NAMEPLATE_ALPHA_TOOLTIP = "Непрозрачность таблички с именем вашей цели.";
CLASSICAPI_UI_SHAKE_STRENGTH = "Сила дрожания интерфейса";
CLASSICAPI_UI_SHAKE_STRENGTH_TOOLTIP = "Определяет, насколько сильно эффекты дрожания смещают рамки. 0 отключает дрожание.";
CLASSICAPI_UNIT_DIRECTION_TOOLTIP = "Где должна находиться цель, чтобы ее выбрало динамическое выделение. Направление определяется камерой, а не персонажем.";
CLASSICAPI_WHEN_YOU_HAVE_A_TARGET = "Когда у вас есть цель";
COMBAT_LABEL = "Бой";
COUNTDOWN_FOR_COOLDOWNS_TEXT = "Отсчет времени восстановления";
CONFIRM_DELETE_EQUIPMENT_SET = "Вы действительно хотите удалить комплект экипировки \"%s\"?";
CONFIRM_SAVE_EQUIPMENT_SET = "Хотите сохранить комплект экипировки \"%s\"?";
COPPER_AMOUNT = "%d |4медная монета:медные монеты:медных монет;";
COPPER_AMOUNT_SYMBOL = "м";
COPPER_AMOUNT_TEXTURE = "%d|TInterface\\MoneyFrame\\UI-MoneyIcons:%d:%d:2:0:4:1:2:3:0:1|t";
DEBUFF_SYMBOL_CURSE = "Пр";
DEBUFF_SYMBOL_DISEASE = "Бз";
DEBUFF_SYMBOL_MAGIC = "Мг";
@@ -24,74 +116,66 @@ ENABLE_INTERACT_TEXT = "Включить клавишу взаимодейств
EQUIPMENT_MANAGER = "Комплекты экипировки";
EQUIPMENT_MANAGER_IGNORE_SLOT = "Пропустить ячейку";
EQUIPMENT_MANAGER_IS_DISABLED = "Управление экипировкой отключено.";
EQUIPMENT_MANAGER_ITEMS_MISSING_TOOLTIP = "%d %s |cffff0000(предмет для ячейки \"%d\" отсутствует)|r";
EQUIPMENT_MANAGER_ITEMS_MISSING_TOOLTIP = "%d %s |cffff0000(не хватает: %d)|r";
EQUIPMENT_MANAGER_PLACE_IN_BAGS = "Поместить в сумку";
EQUIPMENT_MANAGER_UNIGNORE_SLOT = "Учитывать ячейку";
EQUIPMENT_SETS = "Комплекты экипировки: |cFFFFFFFF%s|r";
EQUIPMENT_SETS_CANT_RENAME = "Это название уже используется для другого комплекта.";
EQUIPMENT_SETS_TOO_MANY = "Вы не можете создавать новые комплекты экипировки.";
EQUIPMENT_SET_ASSIGN_TO_SPEC = "Выбрать специализацию:";
EQUIPMENT_SET_ASSIGN_TO_SPEC = "Назначить специализации:";
EQUIPMENT_SET_EDIT = "Изменить название/значок";
EQUIPMENT_SET_SETTINGS = "Параметры";
EQUIPSET_EQUIP = "Надеть";
ERR_EQUIPMENT_MANAGER_BAGS_FULL = "Не удалось заменить комплект обмундирования. Инвентарь переполнен.";
ERR_EQUIPMENT_MANAGER_COMBAT_SWAP_S = "Некоторые предметы из набора \"%s\" не будут экипированы, поскольку вы участвуете в бою.";
ERR_EQUIPMENT_MANAGER_MISSING_ITEM_S = "Недостает одного или более предметов из набора \"%s\".";
ERR_EQUIPMENT_MANAGER_BAGS_FULL = "Не удалось сменить экипировку. Инвентарь переполнен.";
ERR_EQUIPMENT_MANAGER_COMBAT_SWAP_S = "Некоторые предметы из комплекта \"%s\" нельзя сменить, поскольку вы участвуете в бою.";
ERR_EQUIPMENT_MANAGER_MISSING_ITEM_S = "Один или несколько предметов из комплекта \"%s\" отсутствуют и не были надеты.";
EVENTTRACE_APPLY_DEFAULT_FILTER = "Применить фильтр по умолчанию";
EVENTTRACE_ARG_FMT = "Арг. %d:";
EVENTTRACE_BUTTON_DISABLE_FILTERS = "Снять выделение";
EVENTTRACE_ARG_FMT = "Аргумент %d:";
EVENTTRACE_BUTTON_DISABLE_FILTERS = "Снять все";
EVENTTRACE_BUTTON_DISCARD_FILTER = "Сбросить все";
EVENTTRACE_BUTTON_ENABLE_FILTERS = "Выделить все";
EVENTTRACE_BUTTON_ENABLE_FILTERS = "Выбрать все";
EVENTTRACE_BUTTON_MARKER = "Маркер";
EVENTTRACE_BUTTON_PAUSE = "Пауза";
EVENTTRACE_BUTTON_PLAY = "Играть";
EVENTTRACE_BUTTON_PLAY = "Продолжить";
EVENTTRACE_FILTER_HEADER = "Фильтр";
EVENTTRACE_HEADER = "Журнал события";
EVENTTRACE_LOG_CR_EVENTS = "Отображать глобальные события по регистрации";
EVENTTRACE_HEADER = "Журнал событий";
EVENTTRACE_LOG_CR_EVENTS = "Показывать события глобального реестра";
EVENTTRACE_LOG_DISCARD = "Журнал сброшен";
EVENTTRACE_LOG_HEADER = "Журнал";
EVENTTRACE_LOG_PAUSE = "Регистрация участников события приостановлена";
EVENTTRACE_LOG_PAUSE_WHILE_HIDDEN = "Фиксация событий прекращается в свернутом состоянии";
EVENTTRACE_LOG_START = "Регистрация участников события началась";
EVENTTRACE_LOG_WHEN_HIDDEN = "Фиксировать события в свернутом состоянии";
EVENTTRACE_LOG_PAUSE = "Запись событий приостановлена";
EVENTTRACE_LOG_PAUSE_WHILE_HIDDEN = "Запись событий приостановлена, пока окно скрыто";
EVENTTRACE_LOG_START = "Запись событий начата";
EVENTTRACE_LOG_WHEN_HIDDEN = "Записывать события, когда окно скрыто";
EVENTTRACE_MARKER = "Маркер";
EVENTTRACE_MESSAGE_FORMAT = "--- %s ---";
EVENTTRACE_OPTIONS = "Параметры";
EVENTTRACE_RESULTS = "Результаты: %d";
EVENTTRACE_SHOW_ARGUMENTS = "Отображать аргументы события";
EVENTTRACE_SHOW_TIMESTAMP = "Отображать метку времени события";
EVENTTRACE_RESULTS = "Результатов: %d";
EVENTTRACE_SHOW_ARGUMENTS = "Показывать аргументы событий";
EVENTTRACE_SHOW_TIMESTAMP = "Показывать время событий";
EVENTTRACE_TIMESTAMP = "Время:";
CLEAR_FOCUS = "Отменить запоминание цели";
INTERACT_ICONS_DEFAULT = "Только NPC (по умолчанию)";
INTERACT_ICONS_OPTION = "Значки клавиш взаимодействия";
INTERACT_ICONS_SHOW_ALL = "Показать все";
INTERACT_ICONS_OPTION = "Значки клавиши взаимодействия";
INTERACT_ICONS_SHOW_ALL = "Показывать все";
INTERACT_ICONS_SHOW_NONE = "Не показывать";
OPTION_TOOLTIP_ACTION_BUTTON_USE_KEY_DOWN = "Способности будут задействоваться, когда пользователь нажимает, а не отпускает клавишу.";
OPTION_TOOLTIP_ACTION_TARGETING = "Система захвата целей, динамически выбирающая цели в поле зрения. Работает параллельно со стандартной системой наведения.";
OPTION_TOOLTIP_COUNTDOWN_FOR_COOLDOWNS = "Отображение обратного отсчета для времени восстановления на панели команд.";
OPTION_TOOLTIP_ENABLE_INTERACT = "Позволяет взаимодействовать с NPC и объектами по нажатию клавиши.";
OPTION_TOOLTIP_INTERACT_ICONS = "Определяет, какие значки появляются над NPC и объектами, если включен параметр клавиши взаимодействия.";
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Прекращение автоматической атаки при смене цели.";
OPTION_TOOLTIP_ACTION_BUTTON_USE_KEY_DOWN = "Способности на панели команд срабатывают при нажатии клавиши, а не при ее отпускании.";
OPTION_TOOLTIP_ACTION_TARGETING = "Система выделения, которая динамически выбирает противников в зависимости от направления взгляда. Работает вместе со стандартной системой выделения.";
OPTION_TOOLTIP_COUNTDOWN_FOR_COOLDOWNS = "Показывать числовой отсчет времени восстановления на панели команд.";
OPTION_TOOLTIP_ENABLE_INTERACT = "Позволяет взаимодействовать с NPC и объектами нажатием клавиши.";
OPTION_TOOLTIP_INTERACT_ICONS = "Определяет, какие значки появляются над NPC и объектами, когда включена клавиша взаимодействия.";
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Прекращать автоатаку при смене цели.";
KEY_BINDING_NAME_AND_KEY = "%s (%s)";
KEY_BINDING_TOOLTIP = "<ПКМ, чтобы отвязать>";
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Назначьте клавишу \"%s\"или нажмите %s для отмены";
KEY_BINDING_TOOLTIP = "<ПКМ, чтобы снять привязку>";
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Назначьте клавишу для \"%s\" или нажмите %s для отмены";
SET_FOCUS = "Запомнить цель";
FOCUS_TOKEN_NOT_FOUND = "<фокус отсутствует>";
FOCUSTARGET = "Запомнить цель";
ITEMS_VARIABLE_QUANTITY = "%d |4предмет:предмета:предметов;";
ITEMS_EQUIPPED = "%d |4предмет экипирован:предмета экипировано:предметов экипировано;";
ITEMS_IN_INVENTORY = "%d |4предмет:предмета:предметов; в сумках";
ITEM_SLOTS_IGNORED = "%d |4ячейка пропущена:ячейки пропущены:ячейки пропущены;";
ITEM_MISSING = "Предмет для ячейки %s отсутствует";
ITEMS_NOT_IN_INVENTORY = "%d |4предмет отсутствует:предмета отсутствует:предметов отсутствуют;";
STOP_AUTO_ATTACK = "Прекращение автоатаки";
TARGETFOCUS = "Восстановить цель";
-- BINDING_NAME_AOELOOT = "AoE Loot";
COPPER_AMOUNT = "%d |4медная монета:медные монеты:медных монет;";
COPPER_AMOUNT_SYMBOL = "м";
GEARSETS_POPUP_TEXT = "Введите название комплекта (не более 16 символов):";
GENERIC_FRACTION_STRING = "%d/%d";
GOLD_AMOUNT = "%d |4золотая:золотые:золотых;";
GOLD_AMOUNT_SYMBOL = "з";
GOLD_AMOUNT_TEXTURE = "%d|TInterface\\MoneyFrame\\UI-MoneyIcons:%d:%d:2:0:4:1:0:1:0:1|t";
GOLD_AMOUNT_TEXTURE_STRING = "%s|TInterface\\MoneyFrame\\UI-MoneyIcons:%d:%d:2:0:4:1:0:1:0:1|t";
HOURS_MINUTES_SECONDS = "%.2d:%.2d:%.2d";
ICON_FILTER_ALL = "Все значки";
ICON_FILTER_ITEM = "Предметы";
@@ -109,17 +193,17 @@ ITEM_MOD_ATTACK_POWER_SHORT = "к силе атаки";
ITEM_MOD_RANGED_ATTACK_POWER_SHORT = "к силе атаки дальнего боя";
ITEM_MOD_CRIT_MELEE_RATING_SHORT = "к показателю критического удара в ближнем бою";
ITEM_MOD_CRIT_RANGED_RATING_SHORT = "к показателю критического удара в дальнем бою";
ITEM_MOD_HIT_MELEE_RATING_SHORT = "к меткости в ближнем бою";
ITEM_MOD_HIT_MELEE_RATING_SHORT = "к меткости в ближнем бою";
ITEM_MOD_HIT_RANGED_RATING_SHORT = "к меткости в дальнем бою";
ITEM_MOD_HIT_SPELL_RATING_SHORT = "к меткости заклинаний";
ITEM_MOD_CRIT_SPELL_RATING_SHORT = "к критическому эффекту заклинаний";
ITEM_MOD_SPELL_DAMAGE_DONE_SHORT = "к дополнительному урону";
ITEM_MOD_SPELL_HEALING_DONE_SHORT = "к дополнительному исцелению";
ITEM_MOD_MANA_REGENERATION_SHORT = "к восполнению маны";
ITEM_MOD_DEFENSE_SKILL_RATING_SHORT = "Защита";
ITEM_MOD_DODGE_RATING_SHORT = "Уклонение";
ITEM_MOD_PARRY_RATING_SHORT = "Парирование";
ITEM_MOD_BLOCK_RATING_SHORT = "Блок";
ITEM_MOD_DEFENSE_SKILL_RATING_SHORT = "к защите";
ITEM_MOD_DODGE_RATING_SHORT = "к уклонению";
ITEM_MOD_PARRY_RATING_SHORT = "к парированию";
ITEM_MOD_BLOCK_RATING_SHORT = "к блокированию";
ITEM_MOD_BLOCK_VALUE_SHORT = "к показателю блокирования";
ITEM_MOD_HOLY_SPELL_DAMAGE_DONE_SHORT = "к урону от магии света";
ITEM_MOD_FIRE_SPELL_DAMAGE_DONE_SHORT = "к урону от магии огня";
@@ -131,6 +215,12 @@ ITEM_MOD_SPELL_PENETRATION_SHORT = "к проникающей способнос
ITEM_MOD_ARMOR_PENETRATION_RATING_SHORT = "к пробиванию брони";
ITEM_MOD_HEALTH_REGEN_SHORT = "к восполнению здоровья";
ITEM_MOD_DAMAGE_PER_SECOND_SHORT = "к урону в секунду";
ITEMS_EQUIPPED = "%d |4предмет экипирован:предмета экипировано:предметов экипировано;";
ITEMS_IN_INVENTORY = "%d |4предмет:предмета:предметов; в инвентаре";
ITEMS_NOT_IN_INVENTORY = "%d |4предмет отсутствует:предмета отсутствует:предметов отсутствует;";
ITEMS_VARIABLE_QUANTITY = "%d |4предмет:предмета:предметов;";
ITEM_MISSING = "%s отсутствует";
ITEM_SLOTS_IGNORED = "%d |4ячейка пропущена:ячейки пропущены:ячеек пропущено;";
LARGE_NUMBER_SEPERATOR = "";
DECIMAL_SEPERATOR = ",";
FIRST_NUMBER = "тыс.";
@@ -141,14 +231,18 @@ SECOND_NUMBER_CAP = "М";
SECOND_NUMBER_CAP_NO_SPACE = "М";
THIRD_NUMBER = "млрд";
FOURTH_NUMBER = "трлн";
LUA_ERROR = "Ошибка Lua";
MINUTES_SECONDS = "%.2d:%.2d";
PAPERDOLL_EQUIPMENTMANAGER = "Комплекты экипировки";
PAPERDOLL_NEWEQUIPMENTSET = "Новый комплект";
PERCENTAGE_STRING = "%d%%";
SET_FRIENDNOTE_LABEL = "Заметка о |3-5(%s):";
PLAYER_LIST_DELIMITER = ", ";
RELOADUI = "Перезагрузить интерфейс";
SET_FRIENDNOTE_LABEL = "Задать заметку для %s:";
SET_NOTE = "Добавить заметку";
SILVER_AMOUNT = "%d |4серебряная:серебряные:серебряных;";
SILVER_AMOUNT_SYMBOL = "с";
SILVER_AMOUNT_TEXTURE = "%d|TInterface\\MoneyFrame\\UI-MoneyIcons:%d:%d:2:0:4:1:1:2:0:1|t";
SLASH_CANCELAURA1 = "/снятьауру";
SLASH_CANCELAURA2 = "/cancelaura";
SLASH_CANCELFORM1 = "/нормоблик";
@@ -222,10 +316,14 @@ SLASH_TARGET_NEAREST_RAID2 = "/targetraid";
SLASH_USE1 = "/use";
SLASH_USE2 = "/использовать";
SOCIAL_LABEL = "Общение";
LINK_TRADESKILL_TOOLTIP = "Щелкните здесь, чтобы создать ссылку на вашу профессию.";
TEXT_TO_SPEECH = "Текст в речь";
TEXT_TO_SPEECH_ADJUST_RATE = "Изменить скорость речи.";
STOP_AUTO_ATTACK = "Прекращение автоатаки";
SYMBOLIC_NEGATIVE_NUMBER = "-%s";
SYMBOLIC_POSITIVE_NUMBER = "+%s";
TARGETFOCUS = "Восстановить цель";
LINK_TRADESKILL_TOOLTIP = "Щелкните здесь, чтобы создать ссылку на свою профессию.";
TEXT_TO_SPEECH = "Синтез речи";
TEXT_TO_SPEECH_ADJUST_RATE = "Изменить скорость речи";
TEXT_TO_SPEECH_ADJUST_VOLUME = "Громкость речи";
TEXT_TO_SPEECH_VOICE_OPTION = "Параметры голоса";
TOTAL_MEM_KB_ABBR = "Память: %.0f КБ";
TOTAL_MEM_MB_ABBR = "Модификации занимают в памяти: %.2f МБ";
TEXT_TO_SPEECH_VOICE_OPTION = "Выбор голоса";
TOTAL_MEM_KB_ABBR = "Память модификаций: %.0f КБ";
TOTAL_MEM_MB_ABBR = "Память модификаций: %.2f МБ";
+1 -2
View File
@@ -217,8 +217,7 @@ void Register() {
{"Standard", static_cast<int>(Variant::Standard)},
{"UrlSafe", static_cast<int>(Variant::UrlSafe)},
};
Game::Lua::RegisterIntegerEnum("Enum", "Base64Variant",
kBase64Variant, 2);
Game::Lua::RegisterIntegerEnum("Enum", "Base64Variant", kBase64Variant);
}
} // namespace
+1 -2
View File
@@ -305,8 +305,7 @@ void Register() {
{"Zlib", static_cast<int>(Method::Zlib)},
{"Gzip", static_cast<int>(Method::Gzip)},
};
Game::Lua::RegisterIntegerEnum("Enum", "CompressionMethod",
kCompressionMethod, 3);
Game::Lua::RegisterIntegerEnum("Enum", "CompressionMethod", kCompressionMethod);
}
} // namespace
+1 -3
View File
@@ -224,9 +224,7 @@ static void RegisterLuaFunctions() {
// 4.3.4's switch for the item-tooltip line; dedups by name on /reload.
g_hideSetsCVar = CVar::Factory::Register("dontShowEquipmentSetsOnItems", "Don't show which equipment sets an item is associated with", Offsets::CVAR_CATEGORY_GAME, "0");
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_GAMETOOLTIP_METHOD_REGISTRY),
g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
reinterpret_cast<void *>(Offsets::VAR_GAMETOOLTIP_METHOD_REGISTRY), g_methods);
}
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+5 -4
View File
@@ -144,10 +144,11 @@ const Game::Doc::Method kFontDocs[] = {
};
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY),
kFontStringMethods, 2, kFontStringDocs, 2);
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_FONT_METHOD_REGISTRY), kFontMethods,
2, kFontDocs, 2);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), kFontStringMethods,
kFontStringDocs);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FONT_METHOD_REGISTRY), kFontMethods, kFontDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+2 -2
View File
@@ -181,8 +181,8 @@ const Game::Doc::Method kHtmlDocs[] = {
{"GetIndentedWordWrap", &kHtmlGet},
};
template <typename P> void Register(uintptr_t registry, const Game::Doc::Method *docs) {
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(registry), P::methods, 2, docs, 2);
template <typename P> void Register(uintptr_t registry, Game::Doc::MethodList docs) {
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(registry), P::methods, docs);
}
// A font string takes the bit from its font only when its settable mask
+1 -3
View File
@@ -378,9 +378,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+2 -2
View File
@@ -96,8 +96,8 @@ const Game::Doc::Method kDocs[] = {
};
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), kMethods, 2,
kDocs, 2);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), kMethods, kDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -324,9 +324,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -1165,9 +1165,7 @@ const Game::Lua::FrameMethodEntry g_frameMethods[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY),
g_frameMethods,
static_cast<int>(sizeof(g_frameMethods) / sizeof(g_frameMethods[0])));
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_frameMethods);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -117,9 +117,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -186,9 +186,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -2
View File
@@ -102,8 +102,7 @@ const Game::Lua::FrameMethodEntry g_methods[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), g_methods);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -453,9 +453,7 @@ void RegisterLuaFunctions() {
Common::ReplaceWithCall(Offsets::CALL_FRAME_DRAW_SELF_CHILD, kDrawChildCall, sizeof kDrawChildCall,
reinterpret_cast<const void *>(&DrawChildThunk));
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -1588,9 +1588,7 @@ void RegisterLuaFunctions() {
reinterpret_cast<RegisterFactory_t>(Offsets::FUN_FRAMEXML_REGISTER_FACTORY)(
"Cooldown", &CreateCooldown, 0);
Api::Documentation::NameWidgetSystem(g_hash, "FrameAPICooldown");
Game::Lua::RegisterFrameMethods(
g_hash, g_methods, static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])),
g_methodDocs, static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
Game::Lua::RegisterFrameMethods(g_hash, g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -4
View File
@@ -53,9 +53,6 @@ using SetAttr_t = void(__thiscall *)(void *node, const char *name, const char *v
using Build_t = void *(__fastcall *)(const void *node, void *parent, void *status);
using StatusReset_t = void(__thiscall *)(void *self);
// LUA_TNONE: lua_type of an index past the top.
constexpr int kTypeNone = -1;
// 4.3.4's usage line; vanilla's (0x00872F60) has no `id`.
constexpr const char *kUsage =
"Usage: CreateFrame(\"frameType\" [, \"name\"] [, parent] [, \"template\"] [, id])";
@@ -102,7 +99,7 @@ int __fastcall CreateFrame_h(void *L) {
const int parentType = Type(L, 3);
if (!IsString(L, 1) ||
(parentType != kTypeNone && parentType != TYPE_NIL && parentType != TYPE_TABLE)) {
(parentType != TYPE_NONE && parentType != TYPE_NIL && parentType != TYPE_TABLE)) {
Error(L, kUsage);
return 0;
}
+1 -3
View File
@@ -111,9 +111,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
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,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+2 -3
View File
@@ -221,9 +221,8 @@ const Game::Doc::Method g_methodDocs[] = {
};
void RegisterLuaFunctions() {
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,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -67,9 +67,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -113,9 +113,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+5 -4
View File
@@ -108,10 +108,11 @@ const Game::Doc::Method kFontStringDocs[] = {{"SetFormattedText", &kFontStringDo
const Game::Doc::Method kButtonDocs[] = {{"SetFormattedText", &kButtonDoc}};
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY),
kFontStringMethods, 1, kFontStringDocs, 1);
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), kButtonMethods,
1, kButtonDocs, 1);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), kFontStringMethods,
kFontStringDocs);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), kButtonMethods, kButtonDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -2
View File
@@ -198,8 +198,7 @@ const Game::Lua::FrameMethodEntry g_methods[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -2
View File
@@ -95,8 +95,7 @@ const Game::Lua::FrameMethodEntry g_methods[] = {
void RegisterLuaFunctions() {
for (const uintptr_t registry : {static_cast<uintptr_t>(Offsets::VAR_SCROLLINGMESSAGEFRAME_METHOD_REGISTRY),
static_cast<uintptr_t>(Offsets::VAR_SIMPLEHTML_METHOD_REGISTRY)}) {
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(registry), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(registry), g_methods);
}
}
+1 -3
View File
@@ -139,9 +139,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+5 -16
View File
@@ -569,26 +569,15 @@ const Game::Lua::FrameMethodEntry g_fontStringMethods[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY),
g_regionMethods,
static_cast<int>(sizeof(g_regionMethods) / sizeof(g_regionMethods[0])));
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_regionMethods);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY),
g_frameMethods,
static_cast<int>(sizeof(g_frameMethods) / sizeof(g_frameMethods[0])));
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_frameMethods);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY),
g_buttonMethods,
static_cast<int>(sizeof(g_buttonMethods) / sizeof(g_buttonMethods[0])));
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), g_buttonMethods);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_TEXTURE_METHOD_REGISTRY),
g_textureMethods,
static_cast<int>(sizeof(g_textureMethods) / sizeof(g_textureMethods[0])));
reinterpret_cast<void *>(Offsets::VAR_TEXTURE_METHOD_REGISTRY), g_textureMethods);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY),
g_fontStringMethods,
static_cast<int>(sizeof(g_fontStringMethods) /
sizeof(g_fontStringMethods[0])));
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), g_fontStringMethods);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+2 -3
View File
@@ -380,9 +380,8 @@ void RegisterLuaFunctions() {
reinterpret_cast<const void *>(&RemovalThunk));
Common::ReplaceWithCall(Offsets::ADDR_UI_FOCUS_PASS_PROBE, kProbe, sizeof kProbe,
reinterpret_cast<const void *>(&ProbeThunk));
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
Game::Lua::RegisterGlobalFunction("GetMouseFoci", &Script_GetMouseFoci, &kGetMouseFoci);
}
+2 -2
View File
@@ -148,8 +148,8 @@ const Game::Doc::Method kDocs[] = {
};
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), kMethods, 2,
kDocs, 2);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), kMethods, kDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+2 -3
View File
@@ -389,9 +389,8 @@ void RegisterLuaFunctions() {
reinterpret_cast<const void *>(&PressDispatchThunk));
Common::ReplaceWithCall(Offsets::ADDR_UI_RELEASE_DISPATCH, kReleaseDispatch, sizeof kReleaseDispatch,
reinterpret_cast<const void *>(&ReleaseDispatchThunk));
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+2 -2
View File
@@ -104,8 +104,8 @@ const Game::Doc::Function kDoc{"Sets which mouse buttons run the OnMouseDown and
const Game::Doc::Method kDocs[] = {{"RegisterForMouse", &kDoc}};
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), kMethods, 1,
kDocs, 1);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), kMethods, kDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -209,9 +209,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
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,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+2 -5
View File
@@ -108,12 +108,9 @@ const Game::Doc::Method g_frameDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_regionMethods,
static_cast<int>(sizeof(g_regionMethods) / sizeof(g_regionMethods[0])), g_regionDocs,
static_cast<int>(sizeof(g_regionDocs) / sizeof(g_regionDocs[0])));
g_regionDocs);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_frameMethods,
static_cast<int>(sizeof(g_frameMethods) / sizeof(g_frameMethods[0])), g_frameDocs,
static_cast<int>(sizeof(g_frameDocs) / sizeof(g_frameDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_frameMethods, g_frameDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+2 -2
View File
@@ -49,8 +49,8 @@ const Game::Doc::Function kDoc{"Enables or disables the button, as Enable() or D
const Game::Doc::Method kDocs[] = {{"SetEnabled", &kDoc}};
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), kMethods, 1,
kDocs, 1);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), kMethods, kDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -203,9 +203,7 @@ 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,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -2
View File
@@ -351,8 +351,7 @@ const Game::Lua::FrameMethodEntry g_frameMethods[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_frameMethods,
static_cast<int>(sizeof(g_frameMethods) / sizeof(g_frameMethods[0])));
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_frameMethods);
}
// Frames survive /reload but the engine rebuilds the event table with empty
+1 -3
View File
@@ -114,9 +114,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -126,9 +126,7 @@ static const Game::Lua::FrameMethodEntry g_methods[] = {
static void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_GAMETOOLTIP_METHOD_REGISTRY),
g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
reinterpret_cast<void *>(Offsets::VAR_GAMETOOLTIP_METHOD_REGISTRY), g_methods);
}
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -366,9 +366,7 @@ static const Game::Lua::FrameMethodEntry g_methods[] = {
static void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_GAMETOOLTIP_METHOD_REGISTRY),
g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
reinterpret_cast<void *>(Offsets::VAR_GAMETOOLTIP_METHOD_REGISTRY), g_methods);
}
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+3 -9
View File
@@ -309,15 +309,9 @@ void RegisterLuaFunctions() {
&Script_GetItemInventorySlotKey);
Game::Lua::RegisterTableFunction("C_Item", "GetItemInventorySlotInfo",
&Script_GetItemInventorySlotInfo);
Game::Lua::RegisterIntegerEnum(
"Enum", "InventoryType", kInventoryTypeEntries,
sizeof(kInventoryTypeEntries) / sizeof(kInventoryTypeEntries[0]));
Game::Lua::RegisterIntegerEnum(
"Enum", "ItemClass", kItemClassEntries,
sizeof(kItemClassEntries) / sizeof(kItemClassEntries[0]));
Game::Lua::RegisterIntegerEnum(
"Enum", "ItemQuality", kItemQualityEntries,
sizeof(kItemQualityEntries) / sizeof(kItemQualityEntries[0]));
Game::Lua::RegisterIntegerEnum("Enum", "InventoryType", kInventoryTypeEntries);
Game::Lua::RegisterIntegerEnum("Enum", "ItemClass", kItemClassEntries);
Game::Lua::RegisterIntegerEnum("Enum", "ItemQuality", kItemQualityEntries);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+33
View File
@@ -0,0 +1,33 @@
// 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/>.
#include "localization/Locale.h"
#include "Game.h"
#include "Offsets.h"
#include <cstdint>
#include <cstring>
namespace Localization::Locale {
// Script_GetLocale (FUN_0048d8b0): VAR_LOCALE_NAME_TABLE[VAR_LOCALE_INDEX].
const char *Client() {
const uint32_t index = Game::Read<uint32_t>(Offsets::VAR_LOCALE_INDEX);
return reinterpret_cast<const char *const *>(
static_cast<uintptr_t>(Offsets::VAR_LOCALE_NAME_TABLE))[index];
}
bool Is(const char *code) { return std::strcmp(Client(), code) == 0; }
} // namespace Localization::Locale
+32
View File
@@ -0,0 +1,32 @@
// 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/>.
#pragma once
// The client locale by NAME, as GetLocale() reports it.
//
// Language rules (plurals, accent folding, number abbreviations) must pick
// by this name, never by VAR_LOCALE_INDEX. The 1.12 index is the Wow.ini
// tag's position in a table of eight codes (enUS koKR frFR deDE zhCN zhTW
// esES xxYY), not 4.3.4's nine-slot order, so a 4.3.4 index such as 8 for
// ruRU never occurs here. A Russian client is one whose table holds "ruRU",
// in whatever slot. The index stays right for picking a DBC string column.
namespace Localization::Locale {
// The locale code GetLocale() returns ("enUS", "frFR", ...).
const char *Client();
// Whether the client locale is `code`.
bool Is(const char *code);
} // namespace Localization::Locale
+3 -6
View File
@@ -62,6 +62,7 @@
#include "Game.h"
#include "Offsets.h"
#include "cvar/Factory.h"
#include "localization/Locale.h"
#include <cmath>
#include <cstdio>
@@ -131,17 +132,13 @@ constexpr Scale kScalesAsian[] = {
CVar::Factory::Handle g_breakUpLargeNumbers = nullptr;
// VAR_LOCALE_INDEX order: enUS, koKR, frFR, deDE, zhCN, zhTW, esES, esMX, ruRU.
bool ClientIsAsian() {
const int locale = Game::Read<int>(Offsets::VAR_LOCALE_INDEX);
return locale == 1 || locale == 4 || locale == 5;
}
bool IsAsianLocale(const char *locale) {
return std::strcmp(locale, "koKR") == 0 || std::strcmp(locale, "zhCN") == 0 ||
std::strcmp(locale, "zhTW") == 0;
}
bool ClientIsAsian() { return IsAsianLocale(Localization::Locale::Client()); }
// `_G[name]` when it is a string, else "".
std::string GlobalString(void *L, const char *name) {
Lua::PushString(L, name);
+31 -48
View File
@@ -48,6 +48,7 @@
#include "Game.h"
#include "Offsets.h"
#include "model/M2.h"
#include "time/Clock.h"
#include <cstdint>
@@ -58,11 +59,7 @@ namespace {
using PaneFn_t = void(__thiscall *)(void *pane);
using SetModelInstance_t = void(__thiscall *)(void *frame, void *model);
using PlayAnimation_t = void(__thiscall *)(void *model, int keyBone, uint32_t id, int variation,
int offset, float speed, int blend, int primary);
using GetKeyBoneAnim_t = uint32_t(__thiscall *)(void *model, int keyBone);
using LookupIndex_t = uint16_t(__fastcall *)(uint32_t id, const void *table);
using ModelLoaded_t = int(__thiscall *)(void *model, int tryLoad, int recurseChildren);
struct PaneState {
bool doBlend = false;
@@ -78,21 +75,13 @@ std::unordered_map<void *, PaneState> g_panes;
PaneFn_t g_origUpdateModel = nullptr;
SetModelInstance_t g_origSetModelInstance = nullptr;
void *PaneModel(void *pane) {
return Game::Read<void *>(pane, Offsets::OFF_SIMPLEMODELFFX_MODEL_INSTANCE);
}
bool IsLoaded(void *model) {
return reinterpret_cast<ModelLoaded_t>(Offsets::FUN_MODEL_INSTANCE_LOADED)(model, 0, 0) != 0;
}
uint32_t KeyBoneAnim(void *model) {
return reinterpret_cast<GetKeyBoneAnim_t>(Offsets::FUN_M2MODEL_GET_KEY_BONE_ANIM)(model, -1);
}
void Arm(void *model, const PaneState &s) {
reinterpret_cast<PlayAnimation_t>(Offsets::FUN_M2MODEL_PLAY_ANIMATION)(
model, -1, s.animation, s.variation, 0, s.frozen ? 0.0f : 1.0f, s.doBlend ? 1 : 0, 1);
M2::PlayAnimation(model, -1, s.animation, s.variation, 0, s.frozen ? 0.0f : 1.0f,
s.doBlend ? 1 : 0, 1);
// PlayAnimation stores the id only when it found a sequence to play.
if (!s.frozen || KeyBoneAnim(model) != s.animation)
return;
@@ -109,11 +98,11 @@ void __fastcall UpdateModel_h(void *pane, void * /*edx*/) {
}
PaneState &s = it->second;
reinterpret_cast<PaneFn_t>(Offsets::FUN_SIMPLEMODEL_UPDATE_MODEL)(pane);
void *model = PaneModel(pane);
void *model = Pane::Model(pane);
const uint32_t deadline = Game::Read<uint32_t>(pane, Offsets::OFF_PLAYERMODEL_TURN_DEADLINE);
if (Game::Read<int>(pane, Offsets::OFF_PLAYERMODEL_TURNING) == 0 &&
(deadline == 0 || Time::Clock::Reached(deadline)) && model != nullptr && IsLoaded(model) &&
(s.pending || KeyBoneAnim(model) != s.animation)) {
(deadline == 0 || Time::Clock::Reached(deadline)) && model != nullptr &&
M2::IsLoaded(model, 0, 0) && (s.pending || KeyBoneAnim(model) != s.animation)) {
Arm(model, s);
s.pending = false;
Game::Ref<uint32_t>(pane, Offsets::OFF_PLAYERMODEL_TURN_DEADLINE) = 0;
@@ -135,29 +124,33 @@ const Game::HookAutoRegister _setModelHook{Offsets::FUN_SIMPLEMODEL_SET_MODEL_IN
reinterpret_cast<void *>(&SetModelInstance_h),
reinterpret_cast<void **>(&g_origSetModelInstance)};
void *ResolvePane(void *L) {
return Game::Lua::ResolveTypedObject(L, 1, Offsets::VAR_PLAYERMODEL_LUA_TYPE_ID);
// SetAnimation / FreezeAnimation: the pane rests on `animation`, re-armed on
// the next update.
void Rest(void *pane, uint32_t animation, int variation, bool frozen, uint32_t frame) {
PaneState &s = g_panes[pane];
s.resting = true;
s.pending = true;
s.frozen = frozen;
s.animation = animation;
s.variation = variation;
s.frame = frame;
}
int __fastcall Script_SetAnimation(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
if (!Game::Lua::IsNumber(L, 2)) {
Game::Lua::Error(L, "Usage: SetAnimation(animation [, variation])");
return 0;
}
PaneState &s = g_panes[pane];
s.resting = true;
s.pending = true;
s.frozen = false;
s.animation = static_cast<uint32_t>(Game::Lua::ToNumber(L, 2));
s.variation = Game::Lua::IsNumber(L, 3) ? static_cast<int>(Game::Lua::ToNumber(L, 3)) : 0;
Rest(pane, static_cast<uint32_t>(Game::Lua::ToNumber(L, 2)),
Game::Lua::IsNumber(L, 3) ? static_cast<int>(Game::Lua::ToNumber(L, 3)) : 0, false, 0);
return 0;
}
int __fastcall Script_FreezeAnimation(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
if (!Game::Lua::IsNumber(L, 2) || !Game::Lua::IsNumber(L, 3) || !Game::Lua::IsNumber(L, 4)) {
@@ -165,24 +158,20 @@ int __fastcall Script_FreezeAnimation(void *L) {
return 0;
}
const double frame = Game::Lua::ToNumber(L, 4);
PaneState &s = g_panes[pane];
s.resting = true;
s.pending = true;
s.frozen = true;
s.animation = static_cast<uint32_t>(Game::Lua::ToNumber(L, 2));
s.variation = static_cast<int>(Game::Lua::ToNumber(L, 3));
s.frame = frame > 0.0 ? static_cast<uint32_t>(frame) : 0;
Rest(pane, static_cast<uint32_t>(Game::Lua::ToNumber(L, 2)),
static_cast<int>(Game::Lua::ToNumber(L, 3)), true,
frame > 0.0 ? static_cast<uint32_t>(frame) : 0);
return 0;
}
int __fastcall Script_SetDoBlend(void *L) {
if (void *pane = ResolvePane(L))
if (void *pane = Game::Lua::ResolvePlayerModel(L))
g_panes[pane].doBlend = Game::Lua::ToBoolean(L, 2) != 0;
return 0;
}
int __fastcall Script_GetDoBlend(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
const auto it = g_panes.find(pane);
@@ -191,7 +180,7 @@ int __fastcall Script_GetDoBlend(void *L) {
}
int __fastcall Script_HasAnimation(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
if (!Game::Lua::IsNumber(L, 2)) {
@@ -200,15 +189,10 @@ int __fastcall Script_HasAnimation(void *L) {
}
const auto id = static_cast<uint32_t>(Game::Lua::ToNumber(L, 2));
bool has = false;
void *model = PaneModel(pane);
if (model != nullptr && IsLoaded(model)) {
const auto *data = Game::Read<const uint8_t *>(
Game::Read<const uint8_t *>(model, Offsets::OFF_M2MODEL_SHARED),
Offsets::OFF_M2SHARED_DATA);
const uint16_t index = reinterpret_cast<LookupIndex_t>(Offsets::FUN_M2_LOOKUP_INDEX)(
id, data + Offsets::OFF_M2DATA_ANIMATION_LOOKUP);
has = index < Game::Read<uint32_t>(data, Offsets::OFF_M2DATA_SEQUENCE_COUNT);
}
void *model = Pane::Model(pane);
if (model != nullptr && M2::IsLoaded(model, 0, 0))
has = M2::LookupIndex(model, Offsets::OFF_M2DATA_ANIMATION_LOOKUP, id) <
Game::Read<uint32_t>(M2::Data(model), Offsets::OFF_M2DATA_SEQUENCE_COUNT);
Game::Lua::PushBool(L, has);
return 1;
}
@@ -265,8 +249,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_PLAYERMODEL_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+15 -33
View File
@@ -41,6 +41,7 @@
#include "Game.h"
#include "Offsets.h"
#include "model/M2.h"
#include <cmath>
#include <cstdint>
@@ -55,19 +56,13 @@ struct Settings {
};
std::unordered_map<void *, Settings> g_settings;
using GetVector_t = void(__fastcall *)(void *camera, uint32_t id, float *out);
using GetScalar_t = float(__fastcall *)(void *camera, uint32_t id);
using SetVector_t = void(__fastcall *)(void *camera, uint32_t id, const float *value,
uint32_t keep);
using SetScalar_t = void(__fastcall *)(void *camera, uint32_t id, float value);
using GetCameraCount_t = uint32_t(__thiscall *)(void *model);
using GetCamera_t = void *(__thiscall *)(void *model, uint32_t index);
using LookupIndex_t = uint16_t(__fastcall *)(uint32_t id, const void *table);
using SetAnimated_t = void(__thiscall *)(void *model, int animated);
using GetScene_t = void *(__thiscall *)(void *frame);
using SceneAnimate_t = void(__thiscall *)(void *scene, const float *cameraPos);
using UpdateModel_t = void(__thiscall *)(void *frame);
using ModelLoaded_t = int(__thiscall *)(void *model, int tryLoad, int recurseChildren);
using ConfigureCamera_t = void(__thiscall *)(void *pane);
} // namespace
@@ -76,27 +71,21 @@ using ConfigureCamera_t = void(__thiscall *)(void *pane);
void *CameraById(void *model, uint32_t id) {
const uint32_t count =
reinterpret_cast<GetCameraCount_t>(Offsets::FUN_M2MODEL_GET_CAMERA_COUNT)(model);
const uint8_t *data = Game::Read<const uint8_t *>(
Game::Read<const uint8_t *>(model, Offsets::OFF_M2MODEL_SHARED), Offsets::OFF_M2SHARED_DATA);
const uint16_t index = reinterpret_cast<LookupIndex_t>(Offsets::FUN_M2_LOOKUP_INDEX)(
id, data + Offsets::OFF_M2DATA_CAMERA_ID_LOOKUP);
const uint16_t index = M2::LookupIndex(model, Offsets::OFF_M2DATA_CAMERA_ID_LOOKUP, id);
if (index >= count)
return nullptr;
return reinterpret_cast<GetCamera_t>(Offsets::FUN_M2MODEL_GET_CAMERA)(model, index);
}
namespace {
struct Vec3 {
float x, y, z;
};
Vec3 GetVector(void *camera, uint32_t id) {
using Fn = void(__fastcall *)(void *camera, uint32_t id, float *out);
Vec3 v{};
reinterpret_cast<GetVector_t>(Offsets::FUN_CAMERA_GET_VECTOR)(camera, id, &v.x);
reinterpret_cast<Fn>(Offsets::FUN_CAMERA_GET_VECTOR)(camera, id, &v.x);
return v;
}
namespace {
void SetVector(void *camera, uint32_t id, const Vec3 &v) {
reinterpret_cast<SetVector_t>(Offsets::FUN_CAMERA_SET_VECTOR)(camera, id, &v.x, 0);
}
@@ -117,10 +106,9 @@ void AnimateUnrotated(void *pane, void *model) {
facing = 0.0f;
const void *vtable = Game::Read<const void *>(pane, 0);
Game::Read<UpdateModel_t>(vtable, Offsets::VTBL_SIMPLEMODEL_UPDATE_MODEL)(pane);
reinterpret_cast<SetAnimated_t>(Offsets::FUN_M2MODEL_SET_ANIMATED)(model, 1);
M2::SetAnimated(model, true);
const float origin[3] = {0.0f, 0.0f, 0.0f};
void *scene = reinterpret_cast<GetScene_t>(Offsets::FUN_SIMPLEMODEL_GET_SCENE)(pane);
reinterpret_cast<SceneAnimate_t>(Offsets::FUN_M2SCENE_ANIMATE)(scene, origin);
M2::AnimateScene(Pane::Scene(pane), origin);
facing = saved;
}
@@ -131,7 +119,7 @@ Vec3 Lerp(const Vec3 &from, const Vec3 &to, float t) {
// 4.3.4 ConfigureCamera's last two steps, on the camera vanilla installed.
void ApplySettings(void *pane, const Settings &s) {
void *model = Game::Read<void *>(pane, Offsets::OFF_SIMPLEMODELFFX_MODEL_INSTANCE);
void *model = Pane::Model(pane);
void *camera = Game::Read<void *>(pane, Offsets::OFF_SIMPLEMODEL_CAMERA);
if (model == nullptr || camera == nullptr)
return;
@@ -190,19 +178,14 @@ const Game::HookAutoRegister _cameraHook{
// be loaded: ConfigureCamera reads its cameras and bounds, and an unloaded
// model is configured by the ready hook when it arrives, settings included.
void Reconfigure(void *pane) {
void *model = Game::Read<void *>(pane, Offsets::OFF_SIMPLEMODELFFX_MODEL_INSTANCE);
if (model == nullptr ||
!reinterpret_cast<ModelLoaded_t>(Offsets::FUN_MODEL_INSTANCE_LOADED)(model, 0, 0))
void *model = Pane::Model(pane);
if (model == nullptr || !M2::IsLoaded(model, 0, 0))
return;
reinterpret_cast<ConfigureCamera_t>(Offsets::FUN_PLAYERMODEL_CONFIGURE_CAMERA)(pane);
}
void *ResolvePane(void *L) {
return Game::Lua::ResolveTypedObject(L, 1, Offsets::VAR_PLAYERMODEL_LUA_TYPE_ID);
}
int __fastcall Script_SetPortraitZoom(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
if (!Game::Lua::IsNumber(L, 2)) {
@@ -219,7 +202,7 @@ int __fastcall Script_SetPortraitZoom(void *L) {
}
int __fastcall Script_SetCamDistanceScale(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
if (!Game::Lua::IsNumber(L, 2)) {
@@ -241,7 +224,7 @@ int __fastcall Script_SetCamDistanceScale(void *L) {
}
int __fastcall Script_RefreshCamera(void *L) {
if (void *pane = ResolvePane(L))
if (void *pane = Game::Lua::ResolvePlayerModel(L))
Reconfigure(pane);
return 0;
}
@@ -274,8 +257,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_PLAYERMODEL_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+7
View File
@@ -14,7 +14,14 @@
namespace Model::Camera {
struct Vec3 {
float x, y, z;
};
// The M2 model's camera with id `id` (0 = portrait), or null when it has none.
void *CameraById(void *model, uint32_t id);
// A camera's vector property (Offsets::CAMERA_PROP_EYE / _TARGET).
Vec3 GetVector(void *camera, uint32_t id);
} // namespace Model::Camera
+54 -106
View File
@@ -55,6 +55,8 @@
#include "dbc/Lookup.h"
#include "model/DisplayInfo.h"
#include "model/ItemDisplay.h"
#include "model/M2.h"
#include "model/Teardown.h"
#include "tick/WorldTick.h"
#include <cstdint>
@@ -96,8 +98,6 @@ using CharCompDestroy_t = void(__fastcall *)(void *builder);
using CharCompSetInfo_t = char(__thiscall *)(void *builder, const CharCompInfo *info);
using CharCompSetItem_t = void(__thiscall *)(void *builder, int slot, int itemDisplayID);
using CharCompPump_t = char(__thiscall *)(void *builder, int *unused);
using ModelAddRef_t = void(__fastcall *)(void *instance);
using ModelLoaded_t = int(__thiscall *)(void *instance, int tryLoad, int recurseChildren);
// A pending / finished dress for one Model frame. The model loads async, and
// the engine only dresses a LOADED model (its poll gate FUN_00607da0 checks
@@ -107,100 +107,62 @@ using ModelLoaded_t = int(__thiscall *)(void *instance, int tryLoad, int recurse
// lifetime, and it holds the ref on the model instance — until the frame
// loads another display or the reload teardown.
struct DressJob {
void *frame = nullptr; // CSimpleModelFFX that requested the dress
void *instance = nullptr; // model instance the job is bound to
void *instance = nullptr; // model instance the job is bound to
const uint8_t *extraRec = nullptr; // CreatureDisplayInfoExtra (DBC memory, stable)
void *builder = nullptr; // engine compositor (owned)
void *builder = nullptr; // engine compositor (owned)
bool configured = false;
bool done = false;
int ticks = 0;
};
// Model frames are few and permanent; SetDisplayInfo users fewer still.
DressJob g_jobs[8];
std::unordered_map<void *, DressJob> g_jobs; // by frame
// Give up on a model that never finishes loading (bad path, missing file).
constexpr int kMaxDressTicks = 600;
void CancelJob(DressJob &job) {
DestroyDresser(job.builder);
job = DressJob{};
void CancelJob(void *frame) {
const auto it = g_jobs.find(frame);
if (it == g_jobs.end())
return;
DestroyDresser(it->second.builder);
g_jobs.erase(it);
}
DressJob *FindJob(void *frame) {
for (auto &job : g_jobs)
if (job.frame == frame)
return &job;
return nullptr;
}
// Claim a slot for `frame`: its own existing slot, a free one, or — all busy —
// a finished one (the dressed model keeps its geosets and texture refs; only
// the kept-alive builder is dropped early).
DressJob *ClaimJob(void *frame) {
DressJob *slot = FindJob(frame);
if (slot == nullptr)
for (auto &job : g_jobs)
if (job.frame == nullptr) {
slot = &job;
break;
}
if (slot == nullptr)
for (auto &job : g_jobs)
if (job.done) {
slot = &job;
break;
}
if (slot == nullptr)
slot = &g_jobs[0];
CancelJob(*slot);
return slot;
}
// Queue a dress for a character display just loaded into `frame`.
void ScheduleDress(void *frame, const uint8_t *extraRec) {
DressJob *job = ClaimJob(frame);
job->frame = frame;
job->instance = Game::Read<void *>(frame,
Offsets::OFF_SIMPLEMODELFFX_MODEL_INSTANCE);
job->extraRec = extraRec;
}
// The frame's scene is about to free the model its builder holds.
const Model::Teardown::AutoSubscribe _teardown{&CancelJob};
// One WorldTick step for one job: wait for the load, then configure + feed
// equipment (the engine's order: SetInfo, then the 10 Extra equipment slots),
// then pump until done. Field-for-field mirror of FUN_005fb200's NPC branch.
void StepJob(DressJob &job) {
void *instance = Game::Read<void *>(job.frame,
Offsets::OFF_SIMPLEMODELFFX_MODEL_INSTANCE);
// equipment, then pump until done. False once the job is dead.
bool StepJob(void *frame, DressJob &job) {
void *instance = Pane::Model(frame);
if (job.instance == nullptr)
job.instance = instance;
if (instance == nullptr || instance != job.instance || ++job.ticks > kMaxDressTicks) {
CancelJob(job); // model swapped out from under us / never loaded
return;
}
if (instance == nullptr || instance != job.instance || ++job.ticks > kMaxDressTicks)
return false; // model swapped out from under us / never loaded
if (!job.configured) {
if (!reinterpret_cast<ModelLoaded_t>(Offsets::FUN_MODEL_INSTANCE_LOADED)(instance, 0, 0))
return;
if (!M2::IsLoaded(instance, 0, 0))
return true;
job.builder = CreateDresser(instance, job.extraRec);
if (job.builder == nullptr) {
CancelJob(job);
return;
}
if (job.builder == nullptr)
return false;
// The helm/shoulder feeds attach child model instances.
LightChildren(job.frame, instance);
LightChildren(frame, instance);
job.configured = true;
}
if (PumpDresser(job.builder))
job.done = true; // builder kept — it owns the model ref for the dressed model
return true;
}
void OnWorldTick() {
for (auto &job : g_jobs)
if (job.frame != nullptr && !job.done)
StepJob(job);
for (auto it = g_jobs.begin(); it != g_jobs.end();) {
if (it->second.done || StepJob(it->first, it->second)) {
++it;
continue;
}
DestroyDresser(it->second.builder);
it = g_jobs.erase(it);
}
}
const Tick::WorldTick::AutoSubscribe g_tick{&OnWorldTick};
@@ -216,8 +178,7 @@ AttachDisplayModel_t g_origAttach = nullptr;
void __fastcall AttachDisplayModel_h(void *pane, void * /*edx*/, const uint8_t *record) {
// Any pending or kept dress for this pane is about to point at the wrong
// model — drop it before the original swaps the instance.
if (DressJob *job = FindJob(pane))
CancelJob(*job);
CancelJob(pane);
if (Model::ItemDisplay::Attach(pane, record))
return;
@@ -227,7 +188,7 @@ void __fastcall AttachDisplayModel_h(void *pane, void * /*edx*/, const uint8_t *
return;
if (const uint8_t *extraRec =
DressRecord(Game::Read<uint32_t>(record, Offsets::OFF_CREATURE_DISPLAYID)))
ScheduleDress(pane, extraRec);
g_jobs[pane] = DressJob{Pane::Model(pane), extraRec};
}
const Game::HookAutoRegister _attachHook{
@@ -237,31 +198,20 @@ const Game::HookAutoRegister _attachHook{
// ---- Lua methods --------------------------------------------------------------
using SetDisplay_t = void(__thiscall *)(void *pane, const uint8_t *record);
void SetDisplay(void *pane, const uint8_t *record) {
reinterpret_cast<SetDisplay_t>(Offsets::FUN_PLAYERMODEL_SET_DISPLAY)(pane, record);
}
// The record a pane shows for SetDisplayInfo: only the display-ID field is
// ever read. One per pane, never freed — the pane keeps the pointer and
// rebuilds from it on every show, and panes live as long as the UI.
struct DisplayRecord {
uint8_t bytes[Offsets::OFF_CREATURE_DISPLAYID + sizeof(uint32_t)];
};
std::unordered_map<void *, DisplayRecord> g_displayRecords;
// The record each pane shows for SetDisplayInfo.
std::unordered_map<void *, Pane::DisplayRecord> g_displayRecords;
void ShowDisplayID(void *pane, uint32_t displayID) {
if (displayID == 0)
return;
DisplayRecord &rec = g_displayRecords[pane];
std::memset(rec.bytes, 0, sizeof(rec.bytes));
std::memcpy(rec.bytes + Offsets::OFF_CREATURE_DISPLAYID, &displayID, sizeof(displayID));
SetDisplay(pane, rec.bytes);
Pane::DisplayRecord &rec = g_displayRecords[pane];
rec = {};
Game::Ref<uint32_t>(rec.bytes, Offsets::OFF_CREATURE_DISPLAYID) = displayID;
Pane::SetDisplay(pane, rec.bytes);
}
int __fastcall Script_SetDisplayInfo(void *L) {
void *pane = Game::Lua::ResolveTypedObject(L, 1, Offsets::VAR_PLAYERMODEL_LUA_TYPE_ID);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
if (!Game::Lua::IsNumber(L, 2)) {
@@ -273,7 +223,7 @@ int __fastcall Script_SetDisplayInfo(void *L) {
}
int __fastcall Script_SetCreature(void *L) {
void *pane = Game::Lua::ResolveTypedObject(L, 1, Offsets::VAR_PLAYERMODEL_LUA_TYPE_ID);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
if (!Game::Lua::IsNumber(L, 2)) {
@@ -287,7 +237,7 @@ int __fastcall Script_SetCreature(void *L) {
if (Game::Lua::IsNumber(L, 3))
ShowDisplayID(pane, static_cast<uint32_t>(Game::Lua::ToNumber(L, 3)));
else
SetDisplay(pane, record);
Pane::SetDisplay(pane, record);
return 0;
}
@@ -316,12 +266,19 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_PLAYERMODEL_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
// Frames are rebuilt on /reload and a new frame can reuse an old address. The
// dress jobs are gone by now: each went with its frame (Model::Teardown).
void PrepareForReload() {
g_displayRecords.clear();
}
const Game::ReloadAutoRegister _reloadReg{&PrepareForReload};
} // namespace
// The engine's own early-out: an Extra record with no bake name gets no
@@ -371,7 +328,7 @@ void *CreateDresser(void *instance, const uint8_t *extraRec) {
std::memcpy(info.geosetDefaults, kGeosetGroupDefaults, sizeof(kGeosetGroupDefaults));
// The builder owns this ref (releases it on destroy / re-configure).
reinterpret_cast<ModelAddRef_t>(Offsets::FUN_MODEL_INSTANCE_ADDREF)(instance);
M2::AddRef(instance);
if (!reinterpret_cast<CharCompSetInfo_t>(Offsets::FUN_CHARCOMP_SET_INFO)(builder, &info)) {
DestroyDresser(builder); // releases the model ref already copied in
return nullptr;
@@ -391,14 +348,12 @@ void *CreateDresser(void *instance, const uint8_t *extraRec) {
// black. Each child gets the frame's own light/fog callback; it reads the
// frame's live light struct, so later SetLight calls reach them too.
void LightChildren(void *frame, void *instance) {
using SetRenderCb_t = void(__thiscall *)(void *instance, const void *cb, void *userData);
auto setRenderCb = reinterpret_cast<SetRenderCb_t>(Offsets::FUN_MODEL_INSTANCE_SET_RENDER_CB);
const void *lightFogCb =
reinterpret_cast<const void *>(Offsets::FUN_SIMPLEMODELFFX_LIGHT_FOG_CB);
for (auto *child = Game::Read<uint8_t *>(instance, Offsets::OFF_MODEL_INSTANCE_CHILD_HEAD);
child != nullptr;
child = Game::Read<uint8_t *>(child, Offsets::OFF_MODEL_INSTANCE_CHILD_NEXT))
setRenderCb(child, lightFogCb, frame);
M2::SetRenderCallback(child, lightFogCb, frame);
}
bool PumpDresser(void *dresser) {
@@ -410,11 +365,4 @@ void DestroyDresser(void *dresser) {
reinterpret_cast<CharCompDestroy_t>(Offsets::FUN_CHARCOMP_DESTROY)(dresser);
}
void PrepareForReload() {
for (auto &job : g_jobs)
CancelJob(job);
}
static const Game::ReloadAutoRegister _reloadReg{&PrepareForReload};
} // namespace Model::DisplayInfo
-5
View File
@@ -31,9 +31,4 @@ void DestroyDresser(void *dresser);
// `frame` gives its own model, so the children are lit like it.
void LightChildren(void *frame, void *instance);
// The reload teardown destroys every Model frame (and with it the model
// instances our pending character-dress jobs point at). Drop all jobs and
// destroy their engine compositors before the engine starts tearing down.
void PrepareForReload();
} // namespace Model::DisplayInfo
+79 -60
View File
@@ -73,6 +73,8 @@
#include "item/ID.h"
#include "item/Location.h"
#include "item/Record.h"
#include "model/ItemDisplay.h"
#include "model/M2.h"
#include "object/Resolve.h"
#include <cstdint>
@@ -94,9 +96,7 @@ using TryOnItem_t = void(__thiscall *)(void *pane, uint32_t itemID);
using SetModelInstance_t = void(__thiscall *)(void *pane, void *model);
using ClearModel_t = void(__thiscall *)(void *pane);
using GetHeldItemDisplay_t = const void *(__thiscall *)(void *unit, uint32_t hand);
using ModelLoaded_t = int(__thiscall *)(void *model, int tryLoad, int recurseChildren);
using FindChild_t = void *(__thiscall *)(void *model, int attachId);
using AttachItemVisuals_t = void(__fastcall *)(void *model, uint32_t visualID);
using GetVisibleItem_t = const void *(__thiscall *)(void *player, uint32_t slot);
using GetDescVisual_t = int(__thiscall *)(const void *visibleItem);
using DisplayError_t = void(__cdecl *)(uint32_t id, ...);
@@ -104,8 +104,6 @@ using DisplayError_t = void(__cdecl *)(uint32_t id, ...);
// Body slots of the two held lanes.
constexpr uint32_t kBodySlotMainHand = 0xF;
constexpr uint32_t kBodySlotOffHand = 0x10;
constexpr uint8_t kInvTypeShield = 14;
constexpr uint8_t kInvTypeRangedRight = 26;
// Retail's Enum.ItemTryOnReason.
constexpr int kTryOnSuccess = 0;
@@ -113,6 +111,16 @@ constexpr int kTryOnWrongRace = 1;
constexpr int kTryOnNotEquippable = 2;
constexpr int kTryOnDataPending = 3;
// A held lane (OFF_DRESSUPMODEL_*_HAND_LANE).
struct HeldLane {
uint8_t itemClass; // 0 = empty
uint8_t unknown1[2];
uint8_t inventoryType;
uint8_t sheatheType;
uint8_t unknown5[3];
};
static_assert(sizeof(HeldLane) == 8, "ClearModel zeroes 8 bytes per lane");
struct Lane {
uintptr_t offset;
uint32_t bodySlot;
@@ -140,12 +148,8 @@ bool g_dressing = false; // Dress() is rebuilding the pane
uintptr_t g_mainHandPath = Offsets::TRY_ON_MAIN_HAND_PATH;
uintptr_t g_offHandPath = Offsets::TRY_ON_OFF_HAND_PATH;
void *ResolvePane(void *L) {
return Game::Lua::ResolveTypedObject(L, 1, Offsets::VAR_DRESSUPMODEL_LUA_TYPE_ID);
}
void *PaneModel(void *pane) {
return Game::Read<void *>(pane, Offsets::OFF_SIMPLEMODELFFX_MODEL_INSTANCE);
HeldLane &LaneOf(void *pane, int i) {
return Game::Ref<HeldLane>(pane, kLanes[i].offset);
}
int LaneIndex(uint32_t bodySlot) {
@@ -153,12 +157,10 @@ int LaneIndex(uint32_t bodySlot) {
}
// The glow `enchantID` gives an item shown with `display`: none when the
// display has its own ItemVisuals row (GetItemDescVisual 0x0062EC70).
// display has its own.
uint32_t EnchantVisual(const void *display, uint32_t enchantID) {
if (display == nullptr ||
DBC::Record(Offsets::VAR_ITEMVISUALS_RECORDS, Offsets::VAR_ITEMVISUALS_COUNT,
Game::Read<uint32_t>(display, Offsets::OFF_ITEMDISPLAYINFO_ITEM_VISUAL)) !=
nullptr)
Model::ItemDisplay::OwnVisual(static_cast<const uint8_t *>(display)) != 0)
return 0;
const uint8_t *enchant = DBC::Record(Offsets::VAR_SPELLITEMENCHANT_RECORDS,
Offsets::VAR_SPELLITEMENCHANT_COUNT, enchantID);
@@ -174,27 +176,33 @@ void HangVisual(void *model, int attachId, uint32_t visual) {
return;
if (void *item = reinterpret_cast<FindChild_t>(Offsets::FUN_M2MODEL_FIND_CHILD)(model,
attachId))
reinterpret_cast<AttachItemVisuals_t>(Offsets::FUN_ATTACH_ITEM_VISUALS)(item, visual);
M2::AttachItemVisuals(item, visual);
}
// DetachHeldItemSlot on lane `i`'s item.
void DetachLane(void *model, int i, const HeldLane &lane) {
reinterpret_cast<DetachHeldItem_t>(Offsets::FUN_DETACH_HELD_ITEM_SLOT)(
model, kLanes[i].bodySlot, lane.sheatheType,
lane.inventoryType == Offsets::INVTYPE_SHIELD);
}
// The unit's AttachHandItem step on each occupied lane: detach it, then hang
// it in the hand or the sheath, or leave it off.
void ApplyWeapons(void *pane, const PaneState &s) {
void *model = PaneModel(pane);
void *model = Pane::Model(pane);
if (model == nullptr)
return;
for (int i = 0; i < 2; ++i) {
const uint8_t *lane = Game::Ptr<uint8_t>(pane, kLanes[i].offset);
if (lane[0] == 0 || s.laneDisplay[i] == nullptr)
const HeldLane &lane = LaneOf(pane, i);
if (lane.itemClass == 0 || s.laneDisplay[i] == nullptr)
continue;
const bool shield = lane[3] == kInvTypeShield;
reinterpret_cast<DetachHeldItem_t>(Offsets::FUN_DETACH_HELD_ITEM_SLOT)(
model, kLanes[i].bodySlot, lane[4], shield);
DetachLane(model, i, lane);
if (s.hideWeapons)
continue;
const int attachId = reinterpret_cast<AttachItemModel_t>(Offsets::FUN_ATTACH_ITEM_MODEL)(
model, s.laneDisplay[i], kLanes[i].bodySlot, lane[4], s.sheathed, shield,
lane[3] == kInvTypeRangedRight);
model, s.laneDisplay[i], kLanes[i].bodySlot, lane.sheatheType, s.sheathed,
lane.inventoryType == Offsets::INVTYPE_SHIELD,
lane.inventoryType == Offsets::INVTYPE_RANGEDRIGHT);
HangVisual(model, attachId, s.laneVisual[i]);
}
}
@@ -265,8 +273,9 @@ int __fastcall TryOnAttach(void *model, const void *record, uint32_t bodySlot,
void __fastcall TryOnEvict(void *model, uint32_t bodySlot, uint32_t sheatheType, bool shield) {
if (g_tryOnPane != nullptr) {
PaneState &s = g_panes[g_tryOnPane];
s.laneDisplay[LaneIndex(bodySlot)] = nullptr;
s.laneVisual[LaneIndex(bodySlot)] = 0;
const int lane = LaneIndex(bodySlot);
s.laneDisplay[lane] = nullptr;
s.laneVisual[lane] = 0;
}
reinterpret_cast<DetachHeldItem_t>(Offsets::FUN_DETACH_HELD_ITEM_SLOT)(model, bodySlot,
sheatheType, shield);
@@ -294,14 +303,12 @@ __declspec(naked) void HandChoiceThunk() {
}
// ClearModel's lane step: detach the held item and zero the lane.
void ClearLane(void *pane, uintptr_t laneOffset, uint32_t bodySlot) {
uint8_t *lane = Game::Ptr<uint8_t>(pane, laneOffset);
if (lane[0] == 0)
void ClearLane(void *pane, int i) {
HeldLane &lane = LaneOf(pane, i);
if (lane.itemClass == 0)
return;
reinterpret_cast<DetachHeldItem_t>(Offsets::FUN_DETACH_HELD_ITEM_SLOT)(
Game::Read<void *>(pane, Offsets::OFF_SIMPLEMODELFFX_MODEL_INSTANCE), bodySlot, lane[4],
lane[3] == kInvTypeShield);
std::memset(lane, 0, 8);
DetachLane(Pane::Model(pane), i, lane);
lane = HeldLane{};
}
// Component slot for a 0-based inventory slot, or -1 (4.3.4 UndressSlot).
@@ -323,7 +330,7 @@ int ComponentSlot(int slot0) {
}
int __fastcall Script_UndressSlot(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolveDressUpModel(L);
if (pane == nullptr)
return 0;
if (!Game::Lua::IsNumber(L, 2)) {
@@ -332,13 +339,12 @@ int __fastcall Script_UndressSlot(void *L) {
}
const int slot0 = static_cast<int>(Game::Lua::ToNumber(L, 2)) - 1;
void *component = Game::Read<void *>(pane, Offsets::OFF_DRESSUPMODEL_COMPONENT);
if (Game::Read<void *>(pane, Offsets::OFF_SIMPLEMODELFFX_MODEL_INSTANCE) == nullptr ||
component == nullptr)
if (Pane::Model(pane) == nullptr || component == nullptr)
return 0;
if (slot0 == 15) {
ClearLane(pane, Offsets::OFF_DRESSUPMODEL_MAIN_HAND_LANE, kBodySlotMainHand);
} else if (slot0 == 16) {
ClearLane(pane, Offsets::OFF_DRESSUPMODEL_OFF_HAND_LANE, kBodySlotOffHand);
if (slot0 + 1 == Offsets::INVSLOT_MAINHAND) {
ClearLane(pane, 0);
} else if (slot0 + 1 == Offsets::INVSLOT_OFFHAND) {
ClearLane(pane, 1);
} else {
const int compSlot = ComponentSlot(slot0);
if (compSlot >= 0)
@@ -399,8 +405,8 @@ uint32_t NamedHand(const char *name) {
name) != 0)
continue;
switch (Game::Read<int>(row, Offsets::OFF_PAPERDOLLITEMFRAME_SLOT_NUMBER)) {
case 16: return kBodySlotMainHand;
case 17: return kBodySlotOffHand;
case Offsets::INVSLOT_MAINHAND: return kBodySlotMainHand;
case Offsets::INVSLOT_OFFHAND: return kBodySlotOffHand;
default: return 0;
}
}
@@ -413,17 +419,35 @@ int TryOnResult(void *pane, uint32_t itemID) {
if (record == nullptr)
return kTryOnDataPending;
if (Game::Read<uint32_t>(record, Offsets::OFF_ITEMSTATS_DISPLAY_INFO_ID) == 0 ||
PaneModel(pane) == nullptr)
Pane::Model(pane) == nullptr)
return kTryOnNotEquippable;
switch (Game::Read<uint32_t>(record, Offsets::OFF_ITEMSTATS_INVENTORY_TYPE)) {
case 1: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10:
case 16: case 19: case 20:
case Offsets::INVTYPE_HEAD:
case Offsets::INVTYPE_SHOULDER:
case Offsets::INVTYPE_BODY:
case Offsets::INVTYPE_CHEST:
case Offsets::INVTYPE_WAIST:
case Offsets::INVTYPE_LEGS:
case Offsets::INVTYPE_FEET:
case Offsets::INVTYPE_WRIST:
case Offsets::INVTYPE_HAND:
case Offsets::INVTYPE_CLOAK:
case Offsets::INVTYPE_TABARD:
case Offsets::INVTYPE_ROBE:
// The armor arms: the component and the ready byte.
return Game::Read<void *>(pane, Offsets::OFF_DRESSUPMODEL_COMPONENT) != nullptr &&
Game::Read<uint8_t>(pane, Offsets::OFF_DRESSUPMODEL_READY) != 0
? kTryOnSuccess
: kTryOnNotEquippable;
case 13: case 14: case 15: case 17: case 21: case 22: case 23: case 25: case 26:
case Offsets::INVTYPE_WEAPON:
case Offsets::INVTYPE_SHIELD:
case Offsets::INVTYPE_RANGED:
case Offsets::INVTYPE_2HWEAPON:
case Offsets::INVTYPE_WEAPONMAINHAND:
case Offsets::INVTYPE_WEAPONOFFHAND:
case Offsets::INVTYPE_HOLDABLE:
case Offsets::INVTYPE_THROWN:
case Offsets::INVTYPE_RANGEDRIGHT:
return kTryOnSuccess; // the held arm
default:
return kTryOnNotEquippable;
@@ -431,7 +455,7 @@ int TryOnResult(void *pane, uint32_t itemID) {
}
int __fastcall Script_TryOn(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolveDressUpModel(L);
if (pane == nullptr)
return 0;
// Read before the name search below, which stomps the Lua stack.
@@ -484,18 +508,16 @@ int __fastcall Script_Dress(void *L) {
}
int __fastcall Script_IsGeoReady(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolveDressUpModel(L);
if (pane == nullptr)
return 0;
void *model = PaneModel(pane);
Game::Lua::PushBool(L, model != nullptr &&
reinterpret_cast<ModelLoaded_t>(Offsets::FUN_MODEL_INSTANCE_LOADED)(
model, 0, 1) != 0);
void *model = Pane::Model(pane);
Game::Lua::PushBool(L, model != nullptr && M2::IsLoaded(model, 0, 1));
return 1;
}
int __fastcall Script_SetSheathed(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolveDressUpModel(L);
if (pane == nullptr)
return 0;
PaneState &s = g_panes[pane];
@@ -507,7 +529,7 @@ int __fastcall Script_SetSheathed(void *L) {
}
int __fastcall Script_GetSheathed(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolveDressUpModel(L);
if (pane == nullptr)
return 0;
const auto it = g_panes.find(pane);
@@ -516,13 +538,13 @@ int __fastcall Script_GetSheathed(void *L) {
}
int __fastcall Script_SetAutoDress(void *L) {
if (void *pane = ResolvePane(L))
if (void *pane = Game::Lua::ResolveDressUpModel(L))
g_panes[pane].autoDress = Game::Lua::ToBoolean(L, 2) != 0;
return 0;
}
int __fastcall Script_GetAutoDress(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolveDressUpModel(L);
if (pane == nullptr)
return 0;
const auto it = g_panes.find(pane);
@@ -618,11 +640,8 @@ void RegisterLuaFunctions() {
reinterpret_cast<const void *>(&SetModelInstance));
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_DRESSUPMODEL_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
Game::Lua::RegisterIntegerEnum(
"Enum", "ItemTryOnReason", kItemTryOnReason,
static_cast<int>(sizeof(kItemTryOnReason) / sizeof(kItemTryOnReason[0])), "UIModelInfo");
g_methodDocs);
Game::Lua::RegisterIntegerEnum("Enum", "ItemTryOnReason", kItemTryOnReason, "UIModelInfo");
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+2 -2
View File
@@ -47,8 +47,8 @@ const Game::Doc::Method g_methodDocs[] = {
};
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_MODEL_METHOD_REGISTRY),
g_methods, 1, g_methodDocs, 1);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_MODEL_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+37 -55
View File
@@ -53,6 +53,7 @@
#include "item/Record.h"
#include "model/DisplayInfo.h"
#include "model/ItemDisplay.h"
#include "model/M2.h"
#include "texture/Load.h"
#include <cstdint>
@@ -64,24 +65,9 @@ namespace Model::ItemDisplay {
namespace {
using PaneRecordFn_t = void(__thiscall *)(void *pane, const uint8_t *record);
using SetModelInstance_t = void(__thiscall *)(void *frame, void *model);
using GetScene_t = void *(__thiscall *)(void *frame);
using CreateModel_t = void *(__thiscall *)(void *scene, const char *path, uint32_t flags);
using ModelRelease_t = void(__fastcall *)(void *model);
using TextureLoad_t = void *(__fastcall *)(const char *path, void *status, uint32_t flags,
int arg4, int arg5);
using ReplaceTexture_t = void(__thiscall *)(void *model, uint32_t type, void *texture);
using AttachItemVisuals_t = void(__fastcall *)(void *model, uint32_t visualID);
using SetAnimDone_t = void(__thiscall *)(void *model, const void *callback, uint32_t userLow,
uint32_t userHigh);
using PlayAnimation_t = void(__thiscall *)(void *model, int keyBone, uint32_t id, int variation,
int offset, float speed, int blend, int primary);
constexpr uint32_t kInvTypeHead = 1;
constexpr uint32_t kInvTypeShoulders = 3;
constexpr uint32_t kInvTypeShield = 14;
constexpr uint32_t kInvTypeAmmo = 24;
constexpr uint32_t kInvTypeQuiver = 27;
// The texture type an item model takes its skin as.
constexpr uint32_t kItemSkinTexture = 2;
@@ -90,27 +76,25 @@ constexpr uint32_t kItemTextureFilter = 3;
constexpr int kSexLetters = 3;
constexpr size_t kPathSize = 260;
// The record a pane shows for SetItem. The pane reads only the display-ID
// field, which stays 0. One per pane, never freed while the UI lives: the pane
// keeps the pointer and builds from it on every show.
// The record a pane shows for SetItem; its display ID stays 0.
struct ItemRecord {
uint8_t display[Offsets::OFF_CREATURE_DISPLAYID + sizeof(uint32_t)];
uint32_t itemID;
uint32_t visual;
Pane::DisplayRecord display;
uint32_t itemID = 0;
uint32_t visual = 0;
};
std::unordered_map<void *, ItemRecord> g_records;
const char *Directory(uint32_t inventoryType) {
switch (inventoryType) {
case kInvTypeHead:
case Offsets::INVTYPE_HEAD:
return "Item\\ObjectComponents\\Head\\";
case kInvTypeShoulders:
case Offsets::INVTYPE_SHOULDER:
return "Item\\ObjectComponents\\Shoulder\\";
case kInvTypeShield:
case Offsets::INVTYPE_SHIELD:
return "Item\\ObjectComponents\\Shield\\";
case kInvTypeAmmo:
case Offsets::INVTYPE_AMMO:
return "Item\\ObjectComponents\\Ammo\\";
case kInvTypeQuiver:
case Offsets::INVTYPE_QUIVER:
return "Item\\ObjectComponents\\Quiver\\";
default:
return "Item\\ObjectComponents\\Weapon\\";
@@ -166,15 +150,13 @@ void *CreateItemModel(void *pane, const ItemRecord &rec) {
const char *dir = Directory(inventoryType);
char path[kPathSize];
if (inventoryType == kInvTypeHead) {
if (inventoryType == Offsets::INVTYPE_HEAD) {
if (!HeadPath(path, dir, name))
return nullptr;
} else {
std::snprintf(path, sizeof(path), "%s%s", dir, name);
}
void *scene = reinterpret_cast<GetScene_t>(Offsets::FUN_SIMPLEMODEL_GET_SCENE)(pane);
void *model =
reinterpret_cast<CreateModel_t>(Offsets::FUN_M2SCENE_CREATE_MODEL)(scene, path, 0);
void *model = M2::CreateModel(Pane::Scene(pane), path);
if (model == nullptr)
return nullptr;
@@ -186,7 +168,7 @@ void *CreateItemModel(void *pane, const ItemRecord &rec) {
path, reinterpret_cast<void *>(Offsets::VAR_ITEM_MODEL_TEXTURE_STATUS),
Texture::Flags(kItemTextureFilter, false, false), 1, 0);
if (handle == nullptr) {
reinterpret_cast<ModelRelease_t>(Offsets::FUN_MODEL_INSTANCE_RELEASE)(model);
M2::Release(model);
return nullptr;
}
reinterpret_cast<ReplaceTexture_t>(Offsets::FUN_M2MODEL_REPLACE_TEXTURE)(
@@ -194,13 +176,10 @@ void *CreateItemModel(void *pane, const ItemRecord &rec) {
Texture::ReleaseHandle(handle);
}
const uint32_t own = Game::Read<uint32_t>(display, Offsets::OFF_ITEMDISPLAYINFO_ITEM_VISUAL);
const uint32_t visual = DBC::Record(Offsets::VAR_ITEMVISUALS_RECORDS,
Offsets::VAR_ITEMVISUALS_COUNT, own) != nullptr
? own
: rec.visual;
const uint32_t own = OwnVisual(display);
const uint32_t visual = own != 0 ? own : rec.visual;
if (visual != 0) {
reinterpret_cast<AttachItemVisuals_t>(Offsets::FUN_ATTACH_ITEM_VISUALS)(model, visual);
M2::AttachItemVisuals(model, visual);
Model::DisplayInfo::LightChildren(pane, model);
}
return model;
@@ -210,17 +189,15 @@ void *CreateItemModel(void *pane, const ItemRecord &rec) {
// for the item shows nothing.
void Show(void *pane, const ItemRecord &rec) {
void *model = CreateItemModel(pane, rec);
Game::Read<SetModelInstance_t>(Game::Read<const void *>(pane, 0),
Offsets::VTBL_SIMPLEMODEL_SET_MODEL_INSTANCE)(pane, model);
Pane::SetModel(pane, model);
if (model == nullptr)
return;
const auto user = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(pane));
reinterpret_cast<SetAnimDone_t>(Offsets::FUN_M2MODEL_SET_ANIM_DONE_CALLBACK)(
model, reinterpret_cast<const void *>(Offsets::FUN_PLAYERMODEL_ON_ANIM_DONE), user,
static_cast<int32_t>(user) < 0 ? 0xFFFFFFFFu : 0u);
reinterpret_cast<PlayAnimation_t>(Offsets::FUN_M2MODEL_PLAY_ANIMATION)(model, -1, 0, -1, 0,
1.0f, 1, 1);
reinterpret_cast<ModelRelease_t>(Offsets::FUN_MODEL_INSTANCE_RELEASE)(model);
M2::SetAnimDoneCallback(model,
reinterpret_cast<const void *>(Offsets::FUN_PLAYERMODEL_ON_ANIM_DONE),
user, static_cast<int32_t>(user) < 0 ? 0xFFFFFFFFu : 0u);
M2::PlayAnimation(model, -1, 0, -1, 0, 1.0f, 1, 1);
M2::Release(model);
}
// A queued item has loaded: build each visible pane that still shows it,
@@ -229,18 +206,16 @@ void OnItemLoaded(uint32_t itemID, bool success) {
if (!success)
return;
for (auto &[pane, rec] : g_records)
if (rec.itemID == itemID &&
Game::Read<const uint8_t *>(pane, Offsets::OFF_PLAYERMODEL_DISPLAY_RECORD) ==
rec.display &&
Game::Read<int>(pane, Offsets::OFF_FRAME_VISIBLE) != 0)
if (rec.itemID == itemID && Pane::DisplayRecordOf(pane) == rec.display.bytes &&
Pane::IsVisible(pane))
reinterpret_cast<PaneRecordFn_t>(Offsets::FUN_PLAYERMODEL_ATTACH_DISPLAY_MODEL)(
pane, rec.display);
pane, rec.display.bytes);
}
const ::Item::Data::LoadSubscriber _loadSub{&OnItemLoaded};
int __fastcall Script_SetItem(void *L) {
void *pane = Game::Lua::ResolveTypedObject(L, 1, Offsets::VAR_PLAYERMODEL_LUA_TYPE_ID);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
if (!Game::Lua::IsNumber(L, 2)) {
@@ -252,7 +227,7 @@ int __fastcall Script_SetItem(void *L) {
rec.itemID = static_cast<uint32_t>(Game::Lua::ToNumber(L, 2));
if (Game::Lua::IsNumber(L, 4))
rec.visual = static_cast<uint32_t>(Game::Lua::ToNumber(L, 4));
reinterpret_cast<PaneRecordFn_t>(Offsets::FUN_PLAYERMODEL_SET_DISPLAY)(pane, rec.display);
Pane::SetDisplay(pane, rec.display.bytes);
return 0;
}
@@ -274,8 +249,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_PLAYERMODEL_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
@@ -289,9 +263,17 @@ const Game::ReloadAutoRegister _reloadReg{&PrepareForReload};
} // namespace
uint32_t OwnVisual(const uint8_t *display) {
const uint32_t own = Game::Read<uint32_t>(display, Offsets::OFF_ITEMDISPLAYINFO_ITEM_VISUAL);
return DBC::Record(Offsets::VAR_ITEMVISUALS_RECORDS, Offsets::VAR_ITEMVISUALS_COUNT, own) !=
nullptr
? own
: 0;
}
bool Attach(void *pane, const uint8_t *record) {
const auto it = g_records.find(pane);
if (record == nullptr || it == g_records.end() || record != it->second.display)
if (record == nullptr || it == g_records.end() || record != it->second.display.bytes)
return false;
if (::Item::PeekRecord(it->second.itemID) == nullptr)
::Item::Data::WarmCache(it->second.itemID);
+5
View File
@@ -22,4 +22,9 @@ namespace Model::ItemDisplay {
// other record returns false and is the engine's to build.
bool Attach(void *pane, const uint8_t *record);
// The ItemVisuals row an ItemDisplayInfo `display` hangs itself, or 0 when it
// names none that exists. An enchant's glow shows only when this is 0
// (GetItemDescVisual 0x0062EC70).
uint32_t OwnVisual(const uint8_t *display);
} // namespace Model::ItemDisplay
+14 -23
View File
@@ -30,6 +30,7 @@
#include "Game.h"
#include "Offsets.h"
#include "model/M2.h"
#include <cstdint>
#include <unordered_set>
@@ -40,7 +41,6 @@ namespace {
using PaneFn_t = void(__thiscall *)(void *pane);
using SetUnitGuid_t = void(__thiscall *)(void *pane, uint64_t guid);
using SetDisplay_t = void(__thiscall *)(void *pane, const uint8_t *record);
using SetModelInstance_t = void(__thiscall *)(void *pane, void *model);
PaneFn_t g_origHide = nullptr;
PaneFn_t g_origShow = nullptr;
@@ -53,19 +53,16 @@ bool Keeps(void *pane) {
return g_keeping.count(pane) != 0;
}
bool HasModel(void *pane) {
return Game::Read<void *>(pane, Offsets::OFF_SIMPLEMODELFFX_MODEL_INSTANCE) != nullptr;
}
// The pane Hide's own drop: slot 36 with null.
void DropModel(void *pane) {
Game::Read<SetModelInstance_t>(Game::Read<const void *>(pane, 0),
Offsets::VTBL_SIMPLEMODEL_SET_MODEL_INSTANCE)(pane, nullptr);
// Hide's own drop (slot 36 with null), for a hidden pane still holding a
// model.
void DropIfHidden(void *pane) {
if (!Pane::IsVisible(pane) && Pane::Model(pane) != nullptr)
Pane::SetModel(pane, nullptr);
}
void DropIfKeptWhileHidden(void *pane) {
if (Game::Read<int>(pane, Offsets::OFF_FRAME_VISIBLE) == 0 && HasModel(pane) && Keeps(pane))
DropModel(pane);
if (Keeps(pane))
DropIfHidden(pane);
}
void __fastcall Hide_h(void *pane, void * /*edx*/) {
@@ -77,7 +74,7 @@ void __fastcall Hide_h(void *pane, void * /*edx*/) {
}
void __fastcall Show_h(void *pane, void * /*edx*/) {
if (Keeps(pane) && HasModel(pane)) {
if (Keeps(pane) && Pane::Model(pane) != nullptr) {
reinterpret_cast<PaneFn_t>(Offsets::FUN_SIMPLEFRAME_SHOW)(pane);
return;
}
@@ -107,26 +104,21 @@ const Game::HookAutoRegister _setDisplayHook{Offsets::FUN_PLAYERMODEL_SET_DISPLA
reinterpret_cast<void *>(&SetDisplay_h),
reinterpret_cast<void **>(&g_origSetDisplay)};
void *ResolvePane(void *L) {
return Game::Lua::ResolveTypedObject(L, 1, Offsets::VAR_PLAYERMODEL_LUA_TYPE_ID);
}
int __fastcall Script_SetKeepModelOnHide(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
if (Game::Lua::ToBoolean(L, 2) != 0) {
g_keeping.insert(pane);
return 0;
}
if (g_keeping.erase(pane) != 0 && Game::Read<int>(pane, Offsets::OFF_FRAME_VISIBLE) == 0 &&
HasModel(pane))
DropModel(pane);
if (g_keeping.erase(pane) != 0)
DropIfHidden(pane);
return 0;
}
int __fastcall Script_GetKeepModelOnHide(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
Game::Lua::PushBool(L, Keeps(pane));
@@ -156,8 +148,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_PLAYERMODEL_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+148
View File
@@ -0,0 +1,148 @@
// 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/>.
// Typed calls into the engine's M2 model instances and the Model frames
// ("panes") that show them, shared by every model/ module.
#pragma once
#include "Game.h"
#include "Offsets.h"
#include <cstdint>
namespace Model::M2 {
inline bool IsLoaded(void *model, int tryLoad, int recurseChildren) {
using Fn = int(__thiscall *)(void *, int, int);
return reinterpret_cast<Fn>(Offsets::FUN_MODEL_INSTANCE_LOADED)(model, tryLoad,
recurseChildren) != 0;
}
// The draw gate: also makes the device textures when `wait` is set.
inline bool IsReady(void *model, int wait, int recurseChildren) {
using Fn = int(__thiscall *)(void *, int, int);
return reinterpret_cast<Fn>(Offsets::FUN_MODEL_INSTANCE_READY)(model, wait,
recurseChildren) != 0;
}
inline void AddRef(void *model) {
reinterpret_cast<void(__fastcall *)(void *)>(Offsets::FUN_MODEL_INSTANCE_ADDREF)(model);
}
inline void Release(void *model) {
reinterpret_cast<void(__fastcall *)(void *)>(Offsets::FUN_MODEL_INSTANCE_RELEASE)(model);
}
// A new model in `scene` that the caller owns one reference on, or null.
inline void *CreateModel(void *scene, const char *path) {
using Fn = void *(__thiscall *)(void *, const char *, uint32_t);
return reinterpret_cast<Fn>(Offsets::FUN_M2SCENE_CREATE_MODEL)(scene, path, 0);
}
inline void PlayAnimation(void *model, int keyBone, uint32_t id, int variation, int offset,
float speed, int blend, int primary) {
using Fn = void(__thiscall *)(void *, int, uint32_t, int, int, float, int, int);
reinterpret_cast<Fn>(Offsets::FUN_M2MODEL_PLAY_ANIMATION)(model, keyBone, id, variation,
offset, speed, blend, primary);
}
inline void SetAnimDoneCallback(void *model, const void *callback, uint32_t userLow,
uint32_t userHigh) {
using Fn = void(__thiscall *)(void *, const void *, uint32_t, uint32_t);
reinterpret_cast<Fn>(Offsets::FUN_M2MODEL_SET_ANIM_DONE_CALLBACK)(model, callback, userLow,
userHigh);
}
inline void SetVisible(void *model, bool visible) {
using Fn = void(__thiscall *)(void *, int);
reinterpret_cast<Fn>(Offsets::FUN_M2MODEL_SET_VISIBLE)(model, visible ? 1 : 0);
}
inline void SetAnimated(void *model, bool animated) {
using Fn = void(__thiscall *)(void *, int);
reinterpret_cast<Fn>(Offsets::FUN_M2MODEL_SET_ANIMATED)(model, animated ? 1 : 0);
}
inline void AnimateScene(void *scene, const float *cameraPos) {
using Fn = void(__thiscall *)(void *, const float *);
reinterpret_cast<Fn>(Offsets::FUN_M2SCENE_ANIMATE)(scene, cameraPos);
}
inline void AttachItemVisuals(void *model, uint32_t visualID) {
using Fn = void(__fastcall *)(void *, uint32_t);
reinterpret_cast<Fn>(Offsets::FUN_ATTACH_ITEM_VISUALS)(model, visualID);
}
inline void SetRenderCallback(void *model, const void *callback, void *userData) {
using Fn = void(__thiscall *)(void *, const void *, void *);
reinterpret_cast<Fn>(Offsets::FUN_MODEL_INSTANCE_SET_RENDER_CB)(model, callback, userData);
}
// The model's shared M2 data (the file's header and tables).
inline const uint8_t *Data(void *model) {
return Game::Read<const uint8_t *>(Game::Read<const uint8_t *>(model, Offsets::OFF_M2MODEL_SHARED),
Offsets::OFF_M2SHARED_DATA);
}
// The index `id` maps to in one of the data's lookup tables
// (OFF_M2DATA_*_LOOKUP); the caller bounds it by that table's count.
inline uint16_t LookupIndex(void *model, uintptr_t tableOffset, uint32_t id) {
using Fn = uint16_t(__fastcall *)(uint32_t, const void *);
return reinterpret_cast<Fn>(Offsets::FUN_M2_LOOKUP_INDEX)(id, Data(model) + tableOffset);
}
} // namespace Model::M2
namespace Model::Pane {
// The model instance a Model frame shows, or null.
inline void *Model(const void *pane) {
return Game::Read<void *>(pane, Offsets::OFF_SIMPLEMODELFFX_MODEL_INSTANCE);
}
inline void *Scene(void *pane) {
using Fn = void *(__thiscall *)(void *);
return reinterpret_cast<Fn>(Offsets::FUN_SIMPLEMODEL_GET_SCENE)(pane);
}
// The frame's SetModelInstance virtual; null drops its model.
inline void SetModel(void *pane, void *model) {
using Fn = void(__thiscall *)(void *, void *);
Game::Read<Fn>(Game::Read<const void *>(pane, 0),
Offsets::VTBL_SIMPLEMODEL_SET_MODEL_INSTANCE)(pane, model);
}
// PlayerModel's SetDisplay: stores the display record the pane builds from.
inline void SetDisplay(void *pane, const uint8_t *record) {
using Fn = void(__thiscall *)(void *, const uint8_t *);
reinterpret_cast<Fn>(Offsets::FUN_PLAYERMODEL_SET_DISPLAY)(pane, record);
}
inline bool IsVisible(const void *pane) {
return Game::Read<int>(pane, Offsets::OFF_FRAME_VISIBLE) != 0;
}
// The display record the pane shows (SetDisplay's), or null.
inline const uint8_t *DisplayRecordOf(const void *pane) {
return Game::Read<const uint8_t *>(pane, Offsets::OFF_PLAYERMODEL_DISPLAY_RECORD);
}
// A display record of our own for SetDisplay. The pane reads only its
// display-ID field, keeps the pointer and builds from it on every show, so
// the record lives as long as the pane.
struct DisplayRecord {
uint8_t bytes[Offsets::OFF_CREATURE_DISPLAYID + sizeof(uint32_t)] = {};
};
} // namespace Model::Pane
+18 -33
View File
@@ -54,6 +54,7 @@
#include "dbc/Lookup.h"
#include "model/Camera.h"
#include "model/DisplayInfo.h"
#include "model/M2.h"
#include "texture/Load.h"
#include "tick/FrameTick.h"
@@ -71,19 +72,11 @@ constexpr uint32_t kTexels = kSize * kSize;
using CreateScene_t = void *(__cdecl *)();
using ReleaseScene_t = void(__thiscall *)(void *scene);
using CreateModel_t = void *(__thiscall *)(void *scene, const char *path, uint32_t flags);
using CopyModel_t = void *(__thiscall *)(void *scene, void *source, uint32_t flags);
using ApplyDisplay_t = void(__fastcall *)(void *model, const uint8_t *display,
const uint8_t *modelData);
using ReleaseModel_t = void(__fastcall *)(void *model);
using ModelState_t = int(__thiscall *)(void *model, int wait, int recurseChildren);
using SetFlag_t = void(__thiscall *)(void *model, int on);
using PlayAnimation_t = void(__thiscall *)(void *model, int, int, int, int, float, int, int);
using SetRenderCb_t = void(__thiscall *)(void *model, const void *cb, void *userData);
using DetachAll_t = void(__fastcall *)(void *model);
using AnimateScene_t = void(__thiscall *)(void *scene, const float *cameraPos);
using DrawScene_t = void(__thiscall *)(void *scene, uint32_t pass);
using GetVector_t = void(__fastcall *)(void *camera, uint32_t id, float *out);
using SetTransforms_t = void(__fastcall *)(void *camera, const float *rect, uint32_t unused);
using GetMatrix_t = void(__fastcall *)(float *m);
using SetMatrix_t = void(__fastcall *)(const float *m);
@@ -130,7 +123,7 @@ std::vector<Portrait *> g_loading;
void ReleaseModel(Portrait &p) {
Model::DisplayInfo::DestroyDresser(p.dresser);
reinterpret_cast<ReleaseModel_t>(Offsets::FUN_MODEL_INSTANCE_RELEASE)(p.model);
M2::Release(p.model);
reinterpret_cast<ReleaseScene_t>(Offsets::FUN_M2SCENE_RELEASE)(p.scene);
p.dresser = p.model = p.scene = nullptr;
p.dressRecord = nullptr;
@@ -166,8 +159,7 @@ bool Bake(Portrait &p) {
}
reinterpret_cast<DetachAll_t>(Offsets::FUN_DETACH_ALL_ITEM_MODELS)(copy);
void *camera = Model::Camera::CameraById(copy, 0);
reinterpret_cast<PlayAnimation_t>(Offsets::FUN_M2MODEL_PLAY_ANIMATION)(copy, -1, 0, 0, 0,
1.0f, 0, 1);
M2::PlayAnimation(copy, -1, 0, 0, 0, 1.0f, 0, 1);
float projection[16];
float view[16];
@@ -188,19 +180,12 @@ bool Bake(Portrait &p) {
toNdc(&x1, &y1, x1, y1);
// First collection: evaluate the portrait camera.
auto isLoaded = reinterpret_cast<ModelState_t>(Offsets::FUN_MODEL_INSTANCE_LOADED);
auto isReady = reinterpret_cast<ModelState_t>(Offsets::FUN_MODEL_INSTANCE_READY);
auto setAnimated = reinterpret_cast<SetFlag_t>(Offsets::FUN_M2MODEL_SET_ANIMATED);
auto animate = reinterpret_cast<AnimateScene_t>(Offsets::FUN_M2SCENE_ANIMATE);
auto getVector = reinterpret_cast<GetVector_t>(Offsets::FUN_CAMERA_GET_VECTOR);
isLoaded(copy, 1, 1);
setAnimated(copy, 1);
M2::IsLoaded(copy, 1, 1);
M2::SetAnimated(copy, true);
const float origin[3] = {0.0f, 0.0f, 0.0f};
animate(scene, origin);
float eye[3] = {};
float target[3] = {};
getVector(camera, Offsets::CAMERA_PROP_EYE, eye);
getVector(camera, Offsets::CAMERA_PROP_TARGET, target);
M2::AnimateScene(scene, origin);
const Model::Camera::Vec3 eye = Model::Camera::GetVector(camera, Offsets::CAMERA_PROP_EYE);
Model::Camera::GetVector(camera, Offsets::CAMERA_PROP_TARGET); // the bake reads it too
const float rect[4] = {0.0f, 0.0f, 1.0f, aspect};
reinterpret_cast<SetTransforms_t>(Offsets::FUN_CAMERA_SET_DEVICE_TRANSFORMS)(camera, rect, 0);
reinterpret_cast<SetViewport_t>(Offsets::FUN_GX_SET_VIEWPORT)(x0, x1, y0, y1, 0.0f, 1.0f);
@@ -209,12 +194,12 @@ bool Bake(Portrait &p) {
clear(3);
// Second collection from the portrait eye, then both passes.
isReady(copy, 1, 1);
setAnimated(copy, 1);
reinterpret_cast<SetFlag_t>(Offsets::FUN_M2MODEL_SET_VISIBLE)(copy, 1);
reinterpret_cast<SetRenderCb_t>(Offsets::FUN_MODEL_INSTANCE_SET_RENDER_CB)(
copy, reinterpret_cast<const void *>(Offsets::FUN_PORTRAIT_LIGHTING), nullptr);
animate(scene, eye);
M2::IsReady(copy, 1, 1);
M2::SetAnimated(copy, true);
M2::SetVisible(copy, true);
M2::SetRenderCallback(copy, reinterpret_cast<const void *>(Offsets::FUN_PORTRAIT_LIGHTING),
nullptr);
M2::AnimateScene(scene, &eye.x);
auto draw = reinterpret_cast<DrawScene_t>(Offsets::FUN_M2SCENE_DRAW);
draw(scene, 0);
draw(scene, 1);
@@ -239,14 +224,14 @@ bool Bake(Portrait &p) {
reinterpret_cast<SetViewport_t>(Offsets::FUN_GX_SET_VIEWPORT)(
viewport[0], viewport[1], viewport[2], viewport[3], viewport[4], viewport[5]);
setMaster(Offsets::GX_MASTER_FOG, fog ? 1 : 0);
reinterpret_cast<ReleaseModel_t>(Offsets::FUN_MODEL_INSTANCE_RELEASE)(copy);
M2::Release(copy);
reinterpret_cast<ReleaseScene_t>(Offsets::FUN_M2SCENE_RELEASE)(scene);
return true;
}
// One frame for one pending display; true once it is baked (or cannot be).
bool Step(Portrait &p) {
if (!reinterpret_cast<ModelState_t>(Offsets::FUN_MODEL_INSTANCE_LOADED)(p.model, 0, 1))
if (!M2::IsLoaded(p.model, 0, 1))
return false;
if (p.dressRecord != nullptr) {
// A dress the engine rejects bakes undressed, as a unit with no
@@ -298,8 +283,8 @@ Portrait *Acquire(uint32_t displayID) {
void *scene = reinterpret_cast<CreateScene_t>(Offsets::FUN_M2_CREATE_SCENE)();
if (scene == nullptr)
return nullptr;
void *model = reinterpret_cast<CreateModel_t>(Offsets::FUN_M2SCENE_CREATE_MODEL)(
scene, Game::Read<const char *>(modelData, Offsets::OFF_CREATUREMODELDATA_MODEL_PATH), 0);
void *model = M2::CreateModel(
scene, Game::Read<const char *>(modelData, Offsets::OFF_CREATUREMODELDATA_MODEL_PATH));
if (model == nullptr) {
reinterpret_cast<ReleaseScene_t>(Offsets::FUN_M2SCENE_RELEASE)(scene);
return nullptr;
+7 -9
View File
@@ -28,17 +28,15 @@
namespace Model::Rotation {
namespace {
constexpr int kTypeNone = -1; // lua_type of an absent argument
using LuaMethod_t = int(__fastcall *)(void *L);
int __fastcall Script_SetRotation(void *L) {
void *pane = Game::Lua::ResolveTypedObject(L, 1, Offsets::VAR_PLAYERMODEL_LUA_TYPE_ID);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
const bool animate = Game::Lua::Type(L, 3) == kTypeNone || Game::Lua::ToBoolean(L, 3) != 0;
const bool animate =
Game::Lua::Type(L, 3) == Game::Lua::TYPE_NONE || Game::Lua::ToBoolean(L, 3) != 0;
if (animate && !Model::Animation::HasRestingAnimation(pane))
return reinterpret_cast<LuaMethod_t>(Offsets::FUN_SCRIPT_PLAYERMODEL_SET_ROTATION)(L);
return reinterpret_cast<Game::Lua::CFunction>(
Offsets::FUN_SCRIPT_PLAYERMODEL_SET_ROTATION)(L);
if (!Game::Lua::IsNumber(L, 2)) {
Game::Lua::Error(L, "Usage: SetRotation(rotation (in radians))");
return 0;
@@ -63,8 +61,8 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_PLAYERMODEL_METHOD_REGISTRY), g_methods, 1,
g_methodDocs, 1);
reinterpret_cast<void *>(Offsets::VAR_PLAYERMODEL_METHOD_REGISTRY), g_methods,
g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+5 -12
View File
@@ -33,20 +33,14 @@
namespace Model::Subject {
namespace {
using LuaMethod_t = int(__fastcall *)(void *L);
void *ResolvePane(void *L) {
return Game::Lua::ResolveTypedObject(L, 1, Offsets::VAR_PLAYERMODEL_LUA_TYPE_ID);
}
void *PaneUnit(void *pane) {
return Object::ByGuid(Offsets::TYPEMASK_UNIT,
Game::Read<uint64_t>(pane, Offsets::OFF_PLAYERMODEL_UNIT_GUID));
}
int __fastcall Script_SetUnit(void *L) {
reinterpret_cast<LuaMethod_t>(Offsets::FUN_SCRIPT_PLAYERMODEL_SET_UNIT)(L);
void *pane = ResolvePane(L);
reinterpret_cast<Game::Lua::CFunction>(Offsets::FUN_SCRIPT_PLAYERMODEL_SET_UNIT)(L);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
Game::Lua::PushBool(L, PaneUnit(pane) != nullptr);
@@ -54,7 +48,7 @@ int __fastcall Script_SetUnit(void *L) {
}
int __fastcall Script_CanSetUnit(void *L) {
if (ResolvePane(L) == nullptr)
if (Game::Lua::ResolvePlayerModel(L) == nullptr)
return 0;
if (!Game::Lua::IsString(L, 2)) {
Game::Lua::Error(L, "Usage: CanSetUnit(unit)");
@@ -65,7 +59,7 @@ int __fastcall Script_CanSetUnit(void *L) {
}
int __fastcall Script_GetDisplayInfo(void *L) {
void *pane = ResolvePane(L);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
uint32_t displayID = 0;
@@ -112,8 +106,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_PLAYERMODEL_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+43
View File
@@ -0,0 +1,43 @@
// 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/>.
#include "model/Teardown.h"
#include "Game.h"
#include "Offsets.h"
namespace Model::Teardown {
namespace {
AutoSubscribe *g_head = nullptr;
using Destroy_t = void(__fastcall *)(void *frame);
Destroy_t g_origDestroy = nullptr;
void __fastcall Destroy_h(void *frame) {
for (auto *node = g_head; node != nullptr; node = node->next)
node->cb(frame);
g_origDestroy(frame);
}
const Game::HookAutoRegister _destroyHook{Offsets::FUN_SIMPLEMODEL_DESTROY,
reinterpret_cast<void *>(&Destroy_h),
reinterpret_cast<void **>(&g_origDestroy)};
} // namespace
AutoSubscribe::AutoSubscribe(Callback cb) : cb(cb), next(g_head) {
g_head = this;
}
} // namespace Model::Teardown
+38
View File
@@ -0,0 +1,38 @@
// 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/>.
#pragma once
// Model frame teardown. A Model frame's destructor (FUN_SIMPLEMODEL_DESTROY)
// releases its M2 scene, and the scene frees every model in it, whatever
// references are still out. Anything that holds an engine object tied to a
// frame's model (a compositor, an effect model) releases it here, the way the
// engine's own frames free theirs in their destructors. The UI teardown
// (CGGameUI::Shutdown, on /reload and logout) destroys every frame long
// before a ReloadAutoRegister cleanup runs, so a reload cleanup is too late
// for this.
//
// Declare a file-scope `const Model::Teardown::AutoSubscribe` in the module's
// TU; the callback runs with the frame and its models still intact.
namespace Model::Teardown {
using Callback = void (*)(void *frame);
struct AutoSubscribe {
explicit AutoSubscribe(Callback cb);
Callback cb;
AutoSubscribe *next;
};
} // namespace Model::Teardown
+55 -54
View File
@@ -42,12 +42,16 @@
//
// The effects are children of the pane's model, so they go when the model
// does: SetUnit, SetItem, a hide and show, and so on. A model destroyed with
// stopped effects still on it detaches them first (0x0070E3F5).
// stopped effects still on it detaches them first (0x0070E3F5). A stopped
// effect is in the pane's scene, which frees it with the frame, so the pane's
// teardown (Model::Teardown) drops our reference first.
#include "Game.h"
#include "Offsets.h"
#include "dbc/Lookup.h"
#include "model/DisplayInfo.h"
#include "model/M2.h"
#include "model/Teardown.h"
#include "tick/FrameTick.h"
#include <cstdint>
@@ -56,19 +60,12 @@
namespace Model::VisualKit {
namespace {
using GetScene_t = void *(__thiscall *)(void *frame);
using CreateModel_t = void *(__thiscall *)(void *scene, const char *path, uint32_t flags);
using ModelFn_t = void(__fastcall *)(void *model);
using HasAttachment_t = int(__thiscall *)(void *model, uint32_t id);
using Attach_t = void(__thiscall *)(void *child, void *parent, uint32_t id);
using SetVisible_t = void(__thiscall *)(void *model, int visible);
using SetAlpha_t = void(__thiscall *)(void *model, float alpha);
using SetDiffuse_t = void(__thiscall *)(void *model, const float *rgb);
using HasLiveParticles_t = int(__thiscall *)(void *model);
using SetAnimDone_t = void(__thiscall *)(void *model, const void *callback, uint32_t userLow,
uint32_t userHigh);
using PlayAnimation_t = void(__thiscall *)(void *model, int keyBone, uint32_t id, int variation,
int offset, float speed, int blend, int primary);
// The attachment of each effect column, in the ghost kit's order.
constexpr uint32_t kAttachments[] = {20, 34, 19, 21, 22, 17, 23, 24, 25};
@@ -78,28 +75,25 @@ constexpr int32_t kProcAlpha = 14;
// The ghost kit's byte scale (0x008026C8).
constexpr float kByteScale = 1.0f / 255.0f;
// Stopped one-shot effects, each with one reference of ours.
std::vector<void *> g_stopped;
// A stopped one-shot effect, with one reference of ours, and the pane whose
// scene holds it.
struct Stopped {
void *pane;
void *model;
};
std::vector<Stopped> g_stopped;
void Release(void *model) {
reinterpret_cast<ModelFn_t>(Offsets::FUN_MODEL_INSTANCE_RELEASE)(model);
}
void SetDoneCallback(void *model, const void *callback) {
reinterpret_cast<SetAnimDone_t>(Offsets::FUN_M2MODEL_SET_ANIM_DONE_CALLBACK)(model, callback,
0, 0);
}
// EffectDoneCallback's test, then CEffect::Stop's model steps.
// EffectDoneCallback's test, then CEffect::Stop's model steps. The callback's
// user data is the pane.
void __fastcall OnEffectDone(void *model, uint32_t /*keyBone*/, uint32_t /*animID*/,
uint32_t reason, uint32_t /*overshoot*/, uint32_t /*userLow*/,
uint32_t reason, uint32_t /*overshoot*/, uint32_t userLow,
uint32_t /*userHigh*/) {
if (reason != 0)
return;
SetDoneCallback(model, nullptr);
reinterpret_cast<SetVisible_t>(Offsets::FUN_M2MODEL_SET_VISIBLE)(model, 0);
reinterpret_cast<ModelFn_t>(Offsets::FUN_MODEL_INSTANCE_ADDREF)(model);
g_stopped.push_back(model);
M2::SetAnimDoneCallback(model, nullptr, 0, 0);
M2::SetVisible(model, false);
M2::AddRef(model);
g_stopped.push_back({reinterpret_cast<void *>(static_cast<uintptr_t>(userLow)), model});
}
// KitEffectLoopCallback: a sequence that ended cleanly plays again.
@@ -108,8 +102,7 @@ void __fastcall OnEffectLoop(void *model, uint32_t /*keyBone*/, uint32_t animID,
uint32_t /*userHigh*/) {
if (reason != 0)
return;
reinterpret_cast<PlayAnimation_t>(Offsets::FUN_M2MODEL_PLAY_ANIMATION)(model, -1, animID, -1,
0, 1.0f, 1, 1);
M2::PlayAnimation(model, -1, animID, -1, 0, 1.0f, 1, 1);
}
// Detaches a stopped effect that is still on its model, then drops our
@@ -117,27 +110,42 @@ void __fastcall OnEffectLoop(void *model, uint32_t /*keyBone*/, uint32_t animID,
void Discard(void *model) {
if (Game::Read<void *>(model, Offsets::OFF_M2MODEL_PARENT) != nullptr)
reinterpret_cast<ModelFn_t>(Offsets::FUN_M2MODEL_DETACH)(model);
Release(model);
M2::Release(model);
}
// The dying-effect drain: a stopped effect goes once its particles are gone,
// or at once when its model has already dropped it.
void Drain() {
// Discards every stopped effect `done` picks.
template <typename Pred>
void DiscardWhere(Pred done) {
for (size_t i = 0; i < g_stopped.size();) {
void *model = g_stopped[i];
if (Game::Read<void *>(model, Offsets::OFF_M2MODEL_PARENT) != nullptr &&
reinterpret_cast<HasLiveParticles_t>(Offsets::FUN_M2MODEL_HAS_LIVE_PARTICLES)(model)) {
if (!done(g_stopped[i])) {
++i;
continue;
}
Discard(model);
Discard(g_stopped[i].model);
g_stopped[i] = g_stopped.back();
g_stopped.pop_back();
}
}
// The dying-effect drain: a stopped effect goes once its particles are gone,
// or at once when its model has already dropped it.
void Drain() {
DiscardWhere([](const Stopped &e) {
return Game::Read<void *>(e.model, Offsets::OFF_M2MODEL_PARENT) == nullptr ||
!reinterpret_cast<HasLiveParticles_t>(Offsets::FUN_M2MODEL_HAS_LIVE_PARTICLES)(
e.model);
});
}
const Tick::FrameTick::AutoSubscribe g_drain{&Drain};
// The pane's scene is about to free its stopped effects.
void OnPaneDestroyed(void *pane) {
DiscardWhere([pane](const Stopped &e) { return e.pane == pane; });
}
const Model::Teardown::AutoSubscribe _teardown{&OnPaneDestroyed};
// The ghost kit's procedure step: kind 1 tints, kind 14 sets the alpha.
void ApplyProcedures(void *model, const uint8_t *kit) {
for (int i = 0; i < kProcedures; ++i) {
@@ -157,10 +165,10 @@ void ApplyProcedures(void *model, const uint8_t *kit) {
}
void Apply(void *pane, const uint8_t *kit, bool oneShot) {
void *model = Game::Read<void *>(pane, Offsets::OFF_SIMPLEMODELFFX_MODEL_INSTANCE);
void *model = Pane::Model(pane);
if (model == nullptr)
return;
void *scene = reinterpret_cast<GetScene_t>(Offsets::FUN_SIMPLEMODEL_GET_SCENE)(pane);
void *scene = Pane::Scene(pane);
for (int i = 0; i < static_cast<int>(sizeof(kAttachments) / sizeof(kAttachments[0])); ++i) {
const uint8_t *effect = DBC::Record(
Offsets::VAR_SPELLVISUALEFFECTNAME_RECORDS, Offsets::VAR_SPELLVISUALEFFECTNAME_COUNT,
@@ -169,21 +177,24 @@ void Apply(void *pane, const uint8_t *kit, bool oneShot) {
!reinterpret_cast<HasAttachment_t>(Offsets::FUN_M2MODEL_HAS_ATTACHMENT)(
model, kAttachments[i]))
continue;
void *child = reinterpret_cast<CreateModel_t>(Offsets::FUN_M2SCENE_CREATE_MODEL)(
scene, Game::Read<const char *>(effect, Offsets::OFF_SPELLVISUALEFFECTNAME_FILE), 0);
void *child = M2::CreateModel(
scene, Game::Read<const char *>(effect, Offsets::OFF_SPELLVISUALEFFECTNAME_FILE));
if (child == nullptr)
continue;
SetDoneCallback(child, oneShot ? reinterpret_cast<const void *>(&OnEffectDone)
: reinterpret_cast<const void *>(&OnEffectLoop));
if (oneShot)
M2::SetAnimDoneCallback(child, reinterpret_cast<const void *>(&OnEffectDone),
static_cast<uint32_t>(reinterpret_cast<uintptr_t>(pane)), 0);
else
M2::SetAnimDoneCallback(child, reinterpret_cast<const void *>(&OnEffectLoop), 0, 0);
reinterpret_cast<Attach_t>(Offsets::FUN_M2MODEL_ATTACH)(child, model, kAttachments[i]);
Release(child);
M2::Release(child);
}
Model::DisplayInfo::LightChildren(pane, model);
ApplyProcedures(model, kit);
}
int __fastcall Script_ApplySpellVisualKit(void *L) {
void *pane = Game::Lua::ResolveTypedObject(L, 1, Offsets::VAR_PLAYERMODEL_LUA_TYPE_ID);
void *pane = Game::Lua::ResolvePlayerModel(L);
if (pane == nullptr)
return 0;
if (!Game::Lua::IsNumber(L, 2)) {
@@ -216,20 +227,10 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_PLAYERMODEL_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
// Drop every stopped effect before the reload tears the panes down.
void PrepareForReload() {
for (void *model : g_stopped)
Discard(model);
g_stopped.clear();
}
const Game::ReloadAutoRegister _reloadReg{&PrepareForReload};
} // namespace
} // namespace Model::VisualKit
+3 -7
View File
@@ -15,7 +15,6 @@
#include "Offsets.h"
#include <cstdint>
#include <iterator>
// `C_QuestLog.GetInfo(questLogIndex)` and `C_QuestLog.GetQuestLogTitle(
// questLogIndex)` — the modern table and 17-tuple shapes of one quest-log row.
@@ -235,12 +234,9 @@ static void RegisterLuaFunctions() {
Game::Lua::RegisterTableFunction("C_QuestLog", "GetInfo", &Script_GetInfo, &kGetInfo);
Game::Lua::RegisterTableFunction("C_QuestLog", "GetQuestLogTitle",
&Script_GetQuestLogTitle, &kGetQuestLogTitle);
Game::Lua::RegisterIntegerEnum("Enum", "QuestFrequency", kQuestFrequencyEntries,
static_cast<int>(std::size(kQuestFrequencyEntries)),
"QuestLog");
Game::Lua::RegisterIntegerEnum("Enum", "QuestClassification", kQuestClassificationEntries,
static_cast<int>(std::size(kQuestClassificationEntries)),
"QuestLog");
Game::Lua::RegisterIntegerEnum("Enum", "QuestFrequency", kQuestFrequencyEntries, "QuestLog");
Game::Lua::RegisterIntegerEnum(
"Enum", "QuestClassification", kQuestClassificationEntries, "QuestLog");
}
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+3 -2
View File
@@ -168,8 +168,9 @@ const Game::Doc::Method kDocs[] = {
};
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_SCROLLINGMESSAGEFRAME_METHOD_REGISTRY),
kMethods, 1, kDocs, 1);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_SCROLLINGMESSAGEFRAME_METHOD_REGISTRY), kMethods,
kDocs);
// Every way to the put-off layout first; the writer's layout is put off
// only when they all hold.
const bool flushes =
@@ -162,8 +162,9 @@ const Game::Doc::Method kDocs[] = {
void RegisterLuaFunctions() {
// Each registration pass is a new Lua state; the old state's ref is gone.
g_callbacksRef = kNoRef;
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_SCROLLINGMESSAGEFRAME_METHOD_REGISTRY),
kMethods, 1, kDocs, 1);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_SCROLLINGMESSAGEFRAME_METHOD_REGISTRY), kMethods,
kDocs);
}
void PrepareForReload() { g_frames.clear(); }
+3 -2
View File
@@ -166,8 +166,9 @@ void RegisterLuaFunctions() {
void *L = Game::Lua::State();
Game::Lua::SetGlobalNumber(L, "SCROLLING_MESSAGE_FRAME_INSERT_MODE_TOP", kInsertModeTop);
Game::Lua::SetGlobalNumber(L, "SCROLLING_MESSAGE_FRAME_INSERT_MODE_BOTTOM", kInsertModeBottom);
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_SCROLLINGMESSAGEFRAME_METHOD_REGISTRY),
kMethods, 2, kDocs, 2);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_SCROLLINGMESSAGEFRAME_METHOD_REGISTRY), kMethods,
kDocs);
Common::RedirectCall(Offsets::CALL_SMF_LAYOUT_ANCHOR_LINE, Offsets::FUN_REGION_SET_POINT,
reinterpret_cast<const void *>(&AnchorNewLine));
}
+3 -2
View File
@@ -564,8 +564,9 @@ const Game::Doc::Method kDocs[] = {
void RegisterLuaFunctions() {
// Each registration pass is a new Lua state; the old state's ref is gone.
g_extrasRef = kNoRef;
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_SCROLLINGMESSAGEFRAME_METHOD_REGISTRY),
kMethods, 4, kDocs, 4);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_SCROLLINGMESSAGEFRAME_METHOD_REGISTRY), kMethods,
kDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+3 -2
View File
@@ -157,8 +157,9 @@ const Game::Doc::Method kDocs[] = {
};
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_SCROLLINGMESSAGEFRAME_METHOD_REGISTRY),
kMethods, 4, kDocs, 4);
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_SCROLLINGMESSAGEFRAME_METHOD_REGISTRY), kMethods,
kDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -100,9 +100,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_SLIDER_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_SLIDER_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -116,9 +116,7 @@ int __fastcall Script_PlayItemSound(void *L) {
void RegisterLuaFunctions() {
Game::Lua::RegisterTableFunction("C_Sound", "PlayItemSound", &Script_PlayItemSound);
Game::Lua::RegisterIntegerEnum("Enum", "ItemSoundType", kItemSoundTypeEntries,
sizeof(kItemSoundTypeEntries) /
sizeof(kItemSoundTypeEntries[0]));
Game::Lua::RegisterIntegerEnum("Enum", "ItemSoundType", kItemSoundTypeEntries);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -174,9 +174,7 @@ int __fastcall Script_PlayVocalErrorSound(void *L) {
void RegisterLuaFunctions() {
Game::Lua::RegisterTableFunction("C_Sound", "PlayVocalErrorSound",
&Script_PlayVocalErrorSound);
Game::Lua::RegisterIntegerEnum("Enum", "Vocalerrorsounds", kVocalErrorEntries,
sizeof(kVocalErrorEntries) /
sizeof(kVocalErrorEntries[0]));
Game::Lua::RegisterIntegerEnum("Enum", "Vocalerrorsounds", kVocalErrorEntries);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -387,9 +387,7 @@ static void RegisterLuaFunctions() {
Game::Lua::RegisterTableFunction("C_SpellActivationOverlay", "IsSpellOverlayed",
&Script_IsSpellOverlayed, &kIsSpellOverlayed);
Game::Lua::RegisterIntegerEnum(
"Enum", "ScreenLocationType", kScreenLocationEntries,
sizeof(kScreenLocationEntries) / sizeof(kScreenLocationEntries[0]),
"ScreenLocationConstants");
"Enum", "ScreenLocationType", kScreenLocationEntries, "ScreenLocationConstants");
}
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+188 -128
View File
@@ -29,8 +29,11 @@
// auto action is allowed, noting it as pending;
// - both calls to the form check (caster check and FUN_SPELL_IS_USABLE)
// pass a SPELL_FAILED_NOT_SHAPESHIFT refusal when CanAutoUnshift holds;
// - the cast request's one call of the caster check runs the pending
// actions when it passes, before the request sends the cast.
// - the cast request's one call of the caster check keeps the pending
// actions when it passes, and the send of the pending cast runs them.
// 1.12's request checks the caster before it arms the targets, so the
// actions wait for the send, as in 4.3.4: a cast with no target, or a
// targeting cursor the player cancels, does not stand or dismount.
// The actions are the engine's own: the stand setter, and CMSG_CANCEL_AURA
// for the mount / form aura (1.12 has no CMSG_CANCEL_MOUNT_AURA; cancelling
// the mount aura is how its own UI dismounts). The server applies the
@@ -55,6 +58,13 @@
// so they get the same step. A locked object (a chest, a vein) opens by
// a spell whose cast passes the caster check above.
//
// 4.3.4's mount-in-form step (the cast path's and CGItem_C::Use's
// CancelShapeshiftForMount, for a SPELL_AURA_MOUNTED spell when
// !CanCurrentFormMount) needs no site of its own: 482 of 1.12's 484 mount
// spells carry SPELL_ATTR_NOT_SHAPESHIFT, so the form check refuses them
// with SPELL_FAILED_NOT_SHAPESHIFT and the form rung above unshifts; an item
// reaches the same rung through its cast.
//
// The remaining 4.3.4 sites:
// - loot (autoStand): CGUnit_C / CGCorpse_C::OnRightClick stand up in
// place of ERR_LOOT_NOTSTANDING. 1.12's two loot steps read the stand
@@ -64,7 +74,10 @@
// step skips a mounted player, so its read passes, and 1.12's LootUnit
// dismounts at the same point. A corpse is not looted mounted in either.
// - item use (autoUnshift): CGItem_C::Use leaves the form in place of
// ERR_NO_ITEMS_WHILE_SHAPESHIFTED; we do that at 1.12's refusal.
// ERR_NO_ITEMS_WHILE_SHAPESHIFTED, after its toggle test (an item
// whose aura is up only turns it off). 1.12 tests the toggle after the
// form, so its refusal passes and the form is left at the next test,
// the bind-on-use one, where 4.3.4 has it.
// - attack (autoDismount): CanAttackNow (the attack cursor, the right-click
// attack) and VerifyAttackIconPressed (ERR_ATTACK_MOUNTED) pass a mount
// when CanAutoDismount holds; CombatModeEnter then dismounts in melee
@@ -87,6 +100,7 @@
#include "unit/Identity.h"
#include <cstdint>
#include <initializer_list>
namespace Spell::AutoPrepare {
@@ -111,6 +125,31 @@ uintptr_t g_startAttackReturn = Offsets::START_ATTACK_RETURN;
constexpr int kGetStandStateSlot = 0xA4 / 4;
// The instructions the sites replace, by name: the mount reads
// (`mov dst,[base+0x1FC]`) and the GetStandState calls (`call [base+0xA4]`).
constexpr uint8_t kMovEaxEcxMount[6] = {0x8B, 0x81, 0xFC, 0x01, 0x00, 0x00};
constexpr uint8_t kMovEaxEdxMount[6] = {0x8B, 0x82, 0xFC, 0x01, 0x00, 0x00};
constexpr uint8_t kMovEcxEaxMount[6] = {0x8B, 0x88, 0xFC, 0x01, 0x00, 0x00};
constexpr uint8_t kMovEdxEaxMount[6] = {0x8B, 0x90, 0xFC, 0x01, 0x00, 0x00};
constexpr uint8_t kMovEdxEcxMount[6] = {0x8B, 0x91, 0xFC, 0x01, 0x00, 0x00};
constexpr uint8_t kCallEaxStandState[6] = {0xFF, 0x90, 0xA4, 0x00, 0x00, 0x00};
constexpr uint8_t kCallEdxStandState[6] = {0xFF, 0x92, 0xA4, 0x00, 0x00, 0x00};
// Common::ReplaceWithCall / RedirectCall / RedirectSlot, taking the hook
// as a typed function.
template <size_t N, typename Fn>
bool Replace(uintptr_t site, const uint8_t (&original)[N], Fn *target) {
return Common::ReplaceWithCall(site, original, N, reinterpret_cast<const void *>(target));
}
template <typename Fn> bool Redirect(uintptr_t site, uintptr_t expected, Fn *target) {
return Common::RedirectCall(site, expected, reinterpret_cast<const void *>(target));
}
template <typename Fn> bool RedirectSlot(uintptr_t slot, uintptr_t expected, Fn *target) {
return Common::RedirectSlot(slot, expected, reinterpret_cast<const void *>(target));
}
CVar::Factory::Handle g_autoStand = nullptr;
CVar::Factory::Handle g_autoDismount = nullptr;
CVar::Factory::Handle g_autoUnshift = nullptr;
@@ -141,6 +180,15 @@ int Form(const void *unit) {
return (Game::Read<uint32_t>(Fields(unit), Offsets::OFF_UNIT_FIELD_BYTES_1) >> 16) & 0xFF;
}
// The unit's SpellShapeshiftForm row's flags; a form with no row reads as 0.
uint32_t FormFlags(const void *unit) {
const uint8_t *form = DBC::Record(Offsets::VAR_SPELL_SHAPESHIFT_FORM_RECORDS,
Offsets::VAR_SPELL_SHAPESHIFT_FORM_COUNT,
static_cast<uint32_t>(Form(unit)));
return form != nullptr ? Game::Read<uint32_t>(form, Offsets::OFF_SPELL_SHAPESHIFT_FORM_FLAGS)
: 0;
}
bool Enabled(CVar::Factory::Handle cvar) { return CVar::Factory::GetInt(cvar) != 0; }
// 4.3.4 CGUnit_C::CanAutoStand.
@@ -154,14 +202,8 @@ bool CanAutoDismount(const void *unit) {
// 4.3.4 CGUnit_C::CanAutoUnshift.
bool CanAutoUnshift(const void *unit) {
if (!IsActivePlayer(unit) || OnTaxi(unit) || !Enabled(g_autoUnshift))
return false;
const uint8_t *form = DBC::Record(Offsets::VAR_SPELL_SHAPESHIFT_FORM_RECORDS,
Offsets::VAR_SPELL_SHAPESHIFT_FORM_COUNT,
static_cast<uint32_t>(Form(unit)));
return form == nullptr ||
(Game::Read<uint32_t>(form, Offsets::OFF_SPELL_SHAPESHIFT_FORM_FLAGS) &
Offsets::SHAPESHIFT_FORM_FLAG_NO_AUTO_UNSHIFT) == 0;
return IsActivePlayer(unit) && !OnTaxi(unit) && Enabled(g_autoUnshift) &&
(FormFlags(unit) & Offsets::SHAPESHIFT_FORM_FLAG_NO_AUTO_UNSHIFT) == 0;
}
int StandState(const void *unit) {
@@ -174,6 +216,12 @@ int MountDisplay(const void *unit) {
return Game::Read<int>(Fields(unit), Offsets::OFF_UNIT_FIELD_MOUNTDISPLAYID);
}
// 4.3.4's pairing at every site: the state holds and its CanAuto* allows
// the action.
bool AutoStands(const void *unit) { return StandState(unit) != 0 && CanAutoStand(unit); }
bool AutoDismounts(const void *unit) { return MountDisplay(unit) > 0 && CanAutoDismount(unit); }
// The engine's actions: the stand setter, and CMSG_CANCEL_AURA for the
// mount / form aura.
void Stand(const void *player) {
@@ -193,12 +241,7 @@ char IsShapeShifted(const void *unit) {
// The gates' test after IsShapeShifted: a form with no row, or one
// without the interact flag.
bool FormBlocksInteraction(const void *unit) {
const uint8_t *form = DBC::Record(Offsets::VAR_SPELL_SHAPESHIFT_FORM_RECORDS,
Offsets::VAR_SPELL_SHAPESHIFT_FORM_COUNT,
static_cast<uint32_t>(Form(unit)));
return form == nullptr ||
(Game::Read<uint32_t>(form, Offsets::OFF_SPELL_SHAPESHIFT_FORM_FLAGS) &
Offsets::SHAPESHIFT_FORM_FLAG_CAN_INTERACT) == 0;
return (FormFlags(unit) & Offsets::SHAPESHIFT_FORM_FLAG_CAN_INTERACT) == 0;
}
// True when the gate would refuse and autoUnshift takes the form off instead.
@@ -227,8 +270,7 @@ char __fastcall IsShapeShiftedForQuery(const void *player) {
// use, a loot, an attack): the mount display, or 0 when the action that
// follows dismounts first.
int __fastcall MountDisplayForAction(const void *player) {
const int display = MountDisplay(player);
return display > 0 && CanAutoDismount(player) ? 0 : display;
return AutoDismounts(player) ? 0 : MountDisplay(player);
}
// Stands in for `mov ecx,[eax+0x1FC]` at SITE_GAMEOBJECT_CAN_USE_MOUNT_READ,
@@ -283,17 +325,16 @@ __declspec(naked) void ResolverMountReadThunk() {
// The loot steps' GetStandState call (ECX = the player): stands the player
// up and reports 0 when autoStand allows, as 4.3.4's OnRightClick does.
int __fastcall StandStateForLoot(const void *player) {
const int state = StandState(player);
if (state != 0 && CanAutoStand(player)) {
if (AutoStands(player)) {
Stand(player);
return 0;
}
return state;
return StandState(player);
}
// LootUnit before CMSG_LOOT: 4.3.4's dismount.
void __fastcall DismountForLoot(const void *player) {
if (MountDisplay(player) > 0 && CanAutoDismount(player))
if (AutoDismounts(player))
Dismount();
}
@@ -313,13 +354,17 @@ __declspec(naked) void LootSendThunk() {
}
}
// The item use's form refusal: leaves the form and lets the use go on when
// autoUnshift allows.
// Set at the item use's form refusal when autoUnshift lets the use go on;
// the bind-on-use test runs the unshift, the toggle cancel drops it. Every
// path past the form test reaches one of the two.
bool g_itemUnshift = false;
// The item use's form refusal: lets the use go on when autoUnshift allows.
// The form is left later, at 4.3.4's point: past the toggle test, so an
// item whose aura is up is only turned off, as 4.3.4 does it.
bool __fastcall UnshiftForItem(const void *player) {
if (!CanAutoUnshift(player))
return false;
CancelForm(player);
return true;
g_itemUnshift = CanAutoUnshift(player);
return g_itemUnshift;
}
// Stands in for `push 0x15E` at SITE_ITEM_USE_FORM_REFUSAL, player in ESI:
@@ -341,11 +386,39 @@ __declspec(naked) void ItemFormRefusalThunk() {
}
}
// The toggle test's cancel: the use turns the aura off and casts nothing,
// so the form stays.
void __fastcall ItemToggleCancel(int spellId) {
g_itemUnshift = false;
reinterpret_cast<void(__fastcall *)(int)>(Offsets::FUN_CANCEL_AURA_SEND)(spellId);
}
// Before the bind-on-use test: 4.3.4 CGItem_C::Use's form cancel.
void UnshiftBeforeItemBind() {
if (!g_itemUnshift)
return;
g_itemUnshift = false;
if (const void *player = Unit::Identity::PlayerObject())
CancelForm(player);
}
// Stands in for `mov ecx,[ebp-8]; cmp dword [ecx+0x194],3` at
// SITE_ITEM_USE_BIND_TEST: the unshift, then the replaced code, whose flags
// the `jne` after the return reads. The call keeps EBX, ESI, EDI and EBP.
__declspec(naked) void ItemBindTestThunk() {
__asm {
call UnshiftBeforeItemBind
mov ecx, [ebp - 8]
cmp dword ptr [ecx + 0x194], 3
ret
}
}
// StartAttack past its tests: 4.3.4 CombatModeEnter's dismount, in melee
// reach only; out of reach it does not attack, and shows ERR_OUT_OF_RANGE
// unless click-to-move is walking the player there (4.3.4's `silent`).
bool __fastcall PrepareAttack(const void *player, void *target) {
if (MountDisplay(player) <= 0 || !CanAutoDismount(player))
if (!AutoDismounts(player))
return true;
if (!Combat::Swing::InMeleeRange(const_cast<void *>(player), target)) {
if (!reinterpret_cast<CanAutoInteract_t>(Offsets::FUN_PLAYER_CAN_AUTO_INTERACT)(player))
@@ -384,10 +457,12 @@ void __fastcall TaxiMapOpenedTutorial(int id, int arg) {
const void *player = Unit::Identity::PlayerObject();
if (player == nullptr)
return;
if (StandState(player) != 0 && CanAutoStand(player))
if (AutoStands(player))
Stand(player);
if (MountDisplay(player) > 0 && CanAutoDismount(player))
if (AutoDismounts(player))
Dismount();
// 4.3.4's IsCancelShapeshiftAtFlightMaster adds forms with flag 0x800,
// which no 1.12 SpellShapeshiftForm row carries.
if (IsShapeShifted(player) && CanAutoUnshift(player))
CancelForm(player);
}
@@ -400,7 +475,7 @@ void __fastcall PrepareObjectUse(const void *handler) {
return;
const uint8_t *object =
Game::Read<const uint8_t *>(handler, Offsets::OFF_GAMEOBJECT_HANDLER_OBJECT);
if (MountDisplay(player) > 0 && CanAutoDismount(player) &&
if (AutoDismounts(player) &&
!reinterpret_cast<UsableMounted_t>(Offsets::FUN_GAMEOBJECT_USABLE_MOUNTED)(
object + Offsets::OFF_GAMEOBJECT_DESCRIPTOR))
Dismount();
@@ -433,12 +508,11 @@ char __fastcall TextUse(void *handler) {
// The mount rung's read: the mount display, or 0 when the cast will
// dismount first.
int __fastcall MountDisplayForCast(const void *caster) {
const int display = MountDisplay(caster);
if (display > 0 && CanAutoDismount(caster)) {
if (AutoDismounts(caster)) {
g_pending.dismount = true;
return 0;
}
return display;
return MountDisplay(caster);
}
// Stands in for `mov edx,[ecx+0x1FC]` at SITE_CASTER_STATE_MOUNT_READ. The
@@ -459,12 +533,11 @@ __declspec(naked) void MountReadThunk() {
// SITE_CASTER_STATE_STAND_CALL, caster in ECX: the stand state, or 0 when
// the cast will stand first.
int __fastcall StandStateForCast(const void *caster) {
const int state = StandState(caster);
if (state != 0 && CanAutoStand(caster)) {
if (AutoStands(caster)) {
g_pending.stand = true;
return 0;
}
return state;
return StandState(caster);
}
char CheckForm(void *unit, const uint8_t *record, int *reason) {
@@ -489,23 +562,36 @@ char __fastcall CheckFormForUsable(void *unit, void *, const uint8_t *record, in
return *reason == Offsets::SPELL_FAILED_NOT_SHAPESHIFT && CanAutoUnshift(unit);
}
// The cast request's call of the caster check. 4.3.4's order: stand,
// dismount, then leave the form.
// The cast request's call of the caster check: a passed check keeps what its
// rungs noted for the send; a refused one drops it.
char __fastcall CheckCasterState(void *caster, void *, const uint8_t *record, void *item) {
g_pending = {};
const char passed = reinterpret_cast<CheckCasterState_t>(
Offsets::FUN_CHECK_CASTER_STATE_FOR_SPELL)(caster, nullptr, record, item);
if (!passed)
g_pending = {};
return passed;
}
// The pending cast's send (4.3.4's Spell_C_SendPendingCast): stand, dismount,
// then leave the form, in that order, before the packet. Here, past the
// target arm, a cast that finds no target ("You have no target") or whose
// targeting cursor is cancelled has not dismounted. The rungs note only for
// the active player; each action runs only while its state still holds, as
// the cursor can be up while the player stands, dismounts or leaves the form.
void __fastcall SendCast(void *pendingCast, uint8_t flag) {
const Pending pending = g_pending;
g_pending = {};
if (!passed)
return 0;
if (pending.stand)
Stand(caster);
if (pending.dismount)
Dismount();
if (pending.unshift)
CancelForm(caster);
return 1;
if (const void *player = Unit::Identity::PlayerObject()) {
if (pending.stand && StandState(player) != 0)
Stand(player);
if (pending.dismount && MountDisplay(player) > 0)
Dismount();
if (pending.unshift && Form(player) != 0)
CancelForm(player);
}
reinterpret_cast<void(__fastcall *)(void *, uint8_t)>(Offsets::FUN_SPELL_SEND_CAST)(
pendingCast, flag);
}
void Register() {
@@ -519,101 +605,75 @@ void Register() {
// Interaction. The use check passes the mount and form only together
// with the step at every use; without it the object would be used still
// mounted or shifted.
Common::RedirectCall(Offsets::CALL_NPC_INTERACT_IS_SHAPESHIFTED,
Offsets::FUN_UNIT_IS_SHAPESHIFTED,
reinterpret_cast<const void *>(&IsShapeShiftedForTalk));
Redirect(Offsets::CALL_NPC_INTERACT_IS_SHAPESHIFTED, Offsets::FUN_UNIT_IS_SHAPESHIFTED,
&IsShapeShiftedForTalk);
static const uint8_t kUseSend[5] = {0xBF, 0xE4, 0xF9, 0x7F, 0x00};
if (Common::ReplaceWithCall(Offsets::SITE_GAMEOBJECT_USE_SEND, kUseSend, sizeof kUseSend,
reinterpret_cast<const void *>(&UseSendThunk)) &&
Common::RedirectSlot(Offsets::SLOT_GAMEOBJECT_MAILBOX_USE,
Offsets::FUN_GAMEOBJECT_MAILBOX_USE,
reinterpret_cast<const void *>(&MailboxUse)) &&
Common::RedirectSlot(Offsets::SLOT_GAMEOBJECT_TEXT_USE, Offsets::FUN_GAMEOBJECT_TEXT_USE,
reinterpret_cast<const void *>(&TextUse))) {
static const uint8_t kUseMountRead[6] = {0x8B, 0x88, 0xFC, 0x01, 0x00, 0x00};
Common::ReplaceWithCall(Offsets::SITE_GAMEOBJECT_CAN_USE_MOUNT_READ, kUseMountRead,
sizeof kUseMountRead,
reinterpret_cast<const void *>(&UseMountReadThunk));
Common::RedirectCall(Offsets::CALL_GAMEOBJECT_CAN_USE_IS_SHAPESHIFTED,
Offsets::FUN_UNIT_IS_SHAPESHIFTED,
reinterpret_cast<const void *>(&IsShapeShiftedForQuery));
if (Replace(Offsets::SITE_GAMEOBJECT_USE_SEND, kUseSend, &UseSendThunk) &&
RedirectSlot(Offsets::SLOT_GAMEOBJECT_MAILBOX_USE, Offsets::FUN_GAMEOBJECT_MAILBOX_USE,
&MailboxUse) &&
RedirectSlot(Offsets::SLOT_GAMEOBJECT_TEXT_USE, Offsets::FUN_GAMEOBJECT_TEXT_USE,
&TextUse)) {
Replace(Offsets::SITE_GAMEOBJECT_CAN_USE_MOUNT_READ, kMovEcxEaxMount, &UseMountReadThunk);
Redirect(Offsets::CALL_GAMEOBJECT_CAN_USE_IS_SHAPESHIFTED,
Offsets::FUN_UNIT_IS_SHAPESHIFTED, &IsShapeShiftedForQuery);
}
Common::RedirectCall(Offsets::CALL_UNIT_CAN_INTERACT_NOW_IS_SHAPESHIFTED,
Offsets::FUN_UNIT_IS_SHAPESHIFTED,
reinterpret_cast<const void *>(&IsShapeShiftedForQuery));
Redirect(Offsets::CALL_UNIT_CAN_INTERACT_NOW_IS_SHAPESHIFTED,
Offsets::FUN_UNIT_IS_SHAPESHIFTED, &IsShapeShiftedForQuery);
// Loot. The loot step passes the mount only together with LootUnit's
// dismount.
static const uint8_t kUnitStandCall[6] = {0xFF, 0x90, 0xA4, 0x00, 0x00, 0x00};
static const uint8_t kCorpseStandCall[6] = {0xFF, 0x92, 0xA4, 0x00, 0x00, 0x00};
Common::ReplaceWithCall(Offsets::SITE_UNIT_LOOT_STAND_CALL, kUnitStandCall,
sizeof kUnitStandCall,
reinterpret_cast<const void *>(&StandStateForLoot));
Common::ReplaceWithCall(Offsets::SITE_CORPSE_LOOT_STAND_CALL, kCorpseStandCall,
sizeof kCorpseStandCall,
reinterpret_cast<const void *>(&StandStateForLoot));
Replace(Offsets::SITE_UNIT_LOOT_STAND_CALL, kCallEaxStandState, &StandStateForLoot);
Replace(Offsets::SITE_CORPSE_LOOT_STAND_CALL, kCallEdxStandState, &StandStateForLoot);
static const uint8_t kLootSend[6] = {0x6A, 0x00, 0x32, 0xD2, 0xB1, 0x01};
static const uint8_t kLootMountRead[6] = {0x8B, 0x81, 0xFC, 0x01, 0x00, 0x00};
if (Common::ReplaceWithCall(Offsets::SITE_LOOT_UNIT_SEND, kLootSend, sizeof kLootSend,
reinterpret_cast<const void *>(&LootSendThunk)))
Common::ReplaceWithCall(Offsets::SITE_UNIT_LOOT_MOUNT_READ, kLootMountRead,
sizeof kLootMountRead,
reinterpret_cast<const void *>(&MountReadEsiThunk));
if (Replace(Offsets::SITE_LOOT_UNIT_SEND, kLootSend, &LootSendThunk))
Replace(Offsets::SITE_UNIT_LOOT_MOUNT_READ, kMovEaxEcxMount, &MountReadEsiThunk);
// Item use.
// Item use. The refusal passes only together with the unshift after the
// toggle test; without it the item would be used still shifted.
static const uint8_t kItemBindTest[10] = {0x8B, 0x4D, 0xF8, 0x83, 0xB9,
0x94, 0x01, 0x00, 0x00, 0x03};
static const uint8_t kItemFormRefusal[5] = {0x68, 0x5E, 0x01, 0x00, 0x00};
Common::ReplaceWithCall(Offsets::SITE_ITEM_USE_FORM_REFUSAL, kItemFormRefusal,
sizeof kItemFormRefusal,
reinterpret_cast<const void *>(&ItemFormRefusalThunk));
if (Redirect(Offsets::CALL_ITEM_USE_TOGGLE_CANCEL, Offsets::FUN_CANCEL_AURA_SEND,
&ItemToggleCancel) &&
Replace(Offsets::SITE_ITEM_USE_BIND_TEST, kItemBindTest, &ItemBindTestThunk))
Replace(Offsets::SITE_ITEM_USE_FORM_REFUSAL, kItemFormRefusal, &ItemFormRefusalThunk);
// Attack. The mount reads pass only together with StartAttack's
// dismount; without it the attack would start mounted.
static const uint8_t kStartAttackPassed[6] = {0x8B, 0x86, 0x8C, 0x0C, 0x00, 0x00};
if (Common::ReplaceWithCall(Offsets::SITE_START_ATTACK_PASSED, kStartAttackPassed,
sizeof kStartAttackPassed,
reinterpret_cast<const void *>(&StartAttackPassedThunk))) {
static const uint8_t kEdxMountRead[6] = {0x8B, 0x82, 0xFC, 0x01, 0x00, 0x00};
static const uint8_t kEcxMountRead[6] = {0x8B, 0x81, 0xFC, 0x01, 0x00, 0x00};
static const uint8_t kEaxMountReadToEdx[6] = {0x8B, 0x90, 0xFC, 0x01, 0x00, 0x00};
Common::ReplaceWithCall(Offsets::SITE_START_ATTACK_MOUNT_READ, kEcxMountRead,
sizeof kEcxMountRead,
reinterpret_cast<const void *>(&MountReadEsiThunk));
Common::ReplaceWithCall(Offsets::SITE_RIGHT_CLICK_ATTACK_MOUNT_READ, kEdxMountRead,
sizeof kEdxMountRead,
reinterpret_cast<const void *>(&MountReadEsiThunk));
Common::ReplaceWithCall(Offsets::SITE_CURSOR_ATTACK_MOUNT_READ, kEdxMountRead,
sizeof kEdxMountRead,
reinterpret_cast<const void *>(&MountReadEdiThunk));
Common::ReplaceWithCall(Offsets::SITE_ATTACK_RESOLVER_MOUNT_READ, kEcxMountRead,
sizeof kEcxMountRead,
reinterpret_cast<const void *>(&MountReadEdiThunk));
Common::ReplaceWithCall(Offsets::SITE_ATTACK_RESOLVER_CAN_ATTACK_MOUNT_READ,
kEaxMountReadToEdx, sizeof kEaxMountReadToEdx,
reinterpret_cast<const void *>(&ResolverMountReadThunk));
if (Replace(Offsets::SITE_START_ATTACK_PASSED, kStartAttackPassed, &StartAttackPassedThunk)) {
Replace(Offsets::SITE_START_ATTACK_MOUNT_READ, kMovEaxEcxMount, &MountReadEsiThunk);
Replace(Offsets::SITE_RIGHT_CLICK_ATTACK_MOUNT_READ, kMovEaxEdxMount, &MountReadEsiThunk);
Replace(Offsets::SITE_CURSOR_ATTACK_MOUNT_READ, kMovEaxEdxMount, &MountReadEdiThunk);
Replace(Offsets::SITE_ATTACK_RESOLVER_MOUNT_READ, kMovEaxEcxMount, &MountReadEdiThunk);
Replace(Offsets::SITE_ATTACK_RESOLVER_CAN_ATTACK_MOUNT_READ, kMovEdxEaxMount,
&ResolverMountReadThunk);
}
// Taxi.
Common::RedirectCall(Offsets::CALL_TAXI_MAP_OPENED_TUTORIAL, Offsets::FUN_TRIGGER_TUTORIAL,
reinterpret_cast<const void *>(&TaxiMapOpenedTutorial));
Redirect(Offsets::CALL_TAXI_MAP_OPENED_TUTORIAL, Offsets::FUN_TRIGGER_TUTORIAL,
&TaxiMapOpenedTutorial);
// The rungs only note what to do; the caster-check call runs it. Without
// that call a passed rung would send the cast still mounted or seated.
if (!Common::RedirectCall(Offsets::CALL_TRY_CAST_CHECK_CASTER_STATE,
Offsets::FUN_CHECK_CASTER_STATE_FOR_SPELL,
reinterpret_cast<const void *>(&CheckCasterState)))
// The rungs only note what to do; the caster-check call keeps it and
// every send runs it. Without all of them a passed rung would send the
// cast still mounted or seated. A send with nothing noted is the
// original send, so sites redirected before a miss are harmless.
for (const uintptr_t site :
{Offsets::CALL_TARGET_ARM_SEND_CAST, Offsets::CALL_TARGET_ARM_SEND_CAST_GUID,
Offsets::CALL_BIND_TARGET_OBJECT_SEND_CAST, Offsets::CALL_BIND_TARGET_LOCATION_SEND_CAST,
Offsets::CALL_BIND_TARGET_TRADE_SLOT_SEND_CAST}) {
if (!Redirect(site, Offsets::FUN_SPELL_SEND_CAST, &SendCast))
return;
}
if (!Redirect(Offsets::CALL_TRY_CAST_CHECK_CASTER_STATE,
Offsets::FUN_CHECK_CASTER_STATE_FOR_SPELL, &CheckCasterState))
return;
static const uint8_t kMountRead[6] = {0x8B, 0x91, 0xFC, 0x01, 0x00, 0x00};
static const uint8_t kStandCall[6] = {0xFF, 0x92, 0xA4, 0x00, 0x00, 0x00};
Common::ReplaceWithCall(Offsets::SITE_CASTER_STATE_MOUNT_READ, kMountRead,
sizeof kMountRead, reinterpret_cast<const void *>(&MountReadThunk));
Common::ReplaceWithCall(Offsets::SITE_CASTER_STATE_STAND_CALL, kStandCall,
sizeof kStandCall, reinterpret_cast<const void *>(&StandStateForCast));
if (Common::RedirectCall(Offsets::CALL_CASTER_STATE_SHAPESHIFT_FORM,
Offsets::FUN_CHECK_SPELL_SHAPESHIFT_FORM,
reinterpret_cast<const void *>(&CheckFormForCast)))
Common::RedirectCall(Offsets::CALL_SPELL_USABLE_SHAPESHIFT_FORM,
Offsets::FUN_CHECK_SPELL_SHAPESHIFT_FORM,
reinterpret_cast<const void *>(&CheckFormForUsable));
Replace(Offsets::SITE_CASTER_STATE_MOUNT_READ, kMovEdxEcxMount, &MountReadThunk);
Replace(Offsets::SITE_CASTER_STATE_STAND_CALL, kCallEdxStandState, &StandStateForCast);
if (Redirect(Offsets::CALL_CASTER_STATE_SHAPESHIFT_FORM,
Offsets::FUN_CHECK_SPELL_SHAPESHIFT_FORM, &CheckFormForCast))
Redirect(Offsets::CALL_SPELL_USABLE_SHAPESHIFT_FORM,
Offsets::FUN_CHECK_SPELL_SHAPESHIFT_FORM, &CheckFormForUsable);
}
const Game::ModuleAutoRegister _autoreg{&Register};
+4 -4
View File
@@ -1036,10 +1036,10 @@ static void RegisterLuaFunctions() {
Game::Lua::RegisterTableFunction("C_SpellBook", "ContainsAnyDisenchantSpell",
&Script_ContainsAnyDisenchantSpell,
&kContainsAnyDisenchantSpell);
Game::Lua::RegisterIntegerEnum("Enum", "SpellBookSpellBank",
kSpellBookSpellBankEntries, 2, "SpellBook");
Game::Lua::RegisterIntegerEnum("Enum", "SpellBookItemType",
kSpellBookItemTypeEntries, 5, "SpellBook");
Game::Lua::RegisterIntegerEnum(
"Enum", "SpellBookSpellBank", kSpellBookSpellBankEntries, "SpellBook");
Game::Lua::RegisterIntegerEnum(
"Enum", "SpellBookItemType", kSpellBookItemTypeEntries, "SpellBook");
}
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+2 -5
View File
@@ -240,11 +240,8 @@ static const Game::Doc::Method g_methodDocs[] = {
static void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_GAMETOOLTIP_METHOD_REGISTRY),
g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])),
g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_GAMETOOLTIP_METHOD_REGISTRY), g_methods,
g_methodDocs);
}
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+1 -3
View File
@@ -155,9 +155,7 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_STATUSBAR_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
reinterpret_cast<void *>(Offsets::VAR_STATUSBAR_METHOD_REGISTRY), g_methods, g_methodDocs);
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+5 -7
View File
@@ -82,7 +82,6 @@
#include <cmath>
#include <cstdint>
#include <iterator>
#include <unordered_map>
namespace StatusBar::Display {
@@ -359,12 +358,11 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_STATUSBAR_METHOD_REGISTRY), g_methods,
static_cast<int>(std::size(g_methods)), g_methodDocs,
static_cast<int>(std::size(g_methodDocs)));
Lua::RegisterIntegerEnum("Enum", "StatusBarTimerDirection", kDirectionEntries,
static_cast<int>(std::size(kDirectionEntries)), "SimpleStatusBarConstants");
Lua::RegisterIntegerEnum("Enum", "StatusBarInterpolation", kInterpolationEntries,
static_cast<int>(std::size(kInterpolationEntries)), "SimpleStatusBarConstants");
g_methodDocs);
Lua::RegisterIntegerEnum(
"Enum", "StatusBarTimerDirection", kDirectionEntries, "SimpleStatusBarConstants");
Lua::RegisterIntegerEnum(
"Enum", "StatusBarInterpolation", kInterpolationEntries, "SimpleStatusBarConstants");
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
+3 -6
View File
@@ -382,12 +382,9 @@ const Game::Doc::Method g_methodDocs[] = {
void RegisterLuaFunctions() {
Game::Lua::RegisterFrameMethods(
reinterpret_cast<void *>(Offsets::VAR_STATUSBAR_METHOD_REGISTRY), g_methods,
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
Game::Lua::RegisterIntegerEnum("Enum", "StatusBarFillStyle", kFillStyleEntries,
static_cast<int>(sizeof(kFillStyleEntries) / sizeof(kFillStyleEntries[0])),
"SimpleStatusBarConstants");
reinterpret_cast<void *>(Offsets::VAR_STATUSBAR_METHOD_REGISTRY), g_methods, g_methodDocs);
Game::Lua::RegisterIntegerEnum(
"Enum", "StatusBarFillStyle", kFillStyleEntries, "SimpleStatusBarConstants");
}
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};

Some files were not shown because too many files have changed in this diff Show More