187 lines
8.1 KiB
C++
187 lines
8.1 KiB
C++
// ShipSectionDef loader on hand-written samples; option normalisation.
|
|
#include "game/data/shipsection.h"
|
|
#include "test_main.h"
|
|
|
|
using namespace game::data;
|
|
|
|
namespace {
|
|
|
|
const char* kEngine =
|
|
"shipsection\n{\n"
|
|
"\tmodel Species/Test/art/sections/Engine.X\n"
|
|
"\tsocket_fore EngineNode\n"
|
|
"\tSection_Type Engine\n"
|
|
"\tsection_class Destroyer\n"
|
|
"\trequires DRV_Fissn\n"
|
|
"\trequires DRV_Node\n"
|
|
"\tengine_techera fission\n"
|
|
"\thealth 450\n\tmass 2500\n\tcost 5000\n\tcpoints 1380\n\tcrew 3\n"
|
|
"\tnodespeed 4\n\tftlspeed .2\n\trange 9\n"
|
|
"\toption IND_First\n" // scalar group before the blocks
|
|
"\toption { option IND_PlyAlloy option IND_MagLat option IND_QrkRes }\n"
|
|
"\toption { option IND_RefCoat option IND_ImpRfCt }\n"
|
|
"\toption DRV_RecFiss\n" // scalar group after them
|
|
"\toptiondef { option SLD_MkOne option SLD_MkTwo }\n"
|
|
"\texclude \"DEFission\"\n\texclude \"DEPulseFission\"\n"
|
|
"\tnetforcelimits { force_forward 23000 force_right 23000 force_up 23000\n"
|
|
"\t torque_yaw 0 torque_pitch 0 torque_roll 6.4e+7 speed 40 rotspeed -10 }\n"
|
|
"\tthruster { node EngineThruster01 effect effects/a.effect idle_effect effects/b.effect }\n"
|
|
"\tthruster { node EngineThruster02 effect effects/a.effect }\n"
|
|
"\tbank\n\t{\n"
|
|
"\t\tturretclass standard\n\t\tturretsize medium\n\t\tturretsize small\n" // repeated: last wins
|
|
"\t\tmount { node LightGunNode15 min_azimuth -140 max_azimuth 140 min_inclination -12 max_inclination 90 }\n"
|
|
"\t\tmount { node LightGunNode16 min_azimuth -140 max_azimuth 140 home_azimuth 0 }\n"
|
|
"\t}\n"
|
|
"\tbank { weapon \"Species/_NPC/weapons/fixed.weapon\" turretclass standard turretsize small showturrets false\n"
|
|
"\t mount { node N1 min_azimuth 0 max_azimuth 0 } }\n"
|
|
"\tautonomous 1\n"
|
|
"\tnodesign true\n"
|
|
"}\n";
|
|
|
|
} // namespace
|
|
|
|
TEST(section_typed_fields) {
|
|
auto r = parse_shipsection(kEngine, "Species/Test/sections/DEEngine.shipsection");
|
|
CHECK(r.ok());
|
|
CHECK(r.problems.empty());
|
|
const ShipSectionDef& s = *r.value;
|
|
CHECK_EQ(s.model, "Species/Test/art/sections/Engine.X");
|
|
CHECK_EQ(s.socket_fore, "EngineNode");
|
|
CHECK_EQ(s.socket_aft, "");
|
|
CHECK(s.has_sockets());
|
|
CHECK_EQ(s.section_type_text, "Engine");
|
|
CHECK(s.section_type == SectionType::Engine);
|
|
CHECK_EQ(s.section_class_text, "Destroyer");
|
|
CHECK(s.section_class == SectionClass::Destroyer);
|
|
CHECK_EQ(s.requires.size(), std::size_t{2});
|
|
CHECK_EQ(s.requires[1], "DRV_Node");
|
|
CHECK_EQ(s.engine_techera, "fission");
|
|
CHECK(s.health && *s.health == 450);
|
|
CHECK(s.mass && *s.mass == 2500);
|
|
CHECK(s.cost && *s.cost == 5000);
|
|
CHECK(s.cpoints && *s.cpoints == 1380);
|
|
CHECK(s.crew && *s.crew == 3);
|
|
CHECK(s.nodespeed && *s.nodespeed == 4);
|
|
CHECK(s.ftlspeed && *s.ftlspeed == 0.2);
|
|
CHECK(s.range && *s.range == 9);
|
|
CHECK(s.autonomous && *s.autonomous);
|
|
CHECK(s.nodesign && *s.nodesign);
|
|
CHECK(!s.explicit_section);
|
|
CHECK_EQ(s.exclude.size(), std::size_t{2});
|
|
CHECK_EQ(s.exclude[1], "DEPulseFission");
|
|
}
|
|
|
|
TEST(section_option_normalisation_keeps_file_order_across_forms) {
|
|
auto r = parse_shipsection(kEngine);
|
|
CHECK(r.ok());
|
|
const ShipSectionDef& s = *r.value;
|
|
CHECK_EQ(s.options.size(), std::size_t{4});
|
|
CHECK(s.options[0].scalar);
|
|
CHECK_EQ(s.options[0].members.size(), std::size_t{1});
|
|
CHECK_EQ(s.options[0].members[0], "IND_First");
|
|
CHECK(!s.options[1].scalar);
|
|
CHECK_EQ(s.options[1].members.size(), std::size_t{3});
|
|
CHECK_EQ(s.options[1].members[2], "IND_QrkRes");
|
|
CHECK(!s.options[2].scalar);
|
|
CHECK_EQ(s.options[2].members.size(), std::size_t{2});
|
|
CHECK(s.options[3].scalar);
|
|
CHECK_EQ(s.options[3].members[0], "DRV_RecFiss");
|
|
CHECK(s.options[0].line < s.options[1].line && s.options[2].line < s.options[3].line);
|
|
CHECK(s.optiondef.has_value());
|
|
CHECK_EQ(s.optiondef->members.size(), std::size_t{2});
|
|
CHECK_EQ(s.optiondef->members[1], "SLD_MkTwo");
|
|
}
|
|
|
|
TEST(section_banks_and_mounts) {
|
|
auto r = parse_shipsection(kEngine);
|
|
CHECK(r.ok());
|
|
const ShipSectionDef& s = *r.value;
|
|
CHECK_EQ(s.banks.size(), std::size_t{2});
|
|
const BankDef& b0 = s.banks[0];
|
|
CHECK_EQ(b0.turret_class, "standard");
|
|
CHECK_EQ(b0.turret_size, "small"); // last of the two turretsize lines
|
|
CHECK(b0.repeated_turret_spec);
|
|
CHECK_EQ(b0.weapon, "");
|
|
CHECK(!b0.show_turrets);
|
|
CHECK_EQ(b0.mounts.size(), std::size_t{2});
|
|
CHECK_EQ(b0.mounts[0].node, "LightGunNode15");
|
|
CHECK(b0.mounts[0].min_azimuth && *b0.mounts[0].min_azimuth == -140);
|
|
CHECK(b0.mounts[0].max_inclination && *b0.mounts[0].max_inclination == 90);
|
|
CHECK(!b0.mounts[1].min_inclination);
|
|
CHECK(b0.mounts[1].home_azimuth && *b0.mounts[1].home_azimuth == 0);
|
|
const BankDef& b1 = s.banks[1];
|
|
CHECK_EQ(b1.weapon, "Species/_NPC/weapons/fixed.weapon");
|
|
CHECK(!b1.repeated_turret_spec);
|
|
CHECK(b1.show_turrets && !*b1.show_turrets);
|
|
CHECK_EQ(b1.mounts.size(), std::size_t{1});
|
|
}
|
|
|
|
TEST(section_netforcelimits_and_thrusters) {
|
|
auto r = parse_shipsection(kEngine);
|
|
CHECK(r.ok());
|
|
const ShipSectionDef& s = *r.value;
|
|
CHECK(s.netforcelimits.has_value());
|
|
CHECK(s.netforcelimits->force_forward && *s.netforcelimits->force_forward == 23000);
|
|
CHECK(s.netforcelimits->torque_roll && *s.netforcelimits->torque_roll == 6.4e7);
|
|
CHECK(s.netforcelimits->rotspeed && *s.netforcelimits->rotspeed == -10);
|
|
CHECK_EQ(s.thrusters.size(), std::size_t{2});
|
|
CHECK_EQ(s.thrusters[0].idle_effect, "effects/b.effect");
|
|
CHECK_EQ(s.thrusters[1].idle_effect, "");
|
|
}
|
|
|
|
TEST(section_enum_parsing_is_case_insensitive) {
|
|
CHECK(parse_section_type("command") == SectionType::Command);
|
|
CHECK(parse_section_type("Command") == SectionType::Command);
|
|
CHECK(parse_section_type("MISSION") == SectionType::Mission);
|
|
CHECK(parse_section_type("") == SectionType::None);
|
|
CHECK(parse_section_type("rider") == SectionType::Other);
|
|
CHECK(parse_section_class("Dreadnought") == SectionClass::Dreadnought);
|
|
CHECK(parse_section_class("cruiser") == SectionClass::Cruiser);
|
|
CHECK(parse_section_class("station") == SectionClass::Other);
|
|
CHECK_EQ(std::string(section_type_name(SectionType::Engine)), "engine");
|
|
}
|
|
|
|
TEST(section_bad_values_are_problems_raw_keeps_text) {
|
|
auto r = parse_shipsection(
|
|
"shipsection { model m.X health 10 mass 20 crew false netforcelimits { force_right o speed 1 } }", "s.shipsection");
|
|
CHECK(r.ok());
|
|
int bad = 0;
|
|
for (const Problem& p : r.problems)
|
|
if (p.kind == Problem::Kind::BadValue) ++bad;
|
|
CHECK_EQ(bad, 2);
|
|
CHECK(!r.value->crew);
|
|
CHECK(r.value->netforcelimits && !r.value->netforcelimits->force_right);
|
|
CHECK(r.value->netforcelimits->speed && *r.value->netforcelimits->speed == 1);
|
|
CHECK_EQ(r.value->raw.str("crew"), "false");
|
|
}
|
|
|
|
TEST(section_missing_required_keys_reported) {
|
|
auto r = parse_shipsection("shipsection { section_type mission }");
|
|
CHECK(r.ok());
|
|
int missing = 0;
|
|
for (const Problem& p : r.problems)
|
|
if (p.kind == Problem::Kind::MissingKey) ++missing;
|
|
CHECK_EQ(missing, 3); // model, health, mass
|
|
CHECK(!r.value->has_sockets());
|
|
CHECK(r.value->options.empty());
|
|
CHECK(!r.value->optiondef);
|
|
CHECK(!r.value->netforcelimits);
|
|
}
|
|
|
|
TEST(section_unclosed_file_is_recovered) {
|
|
auto r = parse_shipsection("shipsection\n{\n model m.X\n health 1\n mass 2\n bank { turretclass standard turretsize small }\n");
|
|
CHECK(r.ok());
|
|
CHECK_EQ(r.problems.size(), std::size_t{1});
|
|
CHECK(r.problems[0].kind == Problem::Kind::Syntax);
|
|
CHECK_EQ(r.value->banks.size(), std::size_t{1});
|
|
}
|
|
|
|
TEST(section_long_tail_reachable_through_raw) {
|
|
auto r = parse_shipsection("shipsection { model m.X health 1 mass 2 refinery true mining_rate 5 death_effect a death_effect b }");
|
|
CHECK(r.ok());
|
|
const Block& raw = r.value->raw;
|
|
CHECK(raw.find("refinery") && raw.find("refinery")->as_bool().value_or(false));
|
|
CHECK(raw.find("MINING_RATE") && *raw.find("MINING_RATE")->as_int() == 5);
|
|
CHECK_EQ(raw.strs("death_effect").size(), std::size_t{2});
|
|
CHECK(!raw.has("police"));
|
|
}
|