forked from discomrade/EscapeFromCatalunya
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
6.7 KiB
191 lines
6.7 KiB
2 years ago
|
extends KinematicBody
|
||
|
|
||
|
enum State {
|
||
|
APPROACH = 0,
|
||
|
AIM = 1,
|
||
|
SHOOTING = 2,
|
||
|
}
|
||
|
|
||
|
const PLAYER_AIM_TOLERANCE_DEGREES = 15
|
||
|
|
||
|
const SHOOT_WAIT = 0.5
|
||
|
const AIM_TIME = 1
|
||
|
|
||
|
const AIM_PREPARE_TIME = 0.5
|
||
|
const BLEND_AIM_SPEED = 0.05
|
||
|
|
||
|
export(int) var health = 5
|
||
|
export(bool) var test_shoot = false
|
||
|
|
||
|
var state = State.APPROACH
|
||
|
|
||
|
var shoot_countdown = SHOOT_WAIT
|
||
|
var aim_countdown = AIM_TIME
|
||
|
var aim_preparing = AIM_PREPARE_TIME
|
||
|
var dead = false
|
||
|
|
||
|
var player = null
|
||
|
var velocity = Vector3()
|
||
|
var orientation = Transform()
|
||
|
var dummy : Spatial
|
||
|
|
||
|
var in_detection_normal = false
|
||
|
var in_detection_focused = false
|
||
|
|
||
|
const ROTATION_INTERPOLATE_SPEED = 0.025
|
||
|
|
||
|
var root_motion = Transform()
|
||
|
var motion = Vector2()
|
||
|
|
||
|
var last_player_sighting = Vector3()
|
||
|
|
||
|
onready var initial_position = transform.origin
|
||
|
onready var gravity = ProjectSettings.get_setting("physics/3d/default_gravity") * ProjectSettings.get_setting("physics/3d/default_gravity_vector")
|
||
|
|
||
|
onready var animation_tree = $AnimationTree
|
||
|
onready var player_model = $merc1/CharacterRig001
|
||
|
onready var shoot_from = player_model.get_node(@"Skeleton/GunBone/ShootFrom")
|
||
|
onready var fire_cooldown = $FireCooldown
|
||
|
onready var collision_shape = $CapsuleShape
|
||
|
|
||
|
onready var sound_effects = $SoundEffects
|
||
|
onready var sound_effect_jump = sound_effects.get_node(@"Jump")
|
||
|
onready var sound_effect_land = sound_effects.get_node(@"Land")
|
||
|
onready var sound_effect_shoot = sound_effects.get_node(@"Shoot")
|
||
|
onready var explosion_sound = sound_effects.get_node(@"Explosion")
|
||
|
onready var hit_sound = sound_effects.get_node(@"Hit")
|
||
|
|
||
|
onready var reaction_time = $ReactionTime.get_wait_time()
|
||
|
|
||
|
onready var sight_normal = $"PlayerDetection1/NormalLoS"
|
||
|
onready var sight_focused = $"PlayerDetection1/AttentiveLoS"
|
||
|
|
||
|
func _ready():
|
||
|
# Pre-initialize orientation transform.
|
||
|
orientation = player_model.global_transform
|
||
|
orientation.origin = Vector3()
|
||
|
|
||
|
dummy = Spatial.new()
|
||
|
|
||
|
func _physics_process(delta):
|
||
|
if dead:
|
||
|
return
|
||
|
|
||
|
if player != null and fire_cooldown.time_left == 0:
|
||
|
# See if player can be killed because in they're sight.
|
||
|
var ray_origin = shoot_from.global_transform.origin
|
||
|
var ray_to = player.global_transform.origin + Vector3.UP # Above middle of player.
|
||
|
var col = get_world().direct_space_state.intersect_ray(ray_origin, ray_to, [self], 0b111)
|
||
|
# If our raycast hit a player
|
||
|
if not col.empty() and col.collider == player:
|
||
|
# Record last sighting (in case they hide later)
|
||
|
last_player_sighting = ray_to
|
||
|
|
||
|
state = State.AIM
|
||
|
aim_countdown = AIM_TIME
|
||
|
aim_preparing = 0
|
||
|
animation_tree["parameters/state/current"] = 1
|
||
|
|
||
|
if $ReactionTime.time_left == 0:
|
||
|
|
||
|
# If the player is detected by at too large an angle, slerp rotate towards them
|
||
|
var to_player_local = global_transform.xform_inv(player.global_transform.origin)
|
||
|
# The front of this is +Z, and atan2 is zero at +X, so we need to use the Z for the X parameter (second one).
|
||
|
var angle_to_player = atan2(to_player_local.x, to_player_local.z)
|
||
|
var tolerance = deg2rad(PLAYER_AIM_TOLERANCE_DEGREES)
|
||
|
if angle_to_player > tolerance:
|
||
|
lerp_to_face_player(angle_to_player, ROTATION_INTERPOLATE_SPEED)
|
||
|
elif angle_to_player < -tolerance:
|
||
|
lerp_to_face_player(angle_to_player, ROTATION_INTERPOLATE_SPEED)
|
||
|
else:
|
||
|
# Facing player, try to shoot.
|
||
|
self.look_at(ray_to, Vector3.UP)
|
||
|
self.rotate_object_local(Vector3.UP, PI)
|
||
|
|
||
|
var bullet = preload("res://player/bullet/bullet.tscn").instance()
|
||
|
get_parent().add_child(bullet)
|
||
|
bullet.global_transform.origin = ray_origin
|
||
|
# If we don't rotate the bullets there is no useful way to control the particles ..
|
||
|
bullet.look_at(ray_to, Vector3.UP)
|
||
|
bullet.add_collision_exception_with(self)
|
||
|
var shoot_particle = player_model.get_node(@"Skeleton/GunBone/ShootFrom/ShootParticle")
|
||
|
shoot_particle.restart()
|
||
|
shoot_particle.emitting = true
|
||
|
var muzzle_particle = player_model.get_node(@"Skeleton/GunBone/ShootFrom/MuzzleFlash")
|
||
|
muzzle_particle.restart()
|
||
|
muzzle_particle.emitting = true
|
||
|
fire_cooldown.start()
|
||
|
sound_effect_shoot.play()
|
||
|
else:
|
||
|
if $ReactionTime.is_paused():
|
||
|
$ReactionTime.set_paused(false) #restart reaction time
|
||
|
else:
|
||
|
# Player not in sight.
|
||
|
shoot_countdown = SHOOT_WAIT
|
||
|
|
||
|
if $ReactionTime.time_left != reaction_time:
|
||
|
$ReactionTime.start()
|
||
|
$ReactionTime.set_paused(true)
|
||
|
|
||
|
# Run at their last known position
|
||
|
if last_player_sighting != Vector3():
|
||
|
|
||
|
# If the player is detected by at too large an angle, slerp rotate towards them
|
||
|
var to_player_local = global_transform.xform_inv(last_player_sighting)
|
||
|
# The front of this is +Z, and atan2 is zero at +X, so we need to use the Z for the X parameter (second one).
|
||
|
var angle_to_player = atan2(to_player_local.x, to_player_local.z)
|
||
|
var tolerance = deg2rad(PLAYER_AIM_TOLERANCE_DEGREES)
|
||
|
if angle_to_player > tolerance:
|
||
|
lerp_to_face_player(angle_to_player, ROTATION_INTERPOLATE_SPEED)
|
||
|
elif angle_to_player < -tolerance:
|
||
|
lerp_to_face_player(angle_to_player, ROTATION_INTERPOLATE_SPEED)
|
||
|
|
||
|
animation_tree["parameters/state/current"] = 2
|
||
|
# Blend position for walk speed based on motion.
|
||
|
|
||
|
# Run animation straight forward, high speed
|
||
|
animation_tree["parameters/walk/blend_position"] = Vector2(0.5, 0)
|
||
|
|
||
|
# move in XY direction facing
|
||
|
root_motion = animation_tree.get_root_motion_transform()
|
||
|
velocity = move_and_slide(root_motion.origin.length() * self.transform.basis.z * Vector3(1,0,1), Vector3.UP)
|
||
|
self.global_transform.origin += velocity
|
||
|
#self.global_transform.origin += root_motion.origin.length() * self.transform.basis.z * Vector3(1,0,1)
|
||
|
|
||
|
|
||
|
func lerp_to_face_player(angle_to_player, rot_speed):
|
||
|
var langle = lerp_angle(0, angle_to_player, rot_speed)
|
||
|
# restrict to Y axis (no vertical rotation)
|
||
|
self.global_transform.basis = Basis(Vector3(0, 1, 0), langle) * self.global_transform.basis
|
||
|
|
||
|
func hit(col):
|
||
|
health -= 1
|
||
|
if health <= 0:
|
||
|
dead = true
|
||
|
animation_tree.active = false
|
||
|
player_model.visible = false
|
||
|
collision_shape.disabled = true
|
||
|
explosion_sound.play()
|
||
|
self.queue_free() # delete self
|
||
|
hit_sound.play()
|
||
|
# If shot at, attempt to look for player. This will extrapolate from impact position
|
||
|
# so they may validly look the wrong direction
|
||
|
if player == null or player == dummy:
|
||
|
player = dummy
|
||
|
last_player_sighting = (col.position - self.global_transform.origin) * 20 + self.global_transform.origin
|
||
|
# Enable longer focus detection shape
|
||
|
sight_focused.disabled = false
|
||
|
|
||
|
|
||
|
func _on_PlayerDetection1_body_entered(body):
|
||
|
if body.name == "Player" or body.name == "Target":
|
||
|
in_detection_normal = true
|
||
|
player = body
|
||
|
# Enable longer focus detection shape
|
||
|
sight_focused.disabled = false
|
||
|
|
||
|
func _on_PlayerDetection1_body_exited(body):
|
||
|
if body.name == "Player":
|
||
|
player = null
|
||
|
sight_focused.disabled = true
|