305 lines
17 KiB
C++
305 lines
17 KiB
C++
#include "mini_catalog.h"
|
|
|
|
#include <chrono>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
|
|
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 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 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 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 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 DRV_Fissn requires 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 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",
|
|
"// <mount-class> <mount-size> <weapon-file>\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<long long>(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<std::string> weapons, std::vector<std::string> 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<std::string>{"DRV_Fissn", race == Species::Tarkas ? "DRV_Hyper" : "DRV_Node", "WEP_GsDrvr"};
|
|
return d;
|
|
}
|
|
|
|
std::vector<std::string> rules_of(const Design& d, Level level) { return rules_at(mini_rules().validate(d), level); }
|
|
std::vector<std::string> error_rules(const Design& d) { return rules_of(d, Level::Error); }
|
|
|
|
std::string join(const std::vector<std::string>& v) {
|
|
std::string s;
|
|
for (const std::string& x : v) s += (s.empty() ? "" : ",") + x;
|
|
return s;
|
|
}
|