#include "game/data/shipsection.h" #include #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; } int hull_size(SectionClass c) { switch (c) { case SectionClass::Cruiser: return 1; case SectionClass::Dreadnought: return 2; case SectionClass::Destroyer: case SectionClass::None: case SectionClass::Other: return 0; } return 0; } 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 read_options(const Block& section) { struct Item { int ord; OptionGroup group; }; std::vector 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 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 load_shipsection(const mars::parse::Document& doc, std::string file) { Loaded 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"); s.defence_platform = f.opt_bool("defence_platform"); out.value = std::move(s); return out; } Loaded parse_shipsection(std::string_view text, std::string file) { auto parsed = mars::parse::parse_blocks(text); if (!parsed.ok()) { Loaded out; out.problems.push_back({Problem::Kind::Syntax, file, parsed.error().line, "", parsed.error().message}); return out; } Loaded 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