Initial project sync
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
extends SceneTree
|
||||
|
||||
var failures: Array[String] = []
|
||||
var started_actions: Array[StringName] = []
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_run.call_deferred()
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
await process_frame
|
||||
_check_scripts_and_scene()
|
||||
_check_boss_action_resources()
|
||||
await _check_enemy_driver_uses_action_controller()
|
||||
await _check_chart_event_reaches_boss_driver()
|
||||
_check_chart_event_action_id()
|
||||
_finish()
|
||||
|
||||
|
||||
func _check_scripts_and_scene() -> void:
|
||||
for path: String in [
|
||||
"res://scenes/components/ai_intent.gd",
|
||||
"res://scenes/enemies/enemy_action_driver.gd",
|
||||
"res://scenes/enemies/enemy.tscn",
|
||||
"res://scenes/enemies/boss.tscn",
|
||||
]:
|
||||
_expect(load(path) != null, "%s should load" % path)
|
||||
var action_dir := DirAccess.open("res://resources/actions/enemies")
|
||||
_expect(action_dir != null, "Enemy action resource directory should exist")
|
||||
|
||||
|
||||
func _check_boss_action_resources() -> void:
|
||||
var resolver_script: Script = load("res://scenes/combat/action_resolver.gd")
|
||||
_expect(resolver_script != null, "ActionResolver should load for enemy resources")
|
||||
if resolver_script == null:
|
||||
return
|
||||
resolver_script.clear_cache()
|
||||
for action_id: StringName in _boss_action_ids():
|
||||
var action: Resource = resolver_script.get_action(action_id)
|
||||
_expect(action != null, "%s should be a real Boss ActionData resource" % action_id)
|
||||
if action == null:
|
||||
continue
|
||||
_expect_bool(str(action.resource_path).begins_with("res://resources/actions/enemies/"), true, "%s should load from the enemy action directory" % action_id)
|
||||
_expect_bool(Array(action.get("input_pattern")).is_empty(), true, "%s should not expose a player input pattern" % action_id)
|
||||
_expect_bool(Array(action.get("action_tags")).has(&"enemy"), true, "%s should carry the enemy action tag" % action_id)
|
||||
_expect_bool(Array(action.get("action_tags")).has(&"boss"), true, "%s should carry the boss action tag" % action_id)
|
||||
_expect(float(action.get("startup_beats")) > 0.0, "%s should have a startup phase" % action_id)
|
||||
_expect(float(action.get("active_beats")) > 0.0, "%s should have an active phase" % action_id)
|
||||
_expect(float(action.get("recovery_beats")) > 0.0, "%s should have a recovery phase" % action_id)
|
||||
_expect_bool(str(action.get("id")).begins_with("skill_"), false, "%s should not keep legacy skill_* naming" % action_id)
|
||||
var stab: Resource = resolver_script.get_action(&"boss_lunging_stab")
|
||||
if stab != null:
|
||||
_expect_float(float(stab.get("damage_mult")), 3.0, "boss_lunging_stab damage")
|
||||
_expect_float(float(stab.get("move_mult_x")), 2.0, "boss_lunging_stab lunge distance")
|
||||
_expect_bool(Array(stab.get("defense_tags")).has(&"super_armor"), true, "boss_lunging_stab should carry super_armor defense tag")
|
||||
var shoot: Resource = resolver_script.get_action(&"boss_shoot_1")
|
||||
if shoot != null:
|
||||
_expect_equal(shoot.get("hit_type"), &"projectile", "boss_shoot_1 should use projectile hit_type")
|
||||
_expect_bool(resolver_script.resolve_pattern("boss_combo_1") == null, true, "Boss action ids should not resolve as player combo patterns")
|
||||
resolver_script.clear_cache()
|
||||
|
||||
|
||||
func _check_enemy_driver_uses_action_controller() -> void:
|
||||
var scene: PackedScene = load("res://scenes/enemies/enemy.tscn")
|
||||
if scene == null:
|
||||
return
|
||||
var enemy := scene.instantiate()
|
||||
root.add_child(enemy)
|
||||
await process_frame
|
||||
var controller: Node = enemy.get_node("ActionController")
|
||||
var driver: Node = enemy.get_node("EnemyActionDriver")
|
||||
controller.connect("action_started", _on_action_started)
|
||||
started_actions.clear()
|
||||
driver.call("start_action", &"boss_combo_1")
|
||||
_expect(started_actions.has(&"boss_combo_1"), "EnemyActionDriver should start Boss actions through ActionController")
|
||||
_expect_int(int(controller.get("phase")), 1, "Enemy action should enter Startup, not direct Active")
|
||||
enemy.queue_free()
|
||||
await process_frame
|
||||
|
||||
|
||||
func _check_chart_event_reaches_boss_driver() -> void:
|
||||
started_actions.clear()
|
||||
var actors := Node2D.new()
|
||||
actors.name = "ActorsContainer"
|
||||
root.add_child(actors)
|
||||
var boss_scene: PackedScene = load("res://scenes/enemies/boss.tscn")
|
||||
if boss_scene == null:
|
||||
actors.queue_free()
|
||||
return
|
||||
var boss := boss_scene.instantiate()
|
||||
boss.name = "Boss"
|
||||
actors.add_child(boss)
|
||||
await process_frame
|
||||
boss.get_node("ActionController").connect("action_started", _on_action_started)
|
||||
|
||||
var chart_script: Script = load("res://resources/beat_chart.gd")
|
||||
var track_script: Script = load("res://resources/chart_track.gd")
|
||||
var chart: Resource = chart_script.new()
|
||||
var track: Resource = track_script.new()
|
||||
chart.set("chart_id", &"boss_dispatch_chart")
|
||||
track.set("track_id", &"track_boss_melee")
|
||||
track.set("track_type", &"boss")
|
||||
track.set("events", [_make_event(1, &"Boss", &"boss_combo_1")])
|
||||
chart.set("tracks", [track])
|
||||
|
||||
var runner: Node = load("res://scenes/chart/chart_runner.gd").new()
|
||||
root.add_child(runner)
|
||||
runner.set("beat_time_override", 0.5)
|
||||
runner.set("actors_container_path", runner.get_path_to(actors))
|
||||
runner.call("set_chart", chart)
|
||||
runner.call("update_for_song_time", 0.5)
|
||||
_expect(started_actions.has(&"boss_combo_1"), "ChartRunner should dispatch Boss action_id to Boss EnemyActionDriver")
|
||||
runner.queue_free()
|
||||
actors.queue_free()
|
||||
await process_frame
|
||||
|
||||
|
||||
func _check_chart_event_action_id() -> void:
|
||||
var event_script: Script = load("res://resources/chart_event.gd")
|
||||
var event: Resource = event_script.new()
|
||||
_expect(_has_property(event, "action_id"), "ChartEvent should expose action_id for AIIntent dispatch")
|
||||
event.set("action_id", &"boss_2way_shoot")
|
||||
_expect_equal(event.get("action_id"), &"boss_2way_shoot", "ChartEvent action_id should be data-driven")
|
||||
|
||||
|
||||
func _make_event(beat: int, target_id: StringName, action_id: StringName) -> Resource:
|
||||
var event: Resource = load("res://resources/chart_event.gd").new()
|
||||
event.set("beat_index", beat)
|
||||
event.set("event_type", &"enemy_action")
|
||||
event.set("target_id", target_id)
|
||||
event.set("lead_beats", 0.0)
|
||||
event.set("action_id", action_id)
|
||||
return event
|
||||
|
||||
|
||||
func _boss_action_ids() -> Array[StringName]:
|
||||
return [
|
||||
&"boss_combo_1",
|
||||
&"boss_combo_2",
|
||||
&"boss_combo_3",
|
||||
&"boss_lunging_stab",
|
||||
&"boss_dash",
|
||||
&"boss_shoot_1",
|
||||
&"boss_shoot_2",
|
||||
&"boss_2way_shoot",
|
||||
]
|
||||
|
||||
|
||||
func _on_action_started(action: Resource, _intent) -> void:
|
||||
started_actions.append(StringName(str(action.get("id"))))
|
||||
|
||||
|
||||
func _has_property(object: Object, property_name: String) -> bool:
|
||||
for property: Dictionary in object.get_property_list():
|
||||
if str(property.get("name", "")) == property_name:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _expect(condition: bool, label: String) -> void:
|
||||
if not condition:
|
||||
failures.append(label)
|
||||
|
||||
|
||||
func _expect_equal(actual: Variant, expected: Variant, label: String) -> void:
|
||||
if actual != expected:
|
||||
failures.append("%s: expected %s, got %s" % [label, expected, actual])
|
||||
|
||||
|
||||
func _expect_bool(actual: bool, expected: bool, label: String) -> void:
|
||||
if actual != expected:
|
||||
failures.append("%s: expected %s, got %s" % [label, expected, actual])
|
||||
|
||||
|
||||
func _expect_float(actual: float, expected: float, label: String) -> void:
|
||||
if not is_equal_approx(actual, expected):
|
||||
failures.append("%s: expected %.3f, got %.3f" % [label, expected, actual])
|
||||
|
||||
|
||||
func _expect_int(actual: int, expected: int, label: String) -> void:
|
||||
if actual != expected:
|
||||
failures.append("%s: expected %d, got %d" % [label, expected, actual])
|
||||
|
||||
|
||||
func _finish() -> void:
|
||||
if failures.is_empty():
|
||||
print("PASS enemy action pipeline")
|
||||
quit(0)
|
||||
else:
|
||||
for failure: String in failures:
|
||||
push_error(failure)
|
||||
quit(1)
|
||||
Reference in New Issue
Block a user