diff --git a/src/base-environments/youth_room/youth_room.gd b/src/base-environments/youth_room/youth_room.gd index 5f86d5a..bff7b40 100644 --- a/src/base-environments/youth_room/youth_room.gd +++ b/src/base-environments/youth_room/youth_room.gd @@ -26,7 +26,7 @@ func start_room(): func _play_intro_scene() -> void: # The intro scene is auto-played, not triggered by CollectableUi - var intro: Interactable = $logic/CeilingInteractable + var intro: Interactable = $logic/CeilingInteractable await intro.play_story() Scenes.end_sequence(Scenes.id.YOUTH_DRAVEN) @@ -41,7 +41,7 @@ func get_ready(): card_board.board_completed.connect(func(): save_game.is_childhood_board_complete = true save_room()) - + card_board.closed.connect(save_room) card_picker.cards_picked.connect(card_board.populate_board) @@ -64,13 +64,13 @@ func _ready(): func pull_save_state(save: SaveGame) -> void: save_game = save save_game.current_room = id - + # Load board state from save first, before overwriting it card_board.initialise_from_save(save_game) 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) diff --git a/src/dev-util/savegame.gd b/src/dev-util/savegame.gd index 01f7995..edf5014 100644 --- a/src/dev-util/savegame.gd +++ b/src/dev-util/savegame.gd @@ -12,9 +12,10 @@ class_name SaveGame extends Resource @export_flags_2d_physics var sequences_enabled: int = 63 # Board state - properly typed fields -@export var board_cards: Dictionary[StringName, Vector2] = {} -@export var board_stickies: Dictionary[StringName, Variant] = {} -@export var board_randoms: Array[StringName] = [] +@export var board_positions: Dictionary[StringName, Vector2] = {} # Position of all cards and stickies +@export var board_attachments: Dictionary[StringName, StringName] = {} # Sticky name → Card name (if attached) +@export var board_in_panel: Array[StringName] = [] # Stickies currently in the side panel +@export var board_randoms: Array[StringName] = [] # Items picked randomly @export var is_childhood_board_complete: bool = false @export var player_position: Vector3 = Vector3.ZERO @@ -44,10 +45,7 @@ var completed_sequences: int: var total_connections: int: get: - var connections := 0 - for sticky_position in board_stickies.values(): - connections += int(sticky_position is String) - return connections + return board_attachments.size() # === State Variables / External Data === ## Where to save the savegame to / where it was loaded from @@ -179,16 +177,17 @@ func _validate() -> bool: return _validate_board_state() func _validate_board_state() -> bool: - # Validate cards - for card in board_cards.values(): - if not card is Vector2: - push_error("Save %s: Corrupted cards" % unique_save_name) + # Validate positions + for position in board_positions.values(): + if not position is Vector2: + push_error("Save %s: Corrupted board positions" % unique_save_name) return false - - # Validate stickies - for sticky in board_stickies.values(): - if not (sticky is int or sticky is Vector2 or sticky is float or board_cards.has(sticky)): - push_error("Save %s: Corrupted sticky notes" % unique_save_name) + + # Validate attachments (sticky must exist, card must exist) + for sticky_name in board_attachments.keys(): + var card_name = board_attachments[sticky_name] + if not board_positions.has(card_name): + push_error("Save %s: Sticky '%s' attached to non-existent card '%s'" % [unique_save_name, sticky_name, card_name]) return false return true diff --git a/src/logic-scenes/board/card-board.gd b/src/logic-scenes/board/card-board.gd index fd57f98..f174029 100644 --- a/src/logic-scenes/board/card-board.gd +++ b/src/logic-scenes/board/card-board.gd @@ -23,12 +23,12 @@ var focused := false: return var was_focused := focused focused = value - + if focused: _on_board_focused() else: _on_board_unfocused() - + # Emit closed signal when transitioning from focused to unfocused if was_focused and not focused: closed.emit() @@ -142,6 +142,7 @@ func _on_board_focused() -> void: ## Called when board loses focus func _on_board_unfocused() -> void: visible = false + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if is_node_ready(): process_mode = Node.PROCESS_MODE_DISABLED # Stop any active dragging @@ -172,12 +173,19 @@ func populate_board(card_names: Array[StringName]): currently_active_node = dropzone.get_child(0) +## Generates a random position within the dropzone bounds +func _generate_random_position() -> Vector2: + return Vector2( + randi_range(dropzone_padding, int(dropzone_size.x)), + randi_range(dropzone_padding, int(dropzone_size.y)) + ) + func add_card(card: Card, re_parent:bool = true): if re_parent: card.reparent(self) else: add_child(card) - card.position = Vector2(randi_range(dropzone_padding, int(dropzone_size.x)), randi_range(dropzone_padding, int(dropzone_size.y))) + card.position = _generate_random_position() insert_area(dropzone, card) card.set_owner(self) card.is_dragable = true @@ -524,75 +532,166 @@ func on_sticky_panel_cleared(at_id: int): ## Saves board state directly to SaveGame resource func save_to_resource(savegame: SaveGame) -> void: - savegame.board_cards.clear() - savegame.board_stickies.clear() + savegame.board_positions.clear() + savegame.board_attachments.clear() + savegame.board_in_panel.clear() savegame.board_randoms.clear() + print_debug("CardBoard: Saving board state...") + for child in dropzone.get_children(): if child is Card: - # Save position of Card - savegame.board_cards[child.name] = child.transform.origin + # Save card position (local to dropzone) + savegame.board_positions[child.name] = child.position + print_debug(" Card '%s' at %s" % [child.name, child.position]) if child.picked_random: savegame.board_randoms.append(child.name) var note: StickyNote = child.get_attached_sticky_note() if note: - # Save Card Name as position of its children - savegame.board_stickies[child.get_attached_sticky_note().name] = child.name - if child.get_attached_sticky_note().picked_random: - savegame.board_randoms.append(child.get_attached_sticky_note().name) + # Save sticky attachment to card + savegame.board_attachments[note.name] = child.name + # Don't save position for attached stickies - it's determined by the card + print_debug(" Sticky '%s' attached to card '%s'" % [note.name, child.name]) + if note.picked_random: + savegame.board_randoms.append(note.name) elif child is StickyNote: - # Save position of StickyNote - savegame.board_cards[child.name] = child.transform.origin + # Save position of loose sticky on board (local to dropzone) + savegame.board_positions[child.name] = child.position + print_debug(" Loose sticky '%s' at %s" % [child.name, child.position]) + if child.picked_random: + savegame.board_randoms.append(child.name) for child in sticky_note_container.get_children(): - if child is StickyNotePanel: - # Save all collected Stickies that are not on board - savegame.board_stickies[child.attached_sticky_note.name] = -1 + if child is StickyNotePanel and child.attached_sticky_note: + # Save sticky in panel state + savegame.board_in_panel.append(child.attached_sticky_note.name) + print_debug(" Sticky '%s' in panel" % child.attached_sticky_note.name) + if child.attached_sticky_note.picked_random: + savegame.board_randoms.append(child.attached_sticky_note.name) + + print_debug("CardBoard: Saved %d positions, %d attachments, %d in panel" % [ + savegame.board_positions.size(), + savegame.board_attachments.size(), + savegame.board_in_panel.size() + ]) func initialise_from_save(savegame: SaveGame) -> void: # Early return if nothing to load - if savegame.board_cards.is_empty() and savegame.board_stickies.is_empty(): + if savegame.board_positions.is_empty(): + print_debug("CardBoard: No board state to load (save is empty or legacy format)") return - # Collect all card names - var all_cards: Array[StringName] - for card_name: StringName in savegame.board_cards.keys(): - all_cards.append(card_name) - for card_name: StringName in savegame.board_stickies.keys(): - all_cards.append(card_name) + print_debug("CardBoard: Loading board state from save...") + print_debug(" Positions: %d, Attachments: %d, In panel: %d" % [ + savegame.board_positions.size(), + savegame.board_attachments.size(), + savegame.board_in_panel.size() + ]) - var card_pile : Dictionary[String, Array] = HardCards.get_cards_by_name_array(all_cards) + # Collect all card/sticky names from all relevant dictionaries + var all_names: Array[StringName] + + # Names from positions (cards and loose stickies) + for item_name: StringName in savegame.board_positions.keys(): + if not all_names.has(item_name): + all_names.append(item_name) + + # Sticky names from attachments + for sticky_name: StringName in savegame.board_attachments.keys(): + if not all_names.has(sticky_name): + all_names.append(sticky_name) + + # Card names from attachments (the values) + for card_name: StringName in savegame.board_attachments.values(): + if not all_names.has(card_name): + all_names.append(card_name) + + # Sticky names from panel + for item_name: StringName in savegame.board_in_panel: + if not all_names.has(item_name): + all_names.append(item_name) + + print_debug(" Collected %d unique card/sticky names to load" % all_names.size()) + + var card_pile: Dictionary[String, Array] = HardCards.get_cards_by_name_array(all_names) # Track cards by name for sticky note attachment var cards_by_name: Dictionary = {} + # Calculate mementos collected (each memento gives 2 cards) + mementos_collected = int(card_pile["cards"].size() / 2.0) + print_debug(" Calculated mementos_collected: %d (from %d cards)" % [mementos_collected, card_pile["cards"].size()]) + + # Add all cards + print_debug(" Loading %d cards..." % card_pile["cards"].size()) for card: Card in card_pile["cards"]: - add_card(card, false) - card.transform.origin = savegame.board_cards[card.name] + add_child(card) + + # Set position from save, or generate random if missing + if savegame.board_positions.has(card.name): + card.position = savegame.board_positions[card.name] + print_debug(" Card '%s' at %s" % [card.name, card.position]) + else: + card.position = _generate_random_position() + print_debug(" Card '%s' - generated random position: %s" % [card.name, card.position]) + + insert_area(dropzone, card) + card.set_owner(self) + card.is_dragable = true cards_by_name[card.name] = card card.picked_random = savegame.board_randoms.has(card.card_id) + # Add all sticky notes + print_debug(" Loading %d stickies..." % card_pile["sticky_notes"].size()) for sticky: StickyNote in card_pile["sticky_notes"]: - var sticky_data = savegame.board_stickies[sticky.name] + # Check if sticky is in panel + if savegame.board_in_panel.has(sticky.name): + add_sticky_note(sticky, false) + print_debug(" Sticky '%s' added to panel" % sticky.name) - if sticky_data is int: - if sticky_data == -1: + # Check if sticky is attached to a card + elif savegame.board_attachments.has(sticky.name): + var card_name = savegame.board_attachments[sticky.name] + if cards_by_name.has(card_name): + # Must add sticky to scene tree BEFORE attach_sticky_note() can reparent it + add_child(sticky) + sticky.set_owner(self) + sticky.current_handle = self # Required for input handling + cards_by_name[card_name].attach_sticky_note(sticky) + print_debug(" Sticky '%s' attached to card '%s'" % [sticky.name, card_name]) + else: + push_warning("CardBoard: Sticky '%s' attached to non-existent card '%s', adding to panel" % [sticky.name, card_name]) add_sticky_note(sticky, false) - elif sticky_data is String: - # Attached to a card - cards_by_name[sticky_data].attach_sticky_note(sticky) + + # Sticky is loose on board else: - # Loose on board at position + add_child(sticky) + + # Set position from save, or generate random if missing + if savegame.board_positions.has(sticky.name): + sticky.position = savegame.board_positions[sticky.name] + print_debug(" Loose sticky '%s' at %s" % [sticky.name, sticky.position]) + else: + sticky.position = _generate_random_position() + print_debug(" Loose sticky '%s' - generated random position: %s" % [sticky.name, sticky.position]) + insert_area(dropzone, sticky) - sticky.transform.origin = sticky_data + sticky.set_owner(self) + sticky.current_handle = self # Required for input handling + sticky.on_board = true + sticky.attached_to = self + sticky.is_dragable = true sticky.picked_random = savegame.board_randoms.has(sticky.sticky_id) + print_debug("CardBoard: Load complete!") + + func try_select_nearest_card(from: Vector2, towards: Vector2, include_stickies: bool = false) -> bool: var selection_transform := Transform2D(0, from).looking_at(from+towards) diff --git a/src/logic-scenes/board/card.tscn b/src/logic-scenes/board/card.tscn index 43fa6c1..5b53c84 100644 --- a/src/logic-scenes/board/card.tscn +++ b/src/logic-scenes/board/card.tscn @@ -8,6 +8,7 @@ size = Vector2(277, 231) [node name="Card" type="Area2D"] +priority = 50 script = ExtResource("1_emip0") text = "asdf" metadata/_custom_type_script = "uid://ddy8kb2hjvgss" diff --git a/src/logic-scenes/board/sticky-note.gd b/src/logic-scenes/board/sticky-note.gd index 34fd6d7..529da0d 100644 --- a/src/logic-scenes/board/sticky-note.gd +++ b/src/logic-scenes/board/sticky-note.gd @@ -85,16 +85,16 @@ func _ready() -> void: label = $Content/Label background_sprite = $Content/BackgroundSprite content = $Content - + _on_text_updated.call_deferred() - + input_event.connect(_on_input_event) mouse_entered.connect(_on_mouse_entered) mouse_exited.connect(_on_mouse_exited) area_entered.connect(_on_area_enter) area_exited.connect(_on_area_exit) - - + + func _on_text_updated(): label.text = text @@ -116,10 +116,10 @@ func _process(delta: float) -> void: if area is Card or area is CardCollider: if area is CardCollider: position += area.direction * delta - elif not area.highlighted or self.highlighted: + elif not area.highlighted or self.highlighted: var diff:Vector2 = position - area.position position -= diff.normalized() * ((diff.length()-diameter)/diameter) * bounce_speed * (delta/(1.0/60)) - + _move_sticky_note() func _on_mouse_entered(): @@ -132,7 +132,7 @@ func _on_mouse_entered(): func _on_mouse_exited(): if not is_dragged: highlighted = false - + if is_sticky_note_attached() and "check_hover" in attached_to: attached_to.check_hover() @@ -171,7 +171,7 @@ func _move_sticky_note(): if is_dragged: #var old_position = position position = initial_drag_position + get_viewport().get_mouse_position() - mouse_diff - + if hovering_cards != []: var closest: Card = hovering_cards[0] for card in hovering_cards: @@ -194,10 +194,10 @@ var transform_tween: Tween func tween_transform_to(target: Transform2D): if transform_tween and transform_tween.is_running(): transform_tween.stop() - + transform_tween = create_tween() transform_tween.tween_property(self, "transform", target, 0.25) - + await transform_tween.finished transform_tween_finished.emit() diff --git a/src/logic-scenes/board/sticky-note.tscn b/src/logic-scenes/board/sticky-note.tscn index e910626..eb99273 100644 --- a/src/logic-scenes/board/sticky-note.tscn +++ b/src/logic-scenes/board/sticky-note.tscn @@ -9,6 +9,7 @@ radius = 48.0 height = 312.0 [node name="sticky-note" type="Area2D"] +priority = 100 script = ExtResource("1_yvh5n") text = "card" highlight_color = Color(1.2, 1.2, 1.2, 1)