extends SceneTree # Forgejo #231: on a computer each ability has a key that fires it outright. # # Selecting a weapon with a number key and then clicking is two acts for one # shot, and the selection is invisible until you look at the buttons - which # is what made the abilities hard to use with a keyboard. Q, E and space now # fire the gas bomb, the laser and the dash where the mouse already points. # # Covers: # A. the three keys map to the three abilities, in the documented order # B. pressing one fires THAT ability and no other # C. it fires at the mouse, so pointing is the aim # D. Q and E no longer pivot, which is what they used to do # E. the number keys still choose what a CLICK does - #231 replaces the # selection as a way to FIRE, not the click path itself # F. touch is untouched: on a phone these keys do nothing var scene: Node var mouse: Node var controller: Node var game_state: Node var elapsed: float = 0.0 func _initialize() -> void: game_state = root.get_node("GameState") game_state.buildings.clear() game_state.reset_new_game() game_state.intro_active = false scene = load("res://world/Main.tscn").instantiate() root.add_child(scene) current_scene = scene mouse = scene.get_node("MouseCombatController") # GameController is the ROOT of Main.tscn, not a child of it - the same # script roots the planet surface too. controller = scene func _key(code: int) -> InputEventKey: var event := InputEventKey.new() event.keycode = code event.pressed = true return event func _process(delta: float) -> bool: elapsed += delta if elapsed < 0.4: return false # --- A. the mapping is the documented one ---------------------------- if mouse.ABILITY_ORDER.size() != 3: return _fail("expected 3 abilities, found %d" % mouse.ABILITY_ORDER.size()) var expected: Dictionary = {KEY_Q: "GasBombController", KEY_E: "LaserController", KEY_SPACE: "DashController"} for code in expected: if not mouse.ABILITY_KEYS.has(code): return _fail("key %d is not bound to an ability" % code) var slot: int = int(mouse.ABILITY_KEYS[code]) if String(mouse.ABILITY_ORDER[slot]) != String(expected[code]): return _fail("key %d fires %s, expected %s" \ % [code, mouse.ABILITY_ORDER[slot], expected[code]]) print("Q, E and space map to the bomb, the laser and the dash") # --- D. and they no longer pivot -------------------------------------- if controller.KEY_PIVOT_CW in [KEY_Q, KEY_E, KEY_SPACE] \ or controller.KEY_PIVOT_CCW in [KEY_Q, KEY_E, KEY_SPACE]: return _fail("pivot still uses an ability key (cw=%d ccw=%d)" \ % [controller.KEY_PIVOT_CW, controller.KEY_PIVOT_CCW]) print("pivot moved to %d/%d, clear of the ability keys" \ % [controller.KEY_PIVOT_CCW, controller.KEY_PIVOT_CW]) # --- B/C. the KEY itself reaches that ability, aimed at the mouse ----- # Driven through _unhandled_input rather than by calling fire_at(), or # this would prove the abilities work and nothing about the binding. # # Observed through aim_world: the ability branch overwrites it with the # live cursor before firing, so a value that survives means the branch # never ran. The abilities themselves are locked in a fresh game, so # fire_at() correctly refuses - the wiring is what is under test here. mouse.pointer_mode = 1 # PointerMode.ON - a headless display reports no mouse var sentinel := Vector2(98765.0, -4321.0) for code in [KEY_Q, KEY_E, KEY_SPACE]: mouse.aim_world = sentinel mouse._unhandled_input(_key(code)) if mouse.aim_world == sentinel: var slot: int = int(mouse.ABILITY_KEYS[code]) return _fail("key %d never reached %s" % [code, mouse.ABILITY_ORDER[slot]]) print("Q, E and space each reach their ability through the input path") # A key bound to nothing must fall through to the number-key branch # rather than being swallowed by the ability one. mouse.aim_world = sentinel mouse._unhandled_input(_key(KEY_K)) if mouse.aim_world != sentinel: return _fail("an unbound key was treated as an ability trigger") print("an unbound key is left alone") # --- E. the number keys still select for the click path --------------- if not mouse.select_weapon(1): return _fail("the number keys no longer select a weapon for a click") if mouse.selected_weapon != 1: return _fail("selecting weapon 1 left selected_weapon at %d" % mouse.selected_weapon) print("the number keys still choose what a click fires") # --- F. and none of this reaches a phone ------------------------------ mouse.pointer_mode = 2 # PointerMode.OFF if mouse.is_enabled(): return _fail("mouse combat is live with the pointer mode off") print("with a touch pointer the keys are inert") print("TEST_ABILITY_KEYS: PASS") quit(0) return true func _fail(message: String) -> bool: print("TEST_ABILITY_KEYS: FAIL (%s)" % message) quit(1) return true