fix: third-person gun actually held — IK to the weapon, real recoil, no flip

Four separate third-person defects, one shared root: the arms were posed at
art-directed ANGLES that merely approximated the gun, and a stuck one-shot
flag kept handing them back to the animation clip.

- The rifle hold was silently DEAD after the first reload or throw. The gate
  trusted AnimationNodeOneShot's `active` parameter, which never clears, so
  clip_owns_arms stayed true forever and the clip drove the arms while the
  gun hung off the hand. `_upper_lock` (a timer we own) is now the
  authority, and the one-shot is explicitly faded out when it expires.
  This alone is why the gun was never held correctly.
- Arms are now solved with real two-bone IK (`_ik_arm`) onto points derived
  from the WEAPON: the stock is anchored in the shoulder pocket, the grip
  and foregrip fall out along the barrel, and the support hand is placed on
  the actual handguard (sliding inboard if the model is too long to reach).
  Verified numerically: the hand lands within 2mm of its target.
- `_measure_weapon` measures the model's AABB along its barrel and re-seats
  it so the hand sits at a realistic pistol-grip point. Weapon models put
  their origin anywhere — the M4's was 10cm from the muzzle end, so parking
  its stock in the shoulder drove the hand INTO the shoulder and folded the
  arm up behind the head.
- Recoil now shows for the OWNER in third person: server_play_fire_effects
  only ever fired for remote shooters, so the local player saw nothing. The
  ammo counter dropping now kicks the model. The kick rides in the gun's aim
  direction so the IK carries BOTH hands up with it, instead of rotating the
  arms and shoving the support hand off the gun.
- Reload no longer flips the gun. The library's pistol-reload rotates the
  wrist the gun is parented to, which turned the rifle upside-down (mag to
  the sky) while the hand reached down for it. The hold now keeps the right
  arm through a reload and the support hand does the magazine work at the
  real mag well, under the receiver.

anim_capture gains recoil shots and lets ADS settle before framing;
debug/dump_bones.gd prints a skin's bone hierarchy.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Nicholas Butzke
2026-07-21 10:15:54 -04:00
co-authored by Claude Opus 4.8
parent df0bf18e0f
commit 0d125dc03f
4 changed files with 361 additions and 81 deletions
+16
View File
@@ -46,6 +46,9 @@ var _remote_footstep_timer: float = 0.0
@export var synced_action_seq: int = 0
var _last_action_seq: int = 0
var _was_reloading: bool = false
## Ammo count last frame — a drop means we fired, which kicks the owner's
## own third-person model (remote shooters go through server_play_fire_effects).
var _last_local_ammo: int = -1
var dash_player: AudioStreamPlayer
var recent_attackers: Dictionary = {} # attacker_id: timestamp
var hit_player: AudioStreamPlayer
@@ -1048,6 +1051,19 @@ func _process(delta: float) -> void:
if now_reloading and not _was_reloading:
_trigger_action("reload")
_was_reloading = now_reloading
# Shot kick on our OWN third-person model. server_play_fire_effects
# only fires for REMOTE shooters, so without this the owner sees no
# recoil at all in third person. Detect it from the ammo counter.
var ammo_now: int = -1
if lw:
if "current_ammo" in lw:
ammo_now = lw.current_ammo
elif "shells" in lw:
ammo_now = lw.shells
if ammo_now >= 0 and _last_local_ammo >= 0 and ammo_now < _last_local_ammo \
and lvisual.has_method("add_gun_recoil"):
lvisual.add_gun_recoil()
_last_local_ammo = ammo_now
# Update UI
if is_instance_valid(health_bar):