158 lines
6.1 KiB
GDScript
158 lines
6.1 KiB
GDScript
class_name ImportedOperator
|
|||
|
|
extends Node3D
|
||
|
|
|
||
|
|
static var scenes: Dictionary = {}
|
||
|
|
|
||
|
|
var skeleton: Skeleton3D
|
||
|
|
var weapon_mount := Node3D.new()
|
||
|
|
var pose := OperatorPose.new()
|
||
|
|
var current := "pro_rifle__idle"
|
||
|
|
var clock := 0.0
|
||
|
|
var action := ""
|
||
|
|
var action_time := 0.0
|
||
|
|
var action_weight := 0.0
|
||
|
|
var previous_life := -1
|
||
|
|
var previous_shot := -1
|
||
|
|
var previous_grenades := -1
|
||
|
|
var previous_hp := 100.0
|
||
|
|
var previous_yaw := 0.0
|
||
|
|
var was_grounded := true
|
||
|
|
var was_reloading := false
|
||
|
|
var dead := false
|
||
|
|
var hand := -1
|
||
|
|
var socket := Transform3D.IDENTITY
|
||
|
|
var variant := "steve"
|
||
|
|
|
||
|
|
func setup(model: String, arms_only: bool = false) -> void:
|
||
|
|
variant = model if model in ["steve", "gas_mask", "pro_rifle", "basic_shooter", "slim_shooter"] else "steve"
|
||
|
|
var suffix := "_arms" if arms_only else ""
|
||
|
|
var key := variant + suffix
|
||
|
|
if not scenes.has(key): scenes[key] = load("res://scenes/operators/" + key + ".scn") as PackedScene
|
||
|
|
var packed: PackedScene = scenes[key]
|
||
|
|
var body := packed.instantiate() as Node3D
|
||
|
|
body.name = "Body"
|
||
|
|
body.rotation.y = PI
|
||
|
|
add_child(body)
|
||
|
|
skeleton = body.find_child("Skeleton3D", true, false) as Skeleton3D
|
||
|
|
pose.setup(skeleton)
|
||
|
|
weapon_mount.name = "WeaponMount"
|
||
|
|
add_child(weapon_mount)
|
||
|
|
hand = skeleton.find_bone("mixamorig_RightHand")
|
||
|
|
pose.apply("pro_rifle__idle_aiming", 0.2, true, "", 0, 0, 0, 0)
|
||
|
|
var grip := pose.global_poses[hand]
|
||
|
|
# Calibrate in model space once; thereafter the socket follows the hand,
|
||
|
|
# including reloads, recoil and death, instead of floating at chest height.
|
||
|
|
var gun_basis := Basis(Vector3.UP, PI)
|
||
|
|
var origin := grip.origin - gun_basis * Vector3(0, -0.09, 0.04)
|
||
|
|
socket = grip.affine_inverse() * Transform3D(gun_basis, origin)
|
||
|
|
update_socket()
|
||
|
|
|
||
|
|
func update_socket() -> void:
|
||
|
|
var body := skeleton.get_parent() as Node3D
|
||
|
|
weapon_mount.transform = body.transform * skeleton.transform * pose.global_poses[hand] * socket
|
||
|
|
|
||
|
|
func choose(key: String, restart: bool = false) -> void:
|
||
|
|
if key != current or restart:
|
||
|
|
current = key
|
||
|
|
clock = 0.0
|
||
|
|
|
||
|
|
func trigger(key: String) -> void:
|
||
|
|
action = key
|
||
|
|
action_time = 0
|
||
|
|
|
||
|
|
static func direction(velocity: Vector3) -> String:
|
||
|
|
var angle := atan2(velocity.x, -velocity.z)
|
||
|
|
var sector := posmod(roundi(angle / (PI / 4.0)), 8)
|
||
|
|
return ["forward", "forward_right", "right", "backward_right", "backward", "backward_left", "left", "forward_left"][sector]
|
||
|
|
|
||
|
|
func animate_state(delta: float, state: Dictionary) -> void:
|
||
|
|
var velocity: Vector3 = state.get("velocity", Vector3.ZERO)
|
||
|
|
var speed := Vector2(velocity.x, velocity.z).length()
|
||
|
|
var crouch := bool(state.get("crouch", false))
|
||
|
|
var aiming := bool(state.get("aim", false))
|
||
|
|
var grounded := bool(state.get("grounded", true))
|
||
|
|
var hp := float(state.get("hp", 100.0))
|
||
|
|
var life := int(state.get("life", 0))
|
||
|
|
var shot := int(state.get("shot", 0))
|
||
|
|
var grenades := int(state.get("grenades", 2)) + int(state.get("flashes", 1))
|
||
|
|
var reload_time := float(state.get("reload", 0.0))
|
||
|
|
var yaw := float(state.get("yaw", 0.0))
|
||
|
|
if life != previous_life:
|
||
|
|
dead = false
|
||
|
|
action = ""
|
||
|
|
action_weight = 0
|
||
|
|
previous_shot = shot
|
||
|
|
previous_grenades = grenades
|
||
|
|
previous_hp = hp
|
||
|
|
previous_yaw = yaw
|
||
|
|
was_reloading = false
|
||
|
|
choose("pro_rifle__idle", true)
|
||
|
|
if hp <= 0:
|
||
|
|
if not dead:
|
||
|
|
var deaths := ["death_from_the_front", "death_from_the_back", "death_from_right", "death_from_front_headshot", "death_from_back_headshot"]
|
||
|
|
var death_key: String = "crouch_death" if crouch else deaths[posmod(int(state.get("id", 0)) + life, deaths.size())]
|
||
|
|
choose("pro_rifle__" + death_key, true)
|
||
|
|
dead = true
|
||
|
|
action = ""
|
||
|
|
elif float(state.get("slide", 0)) > 0:
|
||
|
|
choose("slide__running_slide")
|
||
|
|
clock = (1.0 - clampf(float(state.slide) / 0.65, 0, 1)) * pose.clip(current).length
|
||
|
|
elif not grounded:
|
||
|
|
choose("pro_rifle__jump_up" if velocity.y > 1 else "pro_rifle__jump_loop")
|
||
|
|
elif not was_grounded:
|
||
|
|
choose("pro_rifle__jump_down", true)
|
||
|
|
elif current == "pro_rifle__jump_down" and clock < minf(0.25, pose.clip(current).length):
|
||
|
|
pass
|
||
|
|
elif speed > 0.2:
|
||
|
|
var gait := "walk_crouching_" if crouch else ("sprint_" if speed > 7 else ("walk_" if speed < 3.3 or aiming else "run_"))
|
||
|
|
choose("pro_rifle__" + gait + direction(velocity))
|
||
|
|
else:
|
||
|
|
var turn := wrapf(yaw - previous_yaw, -PI, PI)
|
||
|
|
if absf(turn) > delta * 1.1 and not aiming:
|
||
|
|
choose("pro_rifle__" + ("crouching_" if crouch else "") + "turn_90_" + ("left" if turn > 0 else "right"))
|
||
|
|
else:
|
||
|
|
choose("pro_rifle__idle" + ("_crouching" if crouch else "") + ("_aiming" if aiming else ""))
|
||
|
|
if not dead:
|
||
|
|
if reload_time > 0:
|
||
|
|
if not was_reloading: trigger("pro_rifle__reload")
|
||
|
|
var duration := maxf(0.1, float(state.get("reload_duration", 2.3)))
|
||
|
|
action_time = (1.0 - clampf(reload_time / duration, 0, 1)) * pose.clip(action).length
|
||
|
|
elif was_reloading:
|
||
|
|
action = ""
|
||
|
|
elif grenades < previous_grenades:
|
||
|
|
trigger("basic_shooter__toss_grenade")
|
||
|
|
elif hp < previous_hp:
|
||
|
|
trigger("basic_shooter__hit_reaction")
|
||
|
|
elif shot != previous_shot:
|
||
|
|
trigger("pro_rifle__firing_rifle_walking" if speed > 0.2 else "pro_rifle__firing_rifle_standing")
|
||
|
|
if not action.is_empty():
|
||
|
|
if reload_time <= 0: action_time += delta
|
||
|
|
if action_time >= pose.clip(action).length: action = ""
|
||
|
|
action_weight = move_toward(action_weight, 0.0 if action.is_empty() else 1.0, delta * 16)
|
||
|
|
clock += delta
|
||
|
|
var looping := not dead and not "jump" in current and not "slide" in current
|
||
|
|
pose.apply(current, clock, looping, action, action_time, action_weight, delta, 0 if dead else float(state.get("pitch", 0)))
|
||
|
|
update_socket()
|
||
|
|
if not dead and action.is_empty() and state.has("weapon"):
|
||
|
|
align_support(int(state.weapon))
|
||
|
|
previous_life = life
|
||
|
|
previous_shot = shot
|
||
|
|
previous_grenades = grenades
|
||
|
|
previous_hp = hp
|
||
|
|
previous_yaw = yaw
|
||
|
|
was_grounded = grounded
|
||
|
|
was_reloading = reload_time > 0
|
||
|
|
|
||
|
|
func preview(key: String, time: float) -> void:
|
||
|
|
pose.apply(key, time, false, "", 0, 0, 0, 0)
|
||
|
|
update_socket()
|
||
|
|
|
||
|
|
static func support_point(weapon: int) -> Vector3:
|
||
|
|
if Arsenal.FAMILIES[weapon] == 4: return Vector3(-0.045, -0.145, 0.08)
|
||
|
|
return Vector3(-0.055, -0.095, -0.17 if Arsenal.FAMILIES[weapon] == 1 else -0.255)
|
||
|
|
|
||
|
|
func align_support(weapon: int) -> void:
|
||
|
|
var body := skeleton.get_parent() as Node3D
|
||
|
|
var target := (body.transform * skeleton.transform).affine_inverse() * weapon_mount.transform * support_point(weapon)
|
||
|
|
pose.reach_arm("Left", target)
|