mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-10 20:07:37 +00:00
refactor(model): share the engine model calls across the model modules
The model modules each declared their own pointers to the same 1.12.1 engine functions: the instance gates FUN_MODEL_INSTANCE_LOADED (0x007103D0) and FUN_MODEL_INSTANCE_READY, AddRef / Release (0x00710390 / 0x007103A0), FUN_M2SCENE_CREATE_MODEL, FUN_M2MODEL_PLAY_ANIMATION, the done callback, the item visuals, the render callback, the M2 data lookup FUN_M2_LOOKUP_INDEX (0x00710310), and the pane's model, scene, SetModelInstance virtual (vtable +0x90) and SetDisplay. model/M2.h now holds one typed wrapper for each, and every module calls through it. - Game::Lua::ResolvePlayerModel / ResolveDressUpModel join the per-type resolvers, replacing four ResolvePane copies and five inline ResolveTypedObject calls. Game::Lua::TYPE_NONE replaces the local kTypeNone in Rotation and frame/CreateFrame. - Offsets.h has the full INVTYPE list, 0..28, named by the engine's own key table at 0x0083DDB0. It replaces the four scattered INVTYPE_* entries and the local copies in DressUp and ItemDisplay, and the try-on result switch reads by name. - DisplayInfo keeps its dress jobs in a map by frame, like the other modules, in place of the fixed 8-slot pool and its eviction. - DressUp names the held lane's bytes (item class, inventory type, sheathe type) and detaches a lane in one place. - ItemDisplay::OwnVisual is the one test for a display's own ItemVisuals row (GetItemDescVisual 0x0062EC70), used by SetItem and by TryOn's enchant glow. Both own-record panes share Pane::DisplayRecord, and the SetDisplayInfo records are cleared on /reload as the SetItem records are.
This commit is contained in:
@@ -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.
|
||||
|
||||
+5
-1
@@ -240,7 +240,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 +481,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
|
||||
|
||||
+29
-11
@@ -6168,15 +6168,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
|
||||
@@ -10938,10 +10960,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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+30
-46
@@ -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;
|
||||
}
|
||||
|
||||
+14
-31
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+53
-105
@@ -55,6 +55,7 @@
|
||||
#include "dbc/Lookup.h"
|
||||
#include "model/DisplayInfo.h"
|
||||
#include "model/ItemDisplay.h"
|
||||
#include "model/M2.h"
|
||||
#include "tick/WorldTick.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -96,8 +97,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 +106,59 @@ 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{};
|
||||
}
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
// 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 +174,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 +184,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 +194,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 +219,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 +233,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;
|
||||
}
|
||||
|
||||
@@ -322,6 +268,17 @@ void RegisterLuaFunctions() {
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
// The reload teardown destroys every Model frame, and with it the model
|
||||
// instances the dress jobs point at: destroy their compositors first.
|
||||
void PrepareForReload() {
|
||||
for (auto &[frame, job] : g_jobs)
|
||||
DestroyDresser(job.builder);
|
||||
g_jobs.clear();
|
||||
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
|
||||
|
||||
+77
-55
@@ -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);
|
||||
|
||||
+36
-53
@@ -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;
|
||||
}
|
||||
|
||||
@@ -289,9 +264,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
|
||||
|
||||
+13
-21
@@ -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));
|
||||
|
||||
+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;
|
||||
|
||||
+4
-10
@@ -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;
|
||||
|
||||
+16
-30
@@ -48,6 +48,7 @@
|
||||
#include "Offsets.h"
|
||||
#include "dbc/Lookup.h"
|
||||
#include "model/DisplayInfo.h"
|
||||
#include "model/M2.h"
|
||||
#include "tick/FrameTick.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -56,19 +57,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};
|
||||
@@ -81,24 +75,15 @@ constexpr float kByteScale = 1.0f / 255.0f;
|
||||
// Stopped one-shot effects, each with one reference of ours.
|
||||
std::vector<void *> 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.
|
||||
void __fastcall OnEffectDone(void *model, uint32_t /*keyBone*/, uint32_t /*animID*/,
|
||||
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);
|
||||
M2::SetAnimDoneCallback(model, nullptr, 0, 0);
|
||||
M2::SetVisible(model, false);
|
||||
M2::AddRef(model);
|
||||
g_stopped.push_back(model);
|
||||
}
|
||||
|
||||
@@ -108,8 +93,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,7 +101,7 @@ 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,
|
||||
@@ -157,10 +141,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 +153,23 @@ 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));
|
||||
M2::SetAnimDoneCallback(child,
|
||||
oneShot ? reinterpret_cast<const void *>(&OnEffectDone)
|
||||
: 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)) {
|
||||
|
||||
Reference in New Issue
Block a user