From 294a5c2a7d664bcef9af57df87b6722c93d0b497 Mon Sep 17 00:00:00 2001 From: Tiger Jove Date: Thu, 22 Jan 2026 11:57:59 +0100 Subject: [PATCH] refactor: interactables also spawn more controlled, and there's a delayed inversion of control to ensure cardboard is ready. --- .../volunteer_room/volunteer_room.gd | 34 ++---------- .../youth_room/youth_room.gd | 52 ++++--------------- src/dev-util/debug_save.tres | 4 +- src/dev-util/room.gd | 37 ++++++------- src/dev-util/room_with_board.gd | 46 ++++++++++++++-- src/logic-scenes/board/card-board.gd | 12 ++++- src/logic-scenes/interactable/interactable.gd | 20 ++++--- 7 files changed, 99 insertions(+), 106 deletions(-) diff --git a/src/base-environments/volunteer_room/volunteer_room.gd b/src/base-environments/volunteer_room/volunteer_room.gd index 6639883..b334962 100644 --- a/src/base-environments/volunteer_room/volunteer_room.gd +++ b/src/base-environments/volunteer_room/volunteer_room.gd @@ -1,37 +1,21 @@ extends RoomWithBoard class_name VolunteerRoom -@onready var card_picker: CardPicker = %Picker -@onready var player: PlayerController = %PlayerController - -func _ready() -> void: +func _ready() -> void: super._ready() # UwU, superclass _ready is not called by Godot automatically... prints("volunteer_room.gd", "_ready()", self) func get_ready() -> void: await super.get_ready() - # Interactions can this way load their correct prompts - get_tree().call_group("interactables", "pull_save_state") + %TherapyVoluntaryInteractable.visible = not save_game.subway_burnout + %TherapyUniInteractable.visible = save_game.subway_burnout - pull_save_state(State.save_game) - - Scenes.scene_finished.connect(_on_scene_finished) - card_picker.cards_picked.connect(card_board.populate_board) - - card_board.closed.connect(save_room) card_board.board_completed.connect(func(): save_game.childhood_board_complete = true #%DoorInteractable.show() ) - # This MUST happen after the signal connection, or the door will remain locked - card_board.initialise_from_save(save_game) - - %TherapyVoluntaryInteractable.visible = not save_game.subway_burnout - %TherapyUniInteractable.visible = save_game.subway_burnout - - func start_room(): await super.start_room() @@ -43,18 +27,6 @@ func start_room(): await Main.curtain.open() - -func _on_scene_finished(_id: int, _repeat:bool): - await get_tree().create_timer(3).timeout - save_room() - -func save_room(): - # Update board state before saving - card_board.save_to_resource(save_game) - save_game.mementos_complete = Scenes.completed_sequences - save_game.sequences_enabled = Scenes.enabled_sequences - super.save_room() - func prepare_transition(): pass diff --git a/src/base-environments/youth_room/youth_room.gd b/src/base-environments/youth_room/youth_room.gd index b7b7154..e5e356a 100644 --- a/src/base-environments/youth_room/youth_room.gd +++ b/src/base-environments/youth_room/youth_room.gd @@ -1,13 +1,7 @@ class_name YouthRoom extends RoomWithBoard -## Used by the room system when this room becomes active -# var is_active: bool = false # Reminder, do not uncomment, this field is inherited from RoomTemplate! - -@onready var card_picker: CardPicker = %Picker -@onready var ui: Control = %UI - -func _ready() -> void: +func _ready() -> void: super._ready() # UwU, superclass _ready is not called by Godot automatically... prints("youth_room.gd", "_ready()", self) @@ -41,49 +35,25 @@ func _play_intro_scene() -> void: func get_ready() -> void: await super.get_ready() - - pull_save_state(State.save_game) - - card_board.closed.connect(save_room) - card_board.board_completed.connect(func(): - save_game.childhood_board_complete = true - %DoorInteractable.show() - ) - - Scenes.scene_finished.connect(_on_scene_finished) - card_picker.cards_picked.connect(card_board.populate_board) - - # This MUST happen after the signal connection, or the door will remain locked - card_board.initialise_from_save(save_game) + prints("youth_room.gd", "get_ready()", self) ui.hide()# Not sure? + card_board.board_completed.connect(func(): + save_game.childhood_board_complete = true + #%DoorInteractable.show() + ) + + func pull_save_state(save: SaveGame) -> void: - save_game = save - save_game.current_room = id + super.pull_save_state(save) + prints("youth_room.gd", "pull_save_state()", self) - # Interactions can this way load their correct prompts - get_tree().call_group("interactables", "pull_save_state") - - # TODO: These should be deprecated, SaveGame is our source of truth + # TODO: These should be deprecated, SaveGame is our source of truth, and that contains what's complete etc. Scenes.started_sequences = save_game.mementos_complete Scenes.completed_sequences = save_game.mementos_complete - # Call parent to restore player position - super.pull_save_state(save) - - -func _on_scene_finished(_id: int, _repeat:bool): - await get_tree().create_timer(3).timeout - save_room() - -func save_room(): - # Update board state before saving - card_board.save_to_resource(save_game) - save_game.mementos_complete = Scenes.completed_sequences - super.save_room() - ## Override to handle scene-specific preparation (chest reveal animation) func prepare_scene_start(scene_id: Scenes.id, is_repeating: bool) -> void: diff --git a/src/dev-util/debug_save.tres b/src/dev-util/debug_save.tres index 5cffe08..e387f8f 100644 --- a/src/dev-util/debug_save.tres +++ b/src/dev-util/debug_save.tres @@ -1,4 +1,4 @@ -[gd_resource type="Resource" script_class="SaveGame" format=3 uid="uid://pndvrrytubnr"] +[gd_resource type="Resource" script_class="SaveGame" load_steps=2 format=3 uid="uid://pndvrrytubnr"] [ext_resource type="Script" uid="uid://d06gpwuxmkxkt" path="res://dev-util/savegame.gd" id="1_jr18u"] @@ -6,6 +6,6 @@ script = ExtResource("1_jr18u") unique_save_name = "DEBUG" current_room = 3 -mementos_complete = 1669 +mementos_complete = 1665 sequences_enabled = 255 metadata/_custom_type_script = "uid://d06gpwuxmkxkt" diff --git a/src/dev-util/room.gd b/src/dev-util/room.gd index f872203..e3579f1 100644 --- a/src/dev-util/room.gd +++ b/src/dev-util/room.gd @@ -3,34 +3,43 @@ extends Node3D ## the scene path to the next room (or null for credits roll) class_name Room +@onready var ui: Control = %UI + ## Tells the main loop to proceed to the next scene signal proceed(next_scene_path: String) @export var id: State.rooms = State.rooms.NULL +@onready var player: PlayerController = %PlayerController @onready var scene_player : AnimationPlayer = %SceneAnimationPlayer +## get-only property with the current authoritative savegame. var save_game : SaveGame: get: return State.save_game + func _ready() -> void: - prints("room.gd", "_ready()", self) assert(id != State.rooms.NULL, "Room " + str(self) + name + " has no proper ID set, it's still State.Rooms.NULL") + prints("room.gd", "_ready()", self) State.room = self - if not State.save_game: + if not save_game: var debug_save_path := "res://dev-util/debug_save.tres" - push_warning("Room initialised without a SaveGame. Loading DEBUG save.", debug_save_path) - State.save_game = ResourceLoader.load(debug_save_path) + push_warning("Room initialised without a SaveGame. Making a copy of ", debug_save_path) + State.save_game = ResourceLoader.load(debug_save_path).duplicate(true) if not Main.normal_boot: _debug_mode() + + func get_ready(): prints("----------", "GET_READY", self.name, "--------------") - for i in range(20): await get_tree().process_frame + pull_save_state(save_game) + save_game.seen.append(name) + func play() -> String: for i in range(20): @@ -47,26 +56,18 @@ func start_room(): await get_tree().process_frame # so this registers as a coroutine in IDE -func pull_save_state(_save: SaveGame) -> void: +func pull_save_state(save: SaveGame) -> void: # Override this function to load the state of the chapter from State.save_game - restore_player_from_save(_save) + restore_player_from_save(save) ## Attempts to find player controller and restore position/rotation from save func restore_player_from_save(save: SaveGame) -> void: - var player: PlayerController = null - - # Try to find player controller in common locations - if has_node("%PlayerController"): - player = get_node("%PlayerController") - elif has_node("logic/PlayerController"): - player = get_node("logic/PlayerController") - - if player and player is PlayerController: + # only restore the player if we've already been in this room + if name in save.seen: player.restore_from_save(save) - else: - print("RoomTemplate: Could not find PlayerController to restore position") func save_room(): + prints("room.gd", "save_room", self) save_game.save_to_file(get_tree().root.get_texture()) func unload(): diff --git a/src/dev-util/room_with_board.gd b/src/dev-util/room_with_board.gd index e502b1f..edeebac 100644 --- a/src/dev-util/room_with_board.gd +++ b/src/dev-util/room_with_board.gd @@ -2,9 +2,45 @@ extends Room ## A room with a CardBoard in it, has some special properties and initialization rules! class_name RoomWithBoard -func _ready() -> void: - prints("room_with_board.gd", "_ready()", self) - super._ready() # UwU, superclass _ready is not called by Godot automatically... +@onready var card_picker: CardPicker = %Picker +var card_board : CardBoard # Initialized by CardBoard itself. - -var card_board : CardBoard # Optional Board, if present - set by the board in its own _ready() + +func _ready() -> void: + super._ready() # UwU, superclass _ready is not called by Godot automatically... + prints("room_with_board.gd", "_ready()", self, owner) + + + +func get_ready(): + super.get_ready() + prints("room_with_board.gd", "get_ready()", self) + + assert(card_board, str(self) + " has no CardBoard") + + # Interactions can this way load their correct prompts + get_tree().call_group("interactables", "pull_save_state") + + Scenes.scene_finished.connect(_on_scene_finished) + + card_picker.cards_picked.connect(card_board.populate_board) + + card_board.closed.connect(save_room) + + # This MUST happen after the signal connection, or the door will remain locked + card_board.initialise_from_save(save_game) + + +func _on_scene_finished(_id: int, _repeat:bool): + save_room.call_deferred() # Formerly there was a 3 second wait here. + + +func save_room(): + prints("room_with_board.gd", "save_room", self) + card_board.save_to_resource(save_game) + + #TODO these should be deprecated + save_game.mementos_complete = Scenes.completed_sequences + save_game.sequences_enabled = Scenes.enabled_sequences + + super.save_room() diff --git a/src/logic-scenes/board/card-board.gd b/src/logic-scenes/board/card-board.gd index 435a380..378f6d3 100644 --- a/src/logic-scenes/board/card-board.gd +++ b/src/logic-scenes/board/card-board.gd @@ -27,14 +27,22 @@ enum {NAVIGATE, ASSIGN, DRAG} func _ready() -> void: - prints("card-board.gd:", "_ready()", self, "room:", State.room) + prints("card-board.gd:", "_ready()", self, "room:", State.room, owner) super._ready() + _delayed_ready.call_deferred() + +# We need to wait for our room further up our parent hierarchy to actually make itself known +# for interactables to do things with it, e.g. CardBoard needs to register itself +func _delayed_ready() ->void: var board_room := State.room as RoomWithBoard - assert(board_room, "A CardBoard is placed in a Room that's not a RoomWithBoard") + assert(board_room, "CardBoard spawned in room that's not a RoomWithboard.") board_room.card_board = self + + + func play(): check_board_completion() await closed diff --git a/src/logic-scenes/interactable/interactable.gd b/src/logic-scenes/interactable/interactable.gd index 2aff2f9..95e080d 100644 --- a/src/logic-scenes/interactable/interactable.gd +++ b/src/logic-scenes/interactable/interactable.gd @@ -31,19 +31,25 @@ var tween: Tween = null func _ready() -> void: assert(note and frame and canvas_layer, "Interactable must have views and frame attached") - # how did this ever work? - await get_tree().process_frame - - if interaction: - playable = interaction.instantiate() as Control - canvas_layer.add_child(playable) - view.scale = Vector3.ZERO frame.modulate.a = 0.0 light.visible = false Scenes.player_enable.connect(_player_active) # TODO: do I have to clean this up? + assert(interaction, "Interactable " + name + " doesn't have an interaction PackedScene set") + playable = interaction.instantiate() as Control + canvas_layer.add_child(playable) + + _delayed_ready.call_deferred() + +# We need to wait for our room further up our parent hierarchy to actually make itself known +# for interactables to do things with it, e.g. CardBoard needs to register itself +func _delayed_ready() ->void: + playable.unique_name_in_owner = true + + + ## To be called by room func pull_save_state() -> void: