class_name SocialCommitmentSystem extends RefCounted const REASON_FACT_FULFILLED := &"world_fact_fulfilled" const REASON_FACT_RESOLVED_BY_OTHER := &"world_fact_resolved_by_other" const REASON_DEADLINE_MISSED := &"deadline_missed" const REASON_SITUATION_EXPIRED := &"situation_expired" const REASON_SITUATION_SUPERSEDED := &"situation_superseded" var _commitments_by_id: Dictionary = {} var _active_contract_keys: Dictionary = {} var _next_commitment_id := 0 func create_for_alternative( situation: SituationStateRecord, definition: SituationDefinition, alternative_id: StringName, debtor: WorldEntityRef, creditor: WorldEntityRef, current_tick: int, deadline_tick: int = -1 ) -> CommitmentStateRecord: if ( situation == null or definition == null or not situation.is_active() or situation.get_definition_id() != definition.situation_definition_id or current_tick < situation.get_created_tick() ): return null var alternative := definition.get_alternative(alternative_id) if alternative == null or alternative.commitment_terms.is_empty(): return null var contract_key := _contract_key(situation.get_situation_id(), debtor, creditor) if contract_key.is_empty() or _active_contract_keys.has(contract_key): return null if not situation.select_alternative(alternative_id): return null var commitment := CommitmentStateRecord.create( _next_commitment_id, situation.get_situation_id(), debtor, creditor, alternative.commitment_terms, current_tick, deadline_tick ) if commitment == null: return null _commitments_by_id[_next_commitment_id] = commitment _active_contract_keys[contract_key] = _next_commitment_id _next_commitment_id += 1 return commitment func maintain( current_tick: int, situation_system: SituationSystem, world_events: WorldEventStore, state_facts: Dictionary ) -> Array[CommitmentStateRecord]: var closed: Array[CommitmentStateRecord] = [] if situation_system == null: return closed for commitment in get_active_sorted(): var situation := situation_system.get_by_id(commitment.get_situation_id()) if situation == null: continue var progress := situation_system.derive_progress( situation, world_events, state_facts, situation.get_selected_alternative_id() ) if progress.resolved: var resolution_event := ( world_events.get_by_id(progress.matched_event_id) if world_events != null else null ) var resolver := ( resolution_event.get_participant(WorldEventRecord.ROLE_ACTOR) if resolution_event != null else null ) var debtor_resolved := resolver != null and resolver.equals(commitment.get_debtor()) var resolution_status := ( CommitmentStateRecord.STATUS_FULFILLED if debtor_resolved else CommitmentStateRecord.STATUS_SUPERSEDED ) var resolution_reason := ( REASON_FACT_FULFILLED if debtor_resolved else REASON_FACT_RESOLVED_BY_OTHER ) if _close( commitment, resolution_status, current_tick, resolution_reason, progress.matched_event_id ): closed.append(commitment) continue if situation.get_status() == SituationStateRecord.STATUS_SUPERSEDED: if _close( commitment, CommitmentStateRecord.STATUS_SUPERSEDED, current_tick, REASON_SITUATION_SUPERSEDED ): closed.append(commitment) continue if situation.get_status() == SituationStateRecord.STATUS_EXPIRED: if _close( commitment, CommitmentStateRecord.STATUS_BROKEN, current_tick, REASON_SITUATION_EXPIRED ): closed.append(commitment) continue var deadline := commitment.get_deadline_tick() if deadline >= 0 and current_tick >= deadline: if _close( commitment, CommitmentStateRecord.STATUS_BROKEN, current_tick, REASON_DEADLINE_MISSED ): closed.append(commitment) return closed func release(commitment_id: int, current_tick: int, reason: StringName) -> bool: var commitment := get_by_id(commitment_id) return _close(commitment, CommitmentStateRecord.STATUS_RELEASED, current_tick, reason) func supersede(commitment_id: int, current_tick: int, reason: StringName) -> bool: var commitment := get_by_id(commitment_id) return _close(commitment, CommitmentStateRecord.STATUS_SUPERSEDED, current_tick, reason) func restore(records: Array[CommitmentStateRecord], restored_next_id: int) -> bool: var by_id: Dictionary = {} var active_keys: Dictionary = {} var next_id := maxi(restored_next_id, 0) for record in records: if record == null or by_id.has(record.get_commitment_id()): return false if record.is_active(): var key := _contract_key( record.get_situation_id(), record.get_debtor(), record.get_creditor() ) if key.is_empty() or active_keys.has(key): return false active_keys[key] = record.get_commitment_id() by_id[record.get_commitment_id()] = record next_id = maxi(next_id, record.get_commitment_id() + 1) _commitments_by_id = by_id _active_contract_keys = active_keys _next_commitment_id = next_id return true func get_by_id(commitment_id: int) -> CommitmentStateRecord: return _commitments_by_id.get(commitment_id) as CommitmentStateRecord func get_all_sorted() -> Array[CommitmentStateRecord]: var records: Array[CommitmentStateRecord] = [] for record in _commitments_by_id.values(): records.append(record) records.sort_custom( func(first: CommitmentStateRecord, second: CommitmentStateRecord) -> bool: return first.get_commitment_id() < second.get_commitment_id() ) return records func get_active_sorted() -> Array[CommitmentStateRecord]: var records: Array[CommitmentStateRecord] = [] for record in get_all_sorted(): if record.is_active(): records.append(record) return records func get_next_commitment_id() -> int: return _next_commitment_id func _close( commitment: CommitmentStateRecord, status: StringName, current_tick: int, reason: StringName, cause_event_id: int = CommitmentStateRecord.NO_EVENT_ID ) -> bool: if commitment == null or not commitment.close(status, current_tick, reason, cause_event_id): return false _active_contract_keys.erase( _contract_key( commitment.get_situation_id(), commitment.get_debtor(), commitment.get_creditor() ) ) return true static func _contract_key( situation_id: int, debtor: WorldEntityRef, creditor: WorldEntityRef ) -> String: if situation_id < 0 or debtor == null or creditor == null: return "" return "%d|%s|%s" % [situation_id, debtor.index_key(), creditor.index_key()]