153 lines
6 KiB
GDScript3
153 lines
6 KiB
GDScript3
|
|
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
|