Compare commits
No commits in common. "6e94ccc1344fa181b5e80ec85215ba577a28fd13" and "971c1622368f18e1d999d742d021b383b4b22fd1" have entirely different histories.
6e94ccc134
...
971c162236
|
|
@ -138,11 +138,6 @@ func _on_board_focused() -> void:
|
||||||
if is_node_ready():
|
if is_node_ready():
|
||||||
process_mode = Node.PROCESS_MODE_INHERIT
|
process_mode = Node.PROCESS_MODE_INHERIT
|
||||||
|
|
||||||
# Check board state and give lore feedback when presented
|
|
||||||
if is_board_complete():
|
|
||||||
board_was_completed = true
|
|
||||||
give_lore_feedback()
|
|
||||||
|
|
||||||
|
|
||||||
## Called when board loses focus
|
## Called when board loses focus
|
||||||
func _on_board_unfocused() -> void:
|
func _on_board_unfocused() -> void:
|
||||||
|
|
@ -209,99 +204,91 @@ func is_in_dropzone(to_check: Node) -> bool:
|
||||||
|
|
||||||
# Called by notes when a mouse event needs handling
|
# Called by notes when a mouse event needs handling
|
||||||
func handle_mouse_button(input: InputEventMouseButton, to_handle = currently_active_node) -> void:
|
func handle_mouse_button(input: InputEventMouseButton, to_handle = currently_active_node) -> void:
|
||||||
# Prevent dragging multiple nodes at once
|
|
||||||
|
# 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:
|
if current_context == DRAG and to_handle != currently_active_node:
|
||||||
return
|
return
|
||||||
|
|
||||||
# === DRAG START ===
|
if input.button_index == MOUSE_BUTTON_MASK_LEFT and input.pressed:
|
||||||
if input.button_index == MOUSE_BUTTON_LEFT and input.pressed:
|
currently_active_node = to_handle
|
||||||
_start_drag(to_handle)
|
to_handle.is_dragged = true
|
||||||
return
|
if to_handle is StickyNote:
|
||||||
|
if not to_handle.on_board:
|
||||||
# === DRAG END ===
|
to_handle.reparent(dropzone)
|
||||||
if input.button_index == MOUSE_BUTTON_LEFT and not input.pressed:
|
to_handle.on_board = true
|
||||||
_end_drag(to_handle)
|
to_handle.attached_to = self
|
||||||
return
|
current_context = DRAG
|
||||||
|
|
||||||
|
|
||||||
## Starts a drag operation for the given draggable
|
# when Drag stops ...
|
||||||
func _start_drag(draggable: Draggable) -> void:
|
if input.button_index == MOUSE_BUTTON_MASK_LEFT and not input.pressed:
|
||||||
currently_active_node = draggable
|
to_handle.is_dragged = false
|
||||||
current_context = DRAG
|
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
|
||||||
|
|
||||||
var mouse_offset = get_viewport().get_mouse_position() - draggable.global_position
|
## Dropping Cards and Sticky Notes not causing a return condition above.
|
||||||
draggable.start_drag(mouse_offset)
|
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)
|
||||||
## Ends a drag operation and handles the drop
|
else:
|
||||||
func _end_drag(draggable: Draggable) -> void:
|
insert_area(dropzone, to_handle)
|
||||||
draggable.end_drag()
|
current_context = NAVIGATE
|
||||||
|
|
||||||
# Let draggable find its own drop target
|
|
||||||
var drop_target = draggable.find_drop_target()
|
|
||||||
|
|
||||||
# Execute the drop
|
|
||||||
if drop_target and Draggable.is_drop_target(drop_target):
|
|
||||||
var result = drop_target.handle_drop(draggable)
|
|
||||||
|
|
||||||
# Handle exchange result (sticky swapped with card's sticky)
|
|
||||||
if result == Draggable.DropResult.EXCHANGED:
|
|
||||||
_handle_sticky_exchange(draggable, drop_target)
|
|
||||||
elif draggable is StickyNote and not is_in_dropzone(draggable):
|
|
||||||
# Sticky dropped in panel area but no empty panel found - create one
|
|
||||||
add_sticky_note(draggable)
|
|
||||||
else:
|
|
||||||
# Fallback: use default board drop
|
|
||||||
handle_drop(draggable)
|
|
||||||
|
|
||||||
# Cleanup and state update
|
|
||||||
_return_sticky_notes_to_panels()
|
|
||||||
current_context = NAVIGATE
|
|
||||||
_update_focus_after_drop(draggable)
|
|
||||||
|
|
||||||
# Check win condition if sticky was attached to card
|
|
||||||
if draggable is StickyNote and draggable.is_sticky_note_attached():
|
|
||||||
check_board_comnpletion()
|
|
||||||
|
|
||||||
|
|
||||||
## Handles the exchange when a sticky is dropped on a card that already has one
|
|
||||||
## The exchanged sticky always goes to the sticky_note_container (panel zone)
|
|
||||||
func _handle_sticky_exchange(new_sticky: StickyNote, card: Card) -> void:
|
|
||||||
var old_sticky = card.get_last_exchanged_sticky()
|
|
||||||
|
|
||||||
if not old_sticky:
|
|
||||||
push_warning("CardBoard: Exchange occurred but no sticky returned")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Reset visual state for old sticky
|
|
||||||
old_sticky.rotation = old_sticky.base_rotation
|
|
||||||
old_sticky.scale = old_sticky.base_scale
|
|
||||||
old_sticky.z_index = 0
|
|
||||||
|
|
||||||
# Exchanged sticky always goes to sticky_note_container
|
|
||||||
if new_sticky._came_from_panel and sticky_note_container.get_child_count() > 0:
|
|
||||||
# New sticky came from panel - return old sticky to that panel (swap positions)
|
|
||||||
var target_panel = sticky_note_container.get_child(current_sticky_note_id)
|
|
||||||
old_sticky.reparent(dropzone)
|
|
||||||
old_sticky.on_board = true
|
|
||||||
target_panel.attached_sticky_note = old_sticky
|
|
||||||
old_sticky.attached_to = target_panel
|
|
||||||
target_panel.attatch_sticky_note(old_sticky, self, false, true)
|
|
||||||
else:
|
|
||||||
# New sticky was loose - create new panel for old sticky
|
|
||||||
add_sticky_note(old_sticky)
|
|
||||||
|
|
||||||
# Clean up empty panel if the new sticky came from one
|
|
||||||
if new_sticky._came_from_panel and sticky_note_container.get_child_count() > 0:
|
|
||||||
sticky_note_container.get_child(current_sticky_note_id).clear_if_empty()
|
|
||||||
|
|
||||||
|
|
||||||
## Updates focus and navigation state after a drop
|
|
||||||
func _update_focus_after_drop(draggable: Draggable) -> void:
|
|
||||||
# Update focus based on where the item ended up
|
|
||||||
if draggable is Card or (draggable is StickyNote and draggable.on_board):
|
|
||||||
focus_stickies = false
|
focus_stickies = false
|
||||||
current_dropzone_id = dropzone.get_children().find(draggable)
|
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:
|
func _return_sticky_notes_to_panels() -> void:
|
||||||
|
|
@ -392,7 +379,7 @@ func insert_area(parent: Control, node: Area2D):
|
||||||
var i = 0
|
var i = 0
|
||||||
|
|
||||||
if not node in parent.get_children():
|
if not node in parent.get_children():
|
||||||
node.reparent(parent, false) # Don't preserve global transform - we set positions explicitly
|
node.reparent(parent)
|
||||||
if node is StickyNote:
|
if node is StickyNote:
|
||||||
node.on_board = true
|
node.on_board = true
|
||||||
node.owner = self
|
node.owner = self
|
||||||
|
|
@ -407,65 +394,6 @@ func insert_area(parent: Control, node: Area2D):
|
||||||
node.attached_to = self
|
node.attached_to = self
|
||||||
node.is_dragable = true
|
node.is_dragable = true
|
||||||
|
|
||||||
## Sorts all children in dropzone by their Y position
|
|
||||||
## Call this after bulk loading to fix child order / z-index issues
|
|
||||||
func _sort_dropzone_children() -> void:
|
|
||||||
var children = dropzone.get_children()
|
|
||||||
if children.size() <= 1:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Sort by global Y position
|
|
||||||
children.sort_custom(func(a, b): return a.global_position.y < b.global_position.y)
|
|
||||||
|
|
||||||
# Reorder children in the scene tree
|
|
||||||
for i in range(children.size()):
|
|
||||||
dropzone.move_child(children[i], i)
|
|
||||||
|
|
||||||
print_debug("CardBoard: Re-sorted %d dropzone children by Y position" % children.size())
|
|
||||||
|
|
||||||
# Force collision shape updates on next physics frame
|
|
||||||
# This ensures Area2D hover detection works correctly after repositioning
|
|
||||||
_update_collision_shapes.call_deferred()
|
|
||||||
|
|
||||||
## Forces collision shape updates for all cards/stickies in dropzone
|
|
||||||
func _update_collision_shapes() -> void:
|
|
||||||
await get_tree().process_frame
|
|
||||||
for child in dropzone.get_children():
|
|
||||||
if child is Area2D:
|
|
||||||
# Force collision shape update by toggling monitoring
|
|
||||||
var was_monitoring = child.monitoring
|
|
||||||
child.monitoring = false
|
|
||||||
child.monitoring = was_monitoring
|
|
||||||
|
|
||||||
# === 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
|
|
||||||
|
|
||||||
if draggable is StickyNote:
|
|
||||||
# Handle sticky note drop
|
|
||||||
var sticky = draggable as StickyNote
|
|
||||||
insert_area(dropzone, sticky)
|
|
||||||
sticky.attached_to = self
|
|
||||||
sticky.on_board = true
|
|
||||||
sticky.is_dragable = true
|
|
||||||
# Reset visual state
|
|
||||||
sticky.rotation = sticky.base_rotation
|
|
||||||
sticky.scale = sticky.base_scale
|
|
||||||
elif draggable is Card:
|
|
||||||
# Handle card drop
|
|
||||||
insert_area(dropzone, draggable)
|
|
||||||
draggable.is_dragable = true
|
|
||||||
|
|
||||||
return Draggable.DropResult.ACCEPTED
|
|
||||||
|
|
||||||
|
|
||||||
# Takes the inputs for control inputs
|
# Takes the inputs for control inputs
|
||||||
func _input(event) -> void:
|
func _input(event) -> void:
|
||||||
|
|
||||||
|
|
@ -702,26 +630,22 @@ func initialise_from_save(savegame: SaveGame) -> void:
|
||||||
# Add all cards
|
# Add all cards
|
||||||
print_debug(" Loading %d cards..." % card_pile["cards"].size())
|
print_debug(" Loading %d cards..." % card_pile["cards"].size())
|
||||||
for card: Card in card_pile["cards"]:
|
for card: Card in card_pile["cards"]:
|
||||||
# Determine target position (will be set after adding to scene)
|
|
||||||
var target_position: Vector2
|
|
||||||
if savegame.board_positions.has(card.name):
|
|
||||||
target_position = savegame.board_positions[card.name]
|
|
||||||
print_debug(" Card '%s' loading at %s" % [card.name, target_position])
|
|
||||||
else:
|
|
||||||
target_position = _generate_random_position()
|
|
||||||
print_debug(" Card '%s' - generated random position: %s" % [card.name, target_position])
|
|
||||||
|
|
||||||
# Add to board first
|
|
||||||
add_child(card)
|
add_child(card)
|
||||||
|
insert_area(dropzone, card)
|
||||||
|
|
||||||
|
# Set position from save, or generate random if missing (must be after insert_area)
|
||||||
|
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])
|
||||||
|
|
||||||
card.set_owner(self)
|
card.set_owner(self)
|
||||||
card.is_dragable = true
|
card.is_dragable = true
|
||||||
cards_by_name[card.name] = card
|
cards_by_name[card.name] = card
|
||||||
card.picked_random = savegame.board_randoms.has(card.card_id)
|
card.picked_random = savegame.board_randoms.has(card.card_id)
|
||||||
|
|
||||||
# Move to dropzone and set position (position must be set after adding to scene)
|
|
||||||
insert_area(dropzone, card)
|
|
||||||
card.position = target_position
|
|
||||||
|
|
||||||
# Add all sticky notes
|
# Add all sticky notes
|
||||||
print_debug(" Loading %d stickies..." % card_pile["sticky_notes"].size())
|
print_debug(" Loading %d stickies..." % card_pile["sticky_notes"].size())
|
||||||
for sticky: StickyNote in card_pile["sticky_notes"]:
|
for sticky: StickyNote in card_pile["sticky_notes"]:
|
||||||
|
|
@ -746,37 +670,27 @@ func initialise_from_save(savegame: SaveGame) -> void:
|
||||||
|
|
||||||
# Sticky is loose on board
|
# Sticky is loose on board
|
||||||
else:
|
else:
|
||||||
# Determine target position (will be set after adding to scene)
|
|
||||||
var target_position: Vector2
|
|
||||||
if savegame.board_positions.has(sticky.name):
|
|
||||||
target_position = savegame.board_positions[sticky.name]
|
|
||||||
print_debug(" Loose sticky '%s' loading at %s" % [sticky.name, target_position])
|
|
||||||
else:
|
|
||||||
target_position = _generate_random_position()
|
|
||||||
print_debug(" Loose sticky '%s' - generated random position: %s" % [sticky.name, target_position])
|
|
||||||
|
|
||||||
# Add to board first
|
|
||||||
add_child(sticky)
|
add_child(sticky)
|
||||||
|
insert_area(dropzone, sticky)
|
||||||
|
|
||||||
|
# Set position from save, or generate random if missing (must be after insert_area)
|
||||||
|
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])
|
||||||
|
|
||||||
sticky.set_owner(self)
|
sticky.set_owner(self)
|
||||||
sticky.current_handle = self # Required for input handling
|
sticky.current_handle = self # Required for input handling
|
||||||
sticky.on_board = true
|
sticky.on_board = true
|
||||||
sticky.attached_to = self
|
sticky.attached_to = self
|
||||||
sticky.is_dragable = true
|
sticky.is_dragable = true
|
||||||
|
|
||||||
# Move to dropzone and set position (position must be set after adding to scene)
|
|
||||||
insert_area(dropzone, sticky)
|
|
||||||
sticky.position = target_position
|
|
||||||
|
|
||||||
sticky.picked_random = savegame.board_randoms.has(sticky.sticky_id)
|
sticky.picked_random = savegame.board_randoms.has(sticky.sticky_id)
|
||||||
|
|
||||||
print_debug("CardBoard: Load complete!")
|
print_debug("CardBoard: Load complete!")
|
||||||
|
|
||||||
# Re-sort dropzone children now that all positions are set correctly
|
|
||||||
# This fixes hover detection issues caused by incorrect z-order during load
|
|
||||||
_sort_dropzone_children()
|
|
||||||
|
|
||||||
# Note: Lore feedback will be triggered when board is presented (in play())
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func try_select_nearest_card(from: Vector2, towards: Vector2, include_stickies: bool = false) -> bool:
|
func try_select_nearest_card(from: Vector2, towards: Vector2, include_stickies: bool = false) -> bool:
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,8 @@ func _on_input_event(_viewport, event, _shape_idx):
|
||||||
|
|
||||||
func _move_card():
|
func _move_card():
|
||||||
if is_dragged:
|
if is_dragged:
|
||||||
update_drag_position(get_viewport().get_mouse_position())
|
position = get_viewport().get_mouse_position() - mouse_offset
|
||||||
|
confine_to_screen()
|
||||||
|
|
||||||
func has_sticky_note_attached() -> bool:
|
func has_sticky_note_attached() -> bool:
|
||||||
return get_attached_sticky_note() != null
|
return get_attached_sticky_note() != null
|
||||||
|
|
@ -295,49 +296,3 @@ func reclaim_sticky_note():
|
||||||
await current_sticky_note.transform_tween_finished
|
await current_sticky_note.transform_tween_finished
|
||||||
current_sticky_note.reparent(self)
|
current_sticky_note.reparent(self)
|
||||||
current_sticky_note.owner = self.owner
|
current_sticky_note.owner = self.owner
|
||||||
|
|
||||||
|
|
||||||
# === DROP TARGET PATTERN IMPLEMENTATION ===
|
|
||||||
|
|
||||||
## Temporary storage for exchanged sticky during drop operation
|
|
||||||
var _last_exchanged_sticky: StickyNote = null
|
|
||||||
|
|
||||||
## Checks if this card can accept the given draggable
|
|
||||||
func can_accept_drop(draggable: Draggable) -> bool:
|
|
||||||
return draggable is StickyNote
|
|
||||||
|
|
||||||
## Handles dropping a sticky note onto this card
|
|
||||||
## Returns DropResult indicating success, rejection, or exchange
|
|
||||||
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
|
|
||||||
_last_exchanged_sticky = exchange_sticky_note_with(draggable)
|
|
||||||
# Reset z_index for newly attached sticky
|
|
||||||
draggable.z_index = 0
|
|
||||||
return Draggable.DropResult.EXCHANGED
|
|
||||||
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
|
|
||||||
|
|
||||||
## Retrieves the sticky that was exchanged during last drop
|
|
||||||
## Clears the reference after retrieval
|
|
||||||
func get_last_exchanged_sticky() -> StickyNote:
|
|
||||||
var result = _last_exchanged_sticky
|
|
||||||
_last_exchanged_sticky = null
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
# === DRAG LIFECYCLE OVERRIDES ===
|
|
||||||
|
|
||||||
## Cards always drop back to board dropzone
|
|
||||||
func find_drop_target() -> Node:
|
|
||||||
return owner if owner is CardBoard else get_parent()
|
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,9 @@ anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
offset_left = -126.0
|
offset_left = -126.0
|
||||||
offset_top = -100.0
|
offset_top = -88.0
|
||||||
offset_right = 136.0
|
offset_right = 136.0
|
||||||
offset_bottom = 77.95834
|
offset_bottom = 89.95834
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
theme = ExtResource("3_mdi7r")
|
theme = ExtResource("3_mdi7r")
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,6 @@ extends Area2D
|
||||||
## Base class for draggable UI elements (Cards and StickyNotes)
|
## Base class for draggable UI elements (Cards and StickyNotes)
|
||||||
## Provides common dragging behavior and boundary protection
|
## Provides common dragging behavior and boundary protection
|
||||||
|
|
||||||
## Drop result codes for DropTarget pattern
|
|
||||||
enum DropResult {
|
|
||||||
ACCEPTED, # Drop successful, item is now owned by target
|
|
||||||
REJECTED, # Drop refused, item stays with previous owner
|
|
||||||
EXCHANGED # Swap occurred, exchanged item needs handling
|
|
||||||
}
|
|
||||||
|
|
||||||
## Static helper to check if a node implements DropTarget pattern
|
|
||||||
## DropTarget pattern requires: can_accept_drop(draggable) and handle_drop(draggable)
|
|
||||||
static func is_drop_target(node: Node) -> bool:
|
|
||||||
return node != null and node.has_method("can_accept_drop") and node.has_method("handle_drop")
|
|
||||||
|
|
||||||
var is_dragged: bool = false:
|
var is_dragged: bool = false:
|
||||||
set(dragged):
|
set(dragged):
|
||||||
is_dragged = dragged
|
is_dragged = dragged
|
||||||
|
|
@ -24,38 +12,6 @@ var is_dragged: bool = false:
|
||||||
## Margin from screen edges when confining to screen bounds
|
## Margin from screen edges when confining to screen bounds
|
||||||
@export var screen_margin: float = 50.0
|
@export var screen_margin: float = 50.0
|
||||||
|
|
||||||
## Drag state tracking
|
|
||||||
var _drag_start_position: Vector2
|
|
||||||
var _mouse_drag_offset: Vector2
|
|
||||||
var _drag_source: Node = null # Where the drag started from
|
|
||||||
|
|
||||||
## === DRAG LIFECYCLE METHODS ===
|
|
||||||
## Override these in Card and StickyNote for specific behavior
|
|
||||||
|
|
||||||
## Starts a drag operation
|
|
||||||
func start_drag(mouse_offset: Vector2) -> void:
|
|
||||||
_drag_start_position = global_position
|
|
||||||
_mouse_drag_offset = mouse_offset
|
|
||||||
_drag_source = get_parent()
|
|
||||||
is_dragged = true
|
|
||||||
|
|
||||||
## Updates position during drag (call from _process or manual update)
|
|
||||||
func update_drag_position(mouse_pos: Vector2) -> void:
|
|
||||||
global_position = mouse_pos - _mouse_drag_offset
|
|
||||||
confine_to_screen()
|
|
||||||
|
|
||||||
## Finds the best drop target for this draggable
|
|
||||||
## Override in subclasses for specific drop target logic
|
|
||||||
## Returns the node that should receive the drop, or null for no valid target
|
|
||||||
func find_drop_target() -> Node:
|
|
||||||
# Base implementation: return parent (board)
|
|
||||||
return get_parent()
|
|
||||||
|
|
||||||
## Called after drop to clean up drag state
|
|
||||||
func end_drag() -> void:
|
|
||||||
is_dragged = false
|
|
||||||
_drag_source = null
|
|
||||||
|
|
||||||
## Confines this draggable element to stay within screen or container bounds
|
## Confines this draggable element to stay within screen or container bounds
|
||||||
## Skip this check if a sticky note is attached to a card
|
## Skip this check if a sticky note is attached to a card
|
||||||
func confine_to_screen() -> void:
|
func confine_to_screen() -> void:
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,6 @@ _data = {
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="board" type="PanelContainer"]
|
[node name="board" type="PanelContainer"]
|
||||||
z_index = -100
|
|
||||||
material = SubResource("ShaderMaterial_ttqei")
|
material = SubResource("ShaderMaterial_ttqei")
|
||||||
clip_contents = true
|
clip_contents = true
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,8 @@ func _on_input_event(_viewport, event, _shape_idx):
|
||||||
|
|
||||||
func _move_sticky_note():
|
func _move_sticky_note():
|
||||||
if is_dragged:
|
if is_dragged:
|
||||||
update_drag_position(get_viewport().get_mouse_position())
|
global_position = get_viewport().get_mouse_position() - mouse_offset
|
||||||
|
confine_to_screen()
|
||||||
|
|
||||||
func is_sticky_note_attached() -> bool:
|
func is_sticky_note_attached() -> bool:
|
||||||
# FIXME: this breaks if attatched to is previousely freed because GODOT IS FUCKING STUPID
|
# FIXME: this breaks if attatched to is previousely freed because GODOT IS FUCKING STUPID
|
||||||
|
|
@ -154,69 +155,3 @@ func tween_transform_to(target: Transform2D):
|
||||||
await transform_tween.finished
|
await transform_tween.finished
|
||||||
transform_tween_finished.emit()
|
transform_tween_finished.emit()
|
||||||
|
|
||||||
|
|
||||||
# === DRAG LIFECYCLE OVERRIDES ===
|
|
||||||
|
|
||||||
## Track whether this sticky came from a panel (for exchange logic)
|
|
||||||
var _came_from_panel: bool = false
|
|
||||||
|
|
||||||
## Start drag: if in panel, immediately move to board
|
|
||||||
func start_drag(offset: Vector2) -> void:
|
|
||||||
super.start_drag(offset)
|
|
||||||
_came_from_panel = is_sticky_note_in_panel()
|
|
||||||
|
|
||||||
# If attached to a card, detach it first
|
|
||||||
if is_sticky_note_attached():
|
|
||||||
var card := attached_to as Card
|
|
||||||
if card and card.has_method("remove_sticky_note"):
|
|
||||||
card.remove_sticky_note()
|
|
||||||
|
|
||||||
# If in panel, immediately reparent to board for dragging
|
|
||||||
if _came_from_panel and current_handle:
|
|
||||||
var board := current_handle
|
|
||||||
var dropzone := board.get_node_or_null("HBoxContainer/dropzone")
|
|
||||||
if dropzone:
|
|
||||||
reparent(dropzone)
|
|
||||||
else:
|
|
||||||
reparent(board)
|
|
||||||
on_board = true
|
|
||||||
attached_to = board
|
|
||||||
|
|
||||||
## Find best drop target: Card > Panel > Board (in priority order)
|
|
||||||
func find_drop_target() -> Node:
|
|
||||||
# Priority 1: Check for overlapping cards in dropzone
|
|
||||||
for area in get_overlapping_areas():
|
|
||||||
if area is Card and Draggable.is_drop_target(area):
|
|
||||||
return area
|
|
||||||
|
|
||||||
# Priority 2: Check if dropped outside dropzone (over panel area)
|
|
||||||
if current_handle and not current_handle.is_in_dropzone(self):
|
|
||||||
var target_panel := _find_nearest_panel()
|
|
||||||
if target_panel:
|
|
||||||
return target_panel
|
|
||||||
|
|
||||||
# Priority 3: Default to board (stay loose in dropzone)
|
|
||||||
return current_handle
|
|
||||||
|
|
||||||
## Find the nearest panel that can accept this sticky
|
|
||||||
func _find_nearest_panel() -> StickyNotePanel:
|
|
||||||
if not current_handle or not current_handle.has_node("HBoxContainer/ScrollContainer/VBoxContainer"):
|
|
||||||
return null
|
|
||||||
|
|
||||||
var panel_container := current_handle.get_node("HBoxContainer/ScrollContainer/VBoxContainer")
|
|
||||||
var sticky_rect := Rect2(global_position - Vector2(diameter/2, 10), Vector2(diameter/2, 10))
|
|
||||||
|
|
||||||
# First pass: look for empty panels we're hovering over
|
|
||||||
for panel in panel_container.get_children():
|
|
||||||
if panel is StickyNotePanel:
|
|
||||||
if panel.is_empty() and panel.get_global_rect().intersects(sticky_rect):
|
|
||||||
return panel
|
|
||||||
|
|
||||||
# Second pass: if no empty panel found, find first empty panel
|
|
||||||
for panel in panel_container.get_children():
|
|
||||||
if panel is StickyNotePanel and panel.is_empty():
|
|
||||||
return panel
|
|
||||||
|
|
||||||
# No empty panels found - will need to create one (handled by board)
|
|
||||||
return null
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ radius = 48.0
|
||||||
height = 312.0
|
height = 312.0
|
||||||
|
|
||||||
[node name="sticky-note" type="Area2D"]
|
[node name="sticky-note" type="Area2D"]
|
||||||
z_index = 1
|
|
||||||
priority = 100
|
priority = 100
|
||||||
script = ExtResource("1_yvh5n")
|
script = ExtResource("1_yvh5n")
|
||||||
text = "card"
|
text = "card"
|
||||||
|
|
|
||||||
|
|
@ -100,25 +100,3 @@ func replace_sticky_note_with(new_sticky_note: StickyNote):
|
||||||
|
|
||||||
func is_empty() -> bool:
|
func is_empty() -> bool:
|
||||||
return get_child_count() == 0 and not is_attatching
|
return get_child_count() == 0 and not is_attatching
|
||||||
|
|
||||||
|
|
||||||
# === DROP TARGET PATTERN IMPLEMENTATION ===
|
|
||||||
|
|
||||||
## Checks if this panel can accept the given draggable
|
|
||||||
func can_accept_drop(draggable: Draggable) -> bool:
|
|
||||||
return draggable is StickyNote and is_empty()
|
|
||||||
|
|
||||||
## Handles dropping a sticky note onto this panel
|
|
||||||
func handle_drop(draggable: StickyNote) -> int:
|
|
||||||
if not can_accept_drop(draggable):
|
|
||||||
return Draggable.DropResult.REJECTED
|
|
||||||
|
|
||||||
# Attach sticky to this panel
|
|
||||||
attatch_sticky_note(draggable, owner, true, true)
|
|
||||||
|
|
||||||
# Clean up other empty panels
|
|
||||||
for panel in get_parent().get_children():
|
|
||||||
if panel is StickyNotePanel and panel != self:
|
|
||||||
panel.clear_if_empty()
|
|
||||||
|
|
||||||
return Draggable.DropResult.ACCEPTED
|
|
||||||
|
|
|
||||||
|
|
@ -22,31 +22,25 @@ func _apply_enabled_state() -> void:
|
||||||
jitter_tween.tween_property(self, "jitter_strength", 1.0, 1.0)
|
jitter_tween.tween_property(self, "jitter_strength", 1.0, 1.0)
|
||||||
if has_entered:
|
if has_entered:
|
||||||
ui_exited.emit()
|
ui_exited.emit()
|
||||||
# Show hand cursor when player is enabled
|
|
||||||
if hand_cursor:
|
|
||||||
hand_cursor.visible = true
|
|
||||||
else:
|
else:
|
||||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||||
jitter_tween = create_tween()
|
jitter_tween = create_tween()
|
||||||
jitter_tween.tween_property(self, "jitter_strength", 0.0, 0.5)
|
jitter_tween.tween_property(self, "jitter_strength", 0.0, 0.5)
|
||||||
if has_entered:
|
if has_entered:
|
||||||
ui_exited.emit()
|
ui_exited.emit()
|
||||||
# Hide hand cursor when player is disabled
|
|
||||||
if hand_cursor:
|
|
||||||
hand_cursor.visible = false
|
|
||||||
sleeping = not enabled
|
sleeping = not enabled
|
||||||
|
|
||||||
@export var mouse_sensitivity: Vector2 = Vector2(6, 5)
|
@export var mouse_sensitivity: Vector2 = Vector2(6, 5)
|
||||||
|
|
||||||
@export var initial_pitch: float = 50
|
@export var initial_pitch: float = 50
|
||||||
|
|
||||||
@export_range (0.0, 10.0) var max_speed: float = 3
|
@export_range (0, 10) var max_speed: float = 3
|
||||||
@export_range (0.0, 10.0) var max_acceleration: float = 5
|
@export_range (0, 10) var max_acceleration: float = 5
|
||||||
@export_range (0.0, 20.0) var damp: float = 10
|
@export_range (0, 20) var damp: float = 10
|
||||||
@export_range (0.1, 1.0) var mouse_jerk:float = 0.5
|
@export_range (0.1, 1) var mouse_jerk:float = 0.5
|
||||||
@export_range (10.0, 100.0) var gamepad_response:float = 50.0
|
@export_range (10, 100) var gamepad_response:float = 50.0
|
||||||
@export_range (50.0, 500.0) var mouse_jerk_rejection:float = 200.0
|
@export_range (50, 500) var mouse_jerk_rejection:float = 200.0
|
||||||
@export var max_angle:float = 75.0
|
@export var max_angle:float = 75
|
||||||
|
|
||||||
@export var camera_jitter_speed:float = 3
|
@export var camera_jitter_speed:float = 3
|
||||||
@export var angular_jitter:Vector3 = Vector3(0.1, 0, 0.05)
|
@export var angular_jitter:Vector3 = Vector3(0.1, 0, 0.05)
|
||||||
|
|
@ -93,11 +87,6 @@ var crouched:bool = false:
|
||||||
@onready var camera: Camera3D = $Yaw/Pitch/Mount/Camera3D
|
@onready var camera: Camera3D = $Yaw/Pitch/Mount/Camera3D
|
||||||
@onready var focus_ray: RayCast3D = $Yaw/Pitch/Mount/Camera3D/RayCast3D
|
@onready var focus_ray: RayCast3D = $Yaw/Pitch/Mount/Camera3D/RayCast3D
|
||||||
@onready var ui_prober: Area3D = $Yaw/Pitch/Mount/Camera3D/UiProber
|
@onready var ui_prober: Area3D = $Yaw/Pitch/Mount/Camera3D/UiProber
|
||||||
@onready var hand_cursor: TextureRect = %Cursor
|
|
||||||
|
|
||||||
# Cursor textures (preloaded for performance)
|
|
||||||
const cursor_default: Texture2D = preload("res://import/interface-elements/cursor_point.png")
|
|
||||||
const cursor_point: Texture2D = preload("res://import/interface-elements/cursor_grab.png")
|
|
||||||
|
|
||||||
@onready var base_fov := camera.fov
|
@onready var base_fov := camera.fov
|
||||||
var zoomed:bool = false:
|
var zoomed:bool = false:
|
||||||
|
|
@ -127,9 +116,6 @@ func _ready():
|
||||||
$CrouchDetector.area_entered.connect(enter_crouch)
|
$CrouchDetector.area_entered.connect(enter_crouch)
|
||||||
$CrouchDetector.area_exited.connect(exit_crouch)
|
$CrouchDetector.area_exited.connect(exit_crouch)
|
||||||
|
|
||||||
# Setup hand cursor
|
|
||||||
_setup_hand_cursor()
|
|
||||||
|
|
||||||
# Connect to central player enable signal.
|
# Connect to central player enable signal.
|
||||||
# Guard for standalone test scenes without autoloads.
|
# Guard for standalone test scenes without autoloads.
|
||||||
if get_node_or_null("/root/Scenes"):
|
if get_node_or_null("/root/Scenes"):
|
||||||
|
|
@ -142,12 +128,6 @@ func _ready():
|
||||||
func _on_player_enable(enable: bool) -> void:
|
func _on_player_enable(enable: bool) -> void:
|
||||||
enabled = enable
|
enabled = enable
|
||||||
|
|
||||||
## Setup the hand cursor in the center of the screen
|
|
||||||
func _setup_hand_cursor() -> void:
|
|
||||||
# Configure the existing TextureRect for cursor display
|
|
||||||
hand_cursor.texture = cursor_default # Start with default cursor
|
|
||||||
hand_cursor.visible = false
|
|
||||||
|
|
||||||
## Restores player position and camera rotation from save game
|
## Restores player position and camera rotation from save game
|
||||||
func restore_from_save(save: SaveGame) -> void:
|
func restore_from_save(save: SaveGame) -> void:
|
||||||
if save.player_position != Vector3.ZERO:
|
if save.player_position != Vector3.ZERO:
|
||||||
|
|
@ -182,17 +162,11 @@ func _on_ray_entered(_area):
|
||||||
assert(parent != null, "Ray entered non-interactable area!")
|
assert(parent != null, "Ray entered non-interactable area!")
|
||||||
printt("ray entered", parent.name, parent)
|
printt("ray entered", parent.name, parent)
|
||||||
parent.hover = true
|
parent.hover = true
|
||||||
# Switch to pointing hand cursor when hovering over interactable
|
|
||||||
if hand_cursor:
|
|
||||||
hand_cursor.texture = cursor_point
|
|
||||||
|
|
||||||
func _on_ray_exited(_area):
|
func _on_ray_exited(_area):
|
||||||
var parent := _area.get_parent() as Interactable
|
var parent := _area.get_parent() as Interactable
|
||||||
printt("ray exited", parent.name, parent)
|
printt("ray exited", parent.name, parent)
|
||||||
parent.hover = false
|
parent.hover = false
|
||||||
# Switch back to default cursor when not hovering
|
|
||||||
if hand_cursor:
|
|
||||||
hand_cursor.texture = cursor_default
|
|
||||||
|
|
||||||
|
|
||||||
func _physics_process(delta: float):
|
func _physics_process(delta: float):
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
[gd_scene load_steps=17 format=3 uid="uid://mkccbig41bqb"]
|
[gd_scene load_steps=16 format=3 uid="uid://mkccbig41bqb"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://bk618uyhghswx" path="res://logic-scenes/player_controller/player_controller.gd" id="1_0b4mi"]
|
[ext_resource type="Script" uid="uid://bk618uyhghswx" path="res://logic-scenes/player_controller/player_controller.gd" id="1_0b4mi"]
|
||||||
[ext_resource type="Texture2D" uid="uid://d005qvnbnishb" path="res://import/interface-elements/cursor_grab.png" id="2_x6v75"]
|
|
||||||
|
|
||||||
[sub_resource type="PhysicsMaterial" id="10"]
|
[sub_resource type="PhysicsMaterial" id="10"]
|
||||||
friction = 0.0
|
friction = 0.0
|
||||||
|
|
@ -658,23 +657,6 @@ mouse_filter = 2
|
||||||
texture = SubResource("GradientTexture2D_x6v75")
|
texture = SubResource("GradientTexture2D_x6v75")
|
||||||
expand_mode = 4
|
expand_mode = 4
|
||||||
|
|
||||||
[node name="Cursor" type="TextureRect" parent="Yaw/Pitch/Mount/Camera3D"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
anchors_preset = 8
|
|
||||||
anchor_left = 0.5
|
|
||||||
anchor_top = 0.5
|
|
||||||
anchor_right = 0.5
|
|
||||||
anchor_bottom = 0.5
|
|
||||||
offset_left = -20.0
|
|
||||||
offset_top = -20.0
|
|
||||||
offset_right = 20.0
|
|
||||||
offset_bottom = 20.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
mouse_filter = 2
|
|
||||||
texture = ExtResource("2_x6v75")
|
|
||||||
stretch_mode = 3
|
|
||||||
|
|
||||||
[node name="PlayerCollision" type="CollisionShape3D" parent="."]
|
[node name="PlayerCollision" type="CollisionShape3D" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, -1, 8.74228e-08, 0, -8.74228e-08, -1, 0, 0.6, 0)
|
transform = Transform3D(1, 0, 0, 0, -1, 8.74228e-08, 0, -8.74228e-08, -1, 0, 0.6, 0)
|
||||||
shape = SubResource("CapsuleShape3D_hpoj0")
|
shape = SubResource("CapsuleShape3D_hpoj0")
|
||||||
|
|
|
||||||
|
|
@ -58,16 +58,6 @@ func _sort_saves() -> void:
|
||||||
return a.last_saved > b.last_saved
|
return a.last_saved > b.last_saved
|
||||||
)
|
)
|
||||||
|
|
||||||
func _get_slot_number(save: SaveGame) -> int:
|
|
||||||
# Create a list sorted by file name (creation order) to determine slot numbers
|
|
||||||
var saves_by_creation := saves.duplicate()
|
|
||||||
saves_by_creation.sort_custom(func(a: SaveGame, b: SaveGame) -> int:
|
|
||||||
return a.file_name < b.file_name # Older files first (ascending)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Find this save's position in the creation-order list
|
|
||||||
return saves_by_creation.find(save) + 1
|
|
||||||
|
|
||||||
func _rebuild_buttons() -> void:
|
func _rebuild_buttons() -> void:
|
||||||
save_buttons = []
|
save_buttons = []
|
||||||
for child in list_container.get_children():
|
for child in list_container.get_children():
|
||||||
|
|
@ -78,8 +68,7 @@ func _rebuild_buttons() -> void:
|
||||||
list_container.add_child(save_box)
|
list_container.add_child(save_box)
|
||||||
|
|
||||||
for i in range(saves.size()):
|
for i in range(saves.size()):
|
||||||
var slot_number := _get_slot_number(saves[i])
|
var new_button := SaveGameDisplay.new(saves[i], i+1)
|
||||||
var new_button := SaveGameDisplay.new(saves[i], slot_number)
|
|
||||||
save_box.add_child(new_button)
|
save_box.add_child(new_button)
|
||||||
save_buttons.append(new_button)
|
save_buttons.append(new_button)
|
||||||
new_button.pressed.connect(_on_game_picked.bind(i))
|
new_button.pressed.connect(_on_game_picked.bind(i))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue