210 lines
7.4 KiB
C++
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
|