- 桥梁与静态物体高度采样修复: - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程 - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题 - 新增 test_bridge_height_parity.gd 自动化对拍测试 - 40250 怪物击杀经验动效: - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附 - 40250 客户端全系统功能对齐(Batches 1-31): - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试 - 文档沉淀: - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
164 lines
4.8 KiB
GDScript
164 lines
4.8 KiB
GDScript
# inventory_sort_system.gd —— Metin2 40250 多页背包智能自动整理与堆叠合并 1:1
|
|
# 对照 40250 服务端 char_item.cpp, uiinventory.py, item.cpp
|
|
class_name InventorySortSystem
|
|
extends RefCounted
|
|
|
|
signal inventory_sorted(total_items: int, merged_stacks: int)
|
|
signal page_switched(new_page: int)
|
|
|
|
const PAGE_SIZE := 45
|
|
const TOTAL_PAGES := 2
|
|
const TOTAL_SLOTS := 90 # 45 x 2
|
|
const MAX_STACK_LIMIT := 200
|
|
|
|
# 排序优先级分类
|
|
enum ItemCategory {
|
|
WEAPON = 1, # 武器
|
|
ARMOR = 2, # 铠甲防具
|
|
JEWELRY = 3, # 饰品首饰
|
|
CONSUMABLE = 4, # 药水与消耗品
|
|
MATERIAL = 5, # 强化升阶材料
|
|
CHEST_KEY = 6, # 宝箱与钥匙
|
|
OTHER = 7 # 其他
|
|
}
|
|
|
|
var current_page: int = 0
|
|
|
|
# 切换当前显示背包页 (0 或 1)
|
|
func switch_page(page_idx: int) -> Dictionary:
|
|
if page_idx < 0 or page_idx >= TOTAL_PAGES:
|
|
return {"ok": false, "reason": "INVALID_PAGE", "msg": "无效的背包页码!"}
|
|
|
|
current_page = page_idx
|
|
page_switched.emit(page_idx)
|
|
|
|
return {
|
|
"ok": true,
|
|
"page": page_idx,
|
|
"slot_start": page_idx * PAGE_SIZE,
|
|
"slot_end": (page_idx + 1) * PAGE_SIZE - 1
|
|
}
|
|
|
|
# 获取物品所属排序大类
|
|
static func get_item_category(item: Dictionary) -> int:
|
|
var it_type: String = item.get("item_type", "")
|
|
match it_type:
|
|
"weapon":
|
|
return ItemCategory.WEAPON
|
|
"armor", "shield", "helmet":
|
|
return ItemCategory.ARMOR
|
|
"jewelry", "necklace", "earrings", "bracelet", "shoes", "belt":
|
|
return ItemCategory.JEWELRY
|
|
"potion", "consumable", "dew", "elixir":
|
|
return ItemCategory.CONSUMABLE
|
|
"material", "fragment", "scroll":
|
|
return ItemCategory.MATERIAL
|
|
"chest", "key":
|
|
return ItemCategory.CHEST_KEY
|
|
_:
|
|
# 根据 vnum 范围与特征推断
|
|
var vnum: int = int(item.get("vnum", 0))
|
|
if vnum < 3000 and vnum >= 10:
|
|
return ItemCategory.WEAPON
|
|
elif vnum >= 11000 and vnum < 13200:
|
|
return ItemCategory.ARMOR
|
|
elif (vnum >= 27000 and vnum < 28000) or (vnum >= 50800 and vnum < 50900) or (vnum >= 72700 and vnum < 72800):
|
|
return ItemCategory.CONSUMABLE
|
|
elif (vnum >= 50000 and vnum < 50200) or (vnum >= 30000 and vnum < 30400):
|
|
return ItemCategory.CHEST_KEY
|
|
elif vnum >= 71000 and vnum < 72000:
|
|
return ItemCategory.MATERIAL
|
|
return ItemCategory.OTHER
|
|
|
|
# 判定物品是否可堆叠
|
|
static func is_item_stackable(item: Dictionary) -> bool:
|
|
if item.get("is_stackable", false):
|
|
return true
|
|
var it_type: String = item.get("item_type", "")
|
|
if ["potion", "consumable", "dew", "elixir", "material", "fragment", "scroll", "arrow"].has(it_type):
|
|
return true
|
|
var cnt: int = int(item.get("count", 1))
|
|
return cnt > 1
|
|
|
|
# 自动合并碎片堆叠 (Auto-Stack Merge)
|
|
func merge_stacks(inventory: Array) -> int:
|
|
var merged_count := 0
|
|
|
|
for i in range(inventory.size()):
|
|
var base = inventory[i]
|
|
if base == null or not is_item_stackable(base):
|
|
continue
|
|
|
|
var b_vnum: int = int(base.get("vnum", 0))
|
|
var b_count: int = int(base.get("count", 1))
|
|
if b_count >= MAX_STACK_LIMIT:
|
|
continue
|
|
|
|
for j in range(i + 1, inventory.size()):
|
|
var other = inventory[j]
|
|
if other == null or int(other.get("vnum", 0)) != b_vnum:
|
|
continue
|
|
|
|
var o_count: int = int(other.get("count", 1))
|
|
var space := MAX_STACK_LIMIT - b_count
|
|
|
|
if o_count <= space:
|
|
b_count += o_count
|
|
base["count"] = b_count
|
|
inventory[j] = null
|
|
merged_count += 1
|
|
else:
|
|
b_count = MAX_STACK_LIMIT
|
|
base["count"] = MAX_STACK_LIMIT
|
|
other["count"] = o_count - space
|
|
merged_count += 1
|
|
break
|
|
|
|
return merged_count
|
|
|
|
# 一键智能整理背包 (堆叠合并 + 分类前置紧凑排序)
|
|
func sort_inventory(inventory: Array) -> Dictionary:
|
|
# 1. 先进行碎片堆叠合并
|
|
var merged := merge_stacks(inventory)
|
|
|
|
# 2. 收集所有有效物品并打上排序权重标签
|
|
var items_to_sort: Array = []
|
|
for i in range(inventory.size()):
|
|
var it = inventory[i]
|
|
if it != null:
|
|
var cat := get_item_category(it)
|
|
var vnum: int = int(it.get("vnum", 0))
|
|
var level: int = int(it.get("level", 0))
|
|
items_to_sort.append({
|
|
"item": it,
|
|
"category": cat,
|
|
"vnum": vnum,
|
|
"level": level
|
|
})
|
|
|
|
# 3. 排序比较器:类别升序 (1~7),同类内 vnum 升序/降序排列
|
|
items_to_sort.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
|
if a["category"] != b["category"]:
|
|
return a["category"] < b["category"]
|
|
if a["vnum"] != b["vnum"]:
|
|
return a["vnum"] < b["vnum"]
|
|
return int(a["item"].get("count", 1)) > int(b["item"].get("count", 1))
|
|
)
|
|
|
|
# 4. 紧凑回填背包并清空末尾
|
|
var total_items := items_to_sort.size()
|
|
for i in range(inventory.size()):
|
|
if i < total_items:
|
|
inventory[i] = items_to_sort[i]["item"]
|
|
else:
|
|
inventory[i] = null
|
|
|
|
inventory_sorted.emit(total_items, merged)
|
|
|
|
return {
|
|
"ok": true,
|
|
"total_items": total_items,
|
|
"merged_stacks": merged,
|
|
"msg": "背包整理完成!共归纳 %d 件物品,合并 %d 处堆叠。" % [total_items, merged]
|
|
}
|