169 lines
6 KiB
GDScript3
169 lines
6 KiB
GDScript3
|
|
extends SceneTree
|
||
|
|
|
||
|
|
# Headless gameplay test: run with
|
||
|
|
# godot --headless -s res://tests/test_skill_tree.gd
|
||
|
|
# The mothership's research tree (Forgejo issue #4). It gates which
|
||
|
|
# structures may be built at all, and carries permanent upgrades that have
|
||
|
|
# no building of their own.
|
||
|
|
#
|
||
|
|
# Verifies:
|
||
|
|
# 1. from a fresh start only the mothership is buildable - the others are
|
||
|
|
# greyed, and pressing one explains which research opens it rather than
|
||
|
|
# silently failing
|
||
|
|
# 2. a locked option cannot be built even if the confirm is forced
|
||
|
|
# 3. research costs rocks, needs its prerequisite, and unlocks its building
|
||
|
|
# 4. the mothership's action button opens the tree (it used to have no
|
||
|
|
# action at all)
|
||
|
|
# 5. fleet capacity upgrades raise the cap, and the extra vessel can
|
||
|
|
# actually be crafted afterwards
|
||
|
|
|
||
|
|
var scene: Node
|
||
|
|
var game_state: Node
|
||
|
|
var controller: Node
|
||
|
|
var fleet: Node
|
||
|
|
var field: Node
|
||
|
|
var popup: Control
|
||
|
|
var stage: int = 0
|
||
|
|
var elapsed: float = 0.0
|
||
|
|
|
||
|
|
|
||
|
|
func _initialize() -> void:
|
||
|
|
game_state = root.get_node("GameState")
|
||
|
|
game_state.rocks = 500
|
||
|
|
game_state.buildings.clear()
|
||
|
|
game_state.extractors.clear()
|
||
|
|
game_state.reset_progression()
|
||
|
|
game_state.reset_formation()
|
||
|
|
|
||
|
|
scene = load("res://Main.tscn").instantiate()
|
||
|
|
root.add_child(scene)
|
||
|
|
controller = scene.get_node("BuildController")
|
||
|
|
fleet = scene.get_node("Fleet")
|
||
|
|
field = scene.get_node("BuildingField")
|
||
|
|
popup = scene.get_node("UI/BuildMenu/InfoPopup")
|
||
|
|
|
||
|
|
|
||
|
|
func _process(delta: float) -> bool:
|
||
|
|
elapsed += delta
|
||
|
|
match stage:
|
||
|
|
0:
|
||
|
|
scene.get_node("AsteroidField").set_process(false)
|
||
|
|
scene.get_node("EnemySpawner").set_process(false)
|
||
|
|
field.place(GameState.BUILDING_MOTHERSHIP, Vector2(400.0, 0.0))
|
||
|
|
elapsed = 0.0
|
||
|
|
stage = 1
|
||
|
|
return false
|
||
|
|
1:
|
||
|
|
if elapsed < 0.2:
|
||
|
|
return false
|
||
|
|
|
||
|
|
# --- everything but the mothership starts locked --------------
|
||
|
|
var mothership_free: bool = game_state.is_building_unlocked(GameState.BUILDING_MOTHERSHIP)
|
||
|
|
var crafter_locked: bool = not game_state.is_building_unlocked(GameState.BUILDING_SHIP_CRAFTER)
|
||
|
|
print("fresh start: mothership buildable=%s crafter locked=%s" % [
|
||
|
|
mothership_free, crafter_locked
|
||
|
|
])
|
||
|
|
if not (mothership_free and crafter_locked):
|
||
|
|
print("TEST_SKILL_TREE: FAIL (wrong things buildable at the start)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
|
||
|
|
controller._on_build_pressed()
|
||
|
|
var greyed: bool = not scene.get_node("UI/BuildMenu/CrafterButton").enabled
|
||
|
|
controller._on_place_crafter()
|
||
|
|
var explains: bool = popup.visible \
|
||
|
|
and "research" in popup.get_node("Panel/MessageLabel").text.to_lower()
|
||
|
|
var offered: bool = popup.get_node("Panel/BuildButton").visible
|
||
|
|
print("locked crafter: greyed=%s explains=%s confirm_offered=%s" % [
|
||
|
|
greyed, explains, offered
|
||
|
|
])
|
||
|
|
if not (greyed and explains) or offered:
|
||
|
|
print("TEST_SKILL_TREE: FAIL (locked option did not explain itself)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
|
||
|
|
# Forcing the confirm must still build nothing.
|
||
|
|
var before: int = game_state.buildings.size()
|
||
|
|
controller.pending_build_type = GameState.BUILDING_SHIP_CRAFTER
|
||
|
|
controller._confirm_build()
|
||
|
|
if game_state.buildings.size() != before:
|
||
|
|
print("TEST_SKILL_TREE: FAIL (built a locked structure)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
controller._close_info()
|
||
|
|
|
||
|
|
# --- the mothership opens the tree ----------------------------
|
||
|
|
var mothership: Node2D = null
|
||
|
|
for b in field.spawned:
|
||
|
|
if is_instance_valid(b) and b.building_type == GameState.BUILDING_MOTHERSHIP:
|
||
|
|
mothership = b
|
||
|
|
controller.current_building = mothership
|
||
|
|
controller._on_action_pressed()
|
||
|
|
var tree_open: bool = scene.get_node("UI/SkillMenu").visible
|
||
|
|
print("mothership action opens research: %s" % tree_open)
|
||
|
|
if not tree_open:
|
||
|
|
print("TEST_SKILL_TREE: FAIL (mothership has no research action)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
|
||
|
|
# --- prerequisites --------------------------------------------
|
||
|
|
if game_state.can_unlock("wing"):
|
||
|
|
print("TEST_SKILL_TREE: FAIL (deep node unlockable with no prerequisite)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
|
||
|
|
# --- research costs rocks and unlocks its building ------------
|
||
|
|
var rocks_before: int = game_state.rocks
|
||
|
|
var cost: int = int(game_state.skill_node("shipyards").cost)
|
||
|
|
var ok: bool = game_state.unlock("shipyards")
|
||
|
|
var charged: bool = game_state.rocks == rocks_before - cost
|
||
|
|
var now_buildable: bool = game_state.is_building_unlocked(GameState.BUILDING_SHIP_CRAFTER)
|
||
|
|
print("research shipyards: ok=%s rocks %d->%d charged=%s crafter unlocked=%s" % [
|
||
|
|
ok, rocks_before, game_state.rocks, charged, now_buildable
|
||
|
|
])
|
||
|
|
if not (ok and charged and now_buildable):
|
||
|
|
print("TEST_SKILL_TREE: FAIL (research did not unlock its building)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
|
||
|
|
# The menu must show it as buildable now, without reopening.
|
||
|
|
controller._on_build_pressed()
|
||
|
|
if not scene.get_node("UI/BuildMenu/CrafterButton").enabled:
|
||
|
|
print("TEST_SKILL_TREE: FAIL (build menu still greys an unlocked option)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
controller._close_info()
|
||
|
|
scene.get_node("UI/BuildMenu").visible = false
|
||
|
|
|
||
|
|
# --- fleet capacity -------------------------------------------
|
||
|
|
var base_cap: int = game_state.max_fleet_size()
|
||
|
|
game_state.unlock("coordination")
|
||
|
|
var wing_ok: bool = game_state.unlock("wing")
|
||
|
|
var raised_cap: int = game_state.max_fleet_size()
|
||
|
|
print("fleet cap %d -> %d after Wing Doctrine (unlocked=%s)" % [
|
||
|
|
base_cap, raised_cap, wing_ok
|
||
|
|
])
|
||
|
|
if not (wing_ok and raised_cap == base_cap + 1):
|
||
|
|
print("TEST_SKILL_TREE: FAIL (capacity upgrade did not raise the cap)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
|
||
|
|
# And the extra vessel must actually be craftable.
|
||
|
|
fleet.spawn_ships(base_cap, Vector2.ZERO)
|
||
|
|
var added: bool = fleet.add_ship()
|
||
|
|
print("fleet %d ships, add one more -> %s (now %d)" % [
|
||
|
|
base_cap, added, fleet.ships.size()
|
||
|
|
])
|
||
|
|
if not (added and fleet.ships.size() == base_cap + 1):
|
||
|
|
print("TEST_SKILL_TREE: FAIL (could not crew the extra slot)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
if fleet.add_ship():
|
||
|
|
print("TEST_SKILL_TREE: FAIL (cap not enforced beyond the upgrade)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
|
||
|
|
print("TEST_SKILL_TREE: PASS")
|
||
|
|
quit(0)
|
||
|
|
return true
|
||
|
|
return false
|