feat(pipeline): add WGTP (Wowee Game Tips) catalog

52nd open format — replaces GameTips.dbc plus loading-screen
tutorial hint tables. Defines the rotating tips shown during
world loads, the contextual tutorial hints that fire on first
gameplay events (first quest accept, first death, first
dungeon entry), and the persistent tooltip-help strings that
explain UI elements.

4 display kinds (LoadingScreen / Tutorial / TooltipHelp /
Hint), 7 audience-filter bits (Alliance / Horde / NewPlayer /
Hardcore / PvE / PvP / Roleplay) for pool selection, level
range gating (minLevel + maxLevel), displayWeight for
relative frequency within the pool, optional WPCN condition
cross-ref for further gating, and class-mask restriction
matching WCHC bit positions.

Cross-references with prior formats — conditionId points at
WPCN.conditionId for advanced gating; requiredClassMask uses
the same WCHC.classId bit layout as WGLY/WSET.

CLI: --gen-tips (3 generic loading-screen tips), --gen-tips-
new-player (5 onboarding Tutorial-kind tips for level 1-15,
weighted higher for new players), --gen-tips-advanced (4
endgame tips for level 70+ covering raid mechanics / arena /
daily professions / dungeon finder), --info-wgtp,
--validate-wgtp with --json variants. Validator catches
id/name/text required, kind 0..3, audienceFilter=0 (tip
never shown), invalid level range, displayWeight=0 (in pool
but never picked) warning, and brevity check (>280 chars)
on Tutorial / Hint kinds that need to fit on screen.

Format graph: 51 → 52 binary formats. CLI flag count: 770
→ 775.
This commit is contained in:
Kelsi 2026-05-09 20:00:56 -07:00
parent 5fe461cca8
commit 383d8d730a
10 changed files with 642 additions and 0 deletions

View file

