This commit is contained in:
Aspergerli
2026-03-09 19:18:47 +01:00
commit ade3d0fb01
240 changed files with 12324 additions and 0 deletions

View File

@@ -0,0 +1,291 @@
## A fan-shaped card container that arranges cards in an arc formation.
##
## Hand provides sophisticated card layout using mathematical curves to create
## natural-looking card arrangements. Cards are positioned in a fan pattern
## with configurable spread, rotation, and vertical displacement.
##
## Key Features:
## - Fan-shaped card arrangement with customizable curves
## - Smooth card reordering with optional swap-only mode
## - Dynamic drop zone sizing to match hand spread
## - Configurable card limits and hover distances
## - Mathematical positioning using Curve resources
##
## Curve Configuration:
## - hand_rotation_curve: Controls card rotation (linear -X to +X recommended)
## - hand_vertical_curve: Controls vertical offset (3-point ease 0-X-0 recommended)
##
## Usage:
## [codeblock]
## @onready var hand = $Hand
## hand.max_hand_size = 7
## hand.max_hand_spread = 600
## hand.card_face_up = true
## [/codeblock]
class_name Hand
extends CardContainer
@export_group("hand_meta_info")
## maximum number of cards that can be held.
@export var max_hand_size := CardFrameworkSettings.LAYOUT_MAX_HAND_SIZE
## maximum spread of the hand.
@export var max_hand_spread := CardFrameworkSettings.LAYOUT_MAX_HAND_SPREAD
## whether the card is face up.
@export var card_face_up := true
## distance the card hovers when interacted with.
@export var card_hover_distance := CardFrameworkSettings.PHYSICS_CARD_HOVER_DISTANCE
@export_group("hand_shape")
## rotation curve of the hand.
## This works best as a 2-point linear rise from -X to +X.
@export var hand_rotation_curve : Curve
## vertical curve of the hand.
## This works best as a 3-point ease in/out from 0 to X to 0
@export var hand_vertical_curve : Curve
@export_group("drop_zone")
## Determines whether the drop zone size follows the hand size. (requires enable drop zone true)
@export var align_drop_zone_size_with_current_hand_size := true
## If true, only swap the positions of two cards when reordering (a <-> b), otherwise shift the range (default behavior).
@export var swap_only_on_reorder := false
var vertical_partitions_from_outside = []
var vertical_partitions_from_inside = []
func _ready() -> void:
super._ready()
$"../..".dealt.connect(sort_hand)
## Returns a random selection of cards from this hand.
## @param n: Number of cards to select
## @returns: Array of randomly selected cards
func get_random_cards(n: int) -> Array:
var deck = _held_cards.duplicate()
deck.shuffle()
if n > _held_cards.size():
n = _held_cards.size()
return deck.slice(0, n)
func sort_hand():
var sort_cards = _held_cards.duplicate()
sort_cards.sort_custom(compare_cards)
#print("sorted")
for card in sort_cards:
print(card.card_info["value"])
var n = 0
for card in sort_cards:
var arr_card : Array
arr_card.append(card)
move_cards((arr_card), n)
arr_card.clear()
n += 1
func compare_cards(a,b):
var val_1 = int(a.card_info["value"])
var val_2 = int(b.card_info["value"])
var su_1 = a.card_info["suit"]
var su_2 = b.card_info["suit"]
match su_1:
"Diampnd":
val_1 -= 10
"Spade":
val_1 -= 20
"Heart":
val_1 -= 30
#print(val_1,su_1)
match su_2:
"Diamond":
val_2 -= 10
"Spade":
val_2 -= 20
"Heart":
val_2 -= 30
#print(val_2,su_2)
return val_1 < val_2
func _card_can_be_added(_cards: Array) -> bool:
var is_all_cards_contained = true
for i in range(_cards.size()):
var card = _cards[i]
if !_held_cards.has(card):
is_all_cards_contained = false
if is_all_cards_contained:
return true
var card_size = _cards.size()
return _held_cards.size() + card_size <= max_hand_size
func _update_target_z_index() -> void:
for i in range(_held_cards.size()):
var card = _held_cards[i]
card.stored_z_index = i
## Calculates target positions for all cards using mathematical curves.
## Implements sophisticated fan-shaped arrangement with rotation and vertical displacement.
func _update_target_positions() -> void:
var x_min: float
var x_max: float
var y_min: float
var y_max: float
var card_size = card_manager.card_size
var _w = card_size.x
var _h = card_size.y
vertical_partitions_from_outside.clear()
# Calculate position and rotation for each card in the fan arrangement
for i in range(_held_cards.size()):
var card = _held_cards[i]
# Calculate normalized position ratio (0.0 to 1.0) for curve sampling
var hand_ratio = 0.5 # Single card centered
if _held_cards.size() > 1:
hand_ratio = float(i) / float(_held_cards.size() - 1)
# Calculate base horizontal position with even spacing
var target_pos = global_position
@warning_ignore("integer_division")
var card_spacing = max_hand_spread / (_held_cards.size() + 1)
target_pos.x += (i + 1) * card_spacing - max_hand_spread / 2.0
# Apply vertical curve displacement for fan shape
if hand_vertical_curve:
target_pos.y -= hand_vertical_curve.sample(hand_ratio)
# Apply rotation curve for realistic card fanning
var target_rotation = 0
if hand_rotation_curve:
target_rotation = deg_to_rad(hand_rotation_curve.sample(hand_ratio))
# Calculate rotated card bounding box for drop zone partitioning
# This complex math determines the actual screen space occupied by each rotated card
var _x = target_pos.x
var _y = target_pos.y
# Calculate angles to card corners after rotation
var _t1 = atan2(_h, _w) + target_rotation # bottom-right corner
var _t2 = atan2(_h, -_w) + target_rotation # bottom-left corner
var _t3 = _t1 + PI + target_rotation # top-left corner
var _t4 = _t2 + PI + target_rotation # top-right corner
# Card center and radius for corner calculation
var _c = Vector2(_x + _w / 2, _y + _h / 2) # card center
var _r = sqrt(pow(_w / 2, 2.0) + pow(_h / 2, 2.0)) # diagonal radius
# Calculate actual corner positions after rotation
var _p1 = Vector2(_r * cos(_t1), _r * sin(_t1)) + _c # right bottom
var _p2 = Vector2(_r * cos(_t2), _r * sin(_t2)) + _c # left bottom
var _p3 = Vector2(_r * cos(_t3), _r * sin(_t3)) + _c # left top
var _p4 = Vector2(_r * cos(_t4), _r * sin(_t4)) + _c # right top
# Find bounding box of rotated card
var current_x_min = min(_p1.x, _p2.x, _p3.x, _p4.x)
var current_x_max = max(_p1.x, _p2.x, _p3.x, _p4.x)
var current_y_min = min(_p1.y, _p2.y, _p3.y, _p4.y)
var current_y_max = max(_p1.y, _p2.y, _p3.y, _p4.y)
var current_x_mid = (current_x_min + current_x_max) / 2
vertical_partitions_from_outside.append(current_x_mid)
if i == 0:
x_min = current_x_min
x_max = current_x_max
y_min = current_y_min
y_max = current_y_max
else:
x_min = minf(x_min, current_x_min)
x_max = maxf(x_max, current_x_max)
y_min = minf(y_min, current_y_min)
y_max = maxf(y_max, current_y_max)
card.move(target_pos, target_rotation)
card.show_front = card_face_up
card.can_be_interacted_with = true
# Calculate midpoints between consecutive values in vertical_partitions_from_outside
vertical_partitions_from_inside.clear()
if vertical_partitions_from_outside.size() > 1:
for j in range(vertical_partitions_from_outside.size() - 1):
var mid = (vertical_partitions_from_outside[j] + vertical_partitions_from_outside[j + 1]) / 2.0
vertical_partitions_from_inside.append(mid)
if align_drop_zone_size_with_current_hand_size:
if _held_cards.size() == 0:
drop_zone.return_sensor_size()
else:
var _size = Vector2(x_max - x_min, y_max - y_min)
var _position = Vector2(x_min, y_min) - position
drop_zone.set_sensor_size_flexibly(_size, _position)
drop_zone.set_vertical_partitions(vertical_partitions_from_outside)
func move_cards(cards: Array, index: int = -1, with_history: bool = true) -> bool:
# Handle single card reordering within same Hand container
if cards.size() == 1 and _held_cards.has(cards[0]) and index >= 0 and index < _held_cards.size():
var current_index = _held_cards.find(cards[0])
# Swap-only mode: exchange two cards directly
if swap_only_on_reorder:
swap_card(cards[0], index)
return true
# Same position optimization
if current_index == index:
# Same card, same position - ensure consistent state
update_card_ui()
_restore_mouse_interaction(cards)
return true
# Different position: use efficient internal reordering
_reorder_card_in_hand(cards[0], current_index, index, with_history)
_restore_mouse_interaction(cards)
return true
# Fall back to parent implementation for other cases
return super.move_cards(cards, index, with_history)
func swap_card(card: Card, index: int) -> void:
var current_index = _held_cards.find(card)
if current_index == index:
return
var temp = _held_cards[current_index]
_held_cards[current_index] = _held_cards[index]
_held_cards[index] = temp
update_card_ui()
## Restore mouse interaction for cards after drag & drop completion.
func _restore_mouse_interaction(cards: Array) -> void:
# Restore mouse interaction for cards after drag & drop completion.
for card in cards:
card.mouse_filter = Control.MOUSE_FILTER_STOP
## Efficiently reorder card within Hand without intermediate UI updates.
## Prevents position calculation errors during same-container moves.
func _reorder_card_in_hand(card: Card, from_index: int, to_index: int, with_history: bool) -> void:
# Efficiently reorder card within Hand without intermediate UI updates.
# Add to history if needed (before making changes)
if with_history:
card_manager._add_history(self, [card])
# Efficient array reordering without intermediate states
_held_cards.remove_at(from_index)
_held_cards.insert(to_index, card)
# Single UI update after array change
update_card_ui()
func hold_card(card: Card) -> void:
if _held_cards.has(card):
drop_zone.set_vertical_partitions(vertical_partitions_from_inside)
super.hold_card(card)