#219: fleet capacity is a built, supplied thing rather than a researched one. The eight fleet-size doctrines are gone; capacity is one berth free plus one per Fleet Office standing. An office charges every 25s for the vessel in its berth, priced off that vessel's own build cost so an expensive hull is expensive to keep, and drawn from its own store the way a gas burner draws gas. An office that cannot pay goes dark, its vessel is scuttled, and the player is told through the same queue quest lines use - which reaches them on a planet surface too. An empty berth is idle rather than starved: an office is only failing while there is something in it to fail, and holding it disabled would leave a berth that could never be crewed and so could never be paid for again. Ships have no hit points and a shield absorbs a hit wholesale, so the lost vessel is scuttled rather than damaged - otherwise a shielded hull would survive its own eviction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
188 lines
7.2 KiB
GDScript
188 lines
7.2 KiB
GDScript
extends SceneTree
|
|
|
|
# Headless test: run with
|
|
# godot --headless -s res://tests/test_desktop_controls.gd
|
|
# Forgejo #118 and #121: two raw-touch controls that a mouse could not
|
|
# reach.
|
|
#
|
|
# Both read InputEventScreenTouch/ScreenDrag, and the project no longer turns
|
|
# clicks into them - the same routing gap #93 found in the research tree.
|
|
# The boost was unreachable on a desktop and the formation could not be
|
|
# rearranged at all.
|
|
#
|
|
# Driven through the REAL routing in each case (GameController for the
|
|
# fader, BuildController for the editor) rather than by calling the controls
|
|
# directly, because the routing is what was missing - a test that called
|
|
# handle_mouse_button() itself would have passed against both reports.
|
|
#
|
|
# Covers:
|
|
# A. #118 - a click on the fader takes hold of it, dragging moves the
|
|
# boost, and releasing lets go
|
|
# B. the fader claims the press, so dragging the boost does not also zoom
|
|
# the world behind it
|
|
# C. #121 - a click grabs a formation slot, moving drags it, and the
|
|
# formation really changes
|
|
# D. releasing drops the slot, and moving afterwards does not keep dragging
|
|
# E. the wheel zooms the formation panel, which is what replaces the pinch
|
|
|
|
var game_state: Node
|
|
var scene: Node
|
|
var controller: Node
|
|
var fleet: Node
|
|
var fader: Control
|
|
var editor: Control
|
|
var formation_menu: Control
|
|
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.buildings.clear()
|
|
game_state.extractors.clear()
|
|
game_state.reset_progression()
|
|
game_state.reset_waves()
|
|
game_state.reset_formation()
|
|
game_state.intro_active = false
|
|
game_state.vessel_levels["unlock_distorder"] = 1
|
|
# #219: fleet capacity is built now - one berth free, one per Fleet
|
|
# Office. This test flies more than one vessel, so it needs the offices
|
|
# standing before the fleet spawns into them.
|
|
load("res://tests/_berths.gd").grant(root.get_node("GameState"), 6)
|
|
scene = load("res://world/Main.tscn").instantiate()
|
|
root.add_child(scene)
|
|
current_scene = scene
|
|
fleet = scene.get_node("Fleet")
|
|
controller = scene.get_node("BuildController")
|
|
fader = scene.get_node("UI/SpeedFader")
|
|
editor = scene.get_node("UI/FormationMenu/Editor")
|
|
formation_menu = scene.get_node("UI/FormationMenu")
|
|
|
|
|
|
func _fail(message: String) -> bool:
|
|
print("TEST_DESKTOP_CONTROLS: FAIL (%s)" % message)
|
|
quit(1)
|
|
return true
|
|
|
|
|
|
func _click(at: Vector2, pressed: bool, target: Node) -> void:
|
|
var event: InputEventMouseButton = InputEventMouseButton.new()
|
|
event.button_index = MOUSE_BUTTON_LEFT
|
|
event.pressed = pressed
|
|
event.position = at
|
|
target._unhandled_input(event)
|
|
|
|
|
|
func _move(to: Vector2, target: Node) -> void:
|
|
var event: InputEventMouseMotion = InputEventMouseMotion.new()
|
|
event.position = to
|
|
target._unhandled_input(event)
|
|
|
|
|
|
func _wheel(button: int, at: Vector2) -> void:
|
|
var event: InputEventMouseButton = InputEventMouseButton.new()
|
|
event.button_index = button
|
|
event.pressed = true
|
|
event.position = at
|
|
controller._unhandled_input(event)
|
|
|
|
|
|
func _process(delta: float) -> bool:
|
|
elapsed += delta
|
|
match stage:
|
|
0:
|
|
if elapsed < 0.3:
|
|
return false
|
|
scene.get_node("AsteroidField").set_process(false)
|
|
scene.get_node("EnemySpawner").set_process(false)
|
|
# The fader only exists where the fleet can actually boost.
|
|
fleet.spawn_ships(2, Vector2.ZERO, PackedStringArray([
|
|
GameState.VESSEL_INTERCEPTOR, GameState.VESSEL_DISTORDER]))
|
|
elapsed = 0.0
|
|
stage = 1
|
|
return false
|
|
1:
|
|
if elapsed < 0.3:
|
|
return false
|
|
if not fader.visible:
|
|
return _fail("the fleet has an Energy Propulser and the fader is still hidden")
|
|
if game_state.max_fleet_speed_multiplier <= 1.0:
|
|
return _fail("the boost has no range to move through")
|
|
|
|
# --- A. click, drag, release -----------------------------
|
|
var top: Vector2 = fader.global_position + Vector2(fader.size.x * 0.5,
|
|
fader.track_margin + 2.0)
|
|
var bottom: Vector2 = fader.global_position + Vector2(fader.size.x * 0.5,
|
|
fader.size.y - fader.track_margin - 2.0)
|
|
_click(bottom, true, scene)
|
|
if not fader.is_active():
|
|
return _fail("a click on the fader did not take hold of it")
|
|
var at_bottom: float = game_state.fleet_speed_multiplier
|
|
_move(top, scene)
|
|
var at_top: float = game_state.fleet_speed_multiplier
|
|
if at_top <= at_bottom:
|
|
return _fail("dragging the fader from bottom to top left the boost at %.2f" % at_top)
|
|
_click(top, false, scene)
|
|
if fader.is_active():
|
|
return _fail("the fader is still held after the button came up")
|
|
_move(bottom, scene)
|
|
if not is_equal_approx(game_state.fleet_speed_multiplier, at_top):
|
|
return _fail("the fader kept following the pointer after release")
|
|
print("fader: %.2fx at the bottom, %.2fx at the top, released cleanly" \
|
|
% [at_bottom, at_top])
|
|
|
|
# --- B. it claims the press ------------------------------
|
|
var zoom_before: float = scene.get_node("Camera2D").zoom.x
|
|
_click(bottom, true, scene)
|
|
_click(bottom, false, scene)
|
|
if not is_equal_approx(scene.get_node("Camera2D").zoom.x, zoom_before):
|
|
return _fail("pressing the fader also moved the camera behind it")
|
|
print("the press stops at the fader; the world behind it is untouched")
|
|
|
|
# --- C/D/E. the formation editor -------------------------
|
|
formation_menu.visible = true
|
|
editor.queue_redraw()
|
|
elapsed = 0.0
|
|
stage = 2
|
|
return false
|
|
2:
|
|
if elapsed < 0.2:
|
|
return false
|
|
var slot_before: Vector2 = game_state.formation[0]
|
|
var grab: Vector2 = editor.global_position + editor._slot_screen_pos(0)
|
|
_click(grab, true, controller)
|
|
if editor.dragging_index != 0:
|
|
return _fail("a click on the first formation slot grabbed %d" % editor.dragging_index)
|
|
_move(grab + Vector2(70.0, 40.0), controller)
|
|
if game_state.formation[0] == slot_before:
|
|
return _fail("dragging the slot did not move it")
|
|
var dragged: Vector2 = game_state.formation[0]
|
|
print("slot 0 moved from %s to %s under a mouse" % [str(slot_before), str(dragged)])
|
|
|
|
# --- D. release lets go ----------------------------------
|
|
_click(grab + Vector2(70.0, 40.0), false, controller)
|
|
if editor.dragging_index != -1:
|
|
return _fail("the slot is still held after the button came up")
|
|
_move(grab + Vector2(200.0, 200.0), controller)
|
|
if game_state.formation[0] != dragged:
|
|
return _fail("the slot kept following the pointer after release")
|
|
print("released, and the slot stopped following")
|
|
|
|
# --- E. the wheel zooms ----------------------------------
|
|
var scale_before: float = editor.scale_factor
|
|
_wheel(MOUSE_BUTTON_WHEEL_UP, editor.global_position + editor.size * 0.5)
|
|
if editor.scale_factor <= scale_before:
|
|
return _fail("the wheel did not zoom the formation panel in")
|
|
_wheel(MOUSE_BUTTON_WHEEL_DOWN, editor.global_position + editor.size * 0.5)
|
|
_wheel(MOUSE_BUTTON_WHEEL_DOWN, editor.global_position + editor.size * 0.5)
|
|
if editor.scale_factor >= scale_before:
|
|
return _fail("the wheel did not zoom back out")
|
|
print("the wheel zooms the panel both ways (%.2f -> %.2f)" \
|
|
% [scale_before, editor.scale_factor])
|
|
|
|
print("TEST_DESKTOP_CONTROLS: PASS")
|
|
quit(0)
|
|
return true
|
|
return false
|