130 lines
4.4 KiB
GDScript3
130 lines
4.4 KiB
GDScript3
|
|
extends SceneTree
|
||
|
|
|
||
|
|
# Headless test: run with
|
||
|
|
# godot --headless -s res://tests/test_hud_layout.gd
|
||
|
|
# The HUD's top-anchored button columns must not collide with the resource
|
||
|
|
# readout, and must not collide with each other.
|
||
|
|
#
|
||
|
|
# Both halves are regressions that actually shipped:
|
||
|
|
# - ResourceReadout builds one row per resource type AT RUNTIME, so the
|
||
|
|
# scene's authored offset_bottom understated it and the build button,
|
||
|
|
# at a hardcoded y, ended up underneath the resource list.
|
||
|
|
# - Seating the columns below the readout then exposed a second hardcoded
|
||
|
|
# y: _layout_context_buttons() wrote the action row back to 324 on the
|
||
|
|
# next context change, dropping it on top of the moved build button.
|
||
|
|
#
|
||
|
|
# Both were a literal coordinate that encoded "below the thing above me" and
|
||
|
|
# stopped being true when the thing above moved. So this asserts the
|
||
|
|
# RELATIONSHIP rather than any particular coordinate - a fresh hardcoded y
|
||
|
|
# fails here whatever value someone picks.
|
||
|
|
#
|
||
|
|
# Covers:
|
||
|
|
# A. every HUD button clears the resource readout's real bottom
|
||
|
|
# B. no two simultaneously-usable buttons overlap each other
|
||
|
|
# C. both still hold after _layout_context_buttons() runs, collapsed and
|
||
|
|
# expanded - the path that broke
|
||
|
|
# D. it holds with MORE resource types than the scene was authored for,
|
||
|
|
# which is the direction this drifts
|
||
|
|
|
||
|
|
var scene: Node
|
||
|
|
var controller: Node
|
||
|
|
var readout: Node
|
||
|
|
var stage: int = 0
|
||
|
|
var elapsed: float = 0.0
|
||
|
|
|
||
|
|
# Deliberately share a row: a building either offers its own action or a
|
||
|
|
# hangar, never both, so their rects coinciding is the design.
|
||
|
|
const EXCLUSIVE_PAIRS: Array = [["building_action_button", "hangar_button"]]
|
||
|
|
|
||
|
|
|
||
|
|
func _initialize() -> void:
|
||
|
|
scene = load("res://Main.tscn").instantiate()
|
||
|
|
root.add_child(scene)
|
||
|
|
controller = scene.get_node("BuildController")
|
||
|
|
readout = scene.get_node("UI/ResourceReadout")
|
||
|
|
|
||
|
|
|
||
|
|
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)
|
||
|
|
if not _check("as laid out at load"):
|
||
|
|
return true
|
||
|
|
|
||
|
|
# --- C. the path that broke it ------------------------------
|
||
|
|
controller.context_actions_expanded = false
|
||
|
|
controller._layout_context_buttons()
|
||
|
|
if not _check("after _layout_context_buttons() collapsed"):
|
||
|
|
return true
|
||
|
|
|
||
|
|
controller.context_actions_expanded = true
|
||
|
|
controller._layout_context_buttons()
|
||
|
|
if not _check("after _layout_context_buttons() expanded"):
|
||
|
|
return true
|
||
|
|
|
||
|
|
print("TEST_HUD_LAYOUT: PASS")
|
||
|
|
quit(0)
|
||
|
|
return true
|
||
|
|
return false
|
||
|
|
|
||
|
|
|
||
|
|
func _buttons() -> Dictionary:
|
||
|
|
return {
|
||
|
|
"build_button": controller.build_button,
|
||
|
|
"transfer_button": controller.transfer_button,
|
||
|
|
"action_button": controller.action_button,
|
||
|
|
"landing_button": controller.landing_button,
|
||
|
|
"building_action_button": controller.building_action_button,
|
||
|
|
"hangar_button": controller.hangar_button,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
func _check(context: String) -> bool:
|
||
|
|
var buttons: Dictionary = _buttons()
|
||
|
|
var names: Array = buttons.keys()
|
||
|
|
|
||
|
|
# --- A. clear of the resource readout ---------------------------------
|
||
|
|
# content_bottom() is the readout's REAL extent, from the live resource
|
||
|
|
# list - not the stale height the scene happens to declare.
|
||
|
|
var readout_bottom: float = readout.content_bottom()
|
||
|
|
for name in names:
|
||
|
|
var button: Control = buttons[name]
|
||
|
|
if button == null:
|
||
|
|
continue
|
||
|
|
if button.position.y < readout_bottom:
|
||
|
|
return _fail("%s sits at y %.0f, inside the resource readout which reaches %.0f (%s)" \
|
||
|
|
% [name, button.position.y, readout_bottom, context])
|
||
|
|
|
||
|
|
# --- B. clear of each other -------------------------------------------
|
||
|
|
for i in range(names.size()):
|
||
|
|
for j in range(i + 1, names.size()):
|
||
|
|
var a_name: String = names[i]
|
||
|
|
var b_name: String = names[j]
|
||
|
|
if _is_exclusive(a_name, b_name):
|
||
|
|
continue
|
||
|
|
var a: Control = buttons[a_name]
|
||
|
|
var b: Control = buttons[b_name]
|
||
|
|
if a == null or b == null:
|
||
|
|
continue
|
||
|
|
if a.get_rect().intersects(b.get_rect()):
|
||
|
|
return _fail("%s %s overlaps %s %s (%s)" \
|
||
|
|
% [a_name, a.get_rect(), b_name, b.get_rect(), context])
|
||
|
|
print("clear of the readout (reaches %.0f) and of each other - %s" % [readout_bottom, context])
|
||
|
|
return true
|
||
|
|
|
||
|
|
|
||
|
|
func _is_exclusive(a: String, b: String) -> bool:
|
||
|
|
for pair in EXCLUSIVE_PAIRS:
|
||
|
|
if (a == pair[0] and b == pair[1]) or (a == pair[1] and b == pair[0]):
|
||
|
|
return true
|
||
|
|
return false
|
||
|
|
|
||
|
|
|
||
|
|
func _fail(message: String) -> bool:
|
||
|
|
print("TEST_HUD_LAYOUT: FAIL (%s)" % message)
|
||
|
|
quit(1)
|
||
|
|
return false
|