2026-01-17 11:11:21 +00:00
class_name CardBoard
extends Playable
2023-06-27 11:51:23 +00:00
2026-01-17 11:11:21 +00:00
signal board_completed
signal closed
2026-01-05 20:52:20 +00:00
2026-01-17 11:11:21 +00:00
@ export var dropzone_padding : int = 100
@ export var sticky_width : float = 400.0
@ export var sticky_height : float = 110.0
2025-12-15 22:13:40 +00:00
2026-01-17 11:11:21 +00:00
var all_names : Array [ StringName ] = [ ]
var notes : Array [ StickyNote ] = [ ]
var cards : Array [ Card ] = [ ]
2025-12-15 22:13:40 +00:00
2026-01-17 11:11:21 +00:00
var board_was_completed : bool = false
2023-07-02 13:10:33 +00:00
2026-01-17 11:11:21 +00:00
var current_context : int = NAVIGATE
var selection_state : SelectionState
2026-01-16 15:30:58 +00:00
2026-01-17 11:11:21 +00:00
@ onready var instructions : = $ instructions_panel / HBoxContainer / cards_remaining
2026-01-16 15:30:58 +00:00
2026-01-17 11:11:21 +00:00
@ onready var dropzone : Control = % CardZone
@ onready var notezone : Control = % NoteZone
2023-07-11 13:04:46 +00:00
2026-01-17 11:11:21 +00:00
enum SelectionState { FREE , STICKIES , CARDS }
enum { NAVIGATE , ASSIGN , DRAG }
func play ( ) :
2026-01-18 21:29:57 +00:00
check_board_completion ( )
2026-01-17 11:11:21 +00:00
await closed
_finalize_board_state ( )
2026-01-11 21:47:56 +00:00
2023-07-17 22:15:04 +00:00
var mementos_collected : int = 0 :
2024-09-15 09:30:31 +00:00
set ( mementos ) :
mementos_collected = mementos
2026-01-17 11:11:21 +00:00
instructions . text = I18n . get_memento_prompt ( mementos_collected )
2023-07-02 13:10:33 +00:00
2026-01-16 13:09:07 +00:00
2026-01-17 11:11:21 +00:00
var selection : Draggable = null :
set ( value ) :
2026-01-18 09:48:03 +00:00
if selection == value : return
2026-01-18 18:16:56 +00:00
2026-01-18 09:48:03 +00:00
# Select & highlight new
if selection : selection . highlighted = false
2026-01-17 11:11:21 +00:00
selection = value
2026-01-18 09:48:03 +00:00
if selection : selection . highlighted = true
2026-01-17 11:11:21 +00:00
# Are we selecting cards or stickies?
if selection is Card :
selection_state = SelectionState . CARDS
if selection is StickyNote :
selection_state = SelectionState . STICKIES
func _navigate_next ( ) :
var candidates : = _selection_candidates
var index : = maxi ( 0 , candidates . find ( selection ) )
selection = candidates [ ( index + 1 ) % len ( candidates ) ]
func _navigate_prev ( ) :
var candidates : = _selection_candidates
var index : = maxi ( 0 , candidates . find ( selection ) )
selection = candidates [ index - 1 ]
2023-07-19 11:52:01 +00:00
2026-01-17 11:11:21 +00:00
func _ready ( ) - > void :
2026-01-21 21:35:35 +00:00
print ( " CardBoard.gd: %s ._ready() " % self . name )
2026-01-18 20:01:20 +00:00
super . _ready ( )
2026-01-17 11:11:21 +00:00
# HACK: Lets us debug more easily
if get_parent ( ) == get_tree ( ) . root :
_debug_mode ( )
return
2026-01-12 21:35:12 +00:00
print ( " Board Ready! " , self , " room " , State . room )
State . room . card_board = self
2026-01-12 21:50:49 +00:00
2025-12-15 22:13:40 +00:00
2026-01-21 21:35:35 +00:00
## frame rate independent FIR smoothing filter used for small or dynamic card adjustments
2026-01-17 11:11:21 +00:00
func _smooth ( current : Vector2 , goal : Vector2 , delta : float ) - > Vector2 :
var k : = pow ( 0.1 , 60.0 * delta )
return ( 1.0 - k ) * current + k * goal
2025-12-15 22:13:40 +00:00
2026-01-18 09:48:03 +00:00
2026-01-17 11:11:21 +00:00
func _process ( delta : float ) :
2026-01-17 11:15:21 +00:00
var zone_position : = Vector2 ( notezone . get_screen_position ( ) . x + sticky_width / 3.0 , sticky_height )
2023-09-23 14:19:58 +00:00
2026-01-21 20:38:31 +00:00
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 )
2026-01-17 11:11:21 +00:00
for note in notes :
# Skip all dragged and already attached notes
if note . is_attached : continue
2026-01-18 06:30:38 +00:00
if note . is_dragged : continue
2025-12-15 22:13:40 +00:00
2026-01-17 11:11:21 +00:00
# Magnetically move all notes to where they ought to be on screen
2026-01-21 20:38:31 +00:00
note . home = zone_position
2026-01-17 11:11:21 +00:00
zone_position . y += sticky_height
2025-12-15 22:13:40 +00:00
2026-01-21 20:38:31 +00:00
# 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 )
2026-01-16 20:36:36 +00:00
2026-01-17 11:11:21 +00:00
func _check_completion ( ) - > void :
2026-01-16 19:46:16 +00:00
if is_board_complete ( ) :
board_was_completed = true
2026-01-18 21:29:57 +00:00
board_completed . emit ( )
2026-01-16 13:09:07 +00:00
2026-01-16 21:18:01 +00:00
## Finalizes board state before closing (ends drags, cleans up transitions)
func _finalize_board_state ( ) - > void :
# End any active drag operations
2026-01-17 11:11:21 +00:00
if current_context == DRAG :
_end_drag ( selection )
for item in notes :
item . is_dragged = false
2026-01-19 09:34:01 +00:00
2026-01-17 11:11:21 +00:00
for item in cards :
item . is_dragged = false
2026-01-16 21:18:01 +00:00
# Reset context to NAVIGATE
current_context = NAVIGATE
2026-01-21 21:35:35 +00:00
print ( " CardBoard: Board state finalized " )
2026-01-16 21:18:01 +00:00
2026-01-17 11:11:21 +00:00
## Spawn Cards and Post-Its
# TODO: rename to "add to board"
func populate_board ( names : Array [ StringName ] ) :
2024-09-15 09:30:31 +00:00
mementos_collected += 1
2025-12-15 22:13:40 +00:00
2026-01-17 11:11:21 +00:00
for item in names :
assert ( name not in all_names , " Tried to re-add card %s " % item )
var all_new : Dictionary = HardCards . get_cards_by_name_array ( names )
2025-12-15 22:13:40 +00:00
2025-06-02 23:48:52 +00:00
for new_card : Card in all_new [ " cards " ] :
2026-01-17 11:11:21 +00:00
add_card ( new_card )
for new_sticky_note : StickyNote in all_new [ " sticky_notes " ] :
add_note ( new_sticky_note )
2025-12-15 22:13:40 +00:00
2023-11-01 22:19:47 +00:00
2026-01-17 11:11:21 +00:00
# FIXME: This can be made even simpler.
2026-01-16 15:30:58 +00:00
## Generates a random position within the dropzone bounds
2026-01-16 21:05:21 +00:00
## Attempts to avoid overlapping with existing cards/stickies
func _generate_random_position ( min_distance : float = 150.0 ) - > Vector2 :
var max_attempts : = 20
var attempt : = 0
var card_diameter : = 336.0 # Card diameter from card.gd
var sticky_diameter : = 312.0 # Sticky diameter from sticky-note.gd
while attempt < max_attempts :
var pos : = Vector2 (
randi_range ( dropzone_padding , int ( dropzone_size . x ) ) ,
randi_range ( dropzone_padding , int ( dropzone_size . y ) )
)
# Check if this position is far enough from existing items
var is_valid : = true
2026-01-17 11:11:21 +00:00
for child in get_children ( ) :
2026-01-16 21:05:21 +00:00
if child is Card or child is StickyNote :
var distance : = pos . distance_to ( child . position )
var required_distance : = min_distance
# Use actual diameters for more precise collision checking
if child is Card :
required_distance = card_diameter * 0.6 # 60% of diameter for some overlap tolerance
elif child is StickyNote :
required_distance = sticky_diameter * 0.6
if distance < required_distance :
is_valid = false
break
if is_valid :
return pos
attempt += 1
# If we couldn't find a good position after max attempts, return a random one
# This prevents infinite loops when the board is crowded
2026-01-16 15:30:58 +00:00
return Vector2 (
randi_range ( dropzone_padding , int ( dropzone_size . x ) ) ,
randi_range ( dropzone_padding , int ( dropzone_size . y ) )
)
2026-01-17 11:11:21 +00:00
func add_card ( card : Card ) - > void :
add_child ( card )
cards . append ( card )
2026-01-16 16:08:48 +00:00
card . position = _generate_random_position ( )
2024-09-15 09:30:31 +00:00
card . is_dragable = true
2025-12-15 22:13:40 +00:00
2026-01-17 11:11:21 +00:00
func add_note ( note : StickyNote ) - > void :
add_child ( note )
notes . append ( note )
2026-01-21 20:38:31 +00:00
note . is_draggable = true
2026-01-17 11:11:21 +00:00
2026-01-18 20:53:19 +00:00
func appear ( ) :
await Main . curtain . close ( )
show ( )
await Main . curtain . open ( )
func vanish ( ) :
await Main . curtain . close ( )
hide ( )
await Main . curtain . open ( )
2025-12-15 22:13:40 +00:00
2023-07-01 13:19:54 +00:00
2025-01-31 02:22:07 +00:00
# Called by notes when a mouse event needs handling
2026-01-18 09:48:03 +00:00
func handle_mouse_button ( input : InputEventMouseButton , target : Draggable ) - > void :
2026-01-16 19:46:16 +00:00
# === DRAG START ===
2026-01-18 09:48:03 +00:00
if input . button_index == MOUSE_BUTTON_LEFT and input . is_pressed ( ) :
_start_drag ( target )
2026-01-16 19:46:16 +00:00
return
2026-01-16 20:36:36 +00:00
2026-01-16 19:46:16 +00:00
# === DRAG END ===
2026-01-18 16:32:31 +00:00
if input . button_index == MOUSE_BUTTON_LEFT and input . is_released ( ) :
2026-01-18 09:48:03 +00:00
_end_drag ( target )
2026-01-16 19:46:16 +00:00
return
2025-12-15 22:13:40 +00:00
2026-01-16 19:46:16 +00:00
## Starts a drag operation for the given draggable
2026-01-18 09:48:03 +00:00
func _start_drag ( draggable : Draggable ) - > void :
2026-01-17 11:11:21 +00:00
selection = draggable
2026-01-16 19:46:16 +00:00
current_context = DRAG
2026-01-17 11:11:21 +00:00
var mouse_offset : = get_viewport ( ) . get_mouse_position ( ) - draggable . global_position
2026-01-16 19:46:16 +00:00
draggable . start_drag ( mouse_offset )
## Ends a drag operation and handles the drop
func _end_drag ( draggable : Draggable ) - > void :
2026-01-18 18:16:56 +00:00
selection = draggable
2026-01-18 09:48:03 +00:00
# Cleanup and state update
current_context = NAVIGATE
2026-01-17 11:11:21 +00:00
2026-01-18 09:48:03 +00:00
var destination : = draggable . end_drag ( )
2026-01-16 20:36:36 +00:00
2026-01-18 16:32:31 +00:00
# Handle sticky note drops
if draggable is StickyNote :
var sticky : = draggable as StickyNote
2026-01-18 18:16:56 +00:00
2026-01-18 16:32:31 +00:00
# If dropped on a card, attach it
if destination and destination is Card :
var target_card : = destination as Card
2026-01-21 20:38:31 +00:00
target_card . attach_or_exchange_note ( sticky )
2026-01-18 18:16:56 +00:00
2026-01-18 16:32:31 +00:00
# If dropped on board (no destination), ensure it's a child of the board
elif not destination :
if sticky . is_attached :
2026-01-21 20:38:31 +00:00
reclaim_sticky ( sticky )
2026-01-18 18:16:56 +00:00
2026-01-18 16:32:31 +00:00
# Check win condition after any sticky movement
2026-01-18 09:48:03 +00:00
check_board_completion ( )
2026-01-16 19:46:16 +00:00
2026-01-17 11:11:21 +00:00
func reclaim_sticky ( note : StickyNote ) :
note . reparent ( self )
2026-01-21 20:38:31 +00:00
note . tween = null
2025-01-31 02:22:07 +00:00
2025-12-15 22:13:40 +00:00
2026-01-18 09:48:03 +00:00
func check_board_completion ( ) :
2025-05-30 14:10:44 +00:00
if is_board_complete ( ) :
if not board_was_completed :
board_was_completed = true
board_completed . emit ( )
2026-01-17 11:11:21 +00:00
2025-05-30 14:10:44 +00:00
if board_was_completed :
give_lore_feedback ( )
2025-12-16 22:21:54 +00:00
func is_board_complete ( ) - > bool :
2026-01-18 16:52:04 +00:00
return mementos_collected == 4 and notes . all ( func ( n : StickyNote ) : return n . is_attached )
2023-08-30 09:07:22 +00:00
2025-05-30 14:10:44 +00:00
var unfitting : bool = false
var incomplete : bool = false
var complete : bool = false
2026-01-17 11:11:21 +00:00
2025-05-30 14:10:44 +00:00
func give_lore_feedback ( ) :
var fitting_card_count : int = 0
var total_card_count : int = 0
2025-12-15 22:13:40 +00:00
2025-05-30 14:10:44 +00:00
for child in dropzone . get_children ( ) :
if child is Card :
2026-01-21 20:38:31 +00:00
if child . has_note_attached ( ) :
fitting_card_count += int ( child . card_id == child . get_attached_note ( ) . parent_id )
2025-05-30 14:10:44 +00:00
total_card_count += 1
2025-12-15 22:13:40 +00:00
2025-05-30 14:10:44 +00:00
if float ( fitting_card_count ) / float ( total_card_count ) < 0.2 :
instructions . text = " You can move on, but you may not have understood Lisa. "
if not unfitting :
2025-07-21 12:33:04 +00:00
if State . speech_language == 2 :
2025-06-23 16:37:01 +00:00
$ AnimationPlayer . play ( " unfitting_de " )
else :
$ AnimationPlayer . play ( " unfitting " )
2025-05-30 14:10:44 +00:00
unfitting = true
2026-01-17 11:11:21 +00:00
elif fitting_card_count < total_card_count :
2025-05-30 14:10:44 +00:00
instructions . text = TranslationServer . translate ( " You may leave the room, but Lisa only agrees with %d of the %d connections. " ) % [ fitting_card_count , total_card_count ]
if not incomplete :
2025-07-21 12:33:04 +00:00
if State . speech_language == 2 :
2025-06-23 16:37:01 +00:00
$ AnimationPlayer . play ( " incomplete_de " )
else :
$ AnimationPlayer . play ( " incomplete " )
2025-05-30 14:10:44 +00:00
incomplete = true
else :
instructions . text = " Lisa would like you to leave her room and move on. "
if not complete :
2025-07-21 12:33:04 +00:00
if State . speech_language == 2 :
2025-06-23 16:37:01 +00:00
$ AnimationPlayer . play ( " complete_de " )
else :
$ AnimationPlayer . play ( " complete " )
2025-05-30 14:10:44 +00:00
complete = true
2023-07-02 13:10:33 +00:00
# Mark area that was hovered over as currently selected
2026-01-18 15:49:40 +00:00
func handle_hover ( _draggable : Draggable ) - > void :
2026-01-17 11:11:21 +00:00
# If we're hovering with the mouse without clicking, that updates our selection
2026-01-18 16:52:04 +00:00
if selection and selection . is_dragged : return
2026-01-18 15:49:40 +00:00
var candidate : = _nearest_hovered ( _sort_by_proximity_and_depth ( notes ) )
if not candidate :
candidate = _nearest_hovered ( _sort_by_proximity_and_depth ( cards ) )
selection = candidate
2026-01-18 09:48:03 +00:00
func _sort_by_proximity_and_depth ( draggables : Array ) - > Array [ Draggable ] :
var result : Array [ Draggable ] = [ ]
result . append_array ( draggables )
result . sort_custom ( _by_mouse )
var depth : = len ( result ) * 2
for item in result :
depth -= 1
2026-01-18 15:49:40 +00:00
item . z_index = depth if item . mouse_over else 0 # only care about the ones we are currently touching
2026-01-18 09:48:03 +00:00
return result
2025-12-15 22:13:40 +00:00
2026-01-18 09:48:03 +00:00
func _nearest_hovered ( candidates : Array [ Draggable ] ) - > Draggable :
for candidate in candidates :
if candidate . mouse_over : return candidate
return null
2025-12-15 22:13:40 +00:00
2026-01-18 09:48:03 +00:00
func _by_spatial ( a : Draggable , b : Draggable ) - > bool :
2026-01-21 21:35:35 +00:00
return a . position . x + a . position . y * 10000 > b . position . x + b . position . y * 10000
2025-12-15 22:13:40 +00:00
2026-01-18 09:48:03 +00:00
func _by_mouse ( a : Draggable , b : Draggable ) - > bool :
2026-01-21 15:25:16 +00:00
var viewport : = get_viewport ( ) # when app closes, the sorting might still be going on
var mouse_pos : Vector2 = viewport . get_mouse_position ( ) if viewport else Vector2 . ZERO
2026-01-18 09:48:03 +00:00
return ( a . position - mouse_pos ) . length ( ) < ( b . position - mouse_pos ) . length ( )
2026-01-16 19:51:07 +00:00
## Call this after bulk loading to fix child order / z-index issues
2026-01-17 11:11:21 +00:00
func _sort_by_positions ( ) - > void :
2026-01-18 09:48:03 +00:00
cards . sort_custom ( _by_spatial )
notes . sort_custom ( _by_spatial )
2026-01-16 20:36:36 +00:00
2026-01-16 19:51:07 +00:00
2026-01-16 19:46:16 +00:00
# === DROP TARGET PATTERN IMPLEMENTATION ===
## Checks if this board can accept the given draggable (always true for board)
func can_accept_drop ( draggable : Draggable ) - > bool :
return draggable is Card or draggable is StickyNote
## Handles dropping a draggable onto the board (into the dropzone)
func handle_drop ( draggable : Draggable ) - > int :
if not can_accept_drop ( draggable ) :
return Draggable . DropResult . REJECTED
2026-01-16 20:36:36 +00:00
2026-01-16 19:46:16 +00:00
return Draggable . DropResult . ACCEPTED
2023-07-02 13:10:33 +00:00
# Takes the inputs for control inputs
2025-12-16 22:21:54 +00:00
func _input ( event ) - > void :
2026-01-18 18:21:03 +00:00
if event . is_action_pressed ( " ui_cancel " ) :
2026-01-17 11:11:21 +00:00
closed . emit ( )
2025-05-16 11:19:22 +00:00
get_viewport ( ) . set_input_as_handled ( )
2025-12-15 22:13:40 +00:00
2026-01-18 16:52:04 +00:00
if selection and not selection . is_dragged and event is InputEventMouseMotion and not event . is_action_pressed ( " mouse_left " ) :
2026-01-18 09:48:03 +00:00
var candidate : = _nearest_hovered ( _sort_by_proximity_and_depth ( notes ) )
if not candidate :
candidate = _nearest_hovered ( _sort_by_proximity_and_depth ( cards ) )
selection = candidate
2025-02-24 15:14:08 +00:00
2024-10-02 23:12:24 +00:00
2026-01-16 12:59:09 +00:00
## Saves board state directly to SaveGame resource
func save_to_resource ( savegame : SaveGame ) - > void :
2026-01-16 15:30:58 +00:00
savegame . board_positions . clear ( )
savegame . board_attachments . clear ( )
2025-12-15 22:13:40 +00:00
2026-01-21 21:35:35 +00:00
print ( " CardBoard: Saving board state... " )
2026-01-16 15:30:58 +00:00
2026-01-18 18:16:56 +00:00
# Save all cards and their positions
for card in cards :
savegame . board_positions [ card . name ] = card . position
2026-01-21 21:35:35 +00:00
print ( " Card ' %s ' at %s " % [ card . name , card . position ] )
2026-01-17 11:11:21 +00:00
2026-01-18 18:16:56 +00:00
# Save sticky note attachment if present
2026-01-21 20:38:31 +00:00
var note : StickyNote = card . get_attached_note ( )
2026-01-18 18:16:56 +00:00
if note :
savegame . board_attachments [ note . name ] = card . name
2026-01-21 21:35:35 +00:00
print ( " Sticky ' %s ' attached to card ' %s ' " % [ note . name , card . name ] )
2026-01-17 11:11:21 +00:00
2026-01-18 18:16:56 +00:00
# Save loose sticky notes (not attached to cards)
for note in notes :
savegame . board_positions [ note . name ] = note . position
2026-01-21 21:35:35 +00:00
print ( " Loose sticky ' %s ' at %s " % [ note . name , note . position ] )
2026-01-16 15:30:58 +00:00
2026-01-21 21:35:35 +00:00
print ( " CardBoard: Saved %d positions, %d attachments " % [
2026-01-16 15:30:58 +00:00
savegame . board_positions . size ( ) ,
2026-01-18 18:16:56 +00:00
savegame . board_attachments . size ( )
2026-01-16 15:30:58 +00:00
] )
2026-01-16 12:59:09 +00:00
2024-10-02 23:12:24 +00:00
2025-03-31 19:31:09 +00:00
2026-01-16 13:09:07 +00:00
func initialise_from_save ( savegame : SaveGame ) - > void :
2026-01-16 12:59:09 +00:00
# Early return if nothing to load
2026-01-16 15:30:58 +00:00
if savegame . board_positions . is_empty ( ) :
2026-01-21 21:35:35 +00:00
print ( " CardBoard: No board state to load (save is empty or legacy format) " )
2026-01-16 12:59:09 +00:00
return
2026-01-21 21:35:35 +00:00
print ( " CardBoard: Loading board state from save... " )
print ( " Positions: %d , Attachments: %d " % [
2026-01-16 15:30:58 +00:00
savegame . board_positions . size ( ) ,
2026-01-18 18:16:56 +00:00
savegame . board_attachments . size ( )
2026-01-16 15:30:58 +00:00
] )
2026-01-18 18:16:56 +00:00
# Collect all card/sticky names from positions and attachments
2026-01-17 11:11:21 +00:00
all_names = [ ]
2026-01-16 15:30:58 +00:00
# Names from positions (cards and loose stickies)
for item_name : StringName in savegame . board_positions . keys ( ) :
2026-01-17 11:11:21 +00:00
all_names . append ( item_name )
2026-01-16 15:30:58 +00:00
2026-01-18 18:16:56 +00:00
# Sticky names from attachments (keys)
2026-01-16 15:30:58 +00:00
for sticky_name : StringName in savegame . board_attachments . keys ( ) :
2026-01-18 18:16:56 +00:00
if sticky_name not in all_names :
all_names . append ( sticky_name )
2026-01-16 15:30:58 +00:00
2026-01-18 18:16:56 +00:00
# Card names from attachments (values)
2026-01-16 15:30:58 +00:00
for card_name : StringName in savegame . board_attachments . values ( ) :
2026-01-18 18:16:56 +00:00
if card_name not in all_names :
all_names . append ( card_name )
2026-01-16 15:30:58 +00:00
2026-01-21 21:35:35 +00:00
print ( " Collected %d unique card/sticky names to load " % all_names . size ( ) )
2025-12-15 22:13:40 +00:00
2026-01-18 18:16:56 +00:00
# Create all cards and stickies
2026-01-17 11:11:21 +00:00
populate_board ( all_names )
2026-01-16 12:59:09 +00:00
2026-01-18 18:16:56 +00:00
# Calculate mementos collected (each memento gives 2 cards)
mementos_collected = int ( len ( cards ) / 2.0 )
# Build lookup dictionary for cards
2026-01-17 11:11:21 +00:00
var cards_by_name : Dictionary [ StringName , Card ] = { }
for card in cards :
cards_by_name [ card . name ] = card
2026-01-16 12:59:09 +00:00
2026-01-17 11:11:21 +00:00
# Position all cards
for card : Card in cards :
2026-01-16 15:30:58 +00:00
if savegame . board_positions . has ( card . name ) :
2026-01-18 18:16:56 +00:00
card . position = savegame . board_positions [ card . name ]
2026-01-21 21:35:35 +00:00
print ( " Card ' %s ' at %s " % [ card . name , card . position ] )
2026-01-16 15:30:58 +00:00
else :
2026-01-18 18:16:56 +00:00
card . position = _generate_random_position ( )
push_warning ( " Card ' %s ' - no saved position, using random " % card . name )
2026-01-16 12:59:09 +00:00
2026-01-18 18:16:56 +00:00
# Attach sticky notes to cards or position them loose
2026-01-17 11:11:21 +00:00
for sticky : StickyNote in notes :
2026-01-21 21:35:35 +00:00
var card_name : StringName = savegame . board_attachments . get ( sticky . name , & " --nil-- " )
2026-01-18 18:16:56 +00:00
if card_name and cards_by_name . has ( card_name ) :
2026-01-21 21:35:35 +00:00
# Sticky must be attached to a card
2026-01-18 18:16:56 +00:00
var card : Card = cards_by_name [ card_name ]
2026-01-21 20:38:31 +00:00
card . attach_or_exchange_note ( sticky , true )
2026-01-21 21:35:35 +00:00
print ( " Sticky ' %s ' attached to card ' %s ' " % [ sticky . name , card_name ] )
2024-10-02 23:12:24 +00:00
else :
2026-01-18 18:16:56 +00:00
# Sticky is loose on the board
if savegame . board_positions . has ( sticky . name ) :
sticky . position = savegame . board_positions [ sticky . name ]
2026-01-21 21:35:35 +00:00
print ( " Loose sticky ' %s ' at %s " % [ sticky . name , sticky . position ] )
2026-01-18 18:16:56 +00:00
else :
# Fallback to center of board
sticky . position = position + size / 2.0
push_warning ( " Sticky ' %s ' - no saved position, using center " % sticky . name )
2025-12-15 22:13:40 +00:00
2026-01-16 20:36:36 +00:00
2026-01-18 18:16:56 +00:00
# Re-sort by positions for correct z-ordering
2026-01-19 09:34:01 +00:00
_sort_by_positions ( )
2026-01-21 21:35:35 +00:00
print ( " CardBoard: Load complete! " )
2026-01-18 21:29:57 +00:00
_check_completion ( )
2025-12-15 22:13:40 +00:00
2026-01-17 11:11:21 +00:00
# === Computed Properties ===
var dropzone_size : Vector2 :
# FIXME: Hardcode
get : return get_viewport_rect ( ) . size - Vector2 ( dropzone_padding + sticky_width , dropzone_padding )
2025-08-18 18:59:47 +00:00
2026-01-17 11:11:21 +00:00
var _selection_candidates : Array [ Draggable ] :
get :
match selection_state :
SelectionState . CARDS : return cards as Array [ Draggable ]
SelectionState . STICKIES : return notes as Array [ Draggable ]
SelectionState . FREE :
2026-01-21 21:35:35 +00:00
print ( " switching from free selection to guided stickies selection " )
2025-12-15 22:13:40 +00:00
2026-01-17 11:11:21 +00:00
# Otherwise default to sticky selection
selection_state = SelectionState . STICKIES
return notes as Array [ Draggable ]
2025-12-15 22:13:40 +00:00
2026-01-17 11:11:21 +00:00
# === Util ===
2025-08-18 18:59:47 +00:00
2026-01-17 11:11:21 +00:00
func _debug_mode ( ) - > void :
populate_board ( [ " c_void " , ' c_gifted ' , " p_wet " , " p_joy " ] )
populate_board ( [ " c_jui_jutsu " , ' c_hit ' , " p_girly " , " p_vent " ] )
populate_board ( [ " c_comic_heroes " , ' c_teasing ' , " p_agent_q " , " p_good_intended " ] )
populate_board ( [ " c_out_of_world " , ' c_confusion ' , " p_outer_conflict " , " p_unique " ] )
await get_tree ( ) . process_frame
play ( )