@ -640,6 +640,7 @@ set(WOWEE_SOURCES
src/pipeline/wowee_trade_skills.cpp
src/pipeline/wowee_creature_equipment.cpp
src/pipeline/wowee_item_sets.cpp
src/pipeline/wowee_game_tips.cpp
src/pipeline/custom_zone_discovery.cpp
src/pipeline/dbc_layout.cpp
@ -1432,6 +1433,7 @@ add_executable(wowee_editor
tools/editor/cli_creature_equipment_catalog.cpp
tools/editor/cli_item_sets_catalog.cpp
tools/editor/cli_touch_tree.cpp
tools/editor/cli_game_tips_catalog.cpp
tools/editor/cli_quest_objective.cpp
tools/editor/cli_quest_reward.cpp
tools/editor/cli_clone.cpp
@ -1550,6 +1552,7 @@ add_executable(wowee_editor
src/pipeline/wowee_trade_skills.cpp
src/pipeline/wowee_creature_equipment.cpp
src/pipeline/wowee_item_sets.cpp
src/pipeline/wowee_game_tips.cpp
src/pipeline/custom_zone_discovery.cpp
src/pipeline/terrain_mesh.cpp

View file

@ -0,0 +1,114 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace pipeline {
// Wowee Open Game Tips catalog (.wgtp) — novel replacement
// for Blizzard's GameTips.dbc plus loading-screen tutorial
// hint tables. Defines the rotating tips shown during world
// loads, the contextual tutorial hints that fire on first
// gameplay events (first quest accept, first death, first
// dungeon entry), and the persistent tooltip-help strings
// that explain UI elements.
//
// Each tip has filter criteria — audience bitmask (faction /
// new-player / hardcore), level range, optional class mask,
// optional WPCN condition cross-ref — that the runtime uses
// to pick the right pool of tips for the current player.
// displayWeight controls relative frequency within the pool.
//
// Cross-references with previously-added formats:
// WGTP.entry.conditionId → WPCN.entry.conditionId
// (further gate beyond audience)
// WGTP.entry.requiredClassMask bit positions match
// WCHC.class.classId
//
// Binary layout (little-endian):
// magic[4] = "WGTP"
// version (uint32) = current 1
// nameLen + name (catalog label)
// entryCount (uint32)
// entries (each):
// tipId (uint32)
// nameLen + name
// textLen + text
// iconLen + iconPath
// displayKind (uint8) / pad[3]
// audienceFilter (uint32)
// minLevel (uint16) / maxLevel (uint16)
// displayWeight (uint16) / pad[2]
// conditionId (uint32)
// requiredClassMask (uint32)
struct WoweeGameTip {
enum DisplayKind : uint8_t {
LoadingScreen = 0, // long load-time scrolling tip
Tutorial = 1, // contextual modal on first event
TooltipHelp = 2, // persistent UI element help
Hint = 3, // brief on-screen flyout
};
// audienceFilter bits — combine with bitwise OR to broaden.
static constexpr uint32_t kAudienceAlliance = 1u << 0;
static constexpr uint32_t kAudienceHorde = 1u << 1;
static constexpr uint32_t kAudienceNewPlayer = 1u << 2;
static constexpr uint32_t kAudienceHardcore = 1u << 3;
static constexpr uint32_t kAudiencePvE = 1u << 4;
static constexpr uint32_t kAudiencePvP = 1u << 5;
static constexpr uint32_t kAudienceRoleplay = 1u << 6;
static constexpr uint32_t kAudienceAll = 0xFFFFFFFFu;
struct Entry {
uint32_t tipId = 0;
std::string name; // internal stable id ("FirstQuest")
std::string text; // the displayed text
std::string iconPath;
uint8_t displayKind = LoadingScreen;
uint32_t audienceFilter = kAudienceAll;
uint16_t minLevel = 1;
uint16_t maxLevel = 80;
uint16_t displayWeight = 1; // relative frequency
uint32_t conditionId = 0; // WPCN cross-ref (0 = none)
uint32_t requiredClassMask = 0; // 0 = any class
};
std::string name;
std::vector<Entry> entries;
bool isValid() const { return !entries.empty(); }
const Entry* findById(uint32_t tipId) const;
static const char* displayKindName(uint8_t k);
};
class WoweeGameTipLoader {
public:
static bool save(const WoweeGameTip& cat,
const std::string& basePath);
static WoweeGameTip load(const std::string& basePath);
static bool exists(const std::string& basePath);
// Preset emitters used by --gen-tips* variants.
//
// makeStarter — 3 generic loading-screen tips
// (combat hint / movement hint /
// quest hint) — kAudienceAll, no
// condition gate.
// makeNewPlayer — 5 onboarding tips for level 1-15
// players (kAudienceNewPlayer bit
// set), Tutorial display kind.
// makeAdvanced — 4 tips for max-level players
// (raid mechanics / PvP mechanics /
// profession dailies / dungeon-finder
// etiquette) gated by minLevel 70+.
static WoweeGameTip makeStarter(const std::string& catalogName);
static WoweeGameTip makeNewPlayer(const std::string& catalogName);
static WoweeGameTip makeAdvanced(const std::string& catalogName);
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,253 @@
#include "pipeline/wowee_game_tips.hpp"
#include <cstdio>
#include <cstring>
#include <fstream>
namespace wowee {
namespace pipeline {
namespace {
constexpr char kMagic[4] = {'W', 'G', 'T', 'P'};
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) != ".wgtp") {
base += ".wgtp";
}
return base;
}
} // namespace
const WoweeGameTip::Entry*
WoweeGameTip::findById(uint32_t tipId) const {
for (const auto& e : entries) if (e.tipId == tipId) return &e;
return nullptr;
}
const char* WoweeGameTip::displayKindName(uint8_t k) {
switch (k) {
case LoadingScreen: return "loading-screen";
case Tutorial: return "tutorial";
case TooltipHelp: return "tooltip-help";
case Hint: return "hint";
default: return "unknown";
}
}
bool WoweeGameTipLoader::save(const WoweeGameTip& 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.tipId);
writeStr(os, e.name);
writeStr(os, e.text);
writeStr(os, e.iconPath);
writePOD(os, e.displayKind);
uint8_t pad3[3] = {0, 0, 0};
os.write(reinterpret_cast<const char*>(pad3), 3);
writePOD(os, e.audienceFilter);
writePOD(os, e.minLevel);
writePOD(os, e.maxLevel);
writePOD(os, e.displayWeight);
uint8_t pad2[2] = {0, 0};
os.write(reinterpret_cast<const char*>(pad2), 2);
writePOD(os, e.conditionId);
writePOD(os, e.requiredClassMask);
}
return os.good();
}
WoweeGameTip WoweeGameTipLoader::load(const std::string& basePath) {
WoweeGameTip 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.tipId)) {
out.entries.clear(); return out;
}
if (!readStr(is, e.name) || !readStr(is, e.text) ||
!readStr(is, e.iconPath)) {
out.entries.clear(); return out;
}
if (!readPOD(is, e.displayKind)) {
out.entries.clear(); return out;
}
uint8_t pad3[3];
is.read(reinterpret_cast<char*>(pad3), 3);
if (is.gcount() != 3) { out.entries.clear(); return out; }
if (!readPOD(is, e.audienceFilter) ||
!readPOD(is, e.minLevel) ||
!readPOD(is, e.maxLevel) ||
!readPOD(is, e.displayWeight)) {
out.entries.clear(); return out;
}
uint8_t pad2[2];
is.read(reinterpret_cast<char*>(pad2), 2);
if (is.gcount() != 2) { out.entries.clear(); return out; }
if (!readPOD(is, e.conditionId) ||
!readPOD(is, e.requiredClassMask)) {
out.entries.clear(); return out;
}
}
return out;
}
bool WoweeGameTipLoader::exists(const std::string& basePath) {
std::ifstream is(normalizePath(basePath), std::ios::binary);
return is.good();
}
WoweeGameTip WoweeGameTipLoader::makeStarter(
const std::string& catalogName) {
WoweeGameTip c;
c.name = catalogName;
auto add = [&](uint32_t id, const char* name, const char* text,
uint16_t weight) {
WoweeGameTip::Entry e;
e.tipId = id; e.name = name; e.text = text;
e.iconPath = "Interface/TipOfTheDay/icon_generic.blp";
e.displayKind = WoweeGameTip::LoadingScreen;
e.displayWeight = weight;
c.entries.push_back(e);
};
add(1, "CombatHint",
"Press <Tab> to cycle through nearby enemies. "
"Right-click to attack.", 1);
add(2, "MovementHint",
"Hold both mouse buttons to move forward without "
"pressing W. Hold right-click to steer with the mouse.", 1);
add(3, "QuestHint",
"Yellow exclamation marks (!) above NPCs mean a "
"quest is available. Yellow question marks (?) mean "
"a quest is ready to turn in.", 2);
return c;
}
WoweeGameTip WoweeGameTipLoader::makeNewPlayer(
const std::string& catalogName) {
WoweeGameTip c;
c.name = catalogName;
auto add = [&](uint32_t id, const char* name, const char* text,
uint16_t maxLevel) {
WoweeGameTip::Entry e;
e.tipId = id; e.name = name; e.text = text;
e.iconPath = "Interface/TipOfTheDay/icon_newplayer.blp";
e.displayKind = WoweeGameTip::Tutorial;
e.audienceFilter = WoweeGameTip::kAudienceNewPlayer |
WoweeGameTip::kAudienceAlliance |
WoweeGameTip::kAudienceHorde;
e.minLevel = 1;
e.maxLevel = maxLevel;
e.displayWeight = 5; // weighted higher for new players
c.entries.push_back(e);
};
add(100, "BindHearthstone",
"Visit an innkeeper to bind your Hearthstone — it's "
"the easiest way to return home.", 10);
add(101, "TalentSpec",
"At level 10 you can spend talent points. Visit your "
"class trainer to learn how.", 15);
add(102, "FirstMount",
"At level 20 you can ride a mount! Save 1 gold and "
"visit a mount vendor in your faction's capital.", 25);
add(103, "QuestLog",
"Press 'L' to open your quest log. You can track up "
"to 25 active quests at once.", 15);
add(104, "ProfessionPick",
"Visit a profession trainer to learn a primary trade. "
"You can have two primary professions.", 15);
return c;
}
WoweeGameTip WoweeGameTipLoader::makeAdvanced(
const std::string& catalogName) {
WoweeGameTip c;
c.name = catalogName;
auto add = [&](uint32_t id, const char* name, const char* text,
uint8_t kind, uint32_t audience, uint32_t cond,
uint16_t weight) {
WoweeGameTip::Entry e;
e.tipId = id; e.name = name; e.text = text;
e.iconPath = "Interface/TipOfTheDay/icon_advanced.blp";
e.displayKind = kind;
e.audienceFilter = audience;
e.minLevel = 70;
e.maxLevel = 80;
e.conditionId = cond;
e.displayWeight = weight;
c.entries.push_back(e);
};
add(200, "RaidMechanic",
"Raid bosses telegraph their abilities — watch for "
"ground markers and mechanic announcements.",
WoweeGameTip::Hint, WoweeGameTip::kAudiencePvE, 0, 3);
add(201, "PvPArena",
"Arena teams require a charter signed by 4 players. "
"Visit an Arena Battlemaster to start one.",
WoweeGameTip::TooltipHelp, WoweeGameTip::kAudiencePvP, 0, 2);
add(202, "DailyProfession",
"Some professions have daily quests at exalted with "
"your faction. Check Shattrath and Dalaran daily.",
WoweeGameTip::LoadingScreen,
WoweeGameTip::kAudienceAll, 0, 2);
add(203, "DungeonFinder",
"Press 'I' to open the Dungeon Finder. It will form a "
"group across servers and teleport you to the dungeon.",
WoweeGameTip::Tutorial,
WoweeGameTip::kAudienceAll, 0, 4);
return c;
}
} // namespace pipeline
} // namespace wowee

