2024-10-06 09:31:47 +00:00
class_name CardBoard extends PanelContainer
2023-06-27 11:51:23 +00:00
2023-10-15 11:27:02 +00:00
enum { NAVIGATE , ASSIGN , DRAG }
2026-01-05 20:52:20 +00:00
2023-11-01 22:19:47 +00:00
var focus_stickies : bool = true :
2024-09-15 09:30:31 +00:00
set ( stickies ) :
2025-05-16 21:50:19 +00:00
if not is_node_ready ( ) : return
2024-09-15 09:30:31 +00:00
if stickies and sticky_note_container . get_child_count ( ) == 0 : return
2025-12-15 22:13:40 +00:00
2025-01-31 02:22:07 +00:00
# this messes things up if called unneeded.
if focus_stickies != stickies :
focus_stickies = stickies
2025-12-15 22:13:40 +00:00
2025-01-31 02:22:07 +00:00
if not current_context == ASSIGN :
if stickies :
current_sticky_note_id = current_sticky_note_id
else :
2025-12-15 22:13:40 +00:00
current_dropzone_id = current_dropzone_id
2023-07-02 13:10:33 +00:00
2026-01-12 17:39:34 +00:00
var focused : = false :
2026-01-16 13:09:07 +00:00
set ( value ) :
if focused == value :
return
2026-01-12 21:50:49 +00:00
var was_focused : = focused
2026-01-16 13:09:07 +00:00
focused = value
2026-01-16 15:30:58 +00:00
2026-01-16 13:09:07 +00:00
if focused :
_on_board_focused ( )
2024-09-15 09:30:31 +00:00
else :
2026-01-16 13:09:07 +00:00
_on_board_unfocused ( )
2026-01-16 15:30:58 +00:00
2026-01-12 21:50:49 +00:00
# Emit closed signal when transitioning from focused to unfocused
if was_focused and not focused :
closed . emit ( )
2023-07-11 13:04:46 +00:00
2026-01-11 21:47:56 +00:00
@ onready var dropzone : = $ HBoxContainer / dropzone
2023-08-01 08:59:24 +00:00
var dropzone_size : Vector2
2025-03-23 14:43:04 +00:00
@ export var dropzone_padding : int = 100
2026-01-11 21:47:56 +00:00
@ onready var sticky_note_container : = $ HBoxContainer / ScrollContainer / VBoxContainer
2023-10-15 11:27:02 +00:00
@ onready var current_context : int = NAVIGATE :
2024-09-15 09:30:31 +00:00
set ( context ) :
current_context = context
2026-01-11 21:47:56 +00:00
@ onready var instructions : = $ instructions_panel / HBoxContainer / cards_remaining
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
match mementos :
1 :
instructions . text = " There are three Mementos left to find. "
2 :
instructions . text = " You have collected half of the mementos. "
3 :
instructions . text = " Find the last Memento to complete the Board. "
4 :
instructions . text = " Combine cards to order your thoughts. "
2023-06-27 11:51:23 +00:00
2023-10-12 16:25:21 +00:00
@ onready var currently_active_node : Area2D = null :
2024-09-15 09:30:31 +00:00
set ( new_node ) :
2025-01-31 02:22:07 +00:00
# this makes sure no accidental context switches can happen while a card is being dragged.
2025-02-24 15:14:08 +00:00
if not ( current_context == DRAG ) :
2025-01-31 02:22:07 +00:00
if not currently_active_node == null :
currently_active_node . highlighted = false
currently_active_node = new_node
if not currently_active_node == null :
currently_active_node . highlighted = true
2023-07-01 13:19:54 +00:00
2023-09-23 14:19:58 +00:00
@ onready var current_dropzone_id : int = 0 :
2024-09-15 09:30:31 +00:00
set ( new_id ) :
2025-05-16 21:50:19 +00:00
if is_node_ready ( ) :
if new_id > dropzone . get_child_count ( ) - 1 : current_dropzone_id = 0
elif new_id < 0 : current_dropzone_id = dropzone . get_child_count ( ) - 1
else : current_dropzone_id = new_id
if current_context == ASSIGN and not focus_stickies :
2025-05-30 11:14:26 +00:00
while not dropzone . get_child ( current_dropzone_id ) is Card :
current_dropzone_id = ( current_dropzone_id + ( 1 if not new_id == - 1 else - 1 ) ) % dropzone . get_child_count ( )
2026-01-11 21:47:56 +00:00
( dropzone . get_child ( current_dropzone_id ) as Card ) . preview_sticky_note ( currently_active_node )
2025-05-16 21:50:19 +00:00
elif not focus_stickies :
currently_active_node = dropzone . get_child ( current_dropzone_id )
2025-12-15 22:13:40 +00:00
2023-10-12 16:25:21 +00:00
@ onready var current_sticky_note_id : int = 0 :
2024-09-15 09:30:31 +00:00
set ( new_id ) :
2025-05-16 21:50:19 +00:00
if is_node_ready ( ) :
2025-08-18 18:59:47 +00:00
if sticky_note_container . get_child_count ( ) < 1 : return
elif sticky_note_container . get_child_count ( ) == 1 : current_sticky_note_id = 0
2025-05-16 21:50:19 +00:00
elif new_id > sticky_note_container . get_child_count ( ) - 1 : current_sticky_note_id = 0
elif new_id < 0 : current_sticky_note_id = sticky_note_container . get_child_count ( ) - 1
2025-08-18 18:59:47 +00:00
elif sticky_note_container . get_child ( new_id ) . invalid :
if sticky_note_container . get_child_count ( ) == 1 : return
if new_id + 1 == sticky_note_container . get_child_count ( ) :
current_sticky_note_id = new_id - 1
else :
current_sticky_note_id = new_id + 1
2025-05-16 21:50:19 +00:00
else : current_sticky_note_id = new_id
if current_context == ASSIGN :
_return_sticky_notes_to_panels ( )
currently_active_node . preview_sticky_note ( sticky_note_container . get_child ( current_sticky_note_id ) . attached_sticky_note )
2025-08-18 18:59:47 +00:00
elif focus_stickies :
2025-05-16 21:50:19 +00:00
if sticky_note_container . get_child ( current_sticky_note_id ) . get_child_count ( ) == 1 :
currently_active_node = sticky_note_container . get_child ( current_sticky_note_id ) . get_child ( 0 )
else :
for i in range ( sticky_note_container . get_child_count ( ) - 1 ) :
if sticky_note_container . get_child ( i ) . get_child_count ( ) == 1 :
currently_active_node = sticky_note_container . get_child ( i ) . get_child ( 0 )
2023-07-02 13:10:33 +00:00
2026-01-16 13:09:07 +00:00
2023-07-19 11:52:01 +00:00
signal board_completed
2026-01-12 21:50:49 +00:00
signal closed
2023-07-19 11:52:01 +00:00
2023-06-27 11:51:23 +00:00
# Called when the node enters the scene tree for the first time.
func _ready ( ) :
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
2026-01-11 21:47:56 +00:00
var size_reference : = StickyNotePanel . new ( )
2025-12-15 22:13:40 +00:00
2025-02-24 15:14:08 +00:00
dropzone_size = get_viewport_rect ( ) . size - Vector2 ( dropzone_padding + size_reference . minimum_size . x , dropzone_padding )
2025-12-15 22:13:40 +00:00
2024-09-15 09:30:31 +00:00
if get_parent ( ) == get_tree ( ) . root :
2025-08-13 17:40:36 +00:00
populate_board ( [ " c_void " , ' c_gifted ' , " p_wet " , " p_joy " ] )
2025-05-16 21:50:19 +00:00
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 " ] )
2023-09-23 14:19:58 +00:00
2025-02-24 15:14:08 +00:00
get_viewport ( ) . gui_focus_changed . connect ( reclaim_lost_focus )
2025-12-15 22:13:40 +00:00
2026-01-16 13:09:07 +00:00
## Called when board receives focus
func _on_board_focused ( ) - > void :
get_tree ( ) . call_group ( " interactables " , " collapse " )
current_dropzone_id = 0
current_sticky_note_id = 0
focus_stickies = true
visible = true
if is_node_ready ( ) :
process_mode = Node . PROCESS_MODE_INHERIT
## Called when board loses focus
func _on_board_unfocused ( ) - > void :
visible = false
2026-01-16 15:30:58 +00:00
Input . mouse_mode = Input . MOUSE_MODE_CAPTURED
2026-01-16 13:09:07 +00:00
if is_node_ready ( ) :
process_mode = Node . PROCESS_MODE_DISABLED
# Stop any active dragging
for sticky in dropzone . get_children ( ) :
if sticky is StickyNote :
sticky . is_dragged = false
2025-02-24 15:14:08 +00:00
2025-06-03 21:18:34 +00:00
func reclaim_lost_focus ( _thief ) :
2026-01-12 17:39:34 +00:00
if focused :
2025-02-24 15:14:08 +00:00
grab_focus ( )
2023-09-23 14:19:58 +00:00
2025-12-15 22:13:40 +00:00
## Will be used later to spawn Cards and Post-Its and remember them in the dictionary
2025-04-13 17:07:31 +00:00
func populate_board ( card_names : Array [ StringName ] ) :
2024-09-15 09:30:31 +00:00
mementos_collected += 1
2025-12-15 22:13:40 +00:00
2025-04-13 17:07:31 +00:00
var all_new : Dictionary = HardCards . get_cards_by_name_array ( card_names )
2025-12-15 22:13:40 +00:00
2024-09-15 09:30:31 +00:00
# spawning the cards and adding them to the dictionary
2025-06-02 23:48:52 +00:00
for new_card : Card in all_new [ " cards " ] :
2025-04-13 17:07:31 +00:00
add_card ( new_card , false )
2025-06-02 23:48:52 +00:00
# marking the first card as random picks
new_card . picked_random = new_card . name == card_names [ 1 ]
for new_sticky_note : StickyNote in all_new [ " sticky_notes " ] : # spawning a sticky note
2025-04-13 17:07:31 +00:00
add_sticky_note ( new_sticky_note , false )
2025-06-02 23:48:52 +00:00
# marking the first sticky as random picks
new_sticky_note . picked_random = new_sticky_note . name == card_names [ 3 ]
2025-12-15 22:13:40 +00:00
2024-09-15 09:30:31 +00:00
currently_active_node = dropzone . get_child ( 0 )
2023-11-01 22:19:47 +00:00
2026-01-16 15:30:58 +00:00
## 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 ) )
)
2025-12-13 10:14:10 +00:00
func add_card ( card : Card , re_parent : bool = true ) :
if re_parent :
2025-04-13 17:07:31 +00:00
card . reparent ( self )
else :
add_child ( card )
2026-01-16 15:30:58 +00:00
card . position = _generate_random_position ( )
2024-09-15 09:30:31 +00:00
insert_area ( dropzone , card )
card . set_owner ( self )
card . is_dragable = true
2025-12-15 22:13:40 +00:00
2025-12-13 10:14:10 +00:00
func add_sticky_note ( sticky : StickyNote , re_parent : bool = true ) :
var new_panel : = StickyNotePanel . new ( )
2025-03-23 13:31:00 +00:00
sticky_note_container . add_child ( new_panel , true , Node . INTERNAL_MODE_DISABLED )
2025-02-24 15:14:08 +00:00
#WARNING this for some reason would break the tweens
2024-09-15 09:30:31 +00:00
new_panel . set_owner ( self )
2025-02-24 15:14:08 +00:00
sticky . current_handle = self
2025-12-13 10:14:10 +00:00
new_panel . attatch_sticky_note ( sticky , self , false , re_parent )
2025-12-15 22:13:40 +00:00
2023-07-01 13:19:54 +00:00
# Checks if a Node is currently inside the dropzone
func is_in_dropzone ( to_check : Node ) - > bool :
2024-09-15 09:30:31 +00:00
return dropzone . get_rect ( ) . has_point ( to_check . global_position )
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
2025-12-16 22:21:54 +00:00
func handle_mouse_button ( input : InputEventMouseButton , to_handle = currently_active_node ) - > void :
2025-12-15 22:13:40 +00:00
2025-01-31 02:22:07 +00:00
# Makes sure that only the same area is dragged.
2025-12-15 22:13:40 +00:00
# Otherwise overlapping areas are dragged at the same time.
2024-09-15 09:30:31 +00:00
if current_context == DRAG and to_handle != currently_active_node :
return
2025-12-15 22:13:40 +00:00
2025-01-31 02:22:07 +00:00
if input . button_index == MOUSE_BUTTON_MASK_LEFT and input . pressed :
currently_active_node = to_handle
2024-09-15 09:30:31 +00:00
to_handle . is_dragged = true
if to_handle is StickyNote :
if not to_handle . on_board :
to_handle . reparent ( dropzone )
to_handle . on_board = true
2025-02-24 15:14:08 +00:00
to_handle . attached_to = self
2024-09-15 09:30:31 +00:00
current_context = DRAG
2025-12-15 22:13:40 +00:00
2025-05-21 17:42:45 +00:00
2024-09-15 09:30:31 +00:00
# when Drag stops ...
2025-01-31 02:22:07 +00:00
if input . button_index == MOUSE_BUTTON_MASK_LEFT and not input . pressed :
2024-09-15 09:30:31 +00:00
to_handle . is_dragged = false
if to_handle is StickyNote :
if is_in_dropzone ( to_handle ) :
if to_handle . has_overlapping_areas ( ) :
for area in to_handle . get_overlapping_areas ( ) :
if area is Card :
focus_stickies = false
if area . has_sticky_note_attached ( ) :
to_handle = area . exchange_sticky_note_with ( to_handle )
to_handle . reparent ( dropzone )
to_handle . on_board = true
2025-06-02 21:51:42 +00:00
if sticky_note_container . get_child_count ( ) > 0 :
sticky_note_container . get_child ( current_sticky_note_id ) . attached_sticky_note = to_handle
to_handle . attached_to = sticky_note_container . get_child ( current_sticky_note_id )
else :
var new_panel = StickyNotePanel . new ( )
sticky_note_container . add_child ( new_panel , true , Node . INTERNAL_MODE_DISABLED )
new_panel . owner = self
new_panel . attatch_sticky_note ( to_handle , self , false )
current_sticky_note_id = 0
2024-09-15 09:30:31 +00:00
to_handle . reset_drag ( )
current_context = NAVIGATE
2025-04-30 14:34:00 +00:00
_return_sticky_notes_to_panels ( )
2024-09-15 09:30:31 +00:00
return
else :
area . attach_sticky_note ( to_handle )
2025-05-08 19:53:53 +00:00
to_handle . z_index = 0
2025-03-31 19:31:09 +00:00
if sticky_note_container . get_child_count ( ) > 0 :
sticky_note_container . get_child ( current_sticky_note_id ) . clear_if_empty ( )
2024-09-15 09:30:31 +00:00
current_context = NAVIGATE
2025-05-30 14:10:44 +00:00
check_board_comnpletion ( )
2024-09-15 09:30:31 +00:00
return
else :
2025-02-24 15:14:08 +00:00
var i : int = 0
for panel : StickyNotePanel in sticky_note_container . get_children ( ) :
i += 1
2025-06-05 16:40:55 +00:00
if panel . is_empty :
if panel . get_global_rect ( ) . intersects ( Rect2 ( to_handle . global_position - Vector2 ( to_handle . diameter / 2 , 10 ) , Vector2 ( to_handle . diameter / 2 , 10 ) ) ) :
panel . attatch_sticky_note ( to_handle , self )
elif panel . is_gapped or i == sticky_note_container . get_child_count ( ) :
2025-02-24 15:14:08 +00:00
panel . collapse_gap ( )
var new_panel = StickyNotePanel . new ( )
sticky_note_container . add_child ( new_panel )
sticky_note_container . move_child ( new_panel , i )
new_panel . attatch_sticky_note ( to_handle , self )
new_panel . owner = self
panel . clear_if_empty ( )
2024-09-15 09:30:31 +00:00
_return_sticky_notes_to_panels ( )
2025-02-24 15:14:08 +00:00
current_context = NAVIGATE
2024-09-15 09:30:31 +00:00
return
2025-12-15 22:13:40 +00:00
2024-09-15 09:30:31 +00:00
## Dropping Cards and Sticky Notes not causing a return condition above.
2025-02-24 15:14:08 +00:00
if not ( to_handle is StickyNote and to_handle . is_sticky_note_attached ( ) ) :
2025-03-31 19:31:09 +00:00
if to_handle . get_parent ( ) is Card :
insert_area ( to_handle . get_parent ( ) . remove_sticky_note ( ) , to_handle )
else :
insert_area ( dropzone , to_handle )
2024-09-15 09:30:31 +00:00
current_context = NAVIGATE
focus_stickies = false
current_dropzone_id = dropzone . get_children ( ) . find ( to_handle )
if to_handle is StickyNote :
to_handle . rotation = to_handle . base_rotation
to_handle . scale = to_handle . base_scale
2023-11-01 22:19:47 +00:00
2024-09-15 09:30:31 +00:00
if input . is_action_pressed ( " mouse_right " ) and current_context == DRAG :
to_handle . reset_drag ( )
2023-07-01 13:19:54 +00:00
2025-01-31 02:22:07 +00:00
2025-12-16 22:21:54 +00:00
func _return_sticky_notes_to_panels ( ) - > void :
2025-08-18 18:59:47 +00:00
if not ( current_context == ASSIGN and focus_stickies ) : return #FIXME this is an early return to prevent race conditions. Check if it is save to be removed.
2025-02-24 15:14:08 +00:00
for panel : StickyNotePanel in sticky_note_container . get_children ( ) :
2024-09-15 09:30:31 +00:00
panel . reclaim_sticky_note ( )
2025-12-15 22:13:40 +00:00
2025-04-30 14:34:00 +00:00
for node in dropzone . get_children ( ) :
if node is StickyNote :
node . is_dragable = true
2025-05-30 14:10:44 +00:00
var board_was_completed : bool = false
func check_board_comnpletion ( ) :
if is_board_complete ( ) :
for child : StickyNotePanel in sticky_note_container . get_children ( ) :
child . clear_if_empty ( )
if not board_was_completed :
board_was_completed = true
board_completed . emit ( )
if board_was_completed :
give_lore_feedback ( )
2025-12-16 22:21:54 +00:00
func is_board_complete ( ) - > bool :
2024-09-15 09:30:31 +00:00
if mementos_collected == 4 :
for card in dropzone . get_children ( ) :
if card is Card :
if not card . has_sticky_note_attached ( ) :
return false
return true
return false
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
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 :
if child . has_sticky_note_attached ( ) :
fitting_card_count += int ( child . card_id == child . get_attached_sticky_note ( ) . parent_id )
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
2025-08-13 17:40:36 +00:00
#FIXME: check if this logic (after the "or") is still needed.
elif fitting_card_count != total_card_count or ( total_card_count != dropzone . get_child_count ( ) and sticky_note_container . get_child_count ( ) != 0 ) :
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
2025-12-16 22:21:54 +00:00
func handle_hover ( to_handle : Area2D ) - > void :
2024-09-15 09:30:31 +00:00
if Input . is_mouse_button_pressed ( MOUSE_BUTTON_LEFT ) : return
currently_active_node = to_handle
2025-12-15 22:13:40 +00:00
2024-09-15 09:30:31 +00:00
if is_in_dropzone ( to_handle ) or to_handle is Card :
2025-08-18 18:59:47 +00:00
if not ( current_context == ASSIGN and not currently_active_node . is_dragged ) : #Prevent Mouse input from messing up directional control selections
if not ( to_handle is StickyNote and ! to_handle . on_board ) :
current_dropzone_id = dropzone . get_children ( ) . find ( to_handle )
focus_stickies = false
2024-09-15 09:30:31 +00:00
else :
current_sticky_note_id = sticky_note_container . get_children ( ) . find ( to_handle . attached_to )
focus_stickies = true
2025-12-15 22:13:40 +00:00
2023-09-23 14:19:58 +00:00
# Adds a child at the correct child indext in an area
func insert_area ( parent : Control , node : Area2D ) :
2025-12-15 22:13:40 +00:00
var children : Array = parent . get_children ( )
2024-09-15 09:30:31 +00:00
var i = 0
2025-12-15 22:13:40 +00:00
2024-09-15 09:30:31 +00:00
if not node in parent . get_children ( ) :
node . reparent ( parent )
if node is StickyNote :
node . on_board = true
node . owner = self
2025-12-15 22:13:40 +00:00
2024-09-15 09:30:31 +00:00
if children . size ( ) > 0 :
children . erase ( node )
while children [ i ] . global_position . y < node . global_position . y and i + 1 < children . size ( ) :
2025-12-15 22:13:40 +00:00
i += 1
2024-09-15 09:30:31 +00:00
parent . move_child ( node , i )
2025-12-15 22:13:40 +00:00
2025-03-31 19:31:09 +00:00
if node is StickyNote :
node . attached_to = self
2025-04-30 14:34:00 +00:00
node . is_dragable = true
2025-12-15 22:13:40 +00:00
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 :
2025-12-15 22:13:40 +00:00
2026-01-12 17:39:34 +00:00
if not focused or not is_instance_valid ( currently_active_node ) : return
2025-05-16 11:19:22 +00:00
if event . is_action_pressed ( " ui_cancel " ) :
2026-01-12 17:39:34 +00:00
focused = false
2025-05-16 11:19:22 +00:00
get_viewport ( ) . set_input_as_handled ( )
2025-12-15 22:13:40 +00:00
2025-05-21 17:42:45 +00:00
if event is InputEventMouse :
# makes sure to pass release events so notes do not get attached to the mouse while the cursor leaves the area.
if event is InputEventMouseButton and current_context == DRAG :
if event . button_index == MOUSE_BUTTON_LEFT and not event . pressed :
handle_mouse_button ( event )
get_viewport ( ) . set_input_as_handled ( )
else :
return
2025-02-24 15:14:08 +00:00
if current_context != DRAG :
2025-08-18 18:59:47 +00:00
var selection_position : Vector2
if current_context == ASSIGN :
selection_position = dropzone . get_child ( current_dropzone_id ) . global_position
else :
selection_position = currently_active_node . global_position
2025-12-15 22:13:40 +00:00
2024-09-15 09:30:31 +00:00
if event . is_action_pressed ( " ui_up " ) :
if focus_stickies :
current_sticky_note_id -= 1
else :
2025-08-18 18:59:47 +00:00
if not try_select_nearest_card ( selection_position , Vector2 . UP ) :
current_dropzone_id -= 1
2025-05-16 11:19:22 +00:00
get_viewport ( ) . set_input_as_handled ( )
2025-12-15 22:13:40 +00:00
2024-09-15 09:30:31 +00:00
elif event . is_action_pressed ( " ui_down " ) : # down to select an element beneath
if focus_stickies :
current_sticky_note_id += 1
else :
2025-08-18 18:59:47 +00:00
if not try_select_nearest_card ( selection_position , Vector2 . DOWN ) :
current_dropzone_id += 1
2025-05-16 11:19:22 +00:00
get_viewport ( ) . set_input_as_handled ( )
2025-12-15 22:13:40 +00:00
2024-09-15 09:30:31 +00:00
elif event . is_action_pressed ( " ui_right " ) : # left to switch context to the left
2025-08-18 18:59:47 +00:00
if not try_select_nearest_card ( selection_position , Vector2 . RIGHT , true ) :
if not focus_stickies :
if current_context == NAVIGATE :
focus_stickies = true
elif current_context == ASSIGN :
sticky_note_container . get_children ( ) [ current_sticky_note_id ] . reclaim_sticky_note ( )
current_context = NAVIGATE
get_viewport ( ) . set_input_as_handled ( )
2025-12-15 22:13:40 +00:00
2024-09-15 09:30:31 +00:00
elif event . is_action_pressed ( " ui_left " ) : # right to switch context to the right
2025-12-13 10:06:15 +00:00
print_debug ( try_select_nearest_card ( selection_position , Vector2 . LEFT ) )
2024-09-15 09:30:31 +00:00
if focus_stickies :
if current_context == NAVIGATE :
focus_stickies = false
elif current_context == ASSIGN :
current_context = NAVIGATE
2025-08-18 18:59:47 +00:00
get_viewport ( ) . set_input_as_handled ( )
2025-12-15 22:13:40 +00:00
2024-09-15 09:30:31 +00:00
elif event . is_action_pressed ( " ui_accept " ) : # select the selected note it
2025-08-18 18:59:47 +00:00
if current_context == ASSIGN :
if not dropzone . get_child ( current_dropzone_id ) is Card : return
2025-05-08 19:53:53 +00:00
var card : Card = dropzone . get_child ( current_dropzone_id )
2025-08-18 18:59:47 +00:00
var sticky : StickyNote = currently_active_node if not focus_stickies else sticky_note_container . get_child ( current_sticky_note_id ) . attached_sticky_note
2025-12-15 22:13:40 +00:00
2025-08-18 18:59:47 +00:00
if card . has_sticky_note_attached ( ) :
currently_active_node = card . exchange_sticky_note_with ( sticky )
focus_stickies = false
if not try_select_nearest_empty_card ( currently_active_node . global_position ) :
2025-05-08 19:53:53 +00:00
current_dropzone_id = find_first_free_card ( )
2024-09-15 09:30:31 +00:00
else :
2025-08-18 18:59:47 +00:00
card . attach_sticky_note ( sticky )
current_context = NAVIGATE
for panel : StickyNotePanel in sticky_note_container . get_children ( ) :
panel . clear_if_empty ( )
if not try_select_nearest_empty_card ( currently_active_node . global_position ) :
current_dropzone_id = find_first_free_card ( )
check_board_comnpletion ( )
2025-05-08 19:53:53 +00:00
if focus_stickies :
2025-08-18 18:59:47 +00:00
focus_stickies = false
2025-05-08 19:53:53 +00:00
current_dropzone_id = current_dropzone_id
2025-08-18 18:59:47 +00:00
else :
focus_stickies = true
current_sticky_note_id -= 1
2025-12-15 22:13:40 +00:00
2025-08-18 18:59:47 +00:00
elif current_context == NAVIGATE :
if focus_stickies :
# this is kind of redundant, but a safety feature to avoid active node and index misaligning.
currently_active_node = sticky_note_container . get_children ( ) [ current_sticky_note_id ] . get_child ( 0 )
2025-05-08 19:53:53 +00:00
current_context = ASSIGN
2025-08-18 18:59:47 +00:00
focus_stickies = false
if not try_select_nearest_empty_card ( currently_active_node . global_position ) :
current_dropzone_id = find_first_free_card ( )
else :
if currently_active_node is StickyNote :
add_sticky_note ( currently_active_node )
current_sticky_note_id = sticky_note_container . get_child_count ( ) - 1
current_context = ASSIGN
focus_stickies = false
if currently_active_node is Card :
if currently_active_node . has_sticky_note_attached ( ) :
currently_active_node = currently_active_node . remove_sticky_note ( )
add_sticky_note ( currently_active_node )
current_sticky_note_id = sticky_note_container . get_child_count ( ) - 1
focus_stickies = true
else :
if not is_board_complete ( ) :
current_context = ASSIGN
focus_stickies = true
current_sticky_note_id = current_sticky_note_id
2025-05-16 11:19:22 +00:00
get_viewport ( ) . set_input_as_handled ( )
2023-07-02 13:10:33 +00:00
2026-01-12 21:50:49 +00:00
## Awaitable function to show the board and wait until user closes it
func play ( ) - > void :
focused = true
Input . mouse_mode = Input . MOUSE_MODE_VISIBLE
await closed
2023-10-15 11:27:02 +00:00
func find_first_free_card ( ) - > int :
2024-09-15 09:30:31 +00:00
for i in range ( dropzone . get_child_count ( ) ) :
# start searching at the current location, use modulo to avoid getting out of array bounds
if ! dropzone . get_child ( ( i + current_dropzone_id ) % dropzone . get_child_count ( ) ) . has_sticky_note_attached ( ) :
return ( i + current_dropzone_id ) % dropzone . get_child_count ( )
return - 1
2023-11-01 22:19:47 +00:00
2025-08-18 18:59:47 +00:00
func on_sticky_panel_cleared ( at_id : int ) :
if current_sticky_note_id == at_id :
current_sticky_note_id += 1
if current_sticky_note_id == sticky_note_container . get_child_count ( ) - 1 :
if current_sticky_note_id - 1 != at_id :
current_sticky_note_id -= 1
else :
current_sticky_note_id += 1
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 ( )
savegame . board_in_panel . clear ( )
2026-01-16 12:59:09 +00:00
savegame . board_randoms . clear ( )
2025-12-15 22:13:40 +00:00
2026-01-16 15:30:58 +00:00
print_debug ( " CardBoard: Saving board state... " )
2024-10-02 23:12:24 +00:00
for child in dropzone . get_children ( ) :
if child is Card :
2026-01-16 15:30:58 +00:00
# Save card position (local to dropzone)
savegame . board_positions [ child . name ] = child . position
print_debug ( " Card ' %s ' at %s " % [ child . name , child . position ] )
2025-06-02 23:48:52 +00:00
if child . picked_random :
2026-01-16 12:59:09 +00:00
savegame . board_randoms . append ( child . name )
2025-12-15 22:13:40 +00:00
2026-01-16 12:59:09 +00:00
var note : StickyNote = child . get_attached_sticky_note ( )
2026-01-12 00:22:25 +00:00
if note :
2026-01-16 15:30:58 +00:00
# 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 )
2025-12-15 22:13:40 +00:00
2024-10-02 23:12:24 +00:00
elif child is StickyNote :
2026-01-16 15:30:58 +00:00
# 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 )
2025-12-15 22:13:40 +00:00
2024-10-02 23:12:24 +00:00
for child in sticky_note_container . get_children ( ) :
2026-01-16 15:30:58 +00:00
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 ( )
] )
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 12:59: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 ( ) :
print_debug ( " CardBoard: No board state to load (save is empty or legacy format) " )
2026-01-16 12:59:09 +00:00
return
2026-01-16 15:30:58 +00:00
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 ( )
] )
# 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 ( ) )
2025-12-15 22:13:40 +00:00
2026-01-16 15:30:58 +00:00
var card_pile : Dictionary [ String , Array ] = HardCards . get_cards_by_name_array ( all_names )
2026-01-16 12:59:09 +00:00
# Track cards by name for sticky note attachment
var cards_by_name : Dictionary = { }
2026-01-16 15:30:58 +00:00
# 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 ( ) )
2026-01-16 12:59:09 +00:00
for card : Card in card_pile [ " cards " ] :
2026-01-16 15:30:58 +00:00
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
2026-01-16 12:59:09 +00:00
cards_by_name [ card . name ] = card
card . picked_random = savegame . board_randoms . has ( card . card_id )
2026-01-16 15:30:58 +00:00
# Add all sticky notes
print_debug ( " Loading %d stickies... " % card_pile [ " sticky_notes " ] . size ( ) )
2026-01-16 12:59:09 +00:00
for sticky : StickyNote in card_pile [ " sticky_notes " ] :
2026-01-16 15:30:58 +00:00
# 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 )
# 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 ] )
2025-12-02 20:33:48 +00:00
add_sticky_note ( sticky , false )
2026-01-16 15:30:58 +00:00
# Sticky is loose on board
2024-10-02 23:12:24 +00:00
else :
2026-01-16 15:30:58 +00:00
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 ] )
2024-10-02 23:12:24 +00:00
insert_area ( dropzone , sticky )
2026-01-16 15:30:58 +00:00
sticky . set_owner ( self )
sticky . current_handle = self # Required for input handling
sticky . on_board = true
sticky . attached_to = self
sticky . is_dragable = true
2026-01-16 12:59:09 +00:00
sticky . picked_random = savegame . board_randoms . has ( sticky . sticky_id )
2025-12-15 22:13:40 +00:00
2026-01-16 15:30:58 +00:00
print_debug ( " CardBoard: Load complete! " )
2025-03-31 19:31:09 +00:00
2025-08-18 18:59:47 +00:00
func try_select_nearest_card ( from : Vector2 , towards : Vector2 , include_stickies : bool = false ) - > bool :
2026-01-16 12:59:09 +00:00
var selection_transform : = Transform2D ( 0 , from ) . looking_at ( from + towards )
2025-12-15 22:13:40 +00:00
2025-08-18 18:59:47 +00:00
var scores : Dictionary [ int , Area2D ] = { - 1 : null }
for child : Area2D in dropzone . get_children ( ) :
if not ( child is StickyNote and current_context == ASSIGN ) :
scores [ get_distance_score ( child . global_position , selection_transform ) ] = child
scores . erase ( - 1 )
scores . sort ( )
2025-12-15 22:13:40 +00:00
2025-08-18 18:59:47 +00:00
if include_stickies :
var panel_scores : Dictionary [ int , StickyNotePanel ] = { - 1 : null }
for child : StickyNotePanel in sticky_note_container . get_children ( ) :
if not child . is_empty ( ) :
panel_scores [ get_distance_score ( child . attached_sticky_note . global_position , selection_transform ) ] = child
panel_scores . erase ( - 1 )
panel_scores . sort ( )
2025-12-15 22:13:40 +00:00
2025-08-18 18:59:47 +00:00
if panel_scores != { } :
if scores != { } :
if panel_scores . keys ( ) [ 0 ] < scores . keys ( ) [ 0 ] :
if current_context == ASSIGN : return false
current_sticky_note_id = sticky_note_container . get_children ( ) . find ( panel_scores . values ( ) [ 0 ] )
focus_stickies = true
return true
else :
if current_context == ASSIGN : return false
current_sticky_note_id = sticky_note_container . get_children ( ) . find ( panel_scores . values ( ) [ 0 ] )
focus_stickies = true
return true
2025-12-15 22:13:40 +00:00
2025-08-18 18:59:47 +00:00
if scores != { } :
current_dropzone_id = dropzone . get_children ( ) . find ( scores . values ( ) [ 0 ] )
return true
return false
func try_select_nearest_empty_card ( from : Vector2 ) - > bool :
var scores : Dictionary [ int , Area2D ] = { }
2025-12-15 22:13:40 +00:00
2025-08-18 18:59:47 +00:00
for card in dropzone . get_children ( ) :
if card is Card :
if not card . has_sticky_note_attached ( ) :
scores [ int ( ( from - card . global_position ) . length ( ) ) ] = card
2025-12-15 22:13:40 +00:00
2025-08-18 18:59:47 +00:00
scores . sort ( )
2025-12-15 22:13:40 +00:00
2025-08-18 18:59:47 +00:00
if scores != { } :
current_dropzone_id = dropzone . get_children ( ) . find ( scores . values ( ) [ 0 ] )
return true
return false
func get_distance_score ( from : Vector2 , to : Transform2D ) - > int :
2026-01-16 12:59:09 +00:00
var diff : = from * to
var dir : = diff . normalized ( )
2025-08-18 18:59:47 +00:00
if dir . x > 0.5 and diff . length ( ) > 0 :
return int ( ( abs ( dir . y ) + 0.5 ) * diff . length ( ) )
else :
return - 1