grunk/src/world/grunk_beast/grunk_beast.gd

126 lines
2.9 KiB
GDScript3
Raw Normal View History

2025-04-16 17:20:33 -06:00
class_name GrunkBeast extends CharacterBody3D
## Grunk beast controller
2025-04-17 10:02:01 -06:00
#region Constants
2025-04-18 09:53:21 -06:00
const STALKING_SOUND_LIMIT := 25.0
2025-04-17 10:02:01 -06:00
#endregion
#region Exported Properties
2025-08-30 18:42:35 -06:00
@export var base_speed := 80.0
@export var pursuit_speed := 360.0
@export var debug_destroy: bool:
set(value):
queue_free()
2025-04-17 10:02:01 -06:00
#endregion
#region Member Variables
2025-04-16 17:20:33 -06:00
var gravity: Vector3 = (
ProjectSettings.get_setting("physics/3d/default_gravity")
* ProjectSettings.get_setting("physics/3d/default_gravity_vector")
)
2025-04-17 10:02:01 -06:00
var pathfinding := true
2025-04-18 09:53:21 -06:00
var traversing_link := false
2025-08-30 18:42:35 -06:00
@onready var model: BeastModel = %Shambler
2025-04-18 09:53:21 -06:00
2025-04-16 17:20:33 -06:00
@onready var nav_agent: NavigationAgent3D = %NavAgent
2025-04-17 13:41:06 -06:00
@onready var nav_probe: NavigationAgent3D = %NavProbe
@onready var stalking_timer: Timer = %StalkingTimer
2025-04-16 17:20:33 -06:00
2025-04-17 13:41:06 -06:00
@onready var blackboard: Blackboard = %Blackboard
2025-04-17 10:02:01 -06:00
2025-04-17 13:41:06 -06:00
#endregion
2025-04-16 17:20:33 -06:00
2025-04-17 13:41:06 -06:00
#region Character Controller
2025-04-17 10:02:01 -06:00
2025-04-16 17:20:33 -06:00
2025-04-17 13:41:06 -06:00
func is_pursuing() -> bool:
return blackboard.has_value("pursuit_target")
2025-04-16 17:20:33 -06:00
2025-04-17 10:02:01 -06:00
2025-04-17 13:41:06 -06:00
func is_stalking() -> bool:
return false # TODO
2025-04-17 10:02:01 -06:00
2025-04-17 13:41:06 -06:00
func get_speed() -> float:
if is_pursuing():
return pursuit_speed
return base_speed
2025-04-17 10:02:01 -06:00
2025-04-17 13:41:06 -06:00
func path_shorter_than(target: Vector3, limit: float) -> bool:
2025-04-17 16:26:41 -06:00
var length := 0.0
2025-04-17 10:02:01 -06:00
var last_pos := global_position
2025-04-17 13:41:06 -06:00
nav_probe.target_position = target
2025-04-17 16:26:41 -06:00
# Fail early if the target is unreachable.
# NOTE: this call also forces a navigation path refresh! Do not remove!
if not nav_probe.is_target_reachable():
return false
var path := nav_probe.get_current_navigation_path().slice(
2025-04-17 13:41:06 -06:00
nav_probe.get_current_navigation_path_index()
2025-04-17 16:26:41 -06:00
)
if not path:
# Shouldn't be possible, but if it is it would cause problems if we didn't fail here
print_debug("Target is reachable but has no path (tell the developer!)")
return false
# Integrate along path
for waypoint: Vector3 in path:
length += last_pos.distance_to(waypoint)
if length > limit:
2025-04-17 10:02:01 -06:00
return false
last_pos = waypoint
return true
## Clear this beast's pursuit and stalking targets
func clear_aggro() -> void:
blackboard.erase_value("pursuit_target")
blackboard.erase_value("stalking_target")
2025-04-17 13:41:06 -06:00
func _physics_process(delta: float) -> void:
var motion := Vector3.ZERO
2025-04-17 10:02:01 -06:00
2025-04-17 17:06:07 -06:00
if pathfinding and not nav_agent.is_navigation_finished():
2025-04-17 13:41:06 -06:00
var path_pos := nav_agent.get_next_path_position()
var relative_pos := path_pos - global_position
motion = relative_pos.normalized() * get_speed() * delta
2025-04-17 10:02:01 -06:00
2025-04-17 13:41:06 -06:00
velocity.x = motion.x
velocity.z = motion.z
2025-04-17 10:02:01 -06:00
2025-04-17 13:41:06 -06:00
if not is_on_floor():
velocity += gravity * delta
2025-04-17 10:02:01 -06:00
2025-04-18 09:53:21 -06:00
if motion:
model.set_target_rotation(atan2(motion.x, motion.z))
2025-08-30 18:42:35 -06:00
model.set_move_speed(velocity.length())
2025-04-17 13:41:06 -06:00
move_and_slide()
2025-04-17 10:02:01 -06:00
func on_sound_detected(source: Vector3) -> void:
# Check that the source isn't too far away, e.g. a sound from another room
2025-04-17 13:41:06 -06:00
if path_shorter_than(source, STALKING_SOUND_LIMIT):
blackboard.set_value("stalking_target", source)
stalking_timer.start()
2025-04-17 10:02:01 -06:00
2025-04-18 09:53:21 -06:00
2025-04-17 10:02:01 -06:00
#endregion
2025-04-18 09:53:21 -06:00
func _on_link_reached(_details: Dictionary) -> void:
traversing_link = true
func _on_waypoint_reached(_details: Dictionary) -> void:
traversing_link = false