class_name FactionStateRecord extends RefCounted signal changed(state: FactionStateRecord) const SCHEMA_VERSION := 1 var data: Dictionary func _init(record_data: Dictionary = {}) -> void: data = record_data.duplicate(true) static func _snap_food(value: float) -> float: return snappedf(value, 1.0 / 64.0) static func create( faction_id: StringName, display_name: String, food: float, warriors: int, aggression: float, morale: float ) -> FactionStateRecord: return ( FactionStateRecord . new( { "schema_version": SCHEMA_VERSION, "faction_id": String(faction_id), "display_name": display_name, "food": _snap_food(food), "warriors": warriors, "aggression": clampf(aggression, 0.0, 1.0), "morale": clampf(morale, 0.0, 1.0), "stance": String(SimulationIds.STANCE_NEUTRAL), "war_plan": String(SimulationIds.WAR_PLAN_NONE), "plan_tick": -1, "confidence": 0.0, } ) ) static func from_dictionary(record_data: Dictionary) -> FactionStateRecord: if int(record_data.get("schema_version", -1)) != SCHEMA_VERSION: return null if not ( record_data . has_all( [ "faction_id", "display_name", "food", "warriors", "aggression", "morale", "stance", "war_plan", "plan_tick", "confidence", ] ) ): return null var faction_id := String(record_data["faction_id"]) var display_name := String(record_data["display_name"]) if faction_id.is_empty() or display_name.is_empty(): return null var food := float(record_data["food"]) var warriors := int(record_data["warriors"]) var aggression := float(record_data["aggression"]) var morale := float(record_data["morale"]) var confidence := float(record_data["confidence"]) if ( not is_finite(food) or not is_finite(aggression) or not is_finite(morale) or not is_finite(confidence) or food < 0.0 or warriors < 0 or aggression < 0.0 or aggression > 1.0 or morale < 0.0 or morale > 1.0 or confidence < 0.0 or confidence > 1.0 ): return null var normalized := record_data.duplicate(true) normalized["schema_version"] = SCHEMA_VERSION normalized["faction_id"] = faction_id normalized["display_name"] = display_name normalized["food"] = _snap_food(food) normalized["warriors"] = warriors normalized["aggression"] = aggression normalized["morale"] = morale normalized["stance"] = String(record_data["stance"]) normalized["war_plan"] = String(record_data["war_plan"]) normalized["plan_tick"] = int(record_data["plan_tick"]) normalized["confidence"] = confidence return FactionStateRecord.new(normalized) func get_faction_id() -> StringName: return StringName(data["faction_id"]) func get_display_name() -> String: return String(data["display_name"]) func get_food() -> float: return maxf(float(data["food"]), 0.0) func get_warriors() -> int: return int(data["warriors"]) func get_aggression() -> float: return clampf(float(data["aggression"]), 0.0, 1.0) func get_morale() -> float: return clampf(float(data["morale"]), 0.0, 1.0) func get_stance() -> StringName: return StringName(data["stance"]) func get_war_plan() -> StringName: return StringName(data["war_plan"]) func get_plan_tick() -> int: return int(data["plan_tick"]) func get_confidence() -> float: return clampf(float(data["confidence"]), 0.0, 1.0) func set_food(value: float) -> void: var next := _snap_food(maxf(value, 0.0)) if is_equal_approx(next, get_food()): return data["food"] = next changed.emit(self) func set_morale(value: float) -> void: var next := clampf(value, 0.0, 1.0) if is_equal_approx(next, get_morale()): return data["morale"] = next changed.emit(self) func set_stance(value: StringName) -> void: if String(value) == String(get_stance()): return data["stance"] = String(value) changed.emit(self) func set_war_plan(value: StringName, current_tick: int, confidence: float = -1.0) -> void: data["war_plan"] = String(value) data["plan_tick"] = current_tick if confidence >= 0.0: data["confidence"] = clampf(confidence, 0.0, 1.0) changed.emit(self) func to_dictionary() -> Dictionary: return data.duplicate(true)