feat: add provenance-aware npc communication

This commit is contained in:
Rijad Zuzo
2026-07-11 19:58:47 +02:00
parent 81409650df
commit 9a479ba182
18 changed files with 938 additions and 70 deletions
+88 -9
View File
@@ -16,6 +16,7 @@ signal state_restored
signal npc_decision_recorded(npc: SimNPC, decision: ActionSelectionResult)
signal relationship_changed(relationship: RelationshipStateRecord, cause_event: EconomicEventRecord)
signal event_knowledge_changed(knower_id: int, event: EconomicEventRecord)
signal event_knowledge_transferred(speaker_id: int, listener_id: int, event: EconomicEventRecord)
var village := SimVillage.new()
@@ -52,6 +53,7 @@ var action_executor := ActionExecutionSystem.new()
var target_resolver := ActionTargetResolver.new()
var speed_index := 2
const SPEED_LEVELS := [0.25, 0.5, 1.0, 2.0, 4.0, 10.0]
const KNOWLEDGE_COMMUNICATION_RADIUS := 2.5
signal speed_changed(multiplier: float)
const NPC_NAMES := ["Amina", "Tarik", "Jasmin", "Elma", "Mirza", "Lejla"]
@@ -384,18 +386,24 @@ func notify_npc_arrived(npc_id: int) -> void:
npc.has_travel_target = false
npc.start_working()
var working_speakers: Array[SimNPC] = []
if npc.target_id != &"":
for other in npcs:
if other.id == npc.id or other.is_dead:
continue
if other.target_id != npc.target_id:
continue
if (
other.target_id == npc.target_id
and (
other.task_state
in [SimNPC.TASK_STATE_TRAVELING, SimNPC.TASK_STATE_WORKING]
)
other.task_state
in [SimNPC.TASK_STATE_TRAVELING, SimNPC.TASK_STATE_WORKING]
):
relationship_system.increase_shared_work_familiarity(npc.id, other.id)
if other.task_state == SimNPC.TASK_STATE_WORKING:
working_speakers.append(other)
working_speakers.sort_custom(_sort_npcs_by_id)
for speaker in working_speakers:
if try_communicate_at_shared_activity(speaker.id, npc.id):
break
if debug_logs:
print(
@@ -583,14 +591,81 @@ func _on_economic_event_recorded(event: EconomicEventRecord) -> void:
event, npcs
)
for learned_record in learned_records:
event_knowledge_changed.emit(learned_record.get_knower_id(), event)
var knower_ids: Array[int] = event_knowledge_system.get_knowers(int(event.data["event_id"]))
_apply_new_event_knowledge(learned_record, event)
economic_event_recorded.emit(event)
func try_communicate_at_shared_activity(speaker_id: int, listener_id: int) -> bool:
var speaker := _find_npc_by_id(speaker_id)
var listener := _find_npc_by_id(listener_id)
if not _can_communicate_at_shared_activity(speaker, listener):
return false
var known_event_ids := event_knowledge_system.get_known_event_ids(speaker.id, 0)
for index in range(known_event_ids.size() - 1, -1, -1):
var event := event_log.get_by_id(known_event_ids[index])
if event == null:
continue
var learned_record := event_knowledge_system.communicate_event(
event, speaker.id, listener.id
)
if learned_record == null:
continue
_apply_new_event_knowledge(learned_record, event)
event_knowledge_transferred.emit(speaker.id, listener.id, event)
return true
return false
func _can_communicate_at_shared_activity(speaker: SimNPC, listener: SimNPC) -> bool:
if speaker == null or listener == null or speaker.id == listener.id:
return false
if speaker.is_dead or listener.is_dead:
return false
if (
speaker.task_state != SimNPC.TASK_STATE_WORKING
or listener.task_state != SimNPC.TASK_STATE_WORKING
):
return false
if speaker.target_id.is_empty() or speaker.target_id != listener.target_id:
return false
if storage_states.has(speaker.target_id):
return false
var speaker_action := SimulationDefinitions.get_action(speaker.current_task)
var listener_action := SimulationDefinitions.get_action(listener.current_task)
if (
speaker_action == null
or listener_action == null
or speaker_action.target_type != SimulationIds.TARGET_ACTIVITY
or listener_action.target_type != SimulationIds.TARGET_ACTIVITY
):
return false
return (
speaker.position.distance_squared_to(listener.position)
<= KNOWLEDGE_COMMUNICATION_RADIUS * KNOWLEDGE_COMMUNICATION_RADIUS
)
func _apply_new_event_knowledge(
known_event: KnownEventStateRecord, event: EconomicEventRecord
) -> void:
event_knowledge_changed.emit(known_event.get_knower_id(), event)
var newly_informed_ids: Array[int] = [known_event.get_knower_id()]
var changed_relationships: Array[RelationshipStateRecord] = relationship_system.apply_event(
event, npcs, knower_ids
event, npcs, newly_informed_ids
)
for relationship in changed_relationships:
relationship_changed.emit(relationship, event)
economic_event_recorded.emit(event)
func _find_npc_by_id(npc_id: int) -> SimNPC:
for npc in npcs:
if npc.id == npc_id:
return npc
return null
static func _sort_npcs_by_id(first: SimNPC, second: SimNPC) -> bool:
return first.id < second.id
func get_primary_relationship(npc_id: int) -> RelationshipStateRecord:
@@ -616,6 +691,10 @@ func npc_knows_event(npc_id: int, event_id: int) -> bool:
return event_knowledge_system.knows_event(npc_id, event_id)
func get_known_event_record(npc_id: int, event_id: int) -> KnownEventStateRecord:
return event_knowledge_system.get_record(npc_id, event_id)
func get_current_speed() -> String:
return "%.2fx" % SPEED_LEVELS[speed_index]