extends SceneTree # Headless gameplay test: run with # godot --headless -s res://tests/test_desktop_combat.gd # Forgejo #80: on a computer the mouse aims and the left button fires, and the # number keys pick the weapon - 1 the fleet's guns, 2 and up the abilities. # # Covers: # A. the weapon numbering is FIXED and named, so "2" is the gas bomb in # every scene rather than whatever node order happens to produce # B. holding the button with weapon 1 selected points the fleet's guns at # the cursor, through the same controller the stick uses - not a second # thing that aims # C. releasing stops it # D. the stick WINS while it is held, so a mouse resting somewhere cannot # override a finger # E. selecting an ability highlights its button, so what a click will do is # on screen rather than remembered # F. it is inert on a touch device. Godot emulates a mouse button from every # touch, so without that guard a tap on a phone would fire the guns var game_state: Node var main: Node var fleet: Node var mouse: Node var fire_stick: Node var stick: Control 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.reset_progression() main = load("res://world/Main.tscn").instantiate() root.add_child(main) current_scene = main fleet = main.get_node("Fleet") mouse = main.get_node("MouseCombatController") # Checked here rather than at first use: a script that fails to PARSE # leaves a bare Node2D in the scene, and every call against it becomes a # per-frame "nonexistent function" error that runs until the harness # times the test out three minutes later instead of failing in one line. if not mouse.has_method("weapon_count"): print("TEST_DESKTOP_COMBAT: FAIL (MouseCombatController.gd did not load - see the parse error above)") quit(1) return fire_stick = main.get_node("FireStickController") stick = main.get_node("UI/FireStick") func _weapon_heading() -> Vector2: return fire_stick.aim_direction func _process(delta: float) -> bool: elapsed += delta match stage: 0: if elapsed < 0.3: return false main.get_node("EnemySpawner").set_process(false) main.get_node("AsteroidField").set_process(false) for hazard in get_nodes_in_group("asteroids") + get_nodes_in_group("enemies"): if is_instance_valid(hazard): hazard.queue_free() fleet.spawn_ships(1, Vector2.ZERO, PackedStringArray([GameState.VESSEL_INTERCEPTOR])) # --- A. fixed, named ordering --------------------------------- if mouse.weapon_count() != mouse.abilities.size() + 1: return _fail("weapon count %d does not match 1 + %d abilities" \ % [mouse.weapon_count(), mouse.abilities.size()]) if mouse.abilities.is_empty(): return _fail("no ability controllers were resolved, so the number keys address nothing") if mouse.abilities[0] != main.get_node("GasBombController"): return _fail("weapon 2 is not the gas bomb - the ticket names it explicitly") if not mouse.select_weapon(0): return _fail("could not select the default weapon") if mouse.select_weapon(mouse.weapon_count()): return _fail("selecting a weapon past the end was accepted") print("weapon 1 is the fleet's guns, weapon 2 is the gas bomb, %d in total" \ % mouse.weapon_count()) # --- F. inert while the player is using touch ------------------ # #171: the gate used to be "does this machine have a # touchscreen", which is a capability rather than a fact and is # answered wrongly by every browser on a touch-capable laptop. It # is now the last input actually used - which, unlike the display # query, can be driven here, so both branches are exercised # instead of whichever one this machine happens to report. if mouse.pointer_mode != mouse.PointerMode.AUTO: return _fail("the scene ships with an overridden pointer mode, so what a real device does is not what was tested") game_state.note_touch_input() if mouse.is_enabled(): return _fail("mouse combat is armed while the player is using touch - a tap would fire the guns") game_state.last_touch_msec = -1 game_state.note_pointer_input() if not mouse.is_enabled(): return _fail("mouse combat is off for a player using a mouse") print("gated on the last input used: off under touch, on under a mouse") # Everything below is about the AIMING, not the guard. Headless # reports a touchscreen, so the mode is forced on here - otherwise # these cases would silently never run and this file would test # nothing but the line above. mouse.pointer_mode = mouse.PointerMode.ON if not mouse.is_enabled(): return _fail("forcing the pointer mode on did not enable it") stage = 1 elapsed = 0.0 return false 1: if elapsed < 0.1: return false # --- B. holding the button aims the guns at the cursor --------- mouse.select_weapon(0) mouse.aim_world = fleet.get_centroid() + Vector2(0.0, -500.0) mouse.firing = true mouse._process(0.0) fire_stick._process(0.0) var heading: Vector2 = _weapon_heading().normalized() if heading.distance_to(Vector2(0.0, -1.0)) > 0.05: return _fail("holding the button aimed along %s, but the cursor was straight up" % heading) print("holding the button aims the guns at the cursor") # --- C. releasing stops ----------------------------------------- mouse.firing = false mouse._process(0.0) fire_stick._process(0.0) if _weapon_heading() != Vector2.ZERO: return _fail("releasing the button left the guns aimed at %s" % _weapon_heading()) print("releasing stops the guns") # --- D. a held stick wins --------------------------------------- var centre: Vector2 = stick.global_position + stick.size * 0.5 var press := InputEventScreenTouch.new() press.index = 9 press.pressed = true press.position = centre stick.handle_touch(press) var drag := InputEventScreenDrag.new() drag.index = 9 drag.position = centre + Vector2(stick.max_radius, 0.0) stick.handle_drag(drag) mouse.aim_world = fleet.get_centroid() + Vector2(0.0, -500.0) mouse.firing = true mouse._process(0.0) fire_stick._process(0.0) var contested: Vector2 = _weapon_heading().normalized() if contested.distance_to(Vector2(1.0, 0.0)) > 0.05: return _fail("with the stick pushed right and the mouse aiming up, the guns went %s - the stick must win" % contested) print("a held stick overrides the mouse") var release := InputEventScreenTouch.new() release.index = 9 release.pressed = false release.position = centre stick.handle_touch(release) mouse.firing = false # --- E. the chosen ability is shown ---------------------------- mouse.select_weapon(1) var bomb_button: Node = mouse.abilities[0].button if not bomb_button.highlighted: return _fail("selecting the gas bomb did not highlight its button") mouse.select_weapon(0) if bomb_button.highlighted: return _fail("going back to the guns left the gas bomb still highlighted") print("the selected ability's own button carries the selection") print("TEST_DESKTOP_COMBAT: PASS") quit(0) return true return false func _fail(message: String) -> bool: print("TEST_DESKTOP_COMBAT: FAIL (%s)" % message) quit(1) return true