// derive_stats and the default-weapon table on the mini catalog. #include "game/design/defaults.h" #include "game/design/stats.h" #include "mini_catalog.h" #include "test_main.h" using namespace game::data; using namespace game::design; TEST(stats_sums_and_counts) { DesignStats st = derive_stats(mini_rules(), armor()); CHECK_EQ(st.race, "Human"); CHECK_EQ(st.hull_class, "destroyer"); CHECK_EQ(st.sections.size(), std::size_t(3)); CHECK_EQ(st.mass, 800.0 + 2000 + 2500); CHECK_EQ(st.section_cost, 2000.0 + 4000 + 5000); CHECK_EQ(st.section_cost_with_options, st.section_cost); CHECK_EQ(st.health_total, 300.0 + 600 + 450); CHECK_EQ(st.cpoints, 500 + 1000 + 1380); CHECK_EQ(st.crew, 0); CHECK_EQ(st.banks, 5); CHECK_EQ(st.turrets, 1 + 3 + 3 + 1 + 1); CHECK_EQ(st.weapon_cost_per_bank, 50.0 * 4 + 3000); CHECK_EQ(st.weapon_cost_per_mount, 50.0 * (1 + 3 + 3 + 1) + 3000); CHECK_EQ(st.total_cost_estimate, 11000.0 + 3200); CHECK_EQ(st.command_cost, 1); CHECK(!st.command_quota); CHECK(st.ftlspeed && *st.ftlspeed == 0.2); CHECK(st.nodespeed && *st.nodespeed == 4); CHECK_EQ(st.engine_techera, "fission"); CHECK(st.netforcelimits && st.netforcelimits->speed == 40); CHECK_EQ(join(st.sections[2].applied_techs), "DRV_Fissn,DRV_Node"); CHECK(st.sections[1].banks[2].turret != nullptr); if (st.sections[1].banks[2].turret) CHECK_EQ(st.sections[1].banks[2].turret->model, "turret_m1missile.x"); CHECK(st.capacity("range") != nullptr); if (st.capacity("range")) CHECK_EQ(*st.capacity("range"), "9"); } TEST(stats_option_cost_multiplier) { Design d = armor(); d.known_techs.reset(); d.mission().options = {"IND_MagLat", "IND_RefCoat"}; // 1.4 x 1.2 DesignStats st = derive_stats(mini_rules(), d); CHECK(st.sections[1].option_cost_multiplier > 1.679 && st.sections[1].option_cost_multiplier < 1.681); CHECK(st.section_cost_with_options > 2000 + 4000 * 1.68 + 5000 - 0.01 && st.section_cost_with_options < 2000 + 4000 * 1.68 + 5000 + 0.01); CHECK_EQ(join(st.sections[1].applied_techs), "IND_MagLat,IND_RefCoat"); } TEST(stats_standalone_and_missing) { Design d; d.race = Species::Human; d.mission() = use("DEDefencePlatform", {"bal_gauss", "bal_gauss", "mis"}); DesignStats st = derive_stats(mini_rules(), d); CHECK_EQ(st.sections.size(), std::size_t(1)); CHECK(st.ftlspeed && *st.ftlspeed == 0); // a standalone mission section supplies the drive figures CHECK(st.command_quota && *st.command_quota == 20); CHECK(st.capacity("defence_platform") && *st.capacity("defence_platform") == "true"); CHECK(st.capacity("scanrange") && *st.capacity("scanrange") == "12"); // an unresolvable section contributes nothing; empty banks count as banks d.mission() = use("DENowhere"); d.engine() = use("DEFission", {""}); st = derive_stats(mini_rules(), d); CHECK_EQ(st.sections.size(), std::size_t(1)); CHECK_EQ(st.banks, 1); CHECK_EQ(st.weapon_cost_per_bank, 0.0); // NPC fixed weapons are costed Design n; n.race = Species::NPC; n.mission() = use("_Herald", {"", "", ""}); st = derive_stats(mini_rules(), n); CHECK_EQ(st.weapon_cost_per_bank, 7.0 + 9 + 7); CHECK_EQ(st.weapon_cost_per_mount, 7.0 * 2 + 9 + 7); CHECK_EQ(st.crew, 0); // `crew false` is a BadValue -> 0 } TEST(default_weapons_table) { Loaded t = parse_default_weapons( "// comment\nstandard small \"las_red.weapon\"\nMissile medium \"mis.weapon\"\nbad row\n"); CHECK(t.ok()); CHECK_EQ(t.value->size(), std::size_t(2)); CHECK_EQ(t.problems.size(), std::size_t(1)); CHECK(t.value->find("standard", "Small") != nullptr); CHECK(t.value->find("missile", "medium") != nullptr); CHECK(t.value->find("standard", "large") == nullptr); CHECK_EQ(t.value->find("standard", "small")->weapon_file, "las_red.weapon"); Loaded missing = load_default_weapons("/nonexistent/root"); CHECK(missing.ok()); CHECK_EQ(missing.value->size(), std::size_t(0)); CHECK_EQ(missing.problems.size(), std::size_t(1)); } TEST(fill_empty_banks_from_defaults) { Loaded t = parse_default_weapons( "standard small \"las_red.weapon\"\nstandard medium \"las_red.weapon\"\ntorpedo large \"trp_photon.weapon\"\n"); Design d = armor(); d.known_techs.reset(); d.mission() = use("DETorpedo", {"", ""}); // large torpedo + medium missile (no default row for missile) d.engine().weapons.clear(); // short list gets extended int n = fill_empty_banks(d, mini_catalog(), *t.value); CHECK_EQ(n, 2); CHECK_EQ(d.mission().weapons[0].text, "Weapons/trp_photon.weapon"); CHECK(d.mission().weapons[1].empty()); CHECK_EQ(d.engine().weapons.size(), std::size_t(1)); CHECK_EQ(d.engine().weapons[0].text, "Weapons/las_red.weapon"); CHECK_EQ(d.command().weapons[0].text, "bal_gauss"); // untouched std::vector v = mini_rules().validate(d); CHECK_EQ(join(rules_at(v, Level::Error)), "B2"); // only the missile bank is still empty // NPC fixed banks are filled with their own weapon Design h; h.race = Species::NPC; h.mission() = use("_Herald"); CHECK_EQ(fill_empty_banks(h, mini_catalog(), *t.value), 3); CHECK_EQ(h.mission().weapons[1].text, "Species/_NPC/weapons/Herald_beam.weapon"); }