#include "mini_catalog.h" #include #include #include using namespace game::data; using namespace game::design; namespace fs = std::filesystem; namespace { void put(const fs::path& p, const std::string& text) { fs::create_directories(p.parent_path()); std::ofstream(p, std::ios::binary) << text; } // --- sections --------------------------------------------------------------- std::string bank(const char* cls, const char* size, int mounts, const char* fixed = nullptr) { std::string s = " bank {"; if (fixed) s += std::string(" weapon \"") + fixed + "\""; if (cls) s += std::string(" turretclass ") + cls; if (size) s += std::string(" turretsize ") + size; for (int i = 0; i < mounts; ++i) s += " mount { node N" + std::to_string(i) + " min_azimuth -90 max_azimuth 90 }"; return s + " }\n"; } const char* kArmorOptions = " option { option IND_PlyAlloy option IND_MagLat option IND_QrkRes } option { option IND_RefCoat option IND_ImpRfCt }\n"; std::string de_command(const char* extra = "") { return "shipsection { model a.X section_type command section_class destroyer socket_aft CommandNode\n" " health 300 mass 800 cost 2000 cpoints 500 command_cost 1\n" + std::string(kArmorOptions) + extra + bank("standard", "small", 1) + "}\n"; } std::string de_armor(const char* extra = "") { return "shipsection { model b.X section_type mission section_class destroyer socket_fore MissionNode01 socket_aft MissionNode\n" " health 600 mass 2000 cost 4000 cpoints 1000\n" + std::string(kArmorOptions) + " optiondef { option SLD_MkOne option SLD_MkTwo }\n" + extra + bank("standard", "small", 3) + bank("standard", "small", 3) + bank("standard", "medium", 1) + "}\n"; } std::string de_fission(const char* drive) { return "shipsection { model c.X Section_Type Engine section_class Destroyer socket_fore EngineNode\n" " requires_tech DRV_Fissn requires_tech " + std::string(drive) + "\n health 450 mass 2500 cost 5000 cpoints 1380 engine_techera fission ftlspeed .2 nodespeed 4 range 9\n" + std::string(kArmorOptions) + " option DRV_RecFiss\n" " netforcelimits { force_forward 23000 force_right 23000 force_up 23000 torque_yaw 5 torque_pitch 5 torque_roll 5 speed 40 rotspeed 10 }\n" + bank("standard", "small", 1) + "}\n"; } std::string de_platform() { return "shipsection { model d.X section_type mission section_class destroyer\n" " health 1500 mass 5000 cost 6000 cpoints 900 defence_platform true scanrange 12 command_quota 20\n" " ftlspeed 0 nodespeed 0\n" + std::string(kArmorOptions) + bank("standard", "small", 2) + bank("standard", "small", 2) + bank("standard", "medium", 1) + "}\n"; } const char* kRider = "shipsection { model r.X section_class destroyer design_class rider entity_class rider health 50 mass 100 cost 100 }\n"; const char* kRiderGated = "shipsection { model r.X section_class destroyer design_class rider entity_class rider requires_tech DRN_AdvFrm health 50 mass 100 cost 100 }\n"; void write_human(const fs::path& r) { fs::path s = r / "Species/Human/sections"; put(s / "DECommand.shipsection", de_command()); put(s / "DEArmor.shipsection", de_armor()); put(s / "DEFission.shipsection", de_fission("DRV_Node")); put(s / "DEHyperTest.shipsection", de_fission("DRV_Hyper")); // C5: Human can never research DRV_Hyper put(s / "DEOddTech.shipsection", de_fission("XXX_Nowhere")); // C1 warn: unknown tech put(s / "DEDefencePlatform.shipsection", de_platform()); put(s / "DERamScoop.shipsection", de_command(" exclude \"DEFission\" ramscoop true\n")); put(s / "DEStrafe.shipsection", "shipsection { model e.X section_type mission section_class destroyer socket_fore F socket_aft A health 1 mass 1 cost 1\n" + bank("strafe", "small", 4) + bank("standard", "large", 1) + "}\n"); put(s / "DETorpedo.shipsection", "shipsection { model e.X section_type mission section_class destroyer socket_fore F socket_aft A health 1 mass 1 cost 1\n" " requires_tech GRP_Torps\n" + bank("torpedo", "large", 1) + bank("missile", "medium", 1) + "}\n"); put(s / "DEAssaultShuttle.shipsection", "shipsection { model e.X section_type mission section_class destroyer socket_fore F socket_aft A health 1 mass 1 cost 1\n" " requires_tech DRN_AdvFrm\n" + bank("assaultshuttlerider", "large", 1) + bank("tarkahunter", "large", 1) + "}\n"); put(s / "DEPlanetMissile.shipsection", "shipsection { model e.X section_type mission section_class destroyer socket_fore F socket_aft A health 1 mass 1 cost 1\n" + bank("planetmissile", "large", 1) + "}\n"); put(s / "CRCommand.shipsection", "shipsection { model f.X section_type command section_class cruiser socket_aft CommandNode health 1 mass 10 cost 100 crew 5\n" + bank("standard", "medium", 1) + "}\n"); put(s / "CRArmor.shipsection", "shipsection { model f.X section_type mission section_class cruiser socket_fore F socket_aft A health 1 mass 20 cost 200 crew 10\n" + bank("standard", "large", 2) + "}\n"); put(s / "CRFission.shipsection", "shipsection { model f.X section_type engine section_class cruiser socket_fore EngineNode health 1 mass 30 cost 300 crew 15\n" " requires_tech DRV_Fissn requires_tech DRV_Node engine_techera fission ftlspeed .2 nodespeed 4\n" + bank("standard", "medium", 1) + "}\n"); // the station triple (design_class station: no hull-class tech; partners pinned) put(s / "DNStation.shipsection", "shipsection { model g.X section_type mission section_class dreadnought socket_fore Fore socket_aft Aft\n" " requires_tech IND_OrbCom health 9000 mass 100000 cost 700000 crew 100 entity_class \"Station\" design_class station command_quota 46\n" " explicit_command_section \"DNStation_Fore\" explicit_engine_section \"DNStation_Aft\"\n" + bank("standard", "large", 2) + "}\n"); put(s / "DNStation_Fore.shipsection", "shipsection { model g.X section_type command section_class dreadnought socket_aft Aft explicit_section true\n" " design_class station health 1 mass 1 cost 1\n" + bank("standard", "medium", 1) + "}\n"); put(s / "DNStation_Aft.shipsection", "shipsection { model g.X section_type engine section_class dreadnought socket_fore Fore explicit_section true\n" " design_class station health 1 mass 1 cost 1\n" + bank("standard", "medium", 1) + "}\n"); put(s / "DNCommand.shipsection", "shipsection { model g.X section_type command section_class dreadnought socket_aft CommandNode health 1 mass 1 cost 1\n" + bank("standard", "medium", 1) + "}\n"); put(s / "_AssaultShuttle.shipsection", kRider); put(s / "_shipsections.txt", "1 _AssaultShuttle.shipsection\n37 DEArmor.shipsection\n42 DECommand.shipsection\n47 DEFission.shipsection\n" "50 DEDefencePlatform.shipsection\n51 DERamScoop.shipsection\n52 DEStrafe.shipsection\n53 DETorpedo.shipsection\n" "54 DEAssaultShuttle.shipsection\n55 DEPlanetMissile.shipsection\n56 DEHyperTest.shipsection\n57 DEOddTech.shipsection\n" "60 CRCommand.shipsection\n61 CRArmor.shipsection\n62 CRFission.shipsection\n" "70 DNStation.shipsection\n71 DNStation_Fore.shipsection\n72 DNStation_Aft.shipsection\n73 DNCommand.shipsection\n"); } void write_tarkas(const fs::path& r) { fs::path s = r / "Species/Tarkas/sections"; put(s / "DECommand.shipsection", de_command()); put(s / "DEArmor.shipsection", de_armor()); put(s / "DEFission.shipsection", de_fission("DRV_Hyper")); put(s / "DEDefencePlatform.shipsection", de_platform()); put(s / "_AssaultShuttle.shipsection", kRiderGated); put(s / "_HunterShip.shipsection", kRider); put(s / "_shipsections.txt", "1 _AssaultShuttle.shipsection\n2 _HunterShip.shipsection\n37 DEArmor.shipsection\n42 DECommand.shipsection\n" "47 DEFission.shipsection\n50 DEDefencePlatform.shipsection\n"); } void write_zuul(const fs::path& r) { fs::path s = r / "Species/Zuul/sections"; put(s / "DECommand.shipsection", de_command()); put(s / "DEArmor.shipsection", de_armor()); put(s / "DEFission.shipsection", de_fission("DRV_Node")); put(s / "_shipsections.txt", "37 DEArmor.shipsection\n42 DECommand.shipsection\n47 DEFission.shipsection\n"); } void write_npc(const fs::path& r) { fs::path s = r / "Species/_NPC/sections"; // an NPC hull: typeless, standalone, fixed weapons in every bank, one bank without a turret spec put(s / "_Herald.shipsection", "shipsection { model h.X section_class cruiser health 5000 mass 20000 cost 1 crew false nodesign 1\n" + bank("standard", "medium", 2, "Species/_NPC/weapons/Herald_gun.weapon") + bank("strafe", "small", 1, "Species/_NPC/weapons/Herald_beam.weapon") + bank(nullptr, nullptr, 1, "Species/_NPC/weapons/Herald_gun.weapon") + "}\n"); // an NPC destroyer triple (nodesign, typed, sockets present, player-style banks) put(s / "_DECommand.shipsection", de_command(" nodesign 1\n")); put(s / "_DEMission.shipsection", de_armor(" nodesign true\n")); put(s / "_DEEngine.shipsection", de_fission("DRV_Node")); put(s / "_shipsections.txt", "1 _Herald.shipsection\n2 _DECommand.shipsection\n3 _DEMission.shipsection\n4 _DEEngine.shipsection\n"); put(r / "Species/_NPC/weapons/Herald_gun.weapon", "weapon { name @WEAPON_HERALD_GUN weaponclass bullet turretsize medium turretclass standard hidden 1 cost 7 }\n"); put(r / "Species/_NPC/weapons/Herald_beam.weapon", "weapon { name @WEAPON_HERALD_BEAM weaponclass beam turretsize small turretclass standard cost 9 }\n"); } // --- weapons ----------------------------------------------------------------- std::string weapon(const char* cls, const char* size, const char* tcls, const std::string& extra, int cost) { return std::string("weapon { name @W weaponclass ") + cls + " turretsize " + size + " turretclass " + tcls + " cost " + std::to_string(cost) + " " + extra + " }\n"; } void write_weapons(const fs::path& r) { fs::path w = r / "Weapons"; put(w / "bal_gauss.weapon", weapon("bullet", "small", "standard", "requires WEP_GsDrvr", 50)); put(w / "las_red.weapon", weapon("beam", "small", "standard", "requires WEP_RedLas", 60)); put(w / "las_pd.weapon", weapon("beam", "tiny", "standard", "requires WEP_PD", 40)); put(w / "can_plasma.weapon", weapon("bullet", "large", "standard", "requires WEP_Plasma", 400)); put(w / "mis.weapon", weapon("missile", "medium", "Missile", "", 3000)); put(w / "mis_defplat.weapon", weapon("missile", "medium", "PlanetMissile", "", 3500)); put(w / "mis_planet.weapon", weapon("missile", "medium", "PlanetMissile", "hidden 1", 1)); put(w / "trp_photon.weapon", weapon("torpedo", "large", "torpedo", "requires WEP_PhotTrp", 900)); put(w / "bal_grapple.weapon", weapon("grapple", "medium", "grapple", "requires WEP_Grapple exclusive_species zuul", 500)); put(w / "brd_shuttle.weapon", weapon("rider", "large", "assaultshuttlerider", "requires DRN_AdvFrm compatible_section _AssaultShuttle", 800)); put(w / "brd_tarkahunter.weapon", weapon("rider", "large", "tarkahunter", "requires DRN_AdvFrm compatible_section _HunterShip compatible_section _HunterShip_B", 800)); put(w / "hvy_bem_lancer.weapon", weapon("beam", "large", "beam", "requires GRP_HvyBeam", 1200)); put(w / "_weapons.txt", "8 bal_gauss.weapon\n9 las_red.weapon\n10 las_pd.weapon\n11 can_plasma.weapon\n21 mis.weapon\n22 mis_defplat.weapon\n" "23 mis_planet.weapon\n30 trp_photon.weapon\n31 bal_grapple.weapon\n32 brd_shuttle.weapon\n33 brd_tarkahunter.weapon\n" "34 hvy_bem_lancer.weapon\n"); put(w / "_turrets.txt", "// size weapon-size class health track az inc model\n" "small tiny standard 100 400 1 1 \"t_s_tiny.x\"\n" "small small standard 120 320 1 1 \"t_s.x\"\n" "medium small standard 300 200 1 1 \"t_m_s.x\"\n" "medium medium standard 500 140 1 1 \"t_m.x\"\n" "large small standard 600 200 1 1 \"t_l_s.x\"\n" "large medium standard 700 140 1 1 \"t_l_m.x\"\n" "Large large standard 900 80 1 1 \"t_l.x\"\n" "medium medium Missile 400 100 1 0.25 \"turret_m1missile.x\"\n" "large medium missile 450 100 1 0.25 \"turret_l1missile.x\"\n" "large large missile 500 100 1 0.25 \"turret_l2missile.x\"\n" "large medium planetmissile 500 100 1 0.25 \"turret_pm.x\"\n" "large large torpedo 800 60 1 1 \"t_trp.x\"\n" "large large beam 1500 80 1 1 \"\"\n" "medium medium grapple 500 140 1 1 \"t_grap.x\"\n" "large medium grapple 700 140 1 1 \"t_grap_l.x\"\n" "small small strafe 120 320 1 1 \"t_s.x\"\n" "medium medium strafe 500 140 1 1 \"t_m.x\"\n" "large large assaultshuttlerider 900 80 1 1 \"t_rider.x\"\n" "large large tarkahunter 900 80 1 1 \"t_rider.x\"\n"); put(w / "_defaultweapons.txt", "// \n" "standard tiny \"las_pd.weapon\"\nstandard small \"las_red.weapon\"\nstandard medium \"las_red.weapon\"\n" "standard large \"can_plasma.weapon\"\nmissile medium \"mis.weapon\"\nstrafe small \"las_red.weapon\"\n" "torpedo large \"trp_photon.weapon\"\n"); } // --- tech tree -------------------------------------------------------------- void write_tech(const fs::path& r) { put(r / "TechTree/MasterTechList.tech", "tech { name \"IND_ROOT\" allows \"IND_PlyAlloy RP:1\" allows \"IND_CruisCon RP:1\" allows \"IND_OrbCom RP:1\" allows \"IND_RefCoat RP:1\" }\n" "tech { name \"IND_PlyAlloy\" option_cost 1.2 allows \"IND_MagLat RP:1\" }\n" "tech { name \"IND_MagLat\" option_cost 1.4 allows \"IND_QrkRes RP:1\" }\n" "tech { name \"IND_QrkRes\" option_cost 1.6 }\n" "tech { name \"IND_RefCoat\" option_cost 1.2 allows \"IND_ImpRfCt RP:1\" }\n" "tech { name \"IND_ImpRfCt\" option_cost 1.5 }\n" "tech { name \"IND_CruisCon\" allows \"IND_DreadCon RP:1\" }\n" "tech { name \"IND_DreadCon\" }\n" "tech { name \"IND_OrbCom\" }\n" "tech { name \"DRV_ROOT\" allows \"DRV_Fissn RP:1\" }\n" "tech { name \"DRV_Fissn\" allows \"DRV_Node RP:1 Human:100 Hiver:0 Tarkas:0 Liir:0 Zuul:100 Morrigi:0\"\n" " allows \"DRV_Hyper RP:1 Human:0 Hiver:0 Tarkas:100 Liir:0 Zuul:0 Morrigi:0\" allows \"DRV_RecFiss RP:1\" }\n" "tech { name \"DRV_Node\" }\n" "tech { name \"DRV_Hyper\" }\n" "tech { name \"DRV_RecFiss\" option_cost 1.2 }\n" "tech { name \"WEP_ROOT\" allows \"WEP_GsDrvr RP:1\" allows \"WEP_RedLas RP:1\" allows \"WEP_Plasma RP:1\" allows \"WEP_Grapple RP:1\" }\n" "tech { name \"WEP_GsDrvr\" }\n" "tech { name \"WEP_RedLas\" allows \"WEP_PD RP:1\" allows \"WEP_HvyLancer RP:1\" }\n" "tech { name \"WEP_PD\" group PD }\n" "tech { name \"WEP_Plasma\" }\n" "tech { name \"WEP_Grapple\" }\n" "tech { name \"WEP_HvyLancer\" group HvyBeam }\n" "tech { name \"TRP_ROOT\" allows \"WEP_PhotTrp RP:1\" allows \"WEP_PlasTrp RP:1\" }\n" "tech { name \"WEP_PhotTrp\" group TORPS }\n" "tech { name \"WEP_PlasTrp\" group TORPS }\n" "tech { name \"DRN_ROOT\" allows \"DRN_AdvFrm RP:1\" }\n" "tech { name \"DRN_AdvFrm\" }\n" "tech { name \"SLD_Root\" allows \"SLD_MkOne RP:1\" }\n" "tech { name \"SLD_MkOne\" option_cost 1.4 allows \"SLD_MkTwo RP:1\" }\n" "tech { name \"SLD_MkTwo\" option_cost 1.8 }\n"); } fs::path make_root() { auto stamp = std::chrono::steady_clock::now().time_since_epoch().count(); fs::path root = fs::temp_directory_path() / ("game_design_test_" + std::to_string(static_cast(stamp))); fs::remove_all(root); write_weapons(root); write_human(root); write_tarkas(root); write_zuul(root); write_npc(root); write_tech(root); return root; } } // namespace const Catalog& mini_catalog() { static const Catalog cat = [] { fs::path root = make_root(); Catalog c = load_catalog(root); fs::remove_all(root); return c; }(); return cat; } const Ruleset& mini_rules() { static const Ruleset rules(mini_catalog()); return rules; } SectionUse use(std::string stem, std::vector weapons, std::vector options) { SectionUse u; u.section = std::move(stem); for (std::string& w : weapons) u.weapons.push_back(w.empty() ? WeaponRef::none() : WeaponRef::by_stem(std::move(w))); u.options = std::move(options); return u; } Design armor(Species race) { Design d; d.name = "Armor"; d.race = race; d.command() = use("DECommand", {"bal_gauss"}); d.mission() = use("DEArmor", {"bal_gauss", "bal_gauss", "mis"}); d.engine() = use("DEFission", {"bal_gauss"}); d.known_techs = std::vector{"DRV_Fissn", race == Species::Tarkas ? "DRV_Hyper" : "DRV_Node", "WEP_GsDrvr"}; return d; } std::vector rules_of(const Design& d, Level level) { return rules_at(mini_rules().validate(d), level); } std::vector error_rules(const Design& d) { return rules_of(d, Level::Error); } std::string join(const std::vector& v) { std::string s; for (const std::string& x : v) s += (s.empty() ? "" : ",") + x; return s; }