feat: package pantry crisis demo

This commit is contained in:
Rijad Zuzo
2026-07-16 00:37:59 +02:00
parent c23f6887c8
commit f82d9683de
12 changed files with 621 additions and 21 deletions
+12 -5
View File
@@ -503,6 +503,15 @@ Jasmin—rather than an unnamed cinematic extra.
The clip has an understandable beginning, decision, and consequence. It does not The clip has an understandable beginning, decision, and consequence. It does not
depend on a voiceover to prove that the simulation is real. depend on a voiceover to prove that the simulation is real.
**Status: complete.** `F11` reloads the fixed authored start and runs one
cinematic pantry-crisis story: a brief valley establishment, the empty pantry
and worried villager, a trusted helper's real finite-resource trip, visible
carrying, deposit, and physical recovery. Four short cardless captions fade over
the world while the camera hands off from the pantry to the helper. The same
withdrawal, consumption, extraction, and deposit records drive the need and its
resolution. `tools/validate_pantry_crisis_demo.gd` verifies that the unassisted
route completes inside the 2030 second budget.
## Build-in-public content pillars ## Build-in-public content pillars
Rotate between four types of updates. Rotate between four types of updates.
@@ -720,11 +729,9 @@ Completed:
Next: Next:
1. Package one 2030 second, repeatable pantry-crisis sequence that communicates 1. Resume the second bounded opportunity proof from a real missing-wood
shortage, purposeful gathering, visible carrying, deposit, and recovery `task_blocked` fact. Include deterministic interested-party death/staleness
without debug UI. Treat any art or timing work as support for that single handling before extracting shared opportunity machinery, as sequenced in
readable cause-and-effect loop. Resume the second missing-wood opportunity
proof after this public-demo exit test, as sequenced in
`LEARNING_ROADMAP.md`. `LEARNING_ROADMAP.md`.
Do not start with GIS data, a full city, a large asset pack, or more NPC Do not start with GIS data, a full city, a large asset pack, or more NPC
+20 -8
View File
@@ -742,14 +742,26 @@ complete:
- the repeatable runtime capture now includes an empty/restocked close pair in - the repeatable runtime capture now includes an empty/restocked close pair in
the established elevated third-person visual language. the established elevated third-person visual language.
Milestone 7 is not complete. The immediate outcome-defined slice is now one The outcome-defined pantry-crisis demo loop is complete:
short, repeatable pantry-crisis sequence that reads without debug UI: shortage,
interested villager, finite-resource trip, visible carrying, deposit, and - `F11` reloads the authored seed and runs a measured 2030 second cinematic
physical recovery. Improve route timing, work feedback, and village composition sequence with the development UI hidden;
only where that sequence exposes a readability problem. Once that public-demo - a real final-food withdrawal and consumption leave the physical pantry empty
loop passes, prove a second bounded unresolved-condition consumer from a real and open the knowledge-gated need for its worried witness;
missing-wood `task_blocked` fact, include deterministic interested-party - a trusted acquaintance selects ordinary `gather_food`, reserves and visits a
death/staleness handling, and only then extract shared opportunity machinery. finite berry source, visibly carries its yield home, and autonomously selects
the real pantry deposit;
- that exact deposit resolves the opportunity, restores the physical stock,
clears concern, and triggers the existing refill response;
- restrained captions and a camera handoff clarify the three beats without
replacing state-driven world cues, while a runtime validator measures the
unassisted route and a headless scenario verifies its full event chain.
Milestone 7 is not complete. The immediate next slice is the second bounded
unresolved-condition consumer from a real missing-wood `task_blocked` fact,
including deterministic interested-party death/staleness handling. Extract
shared opportunity machinery only after that second consumer proves the common
contract.
Recently completed: Recently completed:
+12 -5
View File
@@ -882,11 +882,18 @@ facts now have a cardless world expression: empty/low/stocked pantry props, one
interested-villager concern cue, and a brief refill response. Save/load rebuilds interested-villager concern cue, and a brief refill response. Save/load rebuilds
the stable state without replaying the response. the stable state without replaying the response.
Milestone 7 remains in progress. The immediate next slice is an outcome-defined The outcome-defined pantry-crisis demo loop is complete. `F11` reloads a fixed
2030 second pantry-crisis demo loop—shortage, finite-resource travel, visible authored start and packages a measured 2030 second cinematic story with brief
carrying, deposit, and physical recovery—without debug UI. The second captions: real withdrawal and consumption expose the shortage, a worried
missing-wood consumer and deterministic interested-party death/staleness rules informed villager remains at the empty pantry, and a trusted helper selects a
follow that visual exit test, before common opportunity machinery is extracted. finite berry source, visibly carries its yield, deposits it, and causes the
physical recovery. A headless lifecycle scenario and an unassisted runtime
timing validator cover the complete path.
Milestone 7 remains in progress. The immediate next slice is the second
missing-wood consumer from a real `task_blocked` fact, including deterministic
interested-party death/staleness handling before common opportunity machinery
is extracted.
The remaining simulation-garden target still aims for: The remaining simulation-garden target still aims for:
+35 -1
View File
@@ -126,7 +126,41 @@ offset_right = 940.0
offset_bottom = 66.0 offset_bottom = 66.0
script = ExtResource("12_timedial") script = ExtResource("12_timedial")
[node name="DemoController" type="Node" parent="." unique_id=644713371 node_paths=PackedStringArray("ui", "active_npcs_parent")] [node name="CrisisCaptionLayer" type="CanvasLayer" parent="."]
layer = 4
[node name="CrisisCaption" type="Label" parent="CrisisCaptionLayer"]
visible = false
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -560.0
offset_top = -112.0
offset_right = 560.0
offset_bottom = -54.0
grow_horizontal = 2
grow_vertical = 0
mouse_filter = 2
theme_override_colors/font_color = Color(0.98, 0.92, 0.78, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.055, 0.035, 0.92)
theme_override_colors/font_shadow_color = Color(0.02, 0.015, 0.01, 0.7)
theme_override_constants/outline_size = 9
theme_override_constants/shadow_offset_x = 2
theme_override_constants/shadow_offset_y = 3
theme_override_font_sizes/font_size = 26
text = "The pantry is empty."
horizontal_alignment = 1
vertical_alignment = 1
[node name="DemoController" type="Node" parent="." unique_id=644713371 node_paths=PackedStringArray("ui", "active_npcs_parent", "simulation_manager", "world_view_manager", "camera_rig", "player", "pantry_storage", "crisis_caption")]
script = ExtResource("13_demo") script = ExtResource("13_demo")
ui = NodePath("../UI") ui = NodePath("../UI")
active_npcs_parent = NodePath("../ActiveNPCs") active_npcs_parent = NodePath("../ActiveNPCs")
simulation_manager = NodePath("../SimulationManager")
world_view_manager = NodePath("../WorldViewManager")
camera_rig = NodePath("../CameraRig")
player = NodePath("../Player")
pantry_storage = NodePath("../JajceWorld/WorldObjects/StorageSites/VillagePantry")
crisis_caption = NodePath("../CrisisCaptionLayer/CrisisCaption")
+11
View File
@@ -327,6 +327,17 @@ func set_target_position(pos: Vector3) -> void:
debug_log("New target: %s Path points: %s" % [pos, current_path.size()]) debug_log("New target: %s Path points: %s" % [pos, current_path.size()])
func stop_travel() -> void:
path_request_id += 1
current_path = PackedVector3Array()
path_index = 0
current_target = Vector3.INF
path_pending = false
has_reported_arrival = true
stuck_time = 0.0
velocity = Vector3.ZERO
func _physics_process(delta: float) -> void: func _physics_process(delta: float) -> void:
if is_dead_visual: if is_dead_visual:
velocity = Vector3.ZERO velocity = Vector3.ZERO
+169
View File
@@ -0,0 +1,169 @@
extends SceneTree
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var main_scene: Node = load("res://main.tscn").instantiate()
root.add_child(main_scene)
await process_frame
for _frame in 10:
await physics_frame
var manager := main_scene.get_node("SimulationManager")
var world_view := main_scene.get_node("WorldViewManager")
var demo := main_scene.get_node("DemoController")
var caption := main_scene.get_node("CrisisCaptionLayer/CrisisCaption") as Label
manager.set_process(false)
var staged: Dictionary = demo.stage_pantry_crisis()
_check(not staged.is_empty(), "The repeatable pantry crisis should stage from a fresh scene")
if staged.is_empty():
_finish()
return
var helper: SimNPC = staged["helper"]
var interested: SimNPC = staged["interested"]
var shortage_actor: SimNPC = staged["shortage_actor"]
var opportunity: OpportunityStateRecord = staged["opportunity"]
var pantry: StorageStateRecord = manager.get_pantry()
var helper_visual: Node3D = world_view.active_npc_visuals.get(helper.id)
var interested_visual: Node3D = world_view.active_npc_visuals.get(interested.id)
_check(
is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 0.0),
"The demo shortage should empty the authoritative pantry"
)
_check(
is_equal_approx(shortage_actor.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 0.0),
"The final pantry unit should be consumed through the real economy before the food run"
)
_check(
(
opportunity.get_interested_npc_id() == interested.id
and interested_visual.get_node("OpportunityConcernRoot").visible
and interested_visual.current_target == Vector3.INF
),
"The informed hungry villager should own and visibly express the open need"
)
_check(
not caption.visible,
"The cinematic caption should remain hidden until the full sequence starts"
)
_check(demo.begin_pantry_restock(), "A trusted helper should begin the real food run")
var decision: ActionSelectionResult = manager.get_latest_decision(helper.id)
_check(
(
decision != null
and decision.action_id == SimulationIds.ACTION_GATHER_FOOD
and interested.npc_name in decision.reason
),
"The helper's selected action should be caused by the starving trusted villager"
)
_check(
(
helper.task_state == SimNPC.TASK_STATE_TRAVELING
and helper.has_travel_target
and not helper.target_id.is_empty()
),
"The helper should reserve and travel to a finite food ResourceNode"
)
var resource_state: ResourceStateRecord = manager.get_resource_state(helper.target_id)
_check(
(
resource_state != null
and resource_state.get_resource_id() == SimulationIds.RESOURCE_FOOD
and resource_state.get_reserved_by() == helper.id
),
"The demo trip should use the normal finite-resource reservation contract"
)
helper_visual.global_position = helper.travel_target_position
manager.synchronize_npc_position(helper.id, helper_visual.global_position)
manager.notify_npc_arrived(helper.id)
for _tick in int(ceil(helper.task_duration)):
manager.simulate_tick()
_check(
(
helper.get_inventory_amount(SimulationIds.RESOURCE_FOOD) > 0.0
and helper_visual.get_node("CarriedFood").visible
),
"Completing the real gather action should create visible carried food"
)
manager.simulate_tick()
_check(
(
helper.current_task == SimulationIds.ACTION_DEPOSIT_FOOD
and helper.target_id == SimulationIds.STORAGE_VILLAGE_PANTRY
and helper.has_travel_target
),
"The helper should autonomously choose the real pantry deposit after gathering"
)
helper_visual.global_position = helper.travel_target_position
manager.synchronize_npc_position(helper.id, helper_visual.global_position)
manager.notify_npc_arrived(helper.id)
manager.simulate_tick()
var pantry_visual := (
main_scene.get_node("JajceWorld/WorldObjects/StorageSites/VillagePantry/PantryPresentation")
as PantryStockVisual
)
var resolved: OpportunityStateRecord = manager.opportunity_system.get_latest()
_check(
(
manager.get_active_opportunity() == null
and resolved != null
and resolved.get_status() == SimulationIds.OPPORTUNITY_STATUS_RESOLVED
and resolved.get_resolution_event_id() > resolved.get_trigger_event_id()
),
"The exact later deposit event should resolve the same opportunity record"
)
_check(
(
pantry.get_amount(SimulationIds.RESOURCE_FOOD) > 0.0
and pantry_visual.get_node("RefillResponse").visible
and not interested_visual.get_node("OpportunityConcernRoot").visible
),
"The deposit should physically restock the pantry and clear the concern cue"
)
_check(
(
_has_event(manager.economic_events, SimulationIds.EVENT_RESOURCE_EXTRACTED, helper.id)
and _has_event(
manager.economic_events, SimulationIds.EVENT_STORAGE_DEPOSITED, helper.id
)
),
"The sequence should be traceable through real extraction and deposit events"
)
_finish()
func _has_event(events: Array[EconomicEventRecord], event_type: StringName, actor_id: int) -> bool:
for event in events:
if (
StringName(event.data["event_type"]) == event_type
and int(event.data["actor_id"]) == actor_id
):
return true
return false
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
func _finish() -> void:
if failures.is_empty():
print("[TEST] Pantry crisis demo scenario passed")
quit(0)
return
for failure in failures:
push_error("[TEST] " + failure)
quit(1)
+1
View File
@@ -0,0 +1 @@
uid://drf7vq0pk51ys
+58
View File
@@ -0,0 +1,58 @@
extends SceneTree
const MINIMUM_DURATION_SECONDS := 20.0
const MAXIMUM_DURATION_SECONDS := 30.0
const HARD_TIMEOUT_SECONDS := 36.0
var completed := false
var failed_reason := ""
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var main_scene: Node = load("res://main.tscn").instantiate()
root.add_child(main_scene)
await process_frame
for _frame in 10:
await physics_frame
var demo := main_scene.get_node("DemoController")
demo.pantry_crisis_completed.connect(_on_demo_completed)
demo.pantry_crisis_failed.connect(_on_demo_failed)
var started_at := Time.get_ticks_msec()
demo.start_pantry_crisis_demo()
while not completed and failed_reason.is_empty():
var elapsed := (Time.get_ticks_msec() - started_at) / 1000.0
if elapsed > HARD_TIMEOUT_SECONDS:
failed_reason = "Timed out after %.1f seconds" % elapsed
break
await process_frame
var duration := (Time.get_ticks_msec() - started_at) / 1000.0
if not failed_reason.is_empty():
push_error("[TOOL] Pantry crisis demo failed: %s" % failed_reason)
quit(1)
return
if duration < MINIMUM_DURATION_SECONDS or duration > MAXIMUM_DURATION_SECONDS:
push_error(
(
"[TOOL] Pantry crisis demo took %.1f seconds; expected %.0f%.0f"
% [duration, MINIMUM_DURATION_SECONDS, MAXIMUM_DURATION_SECONDS]
)
)
quit(1)
return
print("[TOOL] Pantry crisis demo completed in %.1f seconds" % duration)
quit(0)
func _on_demo_completed(_helper_id: int, _resolution_event_id: int) -> void:
completed = true
func _on_demo_failed(reason: String) -> void:
failed_reason = reason
+1
View File
@@ -0,0 +1 @@
uid://be73skpg0q6ub
+300 -1
View File
@@ -1,14 +1,42 @@
extends Node extends Node
signal pantry_crisis_staged(helper_id: int, interested_id: int)
signal pantry_crisis_completed(helper_id: int, resolution_event_id: int)
signal pantry_crisis_failed(reason: String)
const FEATURED_HELPER_INDEX := 0
const INTERESTED_VILLAGER_INDEX := 1
const SHORTAGE_ACTOR_INDEX := 2
const VALLEY_ESTABLISH_SECONDS := 2.4
const ESTABLISH_SECONDS := 2.4
const RECOVERY_SECONDS := 5.7
const DEMO_TIMEOUT_SECONDS := 32.0
static var start_pantry_crisis_after_reload := false
@export var ui: CanvasLayer @export var ui: CanvasLayer
@export var active_npcs_parent: Node @export var active_npcs_parent: Node
@export var simulation_manager: Node
@export var world_view_manager: Node
@export var camera_rig: Node3D
@export var player: CharacterBody3D
@export var pantry_storage: StorageNode
@export var crisis_caption: Label
@export var debug_overlay_visible := true @export var debug_overlay_visible := true
var _refresh_accumulator := 0.0 var _refresh_accumulator := 0.0
var _caption_tween: Tween
var _demo_running := false
var _featured_helper_id := -1
var _interested_villager_id := -1
func _ready() -> void: func _ready() -> void:
_apply_debug_overlay_visibility() _apply_debug_overlay_visibility()
_hide_caption()
if start_pantry_crisis_after_reload:
start_pantry_crisis_after_reload = false
call_deferred("start_pantry_crisis_demo")
func _process(delta: float) -> void: func _process(delta: float) -> void:
@@ -25,13 +53,20 @@ func _unhandled_key_input(event: InputEvent) -> void:
if event.keycode == KEY_F10: if event.keycode == KEY_F10:
toggle_debug_overlay() toggle_debug_overlay()
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
elif event.keycode == KEY_F11:
request_pantry_crisis_demo()
get_viewport().set_input_as_handled()
elif event.keycode == KEY_F12: elif event.keycode == KEY_F12:
reset_demo_scene() reset_demo_scene()
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
func toggle_debug_overlay() -> void: func toggle_debug_overlay() -> void:
debug_overlay_visible = not debug_overlay_visible set_debug_overlay_visible(not debug_overlay_visible)
func set_debug_overlay_visible(is_visible: bool) -> void:
debug_overlay_visible = is_visible
_apply_debug_overlay_visibility() _apply_debug_overlay_visibility()
print( print(
( (
@@ -41,11 +76,275 @@ func toggle_debug_overlay() -> void:
) )
func request_pantry_crisis_demo() -> void:
start_pantry_crisis_after_reload = true
print("[DemoController] Reloading the repeatable pantry-crisis demo")
get_tree().reload_current_scene()
func reset_demo_scene() -> void: func reset_demo_scene() -> void:
start_pantry_crisis_after_reload = false
print("[DemoController] Resetting simulation garden demo") print("[DemoController] Resetting simulation garden demo")
get_tree().reload_current_scene() get_tree().reload_current_scene()
func start_pantry_crisis_demo() -> void:
if _demo_running:
return
_demo_running = true
set_debug_overlay_visible(false)
if player != null:
player.set_physics_process(false)
player.velocity = Vector3.ZERO
_focus_camera_on_village()
_show_caption("Morning settles over the village.")
await get_tree().create_timer(VALLEY_ESTABLISH_SECONDS).timeout
if not _demo_running or not is_inside_tree():
return
var staged := stage_pantry_crisis()
if staged.is_empty():
_fail_pantry_crisis("Could not stage the pantry shortage")
return
var helper: SimNPC = staged["helper"]
var interested: SimNPC = staged["interested"]
_show_caption("The pantry is empty. %s is worried." % interested.npc_name)
pantry_crisis_staged.emit(helper.id, interested.id)
await get_tree().create_timer(ESTABLISH_SECONDS).timeout
if not _demo_running or not is_inside_tree():
return
if not begin_pantry_restock():
_fail_pantry_crisis("No villager could begin the food run")
return
_show_caption("%s goes to help." % helper.npc_name)
_focus_camera_on_npc(helper.id)
var elapsed := 0.0
var carrying_was_shown := false
while elapsed < DEMO_TIMEOUT_SECONDS:
if not is_inside_tree():
return
var opportunity: OpportunityStateRecord = simulation_manager.get_active_opportunity()
if opportunity == null:
var resolved: OpportunityStateRecord = (
simulation_manager.opportunity_system.get_latest()
)
var resolution_event_id := -1
if resolved != null:
resolution_event_id = resolved.get_resolution_event_id()
_show_caption("%s restocks the pantry." % helper.npc_name)
await get_tree().create_timer(RECOVERY_SECONDS).timeout
_demo_running = false
pantry_crisis_completed.emit(helper.id, resolution_event_id)
return
if (
not carrying_was_shown
and helper.get_inventory_amount(SimulationIds.RESOURCE_FOOD) > 0.0
):
carrying_was_shown = true
_show_caption("Fresh berries, carried all the way home.")
await get_tree().process_frame
elapsed += get_process_delta_time()
_fail_pantry_crisis("The food route did not finish in time")
func stage_pantry_crisis() -> Dictionary:
if not _has_demo_dependencies() or simulation_manager.npcs.size() < 3:
return {}
var helper: SimNPC = simulation_manager.npcs[FEATURED_HELPER_INDEX]
var interested: SimNPC = simulation_manager.npcs[INTERESTED_VILLAGER_INDEX]
var shortage_actor: SimNPC = simulation_manager.npcs[SHORTAGE_ACTOR_INDEX]
var pantry_position := pantry_storage.get_interaction_position()
for npc in simulation_manager.npcs:
_park_npc_for_demo(npc)
_place_npc_for_demo(helper, pantry_position + Vector3(-1.25, 0.0, 0.15))
_place_npc_for_demo(interested, pantry_position + Vector3(0.0, 0.0, 0.2))
_place_npc_for_demo(shortage_actor, pantry_position + Vector3(1.25, 0.0, 0.15))
var relationship: RelationshipStateRecord = (
simulation_manager.relationship_system.get_relationship(helper.id, interested.id)
)
if relationship == null:
return {}
if relationship.get_trust() < 0.6:
helper.hunger = 85.0
interested.add_inventory(SimulationIds.RESOURCE_FOOD, 1.0)
if (
simulation_manager.economy.deposit_inventory(interested, SimulationIds.RESOURCE_FOOD)
<= 0.0
):
return {}
if relationship.get_trust() < 0.6:
return {}
var pantry: StorageStateRecord = simulation_manager.get_pantry()
if pantry == null:
return {}
var current_food := pantry.get_amount(SimulationIds.RESOURCE_FOOD)
if current_food > 1.0:
pantry.withdraw(SimulationIds.RESOURCE_FOOD, current_food - 1.0)
elif current_food < 1.0:
pantry.deposit(SimulationIds.RESOURCE_FOOD, 1.0 - current_food)
simulation_manager.economy.sync_resource(SimulationIds.RESOURCE_FOOD)
_set_demo_needs(helper, 45.0, false)
_set_demo_needs(interested, 92.0, true)
_set_demo_needs(shortage_actor, 45.0, false)
var withdrawn: float = simulation_manager.economy.withdraw_to_inventory(
shortage_actor, SimulationIds.RESOURCE_FOOD, 1.0
)
var opportunity: OpportunityStateRecord = simulation_manager.get_active_opportunity()
if (
not is_equal_approx(withdrawn, 1.0)
or opportunity == null
or opportunity.get_interested_npc_id() != interested.id
):
return {}
if not simulation_manager.economy.consume_npc_food(shortage_actor):
return {}
_featured_helper_id = helper.id
_interested_villager_id = interested.id
_focus_camera_on_pantry(interested.id)
return {
"helper": helper,
"interested": interested,
"shortage_actor": shortage_actor,
"opportunity": opportunity,
}
func begin_pantry_restock() -> bool:
var helper := _get_npc(_featured_helper_id)
var interested := _get_npc(_interested_villager_id)
if helper == null or interested == null or simulation_manager.get_active_opportunity() == null:
return false
var selection: ActionSelectionResult = simulation_manager.action_selector.select_action(
helper,
simulation_manager.village,
simulation_manager.clock.time_of_day(),
simulation_manager.npcs
)
if (
selection == null
or selection.action_id != SimulationIds.ACTION_GATHER_FOOD
or interested.npc_name not in selection.reason
):
return false
var previous_task := helper.current_task
simulation_manager.latest_decisions[helper.id] = selection
simulation_manager.npc_decision_recorded.emit(helper, selection)
helper.set_task(selection.action_id, selection.duration_override)
simulation_manager.npc_task_changed.emit(helper, previous_task, selection.action_id)
simulation_manager.npc_target_requested.emit(helper)
return (
helper.task_state == SimNPC.TASK_STATE_TRAVELING
and not helper.target_id.is_empty()
and helper.has_travel_target
)
func is_pantry_crisis_running() -> bool:
return _demo_running
func _has_demo_dependencies() -> bool:
return (
simulation_manager != null
and world_view_manager != null
and camera_rig != null
and pantry_storage != null
and crisis_caption != null
and "npcs" in simulation_manager
and "active_npc_visuals" in world_view_manager
)
func _park_npc_for_demo(npc: SimNPC) -> void:
var previous_task := npc.current_task
simulation_manager.release_npc_reservation(npc.id)
npc.set_task(SimulationIds.ACTION_WANDER, 1000.0)
npc.start_working()
simulation_manager.npc_task_changed.emit(npc, previous_task, npc.current_task)
func _place_npc_for_demo(npc: SimNPC, position: Vector3) -> void:
var visual := world_view_manager.active_npc_visuals.get(npc.id) as Node3D
if visual == null:
return
if visual.has_method("stop_travel"):
visual.stop_travel()
visual.global_position = position
simulation_manager.synchronize_npc_position(npc.id, position)
func _set_demo_needs(npc: SimNPC, hunger: float, starving: bool) -> void:
npc.hunger = hunger
npc.energy = 90.0
npc.is_starving = starving
npc.starvation_ticks = 0
func _focus_camera_on_pantry(interested_id: int) -> void:
var interested_visual := world_view_manager.active_npc_visuals.get(interested_id) as Node3D
if interested_visual == null:
return
camera_rig.target = interested_visual
camera_rig.pitch_degrees = -44.0
camera_rig.apply_presentation_preset(
pantry_storage.global_position + Vector3(0.0, 0.9, 0.5), 128.0, 0.28
)
func _focus_camera_on_village() -> void:
camera_rig.target = player
camera_rig.pitch_degrees = -42.0
camera_rig.apply_presentation_preset(Vector3(-1.5, 1.2, -4.0), 128.0, 0.7)
func _focus_camera_on_npc(npc_id: int) -> void:
var visual := world_view_manager.active_npc_visuals.get(npc_id) as Node3D
if visual != null:
camera_rig.target = visual
func _show_caption(message: String) -> void:
if crisis_caption == null:
return
if _caption_tween != null:
_caption_tween.kill()
crisis_caption.text = message
crisis_caption.visible = true
crisis_caption.modulate.a = 0.0
_caption_tween = create_tween()
_caption_tween.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
_caption_tween.tween_property(crisis_caption, "modulate:a", 1.0, 0.28)
func _hide_caption() -> void:
if crisis_caption == null:
return
crisis_caption.visible = false
crisis_caption.modulate.a = 1.0
func _fail_pantry_crisis(reason: String) -> void:
_demo_running = false
_show_caption("The food route was interrupted.")
push_error("DemoController: %s" % reason)
pantry_crisis_failed.emit(reason)
func _get_npc(npc_id: int) -> SimNPC:
for npc in simulation_manager.npcs:
if npc.id == npc_id:
return npc
return null
func _apply_debug_overlay_visibility() -> void: func _apply_debug_overlay_visibility() -> void:
if ui != null and ui.has_method("set_debug_overlay_visible"): if ui != null and ui.has_method("set_debug_overlay_visible"):
ui.set_debug_overlay_visible(debug_overlay_visible) ui.set_debug_overlay_visible(debug_overlay_visible)
+1
View File
@@ -0,0 +1 @@
uid://bivrsk5ukgnoo
+1 -1
View File
@@ -226,7 +226,7 @@ func _refresh_npc_inspector() -> void:
+ "%s" + "%s"
+ "Why\n%s%s\n\n" + "Why\n%s%s\n\n"
+ "Own history\n%s\n\n" + "Own history\n%s\n\n"
+ "[Tab] Next villager [F10] Cinematic/debug [F12] Demo reset" + "[Tab] Next villager [F10] Cinematic/debug [F11] Pantry story [F12] Reset"
) )
% [ % [
npc.npc_name, npc.npc_name,