feat(stage): complete phase 20b action plans
This commit is contained in:
@@ -60,6 +60,8 @@ const MAX_CONTACTS_REPORTED := 32
|
||||
const DEFAULT_WOBBLE_DURATION: float = 0.48
|
||||
const DEFAULT_WOBBLE_ANGLE_DEGREES: float = 7.0
|
||||
const DEFAULT_WOBBLE_OFFSET_LOCAL: float = 4.0
|
||||
const OWNER_COLLISION_GRACE_CLEAR_MARGIN: float = 24.0
|
||||
const OWNER_COLLISION_GRACE_CLEAR_FRAMES: int = 2
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Exported properties
|
||||
@@ -129,6 +131,15 @@ var _wobble_center: Vector2 = Vector2.ZERO
|
||||
var _wobble_baselines: Dictionary = {}
|
||||
var _last_physics_velocity: Vector2 = Vector2.ZERO
|
||||
var _has_physics_velocity_sample: bool = false
|
||||
var _owner_collision_grace_actor_id: int = 0
|
||||
var _owner_collision_grace_actor_ref: WeakRef
|
||||
var _owner_collision_grace_locomotion_ref: WeakRef
|
||||
var _owner_collision_grace_body_refs: Array[WeakRef] = []
|
||||
var _owner_collision_grace_role: RuntimeRole = RuntimeRole.EDIT
|
||||
var _owner_collision_grace_while_reserved: bool = false
|
||||
var _owner_collision_grace_clear_frames: int = 0
|
||||
var _owner_collision_grace_started_frame: int = -1
|
||||
var _owner_collision_grace_last_sample_frame: int = -1
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Signals
|
||||
@@ -178,12 +189,14 @@ func _process(delta: float) -> void:
|
||||
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
_update_owner_collision_grace()
|
||||
if not freeze and runtime_role in [RuntimeRole.START_PHYSICS, RuntimeRole.RELEASED]:
|
||||
_last_physics_velocity = linear_velocity
|
||||
_has_physics_velocity_sample = _last_physics_velocity.is_finite()
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
clear_owner_collision_grace()
|
||||
cancel_wobble()
|
||||
|
||||
|
||||
@@ -320,6 +333,8 @@ func apply_stage_depth() -> void:
|
||||
|
||||
func apply_runtime_role(role: RuntimeRole) -> void:
|
||||
runtime_role = role
|
||||
if _owner_collision_grace_actor_id != 0 and (role != _owner_collision_grace_role or not is_actor_blocking()):
|
||||
clear_owner_collision_grace()
|
||||
match role:
|
||||
RuntimeRole.EDIT:
|
||||
freeze_mode = RigidBody2D.FREEZE_MODE_KINEMATIC
|
||||
@@ -369,6 +384,146 @@ func is_actor_blocking() -> bool:
|
||||
return bool(stage_roles.get("blocks_stickmen", false))
|
||||
|
||||
|
||||
## Temporarily ignores contacts with one stickman. Pickup reservations can keep
|
||||
## protection active until the reservation ends; Set Down uses exit clearance.
|
||||
## The prop's authored role and contacts with supports or other stickmen remain.
|
||||
func begin_owner_collision_grace(actor_rig: Node2D, while_reserved: bool = false) -> void:
|
||||
clear_owner_collision_grace()
|
||||
if actor_rig == null or not is_instance_valid(actor_rig) or not is_actor_blocking():
|
||||
return
|
||||
_owner_collision_grace_actor_id = actor_rig.get_instance_id()
|
||||
_owner_collision_grace_actor_ref = weakref(actor_rig)
|
||||
_owner_collision_grace_role = runtime_role
|
||||
_owner_collision_grace_while_reserved = while_reserved
|
||||
_owner_collision_grace_clear_frames = 0
|
||||
_owner_collision_grace_started_frame = Engine.get_physics_frames()
|
||||
_owner_collision_grace_last_sample_frame = -1
|
||||
for body: PhysicsBody2D in _actor_collision_bodies(actor_rig):
|
||||
body.add_collision_exception_with(self)
|
||||
add_collision_exception_with(body)
|
||||
_owner_collision_grace_body_refs.append(weakref(body))
|
||||
if actor_rig.has_method("get_locomotion"):
|
||||
var locomotion: Node = actor_rig.call("get_locomotion") as Node
|
||||
if locomotion != null and is_instance_valid(locomotion) and locomotion.has_method("register_owner_collision_grace_prop"):
|
||||
locomotion.call("register_owner_collision_grace_prop", self)
|
||||
_owner_collision_grace_locomotion_ref = weakref(locomotion)
|
||||
|
||||
|
||||
func release_owner_collision_grace(actor_rig: Node2D) -> void:
|
||||
if not is_owner_collision_grace_active_for(actor_rig):
|
||||
return
|
||||
_owner_collision_grace_while_reserved = false
|
||||
_owner_collision_grace_clear_frames = 0
|
||||
_owner_collision_grace_started_frame = Engine.get_physics_frames()
|
||||
_owner_collision_grace_last_sample_frame = -1
|
||||
|
||||
|
||||
func is_owner_collision_grace_active_for(actor: Node) -> bool:
|
||||
return (
|
||||
actor != null
|
||||
and is_instance_valid(actor)
|
||||
and _owner_collision_grace_actor_id != 0
|
||||
and actor.get_instance_id() == _owner_collision_grace_actor_id
|
||||
)
|
||||
|
||||
|
||||
func clear_owner_collision_grace() -> void:
|
||||
for body_ref: WeakRef in _owner_collision_grace_body_refs:
|
||||
var body: PhysicsBody2D = body_ref.get_ref() as PhysicsBody2D
|
||||
if body != null and is_instance_valid(body):
|
||||
body.remove_collision_exception_with(self)
|
||||
remove_collision_exception_with(body)
|
||||
_owner_collision_grace_body_refs.clear()
|
||||
var locomotion: Node = _owner_collision_grace_locomotion_ref.get_ref() as Node if _owner_collision_grace_locomotion_ref != null else null
|
||||
if locomotion != null and is_instance_valid(locomotion) and locomotion.has_method("unregister_owner_collision_grace_prop"):
|
||||
locomotion.call("unregister_owner_collision_grace_prop", self)
|
||||
_owner_collision_grace_locomotion_ref = null
|
||||
_owner_collision_grace_actor_ref = null
|
||||
_owner_collision_grace_actor_id = 0
|
||||
_owner_collision_grace_role = RuntimeRole.EDIT
|
||||
_owner_collision_grace_while_reserved = false
|
||||
_owner_collision_grace_clear_frames = 0
|
||||
_owner_collision_grace_started_frame = -1
|
||||
_owner_collision_grace_last_sample_frame = -1
|
||||
|
||||
|
||||
func _update_owner_collision_grace() -> void:
|
||||
if _owner_collision_grace_actor_id == 0:
|
||||
return
|
||||
var actor: Node2D = _owner_collision_grace_actor_ref.get_ref() as Node2D if _owner_collision_grace_actor_ref != null else null
|
||||
if actor == null or not is_instance_valid(actor) or runtime_role != _owner_collision_grace_role or not is_actor_blocking():
|
||||
clear_owner_collision_grace()
|
||||
return
|
||||
if _owner_collision_grace_while_reserved:
|
||||
return
|
||||
var physics_frame: int = Engine.get_physics_frames()
|
||||
if physics_frame <= _owner_collision_grace_started_frame or physics_frame == _owner_collision_grace_last_sample_frame:
|
||||
return
|
||||
_owner_collision_grace_last_sample_frame = physics_frame
|
||||
if _actor_collision_shapes_overlap(actor):
|
||||
_owner_collision_grace_clear_frames = 0
|
||||
return
|
||||
_owner_collision_grace_clear_frames += 1
|
||||
if _owner_collision_grace_clear_frames >= OWNER_COLLISION_GRACE_CLEAR_FRAMES:
|
||||
clear_owner_collision_grace()
|
||||
|
||||
|
||||
func _actor_collision_shapes_overlap(actor_rig: Node2D) -> bool:
|
||||
if not is_inside_tree() or get_world_2d() == null or collision_layer == 0:
|
||||
return false
|
||||
var space_state: PhysicsDirectSpaceState2D = get_world_2d().direct_space_state
|
||||
if space_state == null:
|
||||
return true
|
||||
var active_shape_count: int = 0
|
||||
for body: PhysicsBody2D in _actor_collision_bodies(actor_rig):
|
||||
if body.collision_layer == 0:
|
||||
continue
|
||||
for child: Node in body.get_children():
|
||||
var collision_shape: CollisionShape2D = child as CollisionShape2D
|
||||
if collision_shape == null or collision_shape.disabled or collision_shape.shape == null:
|
||||
continue
|
||||
active_shape_count += 1
|
||||
var query := PhysicsShapeQueryParameters2D.new()
|
||||
query.shape = collision_shape.shape
|
||||
query.transform = collision_shape.global_transform
|
||||
query.collision_mask = collision_layer
|
||||
query.margin = OWNER_COLLISION_GRACE_CLEAR_MARGIN
|
||||
query.exclude = [body.get_rid()]
|
||||
for hit: Dictionary in space_state.intersect_shape(query, 16):
|
||||
if hit.get("collider") == self:
|
||||
return true
|
||||
return active_shape_count == 0
|
||||
|
||||
|
||||
func _actor_collision_bodies(actor_rig: Node2D) -> Array[PhysicsBody2D]:
|
||||
var result: Array[PhysicsBody2D] = []
|
||||
var seen: Dictionary = {}
|
||||
if actor_rig == null or not is_instance_valid(actor_rig):
|
||||
return result
|
||||
if actor_rig.has_method("get_collision_bodies"):
|
||||
var collision_bodies: Variant = actor_rig.call("get_collision_bodies")
|
||||
if collision_bodies is Dictionary:
|
||||
for raw_body: Variant in (collision_bodies as Dictionary).values():
|
||||
_append_actor_collision_body(raw_body, result, seen)
|
||||
if actor_rig.has_method("get_ragdoll_bodies"):
|
||||
var ragdoll_bodies: Variant = actor_rig.call("get_ragdoll_bodies")
|
||||
if ragdoll_bodies is Array:
|
||||
for raw_body: Variant in ragdoll_bodies as Array:
|
||||
_append_actor_collision_body(raw_body, result, seen)
|
||||
return result
|
||||
|
||||
|
||||
func _append_actor_collision_body(raw_body: Variant, bodies: Array[PhysicsBody2D], seen: Dictionary) -> void:
|
||||
var body: PhysicsBody2D = raw_body as PhysicsBody2D
|
||||
if body == null or not is_instance_valid(body):
|
||||
return
|
||||
var body_id: int = body.get_instance_id()
|
||||
if seen.has(body_id):
|
||||
return
|
||||
seen[body_id] = true
|
||||
bodies.append(body)
|
||||
|
||||
|
||||
func can_receive_prop_contact() -> bool:
|
||||
if runtime_role == RuntimeRole.HELD or runtime_role == RuntimeRole.EDIT:
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user