mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-10 20:07:37 +00:00
feat(model): add PlayerModel:ApplySpellVisualKit
PlayerModel:ApplySpellVisualKit(spellVisualKitID [, oneShot]) shows a SpellVisualKit's effects on the pane's model. In the world, 1.12.1 plays a kit on a unit (0x0060EDF0), through effect objects, spell durations and the unit's own animation, and a pane has none of these. The engine's own kit on a bare model is the char-select ghost (0x004727F0), and this is that routine for any kit on the pane's model: - Each of the nine effect columns (+0x0C) is created as a model from its SpellVisualEffectName file, in the pane's scene. It is attached at the column's attachment (20, 34, 19, 21, 22, 17, 23, 24, 25) when the model has that attachment (0x00712CB0, 0x00712F70), as in 0x00472780. The effects get the frame's light through LightChildren. - Procedure kind 1 sets the tint (an 0xRRGGBB colour stored as a float, scaled by 1/255) and kind 14 the alpha, clamped to 0..1. Both are plain stores (0x00710CF0, 0x00710CB0), as the ghost kit makes them. The kit's animation and sound do not play, which matches retail. An effect model's sequence plays one time. Without oneShot, each effect gets the world's loop callback (KitEffectLoopCallback, 0x0060ED00): a clean end (reason 0) plays the animation again. With oneShot, each effect gets EffectDoneCallback's test (0x005FBF50) and CEffect::Stop's model steps (0x006203E0): clear the callback and hide the model, so its emitters stop and the particles already out finish. A FrameTick drain, which runs after the strata walk, then mirrors DrainDyingEffects (0x0061F680). It detaches a stopped effect (0x00713020) once HasLiveParticles (0x007135C0) is false, or at once when its model has already dropped it. A model's destructor detaches its children first (0x0070E3F5), so the drain never reaches a freed parent. The effects are children of the pane's model, so SetUnit, SetItem or a hide and show clears them.
This commit is contained in:
@@ -112,7 +112,7 @@ In the Frame rows, a name that starts with `:` is a method of the widget that th
|
||||
| [Map](docs/API.md#map) | `C_Map.CanSetUserWaypointOnMap`, `C_Map.ClearUserWaypoint`, `C_Map.GetAreaInfo`, `C_Map.GetAreas`, `C_Map.GetAreaTriggerInfo`, `C_Map.GetAreaTriggers`, `C_Map.GetBestMapForUnit`, `C_Map.GetFallbackWorldMapID`, `C_Map.GetMapAreaIDs`, `C_Map.GetMapArtLayers`, `C_Map.GetMapArtLayerTextures`, `C_Map.GetMapChildrenInfo`, `C_Map.GetMapInfo`, `C_Map.GetMapInfoAtPosition`, `C_Map.GetMapOverlays`, `C_Map.GetMapPosFromWorldPos`, `C_Map.GetMapRectOnMap`, `C_Map.GetMapWorldSize`, `C_Map.GetPlayerMapPosition`, `C_Map.GetUserWaypoint`, `C_Map.GetUserWaypointFromHyperlink`, `C_Map.GetUserWaypointHyperlink`, `C_Map.GetUserWaypointPositionForMap`, `C_Map.GetWorldPosFromMapPos`, `C_Map.HasUserWaypoint`, `C_Map.MapHasArt`, `C_Map.SetUserWaypoint` |
|
||||
| [MapExplorationInfo](docs/API.md#mapexplorationinfo) | `C_MapExplorationInfo.GetExploredAreaIDsAtPosition`, `C_MapExplorationInfo.GetExploredMapTextures`, `C_MapExplorationInfo.GetUnexploredMapTextures` |
|
||||
| [MerchantFrame](docs/API.md#merchantframe) | `C_MerchantFrame.GetBuybackItemID`, `C_MerchantFrame.GetItemInfo`, `C_MerchantFrame.GetNumJunkItems`, `C_MerchantFrame.IsMerchantItemRefundable`, `C_MerchantFrame.IsSellAllJunkEnabled`, `C_MerchantFrame.SellAllJunkItems`, `GetBuybackItemLink` |
|
||||
| [Model](docs/API.md#model) | `DressUpModel:TryOn` (links, recipes, names, hand, enchant glow, result), `DressUpModel:UndressSlot`, `DressUpModel:IsGeoReady`, `DressUpModel:SetSheathed` / `GetSheathed`, `DressUpModel:SetAutoDress` / `GetAutoDress`, `Model:SetGlow`, `PlayerModel:RefreshCamera`, `PlayerModel:SetCamDistanceScale`, `PlayerModel:SetCreature` (`displayID`), `PlayerModel:SetDisplayInfo`, `PlayerModel:SetItem`, `PlayerModel:SetPortraitZoom`, `PlayerModel:SetRotation` (`animate`), `PlayerModel:SetUnit` (result), `PlayerModel:CanSetUnit`, `PlayerModel:GetDisplayInfo`, `PlayerModel:SetAnimation`, `PlayerModel:FreezeAnimation`, `PlayerModel:HasAnimation`, `PlayerModel:SetDoBlend` / `GetDoBlend`, `PlayerModel:SetKeepModelOnHide` / `GetKeepModelOnHide` |
|
||||
| [Model](docs/API.md#model) | `DressUpModel:TryOn` (links, recipes, names, hand, enchant glow, result), `DressUpModel:UndressSlot`, `DressUpModel:IsGeoReady`, `DressUpModel:SetSheathed` / `GetSheathed`, `DressUpModel:SetAutoDress` / `GetAutoDress`, `Model:SetGlow`, `PlayerModel:RefreshCamera`, `PlayerModel:SetCamDistanceScale`, `PlayerModel:SetCreature` (`displayID`), `PlayerModel:SetDisplayInfo`, `PlayerModel:SetItem`, `PlayerModel:SetPortraitZoom`, `PlayerModel:SetRotation` (`animate`), `PlayerModel:SetUnit` (result), `PlayerModel:CanSetUnit`, `PlayerModel:GetDisplayInfo`, `PlayerModel:SetAnimation`, `PlayerModel:FreezeAnimation`, `PlayerModel:HasAnimation`, `PlayerModel:SetDoBlend` / `GetDoBlend`, `PlayerModel:SetKeepModelOnHide` / `GetKeepModelOnHide`, `PlayerModel:ApplySpellVisualKit` |
|
||||
| [NamePlate](docs/API.md#nameplate) | `C_NamePlate.GetNamePlateForGUID`, `C_NamePlate.GetNamePlateForUnit`, `C_NamePlate.GetNamePlateGUIDs`, `C_NamePlate.GetNamePlates` |
|
||||
| [NameCache](docs/API.md#namecache) | `C_PlayerCache.GetPlayerInfoByName`, `C_PlayerCache.IsEnabled`, `C_PlayerCache.RememberPlayer`, `C_PlayerCache.SetEnabled`, `GetPlayerInfoByGUID`, `UnitNameFromGUID` |
|
||||
| [NewItems](docs/API.md#newitems) | `C_NewItems.ClearAll`, `C_NewItems.IsNewItem`, `C_NewItems.RemoveNewItem` |
|
||||
|
||||
+24
@@ -796,6 +796,7 @@ build instructions.
|
||||
- [`model:GetDoBlend()`](#modelgetdoblend)
|
||||
- [`model:SetKeepModelOnHide(keepModelOnHide)`](#modelsetkeepmodelonhidekeepmodelonhide)
|
||||
- [`model:GetKeepModelOnHide()`](#modelgetkeepmodelonhide)
|
||||
- [`model:ApplySpellVisualKit(spellVisualKitID [, oneShot])`](#modelapplyspellvisualkitspellvisualkitid--oneshot)
|
||||
- [`DressUpModel:UndressSlot(slot)`](#dressupmodelundressslotslot)
|
||||
- [`DressUpModel:TryOn(item [, handSlotName, spellEnchantID])`](#dressupmodeltryonitem--handslotname-spellenchantid)
|
||||
- [`DressUpModel:IsGeoReady()`](#dressupmodelisgeoready)
|
||||
@@ -18736,6 +18737,29 @@ DressUpModel:SetKeepModelOnHide(true)
|
||||
|
||||
Returns the value that `SetKeepModelOnHide` set.
|
||||
|
||||
### `model:ApplySpellVisualKit(spellVisualKitID [, oneShot])`
|
||||
|
||||
Shows the effects of a spell visual kit (a `SpellVisualKit` ID) on the
|
||||
model, for example the glow on the hands while a spell is cast. Each effect
|
||||
attaches to its point on the model: head, chest, base, hands, breath, or
|
||||
weapons. An effect whose point the model does not have does not show.
|
||||
|
||||
If `oneShot` is `true`, each effect plays one time and then fades out. If
|
||||
`oneShot` is `false` or not given, the effects play again and again until
|
||||
the model changes. The model changes after `SetUnit`, `SetDisplayInfo`,
|
||||
`SetCreature`, `SetItem`, and when the frame shows again after it was
|
||||
hidden.
|
||||
|
||||
A kit can also change the color or the transparency of the model. This
|
||||
change stays until the model changes. The kit does not play its animation
|
||||
or its sound. A kit ID that is not in `SpellVisualKit` does nothing.
|
||||
|
||||
```lua
|
||||
PreviewModel:SetUnit("player")
|
||||
PreviewModel:ApplySpellVisualKit(99) -- holy glow on both hands
|
||||
PreviewModel:ApplySpellVisualKit(154, true) -- Holy Light flash, one time
|
||||
```
|
||||
|
||||
### `DressUpModel:UndressSlot(slot)`
|
||||
|
||||
Removes the item in one inventory slot from a `DressUpModel`. `slot` is an
|
||||
|
||||
@@ -5991,6 +5991,23 @@ enum Offsets {
|
||||
VAR_ITEMVISUALS_RECORDS = 0x00C0DB70,
|
||||
VAR_ITEMVISUALS_COUNT = 0x00C0DB74,
|
||||
|
||||
// SpellVisualKit.dbc (35 columns, 0x8C-byte rows). The char-select ghost
|
||||
// kit (0x004727F0) reads the store at 0x004727F6/0x0047280A: nine effect
|
||||
// IDs from +0x0C (attachments 20, 34, 19, 21, 22, 17, 23, 24, 25, pushed
|
||||
// at 0x00472821..0x0047287F), then four procedure kinds from +0x3C with
|
||||
// their first values from +0x4C (the loop at 0x00472888). Kind 1 is a
|
||||
// tint, the value an 0xRRGGBB colour stored as a float; kind 14 an alpha.
|
||||
VAR_SPELLVISUALKIT_RECORDS = 0x00C0D74C,
|
||||
VAR_SPELLVISUALKIT_COUNT = 0x00C0D750,
|
||||
OFF_SPELLVISUALKIT_EFFECTS = 0x0C,
|
||||
OFF_SPELLVISUALKIT_PROC_KIND = 0x3C,
|
||||
OFF_SPELLVISUALKIT_PROC_VALUE = 0x4C,
|
||||
// SpellVisualEffectName.dbc: the model file at +0x08, which the ghost
|
||||
// kit's effect step (0x00472780) creates (0x004727AF).
|
||||
VAR_SPELLVISUALEFFECTNAME_RECORDS = 0x00C0D760,
|
||||
VAR_SPELLVISUALEFFECTNAME_COUNT = 0x00C0D764,
|
||||
OFF_SPELLVISUALEFFECTNAME_FILE = 0x08,
|
||||
|
||||
// ItemSet.dbc — armor/equipment set definitions referenced by
|
||||
// `ItemStats_C::m_itemSet`. Standard 5-DWORD class shape at
|
||||
// 0x00C0DBB8 (records-ptr at +0x08, count at +0x0C). 45 columns,
|
||||
@@ -12925,6 +12942,28 @@ enum Offsets {
|
||||
FUN_MODEL_INSTANCE_READY = 0x00710450,
|
||||
// CM2Model::SetVisible — `__thiscall(model, bool)`.
|
||||
FUN_M2MODEL_SET_VISIBLE = 0x00710C50,
|
||||
// CM2Model::SetAlpha — `__thiscall(model, float)`; SetDiffuseColor —
|
||||
// `__thiscall(model, const float rgb[3])`. Plain stores, which the
|
||||
// ghost kit applies (0x004728C1, 0x00472939). Attached children take
|
||||
// them through the per-frame combine.
|
||||
FUN_M2MODEL_SET_ALPHA = 0x00710CB0,
|
||||
FUN_M2MODEL_SET_DIFFUSE_COLOR = 0x00710CF0,
|
||||
// CM2Model::HasAttachment — `__thiscall(model, id)`, ret 4. Waits for
|
||||
// the model file (0x00710520) when it has not loaded.
|
||||
FUN_M2MODEL_HAS_ATTACHMENT = 0x00712CB0,
|
||||
// CM2Model::Attach — `__thiscall(child, parent, id)`, ret 8. The
|
||||
// parent takes a reference; the attach index resolves once the parent
|
||||
// loads.
|
||||
FUN_M2MODEL_ATTACH = 0x00712F70,
|
||||
// CM2Model::Detach — `__thiscall(child)`: unlinks the child from its
|
||||
// parent, clears its parent (OFF_M2MODEL_PARENT) and drops the parent's
|
||||
// reference. The destructor (0x0070E3F5) detaches every child this way.
|
||||
FUN_M2MODEL_DETACH = 0x00713020,
|
||||
OFF_M2MODEL_PARENT = 0x1CC,
|
||||
// CM2Model::HasLiveParticles — `__thiscall(model)`: +0x3D8, which
|
||||
// Animate sets while an emitter emits or holds a live particle. The
|
||||
// dying-effect drain (0x0061F6CC) releases a stopped effect once false.
|
||||
FUN_M2MODEL_HAS_LIVE_PARTICLES = 0x007135C0,
|
||||
// CM2Model::PlayAnimation — `__thiscall(model, 7 args)`, `ret 0x1C`. The
|
||||
// bake passes (-1, 0, 0, 0, 1.0f, 0, 1).
|
||||
FUN_M2MODEL_PLAY_ANIMATION = 0x007121A0,
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
// 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/>.
|
||||
|
||||
// `PlayerModel:ApplySpellVisualKit(spellVisualKitID [, oneShot])`: a spell's
|
||||
// visual kit on the pane's model.
|
||||
//
|
||||
// The world plays a kit on a unit (0x0060EDF0) through effect objects, spell
|
||||
// durations and the unit's own animation. A pane has none of these. The
|
||||
// engine's own kit on a bare model is the char-select ghost
|
||||
// (0x004727F0), and this is that routine for any kit, on the pane's model:
|
||||
// - Each of the nine effect columns: a model of the SpellVisualEffectName
|
||||
// file, made in the pane's scene and attached at the column's attachment
|
||||
// when the model has it (0x00472780). The children get the frame's light
|
||||
// like the item glows (Model::DisplayInfo::LightChildren).
|
||||
// - The procedures the ghost applies: kind 1 sets the tint, kind 14 the
|
||||
// alpha clamped to 0..1, as plain stores on the model.
|
||||
// The kit's animation, sound and the world-only procedures are not played,
|
||||
// as retail does not play them on a model frame either (probed: no cast
|
||||
// pose, no sound; the tint applies).
|
||||
//
|
||||
// oneShot is the world's one-shot kind: CEffect::AddEffect (0x0061FDD0)
|
||||
// gives the effect model a done callback, and EffectDoneCallback
|
||||
// (0x005FBF50) stops the effect when the sequence ends cleanly (reason 0).
|
||||
// CEffect::Stop (0x006203E0) clears the model's callbacks and hides it, so
|
||||
// its emitters stop and the particles already out finish; each frame the
|
||||
// drain (0x0061F680) releases a stopped effect once HasLiveParticles is
|
||||
// false. Here the release is the detach from the pane's model, run on
|
||||
// FrameTick's tail where no model is mid-draw. Without oneShot an effect
|
||||
// loops until the model is replaced, as on retail: an effect model's sequence
|
||||
// does not loop by itself, so it gets the world's loop callback
|
||||
// (KitEffectLoopCallback, 0x0060ED00), which plays the ended animation again.
|
||||
//
|
||||
// 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).
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "dbc/Lookup.h"
|
||||
#include "model/DisplayInfo.h"
|
||||
#include "tick/FrameTick.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
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};
|
||||
constexpr int kProcedures = 4;
|
||||
constexpr int32_t kProcTint = 1;
|
||||
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;
|
||||
|
||||
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);
|
||||
g_stopped.push_back(model);
|
||||
}
|
||||
|
||||
// KitEffectLoopCallback: a sequence that ended cleanly plays again.
|
||||
void __fastcall OnEffectLoop(void *model, uint32_t /*keyBone*/, uint32_t animID, uint32_t reason,
|
||||
uint32_t /*overshoot*/, uint32_t /*userLow*/,
|
||||
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);
|
||||
}
|
||||
|
||||
// Detaches a stopped effect that is still on its model, then drops our
|
||||
// reference.
|
||||
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);
|
||||
}
|
||||
|
||||
// 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() {
|
||||
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)) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
Discard(model);
|
||||
g_stopped[i] = g_stopped.back();
|
||||
g_stopped.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
const Tick::FrameTick::AutoSubscribe g_drain{&Drain};
|
||||
|
||||
// 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) {
|
||||
const auto kind = Game::Read<int32_t>(kit, Offsets::OFF_SPELLVISUALKIT_PROC_KIND + i * 4);
|
||||
const float value = Game::Read<float>(kit, Offsets::OFF_SPELLVISUALKIT_PROC_VALUE + i * 4);
|
||||
if (kind == kProcTint) {
|
||||
const auto color = static_cast<uint32_t>(static_cast<int32_t>(value));
|
||||
const float rgb[3] = {static_cast<float>((color >> 16) & 0xFF) * kByteScale,
|
||||
static_cast<float>((color >> 8) & 0xFF) * kByteScale,
|
||||
static_cast<float>(color & 0xFF) * kByteScale};
|
||||
reinterpret_cast<SetDiffuse_t>(Offsets::FUN_M2MODEL_SET_DIFFUSE_COLOR)(model, rgb);
|
||||
} else if (kind == kProcAlpha) {
|
||||
const float alpha = value < 0.0f ? 0.0f : value > 1.0f ? 1.0f : value;
|
||||
reinterpret_cast<SetAlpha_t>(Offsets::FUN_M2MODEL_SET_ALPHA)(model, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Apply(void *pane, const uint8_t *kit, bool oneShot) {
|
||||
void *model = Game::Read<void *>(pane, Offsets::OFF_SIMPLEMODELFFX_MODEL_INSTANCE);
|
||||
if (model == nullptr)
|
||||
return;
|
||||
void *scene = reinterpret_cast<GetScene_t>(Offsets::FUN_SIMPLEMODEL_GET_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,
|
||||
Game::Read<uint32_t>(kit, Offsets::OFF_SPELLVISUALKIT_EFFECTS + i * 4));
|
||||
if (effect == nullptr ||
|
||||
!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);
|
||||
if (child == nullptr)
|
||||
continue;
|
||||
SetDoneCallback(child, oneShot ? reinterpret_cast<const void *>(&OnEffectDone)
|
||||
: reinterpret_cast<const void *>(&OnEffectLoop));
|
||||
reinterpret_cast<Attach_t>(Offsets::FUN_M2MODEL_ATTACH)(child, model, kAttachments[i]);
|
||||
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);
|
||||
if (pane == nullptr)
|
||||
return 0;
|
||||
if (!Game::Lua::IsNumber(L, 2)) {
|
||||
Game::Lua::Error(L, "Usage: ApplySpellVisualKit(spellVisualKitID [, oneShot])");
|
||||
return 0;
|
||||
}
|
||||
const uint8_t *kit =
|
||||
DBC::Record(Offsets::VAR_SPELLVISUALKIT_RECORDS, Offsets::VAR_SPELLVISUALKIT_COUNT,
|
||||
static_cast<uint32_t>(Game::Lua::ToNumber(L, 2)));
|
||||
if (kit != nullptr)
|
||||
Apply(pane, kit, Game::Lua::ToBoolean(L, 3) != 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Game::Lua::FrameMethodEntry g_methods[] = {
|
||||
{"ApplySpellVisualKit", &Script_ApplySpellVisualKit},
|
||||
};
|
||||
|
||||
const Game::Doc::Field kApplyArgs[] = {
|
||||
Game::Doc::Req("spellVisualKitID", "number", "A SpellVisualKit ID."),
|
||||
Game::Doc::Opt("oneShot", "boolean", "false",
|
||||
"True plays the effects once; false keeps them until the model changes."),
|
||||
};
|
||||
const Game::Doc::Function kApply{"Shows a spell visual kit's effects on the model.", kApplyArgs,
|
||||
{}};
|
||||
const Game::Doc::Method g_methodDocs[] = {
|
||||
{"ApplySpellVisualKit", &kApply},
|
||||
};
|
||||
|
||||
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])));
|
||||
}
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user