# treasure_box_key_system.gd —— Metin2 40250 官方金银宝箱与专属钥匙拖拽开启系统 1:1 # 100% 对照 40250 服务端 char_item.cpp (ITEM_TREASURE_BOX, ITEM_TREASURE_KEY), special_item_group.txt, item_proto.txt class_name TreasureBoxKeySystem extends RefCounted signal box_unlocked(box_vnum: int, key_vnum: int, reward: Dictionary) signal unlock_failed(reason: String) # 宝箱类型分类常数 (对照 item->GetValue(0)) const BOX_TYPE_SILVER := 1 # 银系列 (Silver) const BOX_TYPE_GOLD := 2 # 金系列 (Gold) # 40250 官方宝箱资料表 (ITEM_TREASURE_BOX) const BOXES: Dictionary = { 50006: { "name": "银宝箱 (Silver Treasure Box)", "type_id": BOX_TYPE_SILVER, "tr_name": "Gümüş Define Sandığı" }, 50007: { "name": "金宝箱 (Gold Treasure Box)", "type_id": BOX_TYPE_GOLD, "tr_name": "Altın Define Sandığı" }, 50012: { "name": "银宝箱+ (Silver Treasure Box+)", "type_id": BOX_TYPE_SILVER, "tr_name": "Gümüş Define Sandığı+" }, 50013: { "name": "金宝箱+ (Gold Treasure Box+)", "type_id": BOX_TYPE_GOLD, "tr_name": "Altın Define Sandığı+" } } # 40250 官方钥匙资料表 (ITEM_TREASURE_KEY) const KEYS: Dictionary = { 50008: { "name": "银钥匙 (Silver Key)", "type_id": BOX_TYPE_SILVER, "tr_name": "Gümüş Anahtar" }, 50009: { "name": "金钥匙 (Gold Key)", "type_id": BOX_TYPE_GOLD, "tr_name": "Altın Anahtar" }, 50014: { "name": "银钥匙+ (Silver Key+)", "type_id": BOX_TYPE_SILVER, "tr_name": "Gümüş Anahtar+" }, 50015: { "name": "金钥匙+ (Gold Key+)", "type_id": BOX_TYPE_GOLD, "tr_name": "Altın Anahtar+" } } # 40250 special_item_group.txt 官方掉落池 const DROP_TABLES: Dictionary = { 50006: [ # 银宝箱 {"type": "item", "vnum": 50300, "count": 1, "name": "技能书 (Skill Book)", "prob": 100}, {"type": "item", "vnum": 25040, "count": 1, "name": "祝福卷轴 (Blessing Scroll)", "prob": 40}, {"type": "gold", "amount": 80000, "name": "80,000 金币", "prob": 30}, {"type": "exp", "amount": 30000, "name": "30,000 经验值", "prob": 30}, {"type": "item", "vnum": 30047, "count": 1, "name": "诅咒之书 (Curse Book)", "prob": 20}, {"type": "item", "vnum": 30050, "count": 1, "name": "冰之碎片 (Ice Piece)", "prob": 20}, {"type": "item", "vnum": 70008, "count": 1, "name": "温暖葡萄酒", "prob": 15} ], 50007: [ # 金宝箱 {"type": "item", "vnum": 25040, "count": 1, "name": "祝福卷轴 (Blessing Scroll)", "prob": 60}, {"type": "item", "vnum": 70024, "count": 1, "name": "经验指环 (Ring of Experience)", "prob": 30}, {"type": "item", "vnum": 70003, "count": 1, "name": "小偷手套 (Thief Glove)", "prob": 30}, {"type": "gold", "amount": 150000, "name": "150,000 金币", "prob": 40}, {"type": "exp", "amount": 60000, "name": "60,000 经验值", "prob": 40}, {"type": "item", "vnum": 27003, "count": 50, "name": "红色大生命药水 x50", "prob": 50}, {"type": "item", "vnum": 27006, "count": 50, "name": "蓝色大魔法药水 x50", "prob": 50} ], 50012: [ # 银宝箱+ {"type": "item", "vnum": 50300, "count": 2, "name": "技能书 x2", "prob": 100}, {"type": "item", "vnum": 25040, "count": 1, "name": "祝福卷轴", "prob": 50}, {"type": "gold", "amount": 120000, "name": "120,000 金币", "prob": 40}, {"type": "item", "vnum": 70048, "count": 1, "name": "变装斗篷", "prob": 25} ], 50013: [ # 金宝箱+ {"type": "item", "vnum": 25040, "count": 2, "name": "祝福卷轴 x2", "prob": 80}, {"type": "item", "vnum": 70024, "count": 1, "name": "经验指环", "prob": 45}, {"type": "gold", "amount": 250000, "name": "250,000 金币", "prob": 50}, {"type": "exp", "amount": 100000, "name": "100,000 经验值", "prob": 50} ] } # 开启历史记录 var _open_history: Array[Dictionary] = [] # 判断是否是宝箱 func is_treasure_box(vnum: int) -> bool: return BOXES.has(vnum) # 判断是否是钥匙 func is_treasure_key(vnum: int) -> bool: return KEYS.has(vnum) # 校验钥匙与宝箱是否能够配对开启 (item->GetValue(0) == item2->GetValue(0)) func can_unlock(key_vnum: int, box_vnum: int) -> bool: if not KEYS.has(key_vnum) or not BOXES.has(box_vnum): return false return int(KEYS[key_vnum]["type_id"]) == int(BOXES[box_vnum]["type_id"]) # 拖拽钥匙至宝箱进行解锁 func unlock_box_with_key( inventory: Array, key_slot: int, box_slot: int, forced_drop_idx: int = -1 ) -> Dictionary: if key_slot < 0 or key_slot >= inventory.size() or inventory[key_slot] == null: unlock_failed.emit("INVALID_KEY_SLOT") return {"ok": false, "reason": "INVALID_KEY_SLOT", "msg": "未找到指定的钥匙!"} if box_slot < 0 or box_slot >= inventory.size() or inventory[box_slot] == null: unlock_failed.emit("INVALID_BOX_SLOT") return {"ok": false, "reason": "INVALID_BOX_SLOT", "msg": "未找到指定的宝箱!"} var key_item = inventory[key_slot] var box_item = inventory[box_slot] var key_vnum = int(key_item.get("vnum", 0)) var box_vnum = int(box_item.get("vnum", 0)) # 检查物品分类是否为钥匙与宝箱 if not is_treasure_key(key_vnum): unlock_failed.emit("NOT_A_KEY") return {"ok": false, "reason": "NOT_A_KEY", "msg": "被拖拽的物品并不是一把宝箱钥匙!"} if not is_treasure_box(box_vnum): unlock_failed.emit("NOT_A_TREASURE_BOX") # 40250 char_item.cpp:1948 "그 열쇠로는 열 수 없는 것 같군." return {"ok": false, "reason": "NOT_A_TREASURE_BOX", "msg": "这把钥匙似乎无法用来开启这个物品。"} # 检查金银分类匹配 if not can_unlock(key_vnum, box_vnum): unlock_failed.emit("KEY_MISMATCH") return { "ok": false, "reason": "KEY_MISMATCH", "msg": "钥匙型号不吻合!%s 无法打开 %s。" % [KEYS[key_vnum]["name"], BOXES[box_vnum]["name"]] } # 配对成功:双双扣除 1 个数量 _consume_item_one(inventory, key_slot) _consume_item_one(inventory, box_slot) # 计算奖励 var drops: Array = DROP_TABLES.get(box_vnum, []) if drops.is_empty(): drops = DROP_TABLES[50006] var picked_reward: Dictionary = {} if forced_drop_idx >= 0 and forced_drop_idx < drops.size(): picked_reward = drops[forced_drop_idx] else: # 加权随机选取 var total_weight := 0 for d in drops: total_weight += int(d.get("prob", 10)) var r = randi_range(1, total_weight) var current := 0 for d in drops: current += int(d.get("prob", 10)) if r <= current: picked_reward = d break # 发放奖励 var reward_type = picked_reward.get("type", "item") match reward_type: "item": _give_or_stack_item(inventory, int(picked_reward["vnum"]), int(picked_reward.get("count", 1))) "gold": # 金币奖励 pass "exp": # 经验奖励 pass var record = { "box_vnum": box_vnum, "key_vnum": key_vnum, "reward": picked_reward } _open_history.append(record) box_unlocked.emit(box_vnum, key_vnum, picked_reward) return { "ok": true, "box_name": BOXES[box_vnum]["name"], "key_name": KEYS[key_vnum]["name"], "reward": picked_reward, "msg": "咔哒一声脆响!宝箱锁扣开启,你从箱中获得了【%s】!" % picked_reward.get("name", "宝物") } # 获取宝箱完整掉落表 func get_box_drop_table(box_vnum: int) -> Array: return DROP_TABLES.get(box_vnum, []) # 开启历史记录 func get_open_history() -> Array[Dictionary]: return _open_history func clear_history() -> void: _open_history.clear() # 内部消耗与堆叠工具函数 func _consume_item_one(inventory: Array, slot: int) -> void: if slot < 0 or slot >= inventory.size() or inventory[slot] == null: return var item = inventory[slot] var count = int(item.get("count", 1)) if count > 1: item["count"] = count - 1 else: inventory[slot] = null func _give_or_stack_item(inventory: Array, vnum: int, count: int) -> void: for it in inventory: if it != null and int(it.get("vnum", 0)) == vnum: it["count"] = int(it.get("count", 1)) + count return for i in range(inventory.size()): if inventory[i] == null: inventory[i] = {"vnum": vnum, "count": count} return inventory.append({"vnum": vnum, "count": count}) # 序列化与反序列化 func serialize() -> Dictionary: return { "open_history": _open_history.duplicate(true) } func deserialize(data: Dictionary) -> void: _open_history.clear() var raw = data.get("open_history", []) for item in raw: _open_history.append(item.duplicate(true))