完善客户端功能并同步差距文档

This commit is contained in:
shenlei
2026-09-03 18:40:01 +09:00
parent f93a172296
commit 13ccb8d02d
135 changed files with 21881 additions and 1259 deletions
+83 -2
View File
@@ -18,6 +18,8 @@
# M2Client classic 分支按该语义发送。
extends Node
const ItemTooltip = preload("res://ui/item_tooltip.gd")
class PrivateShopDragButton extends Button:
var drag_entry: Dictionary = {}
@@ -32,10 +34,13 @@ class PrivateShopDragButton extends Button:
const PRIVATE_SHOP_ITEM_MAX := 39 # PRIVATE_SHOP_ITEM_MAX_NUM (TPacketCGMyShop::bCount 上限)
const SIGN_MAX := 25 # NameLine input_limit
const ITEM_ANTIFLAG_GIVE := 1 << 13
const ITEM_ANTIFLAG_MYSHOP := 1 << 16
var client: Node
var ui: CanvasLayer # UiManager
var proto: Node
var _tooltip_builder: RefCounted
var assets_root := ""
var uiscript_dir := ""
@@ -48,6 +53,8 @@ var _stock: Dictionary = {} # 格号:int -> {cell, vnum, count, price}
var _picked = null # {cell, vnum, count} 等待落位
var _price_dialog: Control = null
var _server_prices: Dictionary = {} # vnum -> last price returned by MyShopPriceList
var item_mouse: Node
const INVENTORY_WINDOW := 1
func setup(m2client: Node, ui_manager: CanvasLayer, proto_node: Node = null, assets := "") -> void:
client = m2client
@@ -56,6 +63,8 @@ func setup(m2client: Node, ui_manager: CanvasLayer, proto_node: Node = null, ass
assets_root = assets
if assets_root == "" and ui and "assets_root" in ui:
assets_root = ui.assets_root
_tooltip_builder = ItemTooltip.new()
_tooltip_builder.setup(assets_root if assets_root != "" else AssetRoot.path(), "en", proto)
uiscript_dir = assets_root.path_join("uiscript/uiscript")
if not DirAccess.dir_exists_absolute(uiscript_dir):
uiscript_dir = assets_root.path_join("uiscript")
@@ -77,6 +86,10 @@ func toggle() -> void:
func close() -> void:
_dismiss_price_dialog()
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 = {}
@@ -124,6 +137,28 @@ func _index_cells(root: Node) -> void:
n.set_drag_forwarding(Callable(self, "_no_drag_data"),
Callable(self, "_can_drop_slot").bind(idx),
Callable(self, "_drop_slot").bind(idx))
if item_mouse and item_mouse.has_method("register_target"):
item_mouse.register_target(n,
func(payload: Dictionary): return _drop_mouse_item(payload, idx), self)
func _drop_mouse_item(payload: Dictionary, slot: int) -> bool:
if payload.is_empty() or slot < 0 or slot >= 40 or _stock.has(slot):
return false
if int(payload.get("window", -1)) != INVENTORY_WINDOW:
return false
var cell := int(payload.get("cell", -1))
var vnum := int(payload.get("vnum", 0))
if cell < 0 or vnum <= 0:
return false
var entry := payload.duplicate(true)
entry["cell"] = cell
entry["vnum"] = vnum
entry["count"] = maxi(1, int(payload.get("count", 1)))
if not _can_stock(entry):
return false
_picked = entry
_ask_price(slot)
return true
# NameLine 在 uiscript 里是 textLabel)—— 盖一个 LineEdit 上去
func _overlay_name_edit(root: Control) -> void:
@@ -219,10 +254,17 @@ func _refresh_inv() -> void:
var count := int(it.get("count", 1))
var b := PrivateShopDragButton.new()
b.text = "%s ×%d" % [_name_of(vnum), count]
if _tooltip_builder:
var pd: Dictionary = proto.item(vnum) if proto and proto.has_method("item") else {}
b.tooltip_text = _tooltip_builder.format(vnum, count, pd, it)
b.alignment = HORIZONTAL_ALIGNMENT_LEFT
b.custom_minimum_size = Vector2(196, 22)
b.toggle_mode = true
var entry := {"cell": cell, "vnum": vnum, "count": count}
var entry: Dictionary = it.duplicate(true)
entry["cell"] = cell
entry["vnum"] = vnum
entry["count"] = count
entry["anti_flags"] = int(it.get("anti_flags", 0))
b.drag_entry = entry
b.pressed.connect(func() -> void: _pick(entry, b))
if _picked != null and int(_picked["cell"]) == cell:
@@ -256,7 +298,13 @@ func _refresh_grid() -> void:
pr.add_theme_color_override("font_color", Color(0.95, 0.85, 0.5))
pr.mouse_filter = Control.MOUSE_FILTER_IGNORE
cell.add_child(pr)
cell.tooltip_text = "%s ×%d\n%d" % [_name_of(int(s["vnum"])), int(s["count"]), int(s["price"])]
var tooltip_item := s.duplicate(true)
tooltip_item["count"] = int(s["count"])
if _tooltip_builder:
var pd: Dictionary = proto.item(int(s["vnum"])) if proto and proto.has_method("item") else {}
cell.tooltip_text = _tooltip_builder.format(int(s["vnum"]), int(s["count"]), pd, tooltip_item)
else:
cell.tooltip_text = "%s ×%d\n%d" % [_name_of(int(s["vnum"])), int(s["count"]), int(s["price"])]
cell.modulate = Color(1, 1, 1)
else:
cell.modulate = Color(0.72, 0.72, 0.82) if _picked != null else Color(1, 1, 1)
@@ -264,6 +312,8 @@ func _refresh_grid() -> void:
# --- 상호작용 --------------------------------------------------------
func _pick(entry: Dictionary, _btn: Button) -> void:
if not _can_stock(entry):
return
if _picked != null and int(_picked["cell"]) == int(entry["cell"]):
_picked = null
else:
@@ -281,9 +331,36 @@ func _drop_slot(_at_position: Vector2, data, slot: int) -> void:
if not _can_drop_slot(Vector2.ZERO, data, slot):
return
var entry: Dictionary = data["private_shop_entry"] as Dictionary
if not _can_stock(entry):
return
_picked = entry.duplicate(true)
_ask_price(slot)
func _source_item(cell: int) -> Dictionary:
if client == null:
return {}
if client.has_method("get_item"):
var direct: Dictionary = client.get_item(INVENTORY_WINDOW, cell)
if not direct.is_empty():
return direct
if client.has_method("get_inventory"):
for item in client.get_inventory():
if int(item.get("cell", -1)) == cell:
return item
return {}
func _can_stock(entry: Dictionary) -> bool:
var item_data := _source_item(int(entry.get("cell", -1)))
var vnum := int(entry.get("vnum", item_data.get("vnum", 0)))
var proto_data: Dictionary = proto.item(vnum) if proto and proto.has_method("item") else {}
var flags := int(entry.get("anti_flags", 0)) | int(item_data.get("anti_flags", 0)) \
| int(proto_data.get("anti_flags", 0))
if (flags & (ITEM_ANTIFLAG_GIVE | ITEM_ANTIFLAG_MYSHOP)) != 0:
if is_instance_valid(_status):
_status.text = "该物品不可摆摊"
return false
return true
func _on_slot_clicked(slot: int) -> void:
if _stock.has(slot):
_stock.erase(slot)
@@ -343,6 +420,10 @@ func _place(slot: int, price: int) -> void:
if slot < 0 or slot >= 40 or price <= 0 or price > 2000000000 or _picked == null:
_dismiss_price_dialog()
return
if not _can_stock(_picked):
_dismiss_price_dialog()
_picked = null
return
_stock[slot] = {"cell": int(_picked["cell"]), "vnum": int(_picked["vnum"]),
"count": int(_picked["count"]), "price": price}
_picked = null