Files
gamedev-the-steward/simulation/regional/RegionalWorldState.gd
T
2026-08-12 21:22:04 +02:00

859 lines
29 KiB
GDScript

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()