# SelectItemUI (P11) —— 选魔石窗(1:1 迁移 `assets/root/uiselectitem.py`)。 # # var si := preload("res://ui/select_item_ui.gd").new() # add_child(si) # si.setup(ui_manager, m2client, proto, assets_root, item_list) # si.open() # 由 quest_dialog.select_item_requested 触发 # # 布局走 `assets/uiscript/uiscript/selectitemwindow.py`(UiScript → UiBuild)。 # 逐字对照 uiselectitem.py.RefreshSlot:遍历背包前 `INVENTORY_PAGE_SIZE*2` 格, # 只保留 `item.IsMetin`(proto.type == ITEM_TYPE_METIN 10)且 `GetItemGrade <= 2` # (= 物品内部名最后一位数字,非数字按 0)的物品,最多 54 个。 # 点格子 -> `net.SendSelectItemPacket(inventoryCell)`(= M2Client.script_select_item)+ 关窗; # 关闭 / ExitButton -> `net.SendSelectItemPacket(0)` + 关窗(uiselectitem.Close)。 extends Node const UiAssets = preload("res://ui/ui_assets.gd") const ITEM_TYPE_METIN := 10 # GameLib/ItemData.h EItemType const INVENTORY_PAGE_SIZE := 45 # player.INVENTORY_PAGE_SIZE const MAX_ITEMS := 54 # uiselectitem: slotPos > 54 -> break const WINDOW_INVENTORY := 1 var ui: CanvasLayer # UiManager var client: Node # M2Client var proto: Node # Metin2Proto var item_list: RefCounted # ItemListDB(可空) var assets_root := "" var uiscript_dir := "" var _win: Dictionary = {} # { root, nodes } var _cells := {} # slot_index:int -> Panel var _slot_to_inv := {} # 选择窗格号 -> 背包格号(inventorySlotPosDict) var _sent := false # 已发过一次 select(避免关窗再补发 0) func setup(ui_manager: CanvasLayer, m2client: Node, proto_node: Node, assets := "", il: RefCounted = null) -> void: ui = ui_manager client = m2client proto = proto_node item_list = il assets_root = assets if assets_root == "" and ui and "assets_root" in ui: assets_root = ui.assets_root uiscript_dir = assets_root.path_join("uiscript/uiscript") if not DirAccess.dir_exists_absolute(uiscript_dir): uiscript_dir = assets_root.path_join("uiscript") if client and client.has_signal("inventory_changed"): client.inventory_changed.connect(func(_w, _c): if is_open(): _refresh()) # --- open / close ---------------------------------------------------- func is_open() -> bool: return not _win.is_empty() and is_instance_valid(_win.get("root")) func toggle() -> void: if is_open(): close() else: open() # uiselectitem.Close: net.SendSelectItemPacket(0) 再隐藏 func close() -> void: if is_open(): if not _sent and client and client.has_method("script_select_item"): client.script_select_item(0) ui.close(_win["root"]) _win = {} _cells.clear() _slot_to_inv.clear() func open() -> void: if is_open(): _refresh() return var path := uiscript_dir.path_join("selectitemwindow.py") if not FileAccess.file_exists(path): push_warning("SelectItemUI: no selectitemwindow.py at " + path) return _sent = false _win = ui.open_script(path, assets_root) if not is_open(): return _index_cells(_win["root"]) _wire() _refresh() func _node(nm: String) -> Control: if _win.is_empty(): return null var n = _win.get("nodes", {}).get(nm, null) return n if n is Control else null func _index_cells(root: Node) -> void: _cells.clear() for n in root.find_children("slot_*", "Panel", true, false): if n.has_meta("slot_index"): _cells[int(n.get_meta("slot_index"))] = n func _wire() -> void: for idx in _cells: var cell: Panel = _cells[idx] cell.gui_input.connect(func(e: InputEvent): _on_cell_input(int(idx), e)) var exit_btn := _node("ExitButton") if exit_btn is BaseButton: exit_btn.pressed.connect(close) var tb := _node("TitleBar") if tb: for b in tb.find_children("*", "BaseButton", true, false): b.pressed.connect(close) # --- fill (uiselectitem.RefreshSlot) -------------------------------- func _item_grade(vnum: int) -> int: # player.GetItemGrade: 物品内部名最后一位数字(非数字 -> 0) var nm := "" if proto: var pd: Dictionary = proto.item(vnum) nm = String(pd.get("name", "")) if nm == "": return 0 var last := nm.substr(nm.length() - 1, 1) return int(last) if last.is_valid_int() else 0 func _is_metin(vnum: int) -> bool: if proto == null: return false var pd: Dictionary = proto.item(vnum) return int(pd.get("type", 0)) == ITEM_TYPE_METIN func _refresh() -> void: if not is_open() or client == null: return for idx in _cells: _clear_cell(_cells[idx]) _slot_to_inv.clear() # 背包按格号索引,只看前 INVENTORY_PAGE_SIZE*2 格(uiselectitem 的 range) var by_cell := {} for d in client.get_inventory(): by_cell[int(d["cell"])] = d var slot_pos := 0 for i in range(INVENTORY_PAGE_SIZE * 2): var d = by_cell.get(i, null) if d == null: continue var vnum := int(d["vnum"]) if vnum == 0 or not _is_metin(vnum): continue if _item_grade(vnum) > 2: continue _slot_to_inv[slot_pos] = i var cell: Panel = _cells.get(slot_pos, null) if cell: _fill_cell(cell, vnum, int(d.get("count", 1))) slot_pos += 1 if slot_pos > MAX_ITEMS: break func _fill_cell(cell: Panel, vnum: int, count: int) -> void: _clear_cell(cell) cell.set_meta("vnum", vnum) var nm := "item %d" % vnum if proto: var pd: Dictionary = proto.item(vnum) if not pd.is_empty(): nm = String(pd.get("locale_name", pd.get("name", nm))) cell.tooltip_text = "%s\n#%d" % [nm, vnum] var tex := _icon(vnum) if tex: var tr := TextureRect.new() tr.name = "icon" tr.texture = tex tr.set_anchors_preset(Control.PRESET_FULL_RECT) tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE tr.stretch_mode = TextureRect.STRETCH_SCALE tr.mouse_filter = Control.MOUSE_FILTER_IGNORE cell.add_child(tr) else: var lb := Label.new() lb.name = "icon" lb.text = nm.substr(0, 6) lb.add_theme_font_size_override("font_size", 9) lb.mouse_filter = Control.MOUSE_FILTER_IGNORE cell.add_child(lb) if count > 1: var cnt := Label.new() cnt.name = "count" cnt.text = str(count) cnt.position = Vector2(2, 16) cnt.add_theme_font_size_override("font_size", 9) cnt.mouse_filter = Control.MOUSE_FILTER_IGNORE cell.add_child(cnt) func _clear_cell(cell: Panel) -> void: cell.set_meta("vnum", 0) cell.tooltip_text = "" for c in cell.get_children(): c.queue_free() func _icon(vnum: int) -> Texture2D: var rel := "" if item_list and item_list.has(vnum): rel = item_list.icon(vnum) if rel == "": rel = "icon/item/%05d.tga" % ((vnum / 10) * 10) return UiAssets.load_tex(assets_root, rel) # --- select (uiselectitem.SelectItemSlot) -------------------------- func _on_cell_input(slot_pos: int, e: InputEvent) -> void: if not (e is InputEventMouseButton) or e.button_index != MOUSE_BUTTON_LEFT or not e.pressed: return if not _slot_to_inv.has(slot_pos): return var inv_cell := int(_slot_to_inv[slot_pos]) if client and client.has_method("script_select_item"): client.script_select_item(inv_cell) # = net.SendSelectItemPacket(inventorySlotPos) _sent = true close()