# status_skill_reset_system.gd —— Metin2 40250 官方属性重修卷轴与忘却之书洗点系统 1:1 # 对照 40250 服务端 char_item.cpp, char.cpp, root/uipointreset.py class_name StatusSkillResetSystem extends RefCounted signal status_reset(stat_name: String, refunded_points: int) signal all_status_reset(refunded_points: int) signal skill_point_reset(skill_id: int, new_level: int) signal all_skills_reset(refunded_points: int) # 40250 官方重修道具 Vnum const VNUM_SKILL_RESET_SCROLL := 71001 # 技能重置卷轴 (全部技能洗点) const VNUM_STATUS_REDIST_SCROLL := 71002 # 属性重修卷轴 (单项四维属性洗点) const VNUM_ALL_STATUS_RESET_SCROLL := 71003 # 全属性重置卷轴 (血统重塑卷轴) # 40250 四维职业初始基础属性标准表 (Lv 1 Base) const JOB_BASE_STATS: Dictionary = { "warrior": {"vit": 16, "int": 3, "str": 15, "dex": 5}, "assassin": {"vit": 14, "int": 3, "str": 8, "dex": 16}, "sura": {"vit": 15, "int": 16, "str": 8, "dex": 3}, "shaman": {"vit": 15, "int": 16, "str": 5, "dex": 6} } # 单项属性重修 (71002 属性重修卷轴) func reset_single_status(stat_name: String, player_data: Dictionary, inventory: Array, item_slot: int) -> Dictionary: var valid_stats = ["vit", "int", "str", "dex"] if not valid_stats.has(stat_name): return {"ok": false, "reason": "INVALID_STAT_NAME", "msg": "无效的属性名称!"} if item_slot < 0 or item_slot >= inventory.size() or inventory[item_slot] == null: return {"ok": false, "reason": "INVALID_SLOT"} var item: Dictionary = inventory[item_slot] if int(item.get("vnum", 0)) != VNUM_STATUS_REDIST_SCROLL: return {"ok": false, "reason": "NOT_A_STATUS_REDIST_SCROLL", "msg": "需要使用【属性重修卷轴】(71002)!"} var job: String = str(player_data.get("job", "warrior")) var base_stat_table: Dictionary = JOB_BASE_STATS.get(job, JOB_BASE_STATS["warrior"]) var base_val: int = int(base_stat_table.get(stat_name, 10)) var cur_val: int = int(player_data.get(stat_name, base_val)) if cur_val <= base_val: return {"ok": false, "reason": "STAT_ALREADY_AT_BASE", "msg": "该属性尚未分配任何点数,无需重置!"} var refunded: int = cur_val - base_val # 消耗 1 卷 var cnt := int(item.get("count", 1)) if cnt > 1: item["count"] = cnt - 1 else: inventory[item_slot] = null player_data[stat_name] = base_val player_data["unused_status_points"] = int(player_data.get("unused_status_points", 0)) + refunded status_reset.emit(stat_name, refunded) return { "ok": true, "stat_name": stat_name, "refunded_points": refunded, "new_val": base_val, "total_unused": player_data["unused_status_points"], "msg": "成功重置【%s】,全额退还 %d 点属性点!" % [stat_name.to_upper(), refunded] } # 全属性重置 (71003 全属性重置卷轴) func reset_all_status(player_data: Dictionary, inventory: Array, item_slot: int) -> Dictionary: if item_slot < 0 or item_slot >= inventory.size() or inventory[item_slot] == null: return {"ok": false, "reason": "INVALID_SLOT"} var item: Dictionary = inventory[item_slot] if int(item.get("vnum", 0)) != VNUM_ALL_STATUS_RESET_SCROLL: return {"ok": false, "reason": "NOT_AN_ALL_STATUS_RESET_SCROLL", "msg": "需要使用【全属性重置卷轴】(71003)!"} var job: String = str(player_data.get("job", "warrior")) var base_stat_table: Dictionary = JOB_BASE_STATS.get(job, JOB_BASE_STATS["warrior"]) var total_refunded := 0 for stat_name in ["vit", "int", "str", "dex"]: var base_val: int = int(base_stat_table.get(stat_name, 10)) var cur_val: int = int(player_data.get(stat_name, base_val)) if cur_val > base_val: total_refunded += (cur_val - base_val) player_data[stat_name] = base_val # 消耗 1 卷 var cnt := int(item.get("count", 1)) if cnt > 1: item["count"] = cnt - 1 else: inventory[item_slot] = null player_data["unused_status_points"] = int(player_data.get("unused_status_points", 0)) + total_refunded all_status_reset.emit(total_refunded) return { "ok": true, "refunded_points": total_refunded, "total_unused": player_data["unused_status_points"], "msg": "血统重塑完成!四维属性恢复初始基础值,共退还 %d 点属性点。" % total_refunded } # 忘却之书重置单项技能 1 点 (50311~50320 Book of Oblivion) func reset_single_skill_point(skill_id: int, player_skills: Dictionary, player_data: Dictionary, inventory: Array, item_slot: int) -> Dictionary: if item_slot < 0 or item_slot >= inventory.size() or inventory[item_slot] == null: return {"ok": false, "reason": "INVALID_SLOT"} var book: Dictionary = inventory[item_slot] var book_vnum := int(book.get("vnum", 0)) var book_target_skill := int(book.get("target_skill", 0)) # 检查是否为忘却之书 (50311~50330) if book_vnum < 50311 or book_vnum > 50330: return {"ok": false, "reason": "NOT_A_BOOK_OF_OBLIVION", "msg": "需要使用对应的【忘却之书】!"} if book_target_skill != 0 and book_target_skill != skill_id: return {"ok": false, "reason": "SKILL_ID_MISMATCH", "msg": "此忘却之书与目标技能不符!"} var cur_skill_lv := int(player_skills.get(skill_id, 0)) if cur_skill_lv <= 0: return {"ok": false, "reason": "SKILL_LEVEL_ZERO", "msg": "该技能尚未习得,无法忘却!"} # 40250 官方规则:技能达到 Master (>= 20) 后无法使用普通忘却之书降级 if cur_skill_lv >= 20: return {"ok": false, "reason": "SKILL_IS_MASTER", "msg": "已达到大师境界(Master)的技能无法通过普通忘却之书降级!"} # 消耗 1 本忘却之书 var cnt := int(book.get("count", 1)) if cnt > 1: book["count"] = cnt - 1 else: inventory[item_slot] = null player_skills[skill_id] = cur_skill_lv - 1 player_data["unused_skill_points"] = int(player_data.get("unused_skill_points", 0)) + 1 skill_point_reset.emit(skill_id, player_skills[skill_id]) return { "ok": true, "skill_id": skill_id, "new_level": player_skills[skill_id], "refunded_points": 1, "total_unused": player_data["unused_skill_points"], "msg": "领悟忘却之书!技能等级降低 1 级,返还 1 点可用技能点。" } # 技能重置卷轴全技能洗点 (71001) func reset_all_skills(player_skills: Dictionary, player_data: Dictionary, inventory: Array, item_slot: int) -> Dictionary: if item_slot < 0 or item_slot >= inventory.size() or inventory[item_slot] == null: return {"ok": false, "reason": "INVALID_SLOT"} var item: Dictionary = inventory[item_slot] if int(item.get("vnum", 0)) != VNUM_SKILL_RESET_SCROLL: return {"ok": false, "reason": "NOT_A_SKILL_RESET_SCROLL", "msg": "需要使用【技能重置卷轴】(71001)!"} var total_refunded := 0 for sk_id in player_skills.keys(): var lv := int(player_skills[sk_id]) if lv > 0: total_refunded += lv player_skills[sk_id] = 0 # 消耗 1 卷 var cnt := int(item.get("count", 1)) if cnt > 1: item["count"] = cnt - 1 else: inventory[item_slot] = null player_data["unused_skill_points"] = int(player_data.get("unused_skill_points", 0)) + total_refunded all_skills_reset.emit(total_refunded) return { "ok": true, "refunded_points": total_refunded, "total_unused": player_data["unused_skill_points"], "msg": "技能重置完成!所有技能点已洗净,全额返还 %d 点可用技能点。" % total_refunded }