222 lines
8.9 KiB
GDScript
222 lines
8.9 KiB
GDScript
extends SceneTree
|
|
|
|
# Headless gameplay test: run with
|
|
# godot --headless -s res://tests/test_build_menu_hides_locked.gd
|
|
# Forgejo #21: a structure the player has not researched should not be in the
|
|
# build menu at all, rather than sitting there greyed out.
|
|
#
|
|
# The menu is a fixed grid in Main.tscn, so simply hiding entries would leave
|
|
# holes where they used to be - it is repacked from whatever is researched.
|
|
#
|
|
# Verifies:
|
|
# 1. with nothing researched only the mothership is offered (it is
|
|
# deliberately never gated - it is where research happens)
|
|
# 2. researching a structure adds it, and it is placed, not left at
|
|
# whatever offsets it happened to have in the scene file
|
|
# 3. no gaps: what is shown occupies consecutive grid slots
|
|
# 4. an entry alone on its row is centred, not hanging in the left column
|
|
# 5. researched-but-not-usable is still shown greyed (an extractor with no
|
|
# planet under the fleet is "not now", not "not yet")
|
|
# 6. #136 - with every structure researched, no two entries overlap. The
|
|
# grid used to be a fixed list of row positions read with a clamp, so
|
|
# the first row that would not fit was drawn on top of another one
|
|
# 7. #144 - and that holds at any window shape. The header is anchored to
|
|
# the top of the panel and the grid to its centre, so the gap between
|
|
# them is half the panel height - it was a constant measured at 1280,
|
|
# and at 1080 the grid started above the status line and drew through it
|
|
|
|
var game_state: Node
|
|
var scene: Node
|
|
var controller: Node
|
|
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.reset_progression()
|
|
game_state.rocks = 500
|
|
scene = load("res://world/Main.tscn").instantiate()
|
|
root.add_child(scene)
|
|
controller = scene.get_node("BuildController")
|
|
|
|
|
|
func _shown() -> Array:
|
|
var out: Array = []
|
|
for entry in controller.build_menu.entries:
|
|
if entry.button.visible:
|
|
out.append([entry.definition.definition_id, entry.button,
|
|
entry.label, entry.cost, entry.definition])
|
|
return out
|
|
|
|
|
|
func _rect(node: Control) -> Rect2:
|
|
return Rect2(Vector2(node.offset_left, node.offset_top),
|
|
Vector2(node.offset_right - node.offset_left,
|
|
node.offset_bottom - node.offset_top))
|
|
|
|
|
|
func _slots() -> Array:
|
|
# (row, x) of every visible button, in menu order.
|
|
var out: Array = []
|
|
for entry in _shown():
|
|
var b: Control = entry[1]
|
|
out.append([b.offset_top, b.offset_left + (b.offset_right - b.offset_left) / 2.0])
|
|
return out
|
|
|
|
|
|
func _process(delta: float) -> bool:
|
|
elapsed += delta
|
|
match stage:
|
|
0:
|
|
if elapsed < 0.2:
|
|
return false
|
|
scene.get_node("AsteroidField").set_process(false)
|
|
scene.get_node("EnemySpawner").set_process(false)
|
|
|
|
# --- 1. nothing researched ----------------------------------
|
|
controller._refresh_build_status()
|
|
var shown: Array = _shown()
|
|
if shown.size() != 1 or shown[0][0] != GameState.BUILDING_MOTHERSHIP:
|
|
var names: Array = []
|
|
for e in shown:
|
|
names.append(e[0])
|
|
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (unresearched structures on offer: %s)" % [names])
|
|
quit(1)
|
|
return true
|
|
# --- 4. and it is centred, being alone on its row -----------
|
|
var only: Array = _slots()[0]
|
|
if absf(only[1]) > 0.01:
|
|
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (the only entry sits at x=%.1f instead of centred)" % only[1])
|
|
quit(1)
|
|
return true
|
|
print("nothing researched -> only the mothership, centred")
|
|
|
|
# --- 2. research three more ---------------------------------
|
|
game_state.unlocked = ["shipyards", "logistics", "coordination"]
|
|
controller._refresh_build_status()
|
|
stage = 1
|
|
return false
|
|
1:
|
|
var shown2: Array = _shown()
|
|
if shown2.size() != 4:
|
|
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (researched 3, expected 4 entries, got %d)" % shown2.size())
|
|
quit(1)
|
|
return true
|
|
for e in shown2:
|
|
if e[0] == GameState.BUILDING_TRANSPORT_HUB:
|
|
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (the unresearched transport hub is still listed)")
|
|
quit(1)
|
|
return true
|
|
|
|
# --- 3. consecutive slots, no holes -------------------------
|
|
var slots: Array = _slots()
|
|
var columns: int = controller.menu_columns(slots.size())
|
|
var rows: int = controller.menu_rows(slots.size())
|
|
if columns != 2:
|
|
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (4 entries laid out in %d columns)" % columns)
|
|
quit(1)
|
|
return true
|
|
for i in range(slots.size()):
|
|
var want_row: float = controller.menu_row_top(i / columns, rows, columns)
|
|
if absf(slots[i][0] - want_row) > 0.01:
|
|
print("slot %d at y=%.1f, expected row %d at y=%.1f" % [i, slots[i][0], i / columns, want_row])
|
|
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (a gap was left where a hidden entry used to be)")
|
|
quit(1)
|
|
return true
|
|
var spacing: float = controller.menu_column_spacing(columns)
|
|
var want_x: float = -spacing * 0.5 if i % 2 == 0 else spacing * 0.5
|
|
if absf(slots[i][1] - want_x) > 0.01:
|
|
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (entry %d at x=%.1f, expected %.1f)" % [i, slots[i][1], want_x])
|
|
quit(1)
|
|
return true
|
|
print("4 entries packed into consecutive slots, no gaps")
|
|
|
|
# --- 6. #136: nothing overlaps, with EVERY structure on offer -
|
|
# The bug the ticket reported: the row list ran out and the last
|
|
# entry was placed on top of one already there.
|
|
for node in GameState.SKILL_TREE:
|
|
game_state.unlocked.append(String(node.id))
|
|
controller._refresh_build_status()
|
|
var all_shown: Array = _shown()
|
|
if all_shown.size() < 9:
|
|
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (only %d structures unlocked - not the crowded case)" % all_shown.size())
|
|
quit(1)
|
|
return true
|
|
for i in range(all_shown.size()):
|
|
for j in range(i + 1, all_shown.size()):
|
|
for part in [1, 2, 3]:
|
|
var a: Control = all_shown[i][part]
|
|
var b: Control = all_shown[j][part]
|
|
if _rect(a).intersects(_rect(b)):
|
|
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (%s and %s overlap at %s / %s)" \
|
|
% [all_shown[i][0], all_shown[j][0], _rect(a), _rect(b)])
|
|
quit(1)
|
|
return true
|
|
print("%d structures on offer, in %d columns, nothing overlapping" \
|
|
% [all_shown.size(), controller.menu_columns(all_shown.size())])
|
|
|
|
# --- 7. #144: and at any window shape ------------------------
|
|
# The grid is anchored to the panel's CENTRE and the status line
|
|
# above it to the panel's TOP, so the gap between them is half
|
|
# the panel height. It used to be a written-down constant, right
|
|
# only at 1280 - at 1080 the grid started above the status line
|
|
# and drew through it, which is what the ticket reported.
|
|
var menu: Control = scene.get_node("UI/BuildMenu")
|
|
var status: Control = scene.get_node("UI/BuildMenu/StatusLabel")
|
|
var restore: float = menu.offset_bottom
|
|
for panel_height in [900.0, 1080.0, 1280.0, 1600.0]:
|
|
menu.anchor_bottom = 0.0
|
|
menu.offset_top = 0.0
|
|
menu.offset_bottom = panel_height
|
|
controller._refresh_build_status()
|
|
var half: float = menu.size.y * 0.5
|
|
# The status line's own bottom, in the grid's centre-anchored
|
|
# frame - which is the comparison that was never made.
|
|
var header_bottom: float = status.offset_bottom - half
|
|
var placed: Array = _shown()
|
|
for a in range(placed.size()):
|
|
for part in [1, 2, 3]:
|
|
var box: Rect2 = _rect(placed[a][part])
|
|
if box.position.y < header_bottom:
|
|
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (at %.0fpx tall, %s is drawn at y=%.0f, above the status line at %.0f)" \
|
|
% [panel_height, placed[a][0], box.position.y, header_bottom])
|
|
quit(1)
|
|
return true
|
|
for b in range(a + 1, placed.size()):
|
|
for part2 in [1, 2, 3]:
|
|
if _rect(placed[a][part2]).intersects(_rect(placed[b][part2])):
|
|
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (at %.0fpx tall, %s and %s overlap)" \
|
|
% [panel_height, placed[a][0], placed[b][0]])
|
|
quit(1)
|
|
return true
|
|
print(" %.0fpx tall: %d columns, top row at %.0f, clear of the status line at %.0f" \
|
|
% [panel_height, controller.menu_columns(placed.size()),
|
|
_rect(placed[0][1]).position.y, header_bottom])
|
|
menu.anchor_bottom = 1.0
|
|
menu.offset_top = 0.0
|
|
menu.offset_bottom = restore
|
|
controller._refresh_build_status()
|
|
print("the grid clears the header at every window shape")
|
|
|
|
# --- 5. researched but not usable is shown, greyed ----------
|
|
var extractor_shown: bool = false
|
|
for e in shown2:
|
|
if e[0] == GameState.BUILDING_EXTRACTOR:
|
|
extractor_shown = true
|
|
if e[1].enabled:
|
|
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (extractor offered as usable with no planet under the fleet)")
|
|
quit(1)
|
|
return true
|
|
if not extractor_shown:
|
|
print("TEST_BUILD_MENU_HIDES_LOCKED: FAIL (researched extractor hidden - 'not now' is not 'not yet')")
|
|
quit(1)
|
|
return true
|
|
print("researched extractor shown but greyed, with no planet in reach")
|
|
print("TEST_BUILD_MENU_HIDES_LOCKED: PASS")
|
|
quit(0)
|
|
return true
|
|
return false
|