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:
co-authored by
Claude Fable 5
parent
d46530bd8c
commit
df0bf18e0f
@@ -16,11 +16,16 @@ class_name BaseHitscanWeapon
|
||||
@export var penetration_count: int = 0
|
||||
@export var penetration_damage_penalty: float = 0.5
|
||||
|
||||
## Reload choreography style (see ViewmodelAnim): mag, boltmag, break,
|
||||
## tube, cell, none.
|
||||
@export var reload_style: String = "mag"
|
||||
|
||||
var current_ammo: int = 30
|
||||
var reloading: bool = false
|
||||
var reload_timer: float = 0.0
|
||||
var fire_cooldown: float = 0.0
|
||||
var is_firing: bool = false
|
||||
var _vm_kick: float = 0.0 # per-shot viewmodel recoil, decays in _process
|
||||
|
||||
var player: CharacterBody3D
|
||||
var camera: Camera3D
|
||||
@@ -45,25 +50,24 @@ func _build_model() -> void:
|
||||
func _process(delta: float) -> void:
|
||||
if fire_cooldown > 0.0:
|
||||
fire_cooldown -= delta
|
||||
|
||||
|
||||
if reloading:
|
||||
var spin_speed = PI * 8.0
|
||||
model_root.rotation.x += spin_speed * delta
|
||||
|
||||
if reload_timer > 0.0:
|
||||
reload_timer -= delta
|
||||
else:
|
||||
# Continue the loop until facing normal direction (multiples of 2 PI)
|
||||
var target_rot = ceil(model_root.rotation.x / (PI * 2.0)) * (PI * 2.0)
|
||||
if target_rot - model_root.rotation.x < spin_speed * delta * 1.5:
|
||||
current_ammo = max_ammo
|
||||
reloading = false
|
||||
model_root.rotation.x = 0.0
|
||||
print(weapon_name, " Reloaded!")
|
||||
|
||||
# The manual-of-arms animation runs via ViewmodelAnim; here we just
|
||||
# track the timer and hand the ammo over at the end.
|
||||
reload_timer -= delta
|
||||
if reload_timer <= 0.0:
|
||||
current_ammo = max_ammo
|
||||
reloading = false
|
||||
elif automatic and is_firing and fire_cooldown <= 0.0 and current_ammo > 0:
|
||||
_fire()
|
||||
|
||||
# Per-shot recoil: the gun slides back into the grip and the muzzle kicks
|
||||
# up, then springs back. Scaled by this weapon's recoil so an AWP slams
|
||||
# while an MP7 chatters.
|
||||
if not reloading and _vm_kick > 0.001:
|
||||
_vm_kick = lerpf(_vm_kick, 0.0, 1.0 - exp(-14.0 * delta))
|
||||
ViewmodelAnim.apply_kick(self, _vm_kick, recoil_amplitude)
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
|
||||
return
|
||||
@@ -85,16 +89,25 @@ func _start_reload() -> void:
|
||||
reloading = true
|
||||
reload_timer = reload_time
|
||||
is_firing = false
|
||||
_vm_kick = 0.0
|
||||
ViewmodelAnim.play_reload(self, reload_style, 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 _fire() -> void:
|
||||
if current_ammo <= 0 or reloading:
|
||||
return
|
||||
|
||||
current_ammo -= 1
|
||||
fire_cooldown = fire_rate
|
||||
|
||||
_vm_kick = minf(_vm_kick + 1.0, 1.4)
|
||||
|
||||
_shoot_hitscan()
|
||||
_play_muzzle_flash()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user