220 lines
6.8 KiB
GDScript
220 lines
6.8 KiB
GDScript
extends CharacterBody3D
|
|
|
|
|
|
@export var walk_speed : float = 2.0
|
|
@export var run_speed : float = 5.0
|
|
@export var route : Node3D
|
|
|
|
var state_change_cooldown : float = 0.1
|
|
@export var alert_escalation_time : float = 3.0
|
|
@export var suspicion_decay_time : float = 1.0
|
|
|
|
var state : String = "patrol"
|
|
var current_waypoint : int = 0
|
|
var paused : bool = false
|
|
var turntoward : float = 0.0
|
|
var lastseenpos : Vector3
|
|
var pathupdateinterval : float = 0.75
|
|
|
|
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
|
|
@onready var overheadstatus : Control = $StatusSprite/SubViewport/OverheadStatus
|
|
|
|
func _ready() -> void:
|
|
overheadstatus.set_3dots()
|
|
overheadstatus.set_timer(-1)
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
process_alerts()
|
|
|
|
if state == "patrol" and !paused and route and route.get_child_count() > 0 and current_path.is_empty() and NavigationServer3D.map_is_active(default_3d_map_rid):
|
|
nextwaypoint()
|
|
|
|
if not is_on_floor():
|
|
velocity += get_gravity() * delta
|
|
|
|
if !current_path.is_empty() and (state == "patrol" or state == "pursue_walkto"):
|
|
current_path_point = current_path[current_path_index]
|
|
walk_toward(current_path_point)
|
|
|
|
if global_transform.origin.distance_to(current_path_point) <= 0.6:
|
|
current_path_index += 1
|
|
if current_path_index >= current_path.size():
|
|
clearpath()
|
|
if state == "patrol":
|
|
var wayedpoint = route.get_children()[current_waypoint]
|
|
if wayedpoint.pausetime > 0:
|
|
paused = true
|
|
$WaypointPauseTimer.start(wayedpoint.pausetime)
|
|
$AnimationPlayer.play(wayedpoint.animation)
|
|
if wayedpoint.turntoward == true:
|
|
turntoward = wayedpoint.global_rotation.y
|
|
else:
|
|
turntoward = $CharacterArmature.global_rotation.y
|
|
current_waypoint += 1
|
|
if current_waypoint > route.get_child_count()-1:
|
|
current_waypoint = 0
|
|
if state == "pursue_walkto":
|
|
print("done!")
|
|
state = "suspicion_start"
|
|
$StateChangeTimer.start(state_change_cooldown)
|
|
print("player sighted! state changed to suspicion_start")
|
|
overheadstatus.set_3dots()
|
|
overheadstatus.set_timer(-1)
|
|
else:
|
|
stand_still()
|
|
|
|
if paused and state == "patrol":
|
|
# overheadstatus.set_timer(100-(($WaypointPauseTimer.time_left / $WaypointPauseTimer.wait_time)*100))
|
|
if $CharacterArmature.global_rotation.y != turntoward:
|
|
lerp_toward(turntoward)
|
|
|
|
move_and_slide()
|
|
animate()
|
|
|
|
func animate():
|
|
if velocity.length() > 0:
|
|
$AnimationPlayer.play("Walk")
|
|
elif !paused:
|
|
$AnimationPlayer.play("Idle_Neutral")
|
|
|
|
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
|
|
|
|
lerp_toward(atan2(velocity.x, velocity.z))
|
|
|
|
func stand_still():
|
|
velocity.x = 0
|
|
velocity.z = 0
|
|
|
|
func process_alerts():
|
|
var seeingplayer : bool
|
|
|
|
if state == "patrol":
|
|
seeingplayer = checksight()
|
|
if seeingplayer:
|
|
state = "suspicion_start"
|
|
$StateChangeTimer.start(state_change_cooldown)
|
|
print("player sighted! state changed to suspicion_start")
|
|
return
|
|
|
|
if state == "suspicion":
|
|
seeingplayer = checksight()
|
|
|
|
if !seeingplayer and $StateChangeTimer.is_stopped():
|
|
$StateChangeTimer.start(suspicion_decay_time)
|
|
elif seeingplayer:
|
|
if !$StateChangeTimer.is_stopped(): $StateChangeTimer.stop()
|
|
overheadstatus.set_questionmark()
|
|
var percent : float = 100 - (($AlertEscalationTimer.time_left / alert_escalation_time) * 100)
|
|
overheadstatus.set_timer(percent)
|
|
if velocity.length() == 0: lerp_toward(turntoward)
|
|
|
|
if state == "pursue_walkto":
|
|
seeingplayer = checksight()
|
|
if seeingplayer:
|
|
print("update path?")
|
|
if !current_path.is_empty() and (current_path[current_path.size()-1].distance_to(lastseenpos) > 1):
|
|
setpath(lastseenpos)
|
|
print("updated path!")
|
|
|
|
func nextwaypoint():
|
|
var waypoint = route.get_children()[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
|
|
)
|
|
|
|
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":
|
|
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().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))
|
|
$CharacterArmature/SightCollider/SightRay.target_position = top
|
|
$CharacterArmature/SightCollider/SightRay.global_rotation = Vector3(0,0,0)
|
|
$CharacterArmature/SightCollider/SightRay.force_raycast_update()
|
|
if $CharacterArmature/SightCollider/SightRay.is_colliding():
|
|
if $CharacterArmature/SightCollider/SightRay.get_collider().get_name() == "PlayerVisibilityShape":
|
|
turntoward = (Vector2(target.global_transform.origin.z, target.global_transform.origin.x) - Vector2($CharacterArmature.global_transform.origin.z, $CharacterArmature.global_transform.origin.x)).angle()
|
|
lastseenpos = target.global_transform.origin
|
|
return true
|
|
|
|
return false
|
|
|
|
func lerp_toward(angle: float):
|
|
$CharacterArmature.global_rotation.y = lerp_angle($CharacterArmature.global_rotation.y, angle, 0.15)
|
|
|
|
|
|
func _on_state_change_timer_timeout() -> void:
|
|
$StateChangeTimer.stop()
|
|
$AlertEscalationTimer.stop()
|
|
|
|
if state == "suspicion_start":
|
|
state = "suspicion"
|
|
$AlertEscalationTimer.start(alert_escalation_time)
|
|
return
|
|
|
|
if state == "suspicion":
|
|
state = "patrol"
|
|
overheadstatus.set_3dots()
|
|
overheadstatus.set_timer(-1)
|
|
return
|
|
|
|
if state == "pursue_start":
|
|
setpath(lastseenpos)
|
|
state = "pursue_walkto"
|
|
return
|
|
|
|
|
|
func _on_alert_escalation_timer_timeout() -> void:
|
|
$AlertEscalationTimer.stop()
|
|
if state == "suspicion":
|
|
state = "pursue_start"
|
|
$StateChangeTimer.start(state_change_cooldown)
|
|
overheadstatus.set_timer(-1)
|
|
overheadstatus.set_exclamationmark()
|
|
|
|
|
|
func _on_waypoint_pause_timer_timeout() -> void:
|
|
$WaypointPauseTimer.stop()
|
|
if paused:
|
|
paused = false
|