# motion_event_fly_test —— 40250 local MotionEventType=6 parity。 # `.msa` FLY 帧必须按 FlyFileName 创建本地弹道;CG_SHOOT 仍由 GameScene # 单独发送,服务器 GC_CREATE_FLY 不应被拿来替代这条本地视觉路径。 extends SceneTree const AssetRoot = preload("res://asset_root.gd") const FlyObject = preload("res://fly_object.gd") const NetWorld = preload("res://net_world.gd") const GameScene = preload("res://game_scene.gd") class Client extends Node: var shots: Array[int] = [] func get_main_vid() -> int: return 1 func shoot(skill: int) -> bool: shots.append(skill) return true var failures := 0 func check(ok: bool, label: String) -> void: if not ok: failures += 1 printerr("FAIL: " + label) func _init() -> void: call_deferred("run") func run() -> void: await process_frame var mount := Node3D.new() get_root().add_child(mount) var world := NetWorld.new() get_root().add_child(world) world.parent = mount world._assets_root = AssetRoot.path() var manager := FlyObject.new() manager.manual_step = true get_root().add_child(manager) manager.setup(mount, null, AssetRoot.path()) world._fly_mgr = manager var actor := Node3D.new() actor.position = Vector3(0, 1, 0) get_root().add_child(actor) var target := Node3D.new() target.position = Vector3(0, 1, -8) get_root().add_child(target) var event := { "type": 6, "fly_file": "d:/ymir work/pc/assassin/effect/arrow_01_fast.msf", "fly_pos": Vector3.ZERO, "fly_bone": "", "fly_attaching": false, } check(world.spawn_motion_fly(actor, event, target, 7), "valid FlyFileName creates the local motion projectile") check(manager.active_count() == 1, "local motion projectile enters FlyManager once") if manager.active_count() == 1: var before: Vector3 = manager._instances[0].pos manager.step(0.01) check(manager._instances[0].pos != before, "local motion projectile advances") var missing := event.duplicate() missing.fly_file = "d:/ymir work/pc/assassin/effect/missing.msf" check(not world.spawn_motion_fly(actor, missing, target), "missing FlyFileName follows reference create failure") check(manager.active_count() == 1, "missing fly resource does not add a duplicate instance") # GameScene keeps the two 40250 side effects separate: one local visual # instance plus one queued CG_SHOOT at the same motion frame. manager.clear_for_map_change() var game := GameScene.new() var client := Client.new() get_root().add_child(game) get_root().add_child(client) game.client = client game.net_world = world game.player = actor game._mount = mount game._motion_effect_target = weakref(target) game._queue_shot(7) game._on_local_motion_event_detailed(event) check(client.shots == [7], "FLY event still sends exactly one queued CG_SHOOT") check(manager.active_count() == 1, "FLY event creates exactly one local projectile") game.free() client.free() manager.clear_for_map_change() manager.free() actor.free() target.free() world.free() mount.free() await process_frame if failures == 0: print("PASS: motion_event_fly_test") quit(1 if failures else 0)