feat(pipeline): add Wowee Open Weather (.wow) zone schedule

8th open-format addition to the Wowee pipeline. Replaces
WoW's WeatherTypes.dbc / WeatherEffect logic with a single
binary file holding a list of weather states for one zone,
each tagged with intensity bounds, a probability weight,
and duration bounds. The renderer / runtime samples one
entry at a time using weighted-random selection, drives
it for a uniform-random duration in [min, max] sec, then
re-rolls.

  • Types: Clear / Rain / Snow / Storm / Sandstorm / Fog /
    Blizzard (extensible enum).

  • Binary format: magic "WOWA", version 1, name, N entries
    each storing (typeId, minIntensity, maxIntensity, weight,
    minDurationSec, maxDurationSec).

CLI:
  • --info-wow <wow-base> [--json] — inspect a WOW
  • --gen-weather-temperate — clear + rain + fog (forest)
  • --gen-weather-arctic    — snow + blizzard + fog (tundra)
  • --gen-weather-desert    — clear + sandstorm (dunes)
  • --gen-weather-stormy    — rain + storm + occasional clear

The 8th open format complementing the rest:
  M2 → WOM | WMO → WOB | WMO collision → WOC | ADT → WOT
  DBC → JsonDBC | BLP → PNG | Light.dbc → WOL | WeatherTypes.dbc → WOW

Smoke-tested all 4 presets + JSON output. Each preset reads
back identically with the expected entry count and weight
distribution.
This commit is contained in:
Kelsi 2026-05-09 14:10:13 -07:00
parent 1f86130888
commit d537d7163e
6 changed files with 378 additions and 0 deletions

View file

@ -4,6 +4,7 @@
#include "pipeline/wowee_building.hpp"
#include "pipeline/wowee_collision.hpp"
#include "pipeline/wowee_light.hpp"
#include "pipeline/wowee_weather.hpp"
#include "pipeline/wowee_terrain_loader.hpp"
#include "pipeline/adt_loader.hpp"
#include <glm/glm.hpp>
@ -581,6 +582,117 @@ int handleGenLightNight(int& i, int argc, char** argv) {
"moonlit directional + far fog");
}
int handleInfoWow(int& i, int argc, char** argv) {
// Inspect a Wowee Open Weather (.wow) file: zone name +
// per-entry weather type + intensity bounds + selection
// weight + duration bounds.
std::string base = argv[++i];
bool jsonOut = (i + 1 < argc &&
std::strcmp(argv[i + 1], "--json") == 0);
if (jsonOut) ++i;
if (base.size() >= 4 && base.substr(base.size() - 4) == ".wow")
base = base.substr(0, base.size() - 4);
if (!wowee::pipeline::WoweeWeatherLoader::exists(base)) {
std::fprintf(stderr, "WOW not found: %s.wow\n", base.c_str());
return 1;
}
auto wow = wowee::pipeline::WoweeWeatherLoader::load(base);
if (!wow.isValid()) {
std::fprintf(stderr, "WOW parse failed: %s.wow\n", base.c_str());
return 1;
}
if (jsonOut) {
nlohmann::json j;
j["wow"] = base + ".wow";
j["name"] = wow.name;
j["entryCount"] = wow.entries.size();
j["totalWeight"] = wow.totalWeight();
nlohmann::json es = nlohmann::json::array();
for (const auto& e : wow.entries) {
es.push_back({
{"type", wowee::pipeline::WoweeWeather::typeName(
e.weatherTypeId)},
{"typeId", e.weatherTypeId},
{"minIntensity", e.minIntensity},
{"maxIntensity", e.maxIntensity},
{"weight", e.weight},
{"minDurationSec", e.minDurationSec},
{"maxDurationSec", e.maxDurationSec},
});
}
j["entries"] = es;
std::printf("%s\n", j.dump(2).c_str());
return 0;
}
std::printf("WOW: %s.wow\n", base.c_str());
std::printf(" zone : %s\n", wow.name.c_str());
std::printf(" entries : %zu (totalWeight=%.2f)\n",
wow.entries.size(), wow.totalWeight());
for (std::size_t k = 0; k < wow.entries.size(); ++k) {
const auto& e = wow.entries[k];
std::printf(" [%zu] %-9s intensity %.2f..%.2f weight %.2f "
"duration %u..%u s\n",
k,
wowee::pipeline::WoweeWeather::typeName(e.weatherTypeId),
e.minIntensity, e.maxIntensity, e.weight,
e.minDurationSec, e.maxDurationSec);
}
return 0;
}
int emitWeatherPreset(const std::string& cmdName,
int& i, int argc, char** argv,
wowee::pipeline::WoweeWeather (*maker)(const std::string&),
const char* presetDescription) {
std::string base = argv[++i];
std::string zoneName = "Default";
if (i + 1 < argc && argv[i + 1][0] != '-') {
zoneName = argv[++i];
}
if (base.size() >= 4 && base.substr(base.size() - 4) == ".wow") {
base = base.substr(0, base.size() - 4);
}
auto wow = maker(zoneName);
if (!wowee::pipeline::WoweeWeatherLoader::save(wow, base)) {
std::fprintf(stderr, "%s: failed to save %s.wow\n",
cmdName.c_str(), base.c_str());
return 1;
}
std::printf("Wrote %s.wow\n", base.c_str());
std::printf(" zone : %s\n", zoneName.c_str());
std::printf(" preset : %s (%zu entries)\n",
presetDescription, wow.entries.size());
return 0;
}
int handleGenWeatherTemperate(int& i, int argc, char** argv) {
return emitWeatherPreset(
"gen-weather-temperate", i, argc, argv,
wowee::pipeline::WoweeWeatherLoader::makeTemperate,
"clear-dominant + occasional rain + fog");
}
int handleGenWeatherArctic(int& i, int argc, char** argv) {
return emitWeatherPreset(
"gen-weather-arctic", i, argc, argv,
wowee::pipeline::WoweeWeatherLoader::makeArctic,
"snow-dominant + blizzard + fog");
}
int handleGenWeatherDesert(int& i, int argc, char** argv) {
return emitWeatherPreset(
"gen-weather-desert", i, argc, argv,
wowee::pipeline::WoweeWeatherLoader::makeDesert,
"clear-dominant + sandstorm");
}
int handleGenWeatherStormy(int& i, int argc, char** argv) {
return emitWeatherPreset(
"gen-weather-stormy", i, argc, argv,
wowee::pipeline::WoweeWeatherLoader::makeStormy,
"heavy rain + storm + occasional clear");
}
} // namespace
bool handleWorldInfo(int& i, int argc, char** argv, int& outRc) {
@ -617,6 +729,21 @@ bool handleWorldInfo(int& i, int argc, char** argv, int& outRc) {
if (std::strcmp(argv[i], "--gen-light-night") == 0 && i + 1 < argc) {
outRc = handleGenLightNight(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--info-wow") == 0 && i + 1 < argc) {
outRc = handleInfoWow(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--gen-weather-temperate") == 0 && i + 1 < argc) {
outRc = handleGenWeatherTemperate(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--gen-weather-arctic") == 0 && i + 1 < argc) {
outRc = handleGenWeatherArctic(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--gen-weather-desert") == 0 && i + 1 < argc) {
outRc = handleGenWeatherDesert(i, argc, argv); return true;
}
if (std::strcmp(argv[i], "--gen-weather-stormy") == 0 && i + 1 < argc) {
outRc = handleGenWeatherStormy(i, argc, argv); return true;
}
return false;
}