# 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_"), }