feat: scale village queries and enrich Jajce center

This commit is contained in:
Rijad Zuzo
2026-07-16 23:51:08 +02:00
parent 4be72639d8
commit 2c88ed6ea8
48 changed files with 2059 additions and 280 deletions
+12 -11
View File
@@ -56,6 +56,7 @@ var latest_decisions: Dictionary = {}
var action_selector := ActionSelectionSystem.new()
var action_executor := ActionExecutionSystem.new()
var target_resolver := ActionTargetResolver.new()
var _population_view := SimulationPopulationView.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
@@ -134,6 +135,7 @@ func generate_npcs() -> void:
if home_count > 0:
for i in range(npcs.size()):
npcs[i].home_position = home_positions[i % home_count]
_population_view.rebuild(npcs)
func _create_random_source(npc_id: int, stream_id: int) -> RandomNumberGenerator:
@@ -155,6 +157,7 @@ func simulate_tick() -> void:
if debug_logs:
print("--- Tick ", tick_count, " ---")
var village_was_changed := false
_population_view.rebuild(npcs)
for npc in npcs:
village_was_changed = _simulate_npc_tick(npc) or village_was_changed
if village_was_changed:
@@ -178,6 +181,7 @@ func _simulate_npc_tick(npc: SimNPC) -> bool:
var was_dead := npc.is_dead
action_executor.advance_npc(npc, village)
_population_view.refresh_npc(npc)
_select_action_if_idle(npc, previous_state)
if previous_task != npc.current_task and not previous_target.is_empty():
@@ -192,6 +196,7 @@ func _simulate_npc_tick(npc: SimNPC) -> bool:
var completed := not was_complete and npc.task_complete and not npc.is_dead
if completed:
_complete_current_action(npc)
_population_view.refresh_npc(npc)
_debug_npc_tick(npc, previous_state)
return completed
@@ -205,7 +210,9 @@ func _select_action_if_idle(npc: SimNPC, previous_state: StringName) -> void:
if npc.task_state not in [SimNPC.TASK_STATE_IDLE, SimNPC.TASK_STATE_COMPLETE]:
return
var helper := get_active_opportunity_helper()
var selection := action_selector.select_action(npc, village, clock.time_of_day(), npcs, helper)
var selection := action_selector.select_action(
npc, village, clock.time_of_day(), npcs, helper, _population_view
)
if selection == null:
return
latest_decisions[npc.id] = selection
@@ -509,7 +516,7 @@ func get_activity_target_claim_count(target_id: StringName, except_npc_id: int =
func _notify_mourning(dead_npc: SimNPC) -> void:
var mourner := relationship_system.get_most_familiar_living_subject(dead_npc.id, npcs)
var mourner := relationship_system.get_most_familiar_living(dead_npc.id, npcs, _population_view)
if mourner == null:
return
mourner.mourning_ticks = 100
@@ -761,10 +768,7 @@ func _get_lasting_event_ids(npc_id: int) -> Array[int]:
func _find_npc_by_id(npc_id: int) -> SimNPC:
for npc in npcs:
if npc.id == npc_id:
return npc
return null
return _population_view.get_any(npc_id)
static func _sort_npcs_by_id(first: SimNPC, second: SimNPC) -> bool:
@@ -1044,11 +1048,7 @@ func add_knowledge(amount: float) -> void:
func get_starving_count() -> int:
var count := 0
for npc in npcs:
if npc.is_starving:
count += 1
return count
return _population_view.starving_count()
func get_state_snapshot() -> Dictionary:
@@ -1178,6 +1178,7 @@ func restore_state(record: SimulationStateRecord) -> bool:
npcs.clear()
for npc_record in record.npcs:
npcs.append(npc_record.restore(debug_logs))
_population_view.rebuild(npcs)
relationship_system.restore(record.relationships)
event_knowledge_system.restore(record.event_knowledge)
opportunity_system.restore(record.opportunities, int(record.simulation["next_opportunity_id"]))
+3 -2
View File
@@ -22,7 +22,8 @@ func select_action(
village: SimVillage,
time_of_day: float = 0.5,
all_npcs: Array = [],
opportunity_helper: OpportunityHelperResult = null
opportunity_helper: OpportunityHelperResult = null,
population_view: SimulationPopulationView = null
) -> ActionSelectionResult:
if npc.is_dead:
return null
@@ -127,7 +128,7 @@ func select_action(
)
if village.food <= RELATIONSHIP_AID_PANTRY_THRESHOLD and relationship_system != null:
var trusted_starving_npc: SimNPC = relationship_system.get_trusted_starving_subject(
npc.id, all_npcs
npc.id, all_npcs, population_view
)
if trusted_starving_npc != null:
return ActionSelectionResult.new(
@@ -0,0 +1,55 @@
class_name SimulationPopulationView
extends RefCounted
var all_by_id: Dictionary = {}
var living_by_id: Dictionary = {}
var starving_by_id: Dictionary = {}
func _init(npcs: Array = []) -> void:
rebuild(npcs)
func rebuild(npcs: Array) -> void:
all_by_id.clear()
living_by_id.clear()
starving_by_id.clear()
for value in npcs:
var npc := value as SimNPC
if npc != null:
refresh_npc(npc)
func refresh_npc(npc: SimNPC) -> void:
if npc == null:
return
all_by_id[npc.id] = npc
if npc.is_dead:
living_by_id.erase(npc.id)
starving_by_id.erase(npc.id)
return
living_by_id[npc.id] = npc
if npc.is_starving:
starving_by_id[npc.id] = npc
else:
starving_by_id.erase(npc.id)
func get_living(npc_id: int) -> SimNPC:
return living_by_id.get(npc_id) as SimNPC
func get_any(npc_id: int) -> SimNPC:
return all_by_id.get(npc_id) as SimNPC
func get_starving(npc_id: int) -> SimNPC:
return starving_by_id.get(npc_id) as SimNPC
func living_count() -> int:
return living_by_id.size()
func starving_count() -> int:
return starving_by_id.size()
@@ -0,0 +1 @@
uid://btwlag4mpji24
+16 -14
View File
@@ -95,16 +95,17 @@ func get_primary_relationship(observer_id: int) -> RelationshipStateRecord:
return best
func get_most_familiar_living_subject(observer_id: int, npcs: Array[SimNPC]) -> SimNPC:
var living_by_id := {}
for npc in npcs:
if not npc.is_dead:
living_by_id[npc.id] = npc
func get_most_familiar_living(
observer_id: int, npcs: Array[SimNPC], population_view: SimulationPopulationView = null
) -> SimNPC:
var effective_view := population_view
if effective_view == null:
effective_view = SimulationPopulationView.new(npcs)
var best_relationship: RelationshipStateRecord
for relationship in relationships.values():
if relationship.get_observer_id() != observer_id:
continue
if not living_by_id.has(relationship.get_subject_id()):
if effective_view.get_living(relationship.get_subject_id()) == null:
continue
if (
best_relationship == null
@@ -117,21 +118,22 @@ func get_most_familiar_living_subject(observer_id: int, npcs: Array[SimNPC]) ->
best_relationship = relationship
if best_relationship == null:
return null
return living_by_id[best_relationship.get_subject_id()] as SimNPC
return effective_view.get_living(best_relationship.get_subject_id())
func get_trusted_starving_subject(observer_id: int, npcs: Array[SimNPC]) -> SimNPC:
var starving_by_id := {}
for npc in npcs:
if not npc.is_dead and npc.is_starving:
starving_by_id[npc.id] = npc
func get_trusted_starving_subject(
observer_id: int, npcs: Array, population_view: SimulationPopulationView = null
) -> SimNPC:
var effective_view := population_view
if effective_view == null:
effective_view = SimulationPopulationView.new(npcs)
var best_relationship: RelationshipStateRecord
for relationship in relationships.values():
if relationship.get_observer_id() != observer_id:
continue
if relationship.get_trust() < TRUSTED_HELP_THRESHOLD:
continue
if not starving_by_id.has(relationship.get_subject_id()):
if effective_view.get_starving(relationship.get_subject_id()) == null:
continue
if (
best_relationship == null
@@ -144,7 +146,7 @@ func get_trusted_starving_subject(observer_id: int, npcs: Array[SimNPC]) -> SimN
best_relationship = relationship
if best_relationship == null:
return null
return starving_by_id[best_relationship.get_subject_id()] as SimNPC
return effective_view.get_starving(best_relationship.get_subject_id())
func _get_or_create(observer_id: int, subject_id: int) -> RelationshipStateRecord: