# cube_crafting_system.gd —— Metin2 40250 官方魔方炼金合成台系统 1:1 # 100% 对照 40250 服务端 cube.cpp, cube.h, cube.txt, quest/cube.quest class_name CubeCraftingSystem extends RefCounted signal cube_opened(npc_vnum: int, npc_name: String) signal cube_closed() signal craft_succeeded(reward_vnum: int, count: int, reward_name: String) signal craft_failed(recipe_name: String) signal cube_error(reason: String) # 40250 官方四大魔方炼金 NPC const NPC_BAEK_GO := 20018 # 药剂师 白高 (草药汁提炼) const NPC_YU_HWAN := 20017 # 游侠助手 刘焕 (中级药水配制) const NPC_HUAHN_SO := 20022 # 隐者 幻素 (高级露水炼制) const NPC_JAE_SEON_KIM:= 20383 # 顶级工匠 金在善 (神武/神甲熔铸) const NPC_NAMES: Dictionary = { 20018: "药剂师 白高 (Baek-Go)", 20017: "游侠助手 刘焕 (Yu-Hwan)", 20022: "隐者 幻素 (Huahn-So)", 20383: "神级工匠 金在善 (Jae-Seon Kim)" } # 40250 经典 cube.txt 配方定义表 const RECIPES: Array[Dictionary] = [ # --- 药剂师 白高 (20018) & 刘焕 (20017) 基础草药汁 --- { "id": "herb_peach_flower", "name": "桃花汁提炼", "npcs": [20018, 20017], "materials": [{"vnum": 50721, "count": 1}], # 桃花 "reward": {"vnum": 50801, "count": 1, "name": "桃花汁 (Peach Flower Juice)"}, "gold": 0, "percent": 100 }, { "id": "herb_campanula", "name": "风铃草汁提炼", "npcs": [20018, 20017], "materials": [{"vnum": 50722, "count": 1}], # 风铃草 "reward": {"vnum": 50802, "count": 1, "name": "风铃草汁 (Campanula Juice)"}, "gold": 0, "percent": 100 }, { "id": "herb_lilac", "name": "紫丁香汁批量提炼", "npcs": [20018, 20017], "materials": [{"vnum": 50723, "count": 1}], # 紫丁香 "reward": {"vnum": 50803, "count": 100, "name": "紫丁香汁 x100"}, "gold": 0, "percent": 100 }, { "id": "herb_persimmon", "name": "柿子花汁批量提炼", "npcs": [20018, 20017], "materials": [{"vnum": 50724, "count": 1}], # 柿子花 "reward": {"vnum": 50804, "count": 100, "name": "柿子花汁 x100"}, "gold": 0, "percent": 100 }, # --- 隐者 幻素 (20022) 露水药水炼制 (Dew Crafting) --- { "id": "dew_peach_flower", "name": "桃花露水炼制", "npcs": [20022], "materials": [ {"vnum": 50801, "count": 10}, # 桃花汁 x10 {"vnum": 70031, "count": 10} # 空瓶 (Empty Bottle) x10 ], "reward": {"vnum": 50821, "count": 1, "name": "纯净桃花露 (Peach Flower Dew)"}, "gold": 10000, "percent": 100 }, { "id": "dew_campanula", "name": "风铃草露水炼制", "npcs": [20022], "materials": [ {"vnum": 50802, "count": 10}, # 风铃草汁 x10 {"vnum": 70031, "count": 10} # 空瓶 x10 ], "reward": {"vnum": 50822, "count": 1, "name": "纯净风铃露 (Campanula Dew)"}, "gold": 10000, "percent": 100 }, # --- 工匠 金在善 (20383) 顶级神兵海神剑 (Triton Sword) 熔铸 --- { "id": "craft_triton_low", "name": "海神剑试炼熔铸 (低阶+5)", "npcs": [20383], "materials": [ {"vnum": 145, "count": 1}, # 战神之剑+5 {"vnum": 30500, "count": 10},# 能量晶石 x10 {"vnum": 30505, "count": 10} # 熔铸秘石 x10 ], "reward": {"vnum": 460, "count": 1, "name": "海神之剑+0 (Triton Sword)"}, "gold": 100000, "percent": 5 # 40250 原版 5% 概率 }, { "id": "craft_triton_master", "name": "海神剑巅峰熔铸 (满阶+9)", "npcs": [20383], "materials": [ {"vnum": 149, "count": 1}, # 战神之剑+9 {"vnum": 30500, "count": 10},# 能量晶石 x10 {"vnum": 30505, "count": 10} # 熔铸秘石 x10 ], "reward": {"vnum": 460, "count": 1, "name": "海神之剑+0 (Triton Sword)"}, "gold": 100000, "percent": 100 # 40250 原版 +9 保底 100% } ] # 当前魔方状态 var current_active_npc: int = 0 var is_cube_open: bool = false var crafting_history: Array[Dictionary] = [] # 打开魔方窗口 func open_cube(npc_vnum: int) -> bool: if not NPC_NAMES.has(npc_vnum): cube_error.emit("INVALID_NPC") return false current_active_npc = npc_vnum is_cube_open = true cube_opened.emit(npc_vnum, NPC_NAMES[npc_vnum]) return true # 关闭魔方窗口 func close_cube() -> void: current_active_npc = 0 is_cube_open = false cube_closed.emit() # 根据放入槽位的材料与当前 NPC 匹配配方 (对照 cube.cpp: FN_find_cube) func find_matching_recipe(cube_slots: Array, npc_vnum: int) -> Dictionary: # 统计放入槽位的物品 Vnum 与数量 var inputs: Dictionary = {} for it in cube_slots: if it != null: var v = int(it.get("vnum", 0)) var c = int(it.get("count", 1)) inputs[v] = inputs.get(v, 0) + c if inputs.is_empty(): return {} for rec in RECIPES: var npcs: Array = rec["npcs"] if not npcs.has(npc_vnum): continue var req_materials: Array = rec["materials"] if req_materials.size() != inputs.size(): continue var matched := true for mat in req_materials: var mv = int(mat["vnum"]) var mc = int(mat["count"]) if inputs.get(mv, -1) != mc: matched = false break if matched: return rec return {} # 执行魔方制造 (对照 cube.cpp: Cube_make) func craft_item( cube_slots: Array, inventory: Array, player_gold: int, forced_roll: int = -1 ) -> Dictionary: if not is_cube_open: cube_error.emit("CUBE_NOT_OPEN") return {"ok": false, "reason": "CUBE_NOT_OPEN", "msg": "制造窗口尚未打开!"} var recipe = find_matching_recipe(cube_slots, current_active_npc) if recipe.is_empty(): cube_error.emit("NO_MATCHING_RECIPE") return {"ok": false, "reason": "NO_MATCHING_RECIPE", "msg": "材料不足或配方不存在!"} var fee = int(recipe.get("gold", 0)) if player_gold < fee: cube_error.emit("NOT_ENOUGH_GOLD") return { "ok": false, "reason": "NOT_ENOUGH_GOLD", "msg": "金币不足!本次合成需要 %d 金币,你只有 %d 金币。" % [fee, player_gold] } # 扣除材料 (清空魔方放置槽) for i in range(cube_slots.size()): cube_slots[i] = null # 扣除手续费金币 var remaining_gold = player_gold - fee # 判定成功概率 var pct = int(recipe.get("percent", 100)) var roll = forced_roll if forced_roll > 0 else randi_range(1, 100) if roll <= pct: # 合成成功 (cube success) var rew = recipe["reward"] var reward_vnum = int(rew["vnum"]) var reward_count = int(rew.get("count", 1)) var reward_name = str(rew.get("name", "制造道具")) _give_or_stack_item(inventory, reward_vnum, reward_count) var record = { "recipe_id": recipe["id"], "success": true, "reward_vnum": reward_vnum, "reward_count": reward_count, "fee": fee } crafting_history.append(record) craft_succeeded.emit(reward_vnum, reward_count, reward_name) return { "ok": true, "success": true, "reward_vnum": reward_vnum, "reward_count": reward_count, "remaining_gold": remaining_gold, "msg": "叮!伴随着绚烂的灵光,你成功制造出了【%s】!" % reward_name } else: # 合成失败 (cube fail) var record = { "recipe_id": recipe["id"], "success": false, "fee": fee } crafting_history.append(record) craft_failed.emit(recipe["name"]) return { "ok": true, "success": false, "remaining_gold": remaining_gold, "msg": "制造失败了!魔方中传出一阵焦黑的青烟,材料化为了灰烬。" } # 获取当前 NPC 允许合成的所有配方清单 func get_available_recipes(npc_vnum: int) -> Array[Dictionary]: var list: Array[Dictionary] = [] for rec in RECIPES: if rec["npcs"].has(npc_vnum): list.append(rec) return list # 历史记录 func get_history() -> Array[Dictionary]: return crafting_history func clear_history() -> void: crafting_history.clear() # 内部发放道具堆叠 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 { "history": crafting_history.duplicate(true) } func deserialize(data: Dictionary) -> void: crafting_history.clear() var raw = data.get("history", []) for it in raw: crafting_history.append(it.duplicate(true))