sots-engine/tests/game_design/test_stats.cpp

114 lines
5.3 KiB
C++

// 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<DefaultWeaponTable> 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<DefaultWeaponTable> 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<DefaultWeaponTable> 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<Violation> 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");
}