#include "game/data/weapon.h" #include "game/data/fields.h" namespace game::data { using detail::Fields; const std::vector& weapon_behavior_blocks() { static const std::vector names = { "bolt", "beam", "torpedo", "rider", "missile", "chainlightning", "col", "mine", "disintegrator", "grapple", "projectedshield", "mirv", "nodecannon", "siege", "mesonprojector", "spyship", "wraith"}; return names; } namespace { RangeTable read_rangetable(Fields& f) { RangeTable t; auto band = [&](RangeTable::Band& b, const char* prefix) { std::string p(prefix); b.range = f.opt_double(p + "_range"); b.deviation = f.opt_double(p + "_range_dev"); b.damage = f.opt_double(p + "_range_dam"); }; band(t.point_blank, "pb"); band(t.effective, "eff"); band(t.maximum, "max"); return t; } PlanetDamage read_planet_damage(Fields& f) { PlanetDamage d; d.pop = f.opt_double("dam_pop"); d.infra = f.opt_double("dam_infra"); d.terra = f.opt_double("dam_terra"); return d; } } // namespace Loaded load_weapon(const mars::parse::Document& doc, std::string file) { Loaded out; const mars::parse::Node* node = doc.root.first_block("weapon"); if (!node) { out.problems.push_back({Problem::Kind::MissingBlock, file, 0, "weapon", "no top-level weapon{} block"}); return out; } WeaponDef w; w.file = file; w.raw = snapshot(*node); Fields f(w.raw, file, out.problems); w.name = f.required_str("name"); w.weapon_class = f.required_str("weaponclass"); w.weapon_family = f.str("weaponfamily"); w.weapon_damage_type = f.str("weapondamagetype"); w.requires = f.strs("requires"); w.compatible_section = f.strs("compatible_section"); w.exclusive_species = f.str("exclusive_species"); w.cost = f.opt_int("cost"); w.turret_size = f.required_str("turretsize"); w.turret_class = f.required_str("turretclass"); w.track_speed_mod = f.opt_double("trackspeed_mod"); w.burst_volleys = f.opt_int("burst_volleys"); w.recharge_time = f.opt_double("recharge_time"); w.volley_period = f.opt_double("volley_period"); w.volley_duration = f.opt_double("volley_duration"); w.buildup_delay = f.opt_double("buildup_delay"); w.solution_tolerance = f.opt_double("solution_tolerance"); w.range = f.opt_int("range"); w.range_planet = f.opt_int("range_planet"); w.muzzle_speed = f.opt_int("muzzle_speed"); w.hpbonus = f.opt_int("hpbonus"); w.dam_est = f.opt_int("dam_est"); w.hidden = f.opt_bool("hidden"); w.pinpoint = f.opt_bool("pinpoint"); w.blindfire = f.opt_bool("blindfire"); w.secondary_pd = f.opt_bool("secondary_pd"); w.model1 = f.str("model1"); w.model2 = f.str("model2"); w.model3 = f.str("model3"); w.muzzle_effect = f.str("muzzle_effect"); w.muzzle_sound = f.str("muzzle_sound"); w.icon_file = f.str("icon_file"); w.icon_rect = f.str("icon_rect"); w.fc.requires_los = f.opt_bool("fc_requires_los"); w.fc.requires_inrange = f.opt_bool("fc_requires_inrange"); w.fc.requires_enemycolony = f.opt_bool("fc_requires_enemycolony"); w.fc.manual_target = f.opt_bool("fc_manual_target"); w.fc.manual_toggle = f.opt_bool("fc_manual_toggle"); w.fc.manual_launch = f.opt_bool("fc_manual_launch"); w.fc.controllable = f.opt_bool("fc_controllable"); w.fc.holdsfire = f.opt_bool("fc_holdsfire"); w.fc.explicit_target = f.opt_bool("fc_explicit_target"); w.fc.exclusive_launch = f.opt_bool("fc_exclusive_launch"); w.fc.targets_expire = f.opt_bool("fc_targets_expire"); w.ratings.fire_rate = f.opt_double("rating_frate"); w.ratings.damage = f.opt_double("rating_dam"); w.ratings.accuracy = f.opt_double("rating_acc"); w.ratings.range = f.opt_double("rating_range"); for (std::string_view name : weapon_behavior_blocks()) { const Block* b = w.raw.block(name); if (!b) continue; w.behavior_kind = std::string(name); Fields bf = f.sub(*b); w.planet_damage = read_planet_damage(bf); if (const Block* rt = b->block("rangetable")) { Fields rf = f.sub(*rt); w.rangetable = read_rangetable(rf); } if (iequals(name, "bolt")) { BoltDef bolt; if (w.rangetable) bolt.rangetable = *w.rangetable; bolt.planet = w.planet_damage; bolt.mass = bf.opt_double("mass"); bolt.beam_origin = bf.opt_double("beam_origin"); bolt.beam_length = bf.opt_double("beam_length"); bolt.ricochet_mod = bf.opt_double("ricochet_mod"); bolt.effect = bf.str("effect"); bolt.impact_effect = bf.str("impact_effect"); bolt.expire_effect = bf.str("expire_effect"); w.bolt = std::move(bolt); } break; } out.value = std::move(w); return out; } Loaded parse_weapon(std::string_view text, std::string file) { auto parsed = mars::parse::parse_blocks(text); if (!parsed.ok()) { Loaded out; out.problems.push_back({Problem::Kind::Syntax, file, parsed.error().line, "", parsed.error().message}); return out; } Loaded out = load_weapon(parsed.value(), std::move(file)); for (const auto& d : parsed.value().warnings) out.problems.push_back({Problem::Kind::Syntax, out.value ? out.value->file : std::string(), d.line, "", "recovered: " + d.message}); return out; } } // namespace game::data