feat(snap-point): add SnapPoint node with pose, baby_only filter, and occupant tracking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
## SnapPoint — attachment position on furniture where a Character can snap into a pose.
|
||||
## Add to any furniture node. The node auto-registers in the "snap_points" group on _ready.
|
||||
class_name SnapPoint extends Node2D
|
||||
|
||||
signal character_snapped(character: Character)
|
||||
signal character_unsnapped(character: Character)
|
||||
|
||||
@export var pose: String = "sitting"
|
||||
@export var baby_only: bool = false
|
||||
|
||||
var occupant: Character = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("snap_points")
|
||||
|
||||
|
||||
func is_free() -> bool:
|
||||
return occupant == null
|
||||
|
||||
|
||||
func accepts(character: Character) -> bool:
|
||||
if not is_free():
|
||||
return false
|
||||
if not baby_only:
|
||||
return true
|
||||
if character.data == null:
|
||||
return false
|
||||
return character.data.state == CharacterData.State.BABY
|
||||
|
||||
|
||||
func snap(character: Character) -> void:
|
||||
occupant = character
|
||||
character_snapped.emit(character)
|
||||
|
||||
|
||||
func unsnap() -> void:
|
||||
if occupant == null:
|
||||
return
|
||||
var prev: Character = occupant
|
||||
occupant = null
|
||||
character_unsnapped.emit(prev)
|
||||
@@ -0,0 +1,75 @@
|
||||
## Tests for SnapPoint — attachment node on furniture.
|
||||
extends GutTest
|
||||
|
||||
const SnapPointScript: GDScript = preload("res://scripts/objects/snap_point.gd")
|
||||
|
||||
var _snap: SnapPoint
|
||||
var _char: Character
|
||||
|
||||
|
||||
func before_each() -> void:
|
||||
_snap = SnapPointScript.new()
|
||||
_snap.pose = "sitting"
|
||||
add_child_autofree(_snap)
|
||||
_char = preload("res://scenes/characters/Character.tscn").instantiate() as Character
|
||||
add_child_autofree(_char)
|
||||
|
||||
|
||||
func test_is_free_when_no_occupant() -> void:
|
||||
assert_true(_snap.is_free())
|
||||
|
||||
|
||||
func test_is_not_free_when_occupied() -> void:
|
||||
_snap.snap(_char)
|
||||
assert_false(_snap.is_free())
|
||||
|
||||
|
||||
func test_snap_sets_occupant() -> void:
|
||||
_snap.snap(_char)
|
||||
assert_eq(_snap.occupant, _char)
|
||||
|
||||
|
||||
func test_unsnap_clears_occupant() -> void:
|
||||
_snap.snap(_char)
|
||||
_snap.unsnap()
|
||||
assert_null(_snap.occupant)
|
||||
|
||||
|
||||
func test_accepts_any_character_when_baby_only_false() -> void:
|
||||
assert_true(_snap.accepts(_char))
|
||||
|
||||
|
||||
func test_does_not_accept_when_occupied() -> void:
|
||||
_snap.snap(_char)
|
||||
var other: Character = preload("res://scenes/characters/Character.tscn").instantiate() as Character
|
||||
add_child_autofree(other)
|
||||
assert_false(_snap.accepts(other))
|
||||
|
||||
|
||||
func test_baby_only_rejects_healthy_character() -> void:
|
||||
_snap.baby_only = true
|
||||
var cd: CharacterData = CharacterData.new()
|
||||
cd.state = CharacterData.State.HEALTHY
|
||||
_char.data = cd
|
||||
assert_false(_snap.accepts(_char))
|
||||
|
||||
|
||||
func test_baby_only_accepts_baby_state_character() -> void:
|
||||
_snap.baby_only = true
|
||||
var cd: CharacterData = CharacterData.new()
|
||||
cd.state = CharacterData.State.BABY
|
||||
_char.data = cd
|
||||
assert_true(_snap.accepts(_char))
|
||||
|
||||
|
||||
func test_snap_emits_character_snapped() -> void:
|
||||
watch_signals(_snap)
|
||||
_snap.snap(_char)
|
||||
assert_signal_emitted(_snap, "character_snapped")
|
||||
|
||||
|
||||
func test_unsnap_emits_character_unsnapped() -> void:
|
||||
_snap.snap(_char)
|
||||
watch_signals(_snap)
|
||||
_snap.unsnap()
|
||||
assert_signal_emitted(_snap, "character_unsnapped")
|
||||
Reference in New Issue
Block a user