fix(parity): implement ground click ripple, target select glow, and 3D projectile obstacle collision with regression probes
This commit is contained in:
+19
-6
@@ -196,12 +196,25 @@ class FlyInstance extends RefCounted:
|
||||
_explode()
|
||||
return false
|
||||
|
||||
if data.hit_on_background and _world and _world.has_method("sample_height"):
|
||||
var gh := float(_world.call("sample_height", pos.x, pos.z))
|
||||
if gh > pos.y:
|
||||
_emit("at_background")
|
||||
_explode()
|
||||
return false
|
||||
if data.hit_on_background:
|
||||
if _world and _world.has_method("sample_height"):
|
||||
var gh := float(_world.call("sample_height", pos.x, pos.z))
|
||||
if gh > pos.y:
|
||||
_emit("at_background")
|
||||
_explode()
|
||||
return false
|
||||
# 40250 CFlyingInstance::Update FCheckBackgroundDuringFlying:
|
||||
# 空间 3D 建筑与静态障碍物碰撞(碰撞层 2: StaticBody3D)
|
||||
if _world is Node3D and _world.is_inside_tree():
|
||||
var w3d: World3D = _world.get_world_3d()
|
||||
if w3d and w3d.direct_space_state:
|
||||
var q := PhysicsRayQueryParameters3D.create(last_pos, pos, 2)
|
||||
var hit := w3d.direct_space_state.intersect_ray(q)
|
||||
if not hit.is_empty():
|
||||
pos = hit.position
|
||||
_emit("at_background")
|
||||
_explode()
|
||||
return false
|
||||
return true
|
||||
|
||||
func _explode(bomb := true) -> void:
|
||||
|
||||
@@ -239,6 +239,9 @@ func setup(m2client: Node, assets_root: String,
|
||||
# §8.8 game.py OnRender chr.Pick() -> ShowCharacterTextTail:悬停角色也强显名字(pick_show.gd)。
|
||||
if pc.has_signal("hover_entity_changed") and net_world and net_world.has_method("set_hover_vid"):
|
||||
pc.hover_entity_changed.connect(func(vid: int): net_world.set_hover_vid(int(vid)))
|
||||
# 40250 EFFECT_PICK: 鼠标点地时生成绿色地面水波光圈 click.mse
|
||||
if pc.has_signal("ground_clicked"):
|
||||
pc.ground_clicked.connect(_on_ground_clicked)
|
||||
if _assets != "":
|
||||
net_play.set_asset_root(_assets) # §3.5:加载 playersettingmodule.py 连击段表
|
||||
|
||||
@@ -749,6 +752,11 @@ func _on_pickable_added(node: Node3D, _vid: int) -> void:
|
||||
func _on_pickable_removed(vid: int) -> void:
|
||||
_drop_stale_pickables(vid)
|
||||
|
||||
# 40250 EFFECT_PICK: 鼠标点地时生成绿色地面水波光圈 click.mse
|
||||
func _on_ground_clicked(pos: Vector3) -> void:
|
||||
if fx and is_instance_valid(_mount):
|
||||
fx.spawn_at("d:/ymir work/effect/etc/click/click.mse", _mount, pos, true)
|
||||
|
||||
func _lifecycle_node() -> Node:
|
||||
# AppFlow owns the single lifecycle observer across login/select/game. The
|
||||
# game scene only borrows it for Android back and focus recovery.
|
||||
|
||||
@@ -131,6 +131,7 @@ var _fading := {} # vid:int -> true while a culled node twee
|
||||
var _always_show_name := NameShow.ALWAYS_SHOW_NAME_DEFAULT
|
||||
var _alt_held := false
|
||||
var _target_vid := 0
|
||||
var _target_effect: Node3D = null
|
||||
var _hover_vid := 0 # player_controller._update_hover_cursor 射线拾到的角色 vid(0 = 无)
|
||||
# §8.8 / §5.4 伤害飘字(damage_effect.gd)。CPythonSystem::IsShowDamage 门(uigameoption
|
||||
# 「伤害数字」radio)+ CInstanceBase::m_DamageQueue(每帧 pop 一条)+ ProcessDamage 分类。
|
||||
@@ -280,12 +281,30 @@ func set_always_show_name(on: bool) -> void:
|
||||
func set_show_damage(on: bool) -> void:
|
||||
_show_damage = on
|
||||
|
||||
func _detach_target_effect() -> void:
|
||||
if is_instance_valid(_target_effect):
|
||||
_target_effect.queue_free()
|
||||
_target_effect = null
|
||||
|
||||
func _attach_target_effect(node: Node3D) -> void:
|
||||
_detach_target_effect()
|
||||
if not is_instance_valid(node) or fx == null or not fx.has_method("spawn"):
|
||||
return
|
||||
# 40250 EFFECT_TARGET (click_glow_select.mse): 挂在目标脚下的循环旋转发光光圈
|
||||
_target_effect = fx.spawn("d:/ymir work/effect/etc/click/click_glow_select.mse", node, false)
|
||||
if _target_effect:
|
||||
_target_effect.position = Vector3.ZERO
|
||||
|
||||
# targetBoard.GetTargetVID():目标始终强显名字(game.py OnRender ShowCharacterTextTail(targetVID))。
|
||||
# 40250 __AttachTargetEffect / __DetachTargetEffect (EFFECT_TARGET: click_glow_select.mse)
|
||||
func set_target_vid(vid: int) -> void:
|
||||
if _target_vid == vid:
|
||||
return
|
||||
var prev := _target_vid
|
||||
_target_vid = vid
|
||||
_detach_target_effect()
|
||||
if vid != 0 and _by_vid.has(vid):
|
||||
_attach_target_effect(_by_vid[vid])
|
||||
for v in [prev, vid]:
|
||||
if v != 0 and _by_vid.has(v):
|
||||
_set_name_visibility(_by_vid[v])
|
||||
@@ -445,6 +464,8 @@ func _on_spawn(d: Dictionary, allow_defer: bool = false) -> void:
|
||||
shape = "TALK"
|
||||
node.set_meta("cursor_shape", shape)
|
||||
_by_vid[vid] = node
|
||||
if vid == _target_vid:
|
||||
_attach_target_effect(node)
|
||||
_sync_hp_bar(vid, d)
|
||||
_set_name_visibility(node)
|
||||
_refresh_shop_sign(node, str(d.get("shop_sign", "")))
|
||||
@@ -603,6 +624,8 @@ func _detach_text_tail(n: Node3D) -> void:
|
||||
# §2.1 cull / 40250 DeleteBlendOut: keep the data row, blend the node out. It stays discoverable via
|
||||
# _fading (so it is not rebuilt mid-blend) until the tween frees it.
|
||||
func _fade_and_free(vid: int) -> void:
|
||||
if vid == _target_vid:
|
||||
_detach_target_effect()
|
||||
var n: Node3D = _by_vid.get(vid, null)
|
||||
_by_vid.erase(vid)
|
||||
if not is_instance_valid(n):
|
||||
|
||||
@@ -10,6 +10,7 @@ extends Node
|
||||
|
||||
signal anim_state(state: String)
|
||||
signal target_selected(node: Node3D)
|
||||
signal ground_clicked(world_pos: Vector3)
|
||||
signal ground_item_clicked(vid: int)
|
||||
signal moved(pos: Vector3)
|
||||
# game.py OnRender:wndMgr.IsPickedWindow 门内 chr.Pick() 拾到的角色 -> ShowCharacterTextTail。
|
||||
@@ -251,6 +252,7 @@ func _on_click(screen_pos: Vector2) -> void:
|
||||
return
|
||||
if cancel_fishing_input.is_valid() and bool(cancel_fishing_input.call()):
|
||||
return
|
||||
ground_clicked.emit(Vector3(hit.x, hit.y, hit.z))
|
||||
_goto(hit)
|
||||
|
||||
# 射线 vs 实体(节点下所有 MeshInstance3D 的世界 AABB 合并)的进入距离 t;不命中返回 -1。
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
# test_all_map_height_probe.gd
|
||||
# 严格按照《CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md》手段 1(全图几何与阻挡坐标探针):
|
||||
# 自动化探针遍历多个核心地图(OutdoorA1、OutdoorC1 等),全面巡检跨河桥梁、高架栈道与悬索吊桥:
|
||||
# 1. 验证物体中心及行进跨度内 sample_height 稳定采样到表面高度,不跌入河床裸地形;
|
||||
# 2. 验证桥面行走通道未被 is_blocked 错误拦截;
|
||||
# 3. 验证侧向偏离点正确回退到裸地形标高;
|
||||
# 4. 验证高度数据不存在 NaN / Inf 异常。
|
||||
extends SceneTree
|
||||
|
||||
const ASSETS_PATH := "res://../assets"
|
||||
|
||||
func _init() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
print("=== Starting test_all_map_height_probe (Headless Coordinate Probe Suite) ===")
|
||||
var fails := [0]
|
||||
var check = func(cond: bool, msg: String):
|
||||
if not cond:
|
||||
fails[0] += 1
|
||||
printerr("FAIL: ", msg)
|
||||
else:
|
||||
print("PASS: ", msg)
|
||||
|
||||
if not ClassDB.class_exists("Metin2World"):
|
||||
printerr("FAIL: Metin2World class not found")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var world: Node = ClassDB.instantiate("Metin2World")
|
||||
get_root().add_child(world)
|
||||
var assets_abs := ProjectSettings.globalize_path(ASSETS_PATH)
|
||||
world.set("assets_root", assets_abs)
|
||||
world.set("objects_enabled", true)
|
||||
world.set("water_enabled", true)
|
||||
world.set("load_radius_tiles", -1)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 巡检 1: OutdoorA1/metin2_map_a1 跨河石桥与悬索吊桥
|
||||
# -------------------------------------------------------------
|
||||
print("\n--- Probing Map 1: OutdoorA1/metin2_map_a1 ---")
|
||||
world.set("map_path", "OutdoorA1/metin2_map_a1")
|
||||
var ok: bool = bool(world.call("load_map"))
|
||||
check.call(ok, "OutdoorA1 load_map succeeded")
|
||||
|
||||
if ok:
|
||||
# 桥梁 1: a1-024-10m-bridge (Chunk 002002)
|
||||
var b1_x := 761.57
|
||||
var b1_z := 594.55
|
||||
var h1: float = float(world.call("sample_height", b1_x, b1_z))
|
||||
var bl1: bool = bool(world.call("is_blocked", b1_x, b1_z))
|
||||
print(" A1 Bridge 1 (a1-024-10m-bridge) at (%f, %f): height=%f m, blocked=%s" % [b1_x, b1_z, h1, bl1])
|
||||
check.call(h1 >= 204.0 and h1 <= 208.0, "Bridge 1 deck elevation in expected range (204m~208m), got %f" % h1)
|
||||
check.call(not bl1, "Bridge 1 walking lane is NOT blocked (is_blocked == false)")
|
||||
|
||||
# 跨桥连续性巡检 (沿 x 轴跨度从 751 到 771)
|
||||
var span_continuous := true
|
||||
for dx in range(-10, 11, 2):
|
||||
var step_x := b1_x + float(dx)
|
||||
var step_h: float = float(world.call("sample_height", step_x, b1_z))
|
||||
var step_bl: bool = bool(world.call("is_blocked", step_x, b1_z))
|
||||
if step_h < 203.0 or step_bl:
|
||||
span_continuous = false
|
||||
printerr(" Span defect at x=%f: height=%f (expected >= 203), blocked=%s" % [step_x, step_h, step_bl])
|
||||
break
|
||||
check.call(span_continuous, "Bridge 1 walking span maintains elevated deck and walkable status across entire length")
|
||||
|
||||
# 桥侧河床自然回退验证
|
||||
var off_h: float = float(world.call("sample_height", b1_x, 570.0))
|
||||
check.call(off_h < 200.0, "Off-bridge point correctly samples riverbed terrain (%f m < 200m)" % off_h)
|
||||
|
||||
# 桥梁 2: 悬索吊桥 (general_obj_suspension bridge01)
|
||||
var b2_x := 213.91
|
||||
var b2_z := 700.45
|
||||
var h2: float = float(world.call("sample_height", b2_x, b2_z))
|
||||
var bl2: bool = bool(world.call("is_blocked", b2_x, b2_z))
|
||||
print(" A1 Bridge 2 (suspension bridge) at (%f, %f): height=%f m, blocked=%s" % [b2_x, b2_z, h2, bl2])
|
||||
check.call(h2 >= 130.0 and h2 <= 140.0, "Suspension bridge deck in expected range (130m~140m), got %f" % h2)
|
||||
check.call(not bl2, "Suspension bridge walking deck is NOT blocked")
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 巡检 2: OutdoorC1/metin2_map_c1 跨河石桥
|
||||
# -------------------------------------------------------------
|
||||
print("\n--- Probing Map 2: OutdoorC1/metin2_map_c1 ---")
|
||||
world.call("unload_map")
|
||||
world.set("map_path", "OutdoorC1/metin2_map_c1")
|
||||
var ok_c1: bool = bool(world.call("load_map"))
|
||||
check.call(ok_c1, "OutdoorC1 load_map succeeded")
|
||||
|
||||
if ok_c1:
|
||||
# C1 跨河石桥: c1-022-10m-bridge (位于 Chunk 001001, areadata pos: 35755.88, -50457.72, 16555.45, bias=-705.0)
|
||||
var c1_x := 357.56
|
||||
var c1_z := 504.58
|
||||
var c1_h: float = float(world.call("sample_height", c1_x, c1_z))
|
||||
var c1_bl: bool = bool(world.call("is_blocked", c1_x, c1_z))
|
||||
print(" C1 Bridge (c1-022-10m-bridge) at (%f, %f): height=%f m, blocked=%s" % [c1_x, c1_z, c1_h, c1_bl])
|
||||
check.call(c1_h >= 170.0 and c1_h <= 180.0, "C1 Bridge deck elevation in expected range (170m~180m), got %f" % c1_h)
|
||||
check.call(not c1_bl, "C1 Bridge walking lane is NOT blocked")
|
||||
|
||||
# 跨桥连续性巡检 (沿 z 轴方向,因为 rot=90度,桥面沿南北走向展开)
|
||||
var c1_span_continuous := true
|
||||
for dz in range(-12, 13, 3):
|
||||
var step_z := c1_z + float(dz)
|
||||
var step_h: float = float(world.call("sample_height", c1_x, step_z))
|
||||
var step_bl: bool = bool(world.call("is_blocked", c1_x, step_z))
|
||||
if step_h < 172.0 or step_bl:
|
||||
c1_span_continuous = false
|
||||
printerr(" C1 Span defect at z=%f: height=%f (expected >= 172), blocked=%s" % [step_z, step_h, step_bl])
|
||||
break
|
||||
check.call(c1_span_continuous, "C1 Bridge walking span maintains elevated deck across entire span")
|
||||
|
||||
# 侧向远离桥梁 15m 的点 (河道): (357.56 - 15.0, 504.58)
|
||||
var c1_off_x := c1_x - 15.0
|
||||
var c1_off_h: float = float(world.call("sample_height", c1_off_x, c1_z))
|
||||
print(" C1 Off-bridge river at (%f, %f): height=%f m (< bridge %f m)" % [c1_off_x, c1_z, c1_off_h, c1_h])
|
||||
check.call(c1_off_h < c1_h - 1.0, "C1 Off-bridge point drops below bridge deck (%f m < %f m)" % [c1_off_h, c1_h])
|
||||
|
||||
world.call("unload_map")
|
||||
world.queue_free()
|
||||
|
||||
print("\n=== All Map Height Probes finished with %d failures ===" % fails[0])
|
||||
quit(fails[0])
|
||||
@@ -0,0 +1,183 @@
|
||||
# test_click_target_effect_parity.gd
|
||||
# 严格按照《CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md》手段 2 与工程原则 3:
|
||||
# 验证 40250 地面点选波纹 (EFFECT_PICK / click.mse)、目标光圈 (EFFECT_TARGET / click_glow_select.mse)
|
||||
# 与飞行物建筑碰撞 (FCheckBackgroundDuringFlying) 1:1 对拍测试。
|
||||
extends SceneTree
|
||||
|
||||
const PlayerController = preload("res://player_controller.gd")
|
||||
const NetWorld = preload("res://net_world.gd")
|
||||
const FlyObject = preload("res://fly_object.gd")
|
||||
|
||||
class MockFx extends RefCounted:
|
||||
var spawned_effects: Array = []
|
||||
func spawn(effect_name: String, parent_node: Node3D, looping: bool) -> Node3D:
|
||||
spawned_effects.append({"name": effect_name, "parent": parent_node, "looping": looping})
|
||||
var eft := Node3D.new()
|
||||
eft.name = "mock_fx_" + effect_name.get_file().get_basename()
|
||||
parent_node.add_child(eft)
|
||||
return eft
|
||||
func spawn_at(effect_name: String, parent_node: Node3D, pos: Vector3, looping: bool) -> Node3D:
|
||||
spawned_effects.append({"name": effect_name, "parent": parent_node, "pos": pos, "looping": looping})
|
||||
var eft := Node3D.new()
|
||||
eft.name = "mock_fx_" + effect_name.get_file().get_basename()
|
||||
parent_node.add_child(eft)
|
||||
eft.position = pos
|
||||
return eft
|
||||
|
||||
func _init() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
print("=== Starting test_click_target_effect_parity ===")
|
||||
var fails := [0]
|
||||
var check = func(cond: bool, msg: String):
|
||||
if not cond:
|
||||
fails[0] += 1
|
||||
printerr("FAIL: ", msg)
|
||||
else:
|
||||
print("PASS: ", msg)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Test 1: PlayerController ground_clicked 信号与坐标
|
||||
# -------------------------------------------------------------
|
||||
print("\n--- Test 1: Ground Click Signal & Position ---")
|
||||
var pc := PlayerController.new()
|
||||
root.add_child(pc)
|
||||
var player_node := Node3D.new()
|
||||
root.add_child(player_node)
|
||||
player_node.position = Vector3(100.0, 50.0, 100.0)
|
||||
pc.player = player_node
|
||||
pc.active = true
|
||||
|
||||
# 模拟世界
|
||||
var mock_world = Node.new()
|
||||
var w_script = GDScript.new()
|
||||
w_script.source_code = """
|
||||
extends Node
|
||||
func sample_height(x: float, z: float) -> float:
|
||||
return 25.0
|
||||
func is_blocked(_x: float, _z: float) -> bool:
|
||||
return false
|
||||
"""
|
||||
w_script.reload()
|
||||
mock_world.set_script(w_script)
|
||||
root.add_child(mock_world)
|
||||
pc.world = mock_world
|
||||
|
||||
var ground_clicked_pos := [Vector3.ZERO]
|
||||
var ground_clicked_count := [0]
|
||||
pc.ground_clicked.connect(func(pos: Vector3):
|
||||
ground_clicked_count[0] += 1
|
||||
ground_clicked_pos[0] = pos
|
||||
)
|
||||
|
||||
# 模拟鼠标输入事件点地 (从上方向下发射射线)
|
||||
# _ray_ground(from, dir): from=(110, 50, 110), dir=(0, -1, 0) -> 命中 y=25.0 处
|
||||
var click_hit = pc._ray_ground(Vector3(110.0, 50.0, 110.0), Vector3(0.0, -1.0, 0.0))
|
||||
check.call(click_hit != null and absf(click_hit.y - 25.0) < 0.01, "Mock ray_ground hits surface at y=25.0")
|
||||
|
||||
# 手动触发点地流程并验证信号发射
|
||||
pc.ground_clicked.emit(click_hit)
|
||||
check.call(ground_clicked_count[0] == 1, "ground_clicked signal emitted")
|
||||
check.call(ground_clicked_pos[0].is_equal_approx(Vector3(110.0, 25.0, 110.0)), "ground_clicked emitted exact world position")
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Test 2: NetWorld 目标光圈 AttachTargetEffect / DetachTargetEffect
|
||||
# -------------------------------------------------------------
|
||||
print("\n--- Test 2: NetWorld Target Effect (click_glow_select.mse) ---")
|
||||
var nw := NetWorld.new()
|
||||
root.add_child(nw)
|
||||
|
||||
var mock_fx := MockFx.new()
|
||||
nw.fx = mock_fx
|
||||
|
||||
# 添加一个实体
|
||||
var target_entity := Node3D.new()
|
||||
root.add_child(target_entity)
|
||||
target_entity.position = Vector3(200.0, 10.0, 300.0)
|
||||
var vid := 5001
|
||||
nw.set("_by_vid", {vid: target_entity})
|
||||
|
||||
# 选中目标: set_target_vid(5001)
|
||||
nw.set_target_vid(vid)
|
||||
check.call(mock_fx.spawned_effects.size() == 1, "Target effect spawned on set_target_vid")
|
||||
var last_fx: Dictionary = mock_fx.spawned_effects[0]
|
||||
check.call("click_glow_select.mse" in last_fx.name, "Spawned effect is click_glow_select.mse")
|
||||
check.call(last_fx.parent == target_entity, "Target effect attached to target entity")
|
||||
check.call(not last_fx.looping, "Target effect configured as looping (one_shot=false)")
|
||||
|
||||
# 取消选中: set_target_vid(0)
|
||||
nw.set_target_vid(0)
|
||||
check.call(nw._target_effect == null, "Target effect detached on set_target_vid(0)")
|
||||
|
||||
# 再次选中并在实体被 _fade_and_free 销毁时验证分离
|
||||
nw.set_target_vid(vid)
|
||||
check.call(nw._target_effect != null, "Target effect re-attached on selecting target again")
|
||||
nw._fade_and_free(vid)
|
||||
check.call(nw._target_effect == null, "Target effect cleanly detached on entity removal/death")
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Test 3: Flying Instance 3D Static Obstacle Collision
|
||||
# -------------------------------------------------------------
|
||||
print("\n--- Test 3: Flying Instance 3D Static Obstacle Collision ---")
|
||||
# 3. 飞行物与静态建筑/障碍物碰撞 (CFlyingInstance::FCheckBackgroundDuringFlying)
|
||||
# 构造一个挂在场景树中的 Node3D 作为 world,以及一个 StaticBody3D 障碍物
|
||||
var fly_mgr := FlyObject.new()
|
||||
var fly_mount := Node3D.new()
|
||||
var fly_world := Node3D.new()
|
||||
root.add_child(fly_mount)
|
||||
root.add_child(fly_world)
|
||||
|
||||
var wall := StaticBody3D.new()
|
||||
wall.collision_layer = 2
|
||||
wall.collision_mask = 0
|
||||
var col_shape := CollisionShape3D.new()
|
||||
var box := BoxShape3D.new()
|
||||
box.size = Vector3(10.0, 20.0, 1.0) # x=10m, y=20m, z=1m 厚的墙
|
||||
col_shape.shape = box
|
||||
wall.add_child(col_shape)
|
||||
wall.position = Vector3(0.0, 10.0, 50.0) # 位于 z=50m 处
|
||||
fly_world.add_child(wall)
|
||||
|
||||
# 等待 2 帧让 PhysicsServer3D 完成场景树内静态碰撞体的注册
|
||||
for _f in range(2):
|
||||
await process_frame
|
||||
|
||||
fly_mgr.setup(fly_mount, fly_world)
|
||||
fly_mgr.manual_step = true
|
||||
var fly_data := FlyObject.FlyData.new()
|
||||
fly_data.hit_on_background = true
|
||||
fly_data.init_vel = 100.0 # 100 m/s
|
||||
fly_data.flat_range = 200.0
|
||||
|
||||
# 发射一颗射弹从 z=0 射向 z=100 (穿过 z=50 的墙)
|
||||
var recorded_events := []
|
||||
var inst = fly_mgr.spawn(Vector3(0.0, 10.0, 0.0), Vector3(0.0, 10.0, 100.0), false, fly_data)
|
||||
inst.handler = func(ev: String, p: Vector3, _v: int):
|
||||
recorded_events.append({"ev": ev, "pos": p})
|
||||
|
||||
# 推进时间让射弹穿过墙体
|
||||
for i in range(10):
|
||||
if not inst.alive:
|
||||
break
|
||||
fly_mgr.step(0.1) # 10m 步长
|
||||
|
||||
var hit_bg = false
|
||||
for e in recorded_events:
|
||||
if e.ev == "at_background":
|
||||
hit_bg = true
|
||||
check.call(not inst.alive, "Projectile stopped and exploded")
|
||||
check.call(hit_bg, "Projectile hit static obstacle and triggered at_background")
|
||||
|
||||
# 清理
|
||||
pc.queue_free()
|
||||
player_node.queue_free()
|
||||
mock_world.queue_free()
|
||||
target_entity.queue_free()
|
||||
nw.queue_free()
|
||||
wall.queue_free()
|
||||
fly_mount.queue_free()
|
||||
fly_world.queue_free()
|
||||
|
||||
print("\n=== All Click, Target & Fly Parity Tests finished with %d failures ===" % fails[0])
|
||||
quit(fails[0])
|
||||
Reference in New Issue
Block a user