# boss_summon_scroll_system.gd —— Metin2 40250 官方首领降临与魔石/矿脉召唤卷轴系统 1:1 # 100% 对照 40250 服务端 special_item_group.txt, char_item.cpp (ITEM_GIFTBOX), char_manager.cpp class_name BossSummonScrollSystem extends RefCounted signal boss_summoned(mob_vnum: int, mob_name: String, spawn_pos: Vector2, is_aggressive: bool) signal metin_summoned(metin_vnum: int, metin_name: String, spawn_pos: Vector2, level: int) signal ore_vein_summoned(vein_vnum: int, vein_name: String, spawn_pos: Vector2) signal summon_failed(reason: String) # ========================================== # 40250 官方首领召唤卷轴 (Boss Summon Scrolls) # 对应 special_item_group.txt Vnum 71036 ~ 71043 # ========================================== const BOSS_SCROLLS: Dictionary = { 71036: { "name": "兽人首领召唤卷轴", "tr_name": "Kağıt-Ork Şefi", "mob_vnum": 691, "mob_name": "兽人首领 (Orc Chief)", "level": 50, "rank": 4, # BOSS "is_aggressive": true }, 71037: { "name": "密教首领召唤卷轴", "tr_name": "Kağıt-Karanlık Lider", "mob_vnum": 791, "mob_name": "黑风教主/密教首领 (Dark Leader)", "level": 54, "rank": 4, "is_aggressive": true }, 71038: { "name": "蜘蛛女王召唤卷轴", "tr_name": "Kağıt-Kraliçe Örümcek", "mob_vnum": 2091, "mob_name": "蜘蛛女王 (Spider Queen)", "level": 60, "rank": 4, "is_aggressive": true }, 71039: { "name": "沙漠巨龟召唤卷轴", "tr_name": "Kağıt-Kaplumbağa", "mob_vnum": 2191, "mob_name": "沙漠巨龟 (Giant Desert Tortoise)", "level": 67, "rank": 4, "is_aggressive": true }, 71040: { "name": "烈焰魔王召唤卷轴", "tr_name": "Kağıt-Alev Kral", "mob_vnum": 2206, "mob_name": "烈焰魔王 (Flame King)", "level": 73, "rank": 4, "is_aggressive": true }, 71041: { "name": "九尾狐召唤卷轴", "tr_name": "Kağıt-Dokuz Kuyruk", "mob_vnum": 1901, "mob_name": "九尾狐仙 (Nine Tails)", "level": 72, "rank": 4, "is_aggressive": true }, 71042: { "name": "恶魔之王召唤卷轴", "tr_name": "Kağıt-Şeytan Kral", "mob_vnum": 1091, "mob_name": "恶魔之王 (Demon King)", "level": 75, "rank": 4, "is_aggressive": true }, 71043: { "name": "虎灵妖兽召唤卷轴", "tr_name": "Sarı Kaplan H. Kağıdı", "mob_vnum": 1304, "mob_name": "黄虎幽灵 (Tiger Ghost)", "level": 75, "rank": 4, "is_aggressive": true } } # ========================================== # 40250 官方魔石召唤卷轴 (Metin Summon Scrolls) # 对应 special_item_group.txt Vnum 71080 ~ 71082 # ========================================== const METIN_SCROLLS: Dictionary = { 71080: { "name": "初级魔石召唤卷", "tr_name": "Metin Taşı Çağırma Kağıdı (K)", "pool": [ {"vnum": 8001, "name": "悲哀之石 (Metin of Sorrow)", "level": 5}, {"vnum": 8002, "name": "战斗之石 (Metin of Combat)", "level": 10}, {"vnum": 8003, "name": "会战之石 (Metin of Battle)", "level": 15}, {"vnum": 8004, "name": "贪婪之石 (Metin of Greed)", "level": 20}, {"vnum": 8005, "name": "漆黑之石 (Metin of Black)", "level": 25} ] }, 71081: { "name": "中级魔石召唤卷", "tr_name": "Metin Taşı Çağırma Kağıdı (O)", "pool": [ {"vnum": 8006, "name": "黑暗之石 (Metin of Darkness)", "level": 30}, {"vnum": 8007, "name": "嫉妒之石 (Metin of Jealousy)", "level": 35}, {"vnum": 8008, "name": "灵魂之石 (Metin of Soul)", "level": 40}, {"vnum": 8009, "name": "阴影之石 (Metin of Shadow)", "level": 45}, {"vnum": 8010, "name": "强韧之石 (Metin of Toughness)", "level": 50} ] }, 71082: { "name": "高级魔石召唤卷", "tr_name": "Metin Taşı Çağırma Kağıdı (Z)", "pool": [ {"vnum": 8011, "name": "恶魔之石 (Metin of Devil)", "level": 55}, {"vnum": 8012, "name": "陨落之石 (Metin of Fall)", "level": 60}, {"vnum": 8013, "name": "死者之石 (Metin of Death)", "level": 65}, {"vnum": 8014, "name": "屠杀之石 (Metin of Murder)", "level": 70}, {"vnum": 8024, "name": "狂风之石 (Metin of Pung-Ma)", "level": 75}, {"vnum": 8025, "name": "魔岩之石 (Metin of Ma-An)", "level": 80}, {"vnum": 8026, "name": "斗击之石 (Metin of Tu-Gyuk)", "level": 85}, {"vnum": 8027, "name": "绝望之石 (Metin of Jeon-Un)", "level": 90} ] } } # ========================================== # 40250 官方矿脉召唤卷轴 (Ore Vein Scrolls) # 对应 special_item_group.txt Vnum 71057 ~ 71067 # ========================================== const ORE_VEIN_SCROLLS: Dictionary = { 71057: {"mob_vnum": 20049, "name": "钻石矿脉 (Diamond Vein)"}, 71058: {"mob_vnum": 20050, "name": "琥珀矿脉 (Amber Vein)"}, 71059: {"mob_vnum": 20051, "name": "木化石矿脉 (Fossil Wood Vein)"}, 71060: {"mob_vnum": 20052, "name": "铜矿脉 (Copper Vein)"}, 71061: {"mob_vnum": 20053, "name": "银矿脉 (Silver Vein)"}, 71062: {"mob_vnum": 20054, "name": "金矿脉 (Gold Vein)"}, 71063: {"mob_vnum": 20055, "name": "翡翠矿脉 (Jade Vein)"}, 71064: {"mob_vnum": 20056, "name": "黑檀木矿脉 (Ebony Vein)"}, 71065: {"mob_vnum": 20057, "name": "珍珠矿脉 (Pearl Vein)"}, 71066: {"mob_vnum": 20058, "name": "白金矿脉 (White Gold Vein)"}, 71067: {"mob_vnum": 20059, "name": "水晶矿脉 (Crystal Vein)"} } # 当前已召唤生成的实体记录列表 var _active_summoned_entities: Array[Dictionary] = [] # 判断是否是 40250 原版召唤卷轴 func is_summon_scroll(vnum: int) -> bool: return BOSS_SCROLLS.has(vnum) or METIN_SCROLLS.has(vnum) or ORE_VEIN_SCROLLS.has(vnum) # 获取召唤卷轴类型类别 func get_scroll_category(vnum: int) -> String: if BOSS_SCROLLS.has(vnum): return "boss" elif METIN_SCROLLS.has(vnum): return "metin" elif ORE_VEIN_SCROLLS.has(vnum): return "ore_vein" return "" # 使用召唤卷轴 func use_summon_scroll( inventory: Array, slot_index: int, player_pos: Vector2, is_in_safe_zone: bool, forced_choice_idx: int = -1 ) -> Dictionary: if slot_index < 0 or slot_index >= inventory.size(): summon_failed.emit("INVALID_SLOT") return {"ok": false, "reason": "INVALID_SLOT", "msg": "无效的背包物品格位!"} var item = inventory[slot_index] if item == null: summon_failed.emit("EMPTY_SLOT") return {"ok": false, "reason": "EMPTY_SLOT", "msg": "指定格位没有任何道具!"} var vnum = int(item.get("vnum", 0)) var category = get_scroll_category(vnum) if category == "": summon_failed.emit("NOT_A_SUMMON_SCROLL") return {"ok": false, "reason": "NOT_A_SUMMON_SCROLL", "msg": "此物品并非怪物或魔石召唤卷轴!"} # 40250 原版规则:安全区内禁止召唤具有攻击性的首领与魔石 if is_in_safe_zone and category != "ore_vein": summon_failed.emit("CANNOT_SPAWN_IN_SAFEZONE") return {"ok": false, "reason": "CANNOT_SPAWN_IN_SAFEZONE", "msg": "在安全区内无法召唤怪物!请移至野外危险区域。"} # 扣除卷轴消耗 (40250 item->SetCount(item->GetCount()-1)) var current_count = int(item.get("count", 1)) if current_count > 1: item["count"] = current_count - 1 else: inventory[slot_index] = null # 生成随机偏移位置 (类似 40250 GetX() + number(-500, 500)) var offset_x = randf_range(-120.0, 120.0) var offset_y = randf_range(-120.0, 120.0) # 确保不会生成在绝对重合点 if abs(offset_x) < 30.0: offset_x = 50.0 if offset_x >= 0.0 else -50.0 if abs(offset_y) < 30.0: offset_y = 50.0 if offset_y >= 0.0 else -50.0 var spawn_pos = Vector2(player_pos.x + offset_x, player_pos.y + offset_y) var summoned_entity: Dictionary = {} match category: "boss": var boss_info = BOSS_SCROLLS[vnum] summoned_entity = { "vnum": int(boss_info["mob_vnum"]), "name": str(boss_info["mob_name"]), "level": int(boss_info["level"]), "rank": int(boss_info["rank"]), "pos": spawn_pos, "is_aggressive": true, "category": "boss", "summoner_pos": player_pos } _active_summoned_entities.append(summoned_entity) boss_summoned.emit(summoned_entity["vnum"], summoned_entity["name"], spawn_pos, true) "metin": var metin_scroll = METIN_SCROLLS[vnum] var pool: Array = metin_scroll["pool"] var picked_idx = 0 if forced_choice_idx >= 0 and forced_choice_idx < pool.size(): picked_idx = forced_choice_idx else: picked_idx = randi() % pool.size() var picked_metin = pool[picked_idx] summoned_entity = { "vnum": int(picked_metin["vnum"]), "name": str(picked_metin["name"]), "level": int(picked_metin["level"]), "rank": 5, # STONE "pos": spawn_pos, "is_aggressive": false, "category": "metin", "summoner_pos": player_pos } _active_summoned_entities.append(summoned_entity) metin_summoned.emit(summoned_entity["vnum"], summoned_entity["name"], spawn_pos, summoned_entity["level"]) "ore_vein": var vein_info = ORE_VEIN_SCROLLS[vnum] summoned_entity = { "vnum": int(vein_info["mob_vnum"]), "name": str(vein_info["name"]), "pos": spawn_pos, "is_aggressive": false, "category": "ore_vein", "summoner_pos": player_pos } _active_summoned_entities.append(summoned_entity) ore_vein_summoned.emit(summoned_entity["vnum"], summoned_entity["name"], spawn_pos) return { "ok": true, "category": category, "entity": summoned_entity, "msg": "卷轴散发出一道耀眼的光芒,强大的封印能量破空而出!" } # 获取当前已召唤的所有活跃实体 func get_active_summoned_entities() -> Array[Dictionary]: return _active_summoned_entities # 清空/移除已击败或离开的实体 func remove_summoned_entity(index: int) -> bool: if index >= 0 and index < _active_summoned_entities.size(): _active_summoned_entities.remove_at(index) return true return false func clear_summoned_entities() -> void: _active_summoned_entities.clear() # 存档与读档支持 func serialize() -> Dictionary: var list_copy: Array = [] for e in _active_summoned_entities: var pos_dict = {"x": e["pos"].x, "y": e["pos"].y} var copy_e = e.duplicate() copy_e["pos"] = pos_dict if copy_e.has("summoner_pos"): copy_e["summoner_pos"] = {"x": e["summoner_pos"].x, "y": e["summoner_pos"].y} list_copy.append(copy_e) return { "active_entities": list_copy } func deserialize(data: Dictionary) -> void: _active_summoned_entities.clear() var raw_list = data.get("active_entities", []) for item in raw_list: var pos_data = item.get("pos", {"x": 0.0, "y": 0.0}) var ent = item.duplicate() ent["pos"] = Vector2(pos_data.get("x", 0.0), pos_data.get("y", 0.0)) if ent.has("summoner_pos"): var sp_data = ent.get("summoner_pos", {"x": 0.0, "y": 0.0}) ent["summoner_pos"] = Vector2(sp_data.get("x", 0.0), sp_data.get("y", 0.0)) _active_summoned_entities.append(ent)