256 lines
7.6 KiB
GDScript
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
|
|
)
|