feat: add witnessed knowledge and wind ambience
This commit is contained in:
+139
-53
@@ -2,6 +2,8 @@ extends Node
|
||||
|
||||
const SimulationEventLogScript := preload("res://simulation/events/SimulationEventLog.gd")
|
||||
const VillageEconomyScript := preload("res://simulation/economy/VillageEconomy.gd")
|
||||
const RelationshipSystemScript := preload("res://simulation/relationships/RelationshipSystem.gd")
|
||||
const EventKnowledgeSystemScript := preload("res://simulation/knowledge/EventKnowledgeSystem.gd")
|
||||
|
||||
signal npc_task_changed(npc: SimNPC, old_task: StringName, new_task: StringName)
|
||||
signal village_changed(village: SimVillage)
|
||||
@@ -12,6 +14,8 @@ signal npc_inventory_changed(npc: SimNPC, item_id: StringName, amount: float)
|
||||
signal economic_event_recorded(event: EconomicEventRecord)
|
||||
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)
|
||||
|
||||
var village := SimVillage.new()
|
||||
|
||||
@@ -29,6 +33,8 @@ var wander_random_sources := {}
|
||||
var resource_states: Dictionary = {}
|
||||
var event_log := SimulationEventLogScript.new()
|
||||
var economy := VillageEconomyScript.new()
|
||||
var relationship_system := RelationshipSystemScript.new()
|
||||
var event_knowledge_system := EventKnowledgeSystemScript.new()
|
||||
var storage_states: Dictionary:
|
||||
get:
|
||||
return economy.storage_states
|
||||
@@ -57,6 +63,7 @@ func _ready() -> void:
|
||||
economy.inventory_changed.connect(_on_economy_inventory_changed)
|
||||
economy.economic_event_requested.connect(_record_economic_event)
|
||||
economy.narrative_event_requested.connect(record_narrative_event)
|
||||
action_selector.relationship_system = relationship_system
|
||||
var definition_errors := SimulationDefinitions.validate()
|
||||
if not definition_errors.is_empty():
|
||||
for error in definition_errors:
|
||||
@@ -72,6 +79,7 @@ func _ready() -> void:
|
||||
village.update_modifiers()
|
||||
village.update_priorities()
|
||||
generate_npcs()
|
||||
relationship_system.initialize_households(npcs)
|
||||
call_deferred("register_loaded_resource_nodes")
|
||||
call_deferred("register_loaded_storage_nodes")
|
||||
if debug_logs:
|
||||
@@ -122,12 +130,6 @@ func generate_npcs() -> void:
|
||||
for i in range(npcs.size()):
|
||||
npcs[i].home_position = home_positions[i % home_count]
|
||||
|
||||
for i in range(npcs.size()):
|
||||
for j in range(i + 1, npcs.size()):
|
||||
if npcs[i].home_position.distance_to(npcs[j].home_position) < 4.0:
|
||||
npcs[i].familiarity[npcs[j].id] = 0.5
|
||||
npcs[j].familiarity[npcs[i].id] = 0.5
|
||||
|
||||
|
||||
func _create_random_source(npc_id: int, stream_id: int) -> RandomNumberGenerator:
|
||||
var source := RandomNumberGenerator.new()
|
||||
@@ -200,13 +202,13 @@ func _select_action_if_idle(npc: SimNPC, previous_state: StringName) -> void:
|
||||
npc_decision_recorded.emit(npc, selection)
|
||||
npc.set_task(selection.action_id, selection.duration_override)
|
||||
var definition := SimulationDefinitions.get_action(selection.action_id)
|
||||
var display_name := definition.display_name if definition != null else String(selection.action_id)
|
||||
var display_name := (
|
||||
definition.display_name if definition != null else String(selection.action_id)
|
||||
)
|
||||
record_narrative_event(SimulationIds.EVENT_TASK_STARTED, npc.id, &"", display_name)
|
||||
|
||||
|
||||
func _handle_npc_death(
|
||||
npc: SimNPC, previous_task: StringName, previous_target: StringName
|
||||
) -> void:
|
||||
func _handle_npc_death(npc: SimNPC, previous_task: StringName, previous_target: StringName) -> void:
|
||||
if not previous_target.is_empty():
|
||||
release_npc_reservation(npc.id)
|
||||
npc_died.emit(npc)
|
||||
@@ -264,8 +266,10 @@ func _complete_resource_gather(npc: SimNPC, completed_task: StringName) -> void:
|
||||
if resource_state == null or resource_state.get_reserved_by() != npc.id:
|
||||
if debug_logs:
|
||||
print(
|
||||
"[SimulationManager] %s could not complete %s: invalid reservation"
|
||||
% [npc.npc_name, completed_task]
|
||||
(
|
||||
"[SimulationManager] %s could not complete %s: invalid reservation"
|
||||
% [npc.npc_name, completed_task]
|
||||
)
|
||||
)
|
||||
release_npc_reservation(npc.id)
|
||||
return
|
||||
@@ -289,8 +293,10 @@ func _complete_resource_gather(npc: SimNPC, completed_task: StringName) -> void:
|
||||
)
|
||||
if debug_logs:
|
||||
print(
|
||||
"[SimulationManager] %s extracted %.1f %s from %s"
|
||||
% [npc.npc_name, extracted, resource_id, resource_state.get_node_id()]
|
||||
(
|
||||
"[SimulationManager] %s extracted %.1f %s from %s"
|
||||
% [npc.npc_name, extracted, resource_id, resource_state.get_node_id()]
|
||||
)
|
||||
)
|
||||
release_npc_reservation(npc.id)
|
||||
|
||||
@@ -384,16 +390,12 @@ func notify_npc_arrived(npc_id: int) -> void:
|
||||
continue
|
||||
if (
|
||||
other.target_id == npc.target_id
|
||||
and other.task_state in [
|
||||
SimNPC.TASK_STATE_TRAVELING, SimNPC.TASK_STATE_WORKING
|
||||
]
|
||||
and (
|
||||
other.task_state
|
||||
in [SimNPC.TASK_STATE_TRAVELING, SimNPC.TASK_STATE_WORKING]
|
||||
)
|
||||
):
|
||||
npc.familiarity[other.id] = minf(
|
||||
float(npc.familiarity.get(other.id, 0.0)) + 0.1, 1.0
|
||||
)
|
||||
other.familiarity[npc.id] = minf(
|
||||
float(other.familiarity.get(npc.id, 0.0)) + 0.1, 1.0
|
||||
)
|
||||
relationship_system.increase_shared_work_familiarity(npc.id, other.id)
|
||||
|
||||
if debug_logs:
|
||||
print(
|
||||
@@ -491,27 +493,12 @@ func get_activity_target_claim_count(target_id: StringName, except_npc_id: int =
|
||||
|
||||
|
||||
func _notify_mourning(dead_npc: SimNPC) -> void:
|
||||
var best_id := -1
|
||||
var best_score := -1.0
|
||||
for key in dead_npc.familiarity:
|
||||
var other_id: int = int(key)
|
||||
var score: float = float(dead_npc.familiarity[key])
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_id = other_id
|
||||
if best_id < 0:
|
||||
var mourner := relationship_system.get_most_familiar_living_subject(dead_npc.id, npcs)
|
||||
if mourner == null:
|
||||
return
|
||||
for npc in npcs:
|
||||
if npc.id == best_id and not npc.is_dead:
|
||||
npc.mourning_ticks = 100
|
||||
if debug_logs:
|
||||
print(
|
||||
"[SimulationManager] ",
|
||||
npc.npc_name,
|
||||
" is mourning ",
|
||||
dead_npc.npc_name
|
||||
)
|
||||
return
|
||||
mourner.mourning_ticks = 100
|
||||
if debug_logs:
|
||||
print("[SimulationManager] ", mourner.npc_name, " is mourning ", dead_npc.npc_name)
|
||||
|
||||
|
||||
func _record_economic_event(
|
||||
@@ -521,19 +508,66 @@ func _record_economic_event(
|
||||
destination_id: StringName,
|
||||
item_id: StringName,
|
||||
amount: float
|
||||
) -> void:
|
||||
_record_economic_event_at(
|
||||
event_type,
|
||||
actor_id,
|
||||
source_id,
|
||||
destination_id,
|
||||
item_id,
|
||||
amount,
|
||||
_get_event_world_position(actor_id, source_id, destination_id)
|
||||
)
|
||||
|
||||
|
||||
func _record_economic_event_at(
|
||||
event_type: StringName,
|
||||
actor_id: int,
|
||||
source_id: StringName,
|
||||
destination_id: StringName,
|
||||
item_id: StringName,
|
||||
amount: float,
|
||||
world_position: Vector3
|
||||
) -> void:
|
||||
event_log.record_economic(
|
||||
tick_count, event_type, actor_id, source_id, destination_id, item_id, amount
|
||||
tick_count, event_type, actor_id, source_id, destination_id, item_id, amount, world_position
|
||||
)
|
||||
|
||||
|
||||
func record_narrative_event(
|
||||
event_type: StringName, actor_id: int, source_id: StringName = &"", action_display: String = ""
|
||||
) -> void:
|
||||
_record_narrative_event_at(
|
||||
event_type,
|
||||
actor_id,
|
||||
source_id,
|
||||
action_display,
|
||||
_get_event_world_position(actor_id, source_id, &"")
|
||||
)
|
||||
|
||||
|
||||
func _record_narrative_event_at(
|
||||
event_type: StringName,
|
||||
actor_id: int,
|
||||
source_id: StringName = &"",
|
||||
action_display: String = ""
|
||||
source_id: StringName,
|
||||
action_display: String,
|
||||
world_position: Vector3
|
||||
) -> void:
|
||||
event_log.record_narrative(tick_count, event_type, actor_id, source_id, action_display)
|
||||
event_log.record_narrative(
|
||||
tick_count, event_type, actor_id, source_id, action_display, world_position
|
||||
)
|
||||
|
||||
|
||||
func _get_event_world_position(
|
||||
actor_id: int, source_id: StringName, destination_id: StringName
|
||||
) -> Vector3:
|
||||
for npc in npcs:
|
||||
if npc.id != actor_id:
|
||||
continue
|
||||
if not npc.target_id.is_empty() and npc.target_id in [source_id, destination_id]:
|
||||
return npc.travel_target_position
|
||||
return npc.position
|
||||
return Vector3.ZERO
|
||||
|
||||
|
||||
func get_npc_events(npc_id: int, max_count: int = 8) -> Array[EconomicEventRecord]:
|
||||
@@ -545,9 +579,43 @@ func get_recent_events(max_count: int = 5) -> Array[EconomicEventRecord]:
|
||||
|
||||
|
||||
func _on_economic_event_recorded(event: EconomicEventRecord) -> void:
|
||||
var learned_records: Array[KnownEventStateRecord] = event_knowledge_system.observe_event(
|
||||
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"]))
|
||||
var changed_relationships: Array[RelationshipStateRecord] = relationship_system.apply_event(
|
||||
event, npcs, knower_ids
|
||||
)
|
||||
for relationship in changed_relationships:
|
||||
relationship_changed.emit(relationship, event)
|
||||
economic_event_recorded.emit(event)
|
||||
|
||||
|
||||
func get_primary_relationship(npc_id: int) -> RelationshipStateRecord:
|
||||
return relationship_system.get_primary_relationship(npc_id)
|
||||
|
||||
|
||||
func get_relationship_cause(relationship: RelationshipStateRecord) -> EconomicEventRecord:
|
||||
if relationship == null:
|
||||
return null
|
||||
return event_log.get_by_id(relationship.get_last_trust_cause_event_id())
|
||||
|
||||
|
||||
func get_known_events(npc_id: int, max_count: int = 3) -> Array[EconomicEventRecord]:
|
||||
var known: Array[EconomicEventRecord] = []
|
||||
for event_id in event_knowledge_system.get_known_event_ids(npc_id, max_count):
|
||||
var event := event_log.get_by_id(event_id)
|
||||
if event != null:
|
||||
known.append(event)
|
||||
return known
|
||||
|
||||
|
||||
func npc_knows_event(npc_id: int, event_id: int) -> bool:
|
||||
return event_knowledge_system.knows_event(npc_id, event_id)
|
||||
|
||||
|
||||
func get_current_speed() -> String:
|
||||
return "%.2fx" % SPEED_LEVELS[speed_index]
|
||||
|
||||
@@ -576,8 +644,10 @@ func register_storage_node(node: StorageNode) -> bool:
|
||||
var storage_state := storage_states.get(node.storage_id) as StorageStateRecord
|
||||
if storage_state == null:
|
||||
push_error(
|
||||
"SimulationManager: StorageNode '%s' has no authoritative StorageStateRecord"
|
||||
% node.storage_id
|
||||
(
|
||||
"SimulationManager: StorageNode '%s' has no authoritative StorageStateRecord"
|
||||
% node.storage_id
|
||||
)
|
||||
)
|
||||
return false
|
||||
return node.bind_state(storage_state)
|
||||
@@ -671,7 +741,12 @@ func harvest_resource_node(node: ResourceNode) -> float:
|
||||
return 0.0
|
||||
|
||||
var deposited := economy.deposit_resource(resource_state.get_resource_id(), extracted)
|
||||
_record_economic_event(
|
||||
var event_position := (
|
||||
node.interaction_point.global_position
|
||||
if node.interaction_point != null
|
||||
else node.global_position
|
||||
)
|
||||
_record_economic_event_at(
|
||||
SimulationIds.EVENT_RESOURCE_EXTRACTED,
|
||||
-1,
|
||||
resource_state.get_node_id(),
|
||||
@@ -685,11 +760,16 @@ func harvest_resource_node(node: ResourceNode) -> float:
|
||||
)
|
||||
),
|
||||
resource_state.get_resource_id(),
|
||||
deposited
|
||||
deposited,
|
||||
event_position
|
||||
)
|
||||
if resource_state.get_amount_remaining() <= 0.0:
|
||||
record_narrative_event(
|
||||
SimulationIds.EVENT_RESOURCE_DEPLETED, -1, resource_state.get_node_id()
|
||||
_record_narrative_event_at(
|
||||
SimulationIds.EVENT_RESOURCE_DEPLETED,
|
||||
-1,
|
||||
resource_state.get_node_id(),
|
||||
"",
|
||||
event_position
|
||||
)
|
||||
village_changed.emit(village)
|
||||
|
||||
@@ -818,6 +898,10 @@ func create_state_record() -> SimulationStateRecord:
|
||||
record.storages.append(storage_state)
|
||||
for event in economic_events:
|
||||
record.economic_events.append(event)
|
||||
for relationship in relationship_system.get_all_sorted():
|
||||
record.relationships.append(relationship)
|
||||
for known_event in event_knowledge_system.get_all_sorted():
|
||||
record.event_knowledge.append(known_event)
|
||||
return record
|
||||
|
||||
|
||||
@@ -853,6 +937,8 @@ func restore_state(record: SimulationStateRecord) -> bool:
|
||||
npcs.clear()
|
||||
for npc_record in record.npcs:
|
||||
npcs.append(npc_record.restore(debug_logs))
|
||||
relationship_system.restore(record.relationships)
|
||||
event_knowledge_system.restore(record.event_knowledge)
|
||||
|
||||
wander_random_sources.clear()
|
||||
var wander_streams: Array = record.simulation["wander_random_streams"]
|
||||
|
||||
Reference in New Issue
Block a user