mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-11 03:23:51 +00:00
feat(pipeline): add WMOU (Wowee Mount catalog) format
Novel open replacement for Blizzard's Mount.dbc +
MountCapability.dbc + MountType.dbc + the mount-related
subsets of Spell.dbc / Item.dbc. The 32nd open format added
to the editor.
Defines all summonable steeds: ground mounts, flying mounts,
swimming mounts, racial mounts (Tauren Plainsrunner for
druids), and class mounts (Warlock dreadsteed, Paladin
charger). Each mount has a summon spell, optional teach
item, riding skill prerequisite, speed bonus, and faction
/ race availability mask.
Cross-references with previously-added formats:
WMOU.entry.summonSpellId -> WSPL.entry.spellId
WMOU.entry.itemIdToLearn -> WIT.entry.itemId
WMOU.entry.requiredSkillId -> WSKL.entry.skillId
(Riding skill ID 762)
WCHC.race.mountSpellId ~= WMOU.entry.summonSpellId
(loose match by spellId)
Format:
• magic "WMOU", version 1, little-endian
• per mount: mountId / name / description / icon /
displayId / summonSpellId / itemIdToLearn /
requiredSkillId+Rank / speedPercent / mountKind /
factionId / categoryId / raceMask
Enums:
• Kind (5): Ground / Flying / Swimming / Hybrid /
Aquatic
• Faction (3): Both / Alliance / Horde
• Category (8): Common / Epic / Racial / Event /
Achievement / Pvp / Quest / ClassMount
API: WoweeMountLoader::save / load / exists / findById.
Three preset emitters showcase typical mount catalogs:
• makeStarter — 3 mounts (ground horse + epic flying
gryphon + aquatic riding turtle)
• makeRacial — 6 racial mounts (4 Alliance: Pinto / Ram /
Frostsaber / Mechanostrider; 2 Horde:
Dire Wolf / Skeletal Horse) with raceMask
gating per WCHC race bit positions
• makeFlying — 4 flying mounts spanning Common (60%) ->
Epic (100%) -> Achievement (280%) -> Pvp
(310%) speed tiers
CLI added (5 flags, 621 documented total now):
--gen-mounts / --gen-mounts-racial / --gen-mounts-flying
--info-wmou / --validate-wmou
Validator catches: mountId=0 + duplicates, empty name,
summonSpellId=0 (mount cannot be cast), unknown enum values,
speedPercent=0 (no speed bonus), flying mount with
requiredSkillRank<150 (player can't fly), Racial category
without raceMask (any race could use — usually a typo).
This commit is contained in:
parent
262f9291b4
commit
8ab049ff9d
8 changed files with 673 additions and 0 deletions
|
|
@ -619,6 +619,7 @@ set(WOWEE_SOURCES
|
|||
src/pipeline/wowee_triggers.cpp
|
||||
src/pipeline/wowee_titles.cpp
|
||||
src/pipeline/wowee_events.cpp
|
||||
src/pipeline/wowee_mounts.cpp
|
||||
src/pipeline/custom_zone_discovery.cpp
|
||||
src/pipeline/dbc_layout.cpp
|
||||
|
||||
|
|
@ -1384,6 +1385,7 @@ add_executable(wowee_editor
|
|||
tools/editor/cli_triggers_catalog.cpp
|
||||
tools/editor/cli_titles_catalog.cpp
|
||||
tools/editor/cli_events_catalog.cpp
|
||||
tools/editor/cli_mounts_catalog.cpp
|
||||
tools/editor/cli_quest_objective.cpp
|
||||
tools/editor/cli_quest_reward.cpp
|
||||
tools/editor/cli_clone.cpp
|
||||
|
|
@ -1481,6 +1483,7 @@ add_executable(wowee_editor
|
|||
src/pipeline/wowee_triggers.cpp
|
||||
src/pipeline/wowee_titles.cpp
|
||||
src/pipeline/wowee_events.cpp
|
||||
src/pipeline/wowee_mounts.cpp
|
||||
src/pipeline/custom_zone_discovery.cpp
|
||||
src/pipeline/terrain_mesh.cpp
|
||||
|
||||
|
|
|
|||
125
include/pipeline/wowee_mounts.hpp
Normal file
125
include/pipeline/wowee_mounts.hpp
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
// Wowee Open Mount catalog (.wmou) — novel replacement for
|
||||
// Blizzard's Mount.dbc + MountCapability.dbc + MountType.dbc
|
||||
// + the mount-related subsets of Spell.dbc / Item.dbc. The
|
||||
// 32nd open format added to the editor.
|
||||
//
|
||||
// Defines all summonable steeds: ground mounts, flying
|
||||
// mounts, swimming mounts, racial mounts (Tauren Plainsrunner
|
||||
// for druids), and class mounts (Warlock dreadsteed,
|
||||
// Paladin charger). Each mount has a summon spell, optional
|
||||
// teach item, riding skill prerequisite, speed bonus, and
|
||||
// faction / race availability mask.
|
||||
//
|
||||
// Cross-references with previously-added formats:
|
||||
// WMOU.entry.summonSpellId → WSPL.entry.spellId
|
||||
// WMOU.entry.itemIdToLearn → WIT.entry.itemId
|
||||
// WMOU.entry.requiredSkillId → WSKL.entry.skillId (riding)
|
||||
// WCHC.race.mountSpellId ≈ WMOU.entry.summonSpellId
|
||||
// (racial mount per race)
|
||||
//
|
||||
// Binary layout (little-endian):
|
||||
// magic[4] = "WMOU"
|
||||
// version (uint32) = current 1
|
||||
// nameLen + name (catalog label)
|
||||
// entryCount (uint32)
|
||||
// entries (each):
|
||||
// mountId (uint32)
|
||||
// nameLen + name
|
||||
// descLen + description
|
||||
// iconLen + iconPath
|
||||
// displayId (uint32)
|
||||
// summonSpellId (uint32)
|
||||
// itemIdToLearn (uint32)
|
||||
// requiredSkillId (uint32)
|
||||
// requiredSkillRank (uint16)
|
||||
// speedPercent (uint16)
|
||||
// mountKind (uint8) / factionId (uint8) / categoryId (uint8) / pad[1]
|
||||
// raceMask (uint32)
|
||||
struct WoweeMount {
|
||||
enum Kind : uint8_t {
|
||||
Ground = 0,
|
||||
Flying = 1,
|
||||
Swimming = 2,
|
||||
Hybrid = 3, // ground + flying (basic flying mount)
|
||||
Aquatic = 4, // ground + swim (sea horse)
|
||||
};
|
||||
|
||||
enum Faction : uint8_t {
|
||||
Both = 0,
|
||||
Alliance = 1,
|
||||
Horde = 2,
|
||||
};
|
||||
|
||||
enum Category : uint8_t {
|
||||
Common = 0,
|
||||
Epic = 1,
|
||||
Racial = 2,
|
||||
Event = 3,
|
||||
Achievement = 4,
|
||||
Pvp = 5,
|
||||
Quest = 6,
|
||||
ClassMount = 7, // warlock dreadsteed / paladin charger
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
uint32_t mountId = 0;
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string iconPath;
|
||||
uint32_t displayId = 0;
|
||||
uint32_t summonSpellId = 0;
|
||||
uint32_t itemIdToLearn = 0;
|
||||
uint32_t requiredSkillId = 0;
|
||||
uint16_t requiredSkillRank = 0;
|
||||
uint16_t speedPercent = 60; // 60 = +60% (apprentice ground)
|
||||
uint8_t mountKind = Ground;
|
||||
uint8_t factionId = Both;
|
||||
uint8_t categoryId = Common;
|
||||
uint32_t raceMask = 0; // 0 = available to all races
|
||||
};
|
||||
|
||||
std::string name;
|
||||
std::vector<Entry> entries;
|
||||
|
||||
bool isValid() const { return !entries.empty(); }
|
||||
|
||||
const Entry* findById(uint32_t mountId) const;
|
||||
|
||||
static const char* kindName(uint8_t k);
|
||||
static const char* factionName(uint8_t f);
|
||||
static const char* categoryName(uint8_t c);
|
||||
};
|
||||
|
||||
class WoweeMountLoader {
|
||||
public:
|
||||
static bool save(const WoweeMount& cat,
|
||||
const std::string& basePath);
|
||||
static WoweeMount load(const std::string& basePath);
|
||||
static bool exists(const std::string& basePath);
|
||||
|
||||
// Preset emitters used by --gen-mounts* variants.
|
||||
//
|
||||
// makeStarter — 3 mounts: 1 ground / 1 flying / 1
|
||||
// swimming with appropriate speed +
|
||||
// riding skill requirements.
|
||||
// makeRacial — 6 racial mounts (one per Alliance + 2
|
||||
// Horde races) all with raceMask gating.
|
||||
// makeFlying — 4 flying mounts spanning common / epic /
|
||||
// achievement / pvp tiers (60% / 100% /
|
||||
// 280% / 310% speed).
|
||||
static WoweeMount makeStarter(const std::string& catalogName);
|
||||
static WoweeMount makeRacial(const std::string& catalogName);
|
||||
static WoweeMount makeFlying(const std::string& catalogName);
|
||||
};
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
270
src/pipeline/wowee_mounts.cpp
Normal file
270
src/pipeline/wowee_mounts.cpp
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
#include "pipeline/wowee_mounts.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kMagic[4] = {'W', 'M', 'O', 'U'};
|
||||
constexpr uint32_t kVersion = 1;
|
||||
constexpr uint32_t kRidingSkillId = 762; // canonical SkillLine "Riding"
|
||||
|
||||
template <typename T>
|
||||
void writePOD(std::ofstream& os, const T& v) {
|
||||
os.write(reinterpret_cast<const char*>(&v), sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool readPOD(std::ifstream& is, T& v) {
|
||||
is.read(reinterpret_cast<char*>(&v), sizeof(T));
|
||||
return is.gcount() == static_cast<std::streamsize>(sizeof(T));
|
||||
}
|
||||
|
||||
void writeStr(std::ofstream& os, const std::string& s) {
|
||||
uint32_t n = static_cast<uint32_t>(s.size());
|
||||
writePOD(os, n);
|
||||
if (n > 0) os.write(s.data(), n);
|
||||
}
|
||||
|
||||
bool readStr(std::ifstream& is, std::string& s) {
|
||||
uint32_t n = 0;
|
||||
if (!readPOD(is, n)) return false;
|
||||
if (n > (1u << 20)) return false;
|
||||
s.resize(n);
|
||||
if (n > 0) {
|
||||
is.read(s.data(), n);
|
||||
if (is.gcount() != static_cast<std::streamsize>(n)) {
|
||||
s.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string normalizePath(std::string base) {
|
||||
if (base.size() < 5 || base.substr(base.size() - 5) != ".wmou") {
|
||||
base += ".wmou";
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const WoweeMount::Entry* WoweeMount::findById(uint32_t mountId) const {
|
||||
for (const auto& e : entries) if (e.mountId == mountId) return &e;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* WoweeMount::kindName(uint8_t k) {
|
||||
switch (k) {
|
||||
case Ground: return "ground";
|
||||
case Flying: return "flying";
|
||||
case Swimming: return "swimming";
|
||||
case Hybrid: return "hybrid";
|
||||
case Aquatic: return "aquatic";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char* WoweeMount::factionName(uint8_t f) {
|
||||
switch (f) {
|
||||
case Both: return "both";
|
||||
case Alliance: return "alliance";
|
||||
case Horde: return "horde";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char* WoweeMount::categoryName(uint8_t c) {
|
||||
switch (c) {
|
||||
case Common: return "common";
|
||||
case Epic: return "epic";
|
||||
case Racial: return "racial";
|
||||
case Event: return "event";
|
||||
case Achievement: return "achievement";
|
||||
case Pvp: return "pvp";
|
||||
case Quest: return "quest";
|
||||
case ClassMount: return "class";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
bool WoweeMountLoader::save(const WoweeMount& cat,
|
||||
const std::string& basePath) {
|
||||
std::ofstream os(normalizePath(basePath), std::ios::binary);
|
||||
if (!os) return false;
|
||||
os.write(kMagic, 4);
|
||||
writePOD(os, kVersion);
|
||||
writeStr(os, cat.name);
|
||||
uint32_t entryCount = static_cast<uint32_t>(cat.entries.size());
|
||||
writePOD(os, entryCount);
|
||||
for (const auto& e : cat.entries) {
|
||||
writePOD(os, e.mountId);
|
||||
writeStr(os, e.name);
|
||||
writeStr(os, e.description);
|
||||
writeStr(os, e.iconPath);
|
||||
writePOD(os, e.displayId);
|
||||
writePOD(os, e.summonSpellId);
|
||||
writePOD(os, e.itemIdToLearn);
|
||||
writePOD(os, e.requiredSkillId);
|
||||
writePOD(os, e.requiredSkillRank);
|
||||
writePOD(os, e.speedPercent);
|
||||
writePOD(os, e.mountKind);
|
||||
writePOD(os, e.factionId);
|
||||
writePOD(os, e.categoryId);
|
||||
uint8_t pad = 0;
|
||||
writePOD(os, pad);
|
||||
writePOD(os, e.raceMask);
|
||||
}
|
||||
return os.good();
|
||||
}
|
||||
|
||||
WoweeMount WoweeMountLoader::load(const std::string& basePath) {
|
||||
WoweeMount out;
|
||||
std::ifstream is(normalizePath(basePath), std::ios::binary);
|
||||
if (!is) return out;
|
||||
char magic[4];
|
||||
is.read(magic, 4);
|
||||
if (std::memcmp(magic, kMagic, 4) != 0) return out;
|
||||
uint32_t version = 0;
|
||||
if (!readPOD(is, version) || version != kVersion) return out;
|
||||
if (!readStr(is, out.name)) return out;
|
||||
uint32_t entryCount = 0;
|
||||
if (!readPOD(is, entryCount)) return out;
|
||||
if (entryCount > (1u << 20)) return out;
|
||||
out.entries.resize(entryCount);
|
||||
for (auto& e : out.entries) {
|
||||
if (!readPOD(is, e.mountId)) { out.entries.clear(); return out; }
|
||||
if (!readStr(is, e.name) || !readStr(is, e.description) ||
|
||||
!readStr(is, e.iconPath)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
if (!readPOD(is, e.displayId) ||
|
||||
!readPOD(is, e.summonSpellId) ||
|
||||
!readPOD(is, e.itemIdToLearn) ||
|
||||
!readPOD(is, e.requiredSkillId) ||
|
||||
!readPOD(is, e.requiredSkillRank) ||
|
||||
!readPOD(is, e.speedPercent) ||
|
||||
!readPOD(is, e.mountKind) ||
|
||||
!readPOD(is, e.factionId) ||
|
||||
!readPOD(is, e.categoryId)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
uint8_t pad = 0;
|
||||
if (!readPOD(is, pad)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
if (!readPOD(is, e.raceMask)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool WoweeMountLoader::exists(const std::string& basePath) {
|
||||
std::ifstream is(normalizePath(basePath), std::ios::binary);
|
||||
return is.good();
|
||||
}
|
||||
|
||||
WoweeMount WoweeMountLoader::makeStarter(const std::string& catalogName) {
|
||||
WoweeMount c;
|
||||
c.name = catalogName;
|
||||
{
|
||||
WoweeMount::Entry e;
|
||||
e.mountId = 1; e.name = "Brown Horse";
|
||||
e.description = "A common riding horse.";
|
||||
e.summonSpellId = 458; // canonical Apprentice Riding mount
|
||||
e.itemIdToLearn = 5656; // item that teaches it
|
||||
e.requiredSkillId = kRidingSkillId;
|
||||
e.requiredSkillRank = 75;
|
||||
e.speedPercent = 60; // +60% ground speed
|
||||
e.mountKind = WoweeMount::Ground;
|
||||
e.factionId = WoweeMount::Alliance;
|
||||
c.entries.push_back(e);
|
||||
}
|
||||
{
|
||||
WoweeMount::Entry e;
|
||||
e.mountId = 2; e.name = "Swift Gryphon";
|
||||
e.description = "Faster flying gryphon for journeyman flyers.";
|
||||
e.summonSpellId = 32242;
|
||||
e.itemIdToLearn = 25470;
|
||||
e.requiredSkillId = kRidingSkillId;
|
||||
e.requiredSkillRank = 225;
|
||||
e.speedPercent = 280; // +280% flying (epic flyer)
|
||||
e.mountKind = WoweeMount::Flying;
|
||||
e.factionId = WoweeMount::Alliance;
|
||||
e.categoryId = WoweeMount::Epic;
|
||||
c.entries.push_back(e);
|
||||
}
|
||||
{
|
||||
WoweeMount::Entry e;
|
||||
e.mountId = 3; e.name = "Riding Turtle";
|
||||
e.description = "A serene ambulatory turtle. Slow but steady.";
|
||||
e.summonSpellId = 30174;
|
||||
e.itemIdToLearn = 23720;
|
||||
e.requiredSkillId = kRidingSkillId;
|
||||
e.requiredSkillRank = 75;
|
||||
e.speedPercent = 60; // ground-level swimming-style mount
|
||||
e.mountKind = WoweeMount::Aquatic;
|
||||
c.entries.push_back(e);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
WoweeMount WoweeMountLoader::makeRacial(const std::string& catalogName) {
|
||||
WoweeMount c;
|
||||
c.name = catalogName;
|
||||
auto add = [&](uint32_t id, const char* name, uint8_t fac,
|
||||
uint32_t race, uint32_t spellId,
|
||||
uint32_t itemId, uint16_t rank) {
|
||||
WoweeMount::Entry e;
|
||||
e.mountId = id; e.name = name;
|
||||
e.summonSpellId = spellId; e.itemIdToLearn = itemId;
|
||||
e.requiredSkillId = kRidingSkillId;
|
||||
e.requiredSkillRank = rank;
|
||||
e.speedPercent = 60;
|
||||
e.mountKind = WoweeMount::Ground;
|
||||
e.factionId = fac; e.categoryId = WoweeMount::Racial;
|
||||
e.raceMask = race;
|
||||
c.entries.push_back(e);
|
||||
};
|
||||
// Alliance racial mounts.
|
||||
add(100, "Pinto", WoweeMount::Alliance, 1u << 0, 470, 2414, 75); // Human
|
||||
add(101, "Brown Ram", WoweeMount::Alliance, 1u << 2, 6648, 5872, 75); // Dwarf
|
||||
add(102, "Striped Frostsaber", WoweeMount::Alliance, 1u << 3, 10789, 8629, 75); // NightElf
|
||||
add(103, "Grey Mechanostrider", WoweeMount::Alliance, 1u << 6, 17453, 13321, 75); // Gnome
|
||||
// Horde racial mounts.
|
||||
add(200, "Dire Wolf", WoweeMount::Horde, 1u << 1, 458, 1132, 75); // Orc (re-uses item)
|
||||
add(201, "Skeletal Horse", WoweeMount::Horde, 1u << 4, 17463, 13332, 75); // Undead
|
||||
return c;
|
||||
}
|
||||
|
||||
WoweeMount WoweeMountLoader::makeFlying(const std::string& catalogName) {
|
||||
WoweeMount c;
|
||||
c.name = catalogName;
|
||||
auto add = [&](uint32_t id, const char* name, uint16_t speed,
|
||||
uint16_t rankReq, uint8_t cat,
|
||||
uint32_t spellId, uint32_t itemId) {
|
||||
WoweeMount::Entry e;
|
||||
e.mountId = id; e.name = name;
|
||||
e.summonSpellId = spellId; e.itemIdToLearn = itemId;
|
||||
e.requiredSkillId = kRidingSkillId;
|
||||
e.requiredSkillRank = rankReq;
|
||||
e.speedPercent = speed;
|
||||
e.mountKind = WoweeMount::Flying;
|
||||
e.categoryId = cat;
|
||||
c.entries.push_back(e);
|
||||
};
|
||||
add(300, "Common Hippogryph", 60, 225, WoweeMount::Common, 32235, 25470);
|
||||
add(301, "Cenarion War Hippogryph", 100, 225, WoweeMount::Epic, 32240, 25471);
|
||||
add(302, "Bronze Drake", 280, 300, WoweeMount::Achievement, 59569, 43951);
|
||||
add(303, "Vicious War Wolf", 310, 300, WoweeMount::Pvp, 60424, 44083);
|
||||
return c;
|
||||
}
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
|
|
@ -91,6 +91,8 @@ const char* const kArgRequired[] = {
|
|||
"--export-wtit-json", "--import-wtit-json",
|
||||
"--gen-events", "--gen-events-yearly", "--gen-events-weekends",
|
||||
"--info-wsea", "--validate-wsea",
|
||||
"--gen-mounts", "--gen-mounts-racial", "--gen-mounts-flying",
|
||||
"--info-wmou", "--validate-wmou",
|
||||
"--gen-weather-temperate", "--gen-weather-arctic",
|
||||
"--gen-weather-desert", "--gen-weather-stormy",
|
||||
"--gen-zone-atmosphere",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@
|
|||
#include "cli_triggers_catalog.hpp"
|
||||
#include "cli_titles_catalog.hpp"
|
||||
#include "cli_events_catalog.hpp"
|
||||
#include "cli_mounts_catalog.hpp"
|
||||
#include "cli_quest_objective.hpp"
|
||||
#include "cli_quest_reward.hpp"
|
||||
#include "cli_clone.hpp"
|
||||
|
|
@ -159,6 +160,7 @@ constexpr DispatchFn kDispatchTable[] = {
|
|||
handleTriggersCatalog,
|
||||
handleTitlesCatalog,
|
||||
handleEventsCatalog,
|
||||
handleMountsCatalog,
|
||||
handleQuestObjective,
|
||||
handleQuestReward,
|
||||
handleClone,
|
||||
|
|
|
|||
|
|
@ -1147,6 +1147,16 @@ void printUsage(const char* argv0) {
|
|||
std::printf(" Print WSEA entries (id / kind / duration / recurrence / xp bonus / token reward / name)\n");
|
||||
std::printf(" --validate-wsea <wsea-base> [--json]\n");
|
||||
std::printf(" Static checks: id>0+unique, name not empty, kind in 0..6, duration>0, no overlapping recurrence\n");
|
||||
std::printf(" --gen-mounts <wmou-base> [name]\n");
|
||||
std::printf(" Emit .wmou starter: 3 mounts (Brown Horse / Swift Gryphon / Riding Turtle) covering ground/flying/aquatic\n");
|
||||
std::printf(" --gen-mounts-racial <wmou-base> [name]\n");
|
||||
std::printf(" Emit .wmou 6 racial mounts (4 Alliance + 2 Horde) with raceMask gating\n");
|
||||
std::printf(" --gen-mounts-flying <wmou-base> [name]\n");
|
||||
std::printf(" Emit .wmou 4 flying mounts spanning common/epic/achievement/pvp tiers (60%%/100%%/280%%/310%% speed)\n");
|
||||
std::printf(" --info-wmou <wmou-base> [--json]\n");
|
||||
std::printf(" Print WMOU entries (id / kind / speed / required riding rank / faction / category / name)\n");
|
||||
std::printf(" --validate-wmou <wmou-base> [--json]\n");
|
||||
std::printf(" Static checks: id>0+unique, name not empty, summonSpellId>0, flying needs rank>=150, racial needs raceMask\n");
|
||||
std::printf(" --gen-weather-temperate <wow-base> [zoneName]\n");
|
||||
std::printf(" Emit .wow weather schedule: clear-dominant + occasional rain + fog (forest / grassland)\n");
|
||||
std::printf(" --gen-weather-arctic <wow-base> [zoneName]\n");
|
||||
|
|
|
|||
250
tools/editor/cli_mounts_catalog.cpp
Normal file
250
tools/editor/cli_mounts_catalog.cpp
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
#include "cli_mounts_catalog.hpp"
|
||||
#include "cli_arg_parse.hpp"
|
||||
#include "cli_box_emitter.hpp"
|
||||
|
||||
#include "pipeline/wowee_mounts.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
namespace cli {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string stripWmouExt(std::string base) {
|
||||
stripExt(base, ".wmou");
|
||||
return base;
|
||||
}
|
||||
|
||||
bool saveOrError(const wowee::pipeline::WoweeMount& c,
|
||||
const std::string& base, const char* cmd) {
|
||||
if (!wowee::pipeline::WoweeMountLoader::save(c, base)) {
|
||||
std::fprintf(stderr, "%s: failed to save %s.wmou\n",
|
||||
cmd, base.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void printGenSummary(const wowee::pipeline::WoweeMount& c,
|
||||
const std::string& base) {
|
||||
std::printf("Wrote %s.wmou\n", base.c_str());
|
||||
std::printf(" catalog : %s\n", c.name.c_str());
|
||||
std::printf(" mounts : %zu\n", c.entries.size());
|
||||
}
|
||||
|
||||
int handleGenStarter(int& i, int argc, char** argv) {
|
||||
std::string base = argv[++i];
|
||||
std::string name = "StarterMounts";
|
||||
if (parseOptArg(i, argc, argv)) name = argv[++i];
|
||||
base = stripWmouExt(base);
|
||||
auto c = wowee::pipeline::WoweeMountLoader::makeStarter(name);
|
||||
if (!saveOrError(c, base, "gen-mounts")) return 1;
|
||||
printGenSummary(c, base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handleGenRacial(int& i, int argc, char** argv) {
|
||||
std::string base = argv[++i];
|
||||
std::string name = "RacialMounts";
|
||||
if (parseOptArg(i, argc, argv)) name = argv[++i];
|
||||
base = stripWmouExt(base);
|
||||
auto c = wowee::pipeline::WoweeMountLoader::makeRacial(name);
|
||||
if (!saveOrError(c, base, "gen-mounts-racial")) return 1;
|
||||
printGenSummary(c, base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handleGenFlying(int& i, int argc, char** argv) {
|
||||
std::string base = argv[++i];
|
||||
std::string name = "FlyingMounts";
|
||||
if (parseOptArg(i, argc, argv)) name = argv[++i];
|
||||
base = stripWmouExt(base);
|
||||
auto c = wowee::pipeline::WoweeMountLoader::makeFlying(name);
|
||||
if (!saveOrError(c, base, "gen-mounts-flying")) return 1;
|
||||
printGenSummary(c, base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handleInfo(int& i, int argc, char** argv) {
|
||||
std::string base = argv[++i];
|
||||
bool jsonOut = consumeJsonFlag(i, argc, argv);
|
||||
base = stripWmouExt(base);
|
||||
if (!wowee::pipeline::WoweeMountLoader::exists(base)) {
|
||||
std::fprintf(stderr, "WMOU not found: %s.wmou\n", base.c_str());
|
||||
return 1;
|
||||
}
|
||||
auto c = wowee::pipeline::WoweeMountLoader::load(base);
|
||||
if (jsonOut) {
|
||||
nlohmann::json j;
|
||||
j["wmou"] = base + ".wmou";
|
||||
j["name"] = c.name;
|
||||
j["count"] = c.entries.size();
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& e : c.entries) {
|
||||
arr.push_back({
|
||||
{"mountId", e.mountId},
|
||||
{"name", e.name},
|
||||
{"description", e.description},
|
||||
{"iconPath", e.iconPath},
|
||||
{"displayId", e.displayId},
|
||||
{"summonSpellId", e.summonSpellId},
|
||||
{"itemIdToLearn", e.itemIdToLearn},
|
||||
{"requiredSkillId", e.requiredSkillId},
|
||||
{"requiredSkillRank", e.requiredSkillRank},
|
||||
{"speedPercent", e.speedPercent},
|
||||
{"mountKind", e.mountKind},
|
||||
{"mountKindName", wowee::pipeline::WoweeMount::kindName(e.mountKind)},
|
||||
{"factionId", e.factionId},
|
||||
{"factionName", wowee::pipeline::WoweeMount::factionName(e.factionId)},
|
||||
{"categoryId", e.categoryId},
|
||||
{"categoryName", wowee::pipeline::WoweeMount::categoryName(e.categoryId)},
|
||||
{"raceMask", e.raceMask},
|
||||
});
|
||||
}
|
||||
j["entries"] = arr;
|
||||
std::printf("%s\n", j.dump(2).c_str());
|
||||
return 0;
|
||||
}
|
||||
std::printf("WMOU: %s.wmou\n", base.c_str());
|
||||
std::printf(" catalog : %s\n", c.name.c_str());
|
||||
std::printf(" mounts : %zu\n", c.entries.size());
|
||||
if (c.entries.empty()) return 0;
|
||||
std::printf(" id kind speed rank faction category name\n");
|
||||
for (const auto& e : c.entries) {
|
||||
std::printf(" %4u %-7s %4u%% %4u %-9s %-12s %s\n",
|
||||
e.mountId,
|
||||
wowee::pipeline::WoweeMount::kindName(e.mountKind),
|
||||
e.speedPercent, e.requiredSkillRank,
|
||||
wowee::pipeline::WoweeMount::factionName(e.factionId),
|
||||
wowee::pipeline::WoweeMount::categoryName(e.categoryId),
|
||||
e.name.c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handleValidate(int& i, int argc, char** argv) {
|
||||
std::string base = argv[++i];
|
||||
bool jsonOut = consumeJsonFlag(i, argc, argv);
|
||||
base = stripWmouExt(base);
|
||||
if (!wowee::pipeline::WoweeMountLoader::exists(base)) {
|
||||
std::fprintf(stderr,
|
||||
"validate-wmou: WMOU not found: %s.wmou\n", base.c_str());
|
||||
return 1;
|
||||
}
|
||||
auto c = wowee::pipeline::WoweeMountLoader::load(base);
|
||||
std::vector<std::string> errors;
|
||||
std::vector<std::string> warnings;
|
||||
if (c.entries.empty()) {
|
||||
warnings.push_back("catalog has zero entries");
|
||||
}
|
||||
std::vector<uint32_t> idsSeen;
|
||||
for (size_t k = 0; k < c.entries.size(); ++k) {
|
||||
const auto& e = c.entries[k];
|
||||
std::string ctx = "entry " + std::to_string(k) +
|
||||
" (id=" + std::to_string(e.mountId);
|
||||
if (!e.name.empty()) ctx += " " + e.name;
|
||||
ctx += ")";
|
||||
if (e.mountId == 0) errors.push_back(ctx + ": mountId is 0");
|
||||
if (e.name.empty()) errors.push_back(ctx + ": name is empty");
|
||||
if (e.summonSpellId == 0) {
|
||||
errors.push_back(ctx + ": summonSpellId is 0 (mount cannot be cast)");
|
||||
}
|
||||
if (e.mountKind > wowee::pipeline::WoweeMount::Aquatic) {
|
||||
errors.push_back(ctx + ": mountKind " +
|
||||
std::to_string(e.mountKind) + " not in 0..4");
|
||||
}
|
||||
if (e.factionId > wowee::pipeline::WoweeMount::Horde) {
|
||||
errors.push_back(ctx + ": factionId " +
|
||||
std::to_string(e.factionId) + " not in 0..2");
|
||||
}
|
||||
if (e.categoryId > wowee::pipeline::WoweeMount::ClassMount) {
|
||||
errors.push_back(ctx + ": categoryId " +
|
||||
std::to_string(e.categoryId) + " not in 0..7");
|
||||
}
|
||||
if (e.speedPercent == 0) {
|
||||
warnings.push_back(ctx +
|
||||
": speedPercent=0 (mount provides no speed bonus)");
|
||||
}
|
||||
// Flying / Hybrid mounts need >= journeyman riding
|
||||
// (rank 150 in canonical Classic+TBC scaling).
|
||||
if ((e.mountKind == wowee::pipeline::WoweeMount::Flying ||
|
||||
e.mountKind == wowee::pipeline::WoweeMount::Hybrid) &&
|
||||
e.requiredSkillRank < 150) {
|
||||
warnings.push_back(ctx +
|
||||
": flying mount with riding rank < 150 (player can't fly)");
|
||||
}
|
||||
// Racial category needs raceMask; non-racial shouldn't have one.
|
||||
if (e.categoryId == wowee::pipeline::WoweeMount::Racial &&
|
||||
e.raceMask == 0) {
|
||||
warnings.push_back(ctx +
|
||||
": Racial category but raceMask=0 (any race can use)");
|
||||
}
|
||||
for (uint32_t prev : idsSeen) {
|
||||
if (prev == e.mountId) {
|
||||
errors.push_back(ctx + ": duplicate mountId");
|
||||
break;
|
||||
}
|
||||
}
|
||||
idsSeen.push_back(e.mountId);
|
||||
}
|
||||
bool ok = errors.empty();
|
||||
if (jsonOut) {
|
||||
nlohmann::json j;
|
||||
j["wmou"] = base + ".wmou";
|
||||
j["ok"] = ok;
|
||||
j["errors"] = errors;
|
||||
j["warnings"] = warnings;
|
||||
std::printf("%s\n", j.dump(2).c_str());
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
std::printf("validate-wmou: %s.wmou\n", base.c_str());
|
||||
if (ok && warnings.empty()) {
|
||||
std::printf(" OK — %zu mounts, all mountIds unique\n",
|
||||
c.entries.size());
|
||||
return 0;
|
||||
}
|
||||
if (!warnings.empty()) {
|
||||
std::printf(" warnings (%zu):\n", warnings.size());
|
||||
for (const auto& w : warnings)
|
||||
std::printf(" - %s\n", w.c_str());
|
||||
}
|
||||
if (!errors.empty()) {
|
||||
std::printf(" ERRORS (%zu):\n", errors.size());
|
||||
for (const auto& e : errors)
|
||||
std::printf(" - %s\n", e.c_str());
|
||||
}
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool handleMountsCatalog(int& i, int argc, char** argv, int& outRc) {
|
||||
if (std::strcmp(argv[i], "--gen-mounts") == 0 && i + 1 < argc) {
|
||||
outRc = handleGenStarter(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--gen-mounts-racial") == 0 && i + 1 < argc) {
|
||||
outRc = handleGenRacial(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--gen-mounts-flying") == 0 && i + 1 < argc) {
|
||||
outRc = handleGenFlying(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--info-wmou") == 0 && i + 1 < argc) {
|
||||
outRc = handleInfo(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--validate-wmou") == 0 && i + 1 < argc) {
|
||||
outRc = handleValidate(i, argc, argv); return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
11
tools/editor/cli_mounts_catalog.hpp
Normal file
11
tools/editor/cli_mounts_catalog.hpp
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
namespace cli {
|
||||
|
||||
bool handleMountsCatalog(int& i, int argc, char** argv, int& outRc);
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
Loading…
Add table
Add a link
Reference in a new issue