188 lines
7.1 KiB
GDScript3
188 lines
7.1 KiB
GDScript3
|
|
extends SceneTree
|
||
|
|
|
||
|
|
# Headless gameplay test: run with
|
||
|
|
# godot --headless -s res://tests/test_fire_stick.gd
|
||
|
|
# The fleet's guns used to pick their own targets and fire on their own, so
|
||
|
|
# combat happened whether or not the player did anything. Firing is now an
|
||
|
|
# action: the right-hand stick aims it, and nothing else fires it.
|
||
|
|
#
|
||
|
|
# Covers:
|
||
|
|
# A. an idle stick means an idle gun - an enemy parked well inside fire
|
||
|
|
# range takes no shots at all. This is the behaviour that CHANGED, so it
|
||
|
|
# is checked against a target the old auto-aim would certainly have hit
|
||
|
|
# B. pushing the stick fires, and the shots travel along the stick's
|
||
|
|
# heading rather than at whatever happens to be nearest
|
||
|
|
# C. releasing the stick stops the gun
|
||
|
|
# D. the stick claims only touches inside its own rect, so the right half
|
||
|
|
# of the screen does not swallow pinches and taps meant for the world
|
||
|
|
# E. the dotted aim guide is drawn only while aiming, and reaches exactly as
|
||
|
|
# far as the guns actually shoot - a guide that outruns the gun would be
|
||
|
|
# worse than none
|
||
|
|
|
||
|
|
const FIRE_DIRECTION := Vector2(1.0, 0.0)
|
||
|
|
|
||
|
|
var game_state: Node
|
||
|
|
var main: Node
|
||
|
|
var fleet: Node
|
||
|
|
var fire_stick: Control
|
||
|
|
var controller: Node
|
||
|
|
var decoy: Node
|
||
|
|
var stage: int = 0
|
||
|
|
var elapsed: float = 0.0
|
||
|
|
var shots_while_idle: int = 0
|
||
|
|
|
||
|
|
|
||
|
|
func _initialize() -> void:
|
||
|
|
game_state = root.get_node("GameState")
|
||
|
|
game_state.buildings.clear()
|
||
|
|
game_state.extractors.clear()
|
||
|
|
game_state.reset_progression()
|
||
|
|
main = load("res://Main.tscn").instantiate()
|
||
|
|
root.add_child(main)
|
||
|
|
current_scene = main
|
||
|
|
fleet = main.get_node("Fleet")
|
||
|
|
fire_stick = main.get_node("UI/FireStick")
|
||
|
|
controller = main.get_node("FireStickController")
|
||
|
|
|
||
|
|
|
||
|
|
func _projectile_count() -> int:
|
||
|
|
return get_nodes_in_group("player_projectiles").size()
|
||
|
|
|
||
|
|
|
||
|
|
func _push_stick(direction: Vector2) -> void:
|
||
|
|
var centre: Vector2 = fire_stick.global_position + fire_stick.size * 0.5
|
||
|
|
var press := InputEventScreenTouch.new()
|
||
|
|
press.index = 3
|
||
|
|
press.pressed = true
|
||
|
|
press.position = centre
|
||
|
|
if not fire_stick.handle_touch(press):
|
||
|
|
push_error("the fire stick refused a touch at its own centre")
|
||
|
|
var drag := InputEventScreenDrag.new()
|
||
|
|
drag.index = 3
|
||
|
|
drag.position = centre + direction.normalized() * fire_stick.max_radius
|
||
|
|
fire_stick.handle_drag(drag)
|
||
|
|
|
||
|
|
|
||
|
|
func _release_stick() -> void:
|
||
|
|
var release := InputEventScreenTouch.new()
|
||
|
|
release.index = 3
|
||
|
|
release.pressed = false
|
||
|
|
release.position = fire_stick.global_position + fire_stick.size * 0.5
|
||
|
|
fire_stick.handle_touch(release)
|
||
|
|
|
||
|
|
|
||
|
|
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)
|
||
|
|
for hazard in get_nodes_in_group("asteroids") + get_nodes_in_group("enemies"):
|
||
|
|
if is_instance_valid(hazard):
|
||
|
|
hazard.queue_free()
|
||
|
|
for projectile in get_nodes_in_group("player_projectiles"):
|
||
|
|
projectile.free()
|
||
|
|
|
||
|
|
fleet.spawn_ships(1, Vector2.ZERO, PackedStringArray([GameState.VESSEL_INTERCEPTOR]))
|
||
|
|
var ship: Node2D = fleet.ships[0]
|
||
|
|
var weapon: Node = ship.get_component("weapon")
|
||
|
|
if weapon == null:
|
||
|
|
return _fail("the default vessel has no weapon component to test")
|
||
|
|
# Parked at a third of fire range, dead ahead: the old auto-aiming
|
||
|
|
# gun would have opened up on this immediately.
|
||
|
|
decoy = load("res://Enemy.tscn").instantiate()
|
||
|
|
decoy.position = ship.global_position + Vector2(weapon.fire_range * 0.33, 0.0)
|
||
|
|
main.add_child(decoy)
|
||
|
|
decoy.speed = 0.0
|
||
|
|
print("a target sits %.0f away, well inside the gun's %.0f range" \
|
||
|
|
% [ship.global_position.distance_to(decoy.position), weapon.fire_range])
|
||
|
|
stage = 1
|
||
|
|
elapsed = 0.0
|
||
|
|
return false
|
||
|
|
1:
|
||
|
|
# --- A. idle stick, idle gun -----------------------------------
|
||
|
|
shots_while_idle = maxi(shots_while_idle, _projectile_count())
|
||
|
|
if elapsed < 1.5:
|
||
|
|
return false
|
||
|
|
if shots_while_idle != 0:
|
||
|
|
return _fail("the fleet fired %d shot(s) with the stick untouched - the gun is still aiming itself" \
|
||
|
|
% shots_while_idle)
|
||
|
|
print("1.5s with the stick untouched: no shots fired")
|
||
|
|
|
||
|
|
_push_stick(FIRE_DIRECTION)
|
||
|
|
if fire_stick.direction.normalized().distance_to(FIRE_DIRECTION) > 0.01:
|
||
|
|
return _fail("pushing the stick reported direction %s, expected %s" \
|
||
|
|
% [fire_stick.direction, FIRE_DIRECTION])
|
||
|
|
stage = 2
|
||
|
|
elapsed = 0.0
|
||
|
|
return false
|
||
|
|
2:
|
||
|
|
# --- B. it fires, along the stick ------------------------------
|
||
|
|
if elapsed < 0.6:
|
||
|
|
return false
|
||
|
|
var projectiles: Array = get_nodes_in_group("player_projectiles")
|
||
|
|
if projectiles.is_empty():
|
||
|
|
return _fail("pushing the fire stick did not fire anything")
|
||
|
|
for projectile in projectiles:
|
||
|
|
var heading: Vector2 = projectile.velocity.normalized()
|
||
|
|
if heading.distance_to(FIRE_DIRECTION) > 0.05:
|
||
|
|
return _fail("a shot travelled along %s, but the stick was pushed %s" \
|
||
|
|
% [heading, FIRE_DIRECTION])
|
||
|
|
print("%d shot(s) fired, every one along the stick's heading" % projectiles.size())
|
||
|
|
|
||
|
|
# --- E. the aim guide ------------------------------------------
|
||
|
|
if controller.aim_direction == Vector2.ZERO:
|
||
|
|
return _fail("the aim guide has no heading while the stick is held")
|
||
|
|
if controller.aim_direction.normalized().distance_to(FIRE_DIRECTION) > 0.05:
|
||
|
|
return _fail("the guide points along %s but the stick was pushed %s" \
|
||
|
|
% [controller.aim_direction.normalized(), FIRE_DIRECTION])
|
||
|
|
var weapon: Node = fleet.ships[0].get_component("weapon")
|
||
|
|
if not is_equal_approx(controller.guide_range(), weapon.fire_range):
|
||
|
|
return _fail("the guide reaches %.0f but the gun shoots %.0f" \
|
||
|
|
% [controller.guide_range(), weapon.fire_range])
|
||
|
|
print("aim guide drawn along the stick, reaching the gun's own %.0f range" % weapon.fire_range)
|
||
|
|
|
||
|
|
_release_stick()
|
||
|
|
if fire_stick.direction != Vector2.ZERO:
|
||
|
|
return _fail("releasing the stick left it reporting %s" % fire_stick.direction)
|
||
|
|
# One frame for the controller to push the release through to the
|
||
|
|
# weapons, then clear the sky so what follows is unambiguous.
|
||
|
|
controller._process(0.0)
|
||
|
|
for projectile in get_nodes_in_group("player_projectiles"):
|
||
|
|
projectile.free()
|
||
|
|
stage = 3
|
||
|
|
elapsed = 0.0
|
||
|
|
return false
|
||
|
|
3:
|
||
|
|
# --- C. released means stopped ---------------------------------
|
||
|
|
if _projectile_count() != 0:
|
||
|
|
return _fail("the gun kept firing after the stick was released")
|
||
|
|
if elapsed < 1.2:
|
||
|
|
return false
|
||
|
|
if controller.is_firing():
|
||
|
|
return _fail("a weapon still reports itself firing after release")
|
||
|
|
if controller.aim_direction != Vector2.ZERO:
|
||
|
|
return _fail("the aim guide is still drawn after the stick was released")
|
||
|
|
print("1.2s after release: nothing fired")
|
||
|
|
|
||
|
|
# --- D. the stick claims only its own rect ---------------------
|
||
|
|
var outside := InputEventScreenTouch.new()
|
||
|
|
outside.index = 5
|
||
|
|
outside.pressed = true
|
||
|
|
outside.position = fire_stick.global_position - Vector2(40.0, 40.0)
|
||
|
|
if fire_stick.handle_touch(outside):
|
||
|
|
return _fail("the fire stick claimed a touch outside its own rect, which would swallow world gestures")
|
||
|
|
print("a touch outside the stick's rect falls through")
|
||
|
|
|
||
|
|
print("TEST_FIRE_STICK: PASS")
|
||
|
|
quit(0)
|
||
|
|
return true
|
||
|
|
return false
|
||
|
|
|
||
|
|
|
||
|
|
func _fail(message: String) -> bool:
|
||
|
|
print("TEST_FIRE_STICK: FAIL (%s)" % message)
|
||
|
|
quit(1)
|
||
|
|
return true
|