Files
gamedev-the-steward/simulation/state/validation/EmergentStateValidator.gd
T
2026-08-12 21:15:58 +02:00

256 lines
7.6 KiB
GDScript

class_name EmergentStateValidator
extends RefCounted
static func parse(
situation_data: Array,
journal_data: Array,
commitment_data: Array,
simulation_data: Dictionary,
npc_ids: Dictionary,
event_ids: Dictionary
) -> Variant:
var current_tick := int(simulation_data["tick_count"])
var next_situation_id := int(simulation_data["next_situation_id"])
var next_journal_entry_id := int(simulation_data["next_journal_entry_id"])
var next_commitment_id := int(simulation_data["next_commitment_id"])
var situation_system := SituationSystem.create_default()
var situations_by_id: Dictionary = {}
var situations: Array[SituationStateRecord] = []
if not _parse_situations(
situation_data,
current_tick,
next_situation_id,
npc_ids,
event_ids,
situation_system,
situations_by_id,
situations
):
return null
var journal: Array[QuestJournalEntryStateRecord] = []
if not _parse_journal(
journal_data,
current_tick,
next_journal_entry_id,
situation_system,
situations_by_id,
journal
):
return null
var commitments: Array[CommitmentStateRecord] = []
if not _parse_commitments(
commitment_data,
current_tick,
next_commitment_id,
npc_ids,
event_ids,
situation_system,
situations_by_id,
commitments
):
return null
return {
"situations": situations,
"quest_journal": journal,
"commitments": commitments,
}
static func _parse_situations(
serialized: Array,
current_tick: int,
next_id: int,
npc_ids: Dictionary,
event_ids: Dictionary,
system: SituationSystem,
by_id: Dictionary,
result: Array[SituationStateRecord]
) -> bool:
var highest_id := -1
for item in serialized:
if not item is Dictionary:
return false
var record := SituationStateRecord.from_dictionary(item)
if record == null:
return false
var situation_id := record.get_situation_id()
var definition := system.get_definition(record.get_definition_id())
if by_id.has(situation_id) or definition == null:
return false
if (
record.get_created_tick() > current_tick
or (not record.is_active() and record.get_closed_tick() > current_tick)
):
return false
var trigger_event_id := record.get_trigger_event_id()
if (
trigger_event_id != SituationStateRecord.NO_EVENT_ID
and not event_ids.has(trigger_event_id)
):
return false
var resolution_event_id := record.get_resolution_event_id()
if (
resolution_event_id != SituationStateRecord.NO_EVENT_ID
and (not event_ids.has(resolution_event_id) or resolution_event_id <= trigger_event_id)
):
return false
var alternative_id := record.get_selected_alternative_id()
if not alternative_id.is_empty() and definition.get_alternative(alternative_id) == null:
return false
var context := record.get_context()
if context.has("interested_npc_id"):
var interested_id_text := String(context["interested_npc_id"])
if (
not interested_id_text.is_valid_int()
or not npc_ids.has(interested_id_text.to_int())
):
return false
by_id[situation_id] = record
highest_id = maxi(highest_id, situation_id)
result.append(record)
if next_id <= highest_id:
return false
return system.restore(result, next_id)
static func _parse_journal(
serialized: Array,
current_tick: int,
next_id: int,
situation_system: SituationSystem,
situations_by_id: Dictionary,
result: Array[QuestJournalEntryStateRecord]
) -> bool:
var ids: Dictionary = {}
var situation_ids: Dictionary = {}
var highest_id := -1
for item in serialized:
if not item is Dictionary:
return false
var record := QuestJournalEntryStateRecord.from_dictionary(item)
if record == null:
return false
var entry_id := record.get_entry_id()
var situation := situations_by_id.get(record.get_situation_id()) as SituationStateRecord
if (
ids.has(entry_id)
or situation_ids.has(record.get_situation_id())
or situation == null
or record.get_definition_id() != situation.get_definition_id()
or record.get_discovered_tick() < situation.get_created_tick()
or record.get_discovered_tick() > current_tick
or (not record.is_active() and record.get_closed_tick() > current_tick)
):
return false
var definition := situation_system.get_definition(record.get_definition_id())
var alternative_id := record.get_selected_alternative_id()
if (
not alternative_id.is_empty()
and (
definition == null
or definition.get_alternative(alternative_id) == null
or situation.get_selected_alternative_id() != alternative_id
)
):
return false
ids[entry_id] = true
situation_ids[record.get_situation_id()] = true
highest_id = maxi(highest_id, entry_id)
result.append(record)
if next_id <= highest_id:
return false
return QuestJournalSystem.new().restore(result, next_id)
static func _parse_commitments(
serialized: Array,
current_tick: int,
next_id: int,
npc_ids: Dictionary,
event_ids: Dictionary,
situation_system: SituationSystem,
situations_by_id: Dictionary,
result: Array[CommitmentStateRecord]
) -> bool:
var ids: Dictionary = {}
var highest_id := -1
for item in serialized:
if not item is Dictionary:
return false
var record := CommitmentStateRecord.from_dictionary(item)
if record == null:
return false
var commitment_id := record.get_commitment_id()
var situation := situations_by_id.get(record.get_situation_id()) as SituationStateRecord
if (
ids.has(commitment_id)
or situation == null
or record.get_created_tick() < situation.get_created_tick()
or record.get_created_tick() > current_tick
or (not record.is_active() and record.get_closed_tick() > current_tick)
or not is_valid_actor(record.get_debtor(), npc_ids)
or not is_valid_actor(record.get_creditor(), npc_ids)
):
return false
var definition := situation_system.get_definition(situation.get_definition_id())
var alternative: SituationAlternativeDefinition
if definition != null:
alternative = definition.get_alternative(situation.get_selected_alternative_id())
if alternative == null or alternative.commitment_terms != record.get_terms():
return false
var cause_event_id := record.get_cause_event_id()
if (
cause_event_id != CommitmentStateRecord.NO_EVENT_ID
and not event_ids.has(cause_event_id)
):
return false
ids[commitment_id] = true
highest_id = maxi(highest_id, commitment_id)
result.append(record)
if next_id <= highest_id:
return false
return SocialCommitmentSystem.new().restore(result, next_id)
static func is_valid_actor(entity: WorldEntityRef, npc_ids: Dictionary) -> bool:
if entity == null or not entity.is_valid():
return false
if entity.get_entity_type() == WorldEventRecord.ENTITY_TYPE_PLAYER:
return String(entity.get_entity_id()) == str(SimulationIds.PLAYER_ACTOR_ID)
if entity.get_entity_type() != WorldEventRecord.ENTITY_TYPE_NPC:
return false
var entity_id_text := String(entity.get_entity_id())
return entity_id_text.is_valid_int() and npc_ids.has(entity_id_text.to_int())
static func is_valid_commitment_relationship_cause(
cause_event: EconomicEventRecord, subject_id: int
) -> bool:
if cause_event == null:
return false
if (
StringName(cause_event.data["event_type"])
not in [
SimulationIds.EVENT_COMMITMENT_FULFILLED,
SimulationIds.EVENT_COMMITMENT_BROKEN,
]
):
return false
var debtor_data = cause_event.data.get("debtor")
if not debtor_data is Dictionary:
return false
var debtor := WorldEntityRef.from_dictionary(debtor_data)
if debtor == null:
return false
var expected_type := (
WorldEventRecord.ENTITY_TYPE_PLAYER
if subject_id == SimulationIds.PLAYER_ACTOR_ID
else WorldEventRecord.ENTITY_TYPE_NPC
)
return (
debtor.get_entity_type() == expected_type
and String(debtor.get_entity_id()) == str(subject_id)
and int(cause_event.data["actor_id"]) == subject_id
)