# item_attr_system.gd —— 装备附魔与属性洗炼系统(40250 官方 1:1 对齐) # 对齐源码: # - 40250 char_item.cpp:4658-4720 (USE_ADD_ATTRIBUTE, Vnum 71085) # - 40250 char_item.cpp:4553-4657 (USE_CHANGE_ATTRIBUTE, Vnum 71084) # - 40250 char_item.cpp:4728-4780 (USE_ADD_ATTRIBUTE2 / 祝福宝珠, Vnum 70024) # - 40250 constants.cpp:932 (aiItemAttributeAddPercent = [100, 80, 60, 50, 30, 0, 0]) # - 40250 item_attribute.cpp (PutAttributeWithLevel, HasAttr, GetAttributeSetIndex) class_name ItemAttrSystem extends RefCounted # 核心 Vnum 常量(40250 官方标准) const VNUM_ATTR_ADD := 71085 # 物品属性添加秘籍 (USE_ADD_ATTRIBUTE, 添加第 1~4 条) const VNUM_ATTR_CHANGE := 71084 # 物品属性转换秘籍 (USE_CHANGE_ATTRIBUTE, 重置现有全部属性) const VNUM_BLESSING_MARBLE := 70024 # 祝福宝珠 / 第五属性石 (USE_ADD_ATTRIBUTE2, 添加第 5 条) # 40250 constants.cpp:932 官方附魔成功率表(前 4 条及第 5 条宝珠) # 索引:现有词条数 -> 成功概率百分比 const ADD_PERCENT_TABLE := [100, 80, 60, 50, 30] # 装备类型大类(ItemData.h) const ITEM_TYPE_WEAPON := 1 const ITEM_TYPE_ARMOR := 2 # 属性池配置:针对武器与防具的标准 40250 词条池与数值范围(5 档位) # 每个 apply_type: { name, values: [lv1, lv2, lv3, lv4, lv5], prob_weight } const WEAPON_ATTR_POOL := { 1: {"name": "最大生命", "values": [500, 1000, 1500, 2000, 2500], "weight": 10}, 2: {"name": "最大法力", "values": [100, 200, 300, 500, 800], "weight": 10}, 3: {"name": "体质", "values": [2, 4, 6, 8, 12], "weight": 8}, 4: {"name": "智力", "values": [2, 4, 6, 8, 12], "weight": 8}, 5: {"name": "力量", "values": [2, 4, 6, 8, 12], "weight": 8}, 6: {"name": "敏捷", "values": [2, 4, 6, 8, 12], "weight": 8}, 7: {"name": "攻击速度", "values": [2, 4, 6, 8, 10], "weight": 8}, 9: {"name": "施法速度", "values": [4, 8, 12, 16, 20], "weight": 8}, 13: {"name": "眩晕概率", "values": [2, 3, 5, 8, 10], "weight": 6}, 14: {"name": "缓慢概率", "values": [2, 3, 5, 8, 10], "weight": 6}, 15: {"name": "暴击概率", "values": [2, 4, 6, 8, 10], "weight": 7}, 16: {"name": "穿透概率", "values": [2, 4, 6, 8, 10], "weight": 7}, 17: {"name": "对人族伤害", "values": [2, 4, 6, 8, 10], "weight": 6}, 19: {"name": "对兽人伤害", "values": [4, 6, 10, 14, 20], "weight": 8}, 21: {"name": "对不死族伤害", "values": [4, 6, 10, 14, 20], "weight": 8}, 22: {"name": "对恶魔伤害", "values": [4, 6, 10, 14, 20], "weight": 8}, 53: {"name": "攻击等级", "values": [10, 20, 30, 40, 50], "weight": 7}, } const ARMOR_ATTR_POOL := { 1: {"name": "最大生命", "values": [500, 1000, 1500, 2000, 2500], "weight": 12}, 2: {"name": "最大法力", "values": [100, 200, 300, 500, 800], "weight": 10}, 7: {"name": "攻击速度", "values": [2, 4, 6, 8, 10], "weight": 8}, 8: {"name": "移动速度", "values": [2, 4, 6, 8, 10], "weight": 8}, 9: {"name": "施法速度", "values": [4, 8, 12, 16, 20], "weight": 8}, 23: {"name": "生命吸收", "values": [2, 3, 5, 8, 10], "weight": 7}, 24: {"name": "法力吸收", "values": [2, 3, 5, 8, 10], "weight": 7}, 27: {"name": "格挡", "values": [2, 4, 6, 8, 10], "weight": 7}, 29: {"name": "剑防御", "values": [4, 6, 10, 12, 15], "weight": 6}, 30: {"name": "双手防御", "values": [4, 6, 10, 12, 15], "weight": 6}, 31: {"name": "匕首防御", "values": [4, 6, 10, 12, 15], "weight": 6}, 34: {"name": "弓防御", "values": [4, 6, 10, 12, 15], "weight": 6}, 35: {"name": "火抗性", "values": [4, 6, 10, 12, 15], "weight": 7}, 37: {"name": "魔法抗性", "values": [4, 6, 10, 12, 15], "weight": 7}, 38: {"name": "风抗性", "values": [4, 6, 10, 12, 15], "weight": 7}, 54: {"name": "防御等级", "values": [10, 15, 20, 30, 40], "weight": 8}, } ## 判定物品是否是附魔相关卷轴 static func is_attr_scroll(vnum: int) -> bool: return vnum in [VNUM_ATTR_ADD, VNUM_ATTR_CHANGE, VNUM_BLESSING_MARBLE] ## 判定目标物品是否可附魔/洗炼(武器或防具装备,对应 40250 GetAttributeSetIndex != -1) static func can_have_attributes(target_proto: Dictionary) -> bool: var item_type := int(target_proto.get("type", 0)) if item_type == ITEM_TYPE_WEAPON: var sub_type := int(target_proto.get("sub_type", 0)) return sub_type != 5 # 箭矢 (WEAPON_ARROW) 不可附魔 elif item_type == ITEM_TYPE_ARMOR: return true return false ## 获取目标装备对应的属性池 static func get_attr_pool(target_proto: Dictionary) -> Dictionary: var item_type := int(target_proto.get("type", 0)) if item_type == ITEM_TYPE_WEAPON: return WEAPON_ATTR_POOL return ARMOR_ATTR_POOL ## 执行附魔添加属性(USE_ADD_ATTRIBUTE 71085) ## 40250 原生逻辑:词条数 < 4;根据已有条数判定成功率;成功则随机一条不重复属性与档次 static func apply_add_attribute(target_item: Dictionary, target_proto: Dictionary, force_success := false) -> Dictionary: if not can_have_attributes(target_proto): return {"ok": false, "code": "CANNOT_ATTACH_ATTR", "msg": "该物品无法添加属性。"} var attrs: Array = target_item.get("attrs", []).duplicate(true) if attrs.size() >= 4: return {"ok": false, "code": "MAX_ATTR_REACHED", "msg": "此物品已有 4 条或更多属性,无法再添加基础属性。"} var cur_count := attrs.size() var rate: int = int(ADD_PERCENT_TABLE[cur_count]) if cur_count < ADD_PERCENT_TABLE.size() else 0 var roll := 0 if force_success else (randi() % 100 + 1) if not force_success and roll > rate: return { "ok": false, "code": "ADD_ATTR_FAIL", "msg": "属性添加失败。", "consumed": true, } var pool := get_attr_pool(target_proto) var existing_types := {} for a in attrs: existing_types[int(a.get("type", 0))] = true var avail_keys: Array[int] = [] for k in pool.keys(): if not existing_types.has(int(k)): avail_keys.append(int(k)) if avail_keys.is_empty(): return {"ok": false, "code": "NO_AVAILABLE_ATTR", "msg": "无可添加的属性。"} # 按权重随机选取属性 var chosen_type: int = _pick_weighted_type(avail_keys, pool) var grade := randi() % 5 # 0..4 (对应 1~5 档) var value: int = int(pool[chosen_type]["values"][grade]) attrs.append({"type": chosen_type, "value": value}) target_item["attrs"] = attrs return { "ok": true, "code": "SUCCESS", "msg": "属性添加成功!", "consumed": true, "added_attr": {"type": chosen_type, "value": value, "name": pool[chosen_type]["name"]}, } ## 执行洗炼重置全部属性(USE_CHANGE_ATTRIBUTE 71084) ## 40250 原生逻辑:词条数 > 0;保留现有词条总数,全部重新随机生成类型与数值 static func apply_change_attribute(target_item: Dictionary, target_proto: Dictionary) -> Dictionary: if not can_have_attributes(target_proto): return {"ok": false, "code": "CANNOT_ATTACH_ATTR", "msg": "该物品无法重置属性。"} var attrs: Array = target_item.get("attrs", []).duplicate(true) var count := attrs.size() if count == 0: return {"ok": false, "code": "NO_ATTRIBUTES", "msg": "此物品没有任何额外属性,无法洗炼。"} var pool := get_attr_pool(target_proto) var new_attrs: Array = [] var used_types := {} for _i in count: var avail_keys: Array[int] = [] for k in pool.keys(): if not used_types.has(int(k)): avail_keys.append(int(k)) if avail_keys.is_empty(): break var chosen_type: int = _pick_weighted_type(avail_keys, pool) used_types[chosen_type] = true var grade := randi() % 5 var val: int = int(pool[chosen_type]["values"][grade]) new_attrs.append({"type": chosen_type, "value": val}) target_item["attrs"] = new_attrs return { "ok": true, "code": "SUCCESS", "msg": "属性重置成功!", "consumed": true, "attrs": new_attrs, } ## 执行第五属性宝珠(USE_ADD_ATTRIBUTE2 / 祝福宝珠 70024) ## 40250 原生逻辑:严格要求现有词条数 == 4;成功率 30%(40250 ADD_PERCENT_TABLE[4]) static func apply_blessing_marble(target_item: Dictionary, target_proto: Dictionary, force_success := false) -> Dictionary: if not can_have_attributes(target_proto): return {"ok": false, "code": "CANNOT_ATTACH_ATTR", "msg": "该物品无法添加第五属性。"} var attrs: Array = target_item.get("attrs", []).duplicate(true) if attrs.size() < 4: return {"ok": false, "code": "NEED_FOUR_ATTRS", "msg": "装备必须先拥有 4 条属性,才能使用祝福宝珠添加第 5 条属性。"} if attrs.size() >= 5: return {"ok": false, "code": "ALREADY_FIVE_ATTRS", "msg": "该物品已达 5 条属性上限。"} var rate: int = int(ADD_PERCENT_TABLE[4]) # 30% var roll := 0 if force_success else (randi() % 100 + 1) if not force_success and roll > rate: return { "ok": false, "code": "MARBLE_FAIL", "msg": "祝福宝珠开启第 5 条属性失败。", "consumed": true, } var pool := get_attr_pool(target_proto) var existing_types := {} for a in attrs: existing_types[int(a.get("type", 0))] = true var avail_keys: Array[int] = [] for k in pool.keys(): if not existing_types.has(int(k)): avail_keys.append(int(k)) var chosen_type: int = _pick_weighted_type(avail_keys, pool) var grade := randi() % 5 var val: int = int(pool[chosen_type]["values"][grade]) attrs.append({"type": chosen_type, "value": val}) target_item["attrs"] = attrs return { "ok": true, "code": "SUCCESS", "msg": "祝福宝珠成功开启第 5 条属性!", "consumed": true, "added_attr": {"type": chosen_type, "value": val, "name": pool[chosen_type]["name"]}, } static func _pick_weighted_type(avail_keys: Array[int], pool: Dictionary) -> int: var total_weight := 0 for k in avail_keys: total_weight += int(pool[k].get("weight", 1)) var r := randi() % maxi(1, total_weight) var accum := 0 for k in avail_keys: accum += int(pool[k].get("weight", 1)) if r < accum: return k return avail_keys[0]