feat(api): add hybrid merge query logic

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-06 21:24:55 -07:00
parent 426b984b5a
commit 6d6944495e

View file

@ -1440,6 +1440,78 @@ impl GdCity {
out
}
/// Query which hybrid merges are available for this city given its current
/// buildings and the player's researched techs.
///
/// `registry_json` — JSON array of `BuildingDef` objects (the full game
/// pack's building list from DataLoader). Caller passes all buildings; this
/// method filters to hybrids.
///
/// `researched_techs` — list of tech IDs the player has already completed.
///
/// Returns an `Array` of `Dictionary` values, one per available merge:
/// ```text
/// {
/// "building_a": String, // first prereq id
/// "building_b": String, // second prereq id
/// "into": String, // hybrid building id
/// "tech_gated": bool, // true if military_synthesis not yet researched
/// }
/// ```
/// The returned list includes tech-gated merges so the UI can grey them
/// out rather than hide them — the caller decides presentation.
#[func]
fn available_merges(
&self,
registry_json: GString,
researched_techs: PackedStringArray,
) -> Array {
use mc_city::{BuildingDef, MERGE_GATING_TECH};
let defs: Vec<BuildingDef> = match serde_json::from_str(&registry_json.to_string()) {
Ok(v) => v,
Err(e) => {
godot_error!("GdCity::available_merges parse error: {}", e);
return Array::new();
}
};
// Build fast lookup sets from the city's current buildings.
let owned: std::collections::BTreeSet<&str> =
self.inner.buildings.iter().map(|s| s.as_str()).collect();
// Build researched-tech set.
let techs: std::collections::BTreeSet<String> = (0..researched_techs.len())
.filter_map(|i| researched_techs.get(i).map(|t| t.to_string()))
.collect();
let tech_gated = !techs.contains(MERGE_GATING_TECH);
let mut out = Array::new();
for def in &defs {
// Only hybrid buildings have exactly 2 prereqs.
if def.merge_requires.len() != 2 {
continue;
}
let a = def.merge_requires[0].as_str();
let b = def.merge_requires[1].as_str();
// Both prereqs must be present in the city.
if !owned.contains(a) || !owned.contains(b) {
continue;
}
// Irreversibility guard: skip if hybrid already built.
if owned.contains(def.id.as_str()) {
continue;
}
let mut entry = Dictionary::new();
entry.set("building_a", GString::from(a));
entry.set("building_b", GString::from(b));
entry.set("into", GString::from(def.id.as_str()));
entry.set("tech_gated", tech_gated);
out.push(&entry.to_variant());
}
out
}
/// Register a building's flat yield bonuses. GDScript passes the
/// five per-turn bonuses parsed from the building JSON's `effects` array.
/// The city applies these in `get_yields` for each owned building.