Files

203 lines
9.3 KiB
GDScript

class_name WeaponView
extends Node3D
var muzzle_marker: Node3D
var magazine: Node3D
var bolt: Node3D
var model: Node3D
var hands: Node3D
var displayed := -1
var last_ammo := -1
var last_shots := -1
var kick := 0.0
var equip := 0.0
var recoil := Vector2.ZERO
var recoil_velocity := Vector2.ZERO
var sway := Vector2.ZERO
var previous_look := Vector2.ZERO
var look_initialized := false
var walk_clock := 0.0
var muzzle: MeshInstance3D
var muzzle_left := 0.0
var shot_age := 10.0
var previous_life := -1
var skin_revision := -1
func select_weapon(index: int, appearance: String = "") -> void:
if model:
remove_child(model)
model.queue_free()
model = WeaponModels.build(index)
add_child(model)
muzzle_marker = model.get_node("Muzzle")
magazine = model.get_node("Magazine")
bolt = model.get_node("Bolt")
WeaponSkins.apply(model, WeaponSkins.get_design(index))
skin_revision = WeaponSkins.revision
displayed = index
last_ammo = -1
last_shots = -1
equip = float(WeaponHandling.DATA[index].equip)
recoil = Vector2.ZERO
recoil_velocity = Vector2.ZERO
muzzle_left = 0
shot_age = 10
look_initialized = false
muzzle = MeshInstance3D.new()
var flash := SphereMesh.new()
flash.radius = 0.025
flash.height = 0.13
flash.radial_segments = 8
flash.rings = 4
muzzle.mesh = flash
muzzle.position = WeaponModels.muzzle_position(index) + Vector3(0, 0, -0.035)
muzzle.rotation.x = PI / 2
var glow := StandardMaterial3D.new()
glow.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
glow.albedo_color = Color(1, 0.77, 0.32)
glow.emission_enabled = true
glow.emission = Color(1, 0.52, 0.12)
glow.emission_energy_multiplier = 3
muzzle.material_override = glow
muzzle.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
muzzle.visible = false
model.add_child(muzzle)
if hands:
remove_child(hands)
hands.queue_free()
if not appearance.is_empty() and Arsenal.FAMILIES[index] != 4:
var imported_hands := FpsOperatorHands.new()
add_child(imported_hands)
imported_hands.setup(appearance)
hands = imported_hands
else:
hands = Node3D.new()
add_child(hands)
build_hands(index)
WeaponGeometry.batch(hands)
func sleeve(elbow: Vector3, wrist: Vector3) -> void:
var axis := (wrist - elbow).normalized()
# Tapered forearms connect to the hand at the wrist; the sleeve overlaps the
# glove cuff so that aiming and recoil cannot reveal disconnected pipe ends.
OperatorModel.limb(hands, elbow, wrist, 0.068, 0.038, "fabric")
for amount in [0.44, 0.63, 0.81]:
var fold := OperatorModel.oval(hands, elbow.lerp(wrist, amount), Vector3(0.112 - amount * 0.031, 0.026, 0.099 - amount * 0.026), "fabric_dark")
fold.quaternion = Quaternion(Vector3.UP, axis)
var cuff := OperatorModel.oval(hands, wrist - axis * 0.012, Vector3(0.086, 0.051, 0.078), "fabric_dark")
cuff.quaternion = Quaternion(Vector3.UP, axis)
OperatorModel.limb(hands, wrist - axis * 0.024, wrist + axis * 0.029, 0.038, 0.034, "leather")
var strap := OperatorModel.oval(hands, wrist + axis * 0.006, Vector3(0.083, 0.018, 0.074), "rubber")
strap.quaternion = Quaternion(Vector3.UP, axis)
func finger(start: Vector3, joint: Vector3, end: Vector3, radius := 0.009) -> void:
OperatorModel.limb(hands, start, joint, radius, radius * 0.96, "leather")
OperatorModel.oval(hands, joint, Vector3.ONE * radius * 2.08, "rubber")
OperatorModel.limb(hands, joint, end, radius * 0.95, radius * 0.74, "leather")
OperatorModel.oval(hands, end, Vector3.ONE * radius * 1.48, "leather")
func build_hands(index: int) -> void:
var right_wrist := Vector3(0.054, -0.17, 0.114)
sleeve(Vector3(0.185, -0.34, 0.435), right_wrist)
var palm := OperatorModel.oval(hands, Vector3(0.028, -0.12, 0.069), Vector3(0.079, 0.097, 0.064), "leather")
palm.rotation.x = 0.32
palm.rotation.z = -0.17
OperatorModel.oval(hands, Vector3(0.053, -0.123, 0.082), Vector3(0.031, 0.064, 0.048), "rubber")
for i in 3:
var y := -0.116 - i * 0.017
finger(Vector3(0.049, y, 0.066), Vector3(0.041, y + 0.005, 0.033), Vector3(-0.016, y + 0.002, 0.036), 0.009)
# The index finger reaches the trigger; thumb wraps the rear of the grip.
finger(Vector3(0.05, -0.088, 0.062), Vector3(0.044, -0.066, -0.004), Vector3(0.005, -0.069, -0.019), 0.0085)
finger(Vector3(-0.001, -0.122, 0.087), Vector3(-0.024, -0.085, 0.072), Vector3(-0.012, -0.069, 0.026), 0.012)
for i in 3:
OperatorModel.panel(hands, Vector3(0.06, -0.104 - i * 0.019, 0.072), Vector3(0.008, 0.006, 0.031), "webbing", Vector3(0.18, 0, -0.1))
# Pistols use a two-hand wrap; long guns have a separate fore-end support.
if index == 4:
var wrist := Vector3(-0.056, -0.171, 0.105)
sleeve(Vector3(-0.215, -0.345, 0.415), wrist)
OperatorModel.oval(hands, Vector3(-0.035, -0.127, 0.056), Vector3(0.056, 0.097, 0.089), "leather")
for i in 4:
var y := -0.091 - i * 0.018
finger(Vector3(-0.051, y, 0.041), Vector3(-0.044, y, 0.009), Vector3(0.026, y, 0.007), 0.0083)
finger(Vector3(-0.047, -0.102, 0.074), Vector3(-0.037, -0.066, 0.044), Vector3(-0.031, -0.053, -0.008), 0.011)
else:
var support_z := -0.22 if Arsenal.FAMILIES[index] == 1 else -0.31
var wrist := Vector3(-0.072, -0.093, support_z + 0.065)
sleeve(Vector3(-0.246, -0.308, 0.203), wrist)
var support := OperatorModel.oval(hands, Vector3(-0.047, -0.061, support_z), Vector3(0.061, 0.058, 0.103), "leather")
support.rotation.z = -0.27
OperatorModel.oval(hands, Vector3(-0.072, -0.059, support_z), Vector3(0.024, 0.04, 0.081), "rubber")
for i in 4:
var z := support_z - 0.029 + i * 0.019
finger(Vector3(-0.053, -0.053, z), Vector3(-0.049, -0.016, z), Vector3(-0.014, -0.01, z), 0.0085)
finger(Vector3(-0.044, -0.049, support_z + 0.05), Vector3(-0.007, -0.041, support_z + 0.057), Vector3(0.028, -0.025, support_z + 0.041), 0.011)
func fire(p: Fighter) -> void:
if displayed != p.weapon or previous_life != p.life:
select_weapon(p.weapon, "" if p.game.headless else p.operator_id)
previous_life = p.life
WeaponSkins.apply(model, p.skin_designs.get(str(p.weapon), {}))
var profile: Dictionary = WeaponHandling.DATA[p.weapon]
var ads := WeaponHandling.smooth(p.aim_blend)
recoil_velocity += Vector2(profile.kick, profile.rise) * float(profile.spring) * 2.1 * lerpf(1, 0.65, ads)
kick = 1
muzzle_left = 0.06
muzzle.visible = true
shot_age = 0
func animate(p: Fighter, dt: float) -> void:
var ammo: int = p.magazines[p.weapon]
if displayed != p.weapon or previous_life != p.life:
select_weapon(p.weapon, "" if p.game.headless else p.operator_id)
previous_life = p.life
WeaponSkins.apply(model, p.skin_designs.get(str(p.weapon), {}))
if skin_revision != WeaponSkins.revision:
WeaponSkins.apply(model, p.skin_designs.get(str(p.weapon), {}))
skin_revision = WeaponSkins.revision
var profile: Dictionary = WeaponHandling.DATA[p.weapon]
var ads := WeaponHandling.smooth(p.aim_blend)
shot_age += dt
var spring := WeaponHandling.settle(recoil, recoil_velocity, profile.spring, dt)
recoil = spring[0]
recoil_velocity = spring[1]
kick = move_toward(kick, 0, dt * 12)
equip = move_toward(equip, 0, dt)
muzzle_left = maxf(0, muzzle_left - dt)
muzzle.visible = muzzle_left > 0
var look := Vector2(p.yaw, p.pitch)
var turn := Vector2(wrapf(look.x - previous_look.x, -PI, PI), look.y - previous_look.y) if look_initialized else Vector2.ZERO
previous_look = look
look_initialized = true
var sway_target := (turn / maxf(dt, 0.001) * 0.012).limit_length(0.045) * float(profile.sway) * (1 - ads)
sway = sway.lerp(sway_target, 1 - exp(-dt * 12))
var speed := Vector2(p.velocity.x, p.velocity.z).length()
walk_clock += dt * minf(speed, 9) * 1.8
var bob := Vector3(sin(walk_clock) * 0.007, absf(cos(walk_clock)) * 0.009, 0) * minf(speed / 5.8, 1.4) * (1 - ads)
var sprinting := speed > 6.5 and not p.aiming and p.slide_left <= 0
var sprint_pose := Vector3(-0.13, 0.12, -0.19) if sprinting else Vector3.ZERO
rotation = rotation.lerp(sprint_pose + Vector3(-sway.y, -sway.x, -sway.x * 0.5), 1 - exp(-dt * 12))
var progress := 1 - p.reload_left / float(p.weapon_stats().reload) if p.reload_left > 0 else 0.0
var reload_curve := WeaponHandling.reload_pose(progress)
var equip_curve := WeaponHandling.smooth(equip / float(profile.equip))
model.rotation = Vector3(recoil.y - reload_curve * 0.13, reload_curve * 0.12, reload_curve * 0.36 + equip_curve * 0.16)
model.position = bob + Vector3(0, -equip_curve * 0.20, recoil.x)
var removed := WeaponHandling.smooth((progress - 0.12) / 0.16) * (1 - WeaponHandling.smooth((progress - 0.50) / 0.18))
magazine.position = Vector3(-0.04 * removed, -removed * (0.06 if Arsenal.FAMILIES[p.weapon] == 2 else 0.23), 0)
magazine.rotation.x = removed * 0.22
var cycle := clampf(shot_age / float(p.weapon_stats().rate), 0, 1)
var manual_cycle := sin(clampf((cycle - 0.2) / 0.6, 0, 1) * PI) if cycle < 1 and p.weapon in [2, 3] else 0.0
bolt.position.z = kick * (0.04 if p.weapon == 4 else 0.025) + manual_cycle * 0.07
bolt.rotation.z = manual_cycle * 0.5 if p.weapon == 3 else 0.0
if ammo == 0 and p.weapon == 4: bolt.position.z = 0.04
var strike := sin(clampf((0.65 - p.melee_left) / 0.65, 0, 1) * PI) if p.melee_left > 0 else 0.0
model.position += Vector3(-0.10, 0.025, -0.24) * strike
model.rotation += Vector3(-0.12, -0.3, 0.38) * strike
if hands is FpsOperatorHands:
(hands as FpsOperatorHands).animate(model, p, dt)
else:
hands.position = model.position
hands.rotation = model.rotation
ReflexAlignment.align(self, p, ads)
model.visible = not (p.weapon == 3 and p.aim_blend > 0.92 and not ReflexAlignment.equipped(p))
hands.visible = model.visible