193 lines
8 KiB
C++
193 lines
8 KiB
C++
// B* bank and weapon-fit rules on the mini catalog.
|
|
#include "game/design/rules.h"
|
|
#include "mini_catalog.h"
|
|
#include "test_main.h"
|
|
|
|
using namespace game::data;
|
|
using namespace game::design;
|
|
|
|
namespace {
|
|
|
|
Fit fit(const char* section, std::size_t bank, const char* weapon) {
|
|
const Catalog& c = mini_catalog();
|
|
return weapon_fits_bank(c.turrets, c.section("Human", section)->banks[bank], *c.weapon(weapon));
|
|
}
|
|
|
|
std::vector<int> banks_of(const std::vector<Violation>& v, const char* rule) {
|
|
std::vector<int> out;
|
|
for (const Violation& x : v)
|
|
if (x.rule == rule && x.bank) out.push_back(*x.bank);
|
|
return out;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(B1_one_weapon_per_bank) {
|
|
Design d = armor();
|
|
d.mission() = use("DEArmor", {"bal_gauss", "bal_gauss"});
|
|
CHECK_EQ(join(error_rules(d)), "B1,B2");
|
|
d.mission() = use("DEArmor", {"bal_gauss", "bal_gauss", "mis", "mis"});
|
|
CHECK_EQ(join(error_rules(d)), "B1");
|
|
}
|
|
|
|
TEST(B2_no_empty_bank) {
|
|
Design d = armor();
|
|
d.mission() = use("DEArmor", {"bal_gauss", "", "mis"});
|
|
std::vector<Violation> v = mini_rules().validate(d);
|
|
CHECK_EQ(join(rules_at(v, Level::Error)), "B2");
|
|
CHECK_EQ(banks_of(v, "B2").size(), std::size_t(1));
|
|
CHECK_EQ(banks_of(v, "B2")[0], 1);
|
|
}
|
|
|
|
TEST(B3_fixed_npc_banks) {
|
|
Design n;
|
|
n.race = Species::NPC;
|
|
n.hidden = true;
|
|
n.mission() = use("_Herald", {"", "", ""});
|
|
CHECK_EQ(join(error_rules(n)), ""); // empty entries take the bank's fixed weapon
|
|
n.mission().weapons = {WeaponRef::by_file("Species/_NPC/weapons/Herald_gun.weapon"),
|
|
WeaponRef::by_file("Species/_NPC/weapons/Herald_beam.weapon"),
|
|
WeaponRef::by_file("Species/_NPC/weapons/Herald_gun.weapon")};
|
|
CHECK_EQ(join(error_rules(n)), ""); // the same weapon spelled out, as NPC designs store it
|
|
n.mission().weapons[0] = WeaponRef::by_stem("bal_gauss");
|
|
std::vector<Violation> v = mini_rules().validate(n);
|
|
CHECK_EQ(join(rules_at(v, Level::Error)), "B3");
|
|
CHECK_EQ(banks_of(v, "B3")[0], 0);
|
|
// the bank without a turret spec takes only its fixed weapon
|
|
n.mission().weapons[0] = WeaponRef::none();
|
|
n.mission().weapons[2] = WeaponRef::by_file("Species/_NPC/weapons/Herald_beam.weapon");
|
|
CHECK_EQ(join(error_rules(n)), "B3");
|
|
}
|
|
|
|
TEST(B4_class_accept_table) {
|
|
CHECK(bank_accepts_class("standard", "standard"));
|
|
CHECK(bank_accepts_class("Standard", "Missile"));
|
|
CHECK(bank_accepts_class("standard", "grapple"));
|
|
CHECK(!bank_accepts_class("standard", "torpedo"));
|
|
CHECK(bank_accepts_class("strafe", "standard"));
|
|
CHECK(!bank_accepts_class("strafe", "missile"));
|
|
CHECK(bank_accepts_class("torpedo", "Torpedo"));
|
|
CHECK(!bank_accepts_class("missile", "standard"));
|
|
CHECK(!bank_accepts_class("planetmissile", "missile"));
|
|
}
|
|
|
|
TEST(B4_turret_rows) {
|
|
// small standard bank: tiny and small weapons only
|
|
CHECK(fit("DECommand", 0, "las_pd").ok);
|
|
CHECK(fit("DECommand", 0, "bal_gauss").ok);
|
|
CHECK(!fit("DECommand", 0, "mis").ok); // medium missile in a small bank: no row
|
|
CHECK(!fit("DECommand", 0, "trp_photon").ok); // class
|
|
// medium standard bank: small + medium standard, medium missile (missile row supplies the model), grapple
|
|
CHECK(fit("DEArmor", 2, "bal_gauss").ok);
|
|
CHECK(!fit("DEArmor", 2, "las_pd").ok);
|
|
Fit m = fit("DEArmor", 2, "mis");
|
|
CHECK(m.ok);
|
|
CHECK(m.row != nullptr);
|
|
if (m.row) CHECK_EQ(m.row->model, "turret_m1missile.x");
|
|
CHECK(fit("DEArmor", 2, "bal_grapple").ok);
|
|
// strafe bank: standard weapons; the row is looked up by the weapon's class
|
|
// first, so the standard row is used and the strafe rows are the fallback
|
|
Fit s = fit("DEStrafe", 0, "bal_gauss");
|
|
CHECK(s.ok);
|
|
if (s.row) CHECK_EQ(s.row->turret_class, "standard");
|
|
CHECK(!fit("DEStrafe", 0, "mis").ok);
|
|
{
|
|
// fallback to the bank's class when the weapon-class row is missing
|
|
const Catalog& c = mini_catalog();
|
|
BankDef strafe_medium;
|
|
strafe_medium.turret_class = "strafe";
|
|
strafe_medium.turret_size = "medium";
|
|
WeaponDef odd = *c.weapon("bal_gauss");
|
|
odd.turret_size = "medium";
|
|
odd.turret_class = "standard";
|
|
const TurretRow* row = turret_row(c.turrets, strafe_medium, odd);
|
|
CHECK(row != nullptr);
|
|
if (row) CHECK_EQ(row->turret_class, "standard"); // (medium, medium, standard) exists
|
|
odd.turret_class = "nothing";
|
|
CHECK(turret_row(c.turrets, strafe_medium, odd) != nullptr); // (medium, medium, strafe) row
|
|
CHECK(!weapon_fits_bank(c.turrets, strafe_medium, odd).ok); // but the class table rejects it
|
|
}
|
|
// large standard: small/medium/large standard + missiles
|
|
CHECK(fit("DEStrafe", 1, "can_plasma").ok);
|
|
CHECK(fit("DEStrafe", 1, "mis").ok);
|
|
CHECK(!fit("DEStrafe", 1, "las_pd").ok);
|
|
CHECK(!fit("DEStrafe", 1, "trp_photon").ok);
|
|
// large torpedo: large torpedoes only
|
|
CHECK(fit("DETorpedo", 0, "trp_photon").ok);
|
|
CHECK(!fit("DETorpedo", 0, "can_plasma").ok);
|
|
// medium missile bank: missiles only
|
|
CHECK(fit("DETorpedo", 1, "mis").ok);
|
|
CHECK(!fit("DETorpedo", 1, "bal_gauss").ok);
|
|
// planetmissile: the planet missiles (medium PlanetMissile in a large bank)
|
|
CHECK(fit("DEPlanetMissile", 0, "mis_defplat").ok);
|
|
CHECK(!fit("DEPlanetMissile", 0, "mis").ok);
|
|
}
|
|
|
|
TEST(B4_in_a_design) {
|
|
Design d = armor();
|
|
d.known_techs.reset();
|
|
d.command() = use("DECommand", {"trp_photon"});
|
|
CHECK_EQ(join(error_rules(d)), "B4");
|
|
d = armor();
|
|
d.known_techs.reset();
|
|
d.mission() = use("DEArmor", {"las_pd", "las_pd", "las_pd"});
|
|
std::vector<Violation> v = mini_rules().validate(d);
|
|
CHECK_EQ(join(rules_at(v, Level::Error)), "B4");
|
|
CHECK_EQ(banks_of(v, "B4").size(), std::size_t(1));
|
|
CHECK_EQ(banks_of(v, "B4")[0], 2);
|
|
d.mission() = use("DEArmor", {"mis", "bal_gauss", "mis"});
|
|
CHECK_EQ(join(error_rules(d)), "B4");
|
|
}
|
|
|
|
TEST(B5_weapon_must_exist_and_be_in_scope) {
|
|
Design d = armor();
|
|
d.command() = use("DECommand", {"bal_nothing"});
|
|
CHECK_EQ(join(error_rules(d)), "B5");
|
|
d.command().weapons = {WeaponRef::by_id(4242)};
|
|
CHECK_EQ(join(error_rules(d)), "B5");
|
|
d.command().weapons = {WeaponRef::by_file("Species/_NPC/weapons/Herald_beam.weapon")};
|
|
CHECK_EQ(join(error_rules(d)), "B5"); // NPC-only weapon on a player hull
|
|
Design n;
|
|
n.race = Species::NPC;
|
|
n.command() = use("_DECommand", {"Species/_NPC/weapons/Herald_beam.weapon"});
|
|
n.mission() = use("_DEMission", {"bal_gauss", "bal_gauss", "mis"});
|
|
n.engine() = use("_DEEngine", {"bal_gauss"});
|
|
CHECK_EQ(join(error_rules(n)), ""); // fine on an NPC hull
|
|
}
|
|
|
|
TEST(B6_exclusive_species) {
|
|
Design d = armor();
|
|
d.known_techs.reset();
|
|
d.mission() = use("DEArmor", {"bal_gauss", "bal_gauss", "bal_grapple"});
|
|
CHECK_EQ(join(error_rules(d)), "B6");
|
|
Design z = armor(Species::Zuul);
|
|
z.known_techs.reset();
|
|
z.mission() = use("DEArmor", {"bal_gauss", "bal_gauss", "bal_grapple"});
|
|
CHECK_EQ(join(error_rules(z)), "");
|
|
}
|
|
|
|
TEST(B7_rider_weapons_need_their_rider_section) {
|
|
Design d = armor();
|
|
d.known_techs.reset();
|
|
d.mission() = use("DEAssaultShuttle", {"brd_shuttle", "brd_tarkahunter"});
|
|
std::vector<Violation> v = mini_rules().validate(d);
|
|
CHECK_EQ(join(rules_at(v, Level::Error)), "B7"); // Human has _AssaultShuttle but no _HunterShip
|
|
CHECK_EQ(banks_of(v, "B7")[0], 1);
|
|
Design t = armor(Species::Tarkas);
|
|
t.known_techs.reset();
|
|
t.mission() = use("DEArmor", {"bal_gauss", "bal_gauss", "mis"});
|
|
// Tarkas mini catalog has both riders but no DEAssaultShuttle section; check the weapon rule directly
|
|
CHECK(mini_catalog().section("Tarkas", "_HunterShip") != nullptr);
|
|
}
|
|
|
|
TEST(B8_hidden_weapons_are_engine_only) {
|
|
Design d = armor();
|
|
d.known_techs.reset();
|
|
d.mission() = use("DEPlanetMissile", {"mis_planet"});
|
|
CHECK_EQ(join(error_rules(d)), "B8");
|
|
d.hidden = true;
|
|
CHECK_EQ(join(error_rules(d)), "");
|
|
d.hidden = false;
|
|
d.mission() = use("DEPlanetMissile", {"mis_defplat"});
|
|
CHECK_EQ(join(error_rules(d)), "");
|
|
}
|