251 lines
7.6 KiB
GDScript
251 lines
7.6 KiB
GDScript
class_name CommitmentLifecycleService
|
|
extends RefCounted
|
|
|
|
const EVENT_ACCEPTED := SimulationIds.EVENT_COMMITMENT_ACCEPTED
|
|
const EVENT_FULFILLED := SimulationIds.EVENT_COMMITMENT_FULFILLED
|
|
const EVENT_BROKEN := SimulationIds.EVENT_COMMITMENT_BROKEN
|
|
const EVENT_RELEASED := SimulationIds.EVENT_COMMITMENT_RELEASED
|
|
const EVENT_SUPERSEDED := SimulationIds.EVENT_COMMITMENT_SUPERSEDED
|
|
|
|
const ACCEPTED_OBLIGATION := 0.25
|
|
const FULFILLED_TRUST := 0.10
|
|
const BROKEN_TRUST := -0.20
|
|
const BROKEN_HOSTILITY := 0.15
|
|
|
|
|
|
func record_acceptance(
|
|
commitment: CommitmentStateRecord, event_log: RefCounted, relationships: RefCounted
|
|
) -> EconomicEventRecord:
|
|
if commitment == null or not commitment.is_active() or event_log == null:
|
|
return null
|
|
var event := _find_fact(event_log, EVENT_ACCEPTED, commitment.get_commitment_id())
|
|
if event == null:
|
|
var relationship := _get_consequence_relationship(commitment, relationships)
|
|
var obligation_delta := (
|
|
minf(ACCEPTED_OBLIGATION, 1.0 - relationship.get_obligation())
|
|
if relationship != null
|
|
else 0.0
|
|
)
|
|
event = _record_fact(
|
|
commitment,
|
|
event_log,
|
|
EVENT_ACCEPTED,
|
|
commitment.get_created_tick(),
|
|
CommitmentStateRecord.STATUS_ACTIVE,
|
|
&"accepted",
|
|
CommitmentStateRecord.NO_EVENT_ID,
|
|
{"obligation_delta": obligation_delta}
|
|
)
|
|
if event != null:
|
|
_apply_acceptance(commitment, event, relationships)
|
|
return event
|
|
|
|
|
|
func maintain_and_record(
|
|
current_tick: int,
|
|
commitments: SocialCommitmentSystem,
|
|
situations: SituationSystem,
|
|
event_log: RefCounted,
|
|
state_facts: Dictionary,
|
|
relationships: RefCounted
|
|
) -> Array[CommitmentStateRecord]:
|
|
if commitments == null or situations == null or event_log == null:
|
|
return []
|
|
var closed := commitments.maintain(
|
|
current_tick, situations, event_log.world_events, state_facts
|
|
)
|
|
for commitment in closed:
|
|
record_outcome(commitment, event_log, relationships)
|
|
return closed
|
|
|
|
|
|
func record_outcome(
|
|
commitment: CommitmentStateRecord, event_log: RefCounted, relationships: RefCounted
|
|
) -> EconomicEventRecord:
|
|
if commitment == null or commitment.is_active() or event_log == null:
|
|
return null
|
|
var event_type := _event_type_for_status(commitment.get_status())
|
|
if event_type.is_empty():
|
|
return null
|
|
var event := _find_fact(event_log, event_type, commitment.get_commitment_id())
|
|
if event == null:
|
|
var acceptance := _find_fact(event_log, EVENT_ACCEPTED, commitment.get_commitment_id())
|
|
var obligation_delta := (
|
|
float(acceptance.data.get("obligation_delta", ACCEPTED_OBLIGATION))
|
|
if acceptance != null
|
|
else 0.0
|
|
)
|
|
event = _record_fact(
|
|
commitment,
|
|
event_log,
|
|
event_type,
|
|
commitment.get_closed_tick(),
|
|
commitment.get_status(),
|
|
commitment.get_close_reason(),
|
|
commitment.get_cause_event_id(),
|
|
{"obligation_delta": obligation_delta}
|
|
)
|
|
if event != null:
|
|
_apply_outcome(commitment, event, relationships)
|
|
return event
|
|
|
|
|
|
func synchronize_outcomes(
|
|
commitments: SocialCommitmentSystem, event_log: RefCounted, relationships: RefCounted
|
|
) -> Array[EconomicEventRecord]:
|
|
var events: Array[EconomicEventRecord] = []
|
|
if commitments == null:
|
|
return events
|
|
for commitment in commitments.get_all_sorted():
|
|
if commitment.is_active():
|
|
continue
|
|
var event := record_outcome(commitment, event_log, relationships)
|
|
if event != null:
|
|
events.append(event)
|
|
return events
|
|
|
|
|
|
func get_fact(
|
|
event_log: RefCounted, event_type: StringName, commitment_id: int
|
|
) -> EconomicEventRecord:
|
|
return _find_fact(event_log, event_type, commitment_id)
|
|
|
|
|
|
func _record_fact(
|
|
commitment: CommitmentStateRecord,
|
|
event_log: RefCounted,
|
|
event_type: StringName,
|
|
tick: int,
|
|
status: StringName,
|
|
reason: StringName,
|
|
cause_event_id: int,
|
|
additional_payload: Dictionary = {}
|
|
) -> EconomicEventRecord:
|
|
var debtor := commitment.get_debtor()
|
|
var creditor := commitment.get_creditor()
|
|
var debtor_actor_id := _relationship_actor_id(debtor)
|
|
if not RelationshipStateRecord.is_valid_actor_id(debtor_actor_id):
|
|
return null
|
|
var payload := {
|
|
"cause_event_id": cause_event_id,
|
|
"close_reason": String(reason),
|
|
"commitment_id": commitment.get_commitment_id(),
|
|
"creditor": creditor.to_dictionary(),
|
|
"debtor": debtor.to_dictionary(),
|
|
"situation_id": commitment.get_situation_id(),
|
|
"status": String(status),
|
|
"terms": commitment.get_terms(),
|
|
}
|
|
for key in additional_payload:
|
|
if payload.has(key):
|
|
return null
|
|
payload[key] = additional_payload[key]
|
|
return event_log.record_fact(
|
|
tick,
|
|
event_type,
|
|
debtor_actor_id,
|
|
StringName("commitment_%d" % commitment.get_commitment_id()),
|
|
StringName(creditor.index_key()),
|
|
payload
|
|
)
|
|
|
|
|
|
func _apply_acceptance(
|
|
commitment: CommitmentStateRecord, event: EconomicEventRecord, relationships: RefCounted
|
|
) -> void:
|
|
var relationship := _get_consequence_relationship(commitment, relationships)
|
|
if relationship == null:
|
|
return
|
|
_apply_dimension_once(
|
|
relationship,
|
|
RelationshipStateRecord.DIMENSION_OBLIGATION,
|
|
float(event.data.get("obligation_delta", ACCEPTED_OBLIGATION)),
|
|
int(event.data["event_id"])
|
|
)
|
|
|
|
|
|
func _apply_outcome(
|
|
commitment: CommitmentStateRecord, event: EconomicEventRecord, relationships: RefCounted
|
|
) -> void:
|
|
var relationship := _get_consequence_relationship(commitment, relationships)
|
|
if relationship == null:
|
|
return
|
|
var event_id := int(event.data["event_id"])
|
|
_apply_dimension_once(
|
|
relationship,
|
|
RelationshipStateRecord.DIMENSION_OBLIGATION,
|
|
-float(event.data.get("obligation_delta", ACCEPTED_OBLIGATION)),
|
|
event_id
|
|
)
|
|
match commitment.get_status():
|
|
CommitmentStateRecord.STATUS_FULFILLED:
|
|
_apply_dimension_once(
|
|
relationship, RelationshipStateRecord.DIMENSION_TRUST, FULFILLED_TRUST, event_id
|
|
)
|
|
CommitmentStateRecord.STATUS_BROKEN:
|
|
_apply_dimension_once(
|
|
relationship, RelationshipStateRecord.DIMENSION_TRUST, BROKEN_TRUST, event_id
|
|
)
|
|
_apply_dimension_once(
|
|
relationship,
|
|
RelationshipStateRecord.DIMENSION_HOSTILITY,
|
|
BROKEN_HOSTILITY,
|
|
event_id
|
|
)
|
|
|
|
|
|
static func _apply_dimension_once(
|
|
relationship: RelationshipStateRecord, dimension: StringName, amount: float, cause_event_id: int
|
|
) -> void:
|
|
if relationship.get_last_cause_event_id(dimension) >= cause_event_id:
|
|
return
|
|
relationship.adjust_dimension(dimension, amount, cause_event_id)
|
|
|
|
|
|
static func _get_consequence_relationship(
|
|
commitment: CommitmentStateRecord, relationships: RefCounted
|
|
) -> RelationshipStateRecord:
|
|
if relationships == null:
|
|
return null
|
|
var observer_id := _relationship_actor_id(commitment.get_creditor())
|
|
var subject_id := _relationship_actor_id(commitment.get_debtor())
|
|
return relationships.get_or_create_relationship(observer_id, subject_id)
|
|
|
|
|
|
static func _relationship_actor_id(entity: WorldEntityRef) -> int:
|
|
if entity == null:
|
|
return -2147483648
|
|
var entity_id := String(entity.get_entity_id())
|
|
if not entity_id.is_valid_int():
|
|
return -2147483648
|
|
var actor_id := entity_id.to_int()
|
|
if entity.get_entity_type() == WorldEventRecord.ENTITY_TYPE_PLAYER:
|
|
return actor_id if actor_id == SimulationIds.PLAYER_ACTOR_ID else -2147483648
|
|
if entity.get_entity_type() != WorldEventRecord.ENTITY_TYPE_NPC or actor_id < 0:
|
|
return -2147483648
|
|
return actor_id
|
|
|
|
|
|
static func _find_fact(
|
|
event_log: RefCounted, event_type: StringName, commitment_id: int
|
|
) -> EconomicEventRecord:
|
|
if event_log == null or commitment_id < 0:
|
|
return null
|
|
for event in event_log.get_for_type(event_type):
|
|
if int(event.data.get("commitment_id", -1)) == commitment_id:
|
|
return event
|
|
return null
|
|
|
|
|
|
static func _event_type_for_status(status: StringName) -> StringName:
|
|
match status:
|
|
CommitmentStateRecord.STATUS_FULFILLED:
|
|
return EVENT_FULFILLED
|
|
CommitmentStateRecord.STATUS_BROKEN:
|
|
return EVENT_BROKEN
|
|
CommitmentStateRecord.STATUS_RELEASED:
|
|
return EVENT_RELEASED
|
|
CommitmentStateRecord.STATUS_SUPERSEDED:
|
|
return EVENT_SUPERSEDED
|
|
return &""
|