class_name SimulationStateRecord extends RefCounted const SCHEMA_NAME := "the_steward.simulation" const SCHEMA_VERSION := 5 const LEGACY_SCHEMA_VERSION := 1 const EVENT_LEGACY_SCHEMA_VERSION := 2 const RELATIONSHIP_LEGACY_SCHEMA_VERSION := 3 const PREVIOUS_SCHEMA_VERSION := 4 var simulation: Dictionary var village: VillageStateRecord var npcs: Array[NPCStateRecord] = [] var resources: Array[ResourceStateRecord] = [] var storages: Array[StorageStateRecord] = [] var economic_events: Array[EconomicEventRecord] = [] var relationships: Array[RelationshipStateRecord] = [] var event_knowledge: Array[KnownEventStateRecord] = [] 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()) var event_data: Array[Dictionary] = [] for event_record in economic_events: event_data.append(event_record.to_dictionary()) var relationship_data: Array[Dictionary] = [] for relationship_record in relationships: relationship_data.append(relationship_record.to_dictionary()) var knowledge_data: Array[Dictionary] = [] for known_event_record in event_knowledge: knowledge_data.append(known_event_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, "economic_events": event_data, "relationships": relationship_data, "event_knowledge": knowledge_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 in [ LEGACY_SCHEMA_VERSION, EVENT_LEGACY_SCHEMA_VERSION, RELATIONSHIP_LEGACY_SCHEMA_VERSION, PREVIOUS_SCHEMA_VERSION, ] ): record_data = _migrate_legacy(record_data, version) elif version != SCHEMA_VERSION: return null if not ( record_data . has_all( [ "simulation", "village", "npcs", "resources", "storages", "economic_events", "relationships", "event_knowledge", ] ) ): 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", "next_event_id" ] ): 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"] var event_data = record_data["economic_events"] var relationship_data = record_data["relationships"] var knowledge_data = record_data["event_knowledge"] if ( not npc_data is Array or not resource_data is Array or not storage_data is Array or not event_data is Array or not relationship_data is Array or not knowledge_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) var event_ids := {} var event_records_by_id := {} var highest_event_id := -1 for item in event_data: if not item is Dictionary: return null var event_record := EconomicEventRecord.from_dictionary(item) if event_record == null: return null var event_id := int(event_record.data["event_id"]) if event_ids.has(event_id): return null event_ids[event_id] = true event_records_by_id[event_id] = event_record highest_event_id = maxi(highest_event_id, event_id) record.economic_events.append(event_record) if int(record.simulation["next_event_id"]) <= highest_event_id: return null var knowledge_keys := {} for item in knowledge_data: if not item is Dictionary: return null var known_event_record := KnownEventStateRecord.from_dictionary(item) if known_event_record == null: return null var knower_id := known_event_record.get_knower_id() var known_event_id := known_event_record.get_event_id() if not npc_ids.has(knower_id) or not event_ids.has(known_event_id): return null var knowledge_key := "%d:%d" % [knower_id, known_event_id] if knowledge_keys.has(knowledge_key): return null knowledge_keys[knowledge_key] = true record.event_knowledge.append(known_event_record) var relationship_keys := {} for item in relationship_data: if not item is Dictionary: return null var relationship_record := RelationshipStateRecord.from_dictionary(item) if relationship_record == null: return null var observer_id := relationship_record.get_observer_id() var subject_id := relationship_record.get_subject_id() if not npc_ids.has(observer_id) or not npc_ids.has(subject_id): return null var relationship_key := "%d:%d" % [observer_id, subject_id] if relationship_keys.has(relationship_key): return null var cause_event_id := relationship_record.get_last_trust_cause_event_id() if ( cause_event_id != RelationshipStateRecord.NO_CAUSE_EVENT and not event_ids.has(cause_event_id) ): return null if ( cause_event_id != RelationshipStateRecord.NO_CAUSE_EVENT and not knowledge_keys.has("%d:%d" % [observer_id, cause_event_id]) ): return null if cause_event_id != RelationshipStateRecord.NO_CAUSE_EVENT: var cause_event := event_records_by_id[cause_event_id] as EconomicEventRecord if ( int(cause_event.data["actor_id"]) != subject_id or ( StringName(cause_event.data["event_type"]) != SimulationIds.EVENT_STORAGE_DEPOSITED ) or StringName(cause_event.data["item_id"]) != SimulationIds.RESOURCE_FOOD or float(cause_event.data["amount"]) <= 0.0 ): return null relationship_keys[relationship_key] = true record.relationships.append(relationship_record) return record static func _migrate_legacy(legacy_data: Dictionary, version: int) -> Dictionary: var migrated := legacy_data.duplicate(true) migrated["schema_version"] = SCHEMA_VERSION if version == LEGACY_SCHEMA_VERSION: var village_data: Dictionary = legacy_data.get("village", {}) var initial_food := float(village_data.get("food", 0.0)) var initial_wood := float(village_data.get("wood", 0.0)) migrated["storages"] = [ ( StorageStateRecord . create( SimulationIds.STORAGE_VILLAGE_PANTRY, {String(SimulationIds.RESOURCE_FOOD): initial_food} ) . to_dictionary() ), ( StorageStateRecord . create( SimulationIds.STORAGE_VILLAGE_WOODPILE, {String(SimulationIds.RESOURCE_WOOD): initial_wood} ) . to_dictionary() ) ] if version in [LEGACY_SCHEMA_VERSION, EVENT_LEGACY_SCHEMA_VERSION]: migrated["economic_events"] = [] var simulation_data: Dictionary = migrated.get("simulation", {}) simulation_data["next_event_id"] = 0 migrated["simulation"] = simulation_data if ( version in [LEGACY_SCHEMA_VERSION, EVENT_LEGACY_SCHEMA_VERSION, RELATIONSHIP_LEGACY_SCHEMA_VERSION] ): migrated["relationships"] = _migrate_npc_familiarity(legacy_data.get("npcs", [])) migrated["event_knowledge"] = _migrate_relationship_causes(migrated.get("relationships", [])) return migrated static func _migrate_npc_familiarity(npc_data: Variant) -> Array[Dictionary]: var migrated_relationships: Array[Dictionary] = [] if not npc_data is Array: return migrated_relationships for npc_item in npc_data: if not npc_item is Dictionary: continue var observer_id := int(npc_item.get("id", -1)) var familiarity = npc_item.get("familiarity", []) if familiarity is Array: for pair in familiarity: if not pair is Dictionary: continue migrated_relationships.append( ( RelationshipStateRecord . create( observer_id, int(pair.get("id", -1)), float(pair.get("score", 0.0)) ) . to_dictionary() ) ) elif familiarity is Dictionary: for subject_id in familiarity: migrated_relationships.append( ( RelationshipStateRecord . create(observer_id, int(subject_id), float(familiarity[subject_id])) . to_dictionary() ) ) migrated_relationships.sort_custom(_relationship_dictionary_sort) return migrated_relationships static func _relationship_dictionary_sort(first: Dictionary, second: Dictionary) -> bool: if int(first["observer_id"]) != int(second["observer_id"]): return int(first["observer_id"]) < int(second["observer_id"]) return int(first["subject_id"]) < int(second["subject_id"]) static func _migrate_relationship_causes(relationship_data: Variant) -> Array[Dictionary]: var migrated_knowledge: Array[Dictionary] = [] var known_keys := {} if not relationship_data is Array: return migrated_knowledge for relationship_item in relationship_data: if not relationship_item is Dictionary: continue var observer_id := int(relationship_item.get("observer_id", -1)) var cause_event_id := int( relationship_item.get( "last_trust_cause_event_id", RelationshipStateRecord.NO_CAUSE_EVENT ) ) if observer_id < 0 or cause_event_id < 0: continue var knowledge_key := "%d:%d" % [observer_id, cause_event_id] if known_keys.has(knowledge_key): continue known_keys[knowledge_key] = true migrated_knowledge.append( KnownEventStateRecord.create(observer_id, cause_event_id).to_dictionary() ) migrated_knowledge.sort_custom(_knowledge_dictionary_sort) return migrated_knowledge static func _knowledge_dictionary_sort(first: Dictionary, second: Dictionary) -> bool: if int(first["knower_id"]) != int(second["knower_id"]): return int(first["knower_id"]) < int(second["knower_id"]) return int(first["event_id"]) < int(second["event_id"])