From 60fba44316a6e4f814ecde155ce2ee62e2fa0e94 Mon Sep 17 00:00:00 2001 From: Steven Wroblewski Date: Fri, 8 May 2026 22:09:56 +0200 Subject: [PATCH] feat(character): add hand slot API (attach/detach/get_held_item/is_hand_free) --- scripts/characters/character.gd | 43 +++++++++++++++++++++++++ test/unit/test_character_v2.gd | 57 +++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/scripts/characters/character.gd b/scripts/characters/character.gd index 4f53f4d..30cf39e 100644 --- a/scripts/characters/character.gd +++ b/scripts/characters/character.gd @@ -87,6 +87,49 @@ func get_outfit(layer: int) -> String: return data.outfit[layer - 1] +func attach_item(hand: String, item: Node2D) -> bool: + if hand != "left" and hand != "right": + return false + var slot: Node2D = get_node_or_null("Hand" + hand.capitalize()) as Node2D + if slot == null: + return false + if slot.get_child_count() > 0: + return false + var old_parent: Node = item.get_parent() + if old_parent != null: + old_parent.remove_child(item) + slot.add_child(item) + item.position = Vector2.ZERO + return true + + +func detach_item(hand: String) -> Node2D: + if hand != "left" and hand != "right": + return null + var slot: Node2D = get_node_or_null("Hand" + hand.capitalize()) as Node2D + if slot == null or slot.get_child_count() == 0: + return null + var item: Node2D = slot.get_child(0) as Node2D + slot.remove_child(item) + var scene_parent: Node = get_parent() + if scene_parent != null: + scene_parent.add_child(item) + return item + + +func get_held_item(hand: String) -> Node2D: + if hand != "left" and hand != "right": + return null + var slot: Node2D = get_node_or_null("Hand" + hand.capitalize()) as Node2D + if slot == null or slot.get_child_count() == 0: + return null + return slot.get_child(0) as Node2D + + +func is_hand_free(hand: String) -> bool: + return get_held_item(hand) == null + + func _update_visual_state() -> void: if data == null: return diff --git a/test/unit/test_character_v2.gd b/test/unit/test_character_v2.gd index f7002ec..af043df 100644 --- a/test/unit/test_character_v2.gd +++ b/test/unit/test_character_v2.gd @@ -112,3 +112,60 @@ func test_set_outfit_invalid_layer_zero_is_noop() -> void: func test_set_outfit_invalid_layer_four_is_noop() -> void: _char.set_outfit(4, "white_coat", null) assert_eq(_char.get_outfit(3), "") + + +func test_both_hands_free_initially() -> void: + assert_true(_char.is_hand_free("left")) + assert_true(_char.is_hand_free("right")) + + +func test_attach_item_to_left_hand() -> void: + var item: Node2D = Node2D.new() + add_child_autofree(item) + _char.attach_item("left", item) + assert_false(_char.is_hand_free("left")) + + +func test_get_held_item_returns_attached_item() -> void: + var item: Node2D = Node2D.new() + add_child_autofree(item) + _char.attach_item("right", item) + assert_eq(_char.get_held_item("right"), item) + + +func test_attach_to_occupied_hand_returns_false() -> void: + var item1: Node2D = Node2D.new() + var item2: Node2D = Node2D.new() + add_child_autofree(item1) + add_child_autofree(item2) + _char.attach_item("left", item1) + var result: bool = _char.attach_item("left", item2) + assert_false(result) + + +func test_attach_returns_true_on_success() -> void: + var item: Node2D = Node2D.new() + add_child_autofree(item) + var result: bool = _char.attach_item("right", item) + assert_true(result) + + +func test_detach_item_frees_hand() -> void: + var item: Node2D = Node2D.new() + add_child_autofree(item) + _char.attach_item("left", item) + _char.detach_item("left") + assert_true(_char.is_hand_free("left")) + + +func test_detach_returns_item() -> void: + var item: Node2D = Node2D.new() + add_child_autofree(item) + _char.attach_item("right", item) + var returned: Node2D = _char.detach_item("right") + assert_eq(returned, item) + + +func test_detach_from_empty_hand_returns_null() -> void: + var returned: Node2D = _char.detach_item("left") + assert_null(returned)