232 lines
10 KiB
GDScript
232 lines
10 KiB
GDScript
extends SceneTree
|
|
|
|
# Headless gameplay test: run with
|
|
# godot --headless -s res://tests/test_aim_converges.gd
|
|
# Forgejo #172: "when aiming, whether with mouse or touch controls, we should
|
|
# aim for a given point on the space (still using mouse or the joystick). As
|
|
# it's now a point and not a direction, it lets the player try to get the
|
|
# fleet's shot converge and not go in parallel. The auto aim when trying to
|
|
# aim for an enemy vessel should still work."
|
|
#
|
|
# The failure this replaces is worth stating, because it is invisible in a
|
|
# fleet of one and gets worse with every hull added: every gun was handed the
|
|
# SAME direction, so a fleet firing at something threw a sheet of parallel
|
|
# bolts and only the ship that happened to be lined up could hit. The wider
|
|
# the formation, the further the fire missed by.
|
|
#
|
|
# Covers:
|
|
# A. two hulls either side of a target aim at IT, not along one shared
|
|
# heading - and each one's line actually arrives at the point
|
|
# B. the mouse hands over a place: the cursor's world position, distance
|
|
# included, rather than a bearing from the fleet's centre
|
|
# C. stick displacement controls convergence distance: a partial push aims
|
|
# nearby and a full push reaches the guns' maximum range
|
|
# D. the assist (#89) still bends a shot onto an enemy, now per hull and
|
|
# around each hull's own heading to the point
|
|
# E. releasing stops every gun in the fleet
|
|
# F. range belongs to the gun, not to the aim: a point past reach is fired
|
|
# toward rather than refused or clamped into a different heading
|
|
|
|
const WEAPON = preload("res://vessels/VesselWeaponComponent.gd")
|
|
|
|
var game_state: Node
|
|
var main: Node
|
|
var fleet: Node
|
|
var controller: Node
|
|
var mouse: Node
|
|
var stage: int = 0
|
|
var elapsed: float = 0.0
|
|
var left_ship: Node2D
|
|
var right_ship: Node2D
|
|
|
|
|
|
func _initialize() -> void:
|
|
game_state = root.get_node("GameState")
|
|
game_state.buildings.clear()
|
|
game_state.extractors.clear()
|
|
game_state.reset_progression()
|
|
game_state.intro_active = false
|
|
game_state.reset_quests()
|
|
main = load("res://world/Main.tscn").instantiate()
|
|
root.add_child(main)
|
|
current_scene = main
|
|
fleet = main.get_node("Fleet")
|
|
controller = main.get_node("FireStickController")
|
|
mouse = main.get_node("MouseCombatController")
|
|
|
|
|
|
func _fail(message: String) -> bool:
|
|
print("TEST_AIM_CONVERGES: FAIL (%s)" % message)
|
|
quit(1)
|
|
return true
|
|
|
|
|
|
func _clear() -> void:
|
|
for node in get_nodes_in_group("enemies") + get_nodes_in_group("asteroids") \
|
|
+ get_nodes_in_group("player_projectiles"):
|
|
if is_instance_valid(node):
|
|
node.free()
|
|
|
|
|
|
func _weapon(ship: Node2D) -> Node:
|
|
return ship.get_component("weapon")
|
|
|
|
|
|
# Where a shot from this hull would arrive if it flew far enough: the hull,
|
|
# plus its own heading, times the distance to the aimed point. The whole of
|
|
# "converging" is that this comes out the same for every hull.
|
|
func _lands_at(ship: Node2D, point: Vector2) -> Vector2:
|
|
var weapon: Node = _weapon(ship)
|
|
return ship.global_position \
|
|
+ weapon.firing_heading() * ship.global_position.distance_to(point)
|
|
|
|
|
|
func _process(delta: float) -> bool:
|
|
elapsed += delta
|
|
match stage:
|
|
0:
|
|
if elapsed < 0.3:
|
|
return false
|
|
main.get_node("EnemySpawner").set_process(false)
|
|
main.get_node("AsteroidField").set_process(false)
|
|
_clear()
|
|
fleet.spawn_ships(2, Vector2.ZERO, PackedStringArray(
|
|
[GameState.VESSEL_INTERCEPTOR, GameState.VESSEL_INTERCEPTOR]))
|
|
if fleet.ships.size() != 2:
|
|
return _fail("wanted two hulls, got %d" % fleet.ships.size())
|
|
# Placed by hand, wide apart and level, so the geometry the test
|
|
# talks about is the geometry it set up rather than whatever the
|
|
# current default formation happens to be.
|
|
left_ship = fleet.ships[0]
|
|
right_ship = fleet.ships[1]
|
|
left_ship.global_position = Vector2(-400.0, 0.0)
|
|
right_ship.global_position = Vector2(400.0, 0.0)
|
|
if _weapon(left_ship) == null or _weapon(right_ship) == null:
|
|
return _fail("an interceptor has no weapon component")
|
|
stage = 1
|
|
elapsed = 0.0
|
|
return false
|
|
1:
|
|
if elapsed < 0.1:
|
|
return false
|
|
|
|
# --- A. two hulls, one point --------------------------------
|
|
var point := Vector2(0.0, -900.0)
|
|
controller.aim_at(point)
|
|
var left: Vector2 = _weapon(left_ship).firing_heading()
|
|
var right: Vector2 = _weapon(right_ship).firing_heading()
|
|
if left == Vector2.ZERO or right == Vector2.ZERO:
|
|
return _fail("aiming at a point left a gun with no heading")
|
|
if left.distance_to(right) < 0.1:
|
|
return _fail("both hulls fire along %s - that is a direction, not a point" % left)
|
|
for ship in [left_ship, right_ship]:
|
|
var lands: Vector2 = _lands_at(ship, point)
|
|
if lands.distance_to(point) > 1.0:
|
|
return _fail("a hull at %s fires at %s, %.0f from the point it was given" \
|
|
% [ship.global_position, lands, lands.distance_to(point)])
|
|
print("hulls %.0f apart fire on headings %.0f degrees apart, and both arrive at the point" \
|
|
% [left_ship.global_position.distance_to(right_ship.global_position),
|
|
rad_to_deg(left.angle_to(right))])
|
|
|
|
# --- B. the mouse hands over a place ------------------------
|
|
mouse.pointer_mode = mouse.PointerMode.ON
|
|
mouse.select_weapon(0)
|
|
# Deliberately NOT on the line from the fleet's centre through
|
|
# anything: a bearing-from-the-centre implementation would still
|
|
# pass a point straight ahead.
|
|
var cursor := Vector2(650.0, -300.0)
|
|
mouse.aim_world = cursor
|
|
mouse.firing = true
|
|
mouse._process(0.0)
|
|
controller._process(0.0)
|
|
for ship in [left_ship, right_ship]:
|
|
if _weapon(ship).fire_target.distance_to(cursor) > 0.5:
|
|
return _fail("the mouse aimed a hull at %s, the cursor was at %s" \
|
|
% [_weapon(ship).fire_target, cursor])
|
|
if _lands_at(left_ship, cursor).distance_to(_lands_at(right_ship, cursor)) > 1.0:
|
|
return _fail("the two hulls' fire does not meet at the cursor")
|
|
print("holding the button aims every hull at the cursor's own position")
|
|
|
|
# --- E. and releasing stops all of them ---------------------
|
|
mouse.firing = false
|
|
mouse._process(0.0)
|
|
controller._process(0.0)
|
|
for ship in [left_ship, right_ship]:
|
|
if _weapon(ship).is_firing():
|
|
return _fail("releasing the button left a gun firing")
|
|
print("releasing stops every gun")
|
|
|
|
# --- C. a stick still resolves to a point -------------------
|
|
var reach: float = controller.guide_range()
|
|
if reach <= 0.0:
|
|
return _fail("the fleet reports no gun range")
|
|
var pushed := Vector2(0.0, -1.0)
|
|
var resolved: Vector2 = controller.stick_point(pushed)
|
|
var wanted: Vector2 = fleet.get_centroid() + pushed * reach
|
|
if resolved.distance_to(wanted) > 1.0:
|
|
return _fail("a stick pushed up resolved to %s, expected %s" % [resolved, wanted])
|
|
controller.aim(pushed)
|
|
for ship in [left_ship, right_ship]:
|
|
if not _weapon(ship).is_firing():
|
|
return _fail("a pushed stick did not fire a gun")
|
|
if _weapon(ship).fire_target.distance_to(wanted) > 1.0:
|
|
return _fail("a pushed stick aimed a hull somewhere other than the point it names")
|
|
print("a pushed stick names the point at the guns' reach (%.0f) along it" % reach)
|
|
|
|
# A half push must keep its magnitude. TouchJoystick already reports
|
|
# displacement in the 0..1 range; normalizing it here used to turn
|
|
# every nonzero push into a maximum-range aim.
|
|
var partial := Vector2(0.0, -0.45)
|
|
var partial_point: Vector2 = controller.stick_point(partial)
|
|
var partial_wanted: Vector2 = fleet.get_centroid() + partial * reach
|
|
if partial_point.distance_to(partial_wanted) > 1.0:
|
|
return _fail("a 45%% stick push resolved to %s, expected nearby point %s" \
|
|
% [partial_point, partial_wanted])
|
|
controller.aim(partial)
|
|
for ship in [left_ship, right_ship]:
|
|
if _weapon(ship).fire_target.distance_to(partial_wanted) > 1.0:
|
|
return _fail("a partial stick push did not aim the fleet at its nearer point")
|
|
print("a 45%% stick push aims at 45%% range (%.0f), not maximum range" \
|
|
% fleet.get_centroid().distance_to(partial_point))
|
|
|
|
# --- F. reach is the gun's, not the aim's -------------------
|
|
var far_point: Vector2 = fleet.get_centroid() + Vector2(0.0, -reach * 3.0)
|
|
controller.aim_at(far_point)
|
|
var to_far: Vector2 = (far_point - left_ship.global_position).normalized()
|
|
if _weapon(left_ship).firing_heading().distance_to(to_far) > 0.01:
|
|
return _fail("a point past the guns' reach bent the heading to %s, expected %s" \
|
|
% [_weapon(left_ship).firing_heading(), to_far])
|
|
print("a point beyond reach is still fired toward - the bolt falls short, the aim does not bend")
|
|
|
|
# --- D. the assist still works, per hull --------------------
|
|
# Each hull asks its own question now - "is anything within MY
|
|
# reach, near MY heading to the point" - so the case worth
|
|
# checking is one where the two hulls answer differently. The
|
|
# right hull is moved out past its own gun range of the enemy, so
|
|
# it cannot be assisted onto it however the angles fall.
|
|
_clear()
|
|
right_ship.global_position = Vector2(reach * 1.5, 0.0)
|
|
var target_point := Vector2(0.0, -300.0)
|
|
var enemy: Node2D = load("res://enemies/Enemy.tscn").instantiate()
|
|
enemy.global_position = left_ship.global_position \
|
|
+ (target_point - left_ship.global_position).normalized().rotated(
|
|
deg_to_rad(15.0)) * 150.0
|
|
main.add_child(enemy)
|
|
enemy.speed = 0.0
|
|
if right_ship.global_position.distance_to(enemy.global_position) <= reach:
|
|
return _fail("the test failed to put the far hull out of range of the enemy")
|
|
controller.aim_at(target_point)
|
|
var to_enemy: Vector2 = (enemy.global_position - left_ship.global_position).normalized()
|
|
if _weapon(left_ship).firing_heading().distance_to(to_enemy) > 0.02:
|
|
return _fail("the assist no longer bends a shot onto an enemy near the aim (heading %s, enemy %s)" \
|
|
% [_weapon(left_ship).firing_heading(), to_enemy])
|
|
var right_wanted: Vector2 = (target_point - right_ship.global_position).normalized()
|
|
if _weapon(right_ship).firing_heading().distance_to(right_wanted) > 0.02:
|
|
return _fail("a hull out of range of the enemy was still bent onto it - the assist is not per hull")
|
|
print("the assist still snaps onto an enemy near the aim, and only for the hull that can reach it")
|
|
enemy.free()
|
|
|
|
print("TEST_AIM_CONVERGES: PASS")
|
|
quit(0)
|
|
return true
|
|
return false
|