mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-12 12:57:37 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24404b5a23 | ||
|
|
a106d86969 | ||
|
|
fac3be3a0b | ||
|
|
b49c107778 | ||
|
|
53b7f79440 | ||
|
|
7b21453469 | ||
|
|
ebfd9d034d | ||
|
|
d825bcbdda |
+35
-33
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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};
|
||||
|
||||
+21
-9
@@ -72,18 +72,25 @@ struct State {
|
||||
uint32_t endMs = 0;
|
||||
bool active = false; // at least one reset has happened this session
|
||||
bool dirty = false; // needs a PLAYER_SWING fire on the next WorldTick
|
||||
bool held = false; // the latest reset was recorded but not reported yet
|
||||
};
|
||||
State g_swing[3];
|
||||
|
||||
// `notify` false records the server's new timer without firing PLAYER_SWING:
|
||||
// the reset is real, but no swing is coming yet (see OnSpellGo).
|
||||
// the reset is real, but no swing is coming yet (see OnSpellGo). It stays
|
||||
// `held` until the attack start reports it or a later reported reset replaces it.
|
||||
void MarkReset(SwingType type, uint32_t durationMs, bool notify = true) {
|
||||
if (durationMs == 0)
|
||||
return; // no weapon for this hand right now -- nothing to report
|
||||
State &s = g_swing[type];
|
||||
s.endMs = NowMs() + durationMs;
|
||||
s.active = true;
|
||||
s.dirty = s.dirty || notify;
|
||||
if (notify) {
|
||||
s.dirty = true;
|
||||
s.held = false;
|
||||
} else {
|
||||
s.held = true;
|
||||
}
|
||||
}
|
||||
|
||||
using HasAttackTarget_t = int(__fastcall *)(const void *unit);
|
||||
@@ -410,11 +417,17 @@ void OnPacket(uint32_t opcode, CDataStore *packet) {
|
||||
auto *playerObj = const_cast<uint8_t *>(Unit::Identity::PlayerObject());
|
||||
if (InMeleeRange(playerObj, victimObj)) {
|
||||
MarkReset(OFF_HAND, AttackTimeMs(OFF_HAND));
|
||||
// A cast reset made before the attack still holds the
|
||||
// main-hand swing back; report it now that one is coming.
|
||||
// A cast reset made before the attack is still held back;
|
||||
// report it now that a swing is coming. Only a held reset:
|
||||
// a target switch mid-attack also lands here, and there the
|
||||
// main-hand swing was never reset -- re-reporting its
|
||||
// remaining time would read as a fresh, short swing.
|
||||
State &main = g_swing[MAIN_HAND];
|
||||
if (main.active && Time::Clock::Remaining(NowMs(), main.endMs) > 0)
|
||||
main.dirty = true;
|
||||
if (main.held) {
|
||||
main.held = false;
|
||||
if (Time::Clock::Remaining(NowMs(), main.endMs) > 0)
|
||||
main.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -477,6 +490,7 @@ void OnWorldTick() {
|
||||
if (!s.dirty)
|
||||
continue;
|
||||
s.dirty = false;
|
||||
s.held = false;
|
||||
if (slot < 0)
|
||||
continue;
|
||||
const uint32_t remaining = Time::Clock::Remaining(now, s.endMs);
|
||||
@@ -497,9 +511,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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 МБ";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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};
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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};
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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};
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
@@ -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
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user