View file

@ -157,6 +157,8 @@ const char* const kArgRequired[] = {
"--gen-itset", "--gen-itset-tier", "--gen-itset-pvp",
"--info-wset", "--validate-wset",
"--export-wset-json", "--import-wset-json",
"--gen-tips", "--gen-tips-new-player", "--gen-tips-advanced",
"--info-wgtp", "--validate-wgtp",
"--gen-weather-temperate", "--gen-weather-arctic",
"--gen-weather-desert", "--gen-weather-stormy",
"--gen-zone-atmosphere",

View file

@ -85,6 +85,7 @@
#include "cli_creature_equipment_catalog.hpp"
#include "cli_item_sets_catalog.hpp"
#include "cli_touch_tree.hpp"
#include "cli_game_tips_catalog.hpp"
#include "cli_quest_objective.hpp"
#include "cli_quest_reward.hpp"
#include "cli_clone.hpp"
@ -211,6 +212,7 @@ constexpr DispatchFn kDispatchTable[] = {
handleCreatureEquipmentCatalog,
handleItemSetsCatalog,
handleTouchTree,
handleGameTipsCatalog,
handleQuestObjective,
handleQuestReward,
handleClone,

View file

@ -54,6 +54,7 @@ constexpr FormatMagicEntry kFormats[] = {
{{'W','T','S','K'}, ".wtsk", "crafting", "--info-wtsk", "Trade skill recipe catalog"},
{{'W','C','E','Q'}, ".wceq", "creatures", "--info-wceq", "Creature equipment loadout catalog"},
{{'W','S','E','T'}, ".wset", "items", "--info-wset", "Item set / tier-bonus catalog"},
{{'W','G','T','P'}, ".wgtp", "ui", "--info-wgtp", "Game tips / tutorial 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"},

View file

@ -0,0 +1,245 @@
#include "cli_game_tips_catalog.hpp"
#include "cli_arg_parse.hpp"
#include "cli_box_emitter.hpp"
#include "pipeline/wowee_game_tips.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 stripWgtpExt(std::string base) {
stripExt(base, ".wgtp");
return base;
}
bool saveOrError(const wowee::pipeline::WoweeGameTip& c,
const std::string& base, const char* cmd) {
if (!wowee::pipeline::WoweeGameTipLoader::save(c, base)) {
std::fprintf(stderr, "%s: failed to save %s.wgtp\n",
cmd, base.c_str());
return false;
}
return true;
}
void printGenSummary(const wowee::pipeline::WoweeGameTip& c,
const std::string& base) {
std::printf("Wrote %s.wgtp\n", base.c_str());
std::printf(" catalog : %s\n", c.name.c_str());
std::printf(" tips : %zu\n", c.entries.size());
}
int handleGenStarter(int& i, int argc, char** argv) {
std::string base = argv[++i];
std::string name = "StarterTips";
if (parseOptArg(i, argc, argv)) name = argv[++i];
base = stripWgtpExt(base);
auto c = wowee::pipeline::WoweeGameTipLoader::makeStarter(name);
if (!saveOrError(c, base, "gen-tips")) return 1;
printGenSummary(c, base);
return 0;
}
int handleGenNewPlayer(int& i, int argc, char** argv) {
std::string base = argv[++i];
std::string name = "NewPlayerTips";
if (parseOptArg(i, argc, argv)) name = argv[++i];
base = stripWgtpExt(base);
auto c = wowee::pipeline::WoweeGameTipLoader::makeNewPlayer(name);
if (!saveOrError(c, base, "gen-tips-new-player")) return 1;
printGenSummary(c, base);
return 0;
}
int handleGenAdvanced(int& i, int argc, char** argv) {
std::string base = argv[++i];
std::string name = "AdvancedTips";
if (parseOptArg(i, argc, argv)) name = argv[++i];
base = stripWgtpExt(base);
auto c = wowee::pipeline::WoweeGameTipLoader::makeAdvanced(name);
if (!saveOrError(c, base, "gen-tips-advanced")) 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 = stripWgtpExt(base);
if (!wowee::pipeline::WoweeGameTipLoader::exists(base)) {
std::fprintf(stderr, "WGTP not found: %s.wgtp\n", base.c_str());
return 1;
}
auto c = wowee::pipeline::WoweeGameTipLoader::load(base);
if (jsonOut) {
nlohmann::json j;
j["wgtp"] = base + ".wgtp";
j["name"] = c.name;
j["count"] = c.entries.size();
nlohmann::json arr = nlohmann::json::array();
for (const auto& e : c.entries) {
arr.push_back({
{"tipId", e.tipId},
{"name", e.name},
{"text", e.text},
{"iconPath", e.iconPath},
{"displayKind", e.displayKind},
{"displayKindName", wowee::pipeline::WoweeGameTip::displayKindName(e.displayKind)},
{"audienceFilter", e.audienceFilter},
{"minLevel", e.minLevel},
{"maxLevel", e.maxLevel},
{"displayWeight", e.displayWeight},
{"conditionId", e.conditionId},
{"requiredClassMask", e.requiredClassMask},
});
}
j["entries"] = arr;
std::printf("%s\n", j.dump(2).c_str());
return 0;
}
std::printf("WGTP: %s.wgtp\n", base.c_str());
std::printf(" catalog : %s\n", c.name.c_str());
std::printf(" tips : %zu\n", c.entries.size());
if (c.entries.empty()) return 0;
std::printf(" id kind audience levels wt cond classMask name\n");
for (const auto& e : c.entries) {
std::printf(" %4u %-13s 0x%08x %3u-%3u %3u %5u 0x%08x %s\n",
e.tipId,
wowee::pipeline::WoweeGameTip::displayKindName(e.displayKind),
e.audienceFilter,
e.minLevel, e.maxLevel,
e.displayWeight, e.conditionId,
e.requiredClassMask,
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 = stripWgtpExt(base);
if (!wowee::pipeline::WoweeGameTipLoader::exists(base)) {
std::fprintf(stderr,
"validate-wgtp: WGTP not found: %s.wgtp\n", base.c_str());
return 1;
}
auto c = wowee::pipeline::WoweeGameTipLoader::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.tipId);
if (!e.name.empty()) ctx += " " + e.name;
ctx += ")";
if (e.tipId == 0)
errors.push_back(ctx + ": tipId is 0");
if (e.name.empty())
errors.push_back(ctx + ": name is empty");
if (e.text.empty())
errors.push_back(ctx + ": text is empty");
if (e.displayKind > wowee::pipeline::WoweeGameTip::Hint) {
errors.push_back(ctx + ": displayKind " +
std::to_string(e.displayKind) + " not in 0..3");
}
if (e.audienceFilter == 0) {
errors.push_back(ctx +
": audienceFilter=0 (tip would never be shown)");
}
if (e.minLevel > e.maxLevel) {
errors.push_back(ctx + ": minLevel " +
std::to_string(e.minLevel) + " > maxLevel " +
std::to_string(e.maxLevel));
}
if (e.displayWeight == 0) {
warnings.push_back(ctx +
": displayWeight=0 (tip is in pool but never picked)");
}
// Tutorial / Hint kinds typically need to be brief —
// > 280 characters won't fit cleanly on screen.
bool brief = e.displayKind == wowee::pipeline::WoweeGameTip::Tutorial ||
e.displayKind == wowee::pipeline::WoweeGameTip::Hint;
if (brief && e.text.size() > 280) {
warnings.push_back(ctx +
": text length " + std::to_string(e.text.size()) +
" exceeds 280 chars (tutorial/hint should be brief)");
}
for (uint32_t prev : idsSeen) {
if (prev == e.tipId) {
errors.push_back(ctx + ": duplicate tipId");
break;
}
}
idsSeen.push_back(e.tipId);
}
bool ok = errors.empty();
if (jsonOut) {
nlohmann::json j;
j["wgtp"] = base + ".wgtp";
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-wgtp: %s.wgtp\n", base.c_str());
if (ok && warnings.empty()) {
std::printf(" OK — %zu tips, all tipIds unique, all level ranges 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 handleGameTipsCatalog(int& i, int argc, char** argv, int& outRc) {
if (std::strcmp(argv[i], "--gen-tips") == 0 && i + 1 < argc) {
outRc = handleGenStarter(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--gen-tips-new-player") == 0 &&
i + 1 < argc) {
outRc = handleGenNewPlayer(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--gen-tips-advanced") == 0 &&
i + 1 < argc) {
outRc = handleGenAdvanced(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--info-wgtp") == 0 && i + 1 < argc) {
outRc = handleInfo(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--validate-wgtp") == 0 && i + 1 < argc) {
outRc = handleValidate(i, argc, argv); return true;
}
return false;
}
} // namespace cli
} // namespace editor
} // namespace wowee

View file

@ -0,0 +1,11 @@
#pragma once
namespace wowee {
namespace editor {
namespace cli {
bool handleGameTipsCatalog(int& i, int argc, char** argv, int& outRc);
} // namespace cli
} // namespace editor
} // namespace wowee

View file

@ -1457,6 +1457,16 @@ void printUsage(const char* argv0) {
std::printf(" Export binary .wset to a human-editable JSON sidecar with nested itemIds[] + bonuses[] arrays\n");
std::printf(" --import-wset-json <json-path> [out-base]\n");
std::printf(" Import a .wset.json sidecar back into binary .wset (pieceCount/bonusCount derived from array sizes; missing slots cleared)\n");
std::printf(" --gen-tips <wgtp-base> [name]\n");
std::printf(" Emit .wgtp starter: 3 generic loading-screen tips (combat / movement / quest hints) for kAudienceAll\n");
std::printf(" --gen-tips-new-player <wgtp-base> [name]\n");
std::printf(" Emit .wgtp 5 onboarding tutorial tips (hearthstone / talents / mount / quest log / professions) for level 1-15\n");
std::printf(" --gen-tips-advanced <wgtp-base> [name]\n");
std::printf(" Emit .wgtp 4 endgame tips (raid mechanics / arena / daily professions / dungeon finder) for level 70+\n");
std::printf(" --info-wgtp <wgtp-base> [--json]\n");
std::printf(" Print WGTP entries (id / kind / audience mask / level range / weight / condition / classMask / name)\n");
std::printf(" --validate-wgtp <wgtp-base> [--json]\n");
std::printf(" Static checks: id+name+text required, kind 0..3, audienceFilter>0, valid level range, brevity check on tutorial/hint kinds\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");

View file

@ -76,6 +76,7 @@ constexpr FormatRow kFormats[] = {
{"WTSK", ".wtsk", "crafting", "SkillLineAbility.dbc + recipes", "Trade skill recipes (per-profession crafts)"},
{"WCEQ", ".wceq", "creatures", "creature_equip_template", "Creature equipment loadout (visible weapons)"},
{"WSET", ".wset", "items", "ItemSet.dbc + ItemSetSpell.dbc", "Item set + tier-bonus catalog"},
{"WGTP", ".wgtp", "ui", "GameTips.dbc + tutorial hints", "Game tips / tutorial / loading-screen catalog"},
// Additional pipeline catalogs without the alternating
// gen/info/validate CLI surface (loaded by the engine