# fly_attachment_effect_test —— 40250 BuildAttachInstance/UpdateAttachInstance/ # __Bomb 的资源消费和 FlyTrace 回归。 extends SceneTree const AssetRoot = preload("res://asset_root.gd") const EffectRegistry = preload("res://fx/effect_registry.gd") const FlyObject = preload("res://fly_object.gd") var _failures := 0 func _ck(condition: bool, message: String) -> void: if condition: return _failures += 1 printerr("FAIL: ", message) func _init() -> void: await _run() quit(1 if _failures else 0) func _run() -> void: await process_frame var assets := AssetRoot.path() var registry = EffectRegistry.new() registry.setup(assets) var spawned_effects: Array[String] = [] registry.fx_spawned.connect(func(name: String, _id: int, _lifetime: int): spawned_effects.append(name)) var mount := Node3D.new() get_root().add_child(mount) var geometry_probe = FlyObject.FlyInstance.new() geometry_probe.pos = Vector3.ZERO geometry_probe.vel = Vector3(0.0, -1.0, 0.0) var multi_line := {"fly_type": 2, "distance": 1.0, "roll": 0.0} _ck(is_equal_approx(geometry_probe._attachment_position(multi_line).z, -1.0), "AttachData MULTI_LINE keeps 40250's negative local Z offset") var manager := FlyObject.new() manager.manual_step = true mount.add_child(manager) manager.setup(mount, null, assets) manager.set_effect_registry(registry) var target := Node3D.new() target.position = Vector3(5.0, 0.0, 0.0) mount.add_child(target) await process_frame var data = FlyObject.FlyData.load_msf( "d:/ymir work/pc/assassin/effect/arrow_01_fast.msf", assets) _ck(data != null, "arrow_01_fast.msf loads") if data != null: _ck(data.attach_data.size() == 1, "arrow resource keeps one AttachData group") var inst = manager.spawn(Vector3.ZERO, target, false, data) _ck(inst.attachments.size() == 1, "BuildAttachInstance creates one attachment") if inst.attachments.size() == 1: var attachment: Dictionary = inst.attachments[0] _ck(is_instance_valid(attachment.get("effect", null)), "AttachFile is instantiated through EffectRegistry") _ck(is_instance_valid(attachment.get("tail", null)), "TailFlag creates a FlyTrace mesh") _ck(manager._visuals.get(inst, null) == null, "resource-driven projectile does not duplicate a generic SphereMesh") manager.step(0.01) if inst.attachments.size() == 1: var trail_history: Array = inst.attachments[0].get("history", []) _ck(trail_history.size() >= 2, "UpdateAttachInstance records moving tail positions") # Arrow reaches the target and must use BombEffect instead of only the # generic placeholder flash when EffectRegistry has the source resource. for _i in 20: if manager.active_count() == 0: break manager.step(0.05) var found_bomb := false for effect_name in spawned_effects: if String(effect_name).get_file().get_basename() == "blow_1_low_center": found_bomb = true _ck(found_bomb, "__Bomb consumes arrow BombEffect resource") _ck(manager._flash_nodes.is_empty(), "resource BombEffect does not receive a duplicate pre-hit flash") manager.clear_for_map_change() _ck(manager.active_count() == 0 and manager._effect_nodes.is_empty(), "map reset releases resource-driven bomb effects") manager.free() target.free() mount.free() await process_frame if _failures == 0: print("PASS: fly_attachment_effect_test")