merge: integrate remote player quest slices
This commit is contained in:
+172
-34
@@ -5,6 +5,7 @@ extends Node
|
||||
# intentionally keeps its orchestration readable in one authoritative file.
|
||||
|
||||
const SimulationEventLogScript := preload("res://simulation/events/SimulationEventLog.gd")
|
||||
const SimulationEventRecorderScript := preload("res://simulation/events/SimulationEventRecorder.gd")
|
||||
const VillageEconomyScript := preload("res://simulation/economy/VillageEconomy.gd")
|
||||
const AnimalCareSystemScript := preload("res://simulation/animals/animal_care_system.gd")
|
||||
const NpcTickDebugLog := preload("res://simulation/debug/npc_tick_debug_log.gd")
|
||||
@@ -41,6 +42,14 @@ signal raid_started(raider_ids: Array[StringName])
|
||||
signal war_resolved(outcome: StringName)
|
||||
signal war_aborted
|
||||
signal player_downed
|
||||
signal player_quest_opened(quest: PlayerQuestRecord)
|
||||
signal player_quest_completed(quest: PlayerQuestRecord)
|
||||
signal player_quest_expired(quest: PlayerQuestRecord)
|
||||
signal player_standing_changed(standing: PlayerStandingRecord)
|
||||
signal player_tier_advanced(tier: int, tier_name: String)
|
||||
signal player_needs_changed(state: PlayerStateRecord)
|
||||
signal player_inventory_changed(state: PlayerStateRecord)
|
||||
signal player_died(state: PlayerStateRecord)
|
||||
|
||||
var village := SimVillage.new()
|
||||
var player_system := PlayerCitizenSystem.new()
|
||||
@@ -59,6 +68,7 @@ var tick_count := 0
|
||||
var wander_random_sources := {}
|
||||
var resource_states: Dictionary = {}
|
||||
var event_log := SimulationEventLogScript.new()
|
||||
var event_recorder := SimulationEventRecorderScript.new()
|
||||
var economy := VillageEconomyScript.new()
|
||||
var animal_care := AnimalCareSystemScript.new()
|
||||
var relationship_system := RelationshipSystemScript.new()
|
||||
@@ -71,6 +81,10 @@ var commitment_lifecycle := CommitmentLifecycleService.new()
|
||||
var conversation_service := ConversationService.new()
|
||||
var conversation_history: Array[ConversationActStateRecord] = []
|
||||
var conflict_system := ConflictSystemScript.new()
|
||||
var player_quest_system := PlayerQuestSystem.new()
|
||||
var player_needs := PlayerNeedsSystem.new()
|
||||
var player_negotiation := PlayerNegotiationSystem.new()
|
||||
var _last_player_tier := PlayerStandingRecord.TIER_STRANGER
|
||||
var storage_states: Dictionary:
|
||||
get:
|
||||
return economy.storage_states
|
||||
@@ -114,6 +128,19 @@ const NPC_NAMES := ["Amina", "Tarik", "Jasmin", "Elma", "Mirza", "Lejla"]
|
||||
func _ready() -> void:
|
||||
add_to_group("simulation_manager")
|
||||
event_log.event_recorded.connect(_on_economic_event_recorded)
|
||||
event_recorder.configure(event_log, Callable(self, "get_tick_count_for_recording"))
|
||||
event_recorder.set_npcs(npcs)
|
||||
player_needs.configure(economy, event_recorder)
|
||||
player_needs.set_resource_states(resource_states)
|
||||
player_negotiation.configure(
|
||||
player_quest_system,
|
||||
Callable(self, "_find_npc_by_id"),
|
||||
Callable(self, "get_active_opportunity"),
|
||||
Callable(self, "get_active_opportunity_player_response"),
|
||||
player_quest_opened,
|
||||
player_quest_expired,
|
||||
player_standing_changed
|
||||
)
|
||||
economy.inventory_changed.connect(_on_economy_inventory_changed)
|
||||
economy.economic_event_requested.connect(_record_economic_event)
|
||||
economy.narrative_event_requested.connect(record_narrative_event)
|
||||
@@ -225,6 +252,10 @@ func simulate_tick() -> void:
|
||||
if debug_logs:
|
||||
print("--- Tick ", tick_count, " ---")
|
||||
animal_care.advance(tick_count)
|
||||
for quest in player_quest_system.consider_animal_care_quests(
|
||||
animal_care, npcs, get_pantry(), tick_count
|
||||
):
|
||||
player_quest_opened.emit(quest)
|
||||
advance_player_needs()
|
||||
_advance_resource_regrowth()
|
||||
var village_was_changed := false
|
||||
@@ -240,6 +271,10 @@ func simulate_tick() -> void:
|
||||
if invalidated != null:
|
||||
opportunity_invalidated.emit(invalidated)
|
||||
_maintain_emergent_world()
|
||||
if invalidated != null:
|
||||
var expired_quest := player_quest_system.on_opportunity_invalidated(invalidated, tick_count)
|
||||
if expired_quest != null:
|
||||
player_quest_expired.emit(expired_quest)
|
||||
if tick_count % get_knowledge_review_interval() == 0:
|
||||
_maintain_event_knowledge(true)
|
||||
if debug_logs:
|
||||
@@ -314,7 +349,7 @@ func _select_action_if_idle(npc: SimNPC, previous_state: StringName) -> void:
|
||||
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)
|
||||
event_recorder.record_narrative(SimulationIds.EVENT_TASK_STARTED, npc.id, &"", display_name)
|
||||
|
||||
|
||||
func _is_weak_villager_state(npc: SimNPC) -> bool:
|
||||
@@ -391,7 +426,7 @@ func _handle_npc_death(npc: SimNPC, previous_task: StringName, previous_target:
|
||||
release_npc_reservation(npc.id)
|
||||
npc_died.emit(npc)
|
||||
npc_task_changed.emit(npc, previous_task, npc.current_task)
|
||||
record_narrative_event(SimulationIds.EVENT_NPC_DIED, npc.id)
|
||||
event_recorder.record_narrative(SimulationIds.EVENT_NPC_DIED, npc.id)
|
||||
_notify_mourning(npc)
|
||||
if debug_logs:
|
||||
print("[SimulationManager] NPC died: ", npc.npc_name)
|
||||
@@ -480,7 +515,7 @@ func _complete_resource_gather(npc: SimNPC, completed_task: StringName) -> void:
|
||||
var resource_id := resource_state.get_resource_id()
|
||||
npc.add_inventory(resource_id, extracted)
|
||||
npc_inventory_changed.emit(npc, resource_id, npc.get_inventory_amount(resource_id))
|
||||
_record_economic_event(
|
||||
event_recorder.record_economic(
|
||||
SimulationIds.EVENT_RESOURCE_EXTRACTED,
|
||||
npc.id,
|
||||
resource_state.get_node_id(),
|
||||
@@ -489,7 +524,7 @@ func _complete_resource_gather(npc: SimNPC, completed_task: StringName) -> void:
|
||||
extracted
|
||||
)
|
||||
if resource_state.get_amount_remaining() <= 0.0:
|
||||
record_narrative_event(
|
||||
event_recorder.record_narrative(
|
||||
SimulationIds.EVENT_RESOURCE_DEPLETED, npc.id, resource_state.get_node_id()
|
||||
)
|
||||
if debug_logs:
|
||||
@@ -1393,6 +1428,10 @@ func _get_event_world_position(
|
||||
return Vector3.ZERO
|
||||
|
||||
|
||||
func get_tick_count_for_recording() -> int:
|
||||
return tick_count
|
||||
|
||||
|
||||
func get_npc_events(npc_id: int, max_count: int = 8) -> Array[EconomicEventRecord]:
|
||||
return event_log.get_for_actor(npc_id, max_count)
|
||||
|
||||
@@ -1407,22 +1446,42 @@ func _on_economic_event_recorded(event: EconomicEventRecord) -> void:
|
||||
)
|
||||
for learned_record in learned_records:
|
||||
_apply_new_event_knowledge(learned_record, event)
|
||||
if StringName(event.data["event_type"]) == SimulationIds.EVENT_ANIMAL_FED:
|
||||
_finish_quest_resolution(
|
||||
player_quest_system.on_animal_fed(
|
||||
animal_care.get_state(StringName(event.data["destination_id"])), event, tick_count
|
||||
)
|
||||
)
|
||||
_update_opportunity_from_event(event)
|
||||
_update_situations_from_event(event)
|
||||
economic_event_recorded.emit(event)
|
||||
|
||||
|
||||
func _finish_quest_resolution(resolution: Dictionary) -> void:
|
||||
if not resolution.has("quest"):
|
||||
return
|
||||
var quest: PlayerQuestRecord = resolution["quest"]
|
||||
if resolution["completed"]:
|
||||
player_quest_completed.emit(quest)
|
||||
var standing := player_quest_system.standing
|
||||
player_standing_changed.emit(standing)
|
||||
if standing.get_tier() > _last_player_tier:
|
||||
_last_player_tier = standing.get_tier()
|
||||
player_tier_advanced.emit(standing.get_tier(), standing.get_tier_name())
|
||||
return
|
||||
player_quest_expired.emit(quest)
|
||||
|
||||
|
||||
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_communicable_event_ids(
|
||||
for event_id in event_knowledge_system.get_communicable_event_ids(
|
||||
speaker.id,
|
||||
_get_lasting_event_ids(speaker.id),
|
||||
opportunity_system.get_open_trigger_event_id()
|
||||
)
|
||||
for event_id in known_event_ids:
|
||||
):
|
||||
var event := event_log.get_by_id(event_id)
|
||||
if event == null:
|
||||
continue
|
||||
@@ -1439,31 +1498,8 @@ func try_communicate_at_shared_activity(speaker_id: int, listener_id: int) -> bo
|
||||
|
||||
|
||||
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
|
||||
return EventKnowledgeSystem.can_communicate_at_shared_activity(
|
||||
speaker, listener, storage_states, KNOWLEDGE_COMMUNICATION_RADIUS
|
||||
)
|
||||
|
||||
|
||||
@@ -1494,8 +1530,19 @@ func _update_opportunity_from_event(event: EconomicEventRecord) -> void:
|
||||
return
|
||||
if changed.get_status() == OpportunityStateRecord.STATUS_OPEN:
|
||||
opportunity_opened.emit(changed)
|
||||
var quest := player_quest_system.consider_opportunity_opened(
|
||||
changed,
|
||||
get_active_opportunity_player_response(),
|
||||
tick_count,
|
||||
player_quest_system.can_request_personally(changed)
|
||||
)
|
||||
if quest != null:
|
||||
player_quest_opened.emit(quest)
|
||||
return
|
||||
opportunity_resolved.emit(changed, event)
|
||||
_finish_quest_resolution(
|
||||
player_quest_system.on_opportunity_resolved(changed, event, tick_count)
|
||||
)
|
||||
_maintain_event_knowledge(false)
|
||||
|
||||
|
||||
@@ -1652,6 +1699,73 @@ func get_active_opportunity() -> OpportunityStateRecord:
|
||||
return opportunity_system.get_open_opportunity()
|
||||
|
||||
|
||||
func get_active_player_quest() -> PlayerQuestRecord:
|
||||
return player_quest_system.get_active_quest()
|
||||
|
||||
|
||||
func _advance_player_needs() -> void:
|
||||
advance_player_needs()
|
||||
player_needs_changed.emit(player_system.player_state)
|
||||
|
||||
|
||||
func respawn_player() -> bool:
|
||||
var state := player_system.player_state
|
||||
if state == null or not state.is_dead():
|
||||
return false
|
||||
state.respawn()
|
||||
_sync_player_combatant()
|
||||
player_needs_changed.emit(state)
|
||||
player_inventory_changed.emit(state)
|
||||
return true
|
||||
|
||||
|
||||
func player_eat(amount: float) -> float:
|
||||
var state := player_system.player_state
|
||||
var eaten := state.eat(amount) if state != null else 0.0
|
||||
if eaten > 0.0:
|
||||
player_needs_changed.emit(state)
|
||||
return eaten
|
||||
|
||||
|
||||
func player_gather(node: ResourceNode) -> float:
|
||||
var gathered := harvest_resource_node(node)
|
||||
if gathered > 0.0:
|
||||
player_inventory_changed.emit(player_system.player_state)
|
||||
return gathered
|
||||
|
||||
|
||||
func player_deposit(resource_id: StringName) -> float:
|
||||
var deposited := deposit_player_inventory(resource_id)
|
||||
if deposited > 0.0:
|
||||
village_changed.emit(village)
|
||||
player_inventory_changed.emit(player_system.player_state)
|
||||
return deposited
|
||||
|
||||
|
||||
func get_player_quests() -> Array[PlayerQuestRecord]:
|
||||
return player_quest_system.get_all_sorted()
|
||||
|
||||
|
||||
func get_villager_talk(npc_id: int) -> PlayerTalkResult:
|
||||
return player_negotiation.get_talk(npc_id)
|
||||
|
||||
|
||||
func accept_villager_request(npc_id: int) -> PlayerQuestRecord:
|
||||
return player_negotiation.accept_request(npc_id, tick_count)
|
||||
|
||||
|
||||
func decline_villager_request(npc_id: int) -> PlayerQuestRecord:
|
||||
return player_negotiation.decline_request(npc_id, tick_count)
|
||||
|
||||
|
||||
func get_latest_player_quest_for_requester(requester_npc_id: int) -> PlayerQuestRecord:
|
||||
return player_quest_system.get_latest_for_requester(requester_npc_id)
|
||||
|
||||
|
||||
func get_player_standing() -> PlayerStandingRecord:
|
||||
return player_quest_system.standing
|
||||
|
||||
|
||||
func get_active_opportunity_helper() -> OpportunityHelperResult:
|
||||
var active := get_active_opportunity()
|
||||
if active == null:
|
||||
@@ -2005,6 +2119,11 @@ func get_pantry() -> StorageStateRecord:
|
||||
return economy.get_pantry()
|
||||
|
||||
|
||||
func get_animal_display_name(animal_id: StringName) -> String:
|
||||
var animal_state := animal_care.get_state(animal_id)
|
||||
return animal_state.get_display_name() if animal_state != null else ""
|
||||
|
||||
|
||||
func get_woodpile() -> StorageStateRecord:
|
||||
return economy.get_woodpile()
|
||||
|
||||
@@ -2139,7 +2258,7 @@ func harvest_resource_node(node: ResourceNode) -> float:
|
||||
if node.interaction_point != null
|
||||
else node.global_position
|
||||
)
|
||||
_record_economic_event_at(
|
||||
event_recorder.record_economic_at(
|
||||
SimulationIds.EVENT_RESOURCE_EXTRACTED,
|
||||
SimulationIds.PLAYER_ACTOR_ID,
|
||||
resource_state.get_node_id(),
|
||||
@@ -2149,7 +2268,7 @@ func harvest_resource_node(node: ResourceNode) -> float:
|
||||
event_position
|
||||
)
|
||||
if resource_state.get_amount_remaining() <= 0.0:
|
||||
_record_narrative_event_at(
|
||||
event_recorder.record_narrative_at(
|
||||
SimulationIds.EVENT_RESOURCE_DEPLETED,
|
||||
SimulationIds.PLAYER_ACTOR_ID,
|
||||
resource_state.get_node_id(),
|
||||
@@ -2172,8 +2291,16 @@ func harvest_resource_node(node: ResourceNode) -> float:
|
||||
|
||||
|
||||
func advance_player_needs() -> void:
|
||||
var was_dead := player_system.player_state.is_dead()
|
||||
player_system.advance_needs(village.food_modifier)
|
||||
player_system.player_state.advance_health()
|
||||
if not was_dead and player_system.player_state.is_dead():
|
||||
player_died.emit(player_system.player_state)
|
||||
player_quest_system.standing.data["standing"] = (
|
||||
player_quest_system.standing.get_standing() * PlayerStateRecord.DEATH_STANDING_PENALTY
|
||||
)
|
||||
_last_player_tier = player_quest_system.standing.get_tier()
|
||||
player_standing_changed.emit(player_quest_system.standing)
|
||||
_sync_player_combatant()
|
||||
|
||||
|
||||
@@ -2311,6 +2438,7 @@ func create_state_record() -> SimulationStateRecord:
|
||||
"next_conversation_act_id": _next_conversation_act_id,
|
||||
"world_id": String(event_scope["world_id"]),
|
||||
"location_id": String(event_scope["location_id"]),
|
||||
"next_quest_id": player_quest_system.next_quest_id,
|
||||
"cycle_duration_seconds": clock.cycle_duration_seconds
|
||||
}
|
||||
record.village = VillageStateRecord.capture(village)
|
||||
@@ -2345,6 +2473,10 @@ func create_state_record() -> SimulationStateRecord:
|
||||
record.conversation_history.append(conversation_act)
|
||||
record.player = player_system.player_state
|
||||
conflict_system.append_state_records(record)
|
||||
record.player_standing = player_quest_system.standing
|
||||
for quest in player_quest_system.get_all_sorted():
|
||||
record.player_quests.append(quest)
|
||||
record.player_state = player_system.player_state
|
||||
return record
|
||||
|
||||
|
||||
@@ -2421,6 +2553,12 @@ func restore_state(record: SimulationStateRecord) -> bool:
|
||||
_active_player_conversation = &""
|
||||
_conversation_context_by_id.clear()
|
||||
_dialogue_speed_index = -1
|
||||
player_quest_system.restore(
|
||||
record.player_quests, int(record.simulation.get("next_quest_id", 0)), record.player_standing
|
||||
)
|
||||
_last_player_tier = player_quest_system.standing.get_tier()
|
||||
if record.player_state != null:
|
||||
player_system.player_state = record.player_state
|
||||
_maintain_event_knowledge(tick_count % get_knowledge_review_interval() == 0)
|
||||
wander_random_sources.clear()
|
||||
var wander_streams: Array = record.simulation["wander_random_streams"]
|
||||
|
||||
Reference in New Issue
Block a user