387 lines
12 KiB
GDScript
387 lines
12 KiB
GDScript
extends CharacterBody3D
|
|
|
|
var npc : bool = true
|
|
|
|
@export var waypoint_route : Node3D = null
|
|
var waypoints : Array
|
|
|
|
var walk_speed : float = 2.0
|
|
var run_speed : float = 5.0
|
|
var time_suspicionbuild : float = 3.0
|
|
var time_suspiciondecay : float = 2.0
|
|
var time_cooldown : float = 2.0
|
|
|
|
# var ragdolling : bool = false
|
|
|
|
var seeing_player : bool = false
|
|
var move_this_frame : bool = false
|
|
var unpause_this_frame : bool = false
|
|
var on_route : bool = false
|
|
|
|
@export var behavior_type : String = "guard"
|
|
var state : String = "default"
|
|
var current_waypoint : int = 0
|
|
var paused : bool = false
|
|
var pauseanim : String
|
|
var pauseturn : bool
|
|
var pauseangle : float
|
|
var lastseenpos : Vector3
|
|
var pathupdateinterval : float = 0.25
|
|
|
|
var player : Node
|
|
|
|
@export var appearance : String = "guard"
|
|
|
|
var current_path : PackedVector3Array
|
|
@onready var default_3d_map_rid : RID = get_world_3d().get_navigation_map()
|
|
var current_path_index : int = 0
|
|
var current_path_point : Vector3
|
|
var path_end_point : Vector3
|
|
@onready var overheadstatus : Control = $StatusSprite/SubViewport/OverheadStatus
|
|
|
|
func _ready() -> void:
|
|
overheadstatus.set_nothing()
|
|
overheadstatus.set_timer(-1)
|
|
|
|
call_deferred("init_route")
|
|
|
|
$Timers/WaypointPause.timeout.connect(timeout_waypointpause)
|
|
$Timers/Suspicion.timeout.connect(timeout_suspicion)
|
|
$Timers/Sight.timeout.connect(timeout_sight)
|
|
|
|
if appearance == "guard":
|
|
$CharacterArmature/Skeleton3D/head_guard.visible = true
|
|
$CharacterArmature/Skeleton3D/body_guard.visible = true
|
|
$CharacterArmature/Skeleton3D/feet_guard.visible = true
|
|
$CharacterArmature/Skeleton3D/legs_guard.visible = true
|
|
elif appearance == "barista":
|
|
$CharacterArmature/Skeleton3D/head_barista.visible = true
|
|
$CharacterArmature/Skeleton3D/body_barista.visible = true
|
|
$CharacterArmature/Skeleton3D/feet_barista.visible = true
|
|
$CharacterArmature/Skeleton3D/legs_barista.visible = true
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
seeing_player = checksight()
|
|
ai_process()
|
|
if move_this_frame:
|
|
movement_process(delta)
|
|
move_and_slide()
|
|
animate()
|
|
|
|
func impact():
|
|
# ragdolling = true
|
|
state = "unconscious"
|
|
set_collision_layer_value(1,0)
|
|
# $CharacterArmature/Skeleton3D/ragdoll.active = true
|
|
# $CharacterArmature/Skeleton3D/ragdoll.physical_bones_start_simulation()
|
|
$AnimationPlayer.play("Death")
|
|
stand_still()
|
|
clearpath()
|
|
|
|
func ai_process():
|
|
if unpause_this_frame:
|
|
paused = false
|
|
unpause_this_frame = false
|
|
|
|
if state == "to_default":
|
|
state = "default"
|
|
elif state == "to_cooldown":
|
|
clearpath()
|
|
state = "cooldown"
|
|
$Timers/Suspicion.stop()
|
|
$Timers/Suspicion.start(time_cooldown)
|
|
elif state == "unconscious":
|
|
on_route = false
|
|
$Timers/Suspicion.stop()
|
|
$Timers/Sight.stop()
|
|
overheadstatus.set_nothing()
|
|
overheadstatus.set_timer(-1)
|
|
|
|
if behavior_type == "guard":
|
|
if state == "suspicion_escalatecheck":
|
|
on_route = false
|
|
$Timers/Suspicion.stop()
|
|
$Timers/Sight.stop()
|
|
|
|
if seeing_player:
|
|
setpath(lastseenpos)
|
|
overheadstatus.set_exclamationpoint()
|
|
overheadstatus.set_timer(-1)
|
|
$Timers/Sight.start(pathupdateinterval)
|
|
state = "pursuit"
|
|
else:
|
|
state = "default"
|
|
elif state == "pursuit_updatepath":
|
|
$Timers/Sight.stop()
|
|
$Timers/Sight.start(pathupdateinterval)
|
|
setpath(lastseenpos)
|
|
state = "pursuit"
|
|
elif state == "cooldown_end":
|
|
$Timers/Suspicion.stop()
|
|
$Timers/Sight.stop()
|
|
on_route = true
|
|
if len(waypoints) > 1:
|
|
current_waypoint = nearest_waypoint()
|
|
state = "default"
|
|
|
|
if state == "default":
|
|
overheadstatus.set_nothing()
|
|
overheadstatus.set_timer(-1)
|
|
|
|
if paused:
|
|
$AnimationPlayer.play(pauseanim)
|
|
if pauseturn == true:
|
|
lerp_toward(pauseangle)
|
|
|
|
if seeing_player:
|
|
$Timers/Suspicion.stop()
|
|
$Timers/Suspicion.start(time_suspicionbuild)
|
|
state = "suspicion"
|
|
if paused:
|
|
unpause_this_frame = true
|
|
$Timers/WaypointPause.stop()
|
|
else:
|
|
on_route = true
|
|
move_this_frame = true
|
|
elif state == "suspicion":
|
|
clearpath()
|
|
on_route = false
|
|
overheadstatus.set_questionmark()
|
|
var timerpercent : float = ((time_suspicionbuild - $Timers/Suspicion.time_left) / time_suspicionbuild) * 100
|
|
overheadstatus.set_timer(timerpercent)
|
|
lerp_toward(angle_toward(lastseenpos))
|
|
|
|
if seeing_player:
|
|
if $Timers/Suspicion.paused:
|
|
$Timers/Suspicion.paused = false
|
|
if !$Timers/Sight.is_stopped():
|
|
$Timers/Sight.stop()
|
|
$Timers/Sight.start(time_suspiciondecay)
|
|
elif !seeing_player:
|
|
if !$Timers/Suspicion.paused:
|
|
$Timers/Suspicion.paused = true
|
|
if $Timers/Sight.is_stopped():
|
|
$Timers/Sight.start(time_suspiciondecay)
|
|
elif state == "pursuit":
|
|
if global_transform.origin.distance_to(player.global_transform.origin) <= 0.8:
|
|
$Timers/Sight.stop()
|
|
state = "to_cooldown"
|
|
elif global_transform.origin.distance_to(path_end_point) <= 0.5:
|
|
$Timers/Sight.stop()
|
|
pauseangle = $CharacterArmature.global_rotation.y - deg_to_rad(90)
|
|
clearpath()
|
|
state = "pursuit_lookleft"
|
|
else:
|
|
on_route = false
|
|
if !current_path.is_empty():
|
|
move_this_frame = true
|
|
elif state == "pursuit_lookleft":
|
|
on_route = false
|
|
|
|
if seeing_player:
|
|
state = "suspicion_escalatecheck"
|
|
else:
|
|
lerp_toward(pauseangle, 0.05)
|
|
var snappedangle = snapped(fposmod($CharacterArmature.global_rotation.y, TAU), 0.01)
|
|
if snappedangle == snapped(fposmod(pauseangle, TAU), 0.01):
|
|
pauseangle = $CharacterArmature.global_rotation.y + deg_to_rad(180)
|
|
state = "pursuit_lookright"
|
|
elif state == "pursuit_lookright":
|
|
on_route = false
|
|
|
|
if seeing_player:
|
|
state = "suspicion_escalatecheck"
|
|
else:
|
|
lerp_toward(pauseangle, 0.05)
|
|
var snappedangle = snapped(fposmod($CharacterArmature.global_rotation.y, TAU), 0.01)
|
|
if snappedangle == snapped(fposmod(pauseangle, TAU), 0.01):
|
|
pauseangle = $CharacterArmature.global_rotation.y + deg_to_rad(90)
|
|
state = "pursuit_lookback"
|
|
elif state == "pursuit_lookback":
|
|
on_route = false
|
|
|
|
if seeing_player:
|
|
state = "suspicion_escalatecheck"
|
|
else:
|
|
lerp_toward(pauseangle, 0.05)
|
|
var snappedangle = snapped(fposmod($CharacterArmature.global_rotation.y, TAU), 0.01)
|
|
if snappedangle == snapped(fposmod(pauseangle, TAU), 0.01):
|
|
state = "to_cooldown"
|
|
elif state == "cooldown":
|
|
overheadstatus.set_3dots()
|
|
overheadstatus.subtractive = true
|
|
var percent : float = ($Timers/Suspicion.time_left / time_suspiciondecay) * 100
|
|
overheadstatus.set_timer(percent)
|
|
|
|
func movement_process(delta):
|
|
if not is_on_floor():
|
|
velocity += get_gravity() * delta
|
|
|
|
if on_route and len(waypoints) == 1 and (global_transform.origin.distance_to(waypoints[0].global_transform.origin) < 0.6):
|
|
if $CharacterArmature.global_rotation.y != waypoints[0].global_rotation.y:
|
|
lerp_toward(waypoints[0].global_rotation.y)
|
|
stand_still()
|
|
elif !current_path.is_empty():
|
|
current_path_point = current_path[current_path_index]
|
|
if !paused:
|
|
walk_toward(current_path_point)
|
|
|
|
if global_transform.origin.distance_to(current_path_point) <= 0.25:
|
|
current_path_index += 1
|
|
if current_path_index >= current_path.size():
|
|
if on_route:
|
|
var wayedpoint = waypoints[current_waypoint]
|
|
if wayedpoint.pausetime > 0:
|
|
paused = true
|
|
$Timers/WaypointPause.start(wayedpoint.pausetime)
|
|
pauseanim = wayedpoint.animation
|
|
if wayedpoint.turntoward == true:
|
|
pauseturn = true
|
|
pauseangle = wayedpoint.global_rotation.y
|
|
else:
|
|
pauseturn = false
|
|
current_waypoint += 1
|
|
if current_waypoint > len(waypoints)-1:
|
|
current_waypoint = 0
|
|
nextwaypoint()
|
|
else:
|
|
clearpath()
|
|
elif NavigationServer3D.map_get_iteration_id(default_3d_map_rid) != 0 and current_path.is_empty() and on_route:
|
|
nextwaypoint()
|
|
else:
|
|
stand_still()
|
|
|
|
move_this_frame = false
|
|
|
|
func animate():
|
|
if state == "unconscious":
|
|
pass
|
|
elif velocity.length() > 0:
|
|
$AnimationPlayer.play("Walk")
|
|
elif !paused or state != "default":
|
|
$AnimationPlayer.play("Idle_Neutral")
|
|
|
|
func angle_toward(target : Vector3):
|
|
return (Vector2(target.z, target.x) - Vector2($CharacterArmature.global_transform.origin.z, $CharacterArmature.global_transform.origin.x)).angle()
|
|
|
|
func walk_toward(point: Vector3):
|
|
var direction = global_transform.origin.direction_to(point)
|
|
velocity.x = direction.x * walk_speed
|
|
velocity.z = direction.z * walk_speed
|
|
print(direction)
|
|
|
|
lerp_toward(atan2(velocity.x, velocity.z))
|
|
|
|
func stand_still():
|
|
velocity.x = 0
|
|
velocity.z = 0
|
|
|
|
func nextwaypoint():
|
|
var waypoint = waypoints[current_waypoint]
|
|
var target_position: Vector3 = waypoint.global_transform.origin
|
|
setpath(target_position)
|
|
|
|
func setpath(target_position):
|
|
clearpath()
|
|
var start_position: Vector3 = global_transform.origin
|
|
current_path = NavigationServer3D.map_get_path(
|
|
default_3d_map_rid,
|
|
start_position,
|
|
target_position,
|
|
true
|
|
)
|
|
if len(current_path) > 0:
|
|
path_end_point = current_path[len(current_path)-1]
|
|
|
|
func clearpath():
|
|
current_path = []
|
|
current_path_index = 0
|
|
current_path_point = global_transform.origin
|
|
velocity.x = 0
|
|
velocity.z = 0
|
|
|
|
func checksight():
|
|
var seenthings = $CharacterArmature/SightCollider.get_overlapping_bodies()
|
|
|
|
if !seenthings.is_empty():
|
|
for item in seenthings:
|
|
if item.get_name() == "PlayerVisibilityShape":
|
|
player = item.get_parent().get_parent()
|
|
if sightraycheck(item) == true:
|
|
return true
|
|
|
|
return false
|
|
|
|
func sightraycheck(target):
|
|
# var radius = target.shape_owner_get_shape(0,0).radius
|
|
var height : float
|
|
var tramsorm : Transform3D
|
|
|
|
if target.get_parent().get_parent().get_name() == "Player" and player.crouched:
|
|
height = target.shape_owner_get_shape(1,0).height
|
|
tramsorm = target.shape_owner_get_transform(1).rotated(Vector3(0,1,0),target.global_rotation.y)
|
|
else:
|
|
height = target.shape_owner_get_shape(0,0).height
|
|
tramsorm = target.shape_owner_get_transform(0).rotated(Vector3(0,1,0),target.global_rotation.y)
|
|
|
|
var top = to_local(target.global_transform.origin - $CharacterArmature/SightCollider/SightRay.transform.origin + tramsorm.origin + Vector3(0, (height/2)-0.1, 0))
|
|
$CharacterArmature/SightCollider/SightRay.target_position = top
|
|
$CharacterArmature/SightCollider/SightRay.global_rotation = global_rotation
|
|
$CharacterArmature/SightCollider/SightRay.force_raycast_update()
|
|
if $CharacterArmature/SightCollider/SightRay.is_colliding():
|
|
if $CharacterArmature/SightCollider/SightRay.get_collider().get_name() == "PlayerVisibilityShape":
|
|
lastseenpos = target.global_transform.origin
|
|
return true
|
|
|
|
return false
|
|
|
|
func lerp_toward(angle: float, speed = 0.15):
|
|
$CharacterArmature.global_rotation.y = lerp_angle($CharacterArmature.global_rotation.y, angle, speed)
|
|
|
|
func get_waypoints(path: Node3D):
|
|
for point in path.get_children():
|
|
waypoints.append(point)
|
|
|
|
func nearest_waypoint():
|
|
var nearest : Vector3 = waypoints[0].global_transform.origin
|
|
var nearest_index : int = 0
|
|
for point in len(waypoints):
|
|
if global_transform.origin.distance_to(waypoints[point].global_transform.origin) < global_transform.origin.distance_to(nearest):
|
|
nearest = waypoints[point].global_transform.origin
|
|
nearest_index = point
|
|
return nearest_index
|
|
|
|
func init_route() -> void:
|
|
if waypoint_route == null:
|
|
var startpos = Marker3D.new()
|
|
get_tree().current_scene.add_child(startpos)
|
|
startpos.set_global_transform(global_transform)
|
|
startpos.set_script(preload("res://scripts/waypoint.gd"))
|
|
waypoints.append(startpos)
|
|
else:
|
|
get_waypoints(waypoint_route)
|
|
|
|
func timeout_waypointpause() -> void:
|
|
if paused:
|
|
unpause_this_frame = true
|
|
|
|
func timeout_suspicion() -> void:
|
|
if behavior_type == "guard":
|
|
if state == "suspicion":
|
|
state = "suspicion_escalatecheck"
|
|
if state == "pursuit_lookaround":
|
|
state = "to_cooldown"
|
|
elif state == "cooldown":
|
|
state = "cooldown_end"
|
|
|
|
func timeout_sight() -> void:
|
|
if behavior_type == "guard":
|
|
if state == "suspicion":
|
|
$Timers/Sight.stop()
|
|
state = "to_default"
|
|
elif state == "pursuit":
|
|
$Timers/Sight.stop()
|
|
$Timers/Sight.start(pathupdateinterval)
|
|
if path_end_point.distance_to(lastseenpos) >= 2:
|
|
state = "pursuit_updatepath"
|