int_speed/shd_speed granted +max_speed/+acceleration per level to a single vessel, which is redundant now that #59/#62 gave the whole fleet a shared speed fader instead - a per-ship speed stat has nothing left to meaningfully affect since #59's multiplier is applied at the point of use in Ship.gd regardless of the ship's own base stats. Deleted both upgrade resources and their vessel-definition references outright rather than leaving them unused. test_ship_lab.gd's per-vessel-type isolation check used int_speed as an arbitrary interceptor-only upgrade to prove it doesn't leak into transporter stats - swapped to int_range, which still exists. Its upgrade-card-count assertion updated for 3 remaining interceptor upgrades instead of 4.
224 lines
8.8 KiB
GDScript
224 lines
8.8 KiB
GDScript
extends SceneTree
|
|
|
|
# Headless gameplay test: run with
|
|
# godot --headless -s res://tests/test_ship_lab.gd
|
|
# Forgejo #5: the ship lab researches upgrades per vessel type and unlocks
|
|
# new vessel types. The shield vessel is the first of those.
|
|
#
|
|
# Verifies:
|
|
# 1. the lab is gated behind its own research node, like every structure
|
|
# 2. upgrades cannot be bought without a lab built - it is a place you go,
|
|
# not a menu that exists once researched
|
|
# 3. buying raises the level, charges the escalating price, and stops at
|
|
# max
|
|
# 4. an upgrade improves the ships ALREADY FLYING, not just the next one
|
|
# built
|
|
# 5. the shield vessel is unlocked at the lab, and only then can the
|
|
# crafter build one
|
|
# 6. a shield vessel is what comes back from production - a typed order
|
|
# must not decay into an interceptor
|
|
# 7. upgrades are per vessel type: an interceptor upgrade does not tune
|
|
# the transporter
|
|
|
|
var game_state: Node
|
|
var scene: Node
|
|
var fleet: Node
|
|
var field: Node
|
|
var controller: Node
|
|
var crafter_id: int = 0
|
|
var stage: int = 0
|
|
var elapsed: float = 0.0
|
|
|
|
|
|
func _initialize() -> void:
|
|
game_state = root.get_node("GameState")
|
|
game_state.buildings.clear()
|
|
game_state.extractors.clear()
|
|
game_state.transporters.clear()
|
|
game_state.stored_vessels.clear()
|
|
game_state.build_time_scale = 0.02
|
|
game_state.reset_progression()
|
|
game_state.rocks = 1200
|
|
game_state.fleet_cargo = 0
|
|
scene = load("res://Main.tscn").instantiate()
|
|
root.add_child(scene)
|
|
fleet = scene.get_node("Fleet")
|
|
field = scene.get_node("BuildingField")
|
|
controller = scene.get_node("BuildController")
|
|
|
|
|
|
func _process(delta: float) -> bool:
|
|
elapsed += delta
|
|
match stage:
|
|
0:
|
|
scene.get_node("AsteroidField").set_process(false)
|
|
scene.get_node("EnemySpawner").set_process(false)
|
|
for a in get_nodes_in_group("asteroids"):
|
|
if is_instance_valid(a):
|
|
a.queue_free()
|
|
|
|
# --- 1. gated behind research -------------------------------
|
|
if game_state.is_building_unlocked(GameState.BUILDING_SHIP_LAB):
|
|
print("TEST_SHIP_LAB: FAIL (lab buildable with no research)")
|
|
quit(1)
|
|
return true
|
|
var gate: String = game_state.research_for_building(GameState.BUILDING_SHIP_LAB)
|
|
if gate == "":
|
|
print("TEST_SHIP_LAB: FAIL (no research node gates the lab)")
|
|
quit(1)
|
|
return true
|
|
print("lab gated behind '%s'" % gate)
|
|
|
|
# --- 2. no lab, no upgrades ---------------------------------
|
|
if game_state.can_buy_upgrade("int_damage"):
|
|
print("TEST_SHIP_LAB: FAIL (upgrades purchasable with no lab built)")
|
|
quit(1)
|
|
return true
|
|
if game_state.buy_upgrade("int_damage"):
|
|
print("TEST_SHIP_LAB: FAIL (bought an upgrade with no lab built)")
|
|
quit(1)
|
|
return true
|
|
print("no lab -> no upgrades")
|
|
|
|
game_state.unlocked = ["shipyards", "vessel_science", "coordination", "wing", "squadron"]
|
|
game_state.research_points = 10
|
|
field.place(GameState.BUILDING_SHIP_LAB, Vector2(-700.0, 0.0))
|
|
field.place(GameState.BUILDING_SHIP_CRAFTER, Vector2(700.0, 0.0))
|
|
crafter_id = int(game_state.buildings[-1].id)
|
|
elapsed = 0.0
|
|
stage = 1
|
|
return false
|
|
1:
|
|
if elapsed < 0.3:
|
|
return false
|
|
# The lab is a direct vessel -> upgrade flow: tapping a vessel
|
|
# shows its own upgrade cards, and tapping a card changes the
|
|
# purchase detail. A locked vessel must show its unlock alone.
|
|
var lab_menu: Control = controller.get_node("../UI/LabMenu")
|
|
var lab_card_0: Control = controller.get_node("../UI/LabMenu/Upgrade0")
|
|
var lab_card_1: Control = controller.get_node("../UI/LabMenu/Upgrade1")
|
|
var lab_card_2: Control = controller.get_node("../UI/LabMenu/Upgrade2")
|
|
var lab_card_3: Control = controller.get_node("../UI/LabMenu/Upgrade3")
|
|
var lab_detail: Label = controller.get_node("../UI/LabMenu/DetailLabel")
|
|
var lab_buy: IconButton = controller.get_node("../UI/LabMenu/BuyButton")
|
|
lab_menu.visible = true
|
|
controller.call("_on_lab_vessel", 1)
|
|
if not lab_card_0.visible or lab_card_1.visible:
|
|
print("TEST_SHIP_LAB: FAIL (locked shield page did not show only its unlock)")
|
|
quit(1)
|
|
return true
|
|
controller.call("_on_lab_upgrade", 0)
|
|
if not "Unlock Shield Vessel" in lab_detail.text or not lab_buy.enabled:
|
|
print("TEST_SHIP_LAB: FAIL (unlock card did not select a purchasable detail)")
|
|
quit(1)
|
|
return true
|
|
controller.call("_on_lab_vessel", 0)
|
|
# #73: the interceptor's Thrusters upgrade was removed (global
|
|
# fleet speed now belongs to the distorder fader, #59/#62)
|
|
# leaving 3 upgrade cards, not 4 - card 3 is correctly hidden now.
|
|
if not lab_card_0.visible or not lab_card_2.visible or lab_card_3.visible:
|
|
print("TEST_SHIP_LAB: FAIL (interceptor upgrade cards were not shown)")
|
|
quit(1)
|
|
return true
|
|
controller.call("_on_lab_upgrade", 2)
|
|
if not "Targeting Array" in lab_detail.text or not lab_card_2.highlighted or lab_card_0.highlighted:
|
|
print("TEST_SHIP_LAB: FAIL (tapping an upgrade did not update its detail)")
|
|
quit(1)
|
|
return true
|
|
print("lab cards are vessel-specific and selectable")
|
|
|
|
# --- 3. buying raises the level and charges ------------------
|
|
var before_rocks: int = game_state.rocks
|
|
var first_cost: int = game_state.upgrade_cost("int_damage")
|
|
if not game_state.buy_upgrade("int_damage"):
|
|
print("TEST_SHIP_LAB: FAIL (could not buy an upgrade with a lab built)")
|
|
quit(1)
|
|
return true
|
|
if game_state.upgrade_level("int_damage") != 1:
|
|
print("TEST_SHIP_LAB: FAIL (level did not rise)")
|
|
quit(1)
|
|
return true
|
|
if game_state.rocks != before_rocks - first_cost:
|
|
print("TEST_SHIP_LAB: FAIL (wrong price charged)")
|
|
quit(1)
|
|
return true
|
|
var second_cost: int = game_state.upgrade_cost("int_damage")
|
|
if second_cost <= first_cost:
|
|
print("TEST_SHIP_LAB: FAIL (level 2 costs %d, no more than level 1 at %d)" % [second_cost, first_cost])
|
|
quit(1)
|
|
return true
|
|
print("bought level 1 for %d, level 2 now costs %d" % [first_cost, second_cost])
|
|
|
|
# --- 4. it reaches ships already flying ----------------------
|
|
var flying: Node = fleet.ships[0]
|
|
if flying.projectile_damage != GameState.SHIP_BASE_DAMAGE + 1:
|
|
print("TEST_SHIP_LAB: FAIL (a ship already flying still does %d damage)" % flying.projectile_damage)
|
|
quit(1)
|
|
return true
|
|
print("ship already flying now does %d damage" % flying.projectile_damage)
|
|
|
|
# --- 7. per vessel type --------------------------------------
|
|
var trn_speed_before: float = game_state.transporter_speed()
|
|
game_state.buy_upgrade("int_range")
|
|
if game_state.transporter_speed() != trn_speed_before:
|
|
print("TEST_SHIP_LAB: FAIL (an interceptor upgrade changed the transporter)")
|
|
quit(1)
|
|
return true
|
|
print("interceptor upgrades leave the transporter alone")
|
|
|
|
# --- 5. the shield vessel is unlocked at the lab -------------
|
|
if game_state.shield_vessel_unlocked():
|
|
print("TEST_SHIP_LAB: FAIL (shield vessel available before it was unlocked)")
|
|
quit(1)
|
|
return true
|
|
if game_state.queue_order(crafter_id, GameState.VESSEL_SHIELD):
|
|
print("TEST_SHIP_LAB: FAIL (crafter took a shield order before the type was unlocked)")
|
|
quit(1)
|
|
return true
|
|
if not game_state.unlock_shield_vessel():
|
|
print("TEST_SHIP_LAB: FAIL (could not unlock the shield vessel at the lab)")
|
|
quit(1)
|
|
return true
|
|
print("shield vessel unlocked at the lab")
|
|
|
|
# --- 6. a typed order comes back as that type ----------------
|
|
fleet.spawn_ships(1, Vector2.ZERO)
|
|
if not game_state.queue_order(crafter_id, GameState.VESSEL_SHIELD):
|
|
print("TEST_SHIP_LAB: FAIL (crafter refused a shield order after unlocking)")
|
|
quit(1)
|
|
return true
|
|
game_state.deposit_to_building(crafter_id, GameState.SHIELD_COST)
|
|
elapsed = 0.0
|
|
stage = 2
|
|
return false
|
|
2:
|
|
if elapsed < game_state.build_time(GameState.VESSEL_SHIELD) * 3.0:
|
|
return false
|
|
# Finished vessels wait at the mothership (#23); crew it here so
|
|
# the rest of the checks can look at a real ship.
|
|
if game_state.stored_vessels.size() != 1:
|
|
print("TEST_SHIP_LAB: FAIL (the shield order produced nothing: %s)" % [game_state.stored_vessels])
|
|
quit(1)
|
|
return true
|
|
if String(game_state.stored_vessels[0]) != GameState.VESSEL_SHIELD:
|
|
print("TEST_SHIP_LAB: FAIL (a shield order came back as %s)" % game_state.stored_vessels[0])
|
|
quit(1)
|
|
return true
|
|
fleet.add_ship(game_state.take_stored_vessel(0))
|
|
var shield_ship: Node = null
|
|
for ship in fleet.ships:
|
|
if is_instance_valid(ship) and ship.vessel_type == GameState.VESSEL_SHIELD:
|
|
shield_ship = ship
|
|
if shield_ship == null:
|
|
print("TEST_SHIP_LAB: FAIL (crewing the stored shield vessel produced none: %d ships)" % fleet.ships.size())
|
|
quit(1)
|
|
return true
|
|
if not shield_ship.has_shield():
|
|
print("TEST_SHIP_LAB: FAIL (the shield vessel arrived with no shield up)")
|
|
quit(1)
|
|
return true
|
|
print("shield vessel built, shield up with %d charge(s)" % shield_ship.shield_charges)
|
|
print("TEST_SHIP_LAB: PASS")
|
|
quit(0)
|
|
return true
|
|
return false
|