class_name SimulationStateRecord extends RefCounted const SCHEMA_NAME := "the_steward.simulation" const SCHEMA_VERSION := 2 const LEGACY_SCHEMA_VERSION := 1 var simulation: Dictionary var village: VillageStateRecord var npcs: Array[NPCStateRecord] = [] var resources: Array[ResourceStateRecord] = [] var storages: Array[StorageStateRecord] = [] func to_dictionary() -> Dictionary: var npc_data: Array[Dictionary] = [] for npc_record in npcs: npc_data.append(npc_record.to_dictionary()) var resource_data: Array[Dictionary] = [] for resource_record in resources: resource_data.append(resource_record.to_dictionary()) var storage_data: Array[Dictionary] = [] for storage_record in storages: storage_data.append(storage_record.to_dictionary()) return { "schema": SCHEMA_NAME, "schema_version": SCHEMA_VERSION, "simulation": simulation.duplicate(true), "village": village.to_dictionary(), "npcs": npc_data, "resources": resource_data, "storages": storage_data } func to_json() -> String: return JSON.stringify(to_dictionary()) static func from_json(json_text: String) -> SimulationStateRecord: var parsed = JSON.parse_string(json_text) if not parsed is Dictionary: return null return from_dictionary(parsed) static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord: if record_data.get("schema", "") != SCHEMA_NAME: return null var version := int(record_data.get("schema_version", -1)) if version == LEGACY_SCHEMA_VERSION: record_data = _migrate_v1(record_data) elif version != SCHEMA_VERSION: return null if not record_data.has_all([ "simulation", "village", "npcs", "resources", "storages" ]): return null var simulation_data = record_data["simulation"] if not simulation_data is Dictionary: return null if not simulation_data.has_all([ "seed", "tick_interval", "tick_count", "clock_accumulator", "clock_elapsed_ticks", "wander_random_streams" ]): return null var wander_streams = simulation_data["wander_random_streams"] if not wander_streams is Array: return null for stream_data in wander_streams: if not stream_data is Dictionary: return null if not stream_data.has_all(["npc_id", "seed", "state"]): return null var village_data = record_data["village"] if not village_data is Dictionary: return null var village_record := VillageStateRecord.from_dictionary(village_data) if village_record == null: return null var npc_data = record_data["npcs"] var resource_data = record_data["resources"] var storage_data = record_data["storages"] if ( not npc_data is Array or not resource_data is Array or not storage_data is Array ): return null var record := SimulationStateRecord.new() record.simulation = simulation_data.duplicate(true) record.village = village_record var npc_ids := {} for item in npc_data: if not item is Dictionary: return null var npc_record := NPCStateRecord.from_dictionary(item) if npc_record == null: return null var npc_id := int(npc_record.data["id"]) if npc_ids.has(npc_id): return null npc_ids[npc_id] = true record.npcs.append(npc_record) var resource_ids := {} for item in resource_data: if not item is Dictionary: return null var resource_record := ResourceStateRecord.from_dictionary(item) if resource_record == null: return null var resource_id := String(resource_record.data["node_id"]) if resource_ids.has(resource_id): return null resource_ids[resource_id] = true record.resources.append(resource_record) var storage_ids := {} for item in storage_data: if not item is Dictionary: return null var storage_record := StorageStateRecord.from_dictionary(item) if storage_record == null: return null var storage_id := storage_record.get_storage_id() if storage_ids.has(storage_id): return null storage_ids[storage_id] = true record.storages.append(storage_record) return record static func _migrate_v1(legacy_data: Dictionary) -> Dictionary: var migrated := legacy_data.duplicate(true) migrated["schema_version"] = SCHEMA_VERSION var village_data: Dictionary = legacy_data.get("village", {}) var initial_food := float(village_data.get("food", 0.0)) migrated["storages"] = [ StorageStateRecord.create( SimulationIds.STORAGE_VILLAGE_PANTRY, {String(SimulationIds.RESOURCE_FOOD): initial_food} ).to_dictionary() ] return migrated