feat: let opportunity helpers act autonomously
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
```text
|
||||
SimulationManager tick
|
||||
-> ActionExecutionSystem advances needs and working progress
|
||||
-> VillageOpportunitySystem re-derives the current capable helper
|
||||
-> ActionSelectionSystem chooses an action when the NPC is idle
|
||||
-> SimulationManager stores the selected action
|
||||
-> npc_target_requested
|
||||
@@ -24,6 +25,9 @@ SimulationManager tick
|
||||
- queries `RelationshipSystem` after personal survival/schedule overrides, so a
|
||||
trusted starving acquaintance can redirect ordinary work toward food
|
||||
gathering while the pantry is low;
|
||||
- accepts the current ephemeral `OpportunityHelperResult` and, only for its
|
||||
matching NPC, selects the reported ordinary gather/deposit action after
|
||||
urgent self-care and sleep/meal schedule branches;
|
||||
- evaluates current utility scores;
|
||||
- consumes the NPC's deterministic decision RNG;
|
||||
- returns an action ID, optional urgent-duration override, branch reason,
|
||||
@@ -68,9 +72,10 @@ SimulationManager remains the orchestrator and event boundary. It owns
|
||||
simulation records, invokes the focused systems, stores selected actions and
|
||||
targets, and translates presentation callbacks into simulation transitions.
|
||||
It also publishes the latest `ActionSelectionResult` for presentation; the UI
|
||||
does not recompute decisions. At completion it atomically pays any
|
||||
definition-backed stored-resource cost before applying the action effect. A
|
||||
late shortfall suppresses the effect and records a `task_blocked` fact. The
|
||||
does not recompute decisions. Each idle selection re-derives the capable helper
|
||||
instead of consulting persisted assignment state. At completion it atomically
|
||||
pays any definition-backed stored-resource cost before applying the action
|
||||
effect. A late shortfall suppresses the effect and records a `task_blocked` fact. The
|
||||
manager also captures actor/nearby knowledge before forwarding newly recorded
|
||||
events into `RelationshipSystem`. When an NPC arrives beside a worker at the
|
||||
same non-storage activity site, the manager may transfer one direct known fact
|
||||
|
||||
@@ -11,6 +11,7 @@ results.
|
||||
SimulationClock
|
||||
-> SimulationManager orchestrates one deterministic tick
|
||||
-> ActionExecutionSystem advances needs and work
|
||||
-> VillageOpportunitySystem re-derives the current capable helper
|
||||
-> ActionSelectionSystem chooses an action
|
||||
-> ActionTargetResolver resolves a stable target ID
|
||||
-> VillageEconomy performs inventory/storage transactions
|
||||
@@ -45,7 +46,8 @@ would otherwise obscure that lifecycle:
|
||||
open/resolved/invalidated lifecycle for the proven pantry-food and blocked-
|
||||
work wood consumers. It also derives one ephemeral `OpportunityHelperResult`
|
||||
from knowledge, directed relationships, inventory, action definitions, and
|
||||
finite-resource state without changing resources or assigning tasks;
|
||||
finite-resource state without changing resources or assigning tasks. The
|
||||
ordinary action selector consumes that result only for the matching idle NPC;
|
||||
- `simulation/persistence/` owns save-slot file safety;
|
||||
- `simulation/state/` owns versioned serialized record contracts;
|
||||
- `simulation/definitions/` owns stable IDs and immutable action/profession
|
||||
@@ -103,7 +105,9 @@ improving ownership.
|
||||
event IDs rather than object references or prose.
|
||||
- Opportunity records reference stable NPC, storage, resource, trigger-event,
|
||||
and resolution-event IDs. Their generator may observe authoritative state
|
||||
and history, but it does not mutate the economy or command NPC behavior.
|
||||
and history, but it does not mutate the economy or command NPC behavior. The
|
||||
manager re-queries capable helpers at idle selection boundaries; any selected
|
||||
supply task then follows the ordinary persisted NPC-task and target contracts.
|
||||
- Prefer one tested vertical behavior over a generic framework with no proven
|
||||
consumers.
|
||||
|
||||
|
||||
@@ -736,13 +736,19 @@ Completed:
|
||||
deposit action and real inventory/resource route that makes them capable.
|
||||
The result is re-derived after restore and does not assign work or alter the
|
||||
simulation.
|
||||
29. Autonomous capable helper: the matching idle villager now consumes that
|
||||
ephemeral result through ordinary action selection after urgent self-care
|
||||
and sleep/meal precedence. Existing target resolution chooses and reserves
|
||||
the finite source, while no quest acceptance, reward, or helper assignment
|
||||
is introduced.
|
||||
|
||||
Next:
|
||||
|
||||
1. Let the currently derived helper consume the query through ordinary
|
||||
autonomous action selection, after urgent self-care and schedule precedence.
|
||||
Re-query instead of persisting assignment, and keep acceptance/rewards out
|
||||
of scope, as sequenced in `LEARNING_ROADMAP.md`.
|
||||
1. Prove one direct information-to-help chain: transfer the exact open-need
|
||||
trigger at an existing shared activity, then let the newly informed trusted
|
||||
villager be re-derived and respond through the same autonomous selector.
|
||||
Keep generic dialogue and multi-hop rumours out of scope, as sequenced in
|
||||
`LEARNING_ROADMAP.md`.
|
||||
|
||||
Do not start with GIS data, a full city, a large asset pack, or more NPC
|
||||
mechanics. The next proof is a beautiful stage for the systems that already
|
||||
|
||||
+13
-3
@@ -112,6 +112,15 @@ does not select an exact gather target; that remains with `ActiveWorldAdapter`
|
||||
and `ActionTargetResolver`. No event, reservation, RNG draw, task mutation, or
|
||||
serialized assignment is created.
|
||||
|
||||
At an idle NPC decision boundary, `SimulationManager` performs that query again
|
||||
and gives the ephemeral result to `ActionSelectionSystem`. Personal survival,
|
||||
low-energy rest, mourning, and sleep/meal schedule branches retain precedence;
|
||||
otherwise only the named helper selects the reported ordinary gather/deposit
|
||||
action. From there, existing target resolution chooses and reserves an exact
|
||||
finite resource or storage target. The current NPC task may serialize normally
|
||||
while in progress, but no opportunity-to-helper assignment or acceptance state
|
||||
exists in the save.
|
||||
|
||||
The food-loop regression verifies this chain:
|
||||
|
||||
```text
|
||||
@@ -151,6 +160,7 @@ hearing, personalized reinforcement/decay, multi-hop rumours, secrecy, false
|
||||
beliefs, and multi-event causal graphs belong in later event/history slices.
|
||||
They should extend this record family without making prose authoritative or
|
||||
recomputing old evidence from current positions.
|
||||
The current opportunity family is likewise a bounded two-consumer projection
|
||||
with one read-only helper query, not a generic quest, reward, acceptance,
|
||||
dialogue, helper assignment, or capable-helper framework.
|
||||
The current opportunity family is likewise a bounded two-type projection with
|
||||
one read-only helper query and one ordinary action-selection consumer, not a
|
||||
generic quest, reward, acceptance, dialogue, helper assignment, or capable-
|
||||
helper framework.
|
||||
|
||||
@@ -792,11 +792,28 @@ The first bounded capable-helper query is complete:
|
||||
- food and wood scenarios re-derive the same result after restore, and the
|
||||
village summary explains the current helper route from real simulation facts.
|
||||
|
||||
Milestone 7 is not complete. The immediate next slice is one bounded autonomous
|
||||
consumer of this query: let the currently derived helper choose the reported
|
||||
gather/deposit action through ordinary selection while preserving urgent
|
||||
self-care and schedule precedence. Re-query rather than persisting assignment,
|
||||
and do not add quest acceptance or reward state.
|
||||
The first bounded autonomous helper consumer is complete:
|
||||
|
||||
- when an NPC becomes idle, `SimulationManager` re-derives the current helper
|
||||
and passes that ephemeral result into ordinary action selection;
|
||||
- only the matching helper can select the reported gather/deposit action, and
|
||||
starvation, critical hunger, mourning, low energy, sleep, and meal behavior
|
||||
keep precedence;
|
||||
- the resulting task uses the existing definition, target resolver, finite-node
|
||||
reservation, travel, extraction, inventory, and storage transaction paths;
|
||||
- no helper assignment, acceptance, reward, opportunity-specific event, or RNG
|
||||
draw is added, while an in-progress ordinary task continues through the
|
||||
existing NPC save fields and task-start history;
|
||||
- focused headless and Jajce runtime regressions prove autonomous selection,
|
||||
precedence, finite-source targeting, reservation, and restore-time re-query.
|
||||
|
||||
Milestone 7 is not complete. The immediate next slice should connect the
|
||||
existing one-hop fact transfer to this path: prove that a direct witness or
|
||||
performer can communicate an open need's exact trigger at a real shared
|
||||
activity, after which the newly informed trusted villager is re-derived as the
|
||||
helper and responds through the same autonomous selection contract. Keep this
|
||||
as a truthful event-reference transfer, not generic dialogue or multi-hop
|
||||
rumour infrastructure.
|
||||
|
||||
Recently completed:
|
||||
|
||||
|
||||
+18
-6
@@ -619,8 +619,10 @@ These are expected prototype constraints, not necessarily isolated bugs:
|
||||
`supply_missing_wood` need from exact known evidence. Both have exact NPC and
|
||||
player supply resolutions; the wood need also closes deterministically on
|
||||
interested-party death or one-day staleness. One read-only query derives and
|
||||
explains a capable helper, but there is still no helper assignment,
|
||||
acceptance, rewards, free-form dialogue, or quest log.
|
||||
explains a capable helper, and the matching idle NPC can consume it through
|
||||
ordinary action selection after urgent self-care and schedule precedence.
|
||||
There is still no helper assignment, acceptance, rewards, free-form dialogue,
|
||||
or quest log.
|
||||
- The reason inspector exposes current decisions, utility rejections, one exact
|
||||
relationship cause, and a compact person-history view that distinguishes
|
||||
importance-ranked retained memories from objective personal actions.
|
||||
@@ -911,10 +913,20 @@ produce one deterministic result. The village summary explains that result;
|
||||
save/load re-derives it, and querying changes no task, reservation, RNG stream,
|
||||
resource, event, or checksum.
|
||||
|
||||
Milestone 7 remains in progress. The immediate next slice is one bounded
|
||||
autonomous consumer: allow the currently derived helper to choose its reported
|
||||
ordinary supply action after urgent self-care and schedule rules, without
|
||||
persisted assignment, quest acceptance, or reward state.
|
||||
The first autonomous helper consumer is complete. At each idle decision
|
||||
boundary the manager re-derives capability, and only the matching helper may
|
||||
select the reported ordinary gather/deposit action. Urgent self-care and
|
||||
sleep/meal behavior still win; accepted supply work uses the existing target
|
||||
resolver, finite-node reservation, travel, inventory, and transaction paths.
|
||||
The ordinary in-progress NPC task can save normally, but no helper assignment,
|
||||
quest acceptance, reward, or opportunity-specific event is persisted; ordinary
|
||||
task-start history remains unchanged.
|
||||
|
||||
Milestone 7 remains in progress. The immediate next slice should prove the
|
||||
combined information-to-help path: communicate an open need's exact trigger
|
||||
once at a real shared activity, then re-derive the newly informed trusted NPC as
|
||||
the autonomous helper. Keep the transfer direct and source-backed rather than
|
||||
introducing generic dialogue or multi-hop rumours.
|
||||
|
||||
The remaining simulation-garden target still aims for:
|
||||
|
||||
|
||||
@@ -202,6 +202,11 @@ relationship, inventory, action-definition, storage-capacity, and finite-
|
||||
resource records. Save/restore regressions require the same helper/action/route
|
||||
result and an unchanged checksum before and after querying.
|
||||
|
||||
When an idle derived helper chooses the reported supply action, only the
|
||||
existing NPC task, task-state, target, travel, and reservation fields serialize.
|
||||
The helper result remains absent: restore retains any in-progress ordinary task
|
||||
and independently re-derives current capability from restored facts.
|
||||
|
||||
## Resource authority
|
||||
|
||||
`SimulationManager` owns `ResourceStateRecord` instances independently of the
|
||||
|
||||
@@ -156,7 +156,6 @@ func simulate_tick() -> void:
|
||||
tick_count += 1
|
||||
if debug_logs:
|
||||
print("--- Tick ", tick_count, " ---")
|
||||
|
||||
var village_was_changed := false
|
||||
for npc in npcs:
|
||||
village_was_changed = _simulate_npc_tick(npc) or village_was_changed
|
||||
@@ -207,7 +206,8 @@ func _select_action_if_idle(npc: SimNPC, previous_state: StringName) -> void:
|
||||
return
|
||||
if npc.task_state not in [SimNPC.TASK_STATE_IDLE, SimNPC.TASK_STATE_COMPLETE]:
|
||||
return
|
||||
var selection := action_selector.select_action(npc, village, clock.time_of_day(), npcs)
|
||||
var helper := get_active_opportunity_helper()
|
||||
var selection := action_selector.select_action(npc, village, clock.time_of_day(), npcs, helper)
|
||||
if selection == null:
|
||||
return
|
||||
latest_decisions[npc.id] = selection
|
||||
|
||||
@@ -18,7 +18,11 @@ var relationship_system: RefCounted
|
||||
|
||||
|
||||
func select_action(
|
||||
npc: SimNPC, village: SimVillage, time_of_day: float = 0.5, all_npcs: Array = []
|
||||
npc: SimNPC,
|
||||
village: SimVillage,
|
||||
time_of_day: float = 0.5,
|
||||
all_npcs: Array = [],
|
||||
opportunity_helper: OpportunityHelperResult = null
|
||||
) -> ActionSelectionResult:
|
||||
if npc.is_dead:
|
||||
return null
|
||||
@@ -98,6 +102,17 @@ func select_action(
|
||||
return ActionSelectionResult.new(
|
||||
SimulationIds.ACTION_GATHER_FOOD, -1.0, "Hungry; pantry is empty"
|
||||
)
|
||||
var is_opportunity_helper := _is_current_opportunity_helper(npc, opportunity_helper)
|
||||
if is_opportunity_helper and npc.energy < 25.0:
|
||||
return ActionSelectionResult.new(
|
||||
SimulationIds.ACTION_REST, -1.0, "Energy is low; resting before helping"
|
||||
)
|
||||
if is_opportunity_helper:
|
||||
return ActionSelectionResult.new(
|
||||
opportunity_helper.action_id,
|
||||
-1.0,
|
||||
"Responding to village need: %s" % opportunity_helper.reason
|
||||
)
|
||||
if npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD) > 0.0:
|
||||
return ActionSelectionResult.new(
|
||||
SimulationIds.ACTION_DEPOSIT_FOOD, -1.0, "Carrying food for storage"
|
||||
@@ -131,6 +146,24 @@ func select_action(
|
||||
return _choose_best_work_action(npc, village)
|
||||
|
||||
|
||||
func _is_current_opportunity_helper(
|
||||
npc: SimNPC, opportunity_helper: OpportunityHelperResult
|
||||
) -> bool:
|
||||
return (
|
||||
opportunity_helper != null
|
||||
and opportunity_helper.helper_npc_id == npc.id
|
||||
and (
|
||||
opportunity_helper.action_id
|
||||
in [
|
||||
SimulationIds.ACTION_GATHER_FOOD,
|
||||
SimulationIds.ACTION_GATHER_WOOD,
|
||||
SimulationIds.ACTION_DEPOSIT_FOOD,
|
||||
SimulationIds.ACTION_DEPOSIT_WOOD,
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func _choose_best_work_action(npc: SimNPC, village: SimVillage) -> ActionSelectionResult:
|
||||
var scores := {
|
||||
SimulationIds.ACTION_GATHER_FOOD:
|
||||
|
||||
@@ -28,6 +28,7 @@ func _initialize() -> void:
|
||||
|
||||
func _run() -> void:
|
||||
_test_selection_and_execution_are_separate()
|
||||
_test_opportunity_helper_selection_preserves_precedence()
|
||||
_test_target_resolution_and_travel_are_separate()
|
||||
|
||||
if failures.is_empty():
|
||||
@@ -91,6 +92,57 @@ func _test_selection_and_execution_are_separate() -> void:
|
||||
)
|
||||
|
||||
|
||||
func _test_opportunity_helper_selection_preserves_precedence() -> void:
|
||||
var village := SimVillage.new()
|
||||
village.food = 100.0
|
||||
village.wood = 100.0
|
||||
village.safety = 100.0
|
||||
village.knowledge = 100.0
|
||||
village.update_modifiers()
|
||||
village.update_priorities()
|
||||
var npc := SimNPC.new(702, "OpportunityHelper", SimulationIds.PROFESSION_WANDERER, 5.0, 5.0)
|
||||
npc.hunger = 20.0
|
||||
npc.energy = 80.0
|
||||
var helper := _helper_result(npc.id, SimulationIds.ACTION_GATHER_WOOD)
|
||||
var selector := ActionSelectionSystem.new()
|
||||
var selection := selector.select_action(npc, village, 0.5, [npc], helper)
|
||||
_check(
|
||||
(
|
||||
selection.action_id == SimulationIds.ACTION_GATHER_WOOD
|
||||
and "Responding to village need" in selection.reason
|
||||
and "Knows the need" in selection.reason
|
||||
),
|
||||
"The matching derived helper should choose its reported ordinary supply action"
|
||||
)
|
||||
|
||||
npc.energy = 20.0
|
||||
selection = selector.select_action(npc, village, 0.5, [npc], helper)
|
||||
_check(
|
||||
selection.action_id == SimulationIds.ACTION_REST,
|
||||
"Urgent low energy should take precedence over an opportunity response"
|
||||
)
|
||||
npc.energy = 50.0
|
||||
selection = selector.select_action(npc, village, 0.0, [npc], helper)
|
||||
_check(
|
||||
selection.action_id == SimulationIds.ACTION_SLEEP,
|
||||
"The sleep schedule should take precedence over an opportunity response"
|
||||
)
|
||||
npc.energy = 80.0
|
||||
npc.hunger = 90.0
|
||||
selection = selector.select_action(npc, village, 0.5, [npc], helper)
|
||||
_check(
|
||||
selection.action_id == SimulationIds.ACTION_WITHDRAW_FOOD,
|
||||
"Critical hunger should take precedence over an opportunity response"
|
||||
)
|
||||
npc.hunger = 20.0
|
||||
helper = _helper_result(npc.id + 1, SimulationIds.ACTION_GATHER_WOOD)
|
||||
selection = selector.select_action(npc, village, 0.5, [npc], helper)
|
||||
_check(
|
||||
"Responding to village need" not in selection.reason,
|
||||
"A helper result must not redirect a different NPC"
|
||||
)
|
||||
|
||||
|
||||
func _test_target_resolution_and_travel_are_separate() -> void:
|
||||
var adapter := FakeActiveWorld.new()
|
||||
adapter.resource_candidates = [
|
||||
@@ -185,6 +237,28 @@ func _on_travel_requested(_npc: SimNPC, target_position: Vector3) -> void:
|
||||
travel_requests.append(target_position)
|
||||
|
||||
|
||||
func _helper_result(helper_id: int, action_id: StringName) -> OpportunityHelperResult:
|
||||
return (
|
||||
OpportunityHelperResult
|
||||
. new(
|
||||
{
|
||||
"opportunity_id": 9,
|
||||
"helper_npc_id": helper_id,
|
||||
"action_id": action_id,
|
||||
"source_id": "",
|
||||
"resource_id": SimulationIds.RESOURCE_WOOD,
|
||||
"trigger_event_id": 14,
|
||||
"trust": 0.7,
|
||||
"familiarity": 0.8,
|
||||
"uses_inventory": false,
|
||||
"available_source_count": 1,
|
||||
"profession_match": false,
|
||||
"reason": "Knows the need; trust 0.70 toward Neighbour; has 1 finite source",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func _check(condition: bool, message: String) -> void:
|
||||
if not condition:
|
||||
failures.append(message)
|
||||
|
||||
@@ -317,6 +317,30 @@ func _run() -> void:
|
||||
),
|
||||
"A real missing-wood fact should surface through wood-specific UI without a food cue"
|
||||
)
|
||||
witness.current_task = SimulationIds.ACTION_IDLE
|
||||
witness.task_state = SimNPC.TASK_STATE_IDLE
|
||||
witness.task_complete = true
|
||||
witness.target_id = &""
|
||||
witness.has_travel_target = false
|
||||
witness.hunger = 20.0
|
||||
witness.energy = 80.0
|
||||
witness.mourning_ticks = 0
|
||||
simulation_manager.simulate_tick()
|
||||
var runtime_decision: ActionSelectionResult = simulation_manager.get_latest_decision(witness.id)
|
||||
var helper_resource: ResourceStateRecord = simulation_manager.get_resource_state(
|
||||
witness.target_id
|
||||
)
|
||||
_check(
|
||||
(
|
||||
runtime_decision != null
|
||||
and runtime_decision.action_id == SimulationIds.ACTION_GATHER_WOOD
|
||||
and "Responding to village need" in runtime_decision.reason
|
||||
and witness.task_state == SimNPC.TASK_STATE_TRAVELING
|
||||
and helper_resource != null
|
||||
and helper_resource.get_reserved_by() == witness.id
|
||||
),
|
||||
"The runtime helper should autonomously enter normal target resolution and reservation"
|
||||
)
|
||||
contributor.add_inventory(SimulationIds.RESOURCE_WOOD, 1.0)
|
||||
_check(
|
||||
is_equal_approx(
|
||||
|
||||
@@ -109,13 +109,16 @@ func _run() -> void:
|
||||
)
|
||||
|
||||
manager.simulate_tick()
|
||||
var deposit_decision: ActionSelectionResult = manager.get_latest_decision(helper.id)
|
||||
_check(
|
||||
(
|
||||
helper.current_task == SimulationIds.ACTION_DEPOSIT_FOOD
|
||||
and helper.target_id == SimulationIds.STORAGE_VILLAGE_PANTRY
|
||||
and helper.has_travel_target
|
||||
and deposit_decision != null
|
||||
and "Responding to village need" in deposit_decision.reason
|
||||
),
|
||||
"The helper should autonomously choose the real pantry deposit after gathering"
|
||||
"The re-derived helper should autonomously choose the real pantry deposit"
|
||||
)
|
||||
helper_visual.global_position = helper.travel_target_position
|
||||
manager.synchronize_npc_position(helper.id, helper_visual.global_position)
|
||||
|
||||
@@ -107,6 +107,26 @@ func _run() -> void:
|
||||
manager.get_state_checksum() == checksum_before_helper_query,
|
||||
"Capable-helper discovery should not mutate authoritative simulation state"
|
||||
)
|
||||
witness.current_task = SimulationIds.ACTION_IDLE
|
||||
witness.task_state = SimNPC.TASK_STATE_IDLE
|
||||
witness.task_complete = true
|
||||
witness.target_id = &""
|
||||
witness.has_travel_target = false
|
||||
witness.hunger = 20.0
|
||||
witness.energy = 80.0
|
||||
witness.mourning_ticks = 0
|
||||
manager.simulate_tick()
|
||||
var helper_decision: ActionSelectionResult = manager.get_latest_decision(witness.id)
|
||||
_check(
|
||||
(
|
||||
helper_decision != null
|
||||
and helper_decision.action_id == helper_result.action_id
|
||||
and "Responding to village need" in helper_decision.reason
|
||||
and witness.current_task == SimulationIds.ACTION_GATHER_WOOD
|
||||
and witness.task_state == SimNPC.TASK_STATE_TRAVELING
|
||||
),
|
||||
"An idle derived helper should autonomously select the ordinary wood supply action"
|
||||
)
|
||||
|
||||
var active_json: String = manager.serialize_state()
|
||||
var active_restored := _create_manager(1202)
|
||||
@@ -127,8 +147,12 @@ func _run() -> void:
|
||||
"Active restore should preserve opportunity identity and protected evidence"
|
||||
)
|
||||
_check(
|
||||
restored_helper != null and restored_helper.helper_npc_id == witness.id,
|
||||
"Active restore should re-derive the same capable helper"
|
||||
(
|
||||
restored_helper != null
|
||||
and restored_helper.helper_npc_id == witness.id
|
||||
and active_restored.npcs[witness.id].current_task == SimulationIds.ACTION_GATHER_WOOD
|
||||
),
|
||||
"Active restore should keep the ordinary task and re-derive the helper without assignment state"
|
||||
)
|
||||
active_restored.free()
|
||||
|
||||
|
||||
@@ -222,11 +222,17 @@ func begin_pantry_restock() -> bool:
|
||||
var interested := _get_npc(_interested_villager_id)
|
||||
if helper == null or interested == null or simulation_manager.get_active_opportunity() == null:
|
||||
return false
|
||||
var opportunity_helper: OpportunityHelperResult = (
|
||||
simulation_manager.get_active_opportunity_helper()
|
||||
)
|
||||
if opportunity_helper == null or opportunity_helper.helper_npc_id != helper.id:
|
||||
return false
|
||||
var selection: ActionSelectionResult = simulation_manager.action_selector.select_action(
|
||||
helper,
|
||||
simulation_manager.village,
|
||||
simulation_manager.clock.time_of_day(),
|
||||
simulation_manager.npcs
|
||||
simulation_manager.npcs,
|
||||
opportunity_helper
|
||||
)
|
||||
if (
|
||||
selection == null
|
||||
|
||||
Reference in New Issue
Block a user