sots-engine/src/game/data/shipsection.cpp

210 lines
7.4 KiB
C++

#include "game/data/shipsection.h"
#include <algorithm>
#include "game/data/fields.h"
namespace game::data {
using detail::Fields;
std::string_view section_type_name(SectionType t) {
switch (t) {
case SectionType::None: return "";
case SectionType::Command: return "command";
case SectionType::Mission: return "mission";
case SectionType::Engine: return "engine";
case SectionType::Other: return "other";
}
return "";
}
std::string_view section_class_name(SectionClass c) {
switch (c) {
case SectionClass::None: return "";
case SectionClass::Destroyer: return "destroyer";
case SectionClass::Cruiser: return "cruiser";
case SectionClass::Dreadnought: return "dreadnought";
case SectionClass::Other: return "other";
}
return "";
}
SectionType parse_section_type(std::string_view s) {
if (s.empty()) return SectionType::None;
if (iequals(s, "command")) return SectionType::Command;
if (iequals(s, "mission")) return SectionType::Mission;
if (iequals(s, "engine")) return SectionType::Engine;
return SectionType::Other;
}
SectionClass parse_section_class(std::string_view s) {
if (s.empty()) return SectionClass::None;
if (iequals(s, "destroyer")) return SectionClass::Destroyer;
if (iequals(s, "cruiser")) return SectionClass::Cruiser;
if (iequals(s, "dreadnought")) return SectionClass::Dreadnought;
return SectionClass::Other;
}
namespace {
// `option` entries: scalar pairs and blocks share the key; the normalised
// list keeps file order across both forms (ord = position in the block).
std::vector<OptionGroup> read_options(const Block& section) {
struct Item { int ord; OptionGroup group; };
std::vector<Item> items;
for (const Attr& a : section.attrs) {
if (!iequals(a.key, "option")) continue;
OptionGroup g;
g.members.push_back(a.value);
g.scalar = true;
g.line = a.line;
items.push_back({a.ord, std::move(g)});
}
for (const Block& b : section.blocks) {
if (!iequals(b.name, "option")) continue;
OptionGroup g;
g.members = b.strs("option");
g.scalar = false;
g.line = b.line;
items.push_back({b.ord, std::move(g)});
}
std::stable_sort(items.begin(), items.end(), [](const Item& x, const Item& y) { return x.ord < y.ord; });
std::vector<OptionGroup> out;
out.reserve(items.size());
for (auto& it : items) out.push_back(std::move(it.group));
return out;
}
MountDef read_mount(Fields& f) {
MountDef m;
m.node = f.required_str("node");
m.min_azimuth = f.opt_double("min_azimuth");
m.max_azimuth = f.opt_double("max_azimuth");
m.min_inclination = f.opt_double("min_inclination");
m.max_inclination = f.opt_double("max_inclination");
m.home_azimuth = f.opt_double("home_azimuth");
m.home_inclination = f.opt_double("home_inclination");
m.line = f.block().line;
return m;
}
BankDef read_bank(Fields& f) {
BankDef b;
b.turret_class = f.str("turretclass");
b.turret_size = f.str("turretsize");
b.weapon = f.str("weapon");
b.show_turrets = f.opt_bool("showturrets");
b.invincible = f.opt_bool("invincible");
b.repeated_turret_spec = f.block().all("turretsize").size() > 1 || f.block().all("turretclass").size() > 1;
b.line = f.block().line;
for (const Block* m : f.block().blocks_named("mount")) {
Fields mf = f.sub(*m);
b.mounts.push_back(read_mount(mf));
}
return b;
}
NetForceLimits read_netforce(Fields& f) {
NetForceLimits n;
n.force_forward = f.opt_double("force_forward");
n.force_right = f.opt_double("force_right");
n.force_up = f.opt_double("force_up");
n.torque_yaw = f.opt_double("torque_yaw");
n.torque_pitch = f.opt_double("torque_pitch");
n.torque_roll = f.opt_double("torque_roll");
n.speed = f.opt_double("speed");
n.rotspeed = f.opt_double("rotspeed");
return n;
}
} // namespace
Loaded<ShipSectionDef> load_shipsection(const mars::parse::Document& doc, std::string file) {
Loaded<ShipSectionDef> out;
const mars::parse::Node* node = doc.root.first_block("shipsection");
if (!node) {
out.problems.push_back({Problem::Kind::MissingBlock, file, 0, "shipsection", "no top-level shipsection{} block"});
return out;
}
ShipSectionDef s;
s.file = file;
s.raw = snapshot(*node);
Fields f(s.raw, file, out.problems);
s.model = f.required_str("model");
s.dam_model = f.str("dam_model");
s.requires_tech = f.strs("requires");
s.section_type_text = f.str("section_type");
s.section_type = parse_section_type(s.section_type_text);
s.section_class_text = f.str("section_class");
s.section_class = parse_section_class(s.section_class_text);
s.design_class = f.str("design_class");
s.entity_class = f.str("entity_class");
s.health = f.opt_double("health");
if (!f.block().find("health")) f.missing("health");
s.mass = f.opt_double("mass");
if (!f.block().find("mass")) f.missing("mass");
s.cost = f.opt_int("cost");
s.cpoints = f.opt_int("cpoints");
s.crew = f.opt_int("crew");
s.command_cost = f.opt_int("command_cost");
s.maintenance_cost = f.opt_int("maintenance_cost");
s.command_quota = f.opt_int("command_quota");
s.socket_fore = f.str("socket_fore");
s.socket_aft = f.str("socket_aft");
s.dam_socket_fore = f.str("dam_socket_fore");
s.dam_socket_aft = f.str("dam_socket_aft");
s.options = read_options(s.raw);
if (const Block* od = s.raw.block("optiondef")) {
OptionGroup g;
g.members = od->strs("option");
g.line = od->line;
s.optiondef = std::move(g);
}
for (const Block* b : s.raw.blocks_named("bank")) {
Fields bf = f.sub(*b);
s.banks.push_back(read_bank(bf));
}
s.ftlspeed = f.opt_double("ftlspeed");
s.nodespeed = f.opt_double("nodespeed");
s.range = f.opt_double("range");
s.scanrange = f.opt_double("scanrange");
s.tactical_sensor_range = f.opt_double("tacticalsensorrange");
s.engine_techera = f.str("engine_techera");
if (const Block* n = s.raw.block("netforcelimits")) {
Fields nf = f.sub(*n);
s.netforcelimits = read_netforce(nf);
}
for (const Block* t : s.raw.blocks_named("thruster")) {
ThrusterDef th;
th.node = t->str("node");
th.effect = t->str("effect");
th.idle_effect = t->str("idle_effect");
s.thrusters.push_back(std::move(th));
}
s.exclude = f.strs("exclude"); // one `exclude "STEM"` line per forbidden partner
s.explicit_command_section = f.str("explicit_command_section");
s.explicit_engine_section = f.str("explicit_engine_section");
s.explicit_section = f.opt_bool("explicit_section");
s.autonomous = f.opt_bool("autonomous");
s.nodesign = f.opt_bool("nodesign");
out.value = std::move(s);
return out;
}
Loaded<ShipSectionDef> parse_shipsection(std::string_view text, std::string file) {
auto parsed = mars::parse::parse_blocks(text);
if (!parsed.ok()) {
Loaded<ShipSectionDef> out;
out.problems.push_back({Problem::Kind::Syntax, file, parsed.error().line, "", parsed.error().message});
return out;
}
Loaded<ShipSectionDef> out = load_shipsection(parsed.value(), file);
for (const auto& d : parsed.value().warnings)
out.problems.push_back({Problem::Kind::Syntax, file, d.line, "", "recovered: " + d.message});
return out;
}
} // namespace game::data