|
|
|
@@ -0,0 +1,858 @@
|
|
|
|
|
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()
|