class_name KnownEventStateRecord extends RefCounted const SCHEMA_VERSION := 4 const LEGACY_SCHEMA_VERSION := 3 const NO_SOURCE_NPC_ID := -1 const NO_SOURCE_ACTOR_ID := -2 const DEFAULT_CONFIDENCE := 1.0 const DEFAULT_SALIENCE := 0.5 const AUTO_HOP_COUNT := -1 const VALID_ACQUISITION_METHODS := [ SimulationIds.KNOWLEDGE_ACQUISITION_PERFORMED, SimulationIds.KNOWLEDGE_ACQUISITION_WITNESSED, SimulationIds.KNOWLEDGE_ACQUISITION_COMMUNICATED, SimulationIds.KNOWLEDGE_ACQUISITION_LEGACY, ] const DIRECT_ACQUISITION_METHODS := [ SimulationIds.KNOWLEDGE_ACQUISITION_PERFORMED, SimulationIds.KNOWLEDGE_ACQUISITION_WITNESSED, ] var data: Dictionary func _init(record_data: Dictionary = {}) -> void: data = record_data.duplicate(true) static func create( knower_id: int, event_id: int, acquisition_method: StringName = SimulationIds.KNOWLEDGE_ACQUISITION_LEGACY, source_npc_id: int = NO_SOURCE_NPC_ID, acquired_tick: int = 0, source_acquisition_method: StringName = &"", hop_count: int = AUTO_HOP_COUNT, confidence: float = DEFAULT_CONFIDENCE, salience: float = DEFAULT_SALIENCE, source_actor_id: int = NO_SOURCE_ACTOR_ID, pinned: bool = false ) -> KnownEventStateRecord: var is_communicated := acquisition_method == SimulationIds.KNOWLEDGE_ACQUISITION_COMMUNICATED var canonical_source_actor_id := source_actor_id var canonical_source_npc_id := source_npc_id if is_communicated: if canonical_source_actor_id == NO_SOURCE_ACTOR_ID: canonical_source_actor_id = source_npc_id canonical_source_npc_id = ( canonical_source_actor_id if canonical_source_actor_id >= 0 else NO_SOURCE_NPC_ID ) else: canonical_source_actor_id = NO_SOURCE_ACTOR_ID canonical_source_npc_id = NO_SOURCE_NPC_ID var canonical_hop_count := hop_count if canonical_hop_count == AUTO_HOP_COUNT: canonical_hop_count = 1 if is_communicated else 0 return ( KnownEventStateRecord . new( { "schema_version": SCHEMA_VERSION, "knower_id": knower_id, "event_id": event_id, "acquisition_method": String(acquisition_method), "source_npc_id": canonical_source_npc_id, "source_actor_id": canonical_source_actor_id, "acquired_tick": acquired_tick, "source_acquisition_method": String(source_acquisition_method), "hop_count": canonical_hop_count, "confidence": clampf(confidence, 0.0, 1.0), "salience": clampf(salience, 0.0, 1.0), "pinned": pinned, } ) ) static func from_dictionary(record_data: Dictionary) -> KnownEventStateRecord: var version := int(record_data.get("schema_version", -1)) var normalized := record_data.duplicate(true) if version == LEGACY_SCHEMA_VERSION: normalized = _migrate_v3(record_data) elif version != SCHEMA_VERSION: return null if not ( normalized . has_all( [ "knower_id", "event_id", "acquisition_method", "source_npc_id", "source_actor_id", "acquired_tick", "source_acquisition_method", "hop_count", "confidence", "salience", "pinned", ] ) ): return null var knower_id := int(normalized["knower_id"]) var event_id := int(normalized["event_id"]) var acquisition_method := StringName(normalized["acquisition_method"]) var source_npc_id := int(normalized["source_npc_id"]) var source_actor_id := int(normalized["source_actor_id"]) var acquired_tick := int(normalized["acquired_tick"]) var source_acquisition_method := StringName(normalized["source_acquisition_method"]) var hop_count := int(normalized["hop_count"]) var confidence := float(normalized["confidence"]) var salience := float(normalized["salience"]) if not is_valid_actor_id(knower_id) or event_id < 0 or acquired_tick < 0 or hop_count < 0: return null if not is_finite(confidence) or confidence < 0.0 or confidence > 1.0: return null if not is_finite(salience) or salience < 0.0 or salience > 1.0: return null if typeof(normalized["pinned"]) != TYPE_BOOL: return null if acquisition_method not in VALID_ACQUISITION_METHODS: return null if acquisition_method == SimulationIds.KNOWLEDGE_ACQUISITION_COMMUNICATED: if not is_valid_actor_id(source_actor_id) or source_actor_id == knower_id: return null if source_actor_id >= 0 and source_npc_id != source_actor_id: return null if source_actor_id == SimulationIds.PLAYER_ACTOR_ID and source_npc_id != NO_SOURCE_NPC_ID: return null if source_acquisition_method not in DIRECT_ACQUISITION_METHODS or hop_count != 1: return null elif ( source_npc_id != NO_SOURCE_NPC_ID or source_actor_id != NO_SOURCE_ACTOR_ID or not source_acquisition_method.is_empty() or hop_count != 0 ): return null return KnownEventStateRecord.new(normalized) func get_knower_id() -> int: return int(data["knower_id"]) func get_event_id() -> int: return int(data["event_id"]) func get_acquisition_method() -> StringName: return StringName(data["acquisition_method"]) func get_source_npc_id() -> int: return int(data["source_npc_id"]) func get_source_actor_id() -> int: return int(data["source_actor_id"]) func get_acquired_tick() -> int: return int(data["acquired_tick"]) func get_source_acquisition_method() -> StringName: return StringName(data["source_acquisition_method"]) func get_hop_count() -> int: return int(data["hop_count"]) func get_confidence() -> float: return float(data["confidence"]) func get_salience() -> float: return float(data["salience"]) func is_pinned() -> bool: return bool(data["pinned"]) func set_pinned(pinned: bool) -> bool: if is_pinned() == pinned: return false data["pinned"] = pinned return true func set_confidence(confidence: float) -> bool: if not is_finite(confidence): return false var bounded := clampf(confidence, 0.0, 1.0) if is_equal_approx(get_confidence(), bounded): return false data["confidence"] = bounded return true func set_salience(salience: float) -> bool: if not is_finite(salience): return false var bounded := clampf(salience, 0.0, 1.0) if is_equal_approx(get_salience(), bounded): return false data["salience"] = bounded return true func to_dictionary() -> Dictionary: return data.duplicate(true) static func is_valid_actor_id(actor_id: int) -> bool: return actor_id >= 0 or actor_id == SimulationIds.PLAYER_ACTOR_ID static func _migrate_v3(record_data: Dictionary) -> Dictionary: var migrated := record_data.duplicate(true) var acquisition_method := StringName(migrated.get("acquisition_method", &"")) var is_communicated := acquisition_method == SimulationIds.KNOWLEDGE_ACQUISITION_COMMUNICATED migrated["schema_version"] = SCHEMA_VERSION migrated["source_actor_id"] = ( int(migrated.get("source_npc_id", NO_SOURCE_NPC_ID)) if is_communicated else NO_SOURCE_ACTOR_ID ) migrated["hop_count"] = 1 if is_communicated else 0 migrated["confidence"] = DEFAULT_CONFIDENCE migrated["salience"] = DEFAULT_SALIENCE migrated["pinned"] = false return migrated