Files
mtgodot-poc/project/chest_system.gd
T
shenandshen 66d217b313 feat(client): 完成40250客户端核心功能1:1对齐与桥梁高度采样修复
- 桥梁与静态物体高度采样修复:
  - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
2026-09-19 08:51:25 -07:00

146 lines
5.8 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# chest_system.gd —— 40250 首领宝箱与钥匙系统 1:1 规则引擎
# 严格对照:
# metin2/src/server/game/src/char_item.cpp:2024-2080 (ITEM_GIFTBOX / 50070 / 50011)
# metin2/src/server/game/src/char_item.cpp:1936-1980 (ITEM_TREASURE_BOX / ITEM_TREASURE_KEY)
# metin2/src/server/game/src/special_item_group.txt
extends RefCounted
# 宝箱与钥匙 VNUM 定义
const VNUM_SILVER_BOX := 50001 # 银宝箱
const VNUM_SILVER_KEY := 50002 # 银钥匙
const VNUM_GOLD_BOX := 50003 # 金宝箱
const VNUM_GOLD_KEY := 50004 # 金钥匙
const VNUM_MOONLIGHT_BOX := 50011 # 月光宝盒
const VNUM_CHIEF_ORC_CHEST := 50070 # 兽人首领箱
const VNUM_DARK_LEADER_CHEST := 50071# 黑魔首领箱
const VNUM_SPIDER_QUEEN_CHEST := 50073# 蜘蛛女王箱
const VNUM_NINE_TAILS_CHEST := 50078 # 九尾妖狐宝箱
const VNUM_FLAME_KING_CHEST := 50079 # 火炎领主宝箱
const VNUM_REAPER_CHEST := 50082 # 死神宝箱
# 特殊掉落类型标识(CSpecialItemGroup
const SPECIAL_GOLD := 1000000001
const SPECIAL_EXP := 1000000002
const SPECIAL_MOB_TRAP := 1000000003
const SPECIAL_SLOW_TRAP := 1000000004
const SPECIAL_POISON_TRAP := 1000000005
# 钥匙与宝箱配对映射表
const KEY_BOX_PAIRS := {
VNUM_SILVER_KEY: VNUM_SILVER_BOX,
VNUM_GOLD_KEY: VNUM_GOLD_BOX,
}
# 官方 40250 经典掉落表数据结构 (vnum, count, prob_weight)
const DROP_TABLES := {
VNUM_MOONLIGHT_BOX: [
{"type": "item", "vnum": 50300, "count": 1, "weight": 20, "name": "技能书"},
{"type": "item", "vnum": 71001, "count": 1, "weight": 15, "name": "驱魔卷轴"},
{"type": "item", "vnum": 71094, "count": 1, "weight": 10, "name": "隐士教训"},
{"type": "item", "vnum": 25040, "count": 1, "weight": 15, "name": "祝福卷轴"},
{"type": "gold", "vnum": SPECIAL_GOLD, "count": 50000, "weight": 25, "name": "金币 50,000"},
{"type": "exp", "vnum": SPECIAL_EXP, "count": 30000, "weight": 15, "name": "经验值 30,000"},
],
VNUM_CHIEF_ORC_CHEST: [
{"type": "item", "vnum": 50300, "count": 1, "weight": 20, "name": "技能书"},
{"type": "item", "vnum": 50513, "count": 1, "weight": 10, "name": "魂石"},
{"type": "item", "vnum": 25040, "count": 1, "weight": 15, "name": "祝福卷轴"},
{"type": "item", "vnum": 11249, "count": 1, "weight": 10, "name": "幽冥战甲+9"},
{"type": "trap_poison", "vnum": SPECIAL_POISON_TRAP, "count": 1, "weight": 10, "name": "毒气陷阱"},
{"type": "gold", "vnum": SPECIAL_GOLD, "count": 100000, "weight": 20, "name": "金币 100,000"},
{"type": "exp", "vnum": SPECIAL_EXP, "count": 50000, "weight": 15, "name": "经验值 50,000"},
],
VNUM_SILVER_BOX: [
{"type": "item", "vnum": 25040, "count": 1, "weight": 25, "name": "祝福卷轴"},
{"type": "item", "vnum": 50300, "count": 1, "weight": 25, "name": "技能书"},
{"type": "gold", "vnum": SPECIAL_GOLD, "count": 30000, "weight": 30, "name": "金币 30,000"},
{"type": "trap_slow", "vnum": SPECIAL_SLOW_TRAP, "count": 1, "weight": 20, "name": "减速毒雾"},
],
VNUM_GOLD_BOX: [
{"type": "item", "vnum": 25040, "count": 2, "weight": 25, "name": "祝福卷轴 x2"},
{"type": "item", "vnum": 50513, "count": 1, "weight": 20, "name": "魂石"},
{"type": "item", "vnum": 71001, "count": 2, "weight": 20, "name": "驱魔卷轴 x2"},
{"type": "gold", "vnum": SPECIAL_GOLD, "count": 80000, "weight": 35, "name": "金币 80,000"},
]
}
static func is_gift_box(vnum: int) -> bool:
return vnum in [
VNUM_MOONLIGHT_BOX, VNUM_CHIEF_ORC_CHEST, VNUM_DARK_LEADER_CHEST,
VNUM_SPIDER_QUEEN_CHEST, VNUM_NINE_TAILS_CHEST, VNUM_FLAME_KING_CHEST,
VNUM_REAPER_CHEST
]
static func is_treasure_box(vnum: int) -> bool:
return vnum in [VNUM_SILVER_BOX, VNUM_GOLD_BOX]
static func is_treasure_key(vnum: int) -> bool:
return vnum in [VNUM_SILVER_KEY, VNUM_GOLD_KEY]
static func is_matching_key(key_vnum: int, box_vnum: int) -> bool:
return KEY_BOX_PAIRS.get(key_vnum, 0) == box_vnum
# 开启普通礼盒(无需钥匙,直接右键)
static func open_gift_box(
box_vnum: int,
empty_inventory_slots: int,
roll_val := -1
) -> Dictionary:
if not is_gift_box(box_vnum):
return {"success": false, "err": "not_gift_box", "msg": "该物品无法直接开启。"}
if empty_inventory_slots < 1:
return {"success": false, "err": "inventory_full", "msg": "背包已满,无法开启宝箱。"}
return _roll_chest_reward(box_vnum, roll_val)
# 使用钥匙开启上锁宝箱(拖拽钥匙至宝箱)
static func open_treasure_box_with_key(
key_vnum: int,
box_vnum: int,
empty_inventory_slots: int,
roll_val := -1
) -> Dictionary:
if not is_treasure_box(box_vnum):
return {"success": false, "err": "not_treasure_box", "msg": "这不是一个需要钥匙的宝箱。"}
if not is_matching_key(key_vnum, box_vnum):
return {"success": false, "err": "key_mismatch", "msg": "这把钥匙无法打开该宝箱。"}
if empty_inventory_slots < 1:
return {"success": false, "err": "inventory_full", "msg": "背包已满,无法开启宝箱。"}
var res := _roll_chest_reward(box_vnum, roll_val)
res["consumed_key_vnum"] = key_vnum
res["consumed_box_vnum"] = box_vnum
return res
# 抽选掉落物
static func _roll_chest_reward(box_vnum: int, roll_val: int) -> Dictionary:
var drops: Array = DROP_TABLES.get(box_vnum, DROP_TABLES[VNUM_MOONLIGHT_BOX])
var total_weight := 0
for d in drops:
total_weight += int(d.get("weight", 10))
var dice: int = roll_val if roll_val > 0 else (randi() % total_weight)
dice = dice % total_weight # 约束范围
var accum := 0
var chosen: Dictionary = drops[0]
for d in drops:
accum += int(d.get("weight", 10))
if dice < accum:
chosen = d
break
return {
"success": true,
"box_vnum": box_vnum,
"reward_type": chosen.get("type", "item"),
"reward_vnum": chosen.get("vnum", 0),
"reward_count": chosen.get("count", 1),
"reward_name": chosen.get("name", "神秘道具"),
"is_trap": String(chosen.get("type", "")).begins_with("trap_"),
}