Files
wretched/entities/npc.gd

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"