|
|
|
@@ -4,14 +4,18 @@ extends Node
|
|
|
|
|
|
|
|
|
|
const ANCHOR_DATA := preload("res://scripts/props/prop_anchor_data.gd")
|
|
|
|
|
const ACTION_REGISTRY := preload("res://scripts/stage/action_registry.gd")
|
|
|
|
|
const PROP_STAGE_ROLES := preload("res://scripts/props/prop_stage_roles.gd")
|
|
|
|
|
const STAGE_PHYSICS := preload("res://scripts/stage/stage_physics_policy.gd")
|
|
|
|
|
|
|
|
|
|
enum Hand { LEFT, RIGHT }
|
|
|
|
|
enum Phase { IDLE, APPROACHING, REACHING, PICKUP_RECOVERING, HELD, THROWING }
|
|
|
|
|
enum Phase { IDLE, APPROACHING, REACHING, PICKUP_RECOVERING, HELD, THROWING, SET_DOWN_APPROACHING, SET_DOWN_REACHING, SET_DOWN_RECOVERING }
|
|
|
|
|
|
|
|
|
|
const MAX_REACH: float = 390.0
|
|
|
|
|
const APPROACH_GAP: float = 90.0
|
|
|
|
|
const APPROACH_REPLAN_LIMIT: int = 1
|
|
|
|
|
const REACH_DURATION: float = 0.34
|
|
|
|
|
const SET_DOWN_REACH_DURATION: float = 0.22
|
|
|
|
|
const SET_DOWN_RECOVER_DURATION: float = 0.24
|
|
|
|
|
const PICKUP_RECOVER_DURATION: float = 0.32
|
|
|
|
|
const LOW_PICKUP_THRESHOLD: float = 110.0
|
|
|
|
|
const LOW_PICKUP_MAX_DROP: float = 110.0
|
|
|
|
@@ -29,6 +33,7 @@ const THROW_SPIN_SPEED: float = 2.4
|
|
|
|
|
const THROW_TRAJECTORY_SEGMENTS: int = 24
|
|
|
|
|
const THROW_HEAD_CLEARANCE: float = 36.0
|
|
|
|
|
const THROW_REACH_MARGIN: float = 8.0
|
|
|
|
|
const PROP_TRACE_ENABLED: bool = true
|
|
|
|
|
|
|
|
|
|
signal action_completed
|
|
|
|
|
signal prop_event(event_type: String, prop: PropBlock, reason: String)
|
|
|
|
@@ -69,9 +74,33 @@ var _throw_torso_windup_rotation: float = 0.0
|
|
|
|
|
var _approach_replans: int = 0
|
|
|
|
|
var _marker_start_position: Vector2 = Vector2.ZERO
|
|
|
|
|
var _saved_body_state: Dictionary = {}
|
|
|
|
|
var _pickup_transform: Transform2D = Transform2D.IDENTITY
|
|
|
|
|
var _pickup_transform_valid: bool = false
|
|
|
|
|
var _last_transform: Transform2D = Transform2D.IDENTITY
|
|
|
|
|
var _sampled_linear_velocity: Vector2 = Vector2.ZERO
|
|
|
|
|
var _sampled_angular_velocity: float = 0.0
|
|
|
|
|
var _set_down_target: Vector2 = Vector2.ZERO
|
|
|
|
|
var _set_down_grip_target: Vector2 = Vector2.ZERO
|
|
|
|
|
var _set_down_approach_root: Vector2 = Vector2.ZERO
|
|
|
|
|
var _set_down_facing_left: bool = false
|
|
|
|
|
var _set_down_release_transform: Transform2D = Transform2D.IDENTITY
|
|
|
|
|
var _set_down_reach_start_transform: Transform2D = Transform2D.IDENTITY
|
|
|
|
|
var _set_down_recover_elapsed: float = 0.0
|
|
|
|
|
var _set_down_recover_hand_start: Vector2 = Vector2.ZERO
|
|
|
|
|
var _trace_released_prop: PropBlock
|
|
|
|
|
var _trace_released_elapsed: float = 0.0
|
|
|
|
|
var _trace_released_watch_remaining: float = 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
|
|
|
if not PROP_TRACE_ENABLED or _trace_released_prop == null or not is_instance_valid(_trace_released_prop) or _trace_released_watch_remaining <= 0.0:
|
|
|
|
|
return
|
|
|
|
|
_trace_released_elapsed += delta
|
|
|
|
|
_trace_released_watch_remaining -= delta
|
|
|
|
|
if _trace_released_elapsed < 0.5:
|
|
|
|
|
return
|
|
|
|
|
_trace_released_elapsed = 0.0
|
|
|
|
|
_trace_prop_state("released_watch", _trace_released_prop)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _exit_tree() -> void:
|
|
|
|
@@ -135,6 +164,8 @@ func start_action(action: Dictionary) -> bool:
|
|
|
|
|
_throw_released = false
|
|
|
|
|
_phase = Phase.THROWING
|
|
|
|
|
return true
|
|
|
|
|
"set_down_prop":
|
|
|
|
|
return _begin_set_down(action)
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -143,10 +174,24 @@ func tick(delta: float) -> void:
|
|
|
|
|
return
|
|
|
|
|
match _phase:
|
|
|
|
|
Phase.APPROACHING:
|
|
|
|
|
if _locomotion != null and bool(_locomotion.call("consume_walk_failed")):
|
|
|
|
|
_fail_pickup_approach("Too far to reach")
|
|
|
|
|
return
|
|
|
|
|
if _locomotion != null and bool(_locomotion.call("consume_walk_completed")):
|
|
|
|
|
_on_approach_arrived()
|
|
|
|
|
Phase.SET_DOWN_APPROACHING:
|
|
|
|
|
_update_held_transform(delta)
|
|
|
|
|
if _locomotion != null and bool(_locomotion.call("consume_walk_failed")):
|
|
|
|
|
_fail_set_down("Too far to reach")
|
|
|
|
|
return
|
|
|
|
|
if _locomotion != null and bool(_locomotion.call("consume_walk_completed")):
|
|
|
|
|
_on_set_down_approach_arrived()
|
|
|
|
|
Phase.REACHING:
|
|
|
|
|
_update_reach(delta)
|
|
|
|
|
Phase.SET_DOWN_REACHING:
|
|
|
|
|
_update_set_down_reach(delta)
|
|
|
|
|
Phase.SET_DOWN_RECOVERING:
|
|
|
|
|
_update_set_down_recovery(delta)
|
|
|
|
|
Phase.PICKUP_RECOVERING:
|
|
|
|
|
_update_pickup_recovery(delta)
|
|
|
|
|
Phase.HELD:
|
|
|
|
@@ -156,8 +201,14 @@ func tick(delta: float) -> void:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func cancel_action() -> void:
|
|
|
|
|
if _phase == Phase.APPROACHING and _locomotion != null:
|
|
|
|
|
if (_phase == Phase.APPROACHING or _phase == Phase.SET_DOWN_APPROACHING) and _locomotion != null:
|
|
|
|
|
_locomotion.call("cancel_walk")
|
|
|
|
|
if _phase == Phase.SET_DOWN_APPROACHING or _phase == Phase.SET_DOWN_REACHING or _phase == Phase.SET_DOWN_RECOVERING:
|
|
|
|
|
_restore_hand_target()
|
|
|
|
|
_restore_pickup_torso()
|
|
|
|
|
_phase = Phase.HELD if _held_prop != null and is_instance_valid(_held_prop) else Phase.IDLE
|
|
|
|
|
_active_action.clear()
|
|
|
|
|
return
|
|
|
|
|
if _phase == Phase.APPROACHING or _phase == Phase.REACHING:
|
|
|
|
|
_clear_reservation()
|
|
|
|
|
_restore_hand_target()
|
|
|
|
@@ -224,6 +275,33 @@ func preview_throw(target: Vector2, queue_prefix: Array[Dictionary] = []) -> Dic
|
|
|
|
|
return _clip_preview_to_collision(solution, prop)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func preview_set_down(target: Vector2, queue_prefix: Array[Dictionary] = []) -> Dictionary:
|
|
|
|
|
if not target.is_finite():
|
|
|
|
|
return {"known": false, "valid": false, "reason": "Prop depends on earlier actions"}
|
|
|
|
|
var context := _build_throw_preview_context(queue_prefix)
|
|
|
|
|
if not bool(context.get("available", false)) or bool(context.get("uncertain", false)):
|
|
|
|
|
return {
|
|
|
|
|
"known": false,
|
|
|
|
|
"valid": false,
|
|
|
|
|
"reason": "Prop depends on earlier actions",
|
|
|
|
|
"target_position": target,
|
|
|
|
|
}
|
|
|
|
|
var prop: PropBlock = context["prop"]
|
|
|
|
|
var result := _solve_set_down_target(
|
|
|
|
|
target,
|
|
|
|
|
prop,
|
|
|
|
|
context.get("anchor", {}),
|
|
|
|
|
int(context.get("hand", Hand.RIGHT)),
|
|
|
|
|
context.get("root_position", _rig.global_position),
|
|
|
|
|
context.get("root_offset", Vector2.ZERO),
|
|
|
|
|
bool(context.get("facing_left", _is_facing_left())),
|
|
|
|
|
true
|
|
|
|
|
)
|
|
|
|
|
result["known"] = true
|
|
|
|
|
result["prop"] = prop
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _build_throw_preview_context(queue_prefix: Array[Dictionary]) -> Dictionary:
|
|
|
|
|
if _rig == null or not is_instance_valid(_rig):
|
|
|
|
|
return {"available": false, "reason": "The acting stickman is unavailable."}
|
|
|
|
@@ -297,7 +375,7 @@ func _build_throw_preview_context(queue_prefix: Array[Dictionary]) -> Dictionary
|
|
|
|
|
has_prop = true
|
|
|
|
|
planned = true
|
|
|
|
|
plan_step = int(entry.get("step", 0))
|
|
|
|
|
"drop_prop", "throw_prop":
|
|
|
|
|
"drop_prop", "throw_prop", "set_down_prop":
|
|
|
|
|
if has_prop:
|
|
|
|
|
prop = null
|
|
|
|
|
anchor.clear()
|
|
|
|
@@ -315,6 +393,7 @@ func _build_throw_preview_context(queue_prefix: Array[Dictionary]) -> Dictionary
|
|
|
|
|
"hand": hand_value,
|
|
|
|
|
"root_position": root_position,
|
|
|
|
|
"root_offset": root_offset,
|
|
|
|
|
"facing_left": predicted_facing_left,
|
|
|
|
|
"planned": planned,
|
|
|
|
|
"uncertain": uncertain,
|
|
|
|
|
"plan_step": plan_step,
|
|
|
|
@@ -479,19 +558,22 @@ func _invalid_throw_preview(effective_target: Vector2, start_position: Vector2)
|
|
|
|
|
|
|
|
|
|
func disarm_for_ragdoll() -> void:
|
|
|
|
|
var interrupted_pickup := _phase == Phase.APPROACHING or _phase == Phase.REACHING or _phase == Phase.PICKUP_RECOVERING
|
|
|
|
|
var interrupted_set_down := _phase == Phase.SET_DOWN_APPROACHING or _phase == Phase.SET_DOWN_REACHING or _phase == Phase.SET_DOWN_RECOVERING
|
|
|
|
|
var interrupted_windup := _phase == Phase.THROWING and not _throw_released
|
|
|
|
|
if _phase == Phase.APPROACHING or _phase == Phase.REACHING or _phase == Phase.PICKUP_RECOVERING:
|
|
|
|
|
if interrupted_pickup or interrupted_set_down:
|
|
|
|
|
cancel_action()
|
|
|
|
|
elif interrupted_windup:
|
|
|
|
|
cancel_action()
|
|
|
|
|
if _held_prop != null and is_instance_valid(_held_prop):
|
|
|
|
|
_release_held("ragdoll", false)
|
|
|
|
|
if interrupted_pickup or interrupted_windup:
|
|
|
|
|
if interrupted_pickup or interrupted_set_down or interrupted_windup:
|
|
|
|
|
action_completed.emit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func reset_for_scene_exit() -> void:
|
|
|
|
|
if _phase == Phase.APPROACHING or _phase == Phase.REACHING or _phase == Phase.PICKUP_RECOVERING or (_phase == Phase.THROWING and not _throw_released):
|
|
|
|
|
if PROP_TRACE_ENABLED:
|
|
|
|
|
print("[PropTrace] reset_for_scene_exit actor=%s phase=%s held=%s" % [_trace_actor_name(), Phase.keys()[int(_phase)], _trace_prop_id(_held_prop)])
|
|
|
|
|
if _phase == Phase.APPROACHING or _phase == Phase.REACHING or _phase == Phase.PICKUP_RECOVERING or _phase == Phase.SET_DOWN_APPROACHING or _phase == Phase.SET_DOWN_REACHING or _phase == Phase.SET_DOWN_RECOVERING or (_phase == Phase.THROWING and not _throw_released):
|
|
|
|
|
cancel_action()
|
|
|
|
|
if _held_prop != null and is_instance_valid(_held_prop):
|
|
|
|
|
_release_held("scene_reset", false)
|
|
|
|
@@ -501,6 +583,8 @@ func reset_for_scene_exit() -> void:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _begin_pickup(prop_id: String, requested_hand: String, action: Dictionary) -> bool:
|
|
|
|
|
if PROP_TRACE_ENABLED:
|
|
|
|
|
print("[PropTrace] pickup_begin actor=%s target_id=%s hand=%s phase=%s" % [_trace_actor_name(), prop_id, requested_hand, Phase.keys()[int(_phase)]])
|
|
|
|
|
if _phase != Phase.IDLE or _held_prop != null or _rig == null:
|
|
|
|
|
return false
|
|
|
|
|
if int(_rig.get("state")) != 0:
|
|
|
|
@@ -511,8 +595,11 @@ func _begin_pickup(prop_id: String, requested_hand: String, action: Dictionary)
|
|
|
|
|
if not resolved is PropBlock:
|
|
|
|
|
return false
|
|
|
|
|
var prop := resolved as PropBlock
|
|
|
|
|
_trace_prop_state("pickup_resolved", prop)
|
|
|
|
|
if not is_instance_valid(prop) or prop.is_queued_for_deletion():
|
|
|
|
|
return false
|
|
|
|
|
if not prop.can_be_picked_up():
|
|
|
|
|
return false
|
|
|
|
|
if prop.interaction_owner_id != 0 or prop.interaction_reserved_by != 0:
|
|
|
|
|
return false
|
|
|
|
|
var picked := _choose_grip_and_hand(prop, requested_hand)
|
|
|
|
@@ -567,6 +654,506 @@ func _choose_grip_and_hand(prop: PropBlock, requested_hand: String) -> Dictionar
|
|
|
|
|
return candidates[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _begin_set_down(action: Dictionary) -> bool:
|
|
|
|
|
if _held_prop == null or not is_instance_valid(_held_prop):
|
|
|
|
|
prop_event.emit("set_down_failed", null, "No prop is held")
|
|
|
|
|
return false
|
|
|
|
|
var raw_target: Variant = action.get("target_position", Vector2.INF)
|
|
|
|
|
if not raw_target is Vector2 or not (raw_target as Vector2).is_finite():
|
|
|
|
|
prop_event.emit("set_down_failed", _held_prop, "Choose a release place")
|
|
|
|
|
return false
|
|
|
|
|
if _phase != Phase.HELD and _phase != Phase.IDLE:
|
|
|
|
|
return false
|
|
|
|
|
var target := raw_target as Vector2
|
|
|
|
|
var preview := _solve_set_down_target(target, _held_prop, _held_anchor, int(_hand), _rig.global_position, Vector2.ZERO, _is_facing_left(), true)
|
|
|
|
|
if PROP_TRACE_ENABLED:
|
|
|
|
|
var resolved_transform: Transform2D = preview.get("release_transform", Transform2D.IDENTITY)
|
|
|
|
|
print("[PropTrace] set_down_begin actor=%s prop=%s requested=%s valid=%s reason=%s resolved=%s approach=%s grip=%s" % [_trace_actor_name(), _trace_prop_id(_held_prop), str(target), str(preview.get("valid", false)), String(preview.get("reason", "")), str(resolved_transform.origin), str(preview.get("approach_position", Vector2.INF)), str(preview.get("grip_position", Vector2.INF))])
|
|
|
|
|
if not bool(preview.get("valid", false)):
|
|
|
|
|
prop_event.emit("set_down_failed", _held_prop, String(preview.get("reason", "Too far to reach")))
|
|
|
|
|
return false
|
|
|
|
|
_set_down_target = target
|
|
|
|
|
_set_down_grip_target = preview["grip_position"]
|
|
|
|
|
_set_down_approach_root = preview["approach_position"]
|
|
|
|
|
_set_down_facing_left = bool(preview["facing_left"])
|
|
|
|
|
_set_down_release_transform = preview["release_transform"]
|
|
|
|
|
_active_action = action.duplicate(true)
|
|
|
|
|
if absf(_set_down_approach_root.x - _rig.global_position.x) > 8.0:
|
|
|
|
|
if _set_down_facing_left != _is_facing_left():
|
|
|
|
|
_rig.call("set_facing_profile", 0 if _set_down_facing_left else 1)
|
|
|
|
|
var foot_offset: Vector2 = _locomotion.call("get_foot_offset")
|
|
|
|
|
var target_feet := _set_down_approach_root - foot_offset
|
|
|
|
|
if not bool(_locomotion.call("move_to", target_feet, ACTION_REGISTRY.RUN)):
|
|
|
|
|
_active_action.clear()
|
|
|
|
|
prop_event.emit("set_down_failed", _held_prop, "Too far to reach")
|
|
|
|
|
return false
|
|
|
|
|
_phase = Phase.SET_DOWN_APPROACHING
|
|
|
|
|
_trace_prop_state("set_down_approaching", _held_prop)
|
|
|
|
|
return true
|
|
|
|
|
_begin_set_down_reach()
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _solve_set_down_target(target: Vector2, prop: PropBlock, anchor: Dictionary, hand_value: int, actor_root: Vector2, root_offset: Vector2, facing_left: bool, check_occupancy: bool) -> Dictionary:
|
|
|
|
|
if not target.is_finite() or prop == null or not is_instance_valid(prop):
|
|
|
|
|
return {"valid": false, "reason": "Too far to reach"}
|
|
|
|
|
var release_transform := _set_down_transform_for_target(prop, target)
|
|
|
|
|
var clearance_lift := 0.0
|
|
|
|
|
if check_occupancy:
|
|
|
|
|
var resolved := _resolve_set_down_release_transform(prop, target)
|
|
|
|
|
if not bool(resolved.get("valid", false)):
|
|
|
|
|
return {"valid": false, "reason": String(resolved.get("reason", "No room to let go here")), "release_transform": release_transform}
|
|
|
|
|
release_transform = resolved["release_transform"]
|
|
|
|
|
clearance_lift = float(resolved.get("clearance_lift", 0.0))
|
|
|
|
|
var anchor_position: Vector2 = (anchor.get("position", Vector2.ZERO) as Vector2) * prop.get_interaction_scale()
|
|
|
|
|
var prop_geometry := _set_down_collision_geometry(prop)
|
|
|
|
|
var prop_shape: Shape2D = prop_geometry.get("shape") as Shape2D
|
|
|
|
|
var prop_bounds := _set_down_shape_bounds(prop_shape, release_transform * (prop_geometry.get("local_transform", Transform2D.IDENTITY) as Transform2D)) if prop_shape != null else Rect2(target, Vector2.ZERO)
|
|
|
|
|
var candidates: Array[Dictionary] = []
|
|
|
|
|
var current_grip_local := anchor_position
|
|
|
|
|
if facing_left:
|
|
|
|
|
current_grip_local.x = -current_grip_local.x
|
|
|
|
|
var current_grip := release_transform * current_grip_local
|
|
|
|
|
var current_wrist := _preview_wrist_position(hand_value, root_offset, facing_left)
|
|
|
|
|
if current_wrist.distance_to(current_grip) <= MAX_REACH and not _set_down_actor_overlaps_release(prop_bounds, actor_root, facing_left):
|
|
|
|
|
candidates.append({
|
|
|
|
|
"root": actor_root,
|
|
|
|
|
"facing_left": facing_left,
|
|
|
|
|
"grip": current_grip,
|
|
|
|
|
"distance": current_wrist.distance_to(current_grip),
|
|
|
|
|
})
|
|
|
|
|
for actor_on_right: bool in [false, true]:
|
|
|
|
|
var candidate_facing_left := actor_on_right
|
|
|
|
|
var grip_local := anchor_position
|
|
|
|
|
if candidate_facing_left:
|
|
|
|
|
grip_local.x = -grip_local.x
|
|
|
|
|
var grip_world := release_transform * grip_local
|
|
|
|
|
var side := 1.0 if actor_on_right else -1.0
|
|
|
|
|
var approach_root := Vector2(grip_world.x + side * APPROACH_GAP, actor_root.y)
|
|
|
|
|
for body_rect: Rect2 in _set_down_actor_body_rects_relative(candidate_facing_left):
|
|
|
|
|
var body_world := body_rect
|
|
|
|
|
body_world.position += actor_root
|
|
|
|
|
if body_world.end.y <= prop_bounds.position.y or body_world.position.y >= prop_bounds.end.y:
|
|
|
|
|
continue
|
|
|
|
|
if actor_on_right:
|
|
|
|
|
approach_root.x = maxf(approach_root.x, prop_bounds.end.x - body_rect.position.x + 8.0)
|
|
|
|
|
else:
|
|
|
|
|
approach_root.x = minf(approach_root.x, prop_bounds.position.x - body_rect.end.x - 8.0)
|
|
|
|
|
var candidate_offset := root_offset + (approach_root - actor_root)
|
|
|
|
|
var wrist := _preview_wrist_position(hand_value, candidate_offset, candidate_facing_left)
|
|
|
|
|
var distance := wrist.distance_to(grip_world)
|
|
|
|
|
if distance <= MAX_REACH and _set_down_route_is_clear(actor_root, approach_root, root_offset):
|
|
|
|
|
candidates.append({
|
|
|
|
|
"root": approach_root,
|
|
|
|
|
"facing_left": candidate_facing_left,
|
|
|
|
|
"grip": grip_world,
|
|
|
|
|
"distance": distance,
|
|
|
|
|
})
|
|
|
|
|
if candidates.is_empty():
|
|
|
|
|
return {"valid": false, "reason": "Too far to reach", "release_transform": release_transform}
|
|
|
|
|
candidates.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
|
|
|
|
return float(a.get("distance", INF)) < float(b.get("distance", INF))
|
|
|
|
|
)
|
|
|
|
|
var chosen: Dictionary = candidates[0]
|
|
|
|
|
if not _set_down_route_is_clear(actor_root, chosen["root"], root_offset):
|
|
|
|
|
return {"valid": false, "reason": "Too far to reach", "release_transform": release_transform}
|
|
|
|
|
return {
|
|
|
|
|
"valid": true,
|
|
|
|
|
"reason": "",
|
|
|
|
|
"release_transform": release_transform,
|
|
|
|
|
"requested_target": target,
|
|
|
|
|
"clearance_lift": clearance_lift,
|
|
|
|
|
"target_adjusted": clearance_lift > 0.01,
|
|
|
|
|
"grip_position": chosen["grip"],
|
|
|
|
|
"approach_position": chosen["root"],
|
|
|
|
|
"facing_left": bool(chosen["facing_left"]),
|
|
|
|
|
"distance": float(chosen["distance"]),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_down_route_is_clear(start_root: Vector2, target_root: Vector2, root_offset: Vector2) -> bool:
|
|
|
|
|
if _rig == null or not is_instance_valid(_rig):
|
|
|
|
|
return false
|
|
|
|
|
var nav_agent := _rig.get_node_or_null(NodePath("NavigationAgentAnchor/NavigationAgent2D")) as NavigationAgent2D
|
|
|
|
|
if nav_agent != null:
|
|
|
|
|
var nav_map: RID = nav_agent.get_navigation_map()
|
|
|
|
|
if NavigationServer2D.map_get_iteration_id(nav_map) > 0:
|
|
|
|
|
var foot_offset: Vector2 = _locomotion.call("get_foot_offset")
|
|
|
|
|
var path: PackedVector2Array = NavigationServer2D.map_get_path(nav_map, start_root - foot_offset, target_root - foot_offset, true)
|
|
|
|
|
if path.is_empty():
|
|
|
|
|
return false
|
|
|
|
|
var delta := Vector2(target_root.x - start_root.x, 0.0)
|
|
|
|
|
if is_zero_approx(delta.x):
|
|
|
|
|
return true
|
|
|
|
|
var space_state := _rig.get_world_2d().direct_space_state
|
|
|
|
|
if space_state == null:
|
|
|
|
|
return true
|
|
|
|
|
var excluded: Array[RID] = []
|
|
|
|
|
for rid: RID in _rig.call("get_collision_body_rids"):
|
|
|
|
|
excluded.append(rid)
|
|
|
|
|
if _held_prop != null and is_instance_valid(_held_prop):
|
|
|
|
|
excluded.append(_held_prop.get_rid())
|
|
|
|
|
var collision_bodies: Dictionary = _locomotion.get("collision_bodies") if _locomotion != null else {}
|
|
|
|
|
for part_name: String in ["torso", "head"]:
|
|
|
|
|
var proxy := collision_bodies.get(part_name) as AnimatableBody2D
|
|
|
|
|
if proxy == null or not is_instance_valid(proxy):
|
|
|
|
|
continue
|
|
|
|
|
var shape_node := proxy.get_node_or_null(NodePath("CollisionShape2D")) as CollisionShape2D
|
|
|
|
|
if shape_node == null or shape_node.shape == null:
|
|
|
|
|
continue
|
|
|
|
|
var query := PhysicsShapeQueryParameters2D.new()
|
|
|
|
|
query.shape = shape_node.shape
|
|
|
|
|
query.transform = shape_node.global_transform
|
|
|
|
|
query.transform.origin += root_offset
|
|
|
|
|
query.motion = delta
|
|
|
|
|
query.collision_mask = STAGE_PHYSICS.ACTOR_BLOCKING_PROP_LAYER
|
|
|
|
|
query.exclude = excluded
|
|
|
|
|
var cast: PackedFloat32Array = space_state.cast_motion(query)
|
|
|
|
|
if not cast.is_empty() and cast[0] < 0.999:
|
|
|
|
|
return false
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _resolve_set_down_release_transform(prop: PropBlock, target: Vector2) -> Dictionary:
|
|
|
|
|
if prop == null or not is_instance_valid(prop):
|
|
|
|
|
return {"valid": false, "reason": "No room to let go here"}
|
|
|
|
|
var release_transform := _set_down_transform_for_target(prop, target)
|
|
|
|
|
var total_lift := 0.0
|
|
|
|
|
for _attempt: int in 8:
|
|
|
|
|
var overlap_info := _set_down_target_overlap_info(prop, release_transform)
|
|
|
|
|
var overlaps: Array = overlap_info.get("overlaps", [])
|
|
|
|
|
if overlaps.is_empty():
|
|
|
|
|
return {
|
|
|
|
|
"valid": true,
|
|
|
|
|
"release_transform": release_transform,
|
|
|
|
|
"clearance_lift": total_lift,
|
|
|
|
|
}
|
|
|
|
|
if bool(overlap_info.get("blocked", false)):
|
|
|
|
|
if PROP_TRACE_ENABLED:
|
|
|
|
|
for entry: Dictionary in overlaps:
|
|
|
|
|
var blocker: Object = entry.get("collider")
|
|
|
|
|
var blocker_position := (blocker as Node2D).global_position if blocker is Node2D else Vector2.INF
|
|
|
|
|
var part_name := String((blocker as Node).get_meta("part_name", "")) if blocker is Node else ""
|
|
|
|
|
print("[PropTrace] set_down_blocker prop=%s collider=%s class=%s part=%s adjustable=%s position=%s" % [_trace_prop_id(prop), String((blocker as Node).name) if blocker is Node else "<unknown>", blocker.get_class() if blocker is Object else "<unknown>", part_name, str(entry.get("adjustable", false)), str(blocker_position)])
|
|
|
|
|
return {"valid": false, "reason": "No room to let go here", "release_transform": release_transform}
|
|
|
|
|
var highest_support_top := INF
|
|
|
|
|
for entry: Dictionary in overlaps:
|
|
|
|
|
var collider: Object = entry.get("collider")
|
|
|
|
|
var support_top := _set_down_support_top_y(collider)
|
|
|
|
|
if not is_finite(support_top):
|
|
|
|
|
return {"valid": false, "reason": "No room to let go here", "release_transform": release_transform}
|
|
|
|
|
highest_support_top = minf(highest_support_top, support_top)
|
|
|
|
|
var geometry: Dictionary = _set_down_collision_geometry(prop)
|
|
|
|
|
var shape: Shape2D = geometry.get("shape") as Shape2D
|
|
|
|
|
if shape == null:
|
|
|
|
|
return {"valid": true, "release_transform": release_transform, "clearance_lift": total_lift}
|
|
|
|
|
var shape_transform: Transform2D = release_transform * (geometry.get("local_transform", Transform2D.IDENTITY) as Transform2D)
|
|
|
|
|
var shape_bounds := _set_down_shape_bounds(shape, shape_transform)
|
|
|
|
|
var lift := shape_bounds.end.y - highest_support_top + 2.0
|
|
|
|
|
if not is_finite(lift) or lift <= 0.0:
|
|
|
|
|
lift = 2.0
|
|
|
|
|
total_lift += lift
|
|
|
|
|
if total_lift > 8192.0:
|
|
|
|
|
return {"valid": false, "reason": "No room to let go here", "release_transform": release_transform}
|
|
|
|
|
release_transform.origin.y -= lift
|
|
|
|
|
return {"valid": false, "reason": "No room to let go here", "release_transform": release_transform}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_down_collision_geometry(prop: PropBlock) -> Dictionary:
|
|
|
|
|
var collision_shape: Shape2D = null
|
|
|
|
|
var local_transform := Transform2D.IDENTITY
|
|
|
|
|
var polygon_node := prop.get_node_or_null(NodePath("CollisionPolygon2D")) as CollisionPolygon2D
|
|
|
|
|
if polygon_node != null and not polygon_node.disabled and polygon_node.polygon.size() >= 3:
|
|
|
|
|
var points := Geometry2D.convex_hull(polygon_node.polygon)
|
|
|
|
|
if points.size() >= 3:
|
|
|
|
|
var convex := ConvexPolygonShape2D.new()
|
|
|
|
|
convex.points = points
|
|
|
|
|
collision_shape = convex
|
|
|
|
|
local_transform = prop.global_transform.affine_inverse() * polygon_node.global_transform
|
|
|
|
|
var circle_node := prop.get_node_or_null(NodePath("CollisionShape2D")) as CollisionShape2D
|
|
|
|
|
if collision_shape == null and circle_node != null and circle_node.shape != null and not circle_node.disabled:
|
|
|
|
|
collision_shape = circle_node.shape
|
|
|
|
|
local_transform = prop.global_transform.affine_inverse() * circle_node.global_transform
|
|
|
|
|
if collision_shape == null:
|
|
|
|
|
return {}
|
|
|
|
|
return {"shape": collision_shape, "local_transform": local_transform}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_down_target_overlap_info(prop: PropBlock, release_transform: Transform2D) -> Dictionary:
|
|
|
|
|
var geometry := _set_down_collision_geometry(prop)
|
|
|
|
|
var collision_shape: Shape2D = geometry.get("shape") as Shape2D
|
|
|
|
|
if collision_shape == null:
|
|
|
|
|
return {"overlaps": [], "blocked": false}
|
|
|
|
|
var space_state: PhysicsDirectSpaceState2D = prop.get_world_2d().direct_space_state
|
|
|
|
|
if space_state == null:
|
|
|
|
|
return {"overlaps": [], "blocked": false}
|
|
|
|
|
var query := PhysicsShapeQueryParameters2D.new()
|
|
|
|
|
query.shape = collision_shape
|
|
|
|
|
query.transform = release_transform * (geometry.get("local_transform", Transform2D.IDENTITY) as Transform2D)
|
|
|
|
|
query.collision_mask = STAGE_PHYSICS.TERRAIN_LAYER | STAGE_PHYSICS.PROP_LAYER
|
|
|
|
|
if bool(prop.stage_roles.get("released_blocks_stickmen", true)):
|
|
|
|
|
query.collision_mask |= STAGE_PHYSICS.ACTOR_LAYER
|
|
|
|
|
query.exclude = [prop.get_rid()]
|
|
|
|
|
var overlaps: Array[Dictionary] = space_state.intersect_shape(query, 64)
|
|
|
|
|
var blocking_overlaps: Array[Dictionary] = []
|
|
|
|
|
var blocked := false
|
|
|
|
|
for overlap: Dictionary in overlaps:
|
|
|
|
|
var collider: Object = overlap.get("collider")
|
|
|
|
|
if collider == null:
|
|
|
|
|
continue
|
|
|
|
|
if collider is TerrainBlock:
|
|
|
|
|
blocking_overlaps.append({"collider": collider, "adjustable": true})
|
|
|
|
|
continue
|
|
|
|
|
if collider is PropBlock:
|
|
|
|
|
var other := collider as PropBlock
|
|
|
|
|
if PROP_STAGE_ROLES.props_should_collide(prop.stage_roles, PropBlock.RuntimeRole.RELEASED, other.stage_roles, int(other.runtime_role)):
|
|
|
|
|
var adjustable := bool(other.stage_roles.get("holds_other_props", false))
|
|
|
|
|
blocking_overlaps.append({"collider": collider, "adjustable": adjustable})
|
|
|
|
|
blocked = blocked or not adjustable
|
|
|
|
|
continue
|
|
|
|
|
if collider is CollisionObject2D and ((collider as CollisionObject2D).collision_layer & STAGE_PHYSICS.ACTOR_LAYER) != 0:
|
|
|
|
|
if collider is Node and (collider as Node).has_meta("stickman_rig") and (collider as Node).get_meta("stickman_rig") == _rig:
|
|
|
|
|
var part_name := String((collider as Node).get_meta("part_name", ""))
|
|
|
|
|
if part_name != "head" and part_name != "torso":
|
|
|
|
|
continue
|
|
|
|
|
blocking_overlaps.append({"collider": collider, "adjustable": false})
|
|
|
|
|
blocked = true
|
|
|
|
|
return {"overlaps": blocking_overlaps, "blocked": blocked}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_down_actor_body_rects_relative(facing_left: bool) -> Array[Rect2]:
|
|
|
|
|
var result: Array[Rect2] = []
|
|
|
|
|
if _rig == null or not is_instance_valid(_rig) or _locomotion == null:
|
|
|
|
|
return result
|
|
|
|
|
var collision_bodies: Dictionary = _locomotion.get("collision_bodies")
|
|
|
|
|
for part_name: String in ["torso", "head"]:
|
|
|
|
|
var proxy := collision_bodies.get(part_name) as AnimatableBody2D
|
|
|
|
|
if proxy == null or not is_instance_valid(proxy):
|
|
|
|
|
continue
|
|
|
|
|
var shape_node := proxy.get_node_or_null(NodePath("CollisionShape2D")) as CollisionShape2D
|
|
|
|
|
if shape_node == null or shape_node.shape == null:
|
|
|
|
|
continue
|
|
|
|
|
var bounds := _set_down_shape_bounds(shape_node.shape, shape_node.global_transform)
|
|
|
|
|
var local_rect := Rect2(bounds.position - _rig.global_position, bounds.size)
|
|
|
|
|
if facing_left != _is_facing_left():
|
|
|
|
|
local_rect.position.x = -local_rect.end.x
|
|
|
|
|
result.append(local_rect)
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_down_actor_overlaps_release(prop_bounds: Rect2, actor_root: Vector2, facing_left: bool) -> bool:
|
|
|
|
|
for body_rect: Rect2 in _set_down_actor_body_rects_relative(facing_left):
|
|
|
|
|
var body_world := body_rect
|
|
|
|
|
body_world.position += actor_root
|
|
|
|
|
if body_world.intersects(prop_bounds):
|
|
|
|
|
return true
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_down_shape_bounds(shape: Shape2D, shape_transform: Transform2D) -> Rect2:
|
|
|
|
|
var local_rect := shape.get_rect()
|
|
|
|
|
var corners := [
|
|
|
|
|
local_rect.position,
|
|
|
|
|
Vector2(local_rect.end.x, local_rect.position.y),
|
|
|
|
|
local_rect.end,
|
|
|
|
|
Vector2(local_rect.position.x, local_rect.end.y),
|
|
|
|
|
]
|
|
|
|
|
var first := true
|
|
|
|
|
var bounds := Rect2()
|
|
|
|
|
for corner: Vector2 in corners:
|
|
|
|
|
var point := shape_transform * corner
|
|
|
|
|
if first:
|
|
|
|
|
bounds = Rect2(point, Vector2.ZERO)
|
|
|
|
|
first = false
|
|
|
|
|
else:
|
|
|
|
|
bounds = bounds.expand(point)
|
|
|
|
|
return bounds
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_down_transform_for_target(prop: PropBlock, target: Vector2) -> Transform2D:
|
|
|
|
|
var transform := prop.global_transform
|
|
|
|
|
if prop == _held_prop and _pickup_transform_valid:
|
|
|
|
|
transform = _pickup_transform
|
|
|
|
|
transform.origin = target
|
|
|
|
|
return transform
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_down_support_top_y(collider: Object) -> float:
|
|
|
|
|
var collision_polygon: CollisionPolygon2D = null
|
|
|
|
|
if collider is TerrainBlock:
|
|
|
|
|
collision_polygon = (collider as TerrainBlock).get_node_or_null(NodePath("CollisionPolygon2D")) as CollisionPolygon2D
|
|
|
|
|
elif collider is PropBlock:
|
|
|
|
|
collision_polygon = (collider as PropBlock).get_node_or_null(NodePath("CollisionPolygon2D")) as CollisionPolygon2D
|
|
|
|
|
if collision_polygon != null and not collision_polygon.disabled and collision_polygon.polygon.size() >= 3:
|
|
|
|
|
var top_y := INF
|
|
|
|
|
for point: Vector2 in collision_polygon.polygon:
|
|
|
|
|
top_y = minf(top_y, (collision_polygon.global_transform * point).y)
|
|
|
|
|
return top_y
|
|
|
|
|
if collider is PropBlock:
|
|
|
|
|
var circle_node := (collider as PropBlock).get_node_or_null(NodePath("CollisionShape2D")) as CollisionShape2D
|
|
|
|
|
if circle_node != null and circle_node.shape != null and not circle_node.disabled:
|
|
|
|
|
return _set_down_shape_bounds(circle_node.shape, circle_node.global_transform).position.y
|
|
|
|
|
return INF
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _begin_set_down_reach() -> void:
|
|
|
|
|
var marker := _hand_marker()
|
|
|
|
|
if marker == null or _held_prop == null or not is_instance_valid(_held_prop):
|
|
|
|
|
_fail_set_down("Too far to reach")
|
|
|
|
|
return
|
|
|
|
|
if _set_down_facing_left != _is_facing_left():
|
|
|
|
|
_rig.call("set_facing_profile", 0 if _set_down_facing_left else 1)
|
|
|
|
|
_set_down_grip_target = _set_down_release_transform * _scaled_anchor_position(_held_anchor, _held_prop, _set_down_facing_left)
|
|
|
|
|
_reach_start = marker.global_position
|
|
|
|
|
_reach_target = _set_down_grip_target
|
|
|
|
|
_reach_elapsed = 0.0
|
|
|
|
|
_set_down_reach_start_transform = _held_prop.global_transform
|
|
|
|
|
_prepare_set_down_crouch(_set_down_grip_target)
|
|
|
|
|
_phase = Phase.SET_DOWN_REACHING
|
|
|
|
|
_trace_prop_state("set_down_reaching", _held_prop)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_set_down_approach_arrived() -> void:
|
|
|
|
|
_trace_prop_state("set_down_approach_arrived", _held_prop)
|
|
|
|
|
if _held_prop == null or not is_instance_valid(_held_prop):
|
|
|
|
|
_fail_set_down("No prop is held")
|
|
|
|
|
return
|
|
|
|
|
if not _refresh_set_down_release_target():
|
|
|
|
|
_fail_set_down("No room to let go here")
|
|
|
|
|
return
|
|
|
|
|
if _solved_wrist_transform().origin.distance_to(_set_down_grip_target) > MAX_REACH:
|
|
|
|
|
_fail_set_down("Too far to reach")
|
|
|
|
|
return
|
|
|
|
|
_begin_set_down_reach()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _update_set_down_reach(delta: float) -> void:
|
|
|
|
|
if _held_prop == null or not is_instance_valid(_held_prop):
|
|
|
|
|
_fail_set_down("No prop is held")
|
|
|
|
|
return
|
|
|
|
|
var marker := _hand_marker()
|
|
|
|
|
if marker == null:
|
|
|
|
|
_fail_set_down("Too far to reach")
|
|
|
|
|
return
|
|
|
|
|
_reach_elapsed += delta
|
|
|
|
|
var weight := smoothstep(0.0, 1.0, clampf(_reach_elapsed / SET_DOWN_REACH_DURATION, 0.0, 1.0))
|
|
|
|
|
marker.global_position = _reach_start.lerp(_set_down_grip_target, weight)
|
|
|
|
|
_update_pickup_crouch(weight)
|
|
|
|
|
_update_held_transform(delta, true)
|
|
|
|
|
if _pickup_transform_valid:
|
|
|
|
|
_held_prop.global_transform = _set_down_reach_attachment_transform(marker.global_position, weight)
|
|
|
|
|
if weight < 1.0:
|
|
|
|
|
return
|
|
|
|
|
if _solved_wrist_transform().origin.distance_to(_set_down_grip_target) > MAX_REACH:
|
|
|
|
|
_fail_set_down("Too far to reach")
|
|
|
|
|
return
|
|
|
|
|
var previous_release_position := _set_down_release_transform.origin
|
|
|
|
|
if not _refresh_set_down_release_target():
|
|
|
|
|
_fail_set_down("No room to let go here")
|
|
|
|
|
return
|
|
|
|
|
if previous_release_position.distance_to(_set_down_release_transform.origin) > 1.0:
|
|
|
|
|
_reach_start = marker.global_position
|
|
|
|
|
_reach_target = _set_down_grip_target
|
|
|
|
|
_reach_elapsed = 0.0
|
|
|
|
|
_set_down_reach_start_transform = _held_prop.global_transform
|
|
|
|
|
return
|
|
|
|
|
marker.global_position = _set_down_grip_target
|
|
|
|
|
_held_prop.global_transform = _set_down_release_transform
|
|
|
|
|
_set_down_recover_hand_start = marker.position
|
|
|
|
|
_set_down_recover_elapsed = 0.0
|
|
|
|
|
if PROP_TRACE_ENABLED:
|
|
|
|
|
print("[PropTrace] set_down_release requested=%s final=%s" % [str(_set_down_target), str(_set_down_release_transform.origin)])
|
|
|
|
|
_release_held("set_down", false, Vector2.ZERO, 0.0, true, false)
|
|
|
|
|
_phase = Phase.SET_DOWN_RECOVERING
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _refresh_set_down_release_target() -> bool:
|
|
|
|
|
if _held_prop == null or not is_instance_valid(_held_prop):
|
|
|
|
|
return false
|
|
|
|
|
var result := _resolve_set_down_release_transform(_held_prop, _set_down_target)
|
|
|
|
|
if not bool(result.get("valid", false)):
|
|
|
|
|
return false
|
|
|
|
|
_set_down_release_transform = result["release_transform"]
|
|
|
|
|
_set_down_grip_target = _set_down_release_transform * _scaled_anchor_position(_held_anchor, _held_prop, _set_down_facing_left)
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_down_reach_attachment_transform(grip_position: Vector2, weight: float) -> Transform2D:
|
|
|
|
|
var start_rotation := _set_down_reach_start_transform.get_rotation()
|
|
|
|
|
var target_rotation := _pickup_transform.get_rotation()
|
|
|
|
|
var start_scale := _set_down_reach_start_transform.get_scale()
|
|
|
|
|
var target_scale := _pickup_transform.get_scale()
|
|
|
|
|
var clamped_weight := clampf(weight, 0.0, 1.0)
|
|
|
|
|
var transform := Transform2D(
|
|
|
|
|
lerp_angle(start_rotation, target_rotation, clamped_weight),
|
|
|
|
|
Vector2.ZERO
|
|
|
|
|
).scaled(start_scale.lerp(target_scale, clamped_weight))
|
|
|
|
|
transform.origin = grip_position - transform.basis_xform(_scaled_anchor_position(_held_anchor, _held_prop, _set_down_facing_left))
|
|
|
|
|
return transform
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _prepare_set_down_crouch(grip_position: Vector2) -> void:
|
|
|
|
|
var torso_marker := _torso_marker()
|
|
|
|
|
_pickup_uses_crouch = false
|
|
|
|
|
if torso_marker == null:
|
|
|
|
|
return
|
|
|
|
|
_pickup_torso_start = torso_marker.position
|
|
|
|
|
_pickup_torso_start_rotation = torso_marker.rotation
|
|
|
|
|
_pickup_torso_crouched = _pickup_torso_start
|
|
|
|
|
_pickup_torso_crouched_rotation = _pickup_torso_start_rotation
|
|
|
|
|
var grip_local := _rig.to_local(grip_position)
|
|
|
|
|
var low_amount := clampf((grip_local.y - _pickup_torso_start.y - LOW_PICKUP_THRESHOLD) / LOW_PICKUP_MAX_DROP, 0.0, 1.0)
|
|
|
|
|
if low_amount <= 0.0:
|
|
|
|
|
return
|
|
|
|
|
var toward_prop := signf(grip_local.x - _pickup_torso_start.x)
|
|
|
|
|
if is_zero_approx(toward_prop):
|
|
|
|
|
toward_prop = -1.0 if _set_down_facing_left else 1.0
|
|
|
|
|
_pickup_torso_crouched += Vector2(toward_prop * LOW_PICKUP_LEAN * low_amount, LOW_PICKUP_MAX_DROP * low_amount)
|
|
|
|
|
_pickup_torso_crouched_rotation -= toward_prop * 0.10 * low_amount
|
|
|
|
|
_pickup_uses_crouch = true
|
|
|
|
|
_rig.call("_set_limb_mods_enabled", true)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _update_set_down_recovery(delta: float) -> void:
|
|
|
|
|
_set_down_recover_elapsed += delta
|
|
|
|
|
var weight := smoothstep(0.0, 1.0, clampf(_set_down_recover_elapsed / SET_DOWN_RECOVER_DURATION, 0.0, 1.0))
|
|
|
|
|
var marker := _hand_marker()
|
|
|
|
|
if marker != null:
|
|
|
|
|
marker.position = _set_down_recover_hand_start.lerp(_marker_start_position, weight)
|
|
|
|
|
_update_pickup_crouch(1.0 - weight)
|
|
|
|
|
if weight < 1.0:
|
|
|
|
|
return
|
|
|
|
|
_restore_hand_target()
|
|
|
|
|
_restore_pickup_torso()
|
|
|
|
|
_phase = Phase.IDLE
|
|
|
|
|
_active_action.clear()
|
|
|
|
|
action_completed.emit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _fail_set_down(reason: String) -> void:
|
|
|
|
|
if _locomotion != null and _phase == Phase.SET_DOWN_APPROACHING:
|
|
|
|
|
_locomotion.call("cancel_walk")
|
|
|
|
|
var prop := _held_prop if _held_prop != null and is_instance_valid(_held_prop) else null
|
|
|
|
|
if PROP_TRACE_ENABLED:
|
|
|
|
|
print("[PropTrace] set_down_failed actor=%s prop=%s reason=%s phase=%s actor_position=%s" % [_trace_actor_name(), _trace_prop_id(prop), reason, Phase.keys()[int(_phase)], str(_rig.global_position if _rig != null else Vector2.INF)])
|
|
|
|
|
_restore_hand_target()
|
|
|
|
|
_restore_pickup_torso()
|
|
|
|
|
_phase = Phase.HELD if prop != null else Phase.IDLE
|
|
|
|
|
_active_action.clear()
|
|
|
|
|
prop_event.emit("set_down_failed", prop, reason)
|
|
|
|
|
action_completed.emit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _fail_pickup_approach(reason: String) -> void:
|
|
|
|
|
_clear_reservation()
|
|
|
|
|
_restore_hand_target()
|
|
|
|
|
_restore_pickup_torso()
|
|
|
|
|
_phase = Phase.IDLE
|
|
|
|
|
_active_action.clear()
|
|
|
|
|
prop_event.emit("pickup_failed", null, reason)
|
|
|
|
|
action_completed.emit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _start_approach(grip_position: Vector2) -> bool:
|
|
|
|
|
if _locomotion == null:
|
|
|
|
|
return false
|
|
|
|
@@ -683,13 +1270,12 @@ func _commit_pickup(marker: Marker2D) -> void:
|
|
|
|
|
"linear_velocity": _held_prop.linear_velocity,
|
|
|
|
|
"angular_velocity": _held_prop.angular_velocity,
|
|
|
|
|
}
|
|
|
|
|
_pickup_transform = _held_prop.global_transform
|
|
|
|
|
_pickup_transform_valid = true
|
|
|
|
|
_held_prop.interaction_reserved_by = 0
|
|
|
|
|
_held_prop.interaction_owner_id = _rig.get_instance_id()
|
|
|
|
|
_held_prop.freeze_mode = RigidBody2D.FREEZE_MODE_KINEMATIC
|
|
|
|
|
_held_prop.freeze = true
|
|
|
|
|
_held_prop.sleeping = false
|
|
|
|
|
_held_prop.collision_layer = 0
|
|
|
|
|
_held_prop.collision_mask = 0
|
|
|
|
|
_held_prop.apply_runtime_role(PropBlock.RuntimeRole.HELD)
|
|
|
|
|
_trace_prop_state("pickup_committed", _held_prop)
|
|
|
|
|
if _pickup_uses_crouch:
|
|
|
|
|
_pickup_recover_elapsed = 0.0
|
|
|
|
|
_phase = Phase.PICKUP_RECOVERING
|
|
|
|
@@ -986,45 +1572,66 @@ func _quadratic_bezier(from: Vector2, control: Vector2, to: Vector2, weight: flo
|
|
|
|
|
return from * inverse * inverse + control * (2.0 * inverse * t) + to * t * t
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _release_held(reason: String, throwing: bool, throw_velocity: Vector2 = Vector2.ZERO, throw_spin: float = 0.0) -> void:
|
|
|
|
|
func _release_held(reason: String, throwing: bool, throw_velocity: Vector2 = Vector2.ZERO, throw_spin: float = 0.0, zero_velocity: bool = false, restore_hand: bool = true) -> void:
|
|
|
|
|
if _held_prop == null or not is_instance_valid(_held_prop):
|
|
|
|
|
_clear_held_state()
|
|
|
|
|
return
|
|
|
|
|
var prop := _held_prop
|
|
|
|
|
var release_velocity := throw_velocity if throwing else _sampled_linear_velocity
|
|
|
|
|
var release_angular := _sampled_angular_velocity
|
|
|
|
|
_trace_prop_state("release_begin:%s" % reason, prop)
|
|
|
|
|
var force_still_release: bool = zero_velocity or reason == "scene_reset"
|
|
|
|
|
var release_velocity := Vector2.ZERO if force_still_release else (throw_velocity if throwing else _sampled_linear_velocity)
|
|
|
|
|
var release_angular := 0.0 if force_still_release else (throw_spin if throwing else _sampled_angular_velocity)
|
|
|
|
|
if not release_velocity.is_finite():
|
|
|
|
|
release_velocity = Vector2.ZERO
|
|
|
|
|
if not is_finite(release_angular):
|
|
|
|
|
release_angular = 0.0
|
|
|
|
|
prop.interaction_owner_id = 0
|
|
|
|
|
prop.interaction_reserved_by = 0
|
|
|
|
|
prop.collision_layer = int(_saved_body_state.get("collision_layer", 1))
|
|
|
|
|
prop.collision_mask = int(_saved_body_state.get("collision_mask", 1))
|
|
|
|
|
prop.z_index = int(_saved_body_state.get("z_index", prop.z_index))
|
|
|
|
|
prop.z_as_relative = bool(_saved_body_state.get("z_as_relative", prop.z_as_relative))
|
|
|
|
|
prop.freeze_mode = int(_saved_body_state.get("freeze_mode", RigidBody2D.FREEZE_MODE_KINEMATIC))
|
|
|
|
|
prop.freeze = false if throwing else bool(_saved_body_state.get("freeze", false))
|
|
|
|
|
if throwing:
|
|
|
|
|
prop.sleeping = false
|
|
|
|
|
prop.linear_velocity = release_velocity
|
|
|
|
|
prop.angular_velocity = throw_spin
|
|
|
|
|
elif not prop.freeze:
|
|
|
|
|
prop.sleeping = false
|
|
|
|
|
prop.linear_velocity = release_velocity
|
|
|
|
|
prop.angular_velocity = release_angular
|
|
|
|
|
var target_role := PropBlock.RuntimeRole.RELEASED
|
|
|
|
|
if reason == "scene_reset":
|
|
|
|
|
target_role = PropBlock.RuntimeRole.START_PHYSICS if String(prop.stage_roles.get("start_motion", PROP_STAGE_ROLES.START_STILL)) == PROP_STAGE_ROLES.START_PHYSICS else PropBlock.RuntimeRole.START_STILL
|
|
|
|
|
prop.apply_runtime_role(target_role)
|
|
|
|
|
prop.sleeping = false
|
|
|
|
|
prop.linear_velocity = release_velocity
|
|
|
|
|
prop.angular_velocity = release_angular
|
|
|
|
|
_held_prop = null
|
|
|
|
|
_held_anchor.clear()
|
|
|
|
|
_saved_body_state.clear()
|
|
|
|
|
_pickup_transform_valid = false
|
|
|
|
|
_sampled_linear_velocity = Vector2.ZERO
|
|
|
|
|
_sampled_angular_velocity = 0.0
|
|
|
|
|
if not throwing:
|
|
|
|
|
if not throwing and restore_hand:
|
|
|
|
|
_restore_hand_target()
|
|
|
|
|
_phase = Phase.IDLE if not throwing else Phase.THROWING
|
|
|
|
|
if reason == "manual" or reason == "ragdoll":
|
|
|
|
|
if reason == "manual" or reason == "ragdoll" or reason == "set_down":
|
|
|
|
|
prop_event.emit("prop_dropped", prop, reason)
|
|
|
|
|
elif reason == "throw":
|
|
|
|
|
prop_event.emit("prop_thrown", prop, "")
|
|
|
|
|
if PROP_TRACE_ENABLED:
|
|
|
|
|
_trace_released_prop = prop
|
|
|
|
|
_trace_released_elapsed = 0.0
|
|
|
|
|
_trace_released_watch_remaining = 5.0
|
|
|
|
|
_trace_prop_state("release_complete:%s" % reason, prop)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _trace_actor_name() -> String:
|
|
|
|
|
return String(_rig.name) if _rig != null and is_instance_valid(_rig) else "<no actor>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _trace_prop_id(prop: PropBlock) -> String:
|
|
|
|
|
if prop == null or not is_instance_valid(prop):
|
|
|
|
|
return "<none>"
|
|
|
|
|
return prop.stage_object_id if not prop.stage_object_id.is_empty() else String(prop.name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _trace_prop_state(event: String, prop: PropBlock) -> void:
|
|
|
|
|
if not PROP_TRACE_ENABLED:
|
|
|
|
|
return
|
|
|
|
|
var timestamp_ms := Time.get_ticks_msec()
|
|
|
|
|
if prop == null or not is_instance_valid(prop):
|
|
|
|
|
print("[PropTrace t=%dms] %s actor=%s prop=<none> phase=%s" % [timestamp_ms, event, _trace_actor_name(), Phase.keys()[int(_phase)]])
|
|
|
|
|
return
|
|
|
|
|
print("[PropTrace t=%dms] %s actor=%s prop=%s phase=%s role=%d freeze=%s layer=%d mask=%d position=%s rotation=%.3f velocity=%s owner=%d reserved=%d" % [timestamp_ms, event, _trace_actor_name(), _trace_prop_id(prop), Phase.keys()[int(_phase)], int(prop.runtime_role), str(prop.freeze), prop.collision_layer, prop.collision_mask, str(prop.global_position), prop.global_rotation, str(prop.linear_velocity), prop.interaction_owner_id, prop.interaction_reserved_by])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _clear_reservation() -> void:
|
|
|
|
@@ -1041,6 +1648,7 @@ func _clear_held_state() -> void:
|
|
|
|
|
_held_prop = null
|
|
|
|
|
_held_anchor.clear()
|
|
|
|
|
_saved_body_state.clear()
|
|
|
|
|
_pickup_transform_valid = false
|
|
|
|
|
_phase = Phase.IDLE
|
|
|
|
|
_active_action.clear()
|
|
|
|
|
|
|
|
|
@@ -1091,6 +1699,13 @@ func _scaled_anchor(anchor: Dictionary, prop: PropBlock) -> Dictionary:
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _scaled_anchor_position(anchor: Dictionary, prop: PropBlock, facing_left: bool) -> Vector2:
|
|
|
|
|
var local_position: Vector2 = (anchor.get("position", Vector2.ZERO) as Vector2) * prop.get_interaction_scale()
|
|
|
|
|
if facing_left:
|
|
|
|
|
local_position.x = -local_position.x
|
|
|
|
|
return local_position
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _grip_world_position(prop: PropBlock, anchor: Dictionary) -> Vector2:
|
|
|
|
|
return _grip_world_position_for_facing(prop, anchor, _is_facing_left())
|
|
|
|
|
|
|
|
|
|