108 lines
5.5 KiB
C++
108 lines
5.5 KiB
C++
// game::config -- the flat "KEY value" constant tables (Data/globals.txt,
|
|
// Data/Strategy/StrategyVars.txt, Data/Species.txt, Data/encounters.txt, Data/Combat/*.txt).
|
|
//
|
|
// Model of the original loader (findings: loader-prototypes.md section M1):
|
|
//
|
|
// * Every constant is a static "slot" registered at image start-up with a key, a storage
|
|
// word and one typed parser. The *slot* decides the type; the file never does.
|
|
// * The file is read with mars::text::parse_flat_kv, which follows the engine's own loop:
|
|
// the Mars::Script pull tokenizer, KEY value pairs, `NAME {` blocks skipped whole, a
|
|
// lone `}` ignored, and a final pair whose value touches end-of-file dropped.
|
|
// * Keys match case-insensitively (ASCII). A slot is consumed on first sight, so the
|
|
// first occurrence wins; a repeat -- or an unknown key -- is logged and ignored. Slots
|
|
// the file never names keep whatever the image default was.
|
|
//
|
|
// Slot kinds and their storage (widths are what the hook declares as side-effect regions):
|
|
// Int int32 "%d"
|
|
// Float float "%f"
|
|
// FloatScaled float "%f", then multiplied by a process constant (pi/180 as a double)
|
|
// Colour float[4] "%d %d %d %d", each defaulted to 255, /255, clamped to 0..1
|
|
// Vec3 float[3] "%f %f %f" (components not matched stay unchanged)
|
|
// Rect int32[4] "%d %d %d %d" (idem)
|
|
// String an engine std::string (24 bytes): the text itself. This module cannot build
|
|
// the engine's string object, so String slots are written through the
|
|
// caller's ExternWriter (the shim hands them to the game's own parser).
|
|
// Unknown a parser this code does not model; never written, declared 4 bytes wide.
|
|
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
namespace game::config {
|
|
|
|
enum class Kind : std::uint8_t { Int, Float, FloatScaled, Colour, Vec3, Rect, String, Unknown };
|
|
|
|
constexpr std::size_t width(Kind k) {
|
|
switch (k) {
|
|
case Kind::Colour: return 16;
|
|
case Kind::Vec3: return 12;
|
|
case Kind::Rect: return 16;
|
|
case Kind::String: return 24;
|
|
default: return 4;
|
|
}
|
|
}
|
|
const char* kind_name(Kind k); // "int" | "float" | "fscaled" | "colour" | "vec3" | "rect" | "string" | "unknown"
|
|
|
|
// One registered constant: where the value lives and how its text is typed.
|
|
struct Slot {
|
|
std::string key; // as registered (matched case-insensitively)
|
|
Kind kind = Kind::Int;
|
|
void* storage = nullptr; // width(kind) bytes
|
|
void* native = nullptr; // opaque handle for the ExternWriter (the shim keeps the game parser here)
|
|
bool consumed = false; // set by apply(): first occurrence wins
|
|
};
|
|
|
|
struct Stats {
|
|
unsigned applied = 0; // pairs written to a slot
|
|
unsigned duplicate = 0; // repeats of an already consumed key (ignored)
|
|
unsigned unknown = 0; // keys with no slot (ignored)
|
|
unsigned missing = 0; // slots the file never named
|
|
};
|
|
|
|
using LogFn = std::function<void(const std::string& line)>;
|
|
using ExternWriter = std::function<void(Slot& slot, std::string_view value)>;
|
|
|
|
// ---- typed value parsers -------------------------------------------------------------------
|
|
//
|
|
// scan_int / scan_float follow C `sscanf` "%d" / "%f": leading whitespace skipped, an optional
|
|
// sign, then the longest valid prefix; they return false and leave `out` alone when no digit
|
|
// is found. Integers wrap modulo 2^32 like the CRT's accumulator; floats are converted with
|
|
// one rounding from the decimal text. The *_at forms continue from `pos` (for "%d %d ..").
|
|
bool scan_int(std::string_view text, std::int32_t& out);
|
|
bool scan_float(std::string_view text, float& out);
|
|
bool scan_int_at(std::string_view text, std::size_t& pos, std::int32_t& out);
|
|
bool scan_float_at(std::string_view text, std::size_t& pos, float& out);
|
|
// "%d %d %d %d" with every component defaulted to 255; each is divided by 255 (double
|
|
// arithmetic, then stored as float) and clamped to [0, 1]. Always writes all four.
|
|
void parse_colour(std::string_view text, float rgba[4]);
|
|
// "%f %f %f" / "%d %d %d %d": components are written left to right until one fails to scan;
|
|
// returns how many were written.
|
|
int parse_vec3(std::string_view text, float xyz[3]);
|
|
int parse_rect(std::string_view text, std::int32_t rect[4]);
|
|
// The scaled-float product as the original computes it (double product, stored as float).
|
|
float scale_float(float v, double scale);
|
|
|
|
// ---- the pairs ------------------------------------------------------------------------------------
|
|
|
|
struct Pair {
|
|
std::string key, value;
|
|
};
|
|
// Every top-level KEY value pair in file order (duplicates included), per the engine rules.
|
|
std::vector<Pair> pairs(std::string_view text);
|
|
|
|
// Writes the value text into the slot according to its kind. `scale` feeds FloatScaled;
|
|
// String slots go through `ext` (nothing happens without one).
|
|
void write_slot(Slot& slot, std::string_view value, double scale, const ExternWriter& ext);
|
|
|
|
// The whole loader for one file: `text` is the file's bytes, `slots` the constants registered
|
|
// for that file. Consumed slots are flagged; diagnostics mirror the original's wording:
|
|
// "[file] KEY not recognized or is multiply defined." "[file] KEY expected but not found."
|
|
// `file` is only used in the log lines.
|
|
Stats apply(std::string_view file, std::string_view text, std::vector<Slot>& slots, double scale,
|
|
const LogFn& log, const ExternWriter& ext = nullptr);
|
|
|
|
} // namespace game::config
|