完善客户端功能并同步差距文档
This commit is contained in:
+194
-21
@@ -10,39 +10,63 @@
|
||||
# 拖到别的格=move_item。inventory_changed 信号来时刷新。
|
||||
#
|
||||
# 格子编号约定(uiscript):0..89 背包,90+ 装备(wear = index-90)。
|
||||
# 新装备 / 腰带没有可靠的旧 uiscript 布局时,在背包右侧生成扩展面板;发包仍
|
||||
# 使用原客户端的 INVENTORY 全局 cell(90+wear / 152+belt cell)。
|
||||
# 新装备 / 腰带没有可靠的旧 uiscript 布局时,在背包右侧生成扩展面板。发包坐标见
|
||||
# `_to_wire()`:旧 11 格 wear(BODY..SHIELD)走 EQUIPMENT 窗(window=2, wear cell);
|
||||
# costume/ring/belt(wear >= 19)与 belt 背包走 INVENTORY 窗(window=1)的全局 cell
|
||||
# (109..113 / 152+belt cell,见 GameType.h `c_Costume_Slot_Start` / `c_Equipment_Belt`)。
|
||||
extends Node
|
||||
|
||||
# 右键点道具时发出(window, cell 为 wire 坐标)。P8 的商店 / 交易 / 仓库窗接它。
|
||||
signal item_context(window: int, cell: int)
|
||||
|
||||
# 背包物品右键穿戴被 EquipRules 前置门拦下时发出(code 见 equip_rules.gd,need = limit 要求值)。
|
||||
signal equip_rejected(code: String, need: int)
|
||||
|
||||
# 若设置:右键道具时先调它 (window, cell) -> bool;返回 true 表示已处理,不再 use_item。
|
||||
var context_consumer: Callable = Callable()
|
||||
|
||||
const UiAssets = preload("res://ui/ui_assets.gd")
|
||||
const EquipRules = preload("res://equip_rules.gd")
|
||||
const ItemTooltip = preload("res://ui/item_tooltip.gd")
|
||||
|
||||
const EQUIP_BASE := 90 # uiscript EQUIPMENT_START_INDEX
|
||||
const BELT_BASE := 200 # 仅 UI 内部索引,避免与旧 slot_index 相撞
|
||||
const BELT_SLOT_START := 152 # GameType.c_Belt_Inventory_Slot_Start
|
||||
const BELT_CELL_COUNT := 16
|
||||
const WEAR_COUNT := 24
|
||||
const WEAR_BELT := 23
|
||||
const WINDOW_INVENTORY := 1 # mtnet::WINDOW_*
|
||||
const WEAR_COUNT := 24 # get_equipment() 数组上限(server wear 保留位;实际用到 0..23)
|
||||
|
||||
# wear 位置编号(GameType.h `EWearPositions` / `c_Equipment_*`,wear = 全局 cell - c_Equipment_Start(90))。
|
||||
# 旧 11 格(BODY..SHIELD)在客户端走 EQUIPMENT 窗(window=2, wear cell);
|
||||
# ENABLE_NEW_EQUIPMENT_SYSTEM 的 costume/ring/belt 仍留在 INVENTORY 窗(window=1)的
|
||||
# 全局 cell 命名空间里 —— c_Costume_Slot_Start = 90+19, c_New_Equipment_Start = 90+21。
|
||||
const WEAR_MAX_NUM := 11 # ItemData.h EWearPositions::WEAR_MAX_NUM
|
||||
const WEAR_COSTUME_BODY := 19 # GameType.h c_Costume_Slot_Body - c_Equipment_Start
|
||||
const WEAR_COSTUME_HAIR := 20 # c_Costume_Slot_Hair - c_Equipment_Start
|
||||
const WEAR_RING1 := 21 # c_Equipment_Ring1 - c_Equipment_Start
|
||||
const WEAR_RING2 := 22 # c_Equipment_Ring2 - c_Equipment_Start
|
||||
const WEAR_BELT := 23 # c_Equipment_Belt - c_Equipment_Start
|
||||
const NEW_EQUIP_WEAR_START := WEAR_COSTUME_BODY # >= 此值的 wear 位置走 INVENTORY 全局 cell
|
||||
const WINDOW_INVENTORY := 1 # mtnet::WINDOW_INVENTORY
|
||||
const WINDOW_EQUIPMENT := 2 # mtnet::WINDOW_EQUIPMENT(旧 11 格 wear)
|
||||
const WINDOW_SAFEBOX := 3 # global mouse payload source, not an inventory window
|
||||
const WINDOW_MALL := 4 # global mouse payload source, not an inventory window
|
||||
const BELT_RULES := [1, 2, 4, 6, 3, 3, 4, 6, 5, 5, 5, 6, 7, 7, 7, 7]
|
||||
|
||||
var ui: CanvasLayer # UiManager
|
||||
var client: Node # M2Client
|
||||
var proto: Node # Metin2Proto
|
||||
var item_list: RefCounted # ItemList(可空;给了就用它的 icon 路径)
|
||||
var item_tooltip: RefCounted
|
||||
var assets_root := ""
|
||||
var uiscript_dir := ""
|
||||
var item_mouse: Node # MouseController(可空,兼容旧测试桩)
|
||||
|
||||
var _win: Dictionary = {} # { root, nodes }
|
||||
var _cells := {} # ui_index:int -> Panel
|
||||
var _drag_from := -1
|
||||
var _combine_from := -1 # Shift 选中的“使用到物品”来源格
|
||||
var _hint: Label
|
||||
var _money_button: BaseButton
|
||||
|
||||
func setup(ui_manager: CanvasLayer, m2client: Node, proto_node: Node, assets: String,
|
||||
il: RefCounted = null) -> void:
|
||||
@@ -51,6 +75,8 @@ func setup(ui_manager: CanvasLayer, m2client: Node, proto_node: Node, assets: St
|
||||
proto = proto_node
|
||||
item_list = il
|
||||
assets_root = assets
|
||||
item_tooltip = ItemTooltip.new()
|
||||
item_tooltip.setup(assets_root, "en", proto)
|
||||
uiscript_dir = assets.path_join("uiscript/uiscript")
|
||||
if not DirAccess.dir_exists_absolute(uiscript_dir):
|
||||
uiscript_dir = assets.path_join("uiscript")
|
||||
@@ -65,6 +91,10 @@ func toggle() -> void:
|
||||
else: open()
|
||||
|
||||
func close() -> void:
|
||||
if item_mouse and item_mouse.has_method("unregister_owner"):
|
||||
item_mouse.unregister_owner(self)
|
||||
if item_mouse.has_method("is_attached") and item_mouse.is_attached():
|
||||
item_mouse.cancel()
|
||||
if is_open():
|
||||
ui.close(_win["root"])
|
||||
_win = {}
|
||||
@@ -83,6 +113,7 @@ func open() -> void:
|
||||
_index_cells(_win["root"])
|
||||
_add_extended_cells(_win["root"])
|
||||
_wire_cells()
|
||||
_wire_money()
|
||||
refresh()
|
||||
|
||||
# --- 填充 ---------------------------------------------------------------
|
||||
@@ -95,34 +126,40 @@ func refresh() -> void:
|
||||
for d in client.get_inventory():
|
||||
var cell: Panel = _cells.get(int(d["cell"]), null)
|
||||
if cell:
|
||||
_fill_cell(cell, int(d["vnum"]), int(d["count"]))
|
||||
_fill_cell(cell, int(d["vnum"]), int(d["count"]), d)
|
||||
var eq: Array = client.get_equipment()
|
||||
for i in eq.size():
|
||||
var it: Dictionary = eq[i]
|
||||
var cell: Panel = _cells.get(EQUIP_BASE + i, null)
|
||||
if cell and int(it.get("vnum", 0)) != 0:
|
||||
_fill_cell(cell, int(it["vnum"]), int(it.get("count", 1)))
|
||||
for d in client.get_belt_inventory():
|
||||
_fill_cell(cell, int(it["vnum"]), int(it.get("count", 1)), it)
|
||||
var belt_items: Array = client.get_belt_inventory() if client.has_method("get_belt_inventory") else []
|
||||
for d in belt_items:
|
||||
var cell: Panel = _cells.get(BELT_BASE + int(d["cell"]), null)
|
||||
if cell:
|
||||
_fill_cell(cell, int(d["vnum"]), int(d.get("count", 1)))
|
||||
_fill_cell(cell, int(d["vnum"]), int(d.get("count", 1)), d)
|
||||
if is_instance_valid(_money_button):
|
||||
_money_button.text = _format_gold(_current_gold())
|
||||
_apply_belt_locks(eq)
|
||||
|
||||
func _on_inv_changed(_window: int, _cell: int) -> void:
|
||||
refresh()
|
||||
|
||||
func _fill_cell(cell: Panel, vnum: int, count: int) -> void:
|
||||
func _fill_cell(cell: Panel, vnum: int, count: int, instance_data: Dictionary = {}) -> void:
|
||||
_clear_cell(cell)
|
||||
cell.set_meta("vnum", vnum)
|
||||
cell.set_meta("count", count)
|
||||
cell.set_meta("item_data", instance_data.duplicate(true))
|
||||
var name := "item %d" % vnum
|
||||
var itype := 0
|
||||
var proto_data: Dictionary = {}
|
||||
if proto:
|
||||
var pd: Dictionary = proto.item(vnum)
|
||||
if not pd.is_empty():
|
||||
name = String(pd.get("locale_name", pd.get("name", name)))
|
||||
itype = int(pd.get("type", 0))
|
||||
cell.tooltip_text = "%s\n#%d%s" % [name, vnum, (" x%d" % count) if count > 1 else ""]
|
||||
proto_data = proto.item(vnum)
|
||||
if not proto_data.is_empty():
|
||||
name = String(proto_data.get("locale_name", proto_data.get("name", name)))
|
||||
if item_tooltip:
|
||||
cell.tooltip_text = item_tooltip.format(vnum, count, proto_data, instance_data)
|
||||
else:
|
||||
cell.tooltip_text = "%s\n#%d%s" % [name, vnum, (" x%d" % count) if count > 1 else ""]
|
||||
var tex := _icon(vnum)
|
||||
if tex:
|
||||
var tr := TextureRect.new()
|
||||
@@ -152,6 +189,7 @@ func _fill_cell(cell: Panel, vnum: int, count: int) -> void:
|
||||
func _clear_cell(cell: Panel) -> void:
|
||||
cell.set_meta("vnum", 0)
|
||||
cell.set_meta("count", 0)
|
||||
cell.set_meta("item_data", {})
|
||||
cell.tooltip_text = ""
|
||||
for c in cell.get_children():
|
||||
c.queue_free()
|
||||
@@ -175,7 +213,33 @@ func _index_cells(root: Node) -> void:
|
||||
func _wire_cells() -> void:
|
||||
for idx in _cells:
|
||||
var cell: Panel = _cells[idx]
|
||||
cell.gui_input.connect(func(e: InputEvent): _on_cell_input(int(idx), e))
|
||||
var cell_idx: int = int(idx)
|
||||
cell.gui_input.connect(func(e: InputEvent): _on_cell_input(cell_idx, e))
|
||||
if item_mouse and item_mouse.has_method("register_target"):
|
||||
item_mouse.register_target(cell, func(payload: Dictionary): return _drop_mouse_item(payload, cell_idx), self)
|
||||
|
||||
func _wire_money() -> void:
|
||||
_money_button = _win.get("nodes", {}).get("Money_Slot", null)
|
||||
if _money_button is BaseButton:
|
||||
_money_button.pressed.connect(_on_money_pressed)
|
||||
else:
|
||||
_money_button = null
|
||||
|
||||
func _current_gold() -> int:
|
||||
if client and client.has_method("get_points"):
|
||||
return maxi(0, int(client.get_points().get("gold", 0)))
|
||||
return 0
|
||||
|
||||
func _format_gold(gold: int) -> String:
|
||||
return str(gold)
|
||||
|
||||
func _on_money_pressed() -> void:
|
||||
if item_mouse == null or not item_mouse.has_method("attach_money"):
|
||||
return
|
||||
var gold := mini(_current_gold(), 9999999)
|
||||
if gold <= 0:
|
||||
return
|
||||
_ask_count("金币数量", gold, func(amount: int): item_mouse.attach_money(amount, "inventory"))
|
||||
|
||||
func _add_extended_cells(root: Control) -> void:
|
||||
# 旧 inventorywindow.py 只排出前 11 个传统装备位。剩余能力 / 时装 /
|
||||
@@ -190,7 +254,9 @@ func _add_extended_cells(root: Control) -> void:
|
||||
bg.set_border_width_all(1)
|
||||
panel.add_theme_stylebox_override("panel", bg)
|
||||
root.add_child(panel)
|
||||
_add_grid(panel, "扩展装备", 11, WEAR_COUNT, EQUIP_BASE, Vector2(10, 30))
|
||||
# 旧 uiscript 只排 BODY..SHIELD(wear 0..10)。EWearPositions 里 11..18 无位置,
|
||||
# 扩展面板只补 costume(19/20) / ring(21/22) / belt(23) 这 5 个真实新装备位。
|
||||
_add_grid(panel, "时装 / 戒指 / 腰带", WEAR_COSTUME_BODY, WEAR_BELT - WEAR_COSTUME_BODY + 1, EQUIP_BASE, Vector2(10, 30))
|
||||
_add_grid(panel, "腰带背包", 0, BELT_CELL_COUNT, BELT_BASE, Vector2(10, 242))
|
||||
_hint = Label.new()
|
||||
_hint.position = Vector2(10, 430)
|
||||
@@ -270,12 +336,24 @@ func _on_cell_input(ui_idx: int, e: InputEvent) -> void:
|
||||
elif e.button_index == MOUSE_BUTTON_LEFT:
|
||||
if e.pressed and vnum != 0:
|
||||
if e.shift_pressed:
|
||||
_begin_combine(ui_idx)
|
||||
if _item_count(ui_idx) > 1:
|
||||
_split_stack(ui_idx)
|
||||
else:
|
||||
_begin_combine(ui_idx)
|
||||
return
|
||||
if _combine_from != -1:
|
||||
if _combine_from != ui_idx:
|
||||
_use_to_item(_combine_from, ui_idx)
|
||||
return
|
||||
if item_mouse and item_mouse.has_method("attach_item"):
|
||||
var wire := _to_wire(ui_idx)
|
||||
var metadata: Dictionary = {}
|
||||
if client.has_method("get_item"):
|
||||
metadata = client.get_item(wire[0], wire[1])
|
||||
if item_mouse.attach_item(wire[0], wire[1], vnum, _item_count(ui_idx),
|
||||
_icon(vnum), "inventory", metadata):
|
||||
_drag_from = -1
|
||||
return
|
||||
_drag_from = ui_idx
|
||||
elif not e.pressed and _drag_from != -1:
|
||||
var over := _cell_under_mouse()
|
||||
@@ -283,6 +361,35 @@ func _on_cell_input(ui_idx: int, e: InputEvent) -> void:
|
||||
move_to(_drag_from, over, 1)
|
||||
_drag_from = -1
|
||||
|
||||
func _drop_mouse_item(payload: Dictionary, target_ui: int) -> bool:
|
||||
if client == null or payload.is_empty() or target_ui < 0:
|
||||
return false
|
||||
if bool(_cells.get(target_ui, null).get_meta("locked", false)) if _cells.get(target_ui, null) else true:
|
||||
return false
|
||||
var source_window := int(payload.get("window", -1))
|
||||
var source_cell := int(payload.get("cell", -1))
|
||||
if source_cell < 0:
|
||||
return false
|
||||
var target := _to_wire(target_ui)
|
||||
if source_cell == int(target[1]) and source_window == int(target[0]):
|
||||
return true
|
||||
if source_window == WINDOW_SAFEBOX:
|
||||
return target[0] == WINDOW_INVENTORY and client.has_method("safebox_checkout") \
|
||||
and client.safebox_checkout(source_cell, target[0], target[1])
|
||||
if source_window == WINDOW_MALL:
|
||||
return target[0] == WINDOW_INVENTORY and client.has_method("mall_checkout") \
|
||||
and client.mall_checkout(source_cell, target[0], target[1])
|
||||
if source_window not in [WINDOW_INVENTORY, 2]:
|
||||
return false
|
||||
return move_wire(source_window, source_cell, int(target[0]), int(target[1]),
|
||||
maxi(1, int(payload.get("count", 1))))
|
||||
|
||||
func move_wire(source_window: int, source_cell: int, target_window: int,
|
||||
target_cell: int, count := 1) -> bool:
|
||||
if client == null or not client.has_method("move_item"):
|
||||
return false
|
||||
return client.move_item(source_window, source_cell, target_window, target_cell, count)
|
||||
|
||||
# 把 ui 格子 from 的物品挪到 ui 格子 to(发 CG_ITEM_MOVE)
|
||||
func move_to(from_ui: int, to_ui: int, count := 1) -> bool:
|
||||
if client == null:
|
||||
@@ -291,13 +398,63 @@ func move_to(from_ui: int, to_ui: int, count := 1) -> bool:
|
||||
var b := _to_wire(to_ui)
|
||||
return client.move_item(a[0], a[1], b[0], b[1], count)
|
||||
|
||||
# 用掉 ui 格子里的物品(发 CG_ITEM_USE)
|
||||
# 用掉 ui 格子里的物品(发 CG_ITEM_USE)。
|
||||
# 对背包里的可穿戴物品(右键 = 穿),先跑 EquipRules.can_equip 前置门(§4.1 修改 2):
|
||||
# 校验不过 → 发 equip_rejected 并不发包(服务器仍是最终仲裁)。
|
||||
func use(ui_idx: int) -> bool:
|
||||
if client == null:
|
||||
return false
|
||||
if ui_idx < EQUIP_BASE:
|
||||
var cell: Panel = _cells.get(ui_idx, null)
|
||||
var vnum := int(cell.get_meta("vnum", 0)) if cell else 0
|
||||
if vnum != 0 and proto:
|
||||
var pi: Dictionary = proto.item(vnum)
|
||||
if EquipRules.is_equippable(pi):
|
||||
var verdict := EquipRules.can_equip(pi, -1, _equip_ctx())
|
||||
if not bool(verdict["ok"]):
|
||||
_set_hint(_equip_reject_hint(verdict))
|
||||
equip_rejected.emit(String(verdict["code"]), int(verdict["need"]))
|
||||
return false
|
||||
var w := _to_wire(ui_idx)
|
||||
return client.use_item(w[0], w[1])
|
||||
|
||||
# 公开校验入口(tooltip / 拖放目标预览可用)。wear 传 -1 = 用物品默认槽。
|
||||
func can_equip_item(vnum: int, wear := -1) -> Dictionary:
|
||||
if vnum == 0 or proto == null:
|
||||
return {"ok": false, "code": "NO_ITEM", "need": 0}
|
||||
return EquipRules.can_equip(proto.item(vnum), wear, _equip_ctx())
|
||||
|
||||
# 组装 EquipRules.can_equip 需要的运行期上下文。属性点客户端未按名透出 → 传 -1(seam)。
|
||||
func _equip_ctx() -> Dictionary:
|
||||
var ctx := {"race": 0, "empire": 0, "level": 0, "st": -1, "dx": -1, "ht": -1, "iq": -1}
|
||||
if client == null:
|
||||
return ctx
|
||||
if client.has_method("get_points"):
|
||||
var p: Dictionary = client.get_points()
|
||||
ctx["level"] = int(p.get("level", 0))
|
||||
if client.has_method("get_entities"):
|
||||
for e in client.get_entities():
|
||||
if bool(e.get("is_main", false)):
|
||||
ctx["race"] = int(e.get("race", 0))
|
||||
ctx["empire"] = int(e.get("empire", 0))
|
||||
if int(e.get("level", 0)) > 0:
|
||||
ctx["level"] = int(e.get("level", 0))
|
||||
break
|
||||
return ctx
|
||||
|
||||
func _equip_reject_hint(verdict: Dictionary) -> String:
|
||||
match String(verdict.get("code", "")):
|
||||
"ANTI_SEX": return "该装备不符合你的性别"
|
||||
"ANTI_JOB": return "该装备不符合你的职业"
|
||||
"ANTI_EMPIRE": return "该装备不符合你的帝国"
|
||||
"NOT_WEARABLE_HERE": return "该装备不能穿在这个位置"
|
||||
"LIMIT_LEVEL": return "需要等级 %d" % int(verdict.get("need", 0))
|
||||
"LIMIT_STR": return "需要力量 %d" % int(verdict.get("need", 0))
|
||||
"LIMIT_DEX": return "需要敏捷 %d" % int(verdict.get("need", 0))
|
||||
"LIMIT_INT": return "需要智力 %d" % int(verdict.get("need", 0))
|
||||
"LIMIT_CON": return "需要体力 %d" % int(verdict.get("need", 0))
|
||||
return "无法装备"
|
||||
|
||||
# Shift+左键(或 Shift+右键)选来源,再左键另一个非空格。
|
||||
func _begin_combine(ui_idx: int) -> void:
|
||||
if _combine_from == ui_idx:
|
||||
@@ -308,6 +465,16 @@ func _begin_combine(ui_idx: int) -> void:
|
||||
_drag_from = -1
|
||||
_set_hint("请选择目标物品(左键);再次 Shift 点击来源格可取消")
|
||||
|
||||
func _split_stack(ui_idx: int) -> void:
|
||||
var count := _item_count(ui_idx)
|
||||
if count <= 1 or item_mouse == null or not item_mouse.has_method("attach_item"):
|
||||
return
|
||||
_ask_count("拾取数量", count, func(amount: int):
|
||||
var wire := _to_wire(ui_idx)
|
||||
var metadata: Dictionary = client.get_item(wire[0], wire[1]) if client.has_method("get_item") else {}
|
||||
item_mouse.attach_item(wire[0], wire[1], int(_cells[ui_idx].get_meta("vnum", 0)),
|
||||
amount, _icon(int(_cells[ui_idx].get_meta("vnum", 0))), "inventory", metadata))
|
||||
|
||||
func _use_to_item(source_ui: int, target_ui: int) -> bool:
|
||||
var target: Panel = _cells.get(target_ui, null)
|
||||
if target == null or int(target.get_meta("vnum", 0)) == 0:
|
||||
@@ -399,5 +566,11 @@ func _to_wire(ui_idx: int) -> Array:
|
||||
if ui_idx >= BELT_BASE:
|
||||
return [WINDOW_INVENTORY, BELT_SLOT_START + ui_idx - BELT_BASE]
|
||||
if ui_idx >= EQUIP_BASE:
|
||||
return [WINDOW_INVENTORY, ui_idx]
|
||||
var wear := ui_idx - EQUIP_BASE
|
||||
# ENABLE_NEW_EQUIPMENT_SYSTEM 的 costume(19/20) / ring(21/22) / belt(23)
|
||||
# 在客户端里没有独立窗,仍用 INVENTORY 窗的全局 cell(109..113,见 GameType.h)。
|
||||
if wear >= NEW_EQUIP_WEAR_START:
|
||||
return [WINDOW_INVENTORY, EQUIP_BASE + wear]
|
||||
# 旧 11 格(BODY..SHIELD)走 EQUIPMENT 窗(window=2)+ 零基 wear cell。
|
||||
return [WINDOW_EQUIPMENT, wear]
|
||||
return [WINDOW_INVENTORY, ui_idx]
|
||||
|
||||
Reference in New Issue
Block a user