feat: introduce identity-backed goat

This commit is contained in:
Rijad Zuzo
2026-07-26 22:26:00 +02:00
parent 1f09ebe6b6
commit f79c2bb630
47 changed files with 1903 additions and 96 deletions
+45 -46
View File
@@ -2,6 +2,7 @@ extends Node
const SimulationEventLogScript := preload("res://simulation/events/SimulationEventLog.gd")
const VillageEconomyScript := preload("res://simulation/economy/VillageEconomy.gd")
const AnimalCareSystemScript := preload("res://simulation/animals/AnimalCareSystem.gd")
const RelationshipSystemScript := preload("res://simulation/relationships/RelationshipSystem.gd")
const EventKnowledgeSystemScript := preload("res://simulation/knowledge/EventKnowledgeSystem.gd")
@@ -38,6 +39,7 @@ var wander_random_sources := {}
var resource_states: Dictionary = {}
var event_log := SimulationEventLogScript.new()
var economy := VillageEconomyScript.new()
var animal_care := AnimalCareSystemScript.new()
var relationship_system := RelationshipSystemScript.new()
var event_knowledge_system := EventKnowledgeSystemScript.new()
var opportunity_system := VillageOpportunitySystem.new()
@@ -71,6 +73,8 @@ 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)
animal_care.economic_event_requested.connect(_record_economic_event_at)
animal_care.narrative_event_requested.connect(record_narrative_event)
action_selector.relationship_system = relationship_system
var definition_errors := SimulationDefinitions.validate()
if not definition_errors.is_empty():
@@ -83,12 +87,14 @@ func _ready() -> void:
clock.elapsed_ticks = int(0.25 * cycle_duration_seconds / tick_interval)
village.debug_logs = debug_logs
economy.configure(village, debug_logs)
animal_care.configure(economy, active_world_adapter)
economy.initialize_storage()
village.update_modifiers()
village.update_priorities()
generate_npcs()
relationship_system.initialize_households(npcs)
call_deferred("register_loaded_resource_nodes")
animal_care.call_deferred("register_loaded_nodes")
call_deferred("register_loaded_storage_nodes")
if debug_logs:
print("--- Simulation started ---")
@@ -156,6 +162,7 @@ func simulate_tick() -> void:
tick_count += 1
if debug_logs:
print("--- Tick ", tick_count, " ---")
animal_care.advance()
var village_was_changed := false
_population_view.rebuild(npcs)
for npc in npcs:
@@ -210,8 +217,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 animal_feed_name := animal_care.get_available_feed_name(npc.id)
var selection := action_selector.select_action(
npc, village, clock.time_of_day(), npcs, helper, _population_view
npc, village, clock.time_of_day(), npcs, helper, _population_view, animal_feed_name
)
if selection == null:
return
@@ -239,7 +247,12 @@ func _handle_npc_death(npc: SimNPC, previous_task: StringName, previous_target:
func _complete_current_action(npc: SimNPC) -> void:
var completed_task := npc.current_task
var definition := SimulationDefinitions.get_action(completed_task)
if economy.consume_completion_cost(npc, definition):
var completion_succeeded := false
if completed_task == SimulationIds.ACTION_FEED_ANIMAL:
completion_succeeded = feed_animal(npc.target_id, npc.id)
else:
completion_succeeded = economy.consume_completion_cost(npc, definition)
if completion_succeeded:
_apply_action_completion(npc, completed_task, definition)
elif not npc.target_id.is_empty():
release_npc_reservation(npc.id)
@@ -272,6 +285,8 @@ func _apply_action_completion(
npc.energy = minf(npc.energy + 40.0, 100.0)
npc.position = npc.home_position
record_narrative_event(SimulationIds.EVENT_NPC_SLEPT, npc.id)
SimulationIds.ACTION_FEED_ANIMAL:
pass
SimulationIds.ACTION_PATROL, SimulationIds.ACTION_STUDY, SimulationIds.ACTION_REST, SimulationIds.ACTION_WANDER:
village.apply_npc_task(npc)
if debug_logs and completed_task == SimulationIds.ACTION_SLEEP:
@@ -364,6 +379,9 @@ func release_npc_reservation(npc_id: int) -> void:
var resource_state := get_resource_state(npc.target_id)
if resource_state != null:
resource_state.release(npc.id)
var animal_state := animal_care.get_state(npc.target_id)
if animal_state != null:
animal_state.release(npc.id)
npc.target_id = &""
return
@@ -397,6 +415,22 @@ func notify_npc_arrived(npc_id: int) -> void:
)
notify_npc_navigation_failed(npc.id)
return
if (
npc.target_id != &""
and definition != null
and definition.target_type == SimulationIds.TARGET_ANIMAL
):
if not animal_care.can_complete_feed(npc.target_id, npc.id):
if debug_logs:
print(
"[SimulationManager] ",
npc.npc_name,
" arrived but animal ",
npc.target_id,
" no longer needs feeding, replanning"
)
notify_npc_navigation_failed(npc.id)
return
npc.has_travel_target = false
npc.start_working()
@@ -903,6 +937,13 @@ func _on_economy_inventory_changed(npc: SimNPC, item_id: StringName, amount: flo
npc_inventory_changed.emit(npc, item_id, amount)
func feed_animal(animal_id: StringName, actor_id: int = -1) -> bool:
var succeeded := animal_care.feed(animal_id, actor_id, npcs, tick_count)
if succeeded:
village_changed.emit(village)
return succeeded
func register_loaded_resource_nodes() -> void:
for node in ResourceNode.get_all():
register_resource_node(node)
@@ -1051,50 +1092,6 @@ func get_starving_count() -> int:
return _population_view.starving_count()
func get_state_snapshot() -> Dictionary:
var npc_snapshots: Array[Dictionary] = []
for npc in npcs:
npc_snapshots.append(
{
"id": npc.id,
"name": npc.npc_name,
"profession": npc.profession,
"hunger": npc.hunger,
"energy": npc.energy,
"strength": npc.strength,
"intelligence": npc.intelligence,
"task": npc.current_task,
"task_state": npc.task_state,
"task_duration": npc.task_duration,
"task_progress": npc.task_progress,
"target_id": String(npc.target_id),
"position": [npc.position.x, npc.position.y, npc.position.z],
"travel_target_position":
[
npc.travel_target_position.x,
npc.travel_target_position.y,
npc.travel_target_position.z
],
"has_travel_target": npc.has_travel_target,
"starvation_ticks": npc.starvation_ticks,
"is_dead": npc.is_dead
}
)
return {
"seed": simulation_seed,
"tick_count": tick_count,
"village":
{
"food": village.food,
"wood": village.wood,
"safety": village.safety,
"knowledge": village.knowledge
},
"npcs": npc_snapshots
}
func get_state_checksum() -> String:
return create_state_record().to_json().sha256_text()
@@ -1127,6 +1124,7 @@ func create_state_record() -> SimulationStateRecord:
record.village = VillageStateRecord.capture(village)
for npc in npcs:
record.npcs.append(NPCStateRecord.capture(npc))
animal_care.append_state_records(record)
var sorted_resource_ids: Array = resource_states.keys()
sorted_resource_ids.sort()
for resource_id in sorted_resource_ids:
@@ -1194,6 +1192,7 @@ func restore_state(record: SimulationStateRecord) -> bool:
for resource_record in record.resources:
resource_states[resource_record.get_node_id()] = resource_record
register_loaded_resource_nodes()
animal_care.restore_state_records(record.animals)
village_changed.emit(village)
state_restored.emit()
return true