121 lines
4.7 KiB
GDScript3
121 lines
4.7 KiB
GDScript3
|
|
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
|