755 lines
27 KiB
GDScript
755 lines
27 KiB
GDScript
class_name CardBoard extends PanelContainer
|
|
|
|
enum {NAVIGATE, ASSIGN, DRAG}
|
|
|
|
var focus_stickies:bool = true:
|
|
set(stickies):
|
|
if not is_node_ready(): return
|
|
if stickies and sticky_note_container.get_child_count() == 0: return
|
|
|
|
# this messes things up if called unneeded.
|
|
if focus_stickies != stickies:
|
|
focus_stickies = stickies
|
|
|
|
if not current_context == ASSIGN:
|
|
if stickies:
|
|
current_sticky_note_id = current_sticky_note_id
|
|
else:
|
|
current_dropzone_id = current_dropzone_id
|
|
|
|
var focused := false:
|
|
set(value):
|
|
if focused == value:
|
|
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()
|
|
|
|
|
|
@onready var dropzone := $HBoxContainer/dropzone
|
|
var dropzone_size: Vector2
|
|
@export var dropzone_padding:int = 100
|
|
@onready var sticky_note_container := $HBoxContainer/ScrollContainer/VBoxContainer
|
|
@onready var current_context:int = NAVIGATE:
|
|
set(context):
|
|
current_context = context
|
|
@onready var instructions := $instructions_panel/HBoxContainer/cards_remaining
|
|
|
|
var mementos_collected: int = 0:
|
|
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."
|
|
|
|
@onready var currently_active_node: Area2D = null:
|
|
set(new_node):
|
|
# this makes sure no accidental context switches can happen while a card is being dragged.
|
|
if not (current_context == DRAG):
|
|
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
|
|
|
|
@onready var current_dropzone_id: int = 0:
|
|
set(new_id):
|
|
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:
|
|
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()
|
|
(dropzone.get_child(current_dropzone_id) as Card).preview_sticky_note(currently_active_node)
|
|
|
|
elif not focus_stickies:
|
|
currently_active_node = dropzone.get_child(current_dropzone_id)
|
|
|
|
@onready var current_sticky_note_id: int = 0:
|
|
set(new_id):
|
|
if is_node_ready():
|
|
if sticky_note_container.get_child_count() < 1: return
|
|
elif sticky_note_container.get_child_count() == 1: current_sticky_note_id = 0
|
|
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
|
|
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
|
|
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)
|
|
elif focus_stickies:
|
|
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)
|
|
|
|
|
|
|
|
signal board_completed
|
|
signal closed
|
|
|
|
# Called when the node enters the scene tree for the first time.
|
|
func _ready():
|
|
print("Board Ready!", self, "room", State.room)
|
|
State.room.card_board = self
|
|
|
|
var size_reference := StickyNotePanel.new()
|
|
|
|
dropzone_size = get_viewport_rect().size - Vector2(dropzone_padding + size_reference.minimum_size.x, dropzone_padding)
|
|
|
|
if get_parent() == get_tree().root:
|
|
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"])
|
|
|
|
get_viewport().gui_focus_changed.connect(reclaim_lost_focus)
|
|
|
|
|
|
## 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
|
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
|
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
|
|
|
|
|
|
func reclaim_lost_focus(_thief):
|
|
if focused:
|
|
grab_focus()
|
|
|
|
## Will be used later to spawn Cards and Post-Its and remember them in the dictionary
|
|
func populate_board(card_names: Array[StringName]):
|
|
mementos_collected += 1
|
|
|
|
var all_new:Dictionary = HardCards.get_cards_by_name_array(card_names)
|
|
|
|
# spawning the cards and adding them to the dictionary
|
|
for new_card: Card in all_new["cards"]:
|
|
add_card(new_card, false)
|
|
# 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
|
|
add_sticky_note(new_sticky_note, false)
|
|
# marking the first sticky as random picks
|
|
new_sticky_note.picked_random = new_sticky_note.name == card_names[3]
|
|
|
|
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 = _generate_random_position()
|
|
insert_area(dropzone, card)
|
|
card.set_owner(self)
|
|
card.is_dragable = true
|
|
|
|
func add_sticky_note(sticky: StickyNote, re_parent:bool = true):
|
|
var new_panel := StickyNotePanel.new()
|
|
sticky_note_container.add_child(new_panel, true, Node.INTERNAL_MODE_DISABLED)
|
|
#WARNING this for some reason would break the tweens
|
|
new_panel.set_owner(self)
|
|
sticky.current_handle = self
|
|
new_panel.attatch_sticky_note(sticky, self, false, re_parent)
|
|
|
|
# Checks if a Node is currently inside the dropzone
|
|
func is_in_dropzone(to_check: Node) -> bool:
|
|
return dropzone.get_rect().has_point(to_check.global_position)
|
|
|
|
# Called by notes when a mouse event needs handling
|
|
func handle_mouse_button(input: InputEventMouseButton, to_handle = currently_active_node) -> void:
|
|
|
|
# Makes sure that only the same area is dragged.
|
|
# Otherwise overlapping areas are dragged at the same time.
|
|
if current_context == DRAG and to_handle != currently_active_node:
|
|
return
|
|
|
|
if input.button_index == MOUSE_BUTTON_MASK_LEFT and input.pressed:
|
|
currently_active_node = to_handle
|
|
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
|
|
to_handle.attached_to = self
|
|
current_context = DRAG
|
|
|
|
|
|
# when Drag stops ...
|
|
if input.button_index == MOUSE_BUTTON_MASK_LEFT and not input.pressed:
|
|
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
|
|
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
|
|
to_handle.reset_drag()
|
|
current_context = NAVIGATE
|
|
_return_sticky_notes_to_panels()
|
|
return
|
|
else:
|
|
area.attach_sticky_note(to_handle)
|
|
to_handle.z_index = 0
|
|
if sticky_note_container.get_child_count() > 0:
|
|
sticky_note_container.get_child(current_sticky_note_id).clear_if_empty()
|
|
current_context = NAVIGATE
|
|
check_board_comnpletion()
|
|
return
|
|
else:
|
|
var i: int = 0
|
|
for panel: StickyNotePanel in sticky_note_container.get_children():
|
|
i += 1
|
|
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():
|
|
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()
|
|
_return_sticky_notes_to_panels()
|
|
current_context = NAVIGATE
|
|
return
|
|
|
|
## Dropping Cards and Sticky Notes not causing a return condition above.
|
|
if not (to_handle is StickyNote and to_handle.is_sticky_note_attached()):
|
|
if to_handle.get_parent() is Card:
|
|
insert_area(to_handle.get_parent().remove_sticky_note(), to_handle)
|
|
else:
|
|
insert_area(dropzone, to_handle)
|
|
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
|
|
|
|
if input.is_action_pressed("mouse_right") and current_context == DRAG:
|
|
to_handle.reset_drag()
|
|
|
|
|
|
func _return_sticky_notes_to_panels() -> void:
|
|
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.
|
|
for panel:StickyNotePanel in sticky_note_container.get_children():
|
|
panel.reclaim_sticky_note()
|
|
|
|
for node in dropzone.get_children():
|
|
if node is StickyNote:
|
|
node.is_dragable = true
|
|
|
|
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()
|
|
|
|
func is_board_complete() -> bool:
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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:
|
|
if State.speech_language == 2:
|
|
$AnimationPlayer.play("unfitting_de")
|
|
else:
|
|
$AnimationPlayer.play("unfitting")
|
|
unfitting = true
|
|
#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):
|
|
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:
|
|
if State.speech_language == 2:
|
|
$AnimationPlayer.play("incomplete_de")
|
|
else:
|
|
$AnimationPlayer.play("incomplete")
|
|
incomplete = true
|
|
else:
|
|
instructions.text = "Lisa would like you to leave her room and move on."
|
|
if not complete:
|
|
if State.speech_language == 2:
|
|
$AnimationPlayer.play("complete_de")
|
|
else:
|
|
$AnimationPlayer.play("complete")
|
|
complete = true
|
|
|
|
# Mark area that was hovered over as currently selected
|
|
func handle_hover(to_handle: Area2D) -> void:
|
|
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): return
|
|
currently_active_node = to_handle
|
|
|
|
if is_in_dropzone(to_handle) or to_handle is Card:
|
|
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
|
|
else:
|
|
current_sticky_note_id = sticky_note_container.get_children().find(to_handle.attached_to)
|
|
focus_stickies = true
|
|
|
|
# Adds a child at the correct child indext in an area
|
|
func insert_area(parent: Control, node: Area2D):
|
|
var children:Array = parent.get_children()
|
|
var i = 0
|
|
|
|
if not node in parent.get_children():
|
|
node.reparent(parent)
|
|
if node is StickyNote:
|
|
node.on_board = true
|
|
node.owner = self
|
|
|
|
if children.size() > 0:
|
|
children.erase(node)
|
|
while children[i].global_position.y < node.global_position.y and i+1 < children.size():
|
|
i+=1
|
|
parent.move_child(node, i)
|
|
|
|
if node is StickyNote:
|
|
node.attached_to = self
|
|
node.is_dragable = true
|
|
|
|
# Takes the inputs for control inputs
|
|
func _input(event) -> void:
|
|
|
|
if not focused or not is_instance_valid(currently_active_node): return
|
|
|
|
if event.is_action_pressed("ui_cancel"):
|
|
focused = false
|
|
get_viewport().set_input_as_handled()
|
|
|
|
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
|
|
|
|
if current_context != DRAG:
|
|
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
|
|
|
|
if event.is_action_pressed("ui_up"):
|
|
if focus_stickies:
|
|
current_sticky_note_id -= 1
|
|
else:
|
|
if not try_select_nearest_card(selection_position, Vector2.UP):
|
|
current_dropzone_id -= 1
|
|
get_viewport().set_input_as_handled()
|
|
|
|
elif event.is_action_pressed("ui_down"): # down to select an element beneath
|
|
if focus_stickies:
|
|
current_sticky_note_id += 1
|
|
else:
|
|
if not try_select_nearest_card(selection_position, Vector2.DOWN):
|
|
current_dropzone_id += 1
|
|
get_viewport().set_input_as_handled()
|
|
|
|
elif event.is_action_pressed("ui_right"): # left to switch context to the left
|
|
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()
|
|
|
|
elif event.is_action_pressed("ui_left"): # right to switch context to the right
|
|
print_debug(try_select_nearest_card(selection_position, Vector2.LEFT))
|
|
if focus_stickies:
|
|
if current_context == NAVIGATE:
|
|
focus_stickies = false
|
|
elif current_context == ASSIGN:
|
|
current_context = NAVIGATE
|
|
get_viewport().set_input_as_handled()
|
|
|
|
elif event.is_action_pressed("ui_accept"): # select the selected note it
|
|
if current_context == ASSIGN:
|
|
if not dropzone.get_child(current_dropzone_id) is Card: return
|
|
var card:Card = dropzone.get_child(current_dropzone_id)
|
|
var sticky: StickyNote = currently_active_node if not focus_stickies else sticky_note_container.get_child(current_sticky_note_id).attached_sticky_note
|
|
|
|
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):
|
|
current_dropzone_id = find_first_free_card()
|
|
else:
|
|
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()
|
|
if focus_stickies:
|
|
focus_stickies = false
|
|
current_dropzone_id = current_dropzone_id
|
|
else:
|
|
focus_stickies = true
|
|
current_sticky_note_id -= 1
|
|
|
|
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)
|
|
current_context = ASSIGN
|
|
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
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
## 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
|
|
|
|
func find_first_free_card() -> int:
|
|
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
|
|
|
|
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
|
|
|
|
## Saves board state directly to SaveGame resource
|
|
func save_to_resource(savegame: SaveGame) -> void:
|
|
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 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 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 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 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_positions.is_empty():
|
|
print_debug("CardBoard: No board state to load (save is empty or legacy format)")
|
|
return
|
|
|
|
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())
|
|
|
|
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_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"]:
|
|
# 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])
|
|
add_sticky_note(sticky, false)
|
|
|
|
# Sticky is loose on board
|
|
else:
|
|
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.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)
|
|
|
|
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()
|
|
|
|
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()
|
|
|
|
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
|
|
|
|
|
|
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] = {}
|
|
|
|
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
|
|
|
|
scores.sort()
|
|
|
|
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:
|
|
var diff := from * to
|
|
var dir := diff.normalized()
|
|
if dir.x > 0.5 and diff.length() > 0:
|
|
return int((abs(dir.y) + 0.5) * diff.length())
|
|
else:
|
|
return -1
|