From b47801ae5c00536b54efa5130f0dc187d1900521 Mon Sep 17 00:00:00 2001 From: Tiger Jove Date: Wed, 21 Jan 2026 21:38:31 +0100 Subject: [PATCH] fix: card-board uses more single-responsibility, and now allows cards to slide nicely into the gaps --- src/logic-scenes/board/card-board.gd | 49 +++++++++---------- src/logic-scenes/board/card.gd | 68 +++++++++++---------------- src/logic-scenes/board/draggable.gd | 12 ++++- src/logic-scenes/board/sticky-note.gd | 46 +----------------- 4 files changed, 63 insertions(+), 112 deletions(-) diff --git a/src/logic-scenes/board/card-board.gd b/src/logic-scenes/board/card-board.gd index 0c340ad..b135663 100644 --- a/src/logic-scenes/board/card-board.gd +++ b/src/logic-scenes/board/card-board.gd @@ -89,16 +89,28 @@ func _smooth(current: Vector2, goal: Vector2, delta: float) -> Vector2: func _process(delta: float): var zone_position := Vector2(notezone.get_screen_position().x + sticky_width / 3.0, sticky_height) + var dragging := notes.any(func (n : Draggable): return n.is_dragged) + + if dragging: + # Y-sort the nodes, this lets us fill the gap more nicely. + notes.sort_custom(func (a:Draggable, b:Draggable): return a.global_position.y < b.global_position.y) + for note in notes: # Skip all dragged and already attached notes if note.is_attached: continue if note.is_dragged: continue # Magnetically move all notes to where they ought to be on screen - note.position = _smooth(note.position, zone_position, delta) + note.home = zone_position zone_position.y += sticky_height - pass + # Only if not already in transit / animated or user holding on to one + if not dragging and not note.tween: + note.animate_home() + else: + # do adjustment with FIR filter + note.position = _smooth(note.position, note.home, delta) + func _check_completion() -> void: @@ -195,7 +207,7 @@ func add_card(card: Card) -> void: func add_note(note: StickyNote) -> void: add_child(note) notes.append(note) - note.is_dragable = true + note.is_draggable = true func appear(): await Main.curtain.close() @@ -250,30 +262,15 @@ func _end_drag(draggable: Draggable) -> void: if destination and destination is Card: var target_card := destination as Card - # If sticky was previously attached to a different card, detach it first if sticky.is_attached and sticky.attached_to != target_card: - sticky.attached_to.remove_sticky_note() + sticky.attached_to.remove_note_if_present() - # If target card already has a sticky, exchange them - if target_card.has_sticky_note_attached(): - var exchanged_sticky := target_card.exchange_sticky_note_with(sticky) - # Reclaim the exchanged sticky to the board - if exchanged_sticky: - reclaim_sticky(exchanged_sticky) - else: - # Simple attach - sticky.reparent(target_card) - target_card.attach_sticky_note(sticky) + target_card.attach_or_exchange_note(sticky) # If dropped on board (no destination), ensure it's a child of the board elif not destination: - # If it was attached to a card, detach it first if sticky.is_attached: - sticky.attached_to.remove_sticky_note() - - # Make sure sticky is parented to board - if sticky.get_parent() != self: - sticky.reparent(self) + reclaim_sticky(sticky) # Check win condition after any sticky movement check_board_completion() @@ -281,6 +278,7 @@ func _end_drag(draggable: Draggable) -> void: func reclaim_sticky(note: StickyNote): note.reparent(self) + note.tween = null func check_board_completion(): @@ -305,8 +303,8 @@ func give_lore_feedback(): for child in dropzone.get_children(): if child is Card: - if child.has_sticky_note_attached(): - fitting_card_count += int(child.card_id == child.get_attached_sticky_note().parent_id) + if child.has_note_attached(): + fitting_card_count += int(child.card_id == child.get_attached_note().parent_id) total_card_count += 1 if float(fitting_card_count) / float(total_card_count) < 0.2: @@ -421,7 +419,7 @@ func save_to_resource(savegame: SaveGame) -> void: print_debug(" Card '%s' at %s" % [card.name, card.position]) # Save sticky note attachment if present - var note: StickyNote = card.get_attached_sticky_note() + var note: StickyNote = card.get_attached_note() if note: savegame.board_attachments[note.name] = card.name print_debug(" Sticky '%s' attached to card '%s'" % [note.name, card.name]) @@ -496,8 +494,7 @@ func initialise_from_save(savegame: SaveGame) -> void: if card_name and cards_by_name.has(card_name): # Sticky is attached to a card var card: Card = cards_by_name[card_name] - sticky.reparent(card) - card.attach_sticky_note(sticky) + card.attach_or_exchange_note(sticky, true) print_debug(" Sticky '%s' attached to card '%s'" % [sticky.name, card_name]) else: # Sticky is loose on the board diff --git a/src/logic-scenes/board/card.gd b/src/logic-scenes/board/card.gd index ce21479..7a2fb8e 100644 --- a/src/logic-scenes/board/card.gd +++ b/src/logic-scenes/board/card.gd @@ -203,10 +203,10 @@ func _move_card(): if is_dragged: update_drag_position(get_viewport().get_mouse_position()) -func has_sticky_note_attached() -> bool: - return get_attached_sticky_note() != null +func has_note_attached() -> bool: + return get_attached_note() != null -func get_attached_sticky_note() -> StickyNote: +func get_attached_note() -> StickyNote: for child in get_children(false): if child is StickyNote: return child @@ -224,41 +224,40 @@ func preview_sticky_note(sticky_note: StickyNote): else: push_warning("Card.preview_sticky_note: Invalid position calculated, skipping tween") -func attach_sticky_note(sticky_note: StickyNote) -> bool: - if has_sticky_note_attached(): - return false - sticky_note.reparent(self) - sticky_note.position = sticky_note_position - sticky_note.is_dragable = false +func attach_or_exchange_note(note: StickyNote, instant: bool = false) -> void: + prints("Attaching", note, "to", self) - if name == "c_hit" and sticky_note.name == "c_effort" and Steamworks.has_initialized: + # Out with the old... + remove_note_if_present() + + # ... in with the new + note.reparent(self) + note.home = sticky_note_position + + if not instant: + note.animate_home() + else: + note.position = sticky_note_position + + if name == "c_hit" and note.name == "c_effort" and Steamworks.has_initialized: Steam.setAchievement("FIGHT_FOR_GOOD") Steam.storeStats() - return true -func remove_sticky_note() -> StickyNote: - var former_child: StickyNote = get_attached_sticky_note() - if not former_child: - return null +func remove_note_if_present() -> void: + var former_child: StickyNote = get_attached_note() + if not former_child: return + former_child.reparent(get_parent()) - return former_child - -func exchange_sticky_note_with(new_note: StickyNote) -> StickyNote: - if new_note == get_attached_sticky_note(): - return null - - var old_note := remove_sticky_note() - attach_sticky_note(new_note) - return old_note + former_child.tween = null # the positioning logic in card-board will pick that one up and calc a nice slot. # === DROP TARGET PATTERN IMPLEMENTATION === ## Checks if this card can accept the given draggable func can_accept_drop(draggable: Draggable) -> bool: - return draggable is StickyNote and draggable != self + return draggable is StickyNote ## Handles dropping a sticky note onto this card ## Returns DropResult indicating success, rejection, or exchange @@ -266,22 +265,9 @@ func handle_drop(draggable: StickyNote) -> int: if not can_accept_drop(draggable): return Draggable.DropResult.REJECTED - if has_sticky_note_attached(): - # Exchange: remove current, attach new, store old for retrieval - exchange_sticky_note_with(draggable) - # Reset z_index for newly attached sticky - draggable.z_index = 0 - return Draggable.DropResult.ACCEPTED - else: - # Simple attach - if attach_sticky_note(draggable): - # Reset z_index for newly attached sticky - draggable.z_index = 0 - return Draggable.DropResult.ACCEPTED - else: - # Attach failed (shouldn't happen, but handle it) - return Draggable.DropResult.REJECTED - + attach_or_exchange_note(draggable) + draggable.z_index = 0 + return Draggable.DropResult.ACCEPTED # === DRAG LIFECYCLE OVERRIDES === diff --git a/src/logic-scenes/board/draggable.gd b/src/logic-scenes/board/draggable.gd index 652d9e8..224bff5 100644 --- a/src/logic-scenes/board/draggable.gd +++ b/src/logic-scenes/board/draggable.gd @@ -20,6 +20,9 @@ var is_dragged: bool = false: is_dragged = dragged z_index = int(dragged) +# local coordinates home position, where the draggable can try to animate_home to +var home : Vector2 = Vector2.ZERO + ## Internal highlighted state - do not set directly, use set_highlight() var _highlighted: bool = false @@ -63,6 +66,13 @@ func _get_board() -> Node: ## === DRAG LIFECYCLE METHODS === ## Override these in Card and StickyNote for specific behavior +var tween : Tween = null + + +func animate_home() -> void: + if tween: tween.kill() + tween = create_tween().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_BACK) + tween.tween_property(self, "position", home, 0.5) func _on_mouse_entered() -> void: #prints("Draggable[base]._on_mouse_entered", self, self.name) @@ -96,7 +106,7 @@ func _on_input_event(_viewport, event, _shape_idx): if highlighted: var board := _get_board() if board and board.has_method("handle_mouse_button"): - board.handle_mouse_button(event, self) + board.handle_mouse_button(event, self) ## Starts a drag operation diff --git a/src/logic-scenes/board/sticky-note.gd b/src/logic-scenes/board/sticky-note.gd index edb0d2d..32436c4 100644 --- a/src/logic-scenes/board/sticky-note.gd +++ b/src/logic-scenes/board/sticky-note.gd @@ -58,19 +58,13 @@ func set_highlight(value: bool) -> void: shift_tween.tween_property(content, "position", Vector2.ZERO, 0.5) @export var voice_line: AudioStream = null -@export var is_dragable: bool = false +@export var is_draggable: bool = false var mouse_offset: Vector2 @onready var diameter := 312.0 @export_range(1.0, 10.0) var bounce_speed: float = 8 -## Computed property: Check if on the board (dropzone) -## Replaces on_board state tracking -var on_board: bool: - get: - var parent := get_parent() - return parent != null and parent.name == "dropzone" func init(sticky_name: String = "sticky_note", card_id: StringName = "-1") -> void: name = sticky_name @@ -92,45 +86,9 @@ func _on_text_updated(): background_sprite.frame = text.hash() % background_sprite.sprite_frames.get_frame_count(background_sprite.animation) -func _process(delta: float) -> void: - _move_sticky_note(delta) - - -## frame rate independent FIR smoothing filter -func _smooth(current: Vector2, goal: Vector2, delta: float) -> Vector2: - var k := pow(0.1, 60.0 * delta) - return (1.0-k) * current + k * goal - - -func _move_sticky_note(delta: float) -> void: +func _process(_delta: float) -> void: if is_dragged: update_drag_position(get_viewport().get_mouse_position()) - return - - if is_attached: - var card := attached_to - position = _smooth(position, card.sticky_note_position, delta) - - - - -var transform_tween: Tween - -func tween_transform_to(target: Transform2D, duration: float = 0.25) ->void: - # Validate position to prevent teleporting - if not is_finite(target.origin.x) or not is_finite(target.origin.y): - push_warning("StickyNote.tween_transform_to: Invalid position, skipping tween") - transform_tween_finished.emit() - return - - if transform_tween and transform_tween.is_running(): - transform_tween.stop() - - transform_tween = create_tween() - transform_tween.tween_property(self, "transform", target, duration) - - await transform_tween.finished - transform_tween_finished.emit() # === DRAG LIFECYCLE OVERRIDES ===