spaceship/tests/test_shot_and_vfx_weight.gd

153 lines
6 KiB
GDScript3
Raw Normal View History

extends SceneTree
# Headless test: run with
# godot --headless -s res://tests/test_shot_and_vfx_weight.gd
# Forgejo #155 ("default vessel shots should go faster and further") and #152
# ("add a vfx for dead enemies and for exploded rock and for resource
# collected on fleet").
#
# The #152 finding is worth recording: all three effects already existed and
# were already wired. What had happened is that #130 gave a lost VESSEL
# thirty particles and a shockwave, which left every other event in the game
# visibly smaller than it - a kill, the thing the player is rewarded for,
# was a twelve-particle puff beside it. So this is about PROPORTION, and
# proportion is what is checked: numbers get rebalanced, an ordering that
# says a rock outweighs a battleship is a bug.
#
# Covers:
# A. the default gun outranges the enemies that stand off from it
# B. a shot crosses that range quickly enough to read as hitting something
# C. a shot's own lifetime is derived from its range, so faster does not
# mean falling short
# D. every one of #152's three events actually produces something
# E. and they are in proportion - a lost vessel is the biggest, a rock is
# not a puff, and nothing is on the base class's defaults
const ENEMY_STANDOFF = preload("res://enemies/EnemyStandoffComponent.gd")
const EXPLOSION_BASE = preload("res://vfx/ExplosionVFXBase.gd")
var game_state: Node
var scene: Node
var fleet: Node2D
var stage: int = 0
var elapsed: float = 0.0
func _initialize() -> void:
# GameState is an autoload: a bare GameState.<instance member> does not
# compile in a SceneTree script, so the node is fetched by name instead.
game_state = root.get_node("GameState")
game_state.reset_new_game()
game_state.intro_active = false
game_state.reset_quests()
func _fail(message: String) -> bool:
print("TEST_SHOT_AND_VFX_WEIGHT: FAIL (%s)" % message)
quit(1)
return true
func _count(of_class) -> int:
var n: int = 0
for node in root.get_children():
if is_instance_of(node, of_class):
n += 1
return n
func _process(delta: float) -> bool:
elapsed += delta
match stage:
0:
# --- A, B, C. the shot ----------------------------------
var range_units: float = game_state.ship_fire_range(GameState.VESSEL_INTERCEPTOR)
var shot: Node2D = load("res://vessels/Projectile.tscn").instantiate()
var speed: float = shot.speed
shot.set_range(range_units)
var flight: float = shot.lifetime
var standoff: float = ENEMY_STANDOFF.new().preferred_range
if range_units <= standoff:
return _fail("the default gun reaches %.0f and enemies stand off at %.0f - it is outranged" \
% [range_units, standoff])
if flight > 0.9:
return _fail("a shot takes %.2fs to cross its own range - that reads as leaving, not hitting" \
% flight)
if absf(flight - range_units / speed) > 0.001:
return _fail("lifetime %.3fs does not match range %.0f at speed %.0f - shots would fall short or overshoot" \
% [flight, range_units, speed])
print("default gun: %.0f units at %.0f/s, crossing its range in %.2fs (enemies stand off at %.0f)" \
% [range_units, speed, flight, standoff])
shot.free()
scene = load("res://world/Main.tscn").instantiate()
root.add_child(scene)
current_scene = scene
fleet = scene.get_node("Fleet")
elapsed = 0.0
stage = 1
return false
1:
if elapsed < 0.4:
return false
scene.get_node("AsteroidField").set_process(false)
scene.get_node("EnemySpawner").set_process(false)
for hazard in get_nodes_in_group("asteroids") + get_nodes_in_group("enemies"):
hazard.free()
# --- D. an enemy dying is seen --------------------------
var enemy: Node2D = load("res://enemies/Enemy.tscn").instantiate()
enemy.enemy_id = "chaser"
enemy.position = Vector2(600.0, 0.0)
scene.add_child(enemy)
var before: int = _count(EnemyExplosionVFX)
enemy.take_damage(999)
if _count(EnemyExplosionVFX) <= before:
return _fail("an enemy died with nothing to show for it")
# --- ...a rock breaking up ------------------------------
var rock: Node2D = load("res://world/Asteroid.tscn").instantiate()
rock.position = Vector2(-600.0, 0.0)
scene.add_child(rock)
var rocks_before: int = _count(AsteroidExplosionVFX)
rock.shatter(false)
if _count(AsteroidExplosionVFX) <= rocks_before:
return _fail("a rock broke up with nothing to show for it")
# --- ...and ore reaching the hold -----------------------
var pickup_script: GDScript = load("res://vfx/ResourcePickupVFX.gd")
var pickups_before: int = _count(pickup_script)
pickup_script.spawn(root, Vector2.ZERO, GameState.RESOURCE_ORE, 1)
if _count(pickup_script) <= pickups_before:
return _fail("collecting ore showed nothing")
print("all three events produce an effect")
# --- E. and they are in proportion ----------------------
var base: Node2D = EXPLOSION_BASE.new()
var ship: Node2D = ShipExplosionVFX.new()
var kill: Node2D = EnemyExplosionVFX.new()
var stone: Node2D = AsteroidExplosionVFX.new()
for pair in [["a lost vessel", ship], ["an enemy kill", kill], ["a broken rock", stone]]:
var vfx: Node2D = pair[1]
if vfx.particle_count <= base.particle_count:
return _fail("%s is still on the base class's %d particles" \
% [pair[0], base.particle_count])
if vfx.shockwave_radius <= 0.0:
return _fail("%s has no shockwave behind it" % pair[0])
if ship.particle_count < kill.particle_count:
return _fail("losing a vessel (%d) is smaller than killing an enemy (%d)" \
% [ship.particle_count, kill.particle_count])
if kill.particle_count < stone.particle_count:
return _fail("killing an enemy (%d) is smaller than breaking a rock (%d)" \
% [kill.particle_count, stone.particle_count])
print("particles: vessel %d > enemy %d > rock %d > base default %d" \
% [ship.particle_count, kill.particle_count, stone.particle_count,
base.particle_count])
for vfx in [base, ship, kill, stone]:
vfx.free()
print("TEST_SHOT_AND_VFX_WEIGHT: PASS")
quit(0)
return true
return false