implement ship modules

This commit is contained in:
2026-05-18 08:49:51 +02:00
parent b59e392461
commit d08bf5d37b
33 changed files with 1911 additions and 56 deletions

View File

@@ -7,6 +7,7 @@ SET(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/ShipsConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/StationsConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/GameConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/ModulesConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/ConfigLoader.h
${CMAKE_CURRENT_SOURCE_DIR}/SurfaceMask.h
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintSerializer.h

View File

@@ -358,6 +358,7 @@ ShipsConfig ConfigLoader::loadShips(const std::string& path)
ShipDef def;
def.id = requireString(mt["id"], file, elemPath + ".id");
def.availableFromStart = requireBool(mt["available_from_start"], file, elemPath + ".available_from_start");
def.layout = requireStringArray(mt["layout"], file, elemPath + ".layout");
// Schematic
{
@@ -498,6 +499,106 @@ StationsConfig ConfigLoader::loadStations(const std::string& path)
return cfg;
}
// Known category→stat mappings for module stat modifier discovery.
struct StatEntry
{
const char* category;
const char* stat;
};
static const StatEntry kKnownStats[] = {
{"health", "hp"},
{"movement", "speed"},
{"sensor", "sensor_range"},
{"combat", "damage"},
{"combat", "attack_range"},
{"combat", "attack_rate"},
{"repair", "repair_rate"},
{"repair", "repair_range"},
};
ModulesConfig ConfigLoader::loadModules(const std::string& path)
{
const std::string file = "modules.toml";
toml::table tbl = parseFile(path, file);
ModulesConfig cfg;
if (!tbl.contains("module"))
{
return cfg;
}
const toml::array& arr = requireArray(tbl["module"], file, "module");
for (std::size_t i = 0; i < arr.size(); ++i)
{
const std::string elemPath = "module[" + std::to_string(i) + "]";
const toml::table* st = arr[i].as_table();
if (st == nullptr)
{
throw makeError(file, elemPath, "not a table");
}
toml::table& mt = const_cast<toml::table&>(*st);
ModuleDef def;
def.id = requireString(mt["id"], file, elemPath + ".id");
def.surfaceMask = requireStringArray(mt["surface_mask"], file, elemPath + ".surface_mask");
def.playerProductionLevel = static_cast<int>(requireInt(
mt["player_production_level"], file, elemPath + ".player_production_level"));
def.productionTimeSeconds = requireDouble(
mt["production_time_seconds"], file, elemPath + ".production_time_seconds");
def.threatCost = requireDouble(mt["threat_cost"], file, elemPath + ".threat_cost");
def.fillColor = requireString(mt["fill_color"], file, elemPath + ".fill_color");
def.glyph = requireString(mt["glyph"], file, elemPath + ".glyph");
// Materials
{
const toml::array& materials = requireArray(mt["materials"], file, elemPath + ".materials");
def.materials = parseIngredients(materials, file, elemPath + ".materials");
}
// Stat modifiers from [module.<category>] sub-tables
for (const StatEntry& se : kKnownStats)
{
if (!mt.contains(se.category))
{
continue;
}
const toml::table& catTable = requireTable(mt[se.category], file,
elemPath + "." + se.category);
toml::table& catMt = const_cast<toml::table&>(catTable);
const std::string addedKey = std::string("added_") + se.stat + "_formula";
const std::string multipliedKey = std::string("multiplied_") + se.stat + "_formula";
if (catMt.contains(addedKey))
{
ModuleStatModifier mod;
mod.stat = se.stat;
mod.modifierType = "additive";
mod.formula = requireFormula(catMt[addedKey], file,
elemPath + "." + se.category + "." + addedKey);
def.statModifiers.push_back(std::move(mod));
}
if (catMt.contains(multipliedKey))
{
ModuleStatModifier mod;
mod.stat = se.stat;
mod.modifierType = "multiplicative";
mod.formula = requireFormula(catMt[multipliedKey], file,
elemPath + "." + se.category + "." + multipliedKey);
def.statModifiers.push_back(std::move(mod));
}
}
cfg.modules.push_back(std::move(def));
}
return cfg;
}
GameConfig ConfigLoader::loadFromDirectory(const std::string& configDir)
{
GameConfig cfg;
@@ -506,5 +607,6 @@ GameConfig ConfigLoader::loadFromDirectory(const std::string& configDir)
cfg.recipes = loadRecipes(configDir + "/recipes.toml");
cfg.ships = loadShips(configDir + "/ships.toml");
cfg.stations = loadStations(configDir + "/stations.toml");
cfg.modules = loadModules(configDir + "/modules.toml");
return cfg;
}

View File

@@ -4,7 +4,7 @@
#include "GameConfig.h"
// Parses the five simulation TOML files from a directory and returns a fully
// Parses all simulation TOML files from a directory and returns a fully
// populated, immutable GameConfig. Throws std::runtime_error on any parse or
// validation failure; the exception message identifies the offending file,
// field, or formula (see architecture.md "Config Loading").
@@ -21,4 +21,5 @@ public:
static RecipesConfig loadRecipes(const std::string& path);
static ShipsConfig loadShips(const std::string& path);
static StationsConfig loadStations(const std::string& path);
static ModulesConfig loadModules(const std::string& path);
};

View File

@@ -5,8 +5,9 @@
#include "RecipesConfig.h"
#include "ShipsConfig.h"
#include "StationsConfig.h"
#include "ModulesConfig.h"
// Aggregate of all five simulation config files. Loaded at startup and reloaded
// Aggregate of all simulation config files. Loaded at startup and reloaded
// from disk on each game restart (REQ-CFG-RELOAD). See architecture.md "Config Loading".
struct GameConfig
{
@@ -15,4 +16,5 @@ struct GameConfig
RecipesConfig recipes;
ShipsConfig ships;
StationsConfig stations;
ModulesConfig modules;
};

View File

@@ -0,0 +1,34 @@
#pragma once
#include <string>
#include <vector>
#include "Formula.h"
#include "RecipesConfig.h"
// A single stat modifier contributed by a module instance.
// REQ-MOD-STAT-CALC: final = base * (1 + sum(m_i - 1)) + sum(additives).
struct ModuleStatModifier
{
std::string stat; // e.g. "hp", "speed", "sensor_range"
std::string modifierType; // "additive" or "multiplicative"
Formula formula;
};
struct ModuleDef
{
std::string id;
std::vector<std::string> surfaceMask;
std::vector<RecipeIngredient> materials;
int playerProductionLevel;
double productionTimeSeconds;
double threatCost;
std::string fillColor;
std::string glyph;
std::vector<ModuleStatModifier> statModifiers;
};
struct ModulesConfig
{
std::vector<ModuleDef> modules;
};

View File

@@ -69,6 +69,7 @@ struct ShipDef
{
std::string id;
bool availableFromStart;
std::vector<std::string> layout;
ShipSchematic schematic;
ShipThreat threat;