149 lines
6 KiB
GDScript3
149 lines
6 KiB
GDScript3
|
|
extends SceneTree
|
||
|
|
|
||
|
|
# Headless gameplay test: run with
|
||
|
|
# godot --headless -s res://tests/test_fleet_cargo_limit.gd
|
||
|
|
# Forgejo #56: the fleet hold has a finite, composition-driven capacity
|
||
|
|
# instead of being unbounded.
|
||
|
|
#
|
||
|
|
# Covers:
|
||
|
|
# A. capacity is data-driven: only vessels with the "cargo" capability
|
||
|
|
# contribute their cargo_capacity stat - combat specialists (shield,
|
||
|
|
# gas bomber) contribute zero
|
||
|
|
# B. a lone interceptor cannot carry more than its capacity - a further
|
||
|
|
# Rock pickup is refused and the rock is not consumed
|
||
|
|
# C. losing a cargo-capable ship lowers the ceiling immediately without
|
||
|
|
# deleting cargo already held (clamps on the next deposit instead)
|
||
|
|
# D. a transfer into a full hold clamps to the remaining space and
|
||
|
|
# reports the amount actually moved
|
||
|
|
# E. capacity survives the space -> surface scene swap
|
||
|
|
|
||
|
|
var game_state: Node
|
||
|
|
var scene: Node
|
||
|
|
var fleet: Node
|
||
|
|
var stage: int = 0
|
||
|
|
var elapsed: float = 0.0
|
||
|
|
var mothership_id: int = 0
|
||
|
|
var rock: Node
|
||
|
|
|
||
|
|
|
||
|
|
func _initialize() -> void:
|
||
|
|
game_state = root.get_node("GameState")
|
||
|
|
game_state.buildings.clear()
|
||
|
|
game_state.extractors.clear()
|
||
|
|
game_state.rocks = 500
|
||
|
|
game_state.fleet_cargo = 0
|
||
|
|
mothership_id = game_state.add_building(GameState.BUILDING_MOTHERSHIP, Vector2.ZERO)
|
||
|
|
scene = load("res://Main.tscn").instantiate()
|
||
|
|
root.add_child(scene)
|
||
|
|
current_scene = scene
|
||
|
|
|
||
|
|
|
||
|
|
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)
|
||
|
|
fleet = scene.get_node("Fleet")
|
||
|
|
|
||
|
|
# --- A. capacity is data-driven ---------------------------------
|
||
|
|
fleet.spawn_ships(1, Vector2.ZERO, PackedStringArray(["interceptor"]))
|
||
|
|
if game_state.fleet_cargo_total_capacity() != 20:
|
||
|
|
return _fail("one interceptor: capacity %d, expected 20" % game_state.fleet_cargo_total_capacity())
|
||
|
|
|
||
|
|
fleet.spawn_ships(2, Vector2.ZERO, PackedStringArray(["shield", "gas_bomber"]))
|
||
|
|
if game_state.fleet_cargo_total_capacity() != 0:
|
||
|
|
return _fail("shield+gas_bomber: capacity %d, expected 0" % game_state.fleet_cargo_total_capacity())
|
||
|
|
print("capacity is data-driven: interceptor=20, shield+gas_bomber=0")
|
||
|
|
|
||
|
|
# --- B. a full hold refuses a pickup, the rock stays ------------
|
||
|
|
fleet.spawn_ships(1, Vector2.ZERO, PackedStringArray(["interceptor"]))
|
||
|
|
game_state.fleet_cargo = 20
|
||
|
|
rock = load("res://Rock.tscn").instantiate()
|
||
|
|
rock.build_shape(1)
|
||
|
|
rock.global_position = fleet.ships[0].global_position
|
||
|
|
scene.add_child(rock)
|
||
|
|
elapsed = 0.0
|
||
|
|
stage = 1
|
||
|
|
return false
|
||
|
|
1:
|
||
|
|
if elapsed < 0.3:
|
||
|
|
return false
|
||
|
|
if game_state.fleet_cargo != 20:
|
||
|
|
return _fail("a full hold still absorbed a pickup: hold=%d" % game_state.fleet_cargo)
|
||
|
|
if not is_instance_valid(rock):
|
||
|
|
return _fail("the rock was consumed even though the hold was full")
|
||
|
|
print("full hold (20/20) refused a pickup, the rock remains")
|
||
|
|
|
||
|
|
# --- C. losing a cargo ship drops the ceiling, keeps the cargo --
|
||
|
|
# Mirrors Ship.gd's own death sequence (destroyed.emit() then
|
||
|
|
# queue_free()) rather than just calling the handler, so no
|
||
|
|
# untracked ghost ship is left sitting in the scene.
|
||
|
|
var dying_ship: Node = fleet.ships[0]
|
||
|
|
fleet._on_ship_destroyed(dying_ship)
|
||
|
|
dying_ship.queue_free()
|
||
|
|
if game_state.fleet_cargo_total_capacity() != 0:
|
||
|
|
return _fail("empty fleet: capacity %d, expected 0" % game_state.fleet_cargo_total_capacity())
|
||
|
|
if game_state.fleet_cargo != 20:
|
||
|
|
return _fail("existing cargo was deleted when capacity dropped below it: %d" % game_state.fleet_cargo)
|
||
|
|
print("losing the ship: capacity=0, existing cargo untouched at %d" % game_state.fleet_cargo)
|
||
|
|
|
||
|
|
if game_state.collect_cargo(5) != 0:
|
||
|
|
return _fail("an over-capacity hold accepted a further deposit")
|
||
|
|
print("over-capacity hold clamps further deposits to zero, as documented")
|
||
|
|
|
||
|
|
# --- D. a transfer into a hold with partial room clamps ---------
|
||
|
|
fleet.spawn_ships(2, Vector2.ZERO, PackedStringArray(["interceptor", "interceptor"]))
|
||
|
|
if game_state.fleet_cargo_total_capacity() != 40:
|
||
|
|
return _fail("two interceptors: capacity %d, expected 40" % game_state.fleet_cargo_total_capacity())
|
||
|
|
var bank_before: int = game_state.rocks
|
||
|
|
var moved: int = game_state.transfer_resource_to_fleet(mothership_id, GameState.RESOURCE_ORE, 100)
|
||
|
|
if moved != 20:
|
||
|
|
return _fail("transfer into 20/40 room reported moved=%d, expected 20" % moved)
|
||
|
|
if game_state.fleet_cargo != 40:
|
||
|
|
return _fail("hold after the clamped transfer is %d, expected 40 (full)" % game_state.fleet_cargo)
|
||
|
|
if game_state.rocks != bank_before - 20:
|
||
|
|
return _fail("bank dropped by %d, expected exactly the 20 that moved" % (bank_before - game_state.rocks))
|
||
|
|
print("transfer into 20/40 room clamped to 20, hold now full, bank debited exactly 20")
|
||
|
|
|
||
|
|
# --- E. capacity survives the space -> surface scene swap -------
|
||
|
|
game_state.surviving_ship_count = 2
|
||
|
|
game_state.surviving_vessel_types = PackedStringArray(["interceptor", "interceptor"])
|
||
|
|
game_state.planet_id = "test_fleet_cargo_limit"
|
||
|
|
game_state.planet_name = "Cargo Limit Test"
|
||
|
|
scene.queue_free()
|
||
|
|
elapsed = 0.0
|
||
|
|
stage = 2
|
||
|
|
return false
|
||
|
|
2:
|
||
|
|
# A tick for queue_free() to actually leave the tree before adding
|
||
|
|
# the surface scene, matching the shape of every other scene-swap
|
||
|
|
# test in this suite.
|
||
|
|
if elapsed < 0.3:
|
||
|
|
return false
|
||
|
|
scene = load("res://PlanetSurface.tscn").instantiate()
|
||
|
|
root.add_child(scene)
|
||
|
|
current_scene = scene
|
||
|
|
elapsed = 0.0
|
||
|
|
stage = 3
|
||
|
|
return false
|
||
|
|
3:
|
||
|
|
if elapsed < 0.3:
|
||
|
|
return false
|
||
|
|
if game_state.fleet_cargo_total_capacity() != 40:
|
||
|
|
return _fail("capacity after the surface restore is %d, expected 40" % game_state.fleet_cargo_total_capacity())
|
||
|
|
if game_state.fleet_cargo != 40:
|
||
|
|
return _fail("cargo did not survive the scene swap: %d, expected 40" % game_state.fleet_cargo)
|
||
|
|
print("capacity (40) and cargo (40) both survived the space -> surface swap")
|
||
|
|
print("TEST_FLEET_CARGO_LIMIT: PASS")
|
||
|
|
quit(0)
|
||
|
|
return true
|
||
|
|
return false
|
||
|
|
|
||
|
|
|
||
|
|
func _fail(message: String) -> bool:
|
||
|
|
print("TEST_FLEET_CARGO_LIMIT: FAIL (%s)" % message)
|
||
|
|
quit(1)
|
||
|
|
return true
|