# inventory_ui_test —— P2 背包/装备窗 headless 自检(假 client + 假 proto + 真 uiscript)。 # godot --headless --path project --script inventory_ui_test.gd extends SceneTree const UiManager = preload("res://ui/ui_manager.gd") const InventoryUI = preload("res://ui/inventory_ui.gd") class FakeClient extends Node: signal inventory_changed(window: int, cell: int) var inv := {} # cell -> {vnum,count} var equip := [] # 11 x {vnum,count} var used := [] var moved := [] func _init(): for i in 11: equip.append({"vnum": 0, "count": 0, "wear": i}) func get_inventory() -> Array: var out := [] for c in inv: out.append({"cell": c, "vnum": inv[c].vnum, "count": inv[c].count}) return out func get_equipment() -> Array: return equip func use_item(w, c) -> bool: used.append([w, c]); return true func move_item(fw, fc, tw, tc, n) -> bool: moved.append([fw, fc, tw, tc, n]); return true func set_inv(cell, vnum, count): inv[cell] = {"vnum": vnum, "count": count} inventory_changed.emit(1, cell) class FakeProto extends Node: func item(vnum: int) -> Dictionary: # type 1 = WEAPON;给出可穿戴位 + 空限制,让 EquipRules 前置门放行。 return {"vnum": vnum, "name": "Item%d" % vnum, "locale_name": "物品%d" % vnum, "type": 1, "sub_type": 0, "anti_flags": 0, "wear_flags": 1 << 4, "limits": []} var _fail := 0 func _ck(c: bool, m: String) -> void: if not c: _fail += 1 printerr("FAIL: " + m) func _init() -> void: _run() if _fail == 0: print("PASS: inventory_ui_test (window + fill + refresh + use + move)") quit(0) else: printerr("%d check(s) failed" % _fail) quit(1) func _run() -> void: var assets := AssetRoot.path() if not FileAccess.file_exists(assets.path_join("uiscript/uiscript/inventorywindow.py")): print(" (skip: no inventorywindow.py — assets checkout missing)") return var ui: CanvasLayer = UiManager.new() get_root().add_child(ui) ui._ready() var fc := FakeClient.new() var fp := FakeProto.new() get_root().add_child(fc) get_root().add_child(fp) fc.inv[5] = {"vnum": 19, "count": 1} fc.inv[6] = {"vnum": 27, "count": 3} fc.equip[1] = {"vnum": 11209, "count": 1, "wear": 1} var iv: Node = InventoryUI.new() var mouse := preload("res://ui/mouse_controller.gd").new() get_root().add_child(mouse) mouse.setup(ui) iv.item_mouse = mouse get_root().add_child(iv) iv.setup(ui, fc, fp, assets) iv.open() _ck(iv.is_open(), "inventory window opened") # 45 背包格 + 12 装备格(equipment 元组) _ck(iv._cells.size() >= 45, "at least 45 inventory cells indexed (%d)" % iv._cells.size()) _ck(iv._cells.has(90), "equipment cell 90 present") # 填充 var c5: Panel = iv._cells[5] _ck(int(c5.get_meta("vnum", 0)) == 19, "cell 5 filled vnum 19") _ck(c5.tooltip_text.contains("物品19"), "cell 5 tooltip has locale name") var c6: Panel = iv._cells[6] _ck(c6.has_node("count") and (c6.get_node("count") as Label).text == "3", "cell 6 shows count 3") _ck(int(iv._cells[91].get_meta("vnum", 0)) == 11209, "equip wear1 -> ui cell 91 filled") # inventory_changed 刷新 fc.set_inv(8, 100, 1) await process_frame _ck(int(iv._cells[8].get_meta("vnum", 0)) == 100, "cell 8 filled after inventory_changed") # 右键用 iv.use(5) _ck(fc.used.size() == 1 and fc.used[0] == [1, 5], "use(5) -> use_item(1,5)") # 拖 5 -> 91(背包 -> 装备) iv.move_to(5, 91, 1) _ck(fc.moved.size() == 1 and fc.moved[0] == [1, 5, 2, 1, 1], "move_to(5,91) -> move_item(1,5,2,1,1)") _ck(iv._drop_mouse_item({"window": 1, "cell": 6, "vnum": 27, "count": 3}, 8), "mouse drop accepts inventory stack") _ck(fc.moved.size() == 2 and fc.moved[1] == [1, 6, 1, 8, 3], "mouse drop preserves attached stack count") iv.close() _ck(not iv.is_open(), "window closed") # 移动端长按详情与数量输入:不依赖右键、悬停或 PC SpinBox 微小箭头。 ui.set_mobile_mode(true) iv.set_mobile_mode(true) iv.open() var long_down := InputEventScreenTouch.new() long_down.index = 31 long_down.pressed = true long_down.position = Vector2(20, 20) iv._on_cell_input(6, long_down) await create_timer(0.65).timeout _ck(iv._mobile_detail_dialog != null, "mobile long-press detail opens") _ck(ui.top() == iv._mobile_detail_dialog, "detail is a modal window above inventory") ui.close_top() _ck(iv._mobile_detail_dialog == null, "mobile detail closes with back") iv._ask_count("丢弃数量", 3, func(_amount): pass) var count_dialog: Control = ui.top() _ck(count_dialog != null and count_dialog.get_node_or_null("CountInput") is LineEdit, "mobile count dialog uses numeric LineEdit") ui.close_top() iv.close() ui.queue_free()