feat: add deterministic goat routine
This commit is contained in:
@@ -19,10 +19,16 @@ signal narrative_event_requested(
|
||||
item_id: StringName,
|
||||
required_amount: float
|
||||
)
|
||||
signal routine_started(animal_id: StringName, site_id: StringName, target_position: Vector3)
|
||||
signal routine_completed(animal_id: StringName, site_id: StringName, position: Vector3)
|
||||
|
||||
const ROUTINE_STAY_TICKS := 40
|
||||
const ROUTINE_RETRY_TICKS := 10
|
||||
|
||||
var states: Dictionary = {}
|
||||
var economy: RefCounted
|
||||
var active_world_adapter: Node
|
||||
var current_tick := 0
|
||||
|
||||
|
||||
func configure(economy_service: RefCounted, world_adapter: Node) -> void:
|
||||
@@ -30,13 +36,19 @@ func configure(economy_service: RefCounted, world_adapter: Node) -> void:
|
||||
active_world_adapter = world_adapter
|
||||
|
||||
|
||||
func advance() -> void:
|
||||
func advance(simulation_tick: int) -> void:
|
||||
current_tick = simulation_tick
|
||||
var animal_ids: Array = states.keys()
|
||||
animal_ids.sort()
|
||||
for animal_id in animal_ids:
|
||||
var animal_state := states[animal_id] as AnimalStateRecord
|
||||
if animal_state != null:
|
||||
animal_state.advance_tick()
|
||||
if animal_state == null:
|
||||
continue
|
||||
animal_state.advance_tick()
|
||||
if animal_state.has_active_travel_target() and animal_state.needs_feed():
|
||||
animal_state.cancel_travel(current_tick + ROUTINE_RETRY_TICKS)
|
||||
elif animal_state.is_routine_due(current_tick):
|
||||
_begin_due_routine(animal_state)
|
||||
|
||||
|
||||
func register_loaded_nodes() -> void:
|
||||
@@ -74,6 +86,12 @@ func register_node(node: AnimalNode) -> bool:
|
||||
elif not animal_state.apply_definition(node):
|
||||
push_error("AnimalCareSystem: AnimalNode definition mismatch for '%s'" % node.animal_id)
|
||||
return false
|
||||
if not node.position_changed.is_connected(_on_node_position_changed):
|
||||
node.position_changed.connect(_on_node_position_changed)
|
||||
if not node.routine_arrived.is_connected(_on_node_routine_arrived):
|
||||
node.routine_arrived.connect(_on_node_routine_arrived)
|
||||
if not node.navigation_failed.is_connected(_on_node_navigation_failed):
|
||||
node.navigation_failed.connect(_on_node_navigation_failed)
|
||||
return node.bind_state(animal_state)
|
||||
|
||||
|
||||
@@ -190,6 +208,10 @@ func feed(animal_id: StringName, actor_id: int, npcs: Array[SimNPC], current_tic
|
||||
if not animal_state.feed(actor_id, current_tick):
|
||||
economy.deposit_resource(resource_id, consumed)
|
||||
return false
|
||||
if animal_state.has_active_travel_target():
|
||||
animal_state.cancel_travel(current_tick + ROUTINE_STAY_TICKS)
|
||||
else:
|
||||
animal_state.schedule_routine_at(current_tick + ROUTINE_STAY_TICKS)
|
||||
|
||||
economic_event_requested.emit(
|
||||
SimulationIds.EVENT_ANIMAL_FED,
|
||||
@@ -211,13 +233,70 @@ func append_state_records(record: SimulationStateRecord) -> void:
|
||||
record.animals.append(animal_state)
|
||||
|
||||
|
||||
func restore_state_records(records: Array[AnimalStateRecord]) -> void:
|
||||
func restore_state_records(records: Array[AnimalStateRecord], restored_tick: int) -> void:
|
||||
current_tick = restored_tick
|
||||
states.clear()
|
||||
for animal_record in records:
|
||||
states[animal_record.get_animal_id()] = animal_record
|
||||
register_loaded_nodes()
|
||||
|
||||
|
||||
func _begin_due_routine(animal_state: AnimalStateRecord) -> bool:
|
||||
if (
|
||||
active_world_adapter == null
|
||||
or not active_world_adapter.has_method("get_animal_routine_sites")
|
||||
):
|
||||
return false
|
||||
if AnimalNode.get_by_id(animal_state.get_animal_id()) == null:
|
||||
return false
|
||||
var candidates: Array[Dictionary] = active_world_adapter.get_animal_routine_sites(
|
||||
animal_state.get_species_id()
|
||||
)
|
||||
if candidates.is_empty():
|
||||
return false
|
||||
var current_site_id := animal_state.get_routine_site_id()
|
||||
for candidate in candidates:
|
||||
var candidate_id := StringName(candidate["site_id"])
|
||||
if candidate_id == current_site_id:
|
||||
continue
|
||||
var target_position: Vector3 = candidate["position"]
|
||||
if not animal_state.begin_travel(candidate_id, target_position):
|
||||
return false
|
||||
routine_started.emit(animal_state.get_animal_id(), candidate_id, target_position)
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _on_node_position_changed(animal_id: StringName, active_position: Vector3) -> void:
|
||||
var animal_state := get_state(animal_id)
|
||||
if animal_state != null and animal_state.has_active_travel_target():
|
||||
animal_state.set_position(active_position)
|
||||
|
||||
|
||||
func _on_node_routine_arrived(
|
||||
animal_id: StringName, site_id: StringName, arrival_position: Vector3
|
||||
) -> void:
|
||||
var animal_state := get_state(animal_id)
|
||||
if (
|
||||
animal_state == null
|
||||
or not animal_state.has_active_travel_target()
|
||||
or animal_state.get_travel_target_site_id() != site_id
|
||||
):
|
||||
return
|
||||
if animal_state.complete_travel(arrival_position, current_tick + ROUTINE_STAY_TICKS):
|
||||
routine_completed.emit(animal_id, site_id, arrival_position)
|
||||
|
||||
|
||||
func _on_node_navigation_failed(animal_id: StringName, site_id: StringName) -> void:
|
||||
var animal_state := get_state(animal_id)
|
||||
if (
|
||||
animal_state != null
|
||||
and animal_state.has_active_travel_target()
|
||||
and animal_state.get_travel_target_site_id() == site_id
|
||||
):
|
||||
animal_state.cancel_travel(current_tick + ROUTINE_RETRY_TICKS)
|
||||
|
||||
|
||||
static func _find_npc(npc_id: int, npcs: Array[SimNPC]) -> SimNPC:
|
||||
for npc in npcs:
|
||||
if npc.id == npc_id:
|
||||
Reference in New Issue
Block a user