extends SceneTree const CursorManager = preload("res://ui/cursor_manager.gd") const GroundItems = preload("res://ui/ground_items.gd") const PlayerController = preload("res://player_controller.gd") var _fail := 0 func _ck(condition: bool, message: String) -> void: if not condition: _fail += 1 printerr("FAIL: " + message) func _init() -> void: print("--- Running test_drop_and_cursor_parity ---") await _run() preload("res://ui/ui_assets.gd").clear_cache() if _fail == 0: print("test_drop_and_cursor_parity passed with 0 failures!") quit(0) else: printerr("%d failure(s)" % _fail) quit(1) func _run() -> void: test_mapcoord_heading() test_cursor_manager() await test_ground_item_models_and_picking() func test_mapcoord_heading() -> void: print("Testing MapCoord heading alignment with 40250...") _ck(is_equal_approx(MapCoord.heading_to_yaw(0.0), 0.0), "0 deg heading must be 0 yaw (South)") _ck(is_equal_approx(MapCoord.heading_to_yaw(90.0), PI * 0.5), "90 deg heading must be PI/2 yaw (East)") _ck(is_equal_approx(MapCoord.heading_to_yaw(180.0), PI), "180 deg heading must be PI yaw (North)") _ck(is_equal_approx(MapCoord.heading_to_yaw(270.0), PI * 1.5), "270 deg heading must be 3PI/2 yaw (West)") _ck(is_equal_approx(MapCoord.yaw_to_heading(0.0), 0.0), "0 yaw must be 0 heading") _ck(is_equal_approx(MapCoord.yaw_to_heading(PI * 0.5), 90.0), "PI/2 yaw must be 90 heading") _ck(is_equal_approx(MapCoord.yaw_to_heading(PI), 180.0), "PI yaw must be 180 heading") print("OK: MapCoord heading parity verified") func test_cursor_manager() -> void: print("Testing CursorManager custom cursor registration...") var cm := CursorManager.new() get_root().add_child(cm) var root_assets: String = AssetRoot.path() if AssetRoot.available() else "" cm.setup(root_assets) cm.set_cursor("NORMAL") _ck(cm.get_cursor() == "NORMAL", "shape is NORMAL") _ck(cm._godot_shape("NORMAL") == Input.CURSOR_ARROW, "godot shape is CURSOR_ARROW") cm.set_cursor("ATTACK") _ck(cm.get_cursor() == "ATTACK", "shape is ATTACK") _ck(cm._godot_shape("ATTACK") == Input.CURSOR_CROSS, "godot shape is CURSOR_CROSS") cm.set_cursor("PICK") _ck(cm.get_cursor() == "PICK", "shape is PICK") _ck(cm._godot_shape("PICK") == Input.CURSOR_CAN_DROP, "godot shape is CURSOR_CAN_DROP") cm.set_cursor("TALK") _ck(cm.get_cursor() == "TALK", "shape is TALK") _ck(cm._godot_shape("TALK") == Input.CURSOR_POINTING_HAND, "godot shape is CURSOR_POINTING_HAND") cm.set_cursor("DOOR") _ck(cm.get_cursor() == "DOOR", "shape is DOOR") _ck(cm._godot_shape("DOOR") == Input.CURSOR_HELP, "godot shape is CURSOR_HELP") cm.set_cursor("CANT_GO") _ck(cm.get_cursor() == "CANT_GO", "shape is CANT_GO") _ck(cm._godot_shape("CANT_GO") == Input.CURSOR_FORBIDDEN, "godot shape is CURSOR_FORBIDDEN") cm.set_cursor("CAMERA_ROTATE") _ck(cm.get_cursor() == "CAMERA_ROTATE", "shape is CAMERA_ROTATE") _ck(cm._godot_shape("CAMERA_ROTATE") == Input.CURSOR_BDIAGSIZE, "godot shape is CURSOR_BDIAGSIZE") cm.set_cursor("CHAIR") _ck(cm.get_cursor() == "CHAIR", "shape is CHAIR") _ck(cm._godot_shape("CHAIR") == Input.CURSOR_WAIT, "godot shape is CURSOR_WAIT") cm.set_cursor("HSIZE") _ck(cm.get_cursor() == "HSIZE", "shape is HSIZE") _ck(cm._godot_shape("HSIZE") == Input.CURSOR_HSIZE, "godot shape is CURSOR_HSIZE") cm.queue_free() for shape_idx in range(17): Input.set_custom_mouse_cursor(null, shape_idx) CursorManager.UiAssets.clear_cache() print("OK: CursorManager parity verified") class DummyClient extends Node: signal ground_item_added(d: Dictionary) signal ground_item_removed(vid: int) var picked_vids: Array[int] = [] func pickup_item(vid: int) -> bool: picked_vids.append(vid) return true func test_ground_item_models_and_picking() -> void: print("Testing GroundItems model instantiation and picking...") var mount := Node3D.new() get_root().add_child(mount) var cam := Camera3D.new() get_root().add_child(cam) cam.position = Vector3(0, 5, 5) cam.look_at_from_position(Vector3(0, 5, 5), Vector3.ZERO, Vector3.UP) cam.current = true var client := DummyClient.new() get_root().add_child(client) var gi := GroundItems.new() get_root().add_child(gi) var player := Node3D.new() player.position = Vector3.ZERO get_root().add_child(player) gi.assets_override = "__none__" gi.setup(client, mount, func() -> Node: return player, null, null, func() -> Camera3D: return cam) gi._on_added({"vid": 1001, "vnum": 1, "owner": "Player", "pos": Vector3.ZERO}) _ck(gi._by_vid.has(1001), "Drop 1001 must exist") var drop1: Node3D = gi._by_vid[1001] var m1 = drop1.get_node_or_null("model") if m1 == null: m1 = drop1.get_child(0) _ck(m1 != null, "Drop must have a visual child (Metin2Model or BoxMesh fallback)") gi._on_added({"vid": 1002, "vnum": 10, "owner": "Player", "pos": Vector3(10, 0, 0)}) _ck(gi._by_vid.has(1002), "Drop 1002 must exist") await process_frame await process_frame var sp1 := cam.unproject_position(drop1.global_position + Vector3(0, 0.35, 0)) var picked_vid := gi.pick_at(cam, sp1) _ck(picked_vid == 1001, "pick_at should pick drop 1001 at its screen projection") var pc := PlayerController.new() get_root().add_child(pc) pc.player = player pc.camera = cam pc.ground_items = gi var emitted := [0] pc.ground_item_clicked.connect(func(vid: int): emitted[0] = vid) pc._on_click(sp1) _ck(emitted[0] == 1001, "PlayerController._on_click must emit ground_item_clicked with picked vid") gi.clear_for_map_change() pc.queue_free() player.queue_free() gi.queue_free() client.queue_free() cam.queue_free() mount.queue_free() await process_frame await process_frame print("OK: GroundItems model and picking verified")