mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-10 20:07:37 +00:00
feat(mount): add C_MountJournal.GetMountFromSpell and GetMountInfoExtraByID
1.12.1 has no mount table, so a mount's ID is the ID of the spell that summons it: GetMountFromSpell(458) returns 458. A spell is a mount when one of its effects applies SPELL_AURA_MOUNTED (78); Spell.dbc is read directly, so mounts the player has not learned resolve too. On Turtle every mount item carries spell 46499 "Add Mount to Collection" instead of its mount spell (the server maps item to spell from its own table), so only the mount spell gives a mount ID. GetMountInfoExtraByID returns the nine retail values. The display ID is the one the server mounts the player on: the mount aura's misc value is a creature entry, and the client's creature cache holds that creature's display (SMSG_CREATURE_QUERY_RESPONSE carries the template's first display, and every mount creature has exactly one). An uncached creature returns nil and starts the cache load through the new Creature::Info::RequestLoad, which C_CreatureInfo.RequestLoadCreatureByID now uses too; CREATURE_DATA_LOAD_RESULT fires when the data lands. Verified in game: 23219 (Swift Mistsaber) returns 14332. Mount type and model scene default to 230 and 4. Seven mounts take retail's values instead (the six Qiraji battle tanks use scene 44, the Riding Turtle type 231), each checked by name against Turtle's Spell.dbc. The seat animation is 91 (Mount), the animation 1.12.1's world mount plays on the rider.
This commit is contained in:
+68
@@ -806,6 +806,10 @@ build instructions.
|
||||
- [`DressUpModel:GetAutoDress()`](#dressupmodelgetautodress)
|
||||
- [`Model:SetGlow(value)`](#modelsetglowvalue)
|
||||
|
||||
- [Mount](#mount)
|
||||
- [`C_MountJournal.GetMountFromSpell(spellID)`](#c_mountjournalgetmountfromspellspellid)
|
||||
- [`C_MountJournal.GetMountInfoExtraByID(mountID)`](#c_mountjournalgetmountinfoextrabyidmountid)
|
||||
|
||||
- [ChatBubbles](#chatbubbles)
|
||||
- [`C_ChatBubbles.GetAllChatBubbles([includeForbidden])`](#c_chatbubblesgetallchatbubblesincludeforbidden)
|
||||
|
||||
@@ -18883,6 +18887,70 @@ first call, it returns `true`.
|
||||
This method exists on every `Model` frame, for code from later clients. It
|
||||
has no effect in the game.
|
||||
|
||||
## Mount
|
||||
|
||||
A mount ID is the spell ID of the spell that summons the mount. For example,
|
||||
the mount ID of Swift Mistsaber is 23219, the ID of its spell.
|
||||
|
||||
### `C_MountJournal.GetMountFromSpell(spellID)`
|
||||
|
||||
Returns the mount ID of a mount spell. For a spell that is not a mount, the
|
||||
function returns `nil`.
|
||||
|
||||
`spellID` can be a spell ID, a spell link, or a spell name. A spell name
|
||||
works only for a spell in your spellbook. A spell ID works for all mount
|
||||
spells, also for a mount that you do not know.
|
||||
|
||||
```lua
|
||||
C_MountJournal.GetMountFromSpell(23219) -- 23219 (Swift Mistsaber)
|
||||
C_MountJournal.GetMountFromSpell(133) -- nil (Fireball)
|
||||
```
|
||||
|
||||
On Turtle WoW, a mount item does not have its mount spell. Each mount item
|
||||
has the spell "Add Mount to Collection" (46499), and this spell is not a
|
||||
mount. Only the mount spell in the spellbook gives a mount ID.
|
||||
|
||||
### `C_MountJournal.GetMountInfoExtraByID(mountID)`
|
||||
|
||||
Returns nine values for a mount. For an ID that is not a mount, the function
|
||||
returns nothing.
|
||||
|
||||
| # | Value | Contents |
|
||||
|---|---|---|
|
||||
| 1 | `creatureDisplayInfoID` | The display ID that you have when you ride the mount. `nil` when the game does not have the data for the mount yet. |
|
||||
| 2 | `description` | The description of the mount spell. |
|
||||
| 3 | `source` | Always `""`. |
|
||||
| 4 | `isSelfMount` | Always `false`. |
|
||||
| 5 | `mountTypeID` | The mount type. `230` for most mounts. |
|
||||
| 6 | `uiModelSceneID` | The model scene for a preview of the mount. `44` for the Qiraji battle tanks, `4` for all other mounts. |
|
||||
| 7 | `animID` | The animation of the rider on the mount. Always `91`. |
|
||||
| 8 | `spellVisualKitID` | Always `0`. |
|
||||
| 9 | `disablePlayerMountPreview` | Always `false`. |
|
||||
|
||||
The display ID comes from the data of the creature that the mount spell
|
||||
summons. If the game does not have this data, the function returns `nil` for
|
||||
the display ID and requests the data. When the data arrives, the game sends
|
||||
`CREATURE_DATA_LOAD_RESULT`. After this event, the function returns the
|
||||
display ID.
|
||||
|
||||
```lua
|
||||
local function ShowMount(model, mountID)
|
||||
local displayID = C_MountJournal.GetMountInfoExtraByID(mountID)
|
||||
if displayID then
|
||||
model:SetDisplayInfo(displayID)
|
||||
end
|
||||
return displayID ~= nil
|
||||
end
|
||||
|
||||
local f = CreateFrame("Frame")
|
||||
f:RegisterEvent("CREATURE_DATA_LOAD_RESULT")
|
||||
f:SetScript("OnEvent", function()
|
||||
ShowMount(MyModel, 23219)
|
||||
end)
|
||||
|
||||
ShowMount(MyModel, 23219) -- shows display 14332 when the data is ready
|
||||
```
|
||||
|
||||
## ChatBubbles
|
||||
|
||||
### `C_ChatBubbles.GetAllChatBubbles([includeForbidden])`
|
||||
|
||||
@@ -131,10 +131,7 @@ int __fastcall Script_RequestLoadCreatureByID(void *L) {
|
||||
Game::Lua::PushBool(L, false);
|
||||
return 1;
|
||||
}
|
||||
auto *cache = reinterpret_cast<void *>(Offsets::VAR_CREATURE_CACHE);
|
||||
const bool accepted =
|
||||
Cache::QueryLoad::RequestLoad(cache, static_cast<uint32_t>(creatureID));
|
||||
Game::Lua::PushBool(L, accepted);
|
||||
Game::Lua::PushBool(L, RequestLoad(static_cast<uint32_t>(creatureID)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -381,4 +378,9 @@ const uint8_t *CacheRecord(uint32_t creatureID) {
|
||||
return creatureID != 0 ? PeekCreature(creatureID) : nullptr;
|
||||
}
|
||||
|
||||
bool RequestLoad(uint32_t creatureID) {
|
||||
return Cache::QueryLoad::RequestLoad(reinterpret_cast<void *>(Offsets::VAR_CREATURE_CACHE),
|
||||
creatureID);
|
||||
}
|
||||
|
||||
} // namespace Creature::Info
|
||||
|
||||
@@ -24,6 +24,12 @@ namespace Creature::Info {
|
||||
// engine's SetDisplay takes, and `PlayerModel:SetCreature` passes it there.
|
||||
const uint8_t *CacheRecord(uint32_t creatureID);
|
||||
|
||||
// Starts loading `creatureID` into the creature cache; fires
|
||||
// `CREATURE_DATA_LOAD_RESULT(creatureID, success)` when it lands (at once
|
||||
// when already cached). False only when the load could not be tracked.
|
||||
// Backs `C_CreatureInfo.RequestLoadCreatureByID`.
|
||||
bool RequestLoad(uint32_t creatureID);
|
||||
|
||||
// The faction group a player race belongs to, resolved the way
|
||||
// `Script_UnitFactionGroup` does it for a player whose object isn't in the
|
||||
// world (its name-cache branch, which reads the race): ChrRaces -> FactionTemplate
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
// 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/>.
|
||||
|
||||
// `C_MountJournal.GetMountFromSpell(spellID)` -> mountID | nil
|
||||
// `C_MountJournal.GetMountInfoExtraByID(mountID)` -> creatureDisplayInfoID,
|
||||
// description, source, isSelfMount, mountTypeID, uiModelSceneID, animID,
|
||||
// spellVisualKitID, disablePlayerMountPreview
|
||||
//
|
||||
// Retail's mount ID indexes its Mount table. 1.12 has no such table, so here
|
||||
// a mount's ID IS its spell ID: GetMountFromSpell(458) returns 458. That
|
||||
// keeps every mount the client knows addressable (Turtle's own mounts are
|
||||
// in no retail table), and retail's numbering means nothing to a 1.12 addon.
|
||||
//
|
||||
// A spell is a mount when one of its effects applies SPELL_AURA_MOUNTED
|
||||
// (78). Spell.dbc is read directly, so a mount not yet learned resolves too.
|
||||
// On Turtle a mount ITEM does not carry its mount spell: every one carries
|
||||
// 46499 "Add Mount to Collection", which teaches a spell from a server-only
|
||||
// item table. So only the mount spell (spellbook, spell link) resolves; 46499
|
||||
// is not a mount. See docs/notes/model-scene.md.
|
||||
//
|
||||
// creatureDisplayInfoID is the display the server mounts the caster on: the
|
||||
// aura's misc value is a creature entry, and the server takes a display from
|
||||
// that creature's template (tortoise-wow Aura::HandleAuraMounted ->
|
||||
// Creature::ChooseDisplayId). The client's creature cache holds the
|
||||
// template's first display (SMSG_CREATURE_QUERY_RESPONSE sends display_id[0])
|
||||
// and every mount creature has exactly one, so the cached display is the
|
||||
// mounted one (23219 Swift Mistsaber -> creature 14555 -> 14332). An
|
||||
// uncached creature returns nil and starts the cache load;
|
||||
// CREATURE_DATA_LOAD_RESULT fires when it lands, as for
|
||||
// C_CreatureInfo.RequestLoadCreatureByID.
|
||||
//
|
||||
// The rest follows the retail Mount table where a 1.12 mount spell has a
|
||||
// row (kRetailRows), else retail's common values: mount type 230 (ground),
|
||||
// model scene 4, seat animation 91 (Mount — what a retail probe returned for
|
||||
// every mount, and what 1.12's world mount seats the rider with), no visual
|
||||
// kit. 1.12 has no self-mounts and no shop source text. The description is
|
||||
// the spell's own, tokens filled in.
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "creature/Info.h"
|
||||
#include "spell/Arg.h"
|
||||
#include "spell/Description.h"
|
||||
#include "spell/Lookup.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Mount::Journal {
|
||||
|
||||
namespace {
|
||||
|
||||
// Retail Mount rows (12.1.7.70740 export) for 1.12 mount spells whose scene
|
||||
// or mount type differs from the defaults below. Only rows whose spell has
|
||||
// the same name in Turtle's Spell.dbc: retail's 3363 (Nether-Swept Drake) is
|
||||
// Turtle's "Summon Riding Gryphon", so it is left out. The other 92 of the
|
||||
// 100 matching spells are scene 4 / type 230.
|
||||
struct RetailRow {
|
||||
uint32_t spellID;
|
||||
uint32_t mountTypeID;
|
||||
uint32_t uiModelSceneID;
|
||||
};
|
||||
constexpr RetailRow kRetailRows[] = {
|
||||
{25863, 230, 44}, // Black Qiraji Battle Tank
|
||||
{25953, 241, 44}, // Blue Qiraji Battle Tank
|
||||
{26054, 241, 44}, // Red Qiraji Battle Tank
|
||||
{26055, 241, 44}, // Yellow Qiraji Battle Tank
|
||||
{26056, 241, 44}, // Green Qiraji Battle Tank
|
||||
{26655, 230, 44}, // Black Qiraji Battle Tank
|
||||
{30174, 231, 4}, // Riding Turtle
|
||||
};
|
||||
constexpr uint32_t kDefaultMountTypeID = 230;
|
||||
constexpr uint32_t kDefaultUiModelSceneID = 4;
|
||||
constexpr uint32_t kSeatAnimation = 91; // Mount
|
||||
|
||||
// The mount spell's record for `mountID`, or null when it is not a mount.
|
||||
const uint8_t *MountRecord(int mountID) {
|
||||
const uint8_t *record = Spell::Lookup::RecordForID(mountID);
|
||||
return Spell::Lookup::HasAuraEffect(record, Offsets::SPELL_AURA_MOUNTED) ? record : nullptr;
|
||||
}
|
||||
|
||||
// The display the mount aura's creature is cached with, 0 when the creature
|
||||
// is not cached yet (and then its load is started).
|
||||
uint32_t MountDisplayID(const uint8_t *record) {
|
||||
const int effect = Spell::Lookup::FindAuraEffect(record, Offsets::SPELL_AURA_MOUNTED);
|
||||
const int creatureID =
|
||||
Game::Read<int>(record, Offsets::OFF_SPELL_RECORD_EFFECT_MISC_VALUE + effect * 4);
|
||||
if (creatureID <= 0)
|
||||
return 0;
|
||||
const uint8_t *creature = Creature::Info::CacheRecord(static_cast<uint32_t>(creatureID));
|
||||
if (creature == nullptr) {
|
||||
Creature::Info::RequestLoad(static_cast<uint32_t>(creatureID));
|
||||
return 0;
|
||||
}
|
||||
return Game::Read<uint32_t>(creature, Offsets::OFF_CREATURE_DISPLAYID);
|
||||
}
|
||||
|
||||
int __fastcall Script_GetMountFromSpell(void *L) {
|
||||
const int spellID = Spell::Arg::ResolveSpellID(L, 1);
|
||||
if (MountRecord(spellID) == nullptr)
|
||||
return 0;
|
||||
Game::Lua::PushNumber(L, static_cast<double>(spellID));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int __fastcall Script_GetMountInfoExtraByID(void *L) {
|
||||
if (!Game::Lua::IsNumber(L, 1))
|
||||
return 0;
|
||||
const int mountID = static_cast<int>(Game::Lua::ToNumber(L, 1));
|
||||
const uint8_t *record = MountRecord(mountID);
|
||||
if (record == nullptr)
|
||||
return 0;
|
||||
|
||||
uint32_t mountTypeID = kDefaultMountTypeID;
|
||||
uint32_t uiModelSceneID = kDefaultUiModelSceneID;
|
||||
for (const RetailRow &row : kRetailRows) {
|
||||
if (row.spellID == static_cast<uint32_t>(mountID)) {
|
||||
mountTypeID = row.mountTypeID;
|
||||
uiModelSceneID = row.uiModelSceneID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (const uint32_t displayID = MountDisplayID(record))
|
||||
Game::Lua::PushNumber(L, static_cast<double>(displayID));
|
||||
else
|
||||
Game::Lua::PushNil(L);
|
||||
char description[1024];
|
||||
Spell::Description::Expand(record, description, sizeof description);
|
||||
Game::Lua::PushString(L, description);
|
||||
Game::Lua::PushString(L, ""); // source
|
||||
Game::Lua::PushBool(L, false); // isSelfMount
|
||||
Game::Lua::PushNumber(L, static_cast<double>(mountTypeID));
|
||||
Game::Lua::PushNumber(L, static_cast<double>(uiModelSceneID));
|
||||
Game::Lua::PushNumber(L, static_cast<double>(kSeatAnimation));
|
||||
Game::Lua::PushNumber(L, 0); // spellVisualKitID
|
||||
Game::Lua::PushBool(L, false); // disablePlayerMountPreview
|
||||
return 9;
|
||||
}
|
||||
|
||||
// --- Documentation ----------------------------------------------------------
|
||||
|
||||
const Game::Doc::Field kGetMountFromSpellArgs[] = {
|
||||
Game::Doc::Req("spellID", "SpellIdentifier", "A spell ID, spell link, or spell name."),
|
||||
};
|
||||
const Game::Doc::Field kGetMountFromSpellRets[] = {
|
||||
Game::Doc::Opt("mountID", "number", nullptr,
|
||||
"The mount's ID, which is its spell ID. Nil when the spell is not a mount."),
|
||||
};
|
||||
const Game::Doc::Function kGetMountFromSpell{
|
||||
"The mount a spell summons. A mount's ID is the ID of the spell that summons it.",
|
||||
kGetMountFromSpellArgs, kGetMountFromSpellRets};
|
||||
|
||||
const Game::Doc::Field kGetMountInfoExtraByIDArgs[] = {
|
||||
Game::Doc::Req("mountID", "number", "A mount ID, which is the mount's spell ID."),
|
||||
};
|
||||
const Game::Doc::Field kGetMountInfoExtraByIDRets[] = {
|
||||
Game::Doc::Opt("creatureDisplayInfoID", "number", nullptr,
|
||||
"The display the player is mounted on. Nil until the mount's creature "
|
||||
"is cached; the call starts loading it, and CREATURE_DATA_LOAD_RESULT "
|
||||
"fires when it arrives."),
|
||||
Game::Doc::Req("description", "cstring", "The mount spell's description."),
|
||||
Game::Doc::Req("source", "cstring", "Always empty."),
|
||||
Game::Doc::Req("isSelfMount", "bool", "Always false."),
|
||||
Game::Doc::Req("mountTypeID", "number", "The mount type; 230 for most mounts."),
|
||||
Game::Doc::Req("uiModelSceneID", "number", "The model scene that shows the mount."),
|
||||
Game::Doc::Req("animID", "number", "The animation the rider plays while mounted."),
|
||||
Game::Doc::Req("spellVisualKitID", "number", "Always 0."),
|
||||
Game::Doc::Req("disablePlayerMountPreview", "bool", "Always false."),
|
||||
};
|
||||
const Game::Doc::Function kGetMountInfoExtraByID{
|
||||
"Display and preview details of a mount. Returns nothing when the ID is not a mount.",
|
||||
kGetMountInfoExtraByIDArgs, kGetMountInfoExtraByIDRets};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterTableFunction("C_MountJournal", "GetMountFromSpell",
|
||||
&Script_GetMountFromSpell, &kGetMountFromSpell);
|
||||
Game::Lua::RegisterTableFunction("C_MountJournal", "GetMountInfoExtraByID",
|
||||
&Script_GetMountInfoExtraByID, &kGetMountInfoExtraByID);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace Mount::Journal
|
||||
Reference in New Issue
Block a user