feat(editor): add WMAR (Raid Marker Set) — 117th open format

Novel replacement for the hardcoded 8-marker raid set
vanilla WoW shipped (Star/Circle/Diamond/Triangle/Moon/
Square/Cross/Skull) plus world-map pin markers and
5-man party role markers. Each entry binds one marker
slot to its icon resource, single-character chat-overlay
glyph (for "{star}" chat-style links), and priority for
sort order in the marker-picker UI.

Four markerKind values (RaidTarget / WorldMap / Party /
Custom) cover the full marker-system surface. The chat-
overlay displayChar field enables the WoW-canonical
chat shortcuts: "{star}" gets rendered as a star icon
inline in chat, with "*" as the fallback glyph for
non-rich-text contexts (clipboard, log files, mod-
script handlers).

Three preset emitters: makeRaidTargets (8 canonical
raid markers in /raidicon priority order 0..7 with
their iconic colors and glyph mnemonics), makeWorldMap-
Pins (5 world-map pin markers — Pin/Flag/Crosshair/
Question/Compass), makeParty (4 role markers for group-
finder filtering — Tank/Healer/DPS/Caster).

Validator's most novel checks: per-(markerKind,
priority) tuple uniqueness — two markers at same kind+
priority would render in unstable picker UI order. Plus
RaidTarget priority > 7 warns (exceeds canonical 8-slot
/raidicon dispatch range; client keybind macros may not
reach the slot).

Format count 116 -> 117. CLI flag count 1241 -> 1246.
This commit is contained in:
Kelsi 2026-05-10 02:39:55 -07:00
parent 4be543a2ed
commit 42842958df
10 changed files with 714 additions and 0 deletions

View file

