- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
144 lines
5.0 KiB
GDScript
144 lines
5.0 KiB
GDScript
# item_split_system.gd —— Metin2 40250 物品堆叠精准拆分与快捷批量操作 1:1
|
|
# 对照 40250 服务端 char_item.cpp, input_main.cpp, uiinventory.py
|
|
class_name ItemSplitSystem
|
|
extends RefCounted
|
|
|
|
signal item_split(source_slot: int, target_slot: int, split_count: int)
|
|
signal item_quick_sold(slot_idx: int, gold_received: int, item_name: String)
|
|
signal item_split_requested(source_slot: int, target_slot: int, split_count: int)
|
|
signal item_quick_sell_requested(slot_idx: int, count: int, item_name: String)
|
|
|
|
# 堆叠精准拆分 (Shift + 左键)
|
|
func split_item(
|
|
source_slot: int,
|
|
split_count: int,
|
|
target_slot: int,
|
|
inventory: Array,
|
|
client: Node = null,
|
|
source_window: int = 1,
|
|
target_window: int = 1
|
|
) -> Dictionary:
|
|
if source_slot < 0 or source_slot >= inventory.size():
|
|
return {"ok": false, "reason": "INVALID_SOURCE_SLOT", "msg": "无效的源栏位!"}
|
|
|
|
var source_item = inventory[source_slot]
|
|
if source_item == null:
|
|
return {"ok": false, "reason": "EMPTY_SOURCE_SLOT", "msg": "源栏位上没有物品!"}
|
|
|
|
var current_count: int = int(source_item.get("count", 1))
|
|
if current_count <= 1:
|
|
return {"ok": false, "reason": "NOT_SPLITTABLE", "msg": "单件物品无法拆分!"}
|
|
|
|
if split_count < 1 or split_count >= current_count:
|
|
return {
|
|
"ok": false,
|
|
"reason": "INVALID_SPLIT_COUNT",
|
|
"max_allowed": current_count - 1,
|
|
"msg": "拆分数量必须在 1 到 %d 之间!" % (current_count - 1)
|
|
}
|
|
|
|
# 确定目标格子
|
|
var dest_slot := target_slot
|
|
if dest_slot < 0:
|
|
for i in range(inventory.size()):
|
|
if inventory[i] == null:
|
|
dest_slot = i
|
|
break
|
|
|
|
if dest_slot < 0 or dest_slot >= inventory.size():
|
|
return {"ok": false, "reason": "NO_DEST_SLOT", "msg": "背包没有可用空位放置拆分出的物品!"}
|
|
|
|
if inventory[dest_slot] != null:
|
|
return {"ok": false, "reason": "DEST_SLOT_NOT_EMPTY", "msg": "目标栏位已被占用!"}
|
|
|
|
# 40250 does not edit CPythonPlayer's item array optimistically. Splitting
|
|
# is an ordinary CG_ITEM_MOVE(source, destination, count); both resulting
|
|
# counts arrive later through GC_ITEM_UPDATE/SET. Keep this helper from
|
|
# becoming a second local authority when it is used by desktop or mobile UI.
|
|
if client == null or not client.has_method("move_item"):
|
|
return {"ok": false, "reason": "NETWORK_CLIENT_REQUIRED", "msg": "拆分需要连接服务器确认!"}
|
|
if not client.move_item(source_window, source_slot, target_window, dest_slot, split_count):
|
|
return {"ok": false, "reason": "SEND_FAILED", "msg": "拆分请求发送失败!"}
|
|
|
|
item_split_requested.emit(source_slot, dest_slot, split_count)
|
|
|
|
return {
|
|
"ok": true,
|
|
"pending": true,
|
|
"source_slot": source_slot,
|
|
"target_slot": dest_slot,
|
|
"remaining_source": current_count - split_count,
|
|
"split_count": split_count,
|
|
"msg": "已发送拆分请求,等待服务器确认!"
|
|
}
|
|
|
|
# 快捷批量出售给商人 (Ctrl + 右键)
|
|
func quick_sell_item(
|
|
slot_idx: int,
|
|
inventory: Array,
|
|
player_data: Dictionary,
|
|
client: Node = null,
|
|
window: int = 1
|
|
) -> Dictionary:
|
|
if slot_idx < 0 or slot_idx >= inventory.size():
|
|
return {"ok": false, "reason": "INVALID_SLOT", "msg": "无效的栏位!"}
|
|
|
|
var item = inventory[slot_idx]
|
|
if item == null:
|
|
return {"ok": false, "reason": "EMPTY_SLOT", "msg": "该格子上没有物品!"}
|
|
|
|
# 加锁保护
|
|
if item.get("is_locked", false):
|
|
return {"ok": false, "reason": "ITEM_LOCKED", "msg": "已加锁的物品严禁出售!"}
|
|
if window != 1:
|
|
return {"ok": false, "reason": "INVALID_SELL_WINDOW", "msg": "只能出售背包中的物品!"}
|
|
|
|
var count: int = int(item.get("count", 1))
|
|
# 40250's SELL/SELL2 response supplies the authoritative price and the
|
|
# subsequent item/gold updates. Do not use sale_price/player_data as a local
|
|
# substitute for the server result.
|
|
if client == null or not client.has_method("shop_sell"):
|
|
return {"ok": false, "reason": "NETWORK_CLIENT_REQUIRED", "msg": "出售需要连接服务器确认!"}
|
|
if not client.shop_sell(slot_idx, count):
|
|
return {"ok": false, "reason": "SEND_FAILED", "msg": "出售请求发送失败!"}
|
|
|
|
var iname: String = item.get("name", "物品")
|
|
item_quick_sell_requested.emit(slot_idx, count, iname)
|
|
|
|
return {
|
|
"ok": true,
|
|
"pending": true,
|
|
"slot_idx": slot_idx,
|
|
"count": count,
|
|
"item_name": iname,
|
|
"msg": "已发送出售请求,等待服务器确认!"
|
|
}
|
|
|
|
# 高价值物品丢弃/销毁防误触检查
|
|
func check_drop_safety(item: Dictionary) -> Dictionary:
|
|
if item.is_empty():
|
|
return {"can_drop": false}
|
|
|
|
if item.get("is_locked", false):
|
|
return {
|
|
"can_drop": false,
|
|
"require_confirm": false,
|
|
"reason": "ITEM_LOCKED",
|
|
"msg": "已加锁的贵重物品无法丢弃!"
|
|
}
|
|
|
|
var refine_level: int = int(item.get("refine_level", 0))
|
|
var has_attrs: bool = item.has("attributes") and not item["attributes"].is_empty()
|
|
|
|
if refine_level >= 7 or has_attrs:
|
|
return {
|
|
"can_drop": true,
|
|
"require_confirm": true,
|
|
"msg": "此装备强化等级较高或拥有附加属性,属于贵重装备!确定要丢弃吗?"
|
|
}
|
|
|
|
return {
|
|
"can_drop": true,
|
|
"require_confirm": false
|
|
}
|