220 lines
7.8 KiB
GDScript
220 lines
7.8 KiB
GDScript
extends SceneTree
|
|
|
|
# Headless gameplay test: run with
|
|
# godot --headless -s res://tests/test_resource_transfer.gd
|
|
# Forgejo #24: moving ore between the fleet and a building is its own
|
|
# top-level action, not something buried inside the ship crafter or the
|
|
# research screen.
|
|
#
|
|
# Verifies:
|
|
# 1. the transfer button appears alongside the action button whenever the
|
|
# fleet is standing on a building, and opens the transfer panel bound to
|
|
# THAT building - the `== 0` guard failure that made every crafter
|
|
# button look dead is the same trap here
|
|
# 2. ore moves both ways at an ordinary building, capped by what is
|
|
# actually at each end (no overdraft in either direction)
|
|
# 3. at a mothership the building's holdings ARE the bank, so unloading
|
|
# there banks the ore and loading there draws on it
|
|
# 4. a REAL tap through the input path moves ore, not just the handler
|
|
# 5. pressing against an empty end reports why instead of doing nothing
|
|
# 6. the shared resource bar reports the building the panel was opened from
|
|
# 7. ore is conserved across the whole exercise
|
|
|
|
var game_state: Node
|
|
var scene: Node
|
|
var fleet: Node
|
|
var controller: Node
|
|
var crafter_id: int = 0
|
|
var mothership_id: int = 0
|
|
var stage: int = 0
|
|
var elapsed: float = 0.0
|
|
var total_before: int = 0
|
|
var failures: PackedStringArray = PackedStringArray()
|
|
|
|
const MOTHERSHIP_AT: Vector2 = Vector2(0.0, 0.0)
|
|
const CRAFTER_AT: Vector2 = Vector2(3000.0, 0.0)
|
|
|
|
|
|
func _total() -> int:
|
|
var sum: int = game_state.rocks + game_state.fleet_cargo
|
|
for b in game_state.buildings:
|
|
if b.type != GameState.BUILDING_MOTHERSHIP:
|
|
sum += int(b.get("stock", 0))
|
|
return sum
|
|
|
|
|
|
func _initialize() -> void:
|
|
game_state = root.get_node("GameState")
|
|
game_state.buildings.clear()
|
|
game_state.extractors.clear()
|
|
game_state.transporters.clear()
|
|
game_state.rocks = 100
|
|
game_state.fleet_cargo = 0
|
|
game_state.stored_vessels.clear()
|
|
game_state.unlocked = ["shipyards", "coordination", "wing", "squadron", "armada"]
|
|
scene = load("res://Main.tscn").instantiate()
|
|
root.add_child(scene)
|
|
fleet = scene.get_node("Fleet")
|
|
controller = scene.get_node("BuildController")
|
|
|
|
|
|
func _park_fleet_on(at: Vector2) -> void:
|
|
for ship in fleet.ships:
|
|
ship.position = at
|
|
fleet.move_to(at)
|
|
|
|
|
|
func _tap(button: Control) -> void:
|
|
for pressed in [true, false]:
|
|
var ev: InputEventScreenTouch = InputEventScreenTouch.new()
|
|
ev.pressed = pressed
|
|
ev.position = button.size / 2.0
|
|
button._gui_input(ev)
|
|
|
|
|
|
func _open_transfer() -> void:
|
|
# Contextual choices collapse into one button until it is opened.
|
|
_tap(controller.action_button)
|
|
_tap(controller.transfer_button)
|
|
|
|
|
|
func _expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("stage %d: %s" % [stage, message])
|
|
|
|
|
|
func _process(delta: float) -> bool:
|
|
elapsed += delta
|
|
match stage:
|
|
0:
|
|
scene.get_node("AsteroidField").set_process(false)
|
|
scene.get_node("EnemySpawner").set_process(false)
|
|
_clear_hazards()
|
|
fleet.spawn_ships(2, MOTHERSHIP_AT)
|
|
var field: Node = scene.get_node("BuildingField")
|
|
field.place(GameState.BUILDING_MOTHERSHIP, MOTHERSHIP_AT)
|
|
mothership_id = int(game_state.buildings[-1].id)
|
|
field.place(GameState.BUILDING_SHIP_CRAFTER, CRAFTER_AT)
|
|
crafter_id = int(game_state.buildings[-1].id)
|
|
total_before = _total()
|
|
_park_fleet_on(CRAFTER_AT)
|
|
elapsed = 0.0
|
|
stage = 1
|
|
return false
|
|
|
|
1:
|
|
# --- 1. the button appears, and binds to this building ------
|
|
if elapsed < 1.2:
|
|
return false
|
|
_expect(controller.current_building != null,
|
|
"parking on the crafter never registered")
|
|
_open_transfer()
|
|
_expect(controller.transfer_menu.visible,
|
|
"tapping the transfer button did not open the panel")
|
|
_expect(controller.transfer_building_id == crafter_id,
|
|
"panel bound to building %d, crafter is %d"
|
|
% [controller.transfer_building_id, crafter_id])
|
|
|
|
# --- 6. the shared bar follows the panel --------------------
|
|
for bar in controller.resource_bars:
|
|
_expect(bar.building_id == crafter_id,
|
|
"a resource bar is reporting building %d, not the crafter %d"
|
|
% [bar.building_id, crafter_id])
|
|
|
|
# --- 5. an empty hold explains itself -----------------------
|
|
game_state.fleet_cargo = 0
|
|
controller._on_transfer(true, GameState.CARGO_LOAD_BATCH)
|
|
_expect("carrying nothing" in controller.transfer_status.text,
|
|
"unloading an empty hold said: %s" % controller.transfer_status.text)
|
|
|
|
# --- 2. both directions at an ordinary building -------------
|
|
# Baseline taken AFTER the last hand-set figure: the conservation
|
|
# check at the end is about the transfers, and ore conjured into
|
|
# the hold to set the scene would otherwise read as a leak.
|
|
game_state.fleet_cargo = 40
|
|
total_before = _total()
|
|
controller._on_transfer(true, GameState.CARGO_LOAD_BATCH)
|
|
_expect(game_state.building_stock(crafter_id) == GameState.CARGO_LOAD_BATCH,
|
|
"a batch into the crafter left stock at %d"
|
|
% game_state.building_stock(crafter_id))
|
|
_expect(game_state.fleet_cargo == 40 - GameState.CARGO_LOAD_BATCH,
|
|
"the hold paid %d, not the batch" % (40 - game_state.fleet_cargo))
|
|
|
|
controller._on_transfer(false, 999)
|
|
_expect(game_state.building_stock(crafter_id) == 0,
|
|
"loading ALL left %d in the crafter" % game_state.building_stock(crafter_id))
|
|
_expect(game_state.fleet_cargo == 40,
|
|
"the hold came back to %d, not 40" % game_state.fleet_cargo)
|
|
|
|
# Asking for more than exists moves only what is there.
|
|
var over: int = game_state.transfer_to_fleet(crafter_id, 999)
|
|
_expect(over == 0, "overdrew %d from an empty crafter" % over)
|
|
|
|
_park_fleet_on(MOTHERSHIP_AT)
|
|
controller.transfer_menu.visible = false
|
|
elapsed = 0.0
|
|
stage = 2
|
|
return false
|
|
|
|
2:
|
|
# --- 3. the mothership's holdings are the bank --------------
|
|
if elapsed < 1.2:
|
|
return false
|
|
_open_transfer()
|
|
_expect(controller.transfer_building_id == mothership_id,
|
|
"panel bound to %d, mothership is %d"
|
|
% [controller.transfer_building_id, mothership_id])
|
|
_expect(game_state.building_holdings(mothership_id) == game_state.rocks,
|
|
"mothership holdings %d != bank %d"
|
|
% [game_state.building_holdings(mothership_id), game_state.rocks])
|
|
|
|
var bank_before: int = game_state.rocks
|
|
var hold_before: int = game_state.fleet_cargo
|
|
controller._on_transfer(true, 999)
|
|
_expect(game_state.fleet_cargo == 0,
|
|
"unloading ALL at the mothership left %d in the hold" % game_state.fleet_cargo)
|
|
_expect(game_state.rocks == bank_before + hold_before,
|
|
"the bank went to %d, expected %d" % [game_state.rocks, bank_before + hold_before])
|
|
|
|
# --- 4. a REAL tap, through the input path ------------------
|
|
# The handler working while the BUTTON is inert is the exact bug
|
|
# class this project keeps hitting, so one press goes the long way.
|
|
var bank_at_tap: int = game_state.rocks
|
|
_tap(scene.get_node("UI/TransferMenu/ResourceList/OreToFleetButton"))
|
|
_expect(game_state.fleet_cargo == bank_at_tap,
|
|
"a real tap did not load all banked ore")
|
|
_expect(game_state.rocks == 0,
|
|
"the bank did not pay for the tapped withdrawal")
|
|
|
|
# --- 7. conservation ----------------------------------------
|
|
_expect(_total() == total_before,
|
|
"ore created or destroyed: %d -> %d" % [total_before, _total()])
|
|
|
|
_finish()
|
|
return true
|
|
return false
|
|
|
|
|
|
func _finish() -> void:
|
|
if failures.is_empty():
|
|
print("TEST_RESOURCE_TRANSFER: PASS")
|
|
quit(0)
|
|
else:
|
|
print("bank=%d hold=%d crafter=%d" % [
|
|
game_state.rocks, game_state.fleet_cargo, game_state.building_stock(crafter_id)
|
|
])
|
|
for f in failures:
|
|
print("FAIL: %s" % f)
|
|
print("TEST_RESOURCE_TRANSFER: FAIL")
|
|
quit(1)
|
|
|
|
|
|
func _clear_hazards() -> void:
|
|
# Drifting rocks can destroy a parked ship and change the fleet size out
|
|
# from under a logistics test. Same reason as test_manual_supply_route.
|
|
for a in get_nodes_in_group("asteroids"):
|
|
if is_instance_valid(a):
|
|
a.queue_free()
|
|
for e in get_nodes_in_group("enemies"):
|
|
if is_instance_valid(e):
|
|
e.queue_free()
|