class_name RegionalWorldState extends RefCounted const SCHEMA_VERSION := 1 var _world_id: StringName var _locations: Dictionary = {} var _settlements: Dictionary = {} var _routes: Dictionary = {} var _mobile_groups: Dictionary = {} var _persons: Dictionary = {} var _cohorts: Dictionary = {} var _polities: Dictionary = {} var _diplomatic_relations: Dictionary = {} var _diplomacy_by_pair: Dictionary = {} var _route_ids_by_location: Dictionary = {} var _group_ids_by_location: Dictionary = {} var _group_ids_by_route: Dictionary = {} var _person_ids_by_location: Dictionary = {} var _person_ids_by_group: Dictionary = {} var _cohort_ids_by_location: Dictionary = {} var _cohort_ids_by_group: Dictionary = {} var _settlement_ids_by_location: Dictionary = {} var _global_entity_types: Dictionary = {} var _last_validation_errors: Array[String] = [] static func create( world_id: StringName, locations: Array[LocationStateRecord], settlements: Array[SettlementStateRecord], routes: Array[RouteStateRecord], mobile_groups: Array[MobileGroupStateRecord], persons: Array[PersonStateRecord], cohorts: Array[PopulationCohortRecord], polities: Array[PolityStateRecord], diplomatic_relations: Array[DiplomaticRelationRecord] ) -> RegionalWorldState: var state := RegionalWorldState.new() return ( state if state.replace_all( world_id, locations, settlements, routes, mobile_groups, persons, cohorts, polities, diplomatic_relations ) else null ) static func from_dictionary(record_data: Dictionary) -> RegionalWorldState: var state := RegionalWorldState.new() return state if state.restore_from_dictionary(record_data) else null func replace_all( world_id: StringName, locations: Array[LocationStateRecord], settlements: Array[SettlementStateRecord], routes: Array[RouteStateRecord], mobile_groups: Array[MobileGroupStateRecord], persons: Array[PersonStateRecord], cohorts: Array[PopulationCohortRecord], polities: Array[PolityStateRecord], diplomatic_relations: Array[DiplomaticRelationRecord] ) -> bool: var candidate := RegionalWorldState.new() candidate._world_id = world_id if world_id.is_empty(): candidate._add_error("world_id must be non-empty") for source: LocationStateRecord in locations: var record := ( LocationStateRecord.from_dictionary(source.to_dictionary()) if source != null else null ) if record == null: candidate._add_error("invalid location record") elif not candidate._insert_location(record): pass for source: SettlementStateRecord in settlements: var record := ( SettlementStateRecord.from_dictionary(source.to_dictionary()) if source != null else null ) if record == null: candidate._add_error("invalid settlement record") elif not candidate._insert_settlement(record): pass for source: RouteStateRecord in routes: var record := ( RouteStateRecord.from_dictionary(source.to_dictionary()) if source != null else null ) if record == null: candidate._add_error("invalid route record") elif not candidate._insert_route(record): pass for source: MobileGroupStateRecord in mobile_groups: var record := ( MobileGroupStateRecord.from_dictionary(source.to_dictionary()) if source != null else null ) if record == null: candidate._add_error("invalid mobile-group record") elif not candidate._insert_mobile_group(record): pass for source: PersonStateRecord in persons: var record := ( PersonStateRecord.from_dictionary(source.to_dictionary()) if source != null else null ) if record == null: candidate._add_error("invalid person record") elif not candidate._insert_person(record): pass for source: PopulationCohortRecord in cohorts: var record := ( PopulationCohortRecord.from_dictionary(source.to_dictionary()) if source != null else null ) if record == null: candidate._add_error("invalid population-cohort record") elif not candidate._insert_cohort(record): pass for source: PolityStateRecord in polities: var record := ( PolityStateRecord.from_dictionary(source.to_dictionary()) if source != null else null ) if record == null: candidate._add_error("invalid polity record") elif not candidate._insert_polity(record): pass for source: DiplomaticRelationRecord in diplomatic_relations: var record := ( DiplomaticRelationRecord.from_dictionary(source.to_dictionary()) if source != null else null ) if record == null: candidate._add_error("invalid diplomatic-relation record") elif not candidate._insert_diplomatic_relation(record): pass candidate._validate_cross_references() if not candidate._last_validation_errors.is_empty(): candidate._last_validation_errors.sort() _last_validation_errors = candidate._last_validation_errors.duplicate() return false candidate._rebuild_membership_indexes() _adopt(candidate) return true func restore_from_dictionary(record_data: Dictionary) -> bool: if int(record_data.get("schema_version", -1)) != SCHEMA_VERSION: return _reject("unsupported regional-world schema version") if not ( record_data . has_all( [ "world_id", "locations", "settlements", "routes", "mobile_groups", "persons", "population_cohorts", "polities", "diplomatic_relations", ] ) ): return _reject("regional-world save is missing required fields") for field: String in [ "locations", "settlements", "routes", "mobile_groups", "persons", "population_cohorts", "polities", "diplomatic_relations", ]: if not record_data[field] is Array: return _reject("regional-world field '%s' must be an array" % field) var locations: Array[LocationStateRecord] = [] for saved_record: Variant in record_data["locations"]: if not saved_record is Dictionary: return _reject("location entry must be a dictionary") var record := LocationStateRecord.from_dictionary(saved_record) if record == null: return _reject("invalid location entry") locations.append(record) var settlements: Array[SettlementStateRecord] = [] for saved_record: Variant in record_data["settlements"]: if not saved_record is Dictionary: return _reject("settlement entry must be a dictionary") var record := SettlementStateRecord.from_dictionary(saved_record) if record == null: return _reject("invalid settlement entry") settlements.append(record) var routes: Array[RouteStateRecord] = [] for saved_record: Variant in record_data["routes"]: if not saved_record is Dictionary: return _reject("route entry must be a dictionary") var record := RouteStateRecord.from_dictionary(saved_record) if record == null: return _reject("invalid route entry") routes.append(record) var mobile_groups: Array[MobileGroupStateRecord] = [] for saved_record: Variant in record_data["mobile_groups"]: if not saved_record is Dictionary: return _reject("mobile-group entry must be a dictionary") var record := MobileGroupStateRecord.from_dictionary(saved_record) if record == null: return _reject("invalid mobile-group entry") mobile_groups.append(record) var persons: Array[PersonStateRecord] = [] for saved_record: Variant in record_data["persons"]: if not saved_record is Dictionary: return _reject("person entry must be a dictionary") var record := PersonStateRecord.from_dictionary(saved_record) if record == null: return _reject("invalid person entry") persons.append(record) var cohorts: Array[PopulationCohortRecord] = [] for saved_record: Variant in record_data["population_cohorts"]: if not saved_record is Dictionary: return _reject("population-cohort entry must be a dictionary") var record := PopulationCohortRecord.from_dictionary(saved_record) if record == null: return _reject("invalid population-cohort entry") cohorts.append(record) var polities: Array[PolityStateRecord] = [] for saved_record: Variant in record_data["polities"]: if not saved_record is Dictionary: return _reject("polity entry must be a dictionary") var record := PolityStateRecord.from_dictionary(saved_record) if record == null: return _reject("invalid polity entry") polities.append(record) var relations: Array[DiplomaticRelationRecord] = [] for saved_record: Variant in record_data["diplomatic_relations"]: if not saved_record is Dictionary: return _reject("diplomatic-relation entry must be a dictionary") var record := DiplomaticRelationRecord.from_dictionary(saved_record) if record == null: return _reject("invalid diplomatic-relation entry") relations.append(record) return replace_all( StringName(record_data["world_id"]), locations, settlements, routes, mobile_groups, persons, cohorts, polities, relations ) func get_world_id() -> StringName: return _world_id func has_global_id(entity_id: StringName) -> bool: return _global_entity_types.has(entity_id) func get_entity_type(entity_id: StringName) -> StringName: return StringName(_global_entity_types.get(entity_id, &"")) func get_location(location_id: StringName) -> LocationStateRecord: return _locations.get(location_id) as LocationStateRecord func get_settlement(settlement_id: StringName) -> SettlementStateRecord: return _settlements.get(settlement_id) as SettlementStateRecord func get_route(route_id: StringName) -> RouteStateRecord: return _routes.get(route_id) as RouteStateRecord func get_mobile_group(group_id: StringName) -> MobileGroupStateRecord: return _mobile_groups.get(group_id) as MobileGroupStateRecord func get_person(person_id: StringName) -> PersonStateRecord: return _persons.get(person_id) as PersonStateRecord func get_population_cohort(cohort_id: StringName) -> PopulationCohortRecord: return _cohorts.get(cohort_id) as PopulationCohortRecord func get_polity(polity_id: StringName) -> PolityStateRecord: return _polities.get(polity_id) as PolityStateRecord func get_diplomatic_relation(relation_id: StringName) -> DiplomaticRelationRecord: return _diplomatic_relations.get(relation_id) as DiplomaticRelationRecord func get_diplomatic_relation_between( first_polity_id: StringName, second_polity_id: StringName ) -> DiplomaticRelationRecord: var pair_key := RegionalStateSupport.pair_key(first_polity_id, second_polity_id) var relation_id: StringName = _diplomacy_by_pair.get(pair_key, &"") return get_diplomatic_relation(relation_id) func get_all_locations() -> Array[LocationStateRecord]: var records: Array[LocationStateRecord] = [] for id: StringName in _sorted_ids(_locations): records.append(get_location(id)) return records func get_all_settlements() -> Array[SettlementStateRecord]: var records: Array[SettlementStateRecord] = [] for id: StringName in _sorted_ids(_settlements): records.append(get_settlement(id)) return records func get_all_routes() -> Array[RouteStateRecord]: var records: Array[RouteStateRecord] = [] for id: StringName in _sorted_ids(_routes): records.append(get_route(id)) return records func get_all_mobile_groups() -> Array[MobileGroupStateRecord]: var records: Array[MobileGroupStateRecord] = [] for id: StringName in _sorted_ids(_mobile_groups): records.append(get_mobile_group(id)) return records func get_all_persons() -> Array[PersonStateRecord]: var records: Array[PersonStateRecord] = [] for id: StringName in _sorted_ids(_persons): records.append(get_person(id)) return records func get_all_population_cohorts() -> Array[PopulationCohortRecord]: var records: Array[PopulationCohortRecord] = [] for id: StringName in _sorted_ids(_cohorts): records.append(get_population_cohort(id)) return records func get_all_polities() -> Array[PolityStateRecord]: var records: Array[PolityStateRecord] = [] for id: StringName in _sorted_ids(_polities): records.append(get_polity(id)) return records func get_all_diplomatic_relations() -> Array[DiplomaticRelationRecord]: var records: Array[DiplomaticRelationRecord] = [] for id: StringName in _sorted_ids(_diplomatic_relations): records.append(get_diplomatic_relation(id)) return records func get_routes_for_location(location_id: StringName) -> Array[RouteStateRecord]: var records: Array[RouteStateRecord] = [] for route_id: StringName in _index_ids(_route_ids_by_location, location_id): records.append(get_route(route_id)) return records func get_mobile_groups_at_location(location_id: StringName) -> Array[MobileGroupStateRecord]: var records: Array[MobileGroupStateRecord] = [] for group_id: StringName in _index_ids(_group_ids_by_location, location_id): records.append(get_mobile_group(group_id)) return records func get_mobile_groups_on_route(route_id: StringName) -> Array[MobileGroupStateRecord]: var records: Array[MobileGroupStateRecord] = [] for group_id: StringName in _index_ids(_group_ids_by_route, route_id): records.append(get_mobile_group(group_id)) return records func get_persons_at_location(location_id: StringName) -> Array[PersonStateRecord]: var records: Array[PersonStateRecord] = [] for person_id: StringName in _index_ids(_person_ids_by_location, location_id): records.append(get_person(person_id)) return records func get_persons_in_group(group_id: StringName) -> Array[PersonStateRecord]: var records: Array[PersonStateRecord] = [] for person_id: StringName in _index_ids(_person_ids_by_group, group_id): records.append(get_person(person_id)) return records func get_cohorts_at_location(location_id: StringName) -> Array[PopulationCohortRecord]: var records: Array[PopulationCohortRecord] = [] for cohort_id: StringName in _index_ids(_cohort_ids_by_location, location_id): records.append(get_population_cohort(cohort_id)) return records func get_cohorts_in_group(group_id: StringName) -> Array[PopulationCohortRecord]: var records: Array[PopulationCohortRecord] = [] for cohort_id: StringName in _index_ids(_cohort_ids_by_group, group_id): records.append(get_population_cohort(cohort_id)) return records func get_settlements_at_location(location_id: StringName) -> Array[SettlementStateRecord]: var records: Array[SettlementStateRecord] = [] for settlement_id: StringName in _index_ids(_settlement_ids_by_location, location_id): records.append(get_settlement(settlement_id)) return records func get_last_validation_errors() -> Array[String]: return _last_validation_errors.duplicate() func replace_settlement_and_mobile_group( settlement: SettlementStateRecord, mobile_group: MobileGroupStateRecord ) -> bool: if ( settlement == null or mobile_group == null or not _settlements.has(settlement.get_settlement_id()) or not _mobile_groups.has(mobile_group.get_group_id()) ): return false var settlements := get_all_settlements() for index in range(settlements.size()): if settlements[index].get_settlement_id() == settlement.get_settlement_id(): settlements[index] = settlement break var mobile_groups := get_all_mobile_groups() for index in range(mobile_groups.size()): if mobile_groups[index].get_group_id() == mobile_group.get_group_id(): mobile_groups[index] = mobile_group break return replace_all( _world_id, get_all_locations(), settlements, get_all_routes(), mobile_groups, get_all_persons(), get_all_population_cohorts(), get_all_polities(), get_all_diplomatic_relations() ) func to_dictionary() -> Dictionary: var locations: Array[Dictionary] = [] for id: StringName in _sorted_ids(_locations): locations.append(get_location(id).to_dictionary()) var settlements: Array[Dictionary] = [] for id: StringName in _sorted_ids(_settlements): settlements.append(get_settlement(id).to_dictionary()) var routes: Array[Dictionary] = [] for id: StringName in _sorted_ids(_routes): routes.append(get_route(id).to_dictionary()) var mobile_groups: Array[Dictionary] = [] for id: StringName in _sorted_ids(_mobile_groups): mobile_groups.append(get_mobile_group(id).to_dictionary()) var persons: Array[Dictionary] = [] for id: StringName in _sorted_ids(_persons): persons.append(get_person(id).to_dictionary()) var cohorts: Array[Dictionary] = [] for id: StringName in _sorted_ids(_cohorts): cohorts.append(get_population_cohort(id).to_dictionary()) var polities: Array[Dictionary] = [] for id: StringName in _sorted_ids(_polities): polities.append(get_polity(id).to_dictionary()) var diplomatic_relations: Array[Dictionary] = [] for id: StringName in _sorted_ids(_diplomatic_relations): diplomatic_relations.append(get_diplomatic_relation(id).to_dictionary()) return { "schema_version": SCHEMA_VERSION, "world_id": String(_world_id), "locations": locations, "settlements": settlements, "routes": routes, "mobile_groups": mobile_groups, "persons": persons, "population_cohorts": cohorts, "polities": polities, "diplomatic_relations": diplomatic_relations, } func checksum() -> String: return JSON.stringify(to_dictionary()).sha256_text() func _insert_location(record: LocationStateRecord) -> bool: var id := record.get_location_id() if not _claim_global_id(id, &"location"): return false _locations[id] = record return true func _insert_settlement(record: SettlementStateRecord) -> bool: var id := record.get_settlement_id() if not _claim_global_id(id, &"settlement"): return false _settlements[id] = record return true func _insert_route(record: RouteStateRecord) -> bool: var id := record.get_route_id() if not _claim_global_id(id, &"route"): return false _routes[id] = record return true func _insert_mobile_group(record: MobileGroupStateRecord) -> bool: var id := record.get_group_id() if not _claim_global_id(id, &"mobile_group"): return false _mobile_groups[id] = record return true func _insert_person(record: PersonStateRecord) -> bool: var id := record.get_person_id() if not _claim_global_id(id, &"person"): return false _persons[id] = record return true func _insert_cohort(record: PopulationCohortRecord) -> bool: var id := record.get_cohort_id() if not _claim_global_id(id, &"population_cohort"): return false _cohorts[id] = record return true func _insert_polity(record: PolityStateRecord) -> bool: var id := record.get_polity_id() if not _claim_global_id(id, &"polity"): return false _polities[id] = record return true func _insert_diplomatic_relation(record: DiplomaticRelationRecord) -> bool: var id := record.get_relation_id() if not _claim_global_id(id, &"diplomatic_relation"): return false var pair_key := record.get_pair_key() if _diplomacy_by_pair.has(pair_key): _add_error("duplicate diplomatic relation for pair '%s'" % pair_key) return false _diplomatic_relations[id] = record _diplomacy_by_pair[pair_key] = id return true func _claim_global_id(id: StringName, entity_type: StringName) -> bool: if _global_entity_types.has(id): _add_error( "duplicate global ID '%s' (%s and %s)" % [id, _global_entity_types[id], entity_type] ) return false _global_entity_types[id] = entity_type return true func _validate_cross_references() -> void: for location: LocationStateRecord in _locations.values(): if location.get_world_id() != _world_id: _add_error("location '%s' belongs to another world" % location.get_location_id()) for settlement: SettlementStateRecord in _settlements.values(): _require_id( _locations, settlement.get_location_id(), "settlement location", settlement.get_settlement_id() ) _require_id( _polities, settlement.get_polity_id(), "settlement polity", settlement.get_settlement_id() ) for route: RouteStateRecord in _routes.values(): _require_id( _locations, route.get_from_location_id(), "route endpoint", route.get_route_id() ) _require_id(_locations, route.get_to_location_id(), "route endpoint", route.get_route_id()) for group: MobileGroupStateRecord in _mobile_groups.values(): _validate_mobile_group(group) for person: PersonStateRecord in _persons.values(): _validate_person(person) for cohort: PopulationCohortRecord in _cohorts.values(): _validate_cohort(cohort) for polity: PolityStateRecord in _polities.values(): _validate_polity(polity) for relation: DiplomaticRelationRecord in _diplomatic_relations.values(): _require_id( _polities, relation.get_first_polity_id(), "diplomatic polity", relation.get_relation_id() ) _require_id( _polities, relation.get_second_polity_id(), "diplomatic polity", relation.get_relation_id() ) _validate_group_membership_ownership() func _validate_mobile_group(group: MobileGroupStateRecord) -> void: _require_id(_polities, group.get_polity_id(), "mobile-group polity", group.get_group_id()) if group.is_at_location(): var address := group.get_location_address() if address == null or not _locations.has(address.get_location_id()): _add_error("mobile group '%s' has an orphan location" % group.get_group_id()) elif not RegionalStateSupport.address_matches( group.data["location_address"], _world_id, address.get_location_id() ): _add_error( "mobile group '%s' has a cross-world location address" % group.get_group_id() ) return var route := get_route(group.get_route_id()) if route == null: _add_error("mobile group '%s' has an orphan route" % group.get_group_id()) return if not route.connects(group.get_departure_location_id(), group.get_arrival_location_id()): _add_error( "mobile group '%s' uses invalid route direction/endpoints" % group.get_group_id() ) if group.get_arrival_tick() - group.get_departure_tick() != route.get_travel_ticks(): _add_error("mobile group '%s' journey duration does not match route" % group.get_group_id()) func _validate_person(person: PersonStateRecord) -> void: _require_id(_polities, person.get_polity_id(), "person polity", person.get_person_id()) _require_id(_locations, person.get_home_location_id(), "person home", person.get_person_id()) if person.is_at_location(): var address := person.get_location_address() if address == null or not _locations.has(address.get_location_id()): _add_error("person '%s' has an orphan location" % person.get_person_id()) elif not RegionalStateSupport.address_matches( person.data["location_address"], _world_id, address.get_location_id() ): _add_error("person '%s' has a cross-world location address" % person.get_person_id()) else: _require_id( _mobile_groups, person.get_mobile_group_id(), "person mobile group", person.get_person_id() ) func _validate_cohort(cohort: PopulationCohortRecord) -> void: _require_id(_polities, cohort.get_polity_id(), "cohort polity", cohort.get_cohort_id()) if cohort.is_at_location(): _require_id(_locations, cohort.get_location_id(), "cohort location", cohort.get_cohort_id()) else: _require_id( _mobile_groups, cohort.get_mobile_group_id(), "cohort mobile group", cohort.get_cohort_id() ) func _validate_polity(polity: PolityStateRecord) -> void: _require_id( _locations, polity.get_capital_location_id(), "polity capital", polity.get_polity_id() ) for settlement_id: StringName in polity.get_settlement_ids(): var settlement := get_settlement(settlement_id) if settlement == null: _add_error( "polity '%s' lists orphan settlement '%s'" % [polity.get_polity_id(), settlement_id] ) elif settlement.get_polity_id() != polity.get_polity_id(): _add_error("settlement '%s' belongs to another polity" % settlement_id) for settlement: SettlementStateRecord in _settlements.values(): if ( settlement.get_polity_id() == polity.get_polity_id() and settlement.get_settlement_id() not in polity.get_settlement_ids() ): _add_error( ( "polity '%s' omits settlement '%s'" % [polity.get_polity_id(), settlement.get_settlement_id()] ) ) func _validate_group_membership_ownership() -> void: var person_owner: Dictionary = {} var cohort_owner: Dictionary = {} for group: MobileGroupStateRecord in _mobile_groups.values(): for person_id: StringName in group.get_detailed_member_ids(): var person := get_person(person_id) if person == null: _add_error( "mobile group '%s' lists orphan person '%s'" % [group.get_group_id(), person_id] ) continue if person_owner.has(person_id): _add_error("person '%s' is listed by multiple mobile groups" % person_id) person_owner[person_id] = group.get_group_id() if person.get_mobile_group_id() != group.get_group_id(): _add_error("person '%s' has non-reciprocal mobile-group membership" % person_id) if person.get_polity_id() != group.get_polity_id(): _add_error("person '%s' and mobile group have different polities" % person_id) for cohort_key: Variant in group.get_cohort_counts(): var cohort_id := StringName(cohort_key) var cohort := get_population_cohort(cohort_id) if cohort == null: _add_error( "mobile group '%s' lists orphan cohort '%s'" % [group.get_group_id(), cohort_id] ) continue if cohort_owner.has(cohort_id): _add_error("cohort '%s' is listed by multiple mobile groups" % cohort_id) cohort_owner[cohort_id] = group.get_group_id() if cohort.get_mobile_group_id() != group.get_group_id(): _add_error("cohort '%s' has non-reciprocal mobile-group membership" % cohort_id) if int(group.get_cohort_counts()[cohort_key]) != cohort.get_count(): _add_error("cohort '%s' count disagrees with its mobile group" % cohort_id) if cohort.get_polity_id() != group.get_polity_id(): _add_error("cohort '%s' and mobile group have different polities" % cohort_id) for person: PersonStateRecord in _persons.values(): if person.is_in_mobile_group() and not person_owner.has(person.get_person_id()): _add_error("person '%s' is omitted by its mobile group" % person.get_person_id()) for cohort: PopulationCohortRecord in _cohorts.values(): if cohort.is_in_mobile_group() and not cohort_owner.has(cohort.get_cohort_id()): _add_error("cohort '%s' is omitted by its mobile group" % cohort.get_cohort_id()) func _rebuild_membership_indexes() -> void: for route: RouteStateRecord in _routes.values(): _index_append(_route_ids_by_location, route.get_from_location_id(), route.get_route_id()) _index_append(_route_ids_by_location, route.get_to_location_id(), route.get_route_id()) for settlement: SettlementStateRecord in _settlements.values(): _index_append( _settlement_ids_by_location, settlement.get_location_id(), settlement.get_settlement_id() ) for group: MobileGroupStateRecord in _mobile_groups.values(): if group.is_at_location(): _index_append( _group_ids_by_location, group.get_current_location_id(), group.get_group_id() ) else: _index_append(_group_ids_by_route, group.get_route_id(), group.get_group_id()) for person: PersonStateRecord in _persons.values(): if person.is_at_location(): _index_append( _person_ids_by_location, person.get_location_address().get_location_id(), person.get_person_id() ) else: _index_append( _person_ids_by_group, person.get_mobile_group_id(), person.get_person_id() ) for cohort: PopulationCohortRecord in _cohorts.values(): if cohort.is_at_location(): _index_append(_cohort_ids_by_location, cohort.get_location_id(), cohort.get_cohort_id()) else: _index_append( _cohort_ids_by_group, cohort.get_mobile_group_id(), cohort.get_cohort_id() ) func _require_id(index: Dictionary, id: StringName, label: String, owner_id: StringName) -> void: if not index.has(id): _add_error("%s '%s' references missing ID '%s'" % [label, owner_id, id]) func _index_append(index: Dictionary, key: StringName, id: StringName) -> void: var ids: Array[StringName] = index.get(key, [] as Array[StringName]) ids.append(id) ids.sort_custom(_compare_ids) index[key] = ids func _index_ids(index: Dictionary, key: StringName) -> Array[StringName]: var ids: Array[StringName] = index.get(key, [] as Array[StringName]) return ids.duplicate() func _sorted_ids(index: Dictionary) -> Array[StringName]: var ids: Array[StringName] = [] for id: Variant in index: ids.append(StringName(id)) ids.sort_custom(_compare_ids) return ids static func _compare_ids(first: StringName, second: StringName) -> bool: return String(first) < String(second) func _reject(message: String) -> bool: _last_validation_errors = [message] return false func _add_error(message: String) -> void: _last_validation_errors.append(message) func _adopt(source: RegionalWorldState) -> void: _world_id = source._world_id _locations = source._locations _settlements = source._settlements _routes = source._routes _mobile_groups = source._mobile_groups _persons = source._persons _cohorts = source._cohorts _polities = source._polities _diplomatic_relations = source._diplomatic_relations _diplomacy_by_pair = source._diplomacy_by_pair _route_ids_by_location = source._route_ids_by_location _group_ids_by_location = source._group_ids_by_location _group_ids_by_route = source._group_ids_by_route _person_ids_by_location = source._person_ids_by_location _person_ids_by_group = source._person_ids_by_group _cohort_ids_by_location = source._cohort_ids_by_location _cohort_ids_by_group = source._cohort_ids_by_group _settlement_ids_by_location = source._settlement_ids_by_location _global_entity_types = source._global_entity_types _last_validation_errors.clear()