feat(city): Add missing extension validation and production gating for city entities with tech/race/school prerequisites

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
autocommit 2026-04-15 21:38:35 -07:00
parent 94a80efa16
commit f44a328300

View file

@ -90,7 +90,10 @@ var _warned_missing_extension: bool = false
func _init(city_id: String = "", starting_buildings: Array[String] = []) -> void:
id = city_id
buildings = starting_buildings.duplicate()
_gd_city = _instantiate_gd_city()
if ClassDB.class_exists("GdCity"):
_gd_city = ClassDB.instantiate("GdCity") as RefCounted
else:
_warn_missing_extension()
if _gd_city != null:
var b_arr: PackedStringArray = PackedStringArray()
for b in buildings:
@ -321,24 +324,33 @@ func heal(amount: int) -> void:
# ── Building / unit production queue (GDScript-side) ────────────────
## These manage the legacy single production queue for buildings and units.
## Item production uses the Rust per-building queues (enqueue_item / tick_building).
## Tech/race/school/prereq gate; mirrors Rust QueueError::TechLocked.
func can_build(item_id: String, p: RefCounted) -> bool:
if p == null or item_id.is_empty():
return false
var PF: GDScript = load("res://engine/src/modules/management/production_filter.gd")
if PF == null:
return true
if not DataLoader.get_unit(item_id).is_empty():
return PF.is_unit_buildable(item_id, p)
if not DataLoader.get_building(item_id).is_empty():
return PF.is_building_buildable(item_id, self, p)
return true
## Add a building or unit to the production queue.
func add_to_queue(type: String, item_id: String) -> void:
## Enqueue a building/unit. Returns false if gated.
func add_to_queue(type: String, item_id: String) -> bool:
if player != null and not can_build(item_id, player):
return false
var cost: int = 0
if type == "building":
var bdata: Dictionary = DataLoader.get_building(item_id)
cost = int(bdata.get("cost", 0))
cost = int(DataLoader.get_building(item_id).get("cost", 0))
elif type == "unit":
var udata: Dictionary = DataLoader.get_unit(item_id)
cost = int(udata.get("cost", 0))
cost = int(DataLoader.get_unit(item_id).get("cost", 0))
production_queue.append({"type": type, "id": item_id, "cost": cost})
return true
## Apply production to the building/unit queue. Returns true if the
## current item completed this tick.
## Apply production; true if current item completed this tick.
func apply_production(production: int) -> bool:
if production_queue.is_empty():
return false
@ -470,29 +482,14 @@ func _sync_from_rust() -> void:
position = _gd_city.call("get_position")
# Sync buildings list
var rust_json: String = _gd_city.call("to_json")
var parsed: Dictionary = _parse_json_dict(rust_json)
if not parsed.is_empty():
var raw_buildings: Array = parsed.get("buildings", [])
var parsed_json: JSON = JSON.new()
if parsed_json.parse(rust_json) == OK and parsed_json.data is Dictionary:
var raw_buildings: Array = (parsed_json.data as Dictionary).get("buildings", [])
buildings.clear()
for b in raw_buildings:
buildings.append(str(b))
func _parse_json_dict(json: String) -> Dictionary:
var result: Dictionary = {}
var parsed_json: JSON = JSON.new()
if parsed_json.parse(json) == OK and parsed_json.data is Dictionary:
result = parsed_json.data
return result
func _instantiate_gd_city() -> RefCounted:
if not ClassDB.class_exists("GdCity"):
_warn_missing_extension()
return null
return ClassDB.instantiate("GdCity") as RefCounted
func _warn_missing_extension() -> void:
if _warned_missing_extension:
return