feat: real per-weapon reload/recoil choreography + knife slash, fully matte hair

Weapons no longer reload by spinning the gun 360 (that was the upside-down
flip; nothing detached). New ViewmodelAnim module drives per-style manual-of-
arms sequences on the existing model_root + named arm pivots, with a temporary
magazine/shell/rocket/cell prop that visibly leaves and returns to the gun:

  - mag     (M4/AK/MP7/DMR): roll gun to present the well, hand rips the mag
            down and away, fresh mag up, seat, charge. Gun stays UPRIGHT.
  - boltmag (AWP): mag swap + a distinct bolt-cycle rock.
  - break   (double barrel): hinge open, flick both shells out, drop fresh
            shells in, snap shut.
  - tube    (rocket launcher): tip the tube in, shove a rocket home, shoulder.
  - cell    (plasma/nail gun): glowing energy cell swapped on the side.

Per-shot viewmodel recoil kick (ViewmodelAnim.apply_kick) scaled by each
weapon's recoil_amplitude, so an AWP slams and an MP7 chatters — replaces the
old flat model behaviour. Weapons expose reload_style; base classes call
play_reload / stop (on unequip) / apply_kick.

Knife: the old barely-visible flick is now a real diagonal slash that
alternates backhand/forehand, whips the blade through screen centre, and
fires the third-person melee arm-swing action (synced to other players).

Hair/materials: specular fully to 0 (even a 2% stepped glint swept as shine
across Taila's hair when the camera moved); rim trimmed to a whisper. Fully
matte cloth/hair now.

Verify: debug/fp_weapon_capture.gd screenshots each weapon's reload phases,
the knife slash, and the fire kick.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Nicholas Butzke
2026-07-21 00:26:29 -04:00
co-authored by Claude Fable 5
parent d46530bd8c
commit df0bf18e0f
16 changed files with 590 additions and 76 deletions
+15 -7
View File
@@ -6,6 +6,7 @@ class_name DoubleBarrelShotgun
var shells: int = 2
var reloading: bool = false
var reload_timer: float = 0.0
var _vm_kick: float = 0.0
var player: CharacterBody3D
var camera: Camera3D
@@ -57,16 +58,14 @@ func _build_model() -> void:
func _process(delta: float) -> void:
if reloading:
reload_timer -= delta
# Spin vertically (flip) over the duration of the reload
var spin_speed = (PI * 2.0) / reload_time
model_root.rotation.x += spin_speed * delta
if reload_timer <= 0.0:
shells = 2
reloading = false
model_root.rotation.x = 0.0 # Snap back to perfectly level
print("Shotgun Reloaded!")
# Big single-shot shove that springs back.
if not reloading and _vm_kick > 0.001:
_vm_kick = lerpf(_vm_kick, 0.0, 1.0 - exp(-12.0 * delta))
ViewmodelAnim.apply_kick(self, _vm_kick, 0.10)
func _input(event: InputEvent) -> void:
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
@@ -84,13 +83,22 @@ func _input(event: InputEvent) -> void:
func _start_reload() -> void:
reloading = true
reload_timer = reload_time
_vm_kick = 0.0
ViewmodelAnim.play_reload(self, "break", reload_time)
if reload_sound:
reload_sound.play()
## Called by WeaponManager when this weapon is holstered mid-anything.
func unequip() -> void:
ViewmodelAnim.stop(self)
_vm_kick = 0.0
func _try_fire() -> void:
if shells > 0 and not reloading:
shells -= 1
print("BANG! Shells left: ", shells)
_vm_kick = minf(_vm_kick + 1.0, 1.4)
_apply_impulse()
_shoot_hitscan()
_play_muzzle_flash()