Initial project sync
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
extends SceneTree
|
||||
|
||||
## 2026-07-05 第二轮策划需求契约:剑雨放大(范围/视觉翻倍、伤害不变)、
|
||||
## 敌弹降速 80%(玩家斩波不变)、受击不转身 + 击退背离攻击者、
|
||||
## 胜负停留时间 ×3、结算中文 + RANK 只留等级。
|
||||
|
||||
var failures: Array[String] = []
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_run.call_deferred()
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var settings := root.get_node_or_null("GameSettings")
|
||||
if settings != null:
|
||||
settings.call("set_difficulty", &"normal")
|
||||
|
||||
_check_jian_yu_resources()
|
||||
_check_projectile_speeds()
|
||||
_check_knockback_direction()
|
||||
_check_heading_gate()
|
||||
_check_flow_delays()
|
||||
await _check_result_screen()
|
||||
|
||||
if settings != null:
|
||||
settings.call("set_difficulty", &"normal")
|
||||
_finish()
|
||||
|
||||
|
||||
func _check_jian_yu_resources() -> void:
|
||||
var expected := {
|
||||
1: {"damage_mult": 1.8, "base_cost": 20.0},
|
||||
2: {"damage_mult": 2.8, "base_cost": 35.0},
|
||||
3: {"damage_mult": 4.2, "base_cost": 50.0},
|
||||
}
|
||||
for side: String in ["left", "right"]:
|
||||
for level: int in [1, 2, 3]:
|
||||
var path := "res://resources/actions/jian_yu_%s_lv%d.tres" % [side, level]
|
||||
var action := load(path) as Resource
|
||||
if action == null:
|
||||
failures.append("jian_yu resource missing: %s" % path)
|
||||
continue
|
||||
_expect_float(float(action.get("range")), 280.0, "%s range doubled to 280" % path)
|
||||
_expect_float(float(action.get("damage_mult")), expected[level]["damage_mult"], "%s damage unchanged" % path)
|
||||
_expect_float(float(action.get("base_cost")), expected[level]["base_cost"], "%s cost unchanged" % path)
|
||||
|
||||
|
||||
func _check_projectile_speeds() -> void:
|
||||
var container: Node2D = load("res://scenes/stage/actors_container.gd").new()
|
||||
container.name = "ActorsContainerUnderTest"
|
||||
root.add_child(container)
|
||||
container.call("_on_projectile_requested", null, Vector2.ZERO, Vector2.LEFT, {"team": &"enemy"})
|
||||
container.call("_on_projectile_requested", null, Vector2.ZERO, Vector2.RIGHT, {"team": &"player"})
|
||||
var children := container.get_children()
|
||||
_expect_int(children.size(), 2, "both projectiles spawn")
|
||||
if children.size() == 2:
|
||||
_expect_float(float(children[0].get("speed")), 416.0, "enemy bullets fly at 80% speed (416)")
|
||||
_expect_float(float(children[1].get("speed")), 520.0, "player wave keeps full speed (520)")
|
||||
container.free()
|
||||
|
||||
|
||||
func _check_knockback_direction() -> void:
|
||||
var combat := root.get_node_or_null("CombatManager")
|
||||
if combat == null:
|
||||
failures.append("CombatManager autoload missing")
|
||||
return
|
||||
var receiver := Node2D.new()
|
||||
receiver.name = "KnockbackReceiver"
|
||||
root.add_child(receiver)
|
||||
receiver.global_position = Vector2(100.0, 0.0)
|
||||
var from_right := {"from": Vector2(200.0, 0.0)}
|
||||
var from_left := {"from": Vector2(0.0, 0.0)}
|
||||
_expect_float(float(combat.call("_knockback_x_away_from_attacker", receiver, from_right, 120.0)), -120.0, "attacker on the right pushes receiver left")
|
||||
_expect_float(float(combat.call("_knockback_x_away_from_attacker", receiver, from_left, 120.0)), 120.0, "attacker on the left pushes receiver right")
|
||||
_expect_float(float(combat.call("_knockback_x_away_from_attacker", receiver, {}, 120.0)), 120.0, "missing attacker position keeps legacy +X")
|
||||
receiver.free()
|
||||
|
||||
|
||||
func _check_heading_gate() -> void:
|
||||
var actor_script := GDScript.new()
|
||||
actor_script.source_code = "extends CharacterBody2D\nvar heading := Vector2.LEFT\nvar speed := 180.0\nvar height := 0.0\nvar height_speed := 0.0\nvar state := &\"idle\"\n"
|
||||
actor_script.reload()
|
||||
var actor := CharacterBody2D.new()
|
||||
actor.name = "HeadingActor"
|
||||
actor.set_script(actor_script)
|
||||
var motor: Node = load("res://scenes/components/movement_motor.gd").new()
|
||||
motor.name = "MovementMotor"
|
||||
actor.add_child(motor)
|
||||
root.add_child(actor)
|
||||
|
||||
actor.set("heading", Vector2.LEFT)
|
||||
motor.call("apply_knockback", Vector2(120.0, 0.0))
|
||||
motor.call("set_heading")
|
||||
_expect_bool(Vector2(actor.get("heading")) == Vector2.LEFT, true, "knockback velocity must not flip heading (受击不转身)")
|
||||
|
||||
# 残速衰减到 0 后门禁解除,自主移动照常改朝向。
|
||||
motor.call("_decay_stray_velocity", 1.0)
|
||||
_expect_float(actor.velocity.x, 0.0, "knockback stray velocity decays to zero")
|
||||
actor.velocity.x = 50.0
|
||||
motor.call("set_heading")
|
||||
_expect_bool(Vector2(actor.get("heading")) == Vector2.RIGHT, true, "voluntary velocity flips heading again after decay")
|
||||
actor.free()
|
||||
|
||||
|
||||
func _check_flow_delays() -> void:
|
||||
var flow_script := load("res://autoload/game_flow_manager.gd") as GDScript
|
||||
var constants := flow_script.get_script_constant_map()
|
||||
_expect_float(float(constants.get("VICTORY_SCREEN_DELAY", 0.0)), 3.6, "victory delay tripled to 3.6s")
|
||||
_expect_float(float(constants.get("DEFEAT_SCREEN_DELAY", 0.0)), 4.8, "defeat delay tripled to 4.8s")
|
||||
|
||||
|
||||
func _check_result_screen() -> void:
|
||||
var flow: Node = load("res://autoload/game_flow_manager.gd").new()
|
||||
flow.name = "FlowRound2"
|
||||
flow.set("manage_scene", false)
|
||||
root.add_child(flow)
|
||||
await process_frame
|
||||
|
||||
var labels: Dictionary = flow.get("_result_labels")
|
||||
flow.set("_victory", true)
|
||||
flow.call("_populate_result_screen")
|
||||
var rank_label := labels.get("rank", null) as Label
|
||||
if rank_label == null:
|
||||
failures.append("rank label missing")
|
||||
else:
|
||||
_expect_bool(rank_label.text.begins_with("RANK "), true, "rank text keeps the RANK prefix")
|
||||
_expect_bool(rank_label.text.contains("/"), false, "rank text drops the score suffix (只留等级)")
|
||||
|
||||
var result_screen := (flow.get("_screens") as Dictionary).get(&"Result", null) as Control
|
||||
if result_screen == null:
|
||||
failures.append("result screen missing")
|
||||
else:
|
||||
var texts := _collect_label_texts(result_screen)
|
||||
for expected_text: String in ["最大连击", "完美", "良好", "勉强", "失误", "锚点成功", "相位切换"]:
|
||||
_expect_bool(texts.has(expected_text), true, "result stats show Chinese label %s" % expected_text)
|
||||
|
||||
# 页脚实时反映当前难度(标题页 + 难度页)。
|
||||
flow.call("_select_difficulty", &"hard")
|
||||
_expect_string(str((flow.get("_title_difficulty_value") as Label).text), "困难", "title footer tracks difficulty")
|
||||
_expect_string(str((flow.get("_difficulty_footer_value") as Label).text), "当前难度:困难", "difficulty footer tracks difficulty")
|
||||
flow.call("_select_difficulty", &"normal")
|
||||
flow.free()
|
||||
|
||||
|
||||
func _collect_label_texts(node: Node) -> Array[String]:
|
||||
var texts: Array[String] = []
|
||||
if node is Label:
|
||||
texts.append((node as Label).text)
|
||||
for child: Node in node.get_children():
|
||||
texts.append_array(_collect_label_texts(child))
|
||||
return texts
|
||||
|
||||
|
||||
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_int(actual: int, expected: int, label: String) -> void:
|
||||
if actual != expected:
|
||||
failures.append("%s: expected %d, got %d" % [label, expected, actual])
|
||||
|
||||
|
||||
func _expect_float(actual: float, expected: float, label: String) -> void:
|
||||
if not is_equal_approx(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:
|
||||
paused = false
|
||||
if failures.is_empty():
|
||||
print("PASS round2 changes")
|
||||
quit(0)
|
||||
else:
|
||||
for failure: String in failures:
|
||||
push_error(failure)
|
||||
quit(1)
|
||||
Reference in New Issue
Block a user