feat: package pantry crisis demo
This commit is contained in:
@@ -1,14 +1,42 @@
|
||||
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 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
|
||||
|
||||
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:
|
||||
_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:
|
||||
@@ -25,13 +53,20 @@ func _unhandled_key_input(event: InputEvent) -> void:
|
||||
if event.keycode == KEY_F10:
|
||||
toggle_debug_overlay()
|
||||
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:
|
||||
reset_demo_scene()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
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()
|
||||
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:
|
||||
start_pantry_crisis_after_reload = false
|
||||
print("[DemoController] Resetting simulation garden demo")
|
||||
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:
|
||||
if ui != null and ui.has_method("set_debug_overlay_visible"):
|
||||
ui.set_debug_overlay_visible(debug_overlay_visible)
|
||||
|
||||
Reference in New Issue
Block a user