mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-11 03:23:51 +00:00
feat(editor): add WANV (Anniversary & Recurring Events) — 121st open format
Novel replacement for the implicit recurring-event scheduler vanilla WoW encoded across the GameEvent SQL table + per-holiday script hooks. Each entry binds one calendar-driven recurring event (yearly holiday like Hallow's End, monthly tribute day, weekly Double XP Weekend, anniversary celebration) to its scheduling rule and its payload (a spell buff applied to all online players, a gift item granted on first event- window login). Eight eventKind values (Holiday / Anniversary / DoubleXP / DoubleHonor / PetBattleWeekend / BattlegroundBonus / SeasonalQuest / Misc) and four recurrenceKind values (Yearly / Monthly / Weekly / OneOff). The startDay field is polymorphic per recurrenceKind: Yearly/Monthly/OneOff use it as 1..31 day-of-month, Weekly uses it as 0..6 weekday (Sun..Sat) — the validator enforces both ranges per kind. Three preset emitters: makeStandardHolidays (5 yearly holidays with realistic spell+item payload bindings — Hallow's End spell 24710, Winter Veil 26157, Brewfest 42500, etc.), makeBonusEvents (4 weekly recurring bonuses — Friday triple-day weekends and Saturday- Sunday double-day pet-battle bonus), makeAnniversary (3 game-launch anniversaries — WoW Nov 23 / TBC Jan 16 / WotLK Nov 13 with overlapping celebration windows). Validator's most novel checks combine calendar + recurrence semantics: per-kind schedule validity (Weekly startDay 0..6 weekday, durationDays <= 7 to prevent self-overlap; Yearly/Monthly/OneOff startMonth 1..12, startDay 1..31 with calendar sanity — Feb cap at 29, Apr/Jun/Sep/Nov cap at 30 for "no Feb 30" / "no Apr 31" errors). Format count 120 -> 121. CLI flag count 1269 -> 1274.
This commit is contained in:
parent
695c22b274
commit
0df50f9f72
10 changed files with 803 additions and 0 deletions
|
|
@ -709,6 +709,7 @@ set(WOWEE_SOURCES
|
|||
src/pipeline/wowee_loot_modes.cpp
|
||||
src/pipeline/wowee_sky_params.cpp
|
||||
src/pipeline/wowee_server_config.cpp
|
||||
src/pipeline/wowee_anniversary_events.cpp
|
||||
src/pipeline/custom_zone_discovery.cpp
|
||||
src/pipeline/dbc_layout.cpp
|
||||
|
||||
|
|
@ -1581,6 +1582,7 @@ add_executable(wowee_editor
|
|||
tools/editor/cli_loot_modes_catalog.cpp
|
||||
tools/editor/cli_sky_params_catalog.cpp
|
||||
tools/editor/cli_server_config_catalog.cpp
|
||||
tools/editor/cli_anniversary_events_catalog.cpp
|
||||
tools/editor/cli_catalog_pluck.cpp
|
||||
tools/editor/cli_catalog_find.cpp
|
||||
tools/editor/cli_catalog_by_name.cpp
|
||||
|
|
@ -1772,6 +1774,7 @@ add_executable(wowee_editor
|
|||
src/pipeline/wowee_loot_modes.cpp
|
||||
src/pipeline/wowee_sky_params.cpp
|
||||
src/pipeline/wowee_server_config.cpp
|
||||
src/pipeline/wowee_anniversary_events.cpp
|
||||
src/pipeline/custom_zone_discovery.cpp
|
||||
src/pipeline/terrain_mesh.cpp
|
||||
|
||||
|
|
|
|||
150
include/pipeline/wowee_anniversary_events.hpp
Normal file
150
include/pipeline/wowee_anniversary_events.hpp
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
// Wowee Open Anniversary & Recurring Event catalog
|
||||
// (.wanv) — novel replacement for the implicit
|
||||
// recurring-event scheduler vanilla WoW encoded across
|
||||
// the GameEvent SQL table + the per-holiday script
|
||||
// hooks. Each entry binds one calendar-driven recurring
|
||||
// event (holiday like Hallow's End, anniversary, double-
|
||||
// XP weekend, brewfest) to its scheduling rule (yearly
|
||||
// on a fixed date, monthly on a fixed day, weekly on a
|
||||
// weekday) and its payload (a spell buff applied to all
|
||||
// online players, a gift item granted on first login
|
||||
// during the event window).
|
||||
//
|
||||
// Cross-references with previously-added formats:
|
||||
// WSPL: payloadSpellId references the WSPL spell
|
||||
// catalog (buff applied to online players for
|
||||
// the event duration).
|
||||
// WIT: payloadItemId references the WIT item
|
||||
// catalog (gift item granted on first event-
|
||||
// window login).
|
||||
//
|
||||
// Binary layout (little-endian):
|
||||
// magic[4] = "WANV"
|
||||
// version (uint32) = current 1
|
||||
// nameLen + name (catalog label)
|
||||
// entryCount (uint32)
|
||||
// entries (each):
|
||||
// eventId (uint32)
|
||||
// nameLen + name
|
||||
// descLen + description
|
||||
// eventKind (uint8) — Holiday / Anniversary
|
||||
// / DoubleXP /
|
||||
// DoubleHonor /
|
||||
// PetBattleWeekend /
|
||||
// BattlegroundBonus /
|
||||
// SeasonalQuest /
|
||||
// Misc
|
||||
// recurrenceKind (uint8) — Yearly / Monthly /
|
||||
// Weekly / OneOff
|
||||
// startMonth (uint8) — 1..12 for Yearly /
|
||||
// Monthly; ignored for
|
||||
// Weekly (use startDay
|
||||
// = weekday 0..6)
|
||||
// startDay (uint8) — 1..31 for Yearly /
|
||||
// Monthly; 0..6 for
|
||||
// Weekly (Sun..Sat)
|
||||
// durationDays (uint16) — event window length
|
||||
// pad0 (uint8) / pad1 (uint8)
|
||||
// payloadSpellId (uint32) — 0 if no buff
|
||||
// payloadItemId (uint32) — 0 if no gift item
|
||||
// iconColorRGBA (uint32)
|
||||
struct WoweeAnniversaryEvents {
|
||||
enum EventKind : uint8_t {
|
||||
Holiday = 0, // seasonal real-world
|
||||
// holiday tie-in
|
||||
Anniversary = 1, // game-launch
|
||||
// anniversary
|
||||
DoubleXP = 2, // experience boost
|
||||
DoubleHonor = 3, // PvP boost
|
||||
PetBattleWeekend = 4, // pet-battle bonus
|
||||
BattlegroundBonus = 5, // BG honor / token
|
||||
// boost
|
||||
SeasonalQuest = 6, // limited-time quest
|
||||
// chain
|
||||
Misc = 255,
|
||||
};
|
||||
|
||||
enum RecurrenceKind : uint8_t {
|
||||
Yearly = 0, // same date every year
|
||||
// (e.g. Hallow's End Oct 18)
|
||||
Monthly = 1, // same day every month
|
||||
// (e.g. monthly tribute day)
|
||||
Weekly = 2, // same weekday every week
|
||||
// (e.g. Tuesday maintenance)
|
||||
OneOff = 3, // single occurrence at the
|
||||
// specified date — Anniversary
|
||||
// events stay this way until
|
||||
// the next year manually
|
||||
// re-schedules
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
uint32_t eventId = 0;
|
||||
std::string name;
|
||||
std::string description;
|
||||
uint8_t eventKind = Holiday;
|
||||
uint8_t recurrenceKind = Yearly;
|
||||
uint8_t startMonth = 1;
|
||||
uint8_t startDay = 1;
|
||||
uint16_t durationDays = 7;
|
||||
uint8_t pad0 = 0;
|
||||
uint8_t pad1 = 0;
|
||||
uint32_t payloadSpellId = 0;
|
||||
uint32_t payloadItemId = 0;
|
||||
uint32_t iconColorRGBA = 0xFFFFFFFFu;
|
||||
};
|
||||
|
||||
std::string name;
|
||||
std::vector<Entry> entries;
|
||||
|
||||
bool isValid() const { return !entries.empty(); }
|
||||
|
||||
const Entry* findById(uint32_t eventId) const;
|
||||
|
||||
// Returns all events of one kind. Used by the event
|
||||
// scheduler to dispatch per-kind handlers (Holiday
|
||||
// events spawn cosmetic NPCs, DoubleXP events
|
||||
// multiply XP rates, BattlegroundBonus events boost
|
||||
// honor accrual).
|
||||
std::vector<const Entry*> findByKind(uint8_t eventKind) const;
|
||||
};
|
||||
|
||||
class WoweeAnniversaryEventsLoader {
|
||||
public:
|
||||
static bool save(const WoweeAnniversaryEvents& cat,
|
||||
const std::string& basePath);
|
||||
static WoweeAnniversaryEvents load(const std::string& basePath);
|
||||
static bool exists(const std::string& basePath);
|
||||
|
||||
// Preset emitters used by --gen-anv* variants.
|
||||
//
|
||||
// makeStandardHolidays — 5 yearly holidays
|
||||
// (Hallow's End / Winter
|
||||
// Veil / Lunar Festival /
|
||||
// Children's Week /
|
||||
// Brewfest).
|
||||
// makeBonusEvents — 4 weekly bonus events
|
||||
// (Double XP Weekend /
|
||||
// Double Honor / Pet
|
||||
// Battle Weekend / BG
|
||||
// Bonus).
|
||||
// makeAnniversary — 3 game-launch
|
||||
// anniversaries (WoW Nov
|
||||
// 23 / TBC Jan 16 / WotLK
|
||||
// Nov 13).
|
||||
static WoweeAnniversaryEvents makeStandardHolidays(const std::string& catalogName);
|
||||
static WoweeAnniversaryEvents makeBonusEvents(const std::string& catalogName);
|
||||
static WoweeAnniversaryEvents makeAnniversary(const std::string& catalogName);
|
||||
};
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
285
src/pipeline/wowee_anniversary_events.cpp
Normal file
285
src/pipeline/wowee_anniversary_events.cpp
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
#include "pipeline/wowee_anniversary_events.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kMagic[4] = {'W', 'A', 'N', 'V'};
|
||||
constexpr uint32_t kVersion = 1;
|
||||
|
||||
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) != ".wanv") {
|
||||
base += ".wanv";
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
uint32_t packRgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 0xFF) {
|
||||
return (static_cast<uint32_t>(a) << 24) |
|
||||
(static_cast<uint32_t>(b) << 16) |
|
||||
(static_cast<uint32_t>(g) << 8) |
|
||||
static_cast<uint32_t>(r);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const WoweeAnniversaryEvents::Entry*
|
||||
WoweeAnniversaryEvents::findById(uint32_t eventId) const {
|
||||
for (const auto& e : entries)
|
||||
if (e.eventId == eventId) return &e;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<const WoweeAnniversaryEvents::Entry*>
|
||||
WoweeAnniversaryEvents::findByKind(uint8_t eventKind) const {
|
||||
std::vector<const Entry*> out;
|
||||
for (const auto& e : entries)
|
||||
if (e.eventKind == eventKind) out.push_back(&e);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool WoweeAnniversaryEventsLoader::save(
|
||||
const WoweeAnniversaryEvents& 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.eventId);
|
||||
writeStr(os, e.name);
|
||||
writeStr(os, e.description);
|
||||
writePOD(os, e.eventKind);
|
||||
writePOD(os, e.recurrenceKind);
|
||||
writePOD(os, e.startMonth);
|
||||
writePOD(os, e.startDay);
|
||||
writePOD(os, e.durationDays);
|
||||
writePOD(os, e.pad0);
|
||||
writePOD(os, e.pad1);
|
||||
writePOD(os, e.payloadSpellId);
|
||||
writePOD(os, e.payloadItemId);
|
||||
writePOD(os, e.iconColorRGBA);
|
||||
}
|
||||
return os.good();
|
||||
}
|
||||
|
||||
WoweeAnniversaryEvents WoweeAnniversaryEventsLoader::load(
|
||||
const std::string& basePath) {
|
||||
WoweeAnniversaryEvents 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.eventId)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
if (!readStr(is, e.name) || !readStr(is, e.description)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
if (!readPOD(is, e.eventKind) ||
|
||||
!readPOD(is, e.recurrenceKind) ||
|
||||
!readPOD(is, e.startMonth) ||
|
||||
!readPOD(is, e.startDay) ||
|
||||
!readPOD(is, e.durationDays) ||
|
||||
!readPOD(is, e.pad0) ||
|
||||
!readPOD(is, e.pad1) ||
|
||||
!readPOD(is, e.payloadSpellId) ||
|
||||
!readPOD(is, e.payloadItemId) ||
|
||||
!readPOD(is, e.iconColorRGBA)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool WoweeAnniversaryEventsLoader::exists(
|
||||
const std::string& basePath) {
|
||||
std::ifstream is(normalizePath(basePath), std::ios::binary);
|
||||
return is.good();
|
||||
}
|
||||
|
||||
WoweeAnniversaryEvents
|
||||
WoweeAnniversaryEventsLoader::makeStandardHolidays(
|
||||
const std::string& catalogName) {
|
||||
using A = WoweeAnniversaryEvents;
|
||||
WoweeAnniversaryEvents c;
|
||||
c.name = catalogName;
|
||||
auto add = [&](uint32_t id, const char* name,
|
||||
uint8_t month, uint8_t day,
|
||||
uint16_t days, uint32_t spellId,
|
||||
uint32_t itemId, const char* desc) {
|
||||
A::Entry e;
|
||||
e.eventId = id; e.name = name; e.description = desc;
|
||||
e.eventKind = A::Holiday;
|
||||
e.recurrenceKind = A::Yearly;
|
||||
e.startMonth = month;
|
||||
e.startDay = day;
|
||||
e.durationDays = days;
|
||||
e.payloadSpellId = spellId;
|
||||
e.payloadItemId = itemId;
|
||||
e.iconColorRGBA = packRgba(220, 200, 80); // holiday gold
|
||||
c.entries.push_back(e);
|
||||
};
|
||||
add(1, "HallowsEnd", 10, 18, 14, 24710, 33226,
|
||||
"Hallow's End — Oct 18 to Nov 1 yearly. "
|
||||
"14-day window with cosmetic costume buff "
|
||||
"(spell 24710 Trick or Treat) and gift basket "
|
||||
"item (33226).");
|
||||
add(2, "WintersVeil", 12, 16, 17, 26157, 21525,
|
||||
"Feast of Winter Veil — Dec 16 to Jan 1 yearly. "
|
||||
"17-day window with snowfall environmental buff "
|
||||
"(spell 26157) and Smokywood Pastures gift "
|
||||
"(21525).");
|
||||
add(3, "LunarFestival", 1, 22, 21, 8898, 21100,
|
||||
"Lunar Festival — late Jan to mid Feb yearly. "
|
||||
"21-day window with Coin of Ancestry quest "
|
||||
"currency (item 21100). Buff: Spirit of Yu'lon "
|
||||
"8898.");
|
||||
add(4, "ChildrensWeek", 5, 1, 7, 0, 23007,
|
||||
"Children's Week — May 1-7 yearly. 7-day "
|
||||
"window with orphan companion-pet quest reward "
|
||||
"(item 23007 Whiskers the Rat). No buff payload.");
|
||||
add(5, "Brewfest", 9, 20, 17, 42500, 33927,
|
||||
"Brewfest — Sep 20 to Oct 6 yearly. 17-day "
|
||||
"window with Brewfest Buff (spell 42500) and "
|
||||
"Brewfest gift box (33927).");
|
||||
return c;
|
||||
}
|
||||
|
||||
WoweeAnniversaryEvents
|
||||
WoweeAnniversaryEventsLoader::makeBonusEvents(
|
||||
const std::string& catalogName) {
|
||||
using A = WoweeAnniversaryEvents;
|
||||
WoweeAnniversaryEvents c;
|
||||
c.name = catalogName;
|
||||
auto add = [&](uint32_t id, const char* name,
|
||||
uint8_t kind, uint8_t weekday,
|
||||
uint16_t days, uint32_t spellId,
|
||||
const char* desc) {
|
||||
A::Entry e;
|
||||
e.eventId = id; e.name = name; e.description = desc;
|
||||
e.eventKind = kind;
|
||||
e.recurrenceKind = A::Weekly;
|
||||
e.startMonth = 0; // ignored for Weekly
|
||||
e.startDay = weekday; // 0=Sunday..6=Saturday
|
||||
e.durationDays = days;
|
||||
e.payloadSpellId = spellId;
|
||||
e.payloadItemId = 0;
|
||||
e.iconColorRGBA = packRgba(140, 200, 255); // bonus blue
|
||||
c.entries.push_back(e);
|
||||
};
|
||||
add(100, "DoubleXPWeekend", A::DoubleXP, 5, 3, 0,
|
||||
"Double XP Weekend — Friday through Sunday "
|
||||
"(weekday 5, 3-day window). Server XP rate "
|
||||
"doubled via WCFG override; no per-character "
|
||||
"spell buff needed.");
|
||||
add(101, "DoubleHonorWeekend", A::DoubleHonor, 5, 3, 0,
|
||||
"Double Honor Weekend — Friday through Sunday. "
|
||||
"PvP honor accrual doubled.");
|
||||
add(102, "PetBattleWeekend", A::PetBattleWeekend,
|
||||
6, 2, 0,
|
||||
"Pet Battle Weekend — Saturday-Sunday only "
|
||||
"(weekday 6, 2-day window). +50%% pet XP from "
|
||||
"battles. Anachronistic for WotLK (Pet Battles "
|
||||
"came in MoP) but useful template for custom "
|
||||
"servers.");
|
||||
add(103, "BattlegroundBonus", A::BattlegroundBonus,
|
||||
2, 1, 0,
|
||||
"Battleground Bonus Day — Tuesday only "
|
||||
"(weekday 2). Random BG queue grants +100%% "
|
||||
"tokens for that day. Tuesday chosen as a "
|
||||
"weekday-traffic boost to balance the weekend "
|
||||
"events.");
|
||||
return c;
|
||||
}
|
||||
|
||||
WoweeAnniversaryEvents
|
||||
WoweeAnniversaryEventsLoader::makeAnniversary(
|
||||
const std::string& catalogName) {
|
||||
using A = WoweeAnniversaryEvents;
|
||||
WoweeAnniversaryEvents c;
|
||||
c.name = catalogName;
|
||||
auto add = [&](uint32_t id, const char* name,
|
||||
uint8_t month, uint8_t day,
|
||||
uint16_t days, uint32_t spellId,
|
||||
uint32_t itemId, const char* desc) {
|
||||
A::Entry e;
|
||||
e.eventId = id; e.name = name; e.description = desc;
|
||||
e.eventKind = A::Anniversary;
|
||||
e.recurrenceKind = A::Yearly;
|
||||
e.startMonth = month;
|
||||
e.startDay = day;
|
||||
e.durationDays = days;
|
||||
e.payloadSpellId = spellId;
|
||||
e.payloadItemId = itemId;
|
||||
e.iconColorRGBA = packRgba(180, 60, 60); // anniversary red
|
||||
c.entries.push_back(e);
|
||||
};
|
||||
add(200, "WoWLaunchAnniversary", 11, 23, 14, 71601, 49700,
|
||||
"World of Warcraft launch anniversary — Nov 23 "
|
||||
"yearly (US launch 2004). 14-day celebration "
|
||||
"window with Anniversary Buff (spell 71601 "
|
||||
"Bloody Anniversary) and gift item 49700.");
|
||||
add(201, "TBCLaunchAnniversary", 1, 16, 7, 71601, 49701,
|
||||
"The Burning Crusade launch anniversary — "
|
||||
"Jan 16 yearly (2007). 7-day window. Same "
|
||||
"Anniversary Buff spell, distinct gift item.");
|
||||
add(202, "WotLKLaunchAnniversary", 11, 13, 7, 71601, 49702,
|
||||
"Wrath of the Lich King launch anniversary — "
|
||||
"Nov 13 yearly (2008). 7-day window. Overlaps "
|
||||
"with WoW Launch Anniversary by 10 days — both "
|
||||
"events run concurrently for combined celebration.");
|
||||
return c;
|
||||
}
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
337
tools/editor/cli_anniversary_events_catalog.cpp
Normal file
337
tools/editor/cli_anniversary_events_catalog.cpp
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
#include "cli_anniversary_events_catalog.hpp"
|
||||
#include "cli_arg_parse.hpp"
|
||||
#include "cli_box_emitter.hpp"
|
||||
|
||||
#include "pipeline/wowee_anniversary_events.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
namespace cli {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string stripWanvExt(std::string base) {
|
||||
stripExt(base, ".wanv");
|
||||
return base;
|
||||
}
|
||||
|
||||
const char* eventKindName(uint8_t k) {
|
||||
using A = wowee::pipeline::WoweeAnniversaryEvents;
|
||||
switch (k) {
|
||||
case A::Holiday: return "holiday";
|
||||
case A::Anniversary: return "anniversary";
|
||||
case A::DoubleXP: return "doublexp";
|
||||
case A::DoubleHonor: return "doublehonor";
|
||||
case A::PetBattleWeekend: return "petbattle";
|
||||
case A::BattlegroundBonus: return "bgbonus";
|
||||
case A::SeasonalQuest: return "seasonalquest";
|
||||
case A::Misc: return "misc";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char* recurrenceKindName(uint8_t k) {
|
||||
using A = wowee::pipeline::WoweeAnniversaryEvents;
|
||||
switch (k) {
|
||||
case A::Yearly: return "yearly";
|
||||
case A::Monthly: return "monthly";
|
||||
case A::Weekly: return "weekly";
|
||||
case A::OneOff: return "oneoff";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char* weekdayName(uint8_t d) {
|
||||
static const char* kDays[] = {
|
||||
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
|
||||
};
|
||||
return d <= 6 ? kDays[d] : "?";
|
||||
}
|
||||
|
||||
bool saveOrError(const wowee::pipeline::WoweeAnniversaryEvents& c,
|
||||
const std::string& base, const char* cmd) {
|
||||
if (!wowee::pipeline::WoweeAnniversaryEventsLoader::save(
|
||||
c, base)) {
|
||||
std::fprintf(stderr, "%s: failed to save %s.wanv\n",
|
||||
cmd, base.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void printGenSummary(const wowee::pipeline::WoweeAnniversaryEvents& c,
|
||||
const std::string& base) {
|
||||
std::printf("Wrote %s.wanv\n", base.c_str());
|
||||
std::printf(" catalog : %s\n", c.name.c_str());
|
||||
std::printf(" events : %zu\n", c.entries.size());
|
||||
}
|
||||
|
||||
int handleGenHolidays(int& i, int argc, char** argv) {
|
||||
std::string base = argv[++i];
|
||||
std::string name = "StandardHolidays";
|
||||
if (parseOptArg(i, argc, argv)) name = argv[++i];
|
||||
base = stripWanvExt(base);
|
||||
auto c = wowee::pipeline::WoweeAnniversaryEventsLoader::
|
||||
makeStandardHolidays(name);
|
||||
if (!saveOrError(c, base, "gen-anv")) return 1;
|
||||
printGenSummary(c, base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handleGenBonus(int& i, int argc, char** argv) {
|
||||
std::string base = argv[++i];
|
||||
std::string name = "WeeklyBonusEvents";
|
||||
if (parseOptArg(i, argc, argv)) name = argv[++i];
|
||||
base = stripWanvExt(base);
|
||||
auto c = wowee::pipeline::WoweeAnniversaryEventsLoader::
|
||||
makeBonusEvents(name);
|
||||
if (!saveOrError(c, base, "gen-anv-bonus")) return 1;
|
||||
printGenSummary(c, base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handleGenAnniversary(int& i, int argc, char** argv) {
|
||||
std::string base = argv[++i];
|
||||
std::string name = "GameLaunchAnniversaries";
|
||||
if (parseOptArg(i, argc, argv)) name = argv[++i];
|
||||
base = stripWanvExt(base);
|
||||
auto c = wowee::pipeline::WoweeAnniversaryEventsLoader::
|
||||
makeAnniversary(name);
|
||||
if (!saveOrError(c, base, "gen-anv-launch")) 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 = stripWanvExt(base);
|
||||
if (!wowee::pipeline::WoweeAnniversaryEventsLoader::exists(
|
||||
base)) {
|
||||
std::fprintf(stderr, "WANV not found: %s.wanv\n", base.c_str());
|
||||
return 1;
|
||||
}
|
||||
auto c = wowee::pipeline::WoweeAnniversaryEventsLoader::load(
|
||||
base);
|
||||
if (jsonOut) {
|
||||
nlohmann::json j;
|
||||
j["wanv"] = base + ".wanv";
|
||||
j["name"] = c.name;
|
||||
j["count"] = c.entries.size();
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& e : c.entries) {
|
||||
arr.push_back({
|
||||
{"eventId", e.eventId},
|
||||
{"name", e.name},
|
||||
{"description", e.description},
|
||||
{"eventKind", e.eventKind},
|
||||
{"eventKindName", eventKindName(e.eventKind)},
|
||||
{"recurrenceKind", e.recurrenceKind},
|
||||
{"recurrenceKindName",
|
||||
recurrenceKindName(e.recurrenceKind)},
|
||||
{"startMonth", e.startMonth},
|
||||
{"startDay", e.startDay},
|
||||
{"durationDays", e.durationDays},
|
||||
{"payloadSpellId", e.payloadSpellId},
|
||||
{"payloadItemId", e.payloadItemId},
|
||||
{"iconColorRGBA", e.iconColorRGBA},
|
||||
});
|
||||
}
|
||||
j["entries"] = arr;
|
||||
std::printf("%s\n", j.dump(2).c_str());
|
||||
return 0;
|
||||
}
|
||||
std::printf("WANV: %s.wanv\n", base.c_str());
|
||||
std::printf(" catalog : %s\n", c.name.c_str());
|
||||
std::printf(" events : %zu\n", c.entries.size());
|
||||
if (c.entries.empty()) return 0;
|
||||
std::printf(" id kind recurrence schedule dur(d) spell item name\n");
|
||||
for (const auto& e : c.entries) {
|
||||
char schedule[32];
|
||||
using A = wowee::pipeline::WoweeAnniversaryEvents;
|
||||
if (e.recurrenceKind == A::Weekly) {
|
||||
std::snprintf(schedule, sizeof(schedule),
|
||||
"%-3s every week", weekdayName(e.startDay));
|
||||
} else if (e.recurrenceKind == A::Monthly) {
|
||||
std::snprintf(schedule, sizeof(schedule),
|
||||
"Day %u every month", e.startDay);
|
||||
} else {
|
||||
std::snprintf(schedule, sizeof(schedule),
|
||||
"%02u/%02u %s", e.startMonth, e.startDay,
|
||||
recurrenceKindName(e.recurrenceKind));
|
||||
}
|
||||
std::printf(" %4u %-10s %-10s %-15s %4u %5u %5u %s\n",
|
||||
e.eventId, eventKindName(e.eventKind),
|
||||
recurrenceKindName(e.recurrenceKind),
|
||||
schedule, e.durationDays,
|
||||
e.payloadSpellId, e.payloadItemId,
|
||||
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 = stripWanvExt(base);
|
||||
if (!wowee::pipeline::WoweeAnniversaryEventsLoader::exists(
|
||||
base)) {
|
||||
std::fprintf(stderr,
|
||||
"validate-wanv: WANV not found: %s.wanv\n",
|
||||
base.c_str());
|
||||
return 1;
|
||||
}
|
||||
auto c = wowee::pipeline::WoweeAnniversaryEventsLoader::load(
|
||||
base);
|
||||
std::vector<std::string> errors;
|
||||
std::vector<std::string> warnings;
|
||||
if (c.entries.empty()) {
|
||||
warnings.push_back("catalog has zero entries");
|
||||
}
|
||||
std::set<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.eventId);
|
||||
if (!e.name.empty()) ctx += " " + e.name;
|
||||
ctx += ")";
|
||||
if (e.eventId == 0)
|
||||
errors.push_back(ctx + ": eventId is 0");
|
||||
if (e.name.empty())
|
||||
errors.push_back(ctx + ": name is empty");
|
||||
if (e.eventKind > 6 && e.eventKind != 255) {
|
||||
errors.push_back(ctx + ": eventKind " +
|
||||
std::to_string(e.eventKind) +
|
||||
" out of range (must be 0..6 or 255 Misc)");
|
||||
}
|
||||
if (e.recurrenceKind > 3) {
|
||||
errors.push_back(ctx + ": recurrenceKind " +
|
||||
std::to_string(e.recurrenceKind) +
|
||||
" out of range (must be 0..3)");
|
||||
}
|
||||
if (e.durationDays == 0) {
|
||||
errors.push_back(ctx +
|
||||
": durationDays is 0 — event would never "
|
||||
"have an active window");
|
||||
}
|
||||
// Per-recurrence schedule validity: Yearly /
|
||||
// Monthly / OneOff need valid month + day; Weekly
|
||||
// ignores month + uses day as weekday 0..6.
|
||||
using A = wowee::pipeline::WoweeAnniversaryEvents;
|
||||
if (e.recurrenceKind == A::Weekly) {
|
||||
if (e.startDay > 6) {
|
||||
errors.push_back(ctx +
|
||||
": Weekly recurrence with startDay " +
|
||||
std::to_string(e.startDay) +
|
||||
" > 6 — must be 0 (Sun) through 6 (Sat)");
|
||||
}
|
||||
if (e.durationDays > 7) {
|
||||
warnings.push_back(ctx +
|
||||
": Weekly recurrence with "
|
||||
"durationDays > 7 — event would "
|
||||
"overlap with itself across week "
|
||||
"boundaries");
|
||||
}
|
||||
} else {
|
||||
if (e.startMonth < 1 || e.startMonth > 12) {
|
||||
errors.push_back(ctx +
|
||||
": startMonth " +
|
||||
std::to_string(e.startMonth) +
|
||||
" out of range (must be 1..12 for "
|
||||
"Yearly / Monthly / OneOff)");
|
||||
}
|
||||
if (e.startDay < 1 || e.startDay > 31) {
|
||||
errors.push_back(ctx + ": startDay " +
|
||||
std::to_string(e.startDay) +
|
||||
" out of range (must be 1..31)");
|
||||
}
|
||||
// Calendar sanity: Feb has 28-29 days, etc.
|
||||
// The validator doesn't try to be a full
|
||||
// calendar — just catches the obvious "Feb 30"
|
||||
// type errors.
|
||||
if (e.startMonth == 2 && e.startDay > 29) {
|
||||
errors.push_back(ctx +
|
||||
": startDay " + std::to_string(e.startDay) +
|
||||
" for February — must be 1..29 (28 in "
|
||||
"non-leap years; the schedule rolls "
|
||||
"over to Mar 1 in those cases)");
|
||||
}
|
||||
if ((e.startMonth == 4 || e.startMonth == 6 ||
|
||||
e.startMonth == 9 || e.startMonth == 11) &&
|
||||
e.startDay > 30) {
|
||||
errors.push_back(ctx +
|
||||
": startDay 31 for month " +
|
||||
std::to_string(e.startMonth) +
|
||||
" — that month only has 30 days");
|
||||
}
|
||||
}
|
||||
if (!idsSeen.insert(e.eventId).second) {
|
||||
errors.push_back(ctx + ": duplicate eventId");
|
||||
}
|
||||
}
|
||||
bool ok = errors.empty();
|
||||
if (jsonOut) {
|
||||
nlohmann::json j;
|
||||
j["wanv"] = base + ".wanv";
|
||||
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-wanv: %s.wanv\n", base.c_str());
|
||||
if (ok && warnings.empty()) {
|
||||
std::printf(" OK — %zu events, all eventIds "
|
||||
"unique, calendar dates valid\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 handleAnniversaryEventsCatalog(int& i, int argc, char** argv,
|
||||
int& outRc) {
|
||||
if (std::strcmp(argv[i], "--gen-anv") == 0 && i + 1 < argc) {
|
||||
outRc = handleGenHolidays(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--gen-anv-bonus") == 0 &&
|
||||
i + 1 < argc) {
|
||||
outRc = handleGenBonus(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--gen-anv-launch") == 0 &&
|
||||
i + 1 < argc) {
|
||||
outRc = handleGenAnniversary(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--info-wanv") == 0 && i + 1 < argc) {
|
||||
outRc = handleInfo(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--validate-wanv") == 0 && i + 1 < argc) {
|
||||
outRc = handleValidate(i, argc, argv); return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
12
tools/editor/cli_anniversary_events_catalog.hpp
Normal file
12
tools/editor/cli_anniversary_events_catalog.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
namespace cli {
|
||||
|
||||
bool handleAnniversaryEventsCatalog(int& i, int argc, char** argv,
|
||||
int& outRc);
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
|
|
@ -371,6 +371,8 @@ const char* const kArgRequired[] = {
|
|||
"--gen-cfg", "--gen-cfg-perf", "--gen-cfg-sec",
|
||||
"--info-wcfg", "--validate-wcfg",
|
||||
"--export-wcfg-json", "--import-wcfg-json",
|
||||
"--gen-anv", "--gen-anv-bonus", "--gen-anv-launch",
|
||||
"--info-wanv", "--validate-wanv",
|
||||
"--gen-weather-temperate", "--gen-weather-arctic",
|
||||
"--gen-weather-desert", "--gen-weather-stormy",
|
||||
"--gen-zone-atmosphere",
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@
|
|||
#include "cli_loot_modes_catalog.hpp"
|
||||
#include "cli_sky_params_catalog.hpp"
|
||||
#include "cli_server_config_catalog.hpp"
|
||||
#include "cli_anniversary_events_catalog.hpp"
|
||||
#include "cli_catalog_pluck.hpp"
|
||||
#include "cli_catalog_find.hpp"
|
||||
#include "cli_catalog_by_name.hpp"
|
||||
|
|
@ -375,6 +376,7 @@ constexpr DispatchFn kDispatchTable[] = {
|
|||
handleLootModesCatalog,
|
||||
handleSkyParamsCatalog,
|
||||
handleServerConfigCatalog,
|
||||
handleAnniversaryEventsCatalog,
|
||||
handleCatalogPluck,
|
||||
handleCatalogFind,
|
||||
handleCatalogByName,
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ constexpr FormatMagicEntry kFormats[] = {
|
|||
{{'W','L','M','A'}, ".wlma", "loot", "--info-wlma", "Loot mode policy catalog"},
|
||||
{{'W','S','K','P'}, ".wskp", "world", "--info-wskp", "Sky parameters catalog"},
|
||||
{{'W','C','F','G'}, ".wcfg", "server", "--info-wcfg", "Server config catalog"},
|
||||
{{'W','A','N','V'}, ".wanv", "events", "--info-wanv", "Anniversary & recurring event catalog"},
|
||||
{{'W','F','A','C'}, ".wfac", "factions", nullptr, "Faction catalog"},
|
||||
{{'W','L','C','K'}, ".wlck", "locks", nullptr, "Lock catalog"},
|
||||
{{'W','S','K','L'}, ".wskl", "skills", nullptr, "Skill catalog"},
|
||||
|
|
|
|||
|
|
@ -2447,6 +2447,16 @@ void printUsage(const char* argv0) {
|
|||
std::printf(" Export binary .wcfg to a human-editable JSON sidecar (defaults to <base>.wcfg.json; emits configKind + valueKind as int+name; ALL three value carriers (floatValue/intValue/strValue) plus an activeValue derived field rendering only the meaningful one per kind)\n");
|
||||
std::printf(" --import-wcfg-json <json-path> [out-base]\n");
|
||||
std::printf(" Import a .wcfg.json sidecar back into binary .wcfg (configKind int OR \"xprate\"/\"droprate\"/\"honorrate\"/\"restedxp\"/\"realmtype\"/\"worldflag\"/\"performance\"/\"security\"/\"misc\"; valueKind int OR \"float\"/\"int\"/\"bool\"/\"string\"; restartRequired bool OR int)\n");
|
||||
std::printf(" --gen-anv <wanv-base> [name]\n");
|
||||
std::printf(" Emit .wanv 5 yearly holidays (Hallow's End / Winter Veil / Lunar Festival / Children's Week / Brewfest) with payload spell+item bindings\n");
|
||||
std::printf(" --gen-anv-bonus <wanv-base> [name]\n");
|
||||
std::printf(" Emit .wanv 4 weekly bonus events (Double XP weekend / Double Honor / Pet Battle weekend / BG Bonus Day) with weekly recurrence\n");
|
||||
std::printf(" --gen-anv-launch <wanv-base> [name]\n");
|
||||
std::printf(" Emit .wanv 3 game-launch anniversaries (WoW Nov 23 / TBC Jan 16 / WotLK Nov 13) with overlapping celebration windows\n");
|
||||
std::printf(" --info-wanv <wanv-base> [--json]\n");
|
||||
std::printf(" Print WANV entries (id / kind / recurrence / schedule / duration / payload spell+item / name)\n");
|
||||
std::printf(" --validate-wanv <wanv-base> [--json]\n");
|
||||
std::printf(" Static checks: id+name required, eventKind 0..6 OR 255 Misc, recurrenceKind 0..3, durationDays > 0, no duplicate eventIds; per-recurrence schedule validity (Weekly: startDay 0..6 weekday, durationDays <= 7; Yearly/Monthly/OneOff: startMonth 1..12, startDay 1..31 with calendar sanity — Feb < 30, Apr/Jun/Sep/Nov < 31)\n");
|
||||
std::printf(" --catalog-pluck <wXXX-file> <id> [--json]\n");
|
||||
std::printf(" Extract one entry by id from any registered catalog format. Auto-detects magic, dispatches to the per-format --info-* handler internally, then prints just the matching entry. Primary-key field is auto-detected (first *Id field, or first numeric)\n");
|
||||
std::printf(" --catalog-find <directory> <id> [--magic <WXXX>] [--json]\n");
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ constexpr FormatRow kFormats[] = {
|
|||
{"WLMA", ".wlma", "loot", "GroupLoot CMSG_LOOT_METHOD policy", "Loot mode policy catalog (FFA / RR / Master / NBG / Personal)"},
|
||||
{"WSKP", ".wskp", "world", "LightParams.dbc + Light.dbc diurnal","Sky parameters catalog (per-zone keyframes)"},
|
||||
{"WCFG", ".wcfg", "server", "worldserver.conf flat-text config", "Server config catalog (polymorphic Float/Int/Bool/String values)"},
|
||||
{"WANV", ".wanv", "events", "GameEvent SQL + per-holiday script", "Anniversary & recurring event catalog (cron-like scheduling)"},
|
||||
|
||||
// Additional pipeline catalogs without the alternating
|
||||
// gen/info/validate CLI surface (loaded by the engine
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue