feat(stage): complete phase 20b action plans
This commit is contained in:
@@ -6,10 +6,12 @@ const STAGE_SCENE_IO := preload("res://scripts/stage/stage_scene_io.gd")
|
||||
const ACTION_REGISTRY := preload("res://scripts/stage/action_registry.gd")
|
||||
const PROP_BLOCK_SCRIPT := preload("res://scripts/props/prop_block.gd")
|
||||
const PROP_INTERACTOR_SCRIPT := preload("res://scripts/characters/stickman/stickman_prop_interactor.gd")
|
||||
const PROP_ANCHOR_DATA := preload("res://scripts/props/prop_anchor_data.gd")
|
||||
const STICKMAN_RIG_SCENE := preload("res://scenes/characters/stickman/stickman_rig.tscn")
|
||||
|
||||
var _checks: int = 0
|
||||
var _failures: int = 0
|
||||
var _pickup_test_prop: PropBlock = null
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
@@ -22,6 +24,8 @@ func _run() -> void:
|
||||
_test_set_down_serialization()
|
||||
_test_unknown_preview()
|
||||
await _test_runtime_profiles()
|
||||
await _test_set_down_owner_collision_grace()
|
||||
await _test_reserved_pickup_collision_protection()
|
||||
await _test_start_still_support_contact()
|
||||
await _test_set_down_clearance_and_release()
|
||||
await _test_set_down_preserves_pickup_orientation()
|
||||
@@ -140,6 +144,131 @@ func _test_runtime_profiles() -> void:
|
||||
await process_frame
|
||||
|
||||
|
||||
func _test_set_down_owner_collision_grace() -> void:
|
||||
var world := Node2D.new()
|
||||
root.add_child(world)
|
||||
var owner_rig := STICKMAN_RIG_SCENE.instantiate() as Node2D
|
||||
world.add_child(owner_rig)
|
||||
var other_rig := STICKMAN_RIG_SCENE.instantiate() as Node2D
|
||||
other_rig.position = Vector2(1000.0, 0.0)
|
||||
world.add_child(other_rig)
|
||||
await physics_frame
|
||||
var owner_bodies: Dictionary = owner_rig.call("get_collision_bodies")
|
||||
var torso: AnimatableBody2D = owner_bodies.get("torso") as AnimatableBody2D
|
||||
_expect(torso != null, "Set Down grace fixture has an animated torso collider")
|
||||
if torso == null:
|
||||
world.queue_free()
|
||||
await process_frame
|
||||
return
|
||||
var prop: PropBlock = PROP_BLOCK_SCRIPT.new()
|
||||
prop.polygon_points = PackedVector2Array([Vector2(-20.0, -20.0), Vector2(20.0, -20.0), Vector2(20.0, 20.0), Vector2(-20.0, 20.0)])
|
||||
prop.gravity_scale = 0.0
|
||||
world.add_child(prop)
|
||||
await physics_frame
|
||||
prop.global_position = torso.global_position
|
||||
prop.set_stage_roles(PROP_STAGE_ROLES.new_placement(true))
|
||||
prop.apply_runtime_role(PropBlock.RuntimeRole.HELD)
|
||||
prop.interaction_owner_id = owner_rig.get_instance_id()
|
||||
var interactor: Node = owner_rig.get_node(NodePath("RigComponents/PropInteractor"))
|
||||
interactor.set("_held_prop", prop)
|
||||
interactor.call("_release_held", "set_down", false, Vector2.ZERO, 0.0, true, false)
|
||||
for _frame: int in 4:
|
||||
await physics_frame
|
||||
_expect(prop.runtime_role == PropBlock.RuntimeRole.RELEASED and prop.is_actor_blocking(), "Set Down grace preserves the prop's authored released collision role")
|
||||
_expect(prop.is_owner_collision_grace_active_for(owner_rig), "Set Down release starts grace for the releasing stickman")
|
||||
_expect(not prop.is_owner_collision_grace_active_for(other_rig), "Set Down grace does not apply to a different stickman")
|
||||
var owner_exceptions: Array[PhysicsBody2D] = torso.get_collision_exceptions()
|
||||
_expect(owner_exceptions.has(prop) and prop.get_collision_exceptions().has(torso), "Set Down grace installs a symmetric exception for the releasing stickman's collider")
|
||||
var other_bodies: Dictionary = other_rig.call("get_collision_bodies")
|
||||
var other_torso: AnimatableBody2D = other_bodies.get("torso") as AnimatableBody2D
|
||||
_expect(other_torso != null and not other_torso.get_collision_exceptions().has(prop), "other stickmen keep ordinary prop collision")
|
||||
var locomotion: Node = owner_rig.call("get_locomotion") as Node
|
||||
var owner_sweep: Dictionary = locomotion.call("_sweep_actor_blocking_props", Vector2(40.0, 0.0))
|
||||
_expect(float(owner_sweep.get("fraction", 0.0)) >= 0.999, "the releasing stickman's movement sweep skips its grace prop")
|
||||
owner_rig.global_position += Vector2(1000.0, 0.0)
|
||||
await physics_frame
|
||||
_expect(prop.is_owner_collision_grace_active_for(owner_rig), "Set Down grace waits for a second clear physics frame")
|
||||
await physics_frame
|
||||
_expect(not prop.is_owner_collision_grace_active_for(owner_rig), "Set Down grace ends after the releasing stickman's collision shapes clear the prop")
|
||||
_expect(not torso.get_collision_exceptions().has(prop) and not prop.get_collision_exceptions().has(torso), "clearance removes both sides of the temporary collision exception")
|
||||
prop.apply_runtime_role(PropBlock.RuntimeRole.HELD)
|
||||
interactor.set("_held_prop", prop)
|
||||
interactor.call("_release_held", "manual", false, Vector2.ZERO, 0.0, true, false)
|
||||
_expect(not prop.is_owner_collision_grace_active_for(owner_rig), "Drop does not receive Set Down collision grace")
|
||||
world.queue_free()
|
||||
await process_frame
|
||||
|
||||
|
||||
func _test_reserved_pickup_collision_protection() -> void:
|
||||
var world := Node2D.new()
|
||||
root.add_child(world)
|
||||
var rig := STICKMAN_RIG_SCENE.instantiate() as Node2D
|
||||
world.add_child(rig)
|
||||
await physics_frame
|
||||
var collision_bodies: Dictionary = rig.call("get_collision_bodies")
|
||||
var torso: AnimatableBody2D = collision_bodies.get("torso") as AnimatableBody2D
|
||||
_expect(torso != null, "pickup protection fixture has an animated torso collider")
|
||||
if torso == null:
|
||||
world.queue_free()
|
||||
await process_frame
|
||||
return
|
||||
var prop: PropBlock = PROP_BLOCK_SCRIPT.new()
|
||||
prop.stage_object_id = "pickup-protection-test-prop"
|
||||
prop.polygon_points = PackedVector2Array([Vector2(-24.0, -20.0), Vector2(24.0, -20.0), Vector2(24.0, 20.0), Vector2(-24.0, 20.0)])
|
||||
prop.gravity_scale = 0.0
|
||||
world.add_child(prop)
|
||||
await physics_frame
|
||||
prop.global_position = torso.global_position + Vector2(800.0, 0.0)
|
||||
prop.set_interaction_anchors(PROP_ANCHOR_DATA.preset_layout("one_handed", Rect2(Vector2(-24.0, -20.0), Vector2(48.0, 40.0))))
|
||||
var roles: Dictionary = PROP_STAGE_ROLES.new_placement(true)
|
||||
roles["blocks_stickmen"] = true
|
||||
roles["released_blocks_stickmen"] = true
|
||||
prop.set_stage_roles(roles)
|
||||
prop.apply_runtime_role(PropBlock.RuntimeRole.RELEASED)
|
||||
_pickup_test_prop = prop
|
||||
var interactor: Node = rig.get_node(NodePath("RigComponents/PropInteractor"))
|
||||
interactor.set("_resolver", Callable(self, "_resolve_pickup_test_prop"))
|
||||
var action := {"type": "pick_up_prop", "target_prop_id": prop.stage_object_id, "hand": "auto"}
|
||||
var started: bool = bool(interactor.call("start_action", action))
|
||||
_expect(started and int(interactor.get("_phase")) == PROP_INTERACTOR_SCRIPT.Phase.APPROACHING, "pickup reserves a distant prop and enters the approach phase")
|
||||
_expect(prop.interaction_reserved_by == rig.get_instance_id() and prop.is_owner_collision_grace_active_for(rig), "pickup reservation starts protection for its target")
|
||||
for _frame: int in 5:
|
||||
await physics_frame
|
||||
_expect(prop.is_owner_collision_grace_active_for(rig), "reserved pickup protection stays active while the stickman is far away")
|
||||
var locomotion: Node = rig.call("get_locomotion") as Node
|
||||
var approach_sweep: Dictionary = locomotion.call("_sweep_actor_blocking_props", Vector2(1000.0, 0.0))
|
||||
_expect(float(approach_sweep.get("fraction", 0.0)) >= 0.999, "the owner's approach sweep skips its reserved pickup target")
|
||||
var hand_marker: Marker2D = interactor.call("_hand_marker") as Marker2D
|
||||
interactor.call("_commit_pickup", hand_marker)
|
||||
_expect(prop.runtime_role == PropBlock.RuntimeRole.HELD and not prop.is_owner_collision_grace_active_for(rig), "successful pickup clears target protection through the held profile")
|
||||
interactor.call("reset_for_scene_exit")
|
||||
await physics_frame
|
||||
var torso_local: Vector2 = rig.to_local(torso.global_position)
|
||||
locomotion.call("cancel_walk")
|
||||
rig.global_position = prop.global_position - torso_local
|
||||
await physics_frame
|
||||
var retry_started: bool = bool(interactor.call("start_action", action))
|
||||
_expect(retry_started and prop.is_owner_collision_grace_active_for(rig), "a retry protects its target before approach begins")
|
||||
locomotion.call("cancel_walk")
|
||||
interactor.call("cancel_action")
|
||||
_expect(prop.interaction_reserved_by == 0 and prop.is_owner_collision_grace_active_for(rig), "cancel releases the reservation but keeps exit protection")
|
||||
for _frame: int in 4:
|
||||
await physics_frame
|
||||
_expect(prop.is_owner_collision_grace_active_for(rig), "cancel protection remains while the stickman still overlaps the target")
|
||||
rig.global_position += Vector2(1000.0, 0.0)
|
||||
await physics_frame
|
||||
_expect(prop.is_owner_collision_grace_active_for(rig), "cancel protection waits for a second clear frame")
|
||||
await physics_frame
|
||||
_expect(not prop.is_owner_collision_grace_active_for(rig), "cancel protection clears after the stickman leaves the target")
|
||||
_pickup_test_prop = null
|
||||
world.queue_free()
|
||||
await process_frame
|
||||
|
||||
|
||||
func _resolve_pickup_test_prop(prop_id: String) -> PropBlock:
|
||||
return _pickup_test_prop if _pickup_test_prop != null and _pickup_test_prop.stage_object_id == prop_id else null
|
||||
|
||||
|
||||
func _test_start_still_support_contact() -> void:
|
||||
var world := Node2D.new()
|
||||
root.add_child(world)
|
||||
|
||||
Reference in New Issue
Block a user