# motion_event_warp_test —— 40250 MotionEventType 9 / ProcessMotionEventWarp. # The reference moves the actor to target - normalize(target-actor)*270cm, # rejects the move on a blocked map cell, always turns toward the target, and # calls OnWarp -> SendCharacterStatePacket(FUNC_WAIT). extends SceneTree const AssetRoot = preload("res://asset_root.gd") const GameScene = preload("res://game_scene.gd") const NetPlay = preload("res://net_play.gd") class WarpWorld extends Node: var blocked := false var probes: Array[Vector2] = [] func is_blocked(x: float, z: float) -> bool: probes.append(Vector2(x, z)) return blocked class WarpNetPlay extends Node: var states: Array = [] func _send_state(func_id: int, arg: int, position: Vector3) -> void: states.append([func_id, arg, position]) 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: var game := GameScene.new() var world := WarpWorld.new() var net_play := WarpNetPlay.new() var actor := Node3D.new() var target := Node3D.new() get_root().add_child(game) get_root().add_child(world) get_root().add_child(net_play) get_root().add_child(actor) get_root().add_child(target) game.player = actor game.world = world game.net_play = net_play actor.global_position = Vector3(0.0, 1.0, 0.0) target.global_position = Vector3(10.0, 3.0, 0.0) game._motion_effect_target = weakref(target) # The native parser must expose a real type-9 event from the reference asset. if ClassDB.class_exists("Metin2AnimPlayer"): var anim: Node = ClassDB.instantiate("Metin2AnimPlayer") get_root().add_child(anim) anim.set("anim_path", AssetRoot.path().path_join("PC/ymir work/pc/assassin/skill/seomjeon.msa")) await process_frame var found := false for event in anim.call("get_events"): if event is Dictionary and int(event.get("type", -1)) == 9: found = true check(found, "seomjeon.msa exposes MotionEventType 9") anim.free() # Allowed path: target - 2.70m along the complete 3D target vector. game._on_local_motion_event_detailed({"type": 9, "sound": "", "pos": Vector3.ZERO}) var expected := target.global_position - (target.global_position - Vector3(0.0, 1.0, 0.0)).normalized() * 2.70 check(actor.global_position.is_equal_approx(expected), "WARP moves to target minus the 270cm reference distance") check(is_equal_approx(actor.rotation.y, PI * 0.5), "WARP turns toward target on the ground plane") check(net_play.states.size() == 1 and int(net_play.states[0][0]) == NetPlay.FUNC_WAIT, "WARP emits one FUNC_WAIT state through OnWarp") check(world.probes.size() == 1 and world.probes[0].is_equal_approx(Vector2(expected.x, expected.z)), "WARP checks the exact destination against map blocking") # Blocked path: orientation and OnWarp still happen, but SetPixelPosition does not. world.blocked = true actor.global_position = Vector3.ZERO actor.rotation.y = 0.0 net_play.states.clear() game._on_local_motion_event_detailed({"type": 9, "sound": "", "pos": Vector3.ZERO}) check(actor.global_position == Vector3.ZERO, "blocked WARP keeps the actor position") check(is_equal_approx(actor.rotation.y, PI * 0.5), "blocked WARP still turns toward target") check(net_play.states.size() == 1, "blocked WARP still reports OnWarp once") # No target: the reference returns without moving or reporting OnWarp. game._motion_effect_target = null net_play.states.clear() actor.global_position = Vector3.ZERO game._on_local_motion_event_detailed({"type": 9, "sound": "", "pos": Vector3.ZERO}) check(actor.global_position == Vector3.ZERO and net_play.states.is_empty(), "WARP without a valid fly target is a no-op") game.free() world.free() net_play.free() actor.free() target.free() await process_frame if failures == 0: print("PASS: motion_event_warp_test") quit(1 if failures else 0)