# spider_dungeon_key_system.gd —— Metin2 40250 经典深渊蜘蛛洞穴通行证与蛛后巢穴密匙系统 1:1 # 对照 40250 服务端 spider_dungeon_3floor_boss.quest, char_item.cpp, dungeon.cpp class_name SpiderDungeonKeySystem extends RefCounted signal ticket_consumed_entry_granted(floor: int, map_name: String) signal arachnid_key_crafted(key_count: int) signal baroness_lair_opened(time_limit: float) signal baroness_slain(drop_items: Array) signal lair_closed(success: bool) # 40250 官方核心道具 Vnum const VNUM_PASSAGE_TICKET := 71095 # 蛛洞通行证 (Passage Ticket) const VNUM_ARACHNID_KEY := 30324 # 蛛网密匙 (Arachnid Key) # 40250 蛛网密匙合成原材料 const VNUM_SPIDER_POISON_SACK := 30025 # 蜘蛛毒囊 const VNUM_SPIDER_EGG_SACK := 30056 # 蜘蛛卵囊 const VNUM_SPIDER_EYES := 30057 # 蜘蛛眼睛 const VNUM_SPIDER_LEGS := 30058 # 蜘蛛腿 const CRAFT_COST_GOLD := 50000 # 50,000 金币 # NPC Vnum const NPC_SPIDER_GUARD := 20089 # 蛛洞入口守卫 const NPC_SCHOLAR_URIEL:= 20011 # 学者乌列尔 (合成密匙) const NPC_LAIR_TOMBSTONE:= 30130 # 蛛后巢穴封印石碑 # Boss Vnum const MOB_SPIDER_BARONESS := 2092 # 蜘蛛女皇/蛛后 const VNUM_CHEST_BARONESS := 50181 # 蛛后宝箱 # 巢穴限时 (20 分钟 / 1200 秒) const LAIR_DURATION := 1200.0 var is_lair_active: bool = false var lair_time_remaining: float = 0.0 var baroness_alive: bool = false # 使用通行证进入蜘蛛洞穴 2层或3层 func enter_spider_dungeon(floor_target: int, player_level: int, inventory: Array) -> Dictionary: if floor_target != 2 and floor_target != 3: return {"ok": false, "reason": "INVALID_FLOOR", "msg": "只能前往蛛洞二层(SD2)或三层(SD3)!"} var min_lv = 40 if floor_target == 2 else 50 if player_level < min_lv: return { "ok": false, "reason": "LEVEL_TOO_LOW", "msg": "实力不足!进入蛛洞 %d 层需要等级达到 %d 级。" % [floor_target, min_lv] } # 寻找并消耗 1 张通行证 (71095) var ticket_slot := -1 for i in range(inventory.size()): if inventory[i] != null and int(inventory[i].get("vnum", 0)) == VNUM_PASSAGE_TICKET: ticket_slot = i break if ticket_slot == -1: return {"ok": false, "reason": "NO_PASSAGE_TICKET", "msg": "守卫拦住了去路!需要出示【蛛洞通行证】(71095)。"} var t_item: Dictionary = inventory[ticket_slot] var cnt := int(t_item.get("count", 1)) if cnt > 1: t_item["count"] = cnt - 1 else: inventory[ticket_slot] = null var target_map = "Spider_Dungeon_2" if floor_target == 2 else "Spider_Dungeon_3" ticket_consumed_entry_granted.emit(floor_target, target_map) return { "ok": true, "floor": floor_target, "target_map": target_map, "msg": "检票通过!守卫为您开启了通往【蛛洞 %d 层】的隐秘石门。" % floor_target } # 在学者乌列尔处精炼合成蛛网密匙 (40250 spider_dungeon_3floor_boss.quest) func craft_arachnid_key(inventory: Array, player_gold: Dictionary) -> Dictionary: var current_gold := int(player_gold.get("gold", 0)) if current_gold < CRAFT_COST_GOLD: return {"ok": false, "reason": "NOT_ENOUGH_GOLD", "msg": "精炼费用不足!需要 %d 金币。" % CRAFT_COST_GOLD} # 检查 4 大蜘蛛残肢素材 var req_vnums = [VNUM_SPIDER_POISON_SACK, VNUM_SPIDER_EGG_SACK, VNUM_SPIDER_EYES, VNUM_SPIDER_LEGS] var slots_to_consume: Array = [] for req_vnum in req_vnums: var found_slot := -1 for i in range(inventory.size()): if inventory[i] != null and int(inventory[i].get("vnum", 0)) == req_vnum: found_slot = i break if found_slot == -1: return {"ok": false, "reason": "MISSING_MATERIALS", "msg": "合成材料不足!缺少蜘蛛残肢核心原料。"} slots_to_consume.append(found_slot) # 寻找放置生成密匙的空位 var free_slot := -1 for i in range(inventory.size()): if inventory[i] == null: free_slot = i break # 如果没有全新空位,但材料消耗后会腾出空位也是可行的 if free_slot == -1: for s in slots_to_consume: if int(inventory[s].get("count", 1)) == 1: free_slot = s # 可以复用该格 break if free_slot == -1: return {"ok": false, "reason": "INVENTORY_FULL", "msg": "背包已满,无法容纳新炼制的密匙!"} # 消耗材料与金币 player_gold["gold"] = current_gold - CRAFT_COST_GOLD for s in slots_to_consume: var it: Dictionary = inventory[s] var c := int(it.get("count", 1)) if c > 1: it["count"] = c - 1 else: inventory[s] = null # 产出蛛网密匙 (30324) inventory[free_slot] = { "vnum": VNUM_ARACHNID_KEY, "name": "蛛网密匙", "count": 1 } arachnid_key_crafted.emit(1) return { "ok": true, "key_vnum": VNUM_ARACHNID_KEY, "slot": free_slot, "msg": "学者乌列尔成功为您炼制了【蛛网密匙】(30324)!" } # 封印石碑检票开启蛛后巢穴 (40250 NPC 30130) func open_baroness_lair(player_level: int, inventory: Array) -> Dictionary: if is_lair_active: return {"ok": false, "reason": "LAIR_ALREADY_ACTIVE", "msg": "蛛后巢穴已在挑战中!"} if player_level < 50: return {"ok": false, "reason": "LEVEL_TOO_LOW", "msg": "挑战蛛后需要角色等级达到 50 级!"} # 寻找并消耗蛛网密匙 (30324) var key_slot := -1 for i in range(inventory.size()): if inventory[i] != null and int(inventory[i].get("vnum", 0)) == VNUM_ARACHNID_KEY: key_slot = i break if key_slot == -1: return {"ok": false, "reason": "NO_ARACHNID_KEY", "msg": "封印石碑毫无反应!需要镶嵌【蛛网密匙】(30324)。"} # 消耗钥匙 var k_item: Dictionary = inventory[key_slot] var cnt := int(k_item.get("count", 1)) if cnt > 1: k_item["count"] = cnt - 1 else: inventory[key_slot] = null is_lair_active = true lair_time_remaining = LAIR_DURATION baroness_alive = true baroness_lair_opened.emit(LAIR_DURATION) return { "ok": true, "duration": LAIR_DURATION, "boss_vnum": MOB_SPIDER_BARONESS, "msg": "石碑封印破除!蛛网密室大门洞开,限时 20 分钟讨伐蜘蛛女皇!" } # 击败蜘蛛女皇 func slay_baroness(inventory: Array) -> Dictionary: if not is_lair_active or not baroness_alive: return {"ok": false, "reason": "BARONESS_NOT_ALIVE"} baroness_alive = false var drops: Array = [ {"vnum": VNUM_CHEST_BARONESS, "name": "蛛后宝箱", "count": 1}, {"vnum": 25040, "name": "祝福卷轴", "count": 2} ] # 放入背包 for drop in drops: for i in range(inventory.size()): if inventory[i] == null: inventory[i] = drop.duplicate(true) break baroness_slain.emit(drops) # 胜利结束 close_lair(true) return { "ok": true, "drops": drops, "msg": "诛灭深渊蛛后!成功夺得【蛛后宝箱】!" } # 关闭巢穴副本 func close_lair(success: bool) -> void: is_lair_active = false lair_time_remaining = 0.0 baroness_alive = false lair_closed.emit(success) # 时钟心跳推进 func update(delta: float) -> void: if not is_lair_active: return lair_time_remaining -= delta if lair_time_remaining <= 0.0: close_lair(false) # 超时失败关闭 # 序列化 func serialize() -> Dictionary: return { "is_lair_active": is_lair_active, "lair_time_remaining": lair_time_remaining, "baroness_alive": baroness_alive } # 反序列化 func deserialize(data: Dictionary) -> void: is_lair_active = bool(data.get("is_lair_active", false)) lair_time_remaining = float(data.get("lair_time_remaining", 0.0)) baroness_alive = bool(data.get("baroness_alive", false))