135 lines
6 KiB
GDScript3
135 lines
6 KiB
GDScript3
|
|
extends SceneTree
|
||
|
|
|
||
|
|
# Headless gameplay test: run with
|
||
|
|
# godot --headless -s res://tests/test_distorder_stacking.gd
|
||
|
|
# Each space-time distorder vessel PAST THE FIRST raises the fleet's speed
|
||
|
|
# ceiling by 1.0, so a second one is worth crewing rather than dead weight.
|
||
|
|
#
|
||
|
|
# Covers:
|
||
|
|
# A. one vessel gives the original ceiling, so nothing changed for a fleet
|
||
|
|
# that carries a single distorder
|
||
|
|
# B. each additional vessel adds exactly 1.0
|
||
|
|
# C. the fader's full travel reaches the WIDENED ceiling, not the old one
|
||
|
|
# D. losing a vessel mid-boost pulls the fleet back down to the new
|
||
|
|
# ceiling rather than leaving it faster than its crew can sustain
|
||
|
|
# E. full boost still costs the same energy per second however wide the
|
||
|
|
# ceiling is - more vessels buy more speed, not cheaper speed
|
||
|
|
|
||
|
|
var game_state: Node
|
||
|
|
var main: Node
|
||
|
|
var fleet: Node
|
||
|
|
var fader: Node
|
||
|
|
var stage: int = 0
|
||
|
|
var elapsed: float = 0.0
|
||
|
|
|
||
|
|
|
||
|
|
func _initialize() -> void:
|
||
|
|
game_state = root.get_node("GameState")
|
||
|
|
game_state.reset_progression()
|
||
|
|
game_state.vessel_levels["unlock_distorder"] = 1
|
||
|
|
game_state.deposit_fleet_resource(GameState.RESOURCE_ENERGY, 500)
|
||
|
|
main = load("res://Main.tscn").instantiate()
|
||
|
|
root.add_child(main)
|
||
|
|
fleet = main.get_node("Fleet")
|
||
|
|
fader = main.get_node("UI/SpeedFader")
|
||
|
|
|
||
|
|
|
||
|
|
func _process(delta: float) -> bool:
|
||
|
|
elapsed += delta
|
||
|
|
match stage:
|
||
|
|
0:
|
||
|
|
if elapsed < 0.2:
|
||
|
|
return false
|
||
|
|
main.get_node("EnemySpawner").set_process(false)
|
||
|
|
main.get_node("AsteroidField").set_process(false)
|
||
|
|
# Room for a fleet of distorders.
|
||
|
|
game_state.unlocked.append_array(["wing", "squadron", "armada"])
|
||
|
|
|
||
|
|
# --- A. one vessel is unchanged ------------------------------
|
||
|
|
fleet.spawn_ships(2, Vector2.ZERO, PackedStringArray([
|
||
|
|
GameState.VESSEL_INTERCEPTOR, GameState.VESSEL_DISTORDER]))
|
||
|
|
if not is_equal_approx(game_state.max_fleet_speed_multiplier, GameState.MAX_FLEET_SPEED_MULTIPLIER):
|
||
|
|
return _fail("one distorder gives a ceiling of %.1f, expected the unchanged %.1f" \
|
||
|
|
% [game_state.max_fleet_speed_multiplier, GameState.MAX_FLEET_SPEED_MULTIPLIER])
|
||
|
|
print("1 distorder -> ceiling %.1fx" % game_state.max_fleet_speed_multiplier)
|
||
|
|
|
||
|
|
# --- B. each additional vessel adds 1.0 ----------------------
|
||
|
|
for extra in range(1, 4):
|
||
|
|
fleet.add_ship(GameState.VESSEL_DISTORDER)
|
||
|
|
var expected: float = GameState.MAX_FLEET_SPEED_MULTIPLIER + float(extra)
|
||
|
|
if not is_equal_approx(game_state.max_fleet_speed_multiplier, expected):
|
||
|
|
return _fail("%d distorders give a ceiling of %.1f, expected %.1f" \
|
||
|
|
% [extra + 1, game_state.max_fleet_speed_multiplier, expected])
|
||
|
|
print("%d distorders -> ceiling %.1fx" % [extra + 1, game_state.max_fleet_speed_multiplier])
|
||
|
|
|
||
|
|
# --- C. the fader reaches the widened ceiling ----------------
|
||
|
|
# Dragging the track to its top must mean the CURRENT ceiling, or
|
||
|
|
# the extra vessels would be unreachable through the only control
|
||
|
|
# that exists for them.
|
||
|
|
fader._process(0.0)
|
||
|
|
if not fader.visible:
|
||
|
|
return _fail("the fader is hidden with four distorder vessels crewed")
|
||
|
|
var press := InputEventScreenTouch.new()
|
||
|
|
press.index = 3
|
||
|
|
press.position = fader.global_position + Vector2(fader.size.x * 0.5, fader.size.y - 4.0)
|
||
|
|
press.pressed = true
|
||
|
|
main._handle_touch(press)
|
||
|
|
var drag := InputEventScreenDrag.new()
|
||
|
|
drag.index = 3
|
||
|
|
drag.position = fader.global_position + Vector2(fader.size.x * 0.5, 0.0)
|
||
|
|
main._handle_drag(drag)
|
||
|
|
if not is_equal_approx(game_state.fleet_speed_multiplier, game_state.max_fleet_speed_multiplier):
|
||
|
|
return _fail("dragging the fader to the top reached %.2fx, not the ceiling %.2fx" \
|
||
|
|
% [game_state.fleet_speed_multiplier, game_state.max_fleet_speed_multiplier])
|
||
|
|
print("fader top reaches %.1fx" % game_state.fleet_speed_multiplier)
|
||
|
|
|
||
|
|
# --- E. full boost costs the same per second -----------------
|
||
|
|
# The drain is normalized against the current ceiling, so sitting
|
||
|
|
# at full boost is the same price whatever the ceiling is.
|
||
|
|
var wide_energy: int = _energy_spent_over(1.0)
|
||
|
|
|
||
|
|
# --- D. losing vessels pulls the boost back down -------------
|
||
|
|
var boosted_before: float = game_state.fleet_speed_multiplier
|
||
|
|
for i in range(fleet.ships.size() - 1, -1, -1):
|
||
|
|
if is_instance_valid(fleet.ships[i]) and fleet.ships[i].vessel_type == GameState.VESSEL_DISTORDER:
|
||
|
|
fleet.store_ship(i)
|
||
|
|
break
|
||
|
|
if game_state.max_fleet_speed_multiplier >= boosted_before:
|
||
|
|
return _fail("losing a distorder left the ceiling at %.1f, no lower than the %.1f the fleet was already doing" \
|
||
|
|
% [game_state.max_fleet_speed_multiplier, boosted_before])
|
||
|
|
if game_state.fleet_speed_multiplier > game_state.max_fleet_speed_multiplier + 0.001:
|
||
|
|
return _fail("the fleet is still doing %.2fx after its ceiling dropped to %.2fx" \
|
||
|
|
% [game_state.fleet_speed_multiplier, game_state.max_fleet_speed_multiplier])
|
||
|
|
print("losing one pulled %.1fx down to the new %.1fx ceiling" \
|
||
|
|
% [boosted_before, game_state.fleet_speed_multiplier])
|
||
|
|
|
||
|
|
# Same measurement at the narrower ceiling, also at full boost.
|
||
|
|
game_state.set_fleet_speed_multiplier(game_state.max_fleet_speed_multiplier)
|
||
|
|
var narrow_energy: int = _energy_spent_over(1.0)
|
||
|
|
if absi(wide_energy - narrow_energy) > 1:
|
||
|
|
return _fail("full boost cost %d energy with a wide ceiling and %d with a narrow one - extra vessels changed the PRICE, not just the speed" \
|
||
|
|
% [wide_energy, narrow_energy])
|
||
|
|
print("full boost costs %d/%d energy per second at either ceiling" % [wide_energy, narrow_energy])
|
||
|
|
|
||
|
|
print("TEST_DISTORDER_STACKING: PASS")
|
||
|
|
quit(0)
|
||
|
|
return true
|
||
|
|
return false
|
||
|
|
|
||
|
|
|
||
|
|
# Energy actually consumed by holding the current multiplier for `seconds`,
|
||
|
|
# driven directly so the measurement does not depend on frame pacing.
|
||
|
|
func _energy_spent_over(seconds: float) -> int:
|
||
|
|
var before: int = game_state.fleet_resource(GameState.RESOURCE_ENERGY)
|
||
|
|
var step: float = 0.05
|
||
|
|
var t: float = 0.0
|
||
|
|
while t < seconds:
|
||
|
|
game_state._advance_distorder_drain(step)
|
||
|
|
t += step
|
||
|
|
return before - game_state.fleet_resource(GameState.RESOURCE_ENERGY)
|
||
|
|
|
||
|
|
|
||
|
|
func _fail(message: String) -> bool:
|
||
|
|
print("TEST_DISTORDER_STACKING: FAIL (%s)" % message)
|
||
|
|
quit(1)
|
||
|
|
return true
|