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 )