Randoloot/tests/abilities/missile_guidance_test.gd
2026-10-15 22:22:52 +02:00

73 lines
2.7 KiB
GDScript

extends Node2D
const PROJECTILE := preload("res://scenes/game/weapon_projectile.tscn")
func _ready() -> void:
seed(1234)
# Both stationary and crossing targets should be intercepted on the first pass.
for target_velocity in [Vector2.ZERO, Vector2(0, 55)]:
var target := CharacterBody2D.new()
target.position = Vector2(350, 70)
target.velocity = target_velocity
add_child(target)
var missile := _missile(target, 1200.0)
var closest := INF
for step in 240:
var previous: Vector2 = missile.position
target.position += target_velocity / 120.0
missile._physics_process(1.0 / 120.0)
var near := Geometry2D.get_closest_point_to_segment(target.position, previous, missile.position)
closest = minf(closest, near.distance_to(target.position))
assert(absf(missile._turn_velocity) <= missile.homing_max_turn_speed + 0.001)
if closest < 18.0 or missile.is_queued_for_deletion():
break
assert(closest < 18.0, "Missile missed a reachable intercept: %.2f" % closest)
missile.free()
target.free()
# Compare actual flight direction against the previous turn limits.
var side_target := Node2D.new()
side_target.position = Vector2(100, 200)
add_child(side_target)
var faster := _missile(side_target, 600.0)
var previous := _missile(side_target, 600.0)
previous.homing_turn_acceleration = 2.3
previous.homing_max_turn_speed = 1.7
previous.missile_steering_acceleration = 520.0
for step in 48:
faster._physics_process(1.0 / 120.0)
previous._physics_process(1.0 / 120.0)
assert(faster._heading > previous._heading * 1.5)
assert(faster._flight_velocity.angle() > previous._flight_velocity.angle() * 1.5)
assert(faster.homing_target == side_target)
faster.free()
previous.free()
side_target.free()
# Preserve the original pursuit behavior beyond acquisition range.
var far_target := Node2D.new()
far_target.position = Vector2(5000, 0)
far_target.add_to_group("enemies")
add_child(far_target)
var limited := _missile(far_target, 200.0)
limited._has_locked_target = true
for step in 120:
limited._physics_process(1.0 / 120.0)
if limited.is_queued_for_deletion():
break
assert(not limited.is_queued_for_deletion() and limited.homing_target == far_target)
assert(limited._has_locked_target)
limited.free()
far_target.free()
print("MISSILE_GUIDANCE_TEST: PASS (stationary and moving intercepts, faster heading and actual flight turn, original target pursuit)")
get_tree().quit()
func _missile(target: Node2D, flight_range: float) -> Area2D:
var missile := PROJECTILE.instantiate() as Area2D
missile.configure(Vector2.ZERO, Vector2.RIGHT, ItemDefinition.WeaponKind.MISSILE, 400.0, 10.0, flight_range, target)
missile.collision_mask = 0
add_child(missile)
missile.set_physics_process(false)
missile.set_process(false)
return missile