@ -0,0 +1,296 @@
#include "pipeline/wowee_raid_markers.hpp"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <fstream>
namespace wowee {
namespace pipeline {
namespace {
constexpr char kMagic[4] = {'W', 'M', 'A', 'R'};
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) != ".wmar") {
base += ".wmar";
}
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 WoweeRaidMarkers::Entry*
WoweeRaidMarkers::findById(uint32_t markerId) const {
for (const auto& e : entries)
if (e.markerId == markerId) return &e;
return nullptr;
}
std::vector<const WoweeRaidMarkers::Entry*>
WoweeRaidMarkers::findByKind(uint8_t markerKind) const {
std::vector<const Entry*> out;
for (const auto& e : entries)
if (e.markerKind == markerKind) out.push_back(&e);
std::sort(out.begin(), out.end(),
[](const Entry* a, const Entry* b) {
return a->priority < b->priority;
});
return out;
}
bool WoweeRaidMarkersLoader::save(const WoweeRaidMarkers& 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.markerId);
writeStr(os, e.name);
writeStr(os, e.description);
writePOD(os, e.markerKind);
writePOD(os, e.priority);
writePOD(os, e.pad0);
writePOD(os, e.pad1);
writeStr(os, e.iconPath);
writeStr(os, e.displayChar);
writePOD(os, e.iconColorRGBA);
}
return os.good();
}
WoweeRaidMarkers WoweeRaidMarkersLoader::load(
const std::string& basePath) {
WoweeRaidMarkers 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.markerId)) {
out.entries.clear(); return out;
}
if (!readStr(is, e.name) || !readStr(is, e.description)) {
out.entries.clear(); return out;
}
if (!readPOD(is, e.markerKind) ||
!readPOD(is, e.priority) ||
!readPOD(is, e.pad0) ||
!readPOD(is, e.pad1)) {
out.entries.clear(); return out;
}
if (!readStr(is, e.iconPath) ||
!readStr(is, e.displayChar)) {
out.entries.clear(); return out;
}
if (!readPOD(is, e.iconColorRGBA)) {
out.entries.clear(); return out;
}
}
return out;
}
bool WoweeRaidMarkersLoader::exists(const std::string& basePath) {
std::ifstream is(normalizePath(basePath), std::ios::binary);
return is.good();
}
WoweeRaidMarkers WoweeRaidMarkersLoader::makeRaidTargets(
const std::string& catalogName) {
using M = WoweeRaidMarkers;
WoweeRaidMarkers c;
c.name = catalogName;
auto add = [&](uint32_t id, const char* name,
uint8_t prio, const char* iconPath,
const char* glyph, uint32_t color,
const char* desc) {
M::Entry e;
e.markerId = id; e.name = name; e.description = desc;
e.markerKind = M::RaidTarget;
e.priority = prio;
e.iconPath = iconPath;
e.displayChar = glyph;
e.iconColorRGBA = color;
c.entries.push_back(e);
};
// Canonical 8-marker order from /raidicon command.
add(1, "Star", 0,
"Interface\\TargetingFrame\\UI-RaidTargetingIcon_1.blp",
"*", packRgba(255, 220, 80),
"Star (yellow). Mark slot 1. /raidicon star.");
add(2, "Circle", 1,
"Interface\\TargetingFrame\\UI-RaidTargetingIcon_2.blp",
"o", packRgba(255, 130, 60),
"Circle (orange). Mark slot 2.");
add(3, "Diamond", 2,
"Interface\\TargetingFrame\\UI-RaidTargetingIcon_3.blp",
"<>", packRgba(180, 100, 240),
"Diamond (purple). Mark slot 3 — common "
"polymorph CC-target marker.");
add(4, "Triangle", 3,
"Interface\\TargetingFrame\\UI-RaidTargetingIcon_4.blp",
"^", packRgba(80, 220, 80),
"Triangle (green). Mark slot 4.");
add(5, "Moon", 4,
"Interface\\TargetingFrame\\UI-RaidTargetingIcon_5.blp",
"(", packRgba(220, 220, 240),
"Moon (silver). Mark slot 5 — common Sap CC-"
"target marker.");
add(6, "Square", 5,
"Interface\\TargetingFrame\\UI-RaidTargetingIcon_6.blp",
"#", packRgba(80, 130, 240),
"Square (blue). Mark slot 6.");
add(7, "Cross", 6,
"Interface\\TargetingFrame\\UI-RaidTargetingIcon_7.blp",
"X", packRgba(220, 60, 60),
"Cross (red). Mark slot 7 — universal kill-this-"
"first marker.");
add(8, "Skull", 7,
"Interface\\TargetingFrame\\UI-RaidTargetingIcon_8.blp",
"$", packRgba(220, 220, 220),
"Skull (white). Mark slot 8 — universal HIGHEST-"
"priority kill-target marker.");
return c;
}
WoweeRaidMarkers WoweeRaidMarkersLoader::makeWorldMapPins(
const std::string& catalogName) {
using M = WoweeRaidMarkers;
WoweeRaidMarkers c;
c.name = catalogName;
auto add = [&](uint32_t id, const char* name,
uint8_t prio, const char* iconPath,
const char* glyph, uint32_t color,
const char* desc) {
M::Entry e;
e.markerId = id; e.name = name; e.description = desc;
e.markerKind = M::WorldMap;
e.priority = prio;
e.iconPath = iconPath;
e.displayChar = glyph;
e.iconColorRGBA = color;
c.entries.push_back(e);
};
add(100, "Pin", 0,
"Interface\\Minimap\\WorldMap\\Pin_Yellow.blp",
"P", packRgba(255, 220, 80),
"Generic yellow pin. Player-placed waypoint.");
add(101, "Flag", 1,
"Interface\\Minimap\\WorldMap\\Flag_Red.blp",
"F", packRgba(220, 60, 60),
"Red flag. Used by raid leaders for "
"rendezvous points.");
add(102, "Crosshair", 2,
"Interface\\Minimap\\WorldMap\\Crosshair.blp",
"+", packRgba(180, 180, 180),
"Crosshair. Targeting / sniping indicator on "
"PvP maps.");
add(103, "Question", 3,
"Interface\\Minimap\\WorldMap\\Question_Blue.blp",
"?", packRgba(140, 200, 255),
"Blue question mark. Quest-related point of "
"interest.");
add(104, "Compass", 4,
"Interface\\Minimap\\WorldMap\\Compass.blp",
"N", packRgba(220, 220, 240),
"Compass rose. North-indicator overlay (rare; "
"minimap usually fixes north at top).");
return c;
}
WoweeRaidMarkers WoweeRaidMarkersLoader::makeParty(
const std::string& catalogName) {
using M = WoweeRaidMarkers;
WoweeRaidMarkers c;
c.name = catalogName;
auto add = [&](uint32_t id, const char* name,
uint8_t prio, const char* iconPath,
const char* glyph, uint32_t color,
const char* desc) {
M::Entry e;
e.markerId = id; e.name = name; e.description = desc;
e.markerKind = M::Party;
e.priority = prio;
e.iconPath = iconPath;
e.displayChar = glyph;
e.iconColorRGBA = color;
c.entries.push_back(e);
};
add(200, "TankRole", 0,
"Interface\\PartyFrame\\Role_Tank.blp",
"T", packRgba(60, 100, 200),
"Tank role icon. Shown in groupfinder + party "
"frame for tank-specced players.");
add(201, "HealerRole", 1,
"Interface\\PartyFrame\\Role_Healer.blp",
"H", packRgba(80, 200, 80),
"Healer role icon. Shown for healer-specced "
"players.");
add(202, "DamageRole", 2,
"Interface\\PartyFrame\\Role_Damage.blp",
"D", packRgba(220, 80, 80),
"DPS role icon (melee + ranged grouped).");
add(203, "CasterRole", 3,
"Interface\\PartyFrame\\Role_Caster.blp",
"C", packRgba(180, 100, 240),
"Caster sub-role icon for groupfinder filtering "
"(distinct from melee DPS in some custom-server "
"configurations).");
return c;
}
} // namespace pipeline
} // namespace wowee