170 lines
6.1 KiB
GDScript
170 lines
6.1 KiB
GDScript
extends SceneTree
|
|
|
|
var failures: Array[String] = []
|
|
var started: Array[StringName] = []
|
|
|
|
|
|
func _init() -> void:
|
|
_run.call_deferred()
|
|
|
|
|
|
func _run() -> void:
|
|
var fixture := await _fixture()
|
|
if fixture.is_empty():
|
|
_finish()
|
|
return
|
|
var input_component: Node = fixture["input"]
|
|
var controller: Node = fixture["controller"]
|
|
var combo: Node = fixture["combo"]
|
|
controller.connect("action_started", _on_action_started)
|
|
input_component.connect("intent_created", controller.submit_intent)
|
|
|
|
# 每次按键前把节奏钟钉到一个新的拍心:判定确定为 perfect(消除旧的
|
|
# 墙钟 flake),且不与 new1 的"节奏点一次性消耗"规则冲突。
|
|
_pin_beat(100)
|
|
input_component.call("handle_input_event", _key_event(KEY_A))
|
|
_expect_bool(started.is_empty(), false, "physical A should start an action")
|
|
_expect_string(_last_started(), "ground_attack_left_1", "physical A should start left attack")
|
|
controller.call("_reset_to_idle")
|
|
_pin_beat(101)
|
|
input_component.call("handle_input_event", _key_event(KEY_A))
|
|
_expect_array(combo.call("get_slots"), [&"A", &"A"], "physical A A should fill two slots")
|
|
_expect_string(_last_started(), "ground_attack_left_2", "physical A A should start left second attack")
|
|
|
|
controller.call("_reset_to_idle")
|
|
combo.call("clear", &"test-reset")
|
|
started.clear()
|
|
_pin_beat(102)
|
|
input_component.call("handle_input_event", _key_event(KEY_D))
|
|
controller.call("_reset_to_idle")
|
|
_pin_beat(103)
|
|
input_component.call("handle_input_event", _key_event(KEY_D))
|
|
_expect_array(combo.call("get_slots"), [&"D", &"D"], "physical D D should fill two slots")
|
|
_expect_string(_last_started(), "ground_attack_right_2", "physical D D should start right second attack")
|
|
|
|
controller.call("_reset_to_idle")
|
|
combo.call("clear", &"test-reset")
|
|
started.clear()
|
|
_pin_beat(104)
|
|
input_component.call("handle_input_event", _key_event(KEY_W))
|
|
_expect_string(_last_started(), "launcher_up", "physical W should start launcher")
|
|
_expect_array(combo.call("get_slots"), [&"W"], "physical W should enter ComboWindow")
|
|
|
|
controller.call("_reset_to_idle")
|
|
combo.call("clear", &"test-reset")
|
|
started.clear()
|
|
_pin_beat(105)
|
|
input_component.call("handle_input_event", _key_event(KEY_S))
|
|
_expect_string(_last_started(), "block_start", "physical S should start temporary block animation")
|
|
_expect_array(combo.call("get_slots"), [&"S"], "physical S should enter ComboWindow")
|
|
|
|
controller.call("_reset_to_idle")
|
|
started.clear()
|
|
_pin_beat(106)
|
|
input_component.call("handle_input_event", _key_event(KEY_A))
|
|
controller.call("_reset_to_idle")
|
|
_pin_beat(107)
|
|
input_component.call("handle_input_event", _key_event(KEY_S))
|
|
_expect_string(_last_started(), "block_start", "physical S should still block after a previous basic input remains in ComboWindow")
|
|
|
|
fixture["root"].free()
|
|
_finish()
|
|
|
|
|
|
func _last_started() -> String:
|
|
if started.is_empty():
|
|
return ""
|
|
return str(started[started.size() - 1])
|
|
|
|
|
|
## 把 RhythmManager 的时钟偏移钉到指定拍心:紧随其后的实时按键判定为 perfect。
|
|
func _pin_beat(beat: int) -> void:
|
|
var rhythm := root.get_node_or_null("RhythmManager")
|
|
if rhythm == null:
|
|
failures.append("RhythmManager autoload should exist for beat pinning")
|
|
return
|
|
rhythm.set("running", true)
|
|
var beat_time := float(rhythm.get("beat_time"))
|
|
var beat_offset := float(rhythm.get("beat_offset"))
|
|
rhythm.set("_clock_offset", float(beat) * beat_time - beat_offset - Time.get_ticks_msec() / 1000.0)
|
|
|
|
|
|
func _expect_bool(actual: bool, expected: bool, label: String) -> void:
|
|
if actual != expected:
|
|
failures.append("%s: expected %s, got %s" % [label, expected, actual])
|
|
|
|
|
|
func _fixture() -> Dictionary:
|
|
var input_script: Script = load("res://scenes/components/input_component.gd")
|
|
var combo_script: Script = load("res://scenes/components/combo_window.gd")
|
|
var resolver_script: Script = load("res://scenes/combat/action_resolver.gd")
|
|
var state_script: Script = load("res://scenes/components/state_machine.gd")
|
|
var energy_script: Script = load("res://scenes/components/energy_component.gd")
|
|
var controller_script: Script = load("res://scenes/components/action_controller.gd")
|
|
if input_script == null or combo_script == null or resolver_script == null or state_script == null or energy_script == null or controller_script == null:
|
|
failures.append("Phase 3 input/action scripts should load")
|
|
return {}
|
|
var fixture_root := Node.new()
|
|
root.add_child(fixture_root)
|
|
var input_component: Node = input_script.new()
|
|
input_component.name = "InputComponent"
|
|
fixture_root.add_child(input_component)
|
|
var combo: Node = combo_script.new()
|
|
combo.name = "ComboWindow"
|
|
fixture_root.add_child(combo)
|
|
var resolver: Node = resolver_script.new()
|
|
resolver.name = "ActionResolver"
|
|
fixture_root.add_child(resolver)
|
|
var state: Node = state_script.new()
|
|
state.name = "StateMachine"
|
|
fixture_root.add_child(state)
|
|
var energy: Node = energy_script.new()
|
|
energy.name = "EnergyComponent"
|
|
fixture_root.add_child(energy)
|
|
var controller: Node = controller_script.new()
|
|
controller.name = "ActionController"
|
|
controller.set("combo_window_path", NodePath("../ComboWindow"))
|
|
controller.set("action_resolver_path", NodePath("../ActionResolver"))
|
|
controller.set("state_machine_path", NodePath("../StateMachine"))
|
|
fixture_root.add_child(controller)
|
|
await process_frame
|
|
energy.call("set_values", 99, 99)
|
|
return {
|
|
"root": fixture_root,
|
|
"input": input_component,
|
|
"combo": combo,
|
|
"controller": controller,
|
|
}
|
|
|
|
|
|
func _key_event(key: Key) -> InputEventKey:
|
|
var event := InputEventKey.new()
|
|
event.keycode = key
|
|
event.physical_keycode = key
|
|
event.pressed = true
|
|
return event
|
|
|
|
|
|
func _on_action_started(action: Resource, _intent) -> void:
|
|
started.append(StringName(str(action.get("id"))))
|
|
|
|
|
|
func _expect_array(actual: Array, expected: Array, label: String) -> void:
|
|
if actual != expected:
|
|
failures.append("%s: expected %s, got %s" % [label, expected, actual])
|
|
|
|
|
|
func _expect_string(actual: String, expected: String, label: String) -> void:
|
|
if actual != expected:
|
|
failures.append("%s: expected %s, got %s" % [label, expected, actual])
|
|
|
|
|
|
func _finish() -> void:
|
|
if failures.is_empty():
|
|
print("PASS player combo input")
|
|
quit(0)
|
|
else:
|
|
for failure: String in failures:
|
|
push_error(failure)
|
|
quit(1)
|