66 lines
1.9 KiB
GDScript
66 lines
1.9 KiB
GDScript
class_name AIController
|
|
extends Node
|
|
|
|
@export var standoff_distance := 30.0 # throttle down inside this
|
|
@export var fire_range := 120.0 # open fire inside this
|
|
@export var full_rudder_angle_deg := 20.0 # bearing error at which rudder saturates
|
|
|
|
var _boat: Boat
|
|
var _target: Boat # node reference — shift-safe; never store a position
|
|
|
|
|
|
func _ready() -> void:
|
|
_boat = get_parent() as Boat
|
|
|
|
|
|
func _physics_process(_delta: float) -> void:
|
|
if _boat == null:
|
|
return
|
|
if _player_has_boat():
|
|
return # player possessed us — write nothing, resume when they leave
|
|
|
|
if not is_instance_valid(_target):
|
|
_target = _pick_target()
|
|
if _target == null:
|
|
_boat.desired_throttle = 0.0
|
|
_boat.steer_input = 0.0
|
|
_boat.aim_point = null # stow turrets, per the aim contract
|
|
_boat.fire_intents[0] = false
|
|
return
|
|
|
|
# All geometry computed fresh this tick — origin-shift safe.
|
|
var to_target: Vector3 = _target.global_position - _boat.global_position
|
|
to_target.y = 0.0
|
|
var distance := to_target.length()
|
|
|
|
var forward := -_boat.global_basis.z
|
|
forward.y = 0.0
|
|
|
|
var bearing_error := forward.signed_angle_to(to_target, Vector3.UP)
|
|
_boat.steer_input = clampf(
|
|
bearing_error / deg_to_rad(full_rudder_angle_deg), -1.0, 1.0)
|
|
|
|
_boat.desired_throttle = 1.0 if distance > standoff_distance else 0.2
|
|
|
|
_boat.aim_point = _target.global_position
|
|
_boat.fire_intents[0] = distance < fire_range
|
|
|
|
|
|
func _player_has_boat() -> bool:
|
|
var pc := get_tree().get_first_node_in_group(&"player_controllers") as PlayerController
|
|
return pc != null and pc.current_controlled == _boat
|
|
|
|
|
|
func _pick_target() -> Boat:
|
|
var best: Boat = null
|
|
var best_d := INF
|
|
for node in get_tree().get_nodes_in_group(&"boats"):
|
|
var other := node as Boat
|
|
if other == null or other == _boat:
|
|
continue
|
|
var d := _boat.global_position.distance_squared_to(other.global_position)
|
|
if d < best_d:
|
|
best_d = d
|
|
best = other
|
|
return best
|