138 lines
4.8 KiB
GDScript3
138 lines
4.8 KiB
GDScript3
|
|
extends SceneTree
|
||
|
|
|
||
|
|
# Headless gameplay test: run with
|
||
|
|
# godot --headless -s res://tests/test_modal_blocks_input.gd
|
||
|
|
# With a panel open, nothing behind it may react to a touch.
|
||
|
|
#
|
||
|
|
# Two separate leaks, because there are two separate input paths:
|
||
|
|
#
|
||
|
|
# 1. GUI clicks. The skill and formation panels cannot block these the
|
||
|
|
# normal way (mouse_filter = STOP), because the skill tree and the
|
||
|
|
# formation editor are driven from raw _unhandled_input, and a Control
|
||
|
|
# that consumes touches through the GUI system swallows them before
|
||
|
|
# they ever get there. So the panels stay transparent and the buttons
|
||
|
|
# check whether they are behind one.
|
||
|
|
# 2. Raw input. A touch landing on the panel but missing a skill node was
|
||
|
|
# left unhandled, so it fell through to GameController - which sits on
|
||
|
|
# the ROOT node and therefore sees everything its children ignore - and
|
||
|
|
# moved the fleet or pinch-zoomed the world behind the menu.
|
||
|
|
#
|
||
|
|
# Verifies:
|
||
|
|
# 1. a button outside an open panel does not fire
|
||
|
|
# 2. a button INSIDE the open panel still does (or the panel is unusable)
|
||
|
|
# 3. with every panel closed, that same outside button fires normally, so
|
||
|
|
# a pass cannot just mean "the button never works"
|
||
|
|
# 4. a touch on an open panel does not reach the world behind it
|
||
|
|
|
||
|
|
var scene: Node
|
||
|
|
var game_state: Node
|
||
|
|
var fleet: Node
|
||
|
|
var skill_menu: Control
|
||
|
|
var map_button: Control
|
||
|
|
var skill_close: Control
|
||
|
|
var stage: int = 0
|
||
|
|
var elapsed: float = 0.0
|
||
|
|
var outside_fired: bool = false
|
||
|
|
var inside_fired: bool = false
|
||
|
|
|
||
|
|
|
||
|
|
func _initialize() -> void:
|
||
|
|
game_state = root.get_node("GameState")
|
||
|
|
game_state.buildings.clear()
|
||
|
|
game_state.extractors.clear()
|
||
|
|
game_state.transporters.clear()
|
||
|
|
scene = load("res://Main.tscn").instantiate()
|
||
|
|
root.add_child(scene)
|
||
|
|
fleet = scene.get_node("Fleet")
|
||
|
|
skill_menu = scene.get_node("UI/SkillMenu")
|
||
|
|
map_button = scene.get_node("UI/MapButton")
|
||
|
|
skill_close = scene.get_node("UI/SkillMenu/CloseButton")
|
||
|
|
map_button.pressed.connect(func(): outside_fired = true)
|
||
|
|
skill_close.pressed.connect(func(): inside_fired = true)
|
||
|
|
|
||
|
|
|
||
|
|
func _tap(button: Control) -> void:
|
||
|
|
# Straight at the button's own _gui_input, which is where a real tap
|
||
|
|
# lands once Godot has routed it.
|
||
|
|
var centre: Vector2 = button.size / 2.0
|
||
|
|
for pressed in [true, false]:
|
||
|
|
var ev: InputEventScreenTouch = InputEventScreenTouch.new()
|
||
|
|
ev.pressed = pressed
|
||
|
|
ev.position = centre
|
||
|
|
button._gui_input(ev)
|
||
|
|
|
||
|
|
|
||
|
|
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)
|
||
|
|
|
||
|
|
# --- 3. baseline: it works with nothing open ----------------
|
||
|
|
outside_fired = false
|
||
|
|
_tap(map_button)
|
||
|
|
if not outside_fired:
|
||
|
|
print("TEST_MODAL_BLOCKS_INPUT: FAIL (the button never fires even with no panel open - the test proves nothing)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
print("no panel open -> outside button fires")
|
||
|
|
# That tap actually OPENED the world map, which is itself a modal
|
||
|
|
# panel - leave it open and it correctly blocks the research
|
||
|
|
# panel's own buttons, which is a true result but not the one
|
||
|
|
# being measured here.
|
||
|
|
scene.get_node("UI/WorldMap").visible = false
|
||
|
|
|
||
|
|
skill_menu.visible = true
|
||
|
|
elapsed = 0.0
|
||
|
|
stage = 1
|
||
|
|
return false
|
||
|
|
1:
|
||
|
|
if elapsed < 0.2:
|
||
|
|
return false
|
||
|
|
# --- 1. behind an open panel --------------------------------
|
||
|
|
outside_fired = false
|
||
|
|
_tap(map_button)
|
||
|
|
if outside_fired:
|
||
|
|
print("TEST_MODAL_BLOCKS_INPUT: FAIL (a button behind the open research panel still fired)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
print("research panel open -> button behind it is inert")
|
||
|
|
|
||
|
|
# --- 2. inside it ------------------------------------------
|
||
|
|
inside_fired = false
|
||
|
|
_tap(skill_close)
|
||
|
|
if not inside_fired:
|
||
|
|
print("TEST_MODAL_BLOCKS_INPUT: FAIL (the panel's own close button stopped working)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
print("panel's own button still works")
|
||
|
|
elapsed = 0.0
|
||
|
|
stage = 2
|
||
|
|
return false
|
||
|
|
2:
|
||
|
|
if elapsed < 0.2:
|
||
|
|
return false
|
||
|
|
# --- 4. raw input must not reach the world ------------------
|
||
|
|
skill_menu.visible = true
|
||
|
|
var before: Vector2 = fleet.fleet_target
|
||
|
|
var controller: Node = scene.get_node("BuildController")
|
||
|
|
var ev: InputEventScreenTouch = InputEventScreenTouch.new()
|
||
|
|
ev.pressed = true
|
||
|
|
ev.position = Vector2(360.0, 900.0)
|
||
|
|
controller._unhandled_input(ev)
|
||
|
|
if not root.get_viewport().is_input_handled():
|
||
|
|
print("TEST_MODAL_BLOCKS_INPUT: FAIL (a touch on the open panel was left unhandled - it falls through to the world behind)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
if fleet.fleet_target != before:
|
||
|
|
print("TEST_MODAL_BLOCKS_INPUT: FAIL (the fleet moved from a touch on the panel)")
|
||
|
|
quit(1)
|
||
|
|
return true
|
||
|
|
print("touch on the panel consumed, fleet target unchanged")
|
||
|
|
print("TEST_MODAL_BLOCKS_INPUT: PASS")
|
||
|
|
quit(0)
|
||
|
|
return true
|
||
|
|
return false
|