# energy_system.gd —— Metin2 40250 官方能量碎片提取与能量水晶熔炼系统 1:1 # 对照 40250 服务端 energy_system.quest, char_item.cpp:6319 (CBuffOnAttributes), char.h (POINT_ENERGY=128) class_name EnergySystem extends RefCounted signal fragments_extracted(count: int, item_name: String) signal crystal_fused(crystal_item: Dictionary) signal energy_activated(duration: float, bonus_pct: int) signal energy_expired() const NPC_ALCHEMIST := 20001 # 炼金术士 const MIN_PLAYER_LEVEL := 35 # 角色开启门槛 35 级 const MIN_EQUIP_LEVEL := 35 # 装备分解门槛 35 级 const VNUM_ENERGY_STONE := 51001 # 能量碎片 (Energy Stone / Fragment) const VNUM_CHARGING_STONE := 51002 # 能量水晶 (Charging Stone / Energy Crystal) const FUSION_NEED_STONES := 30 # 熔炼需要 30 颗碎片 const FUSION_COST_GOLD := 1000 # 熔炼手续费 1000 金币 const ENERGY_DURATION := 7200.0 # 2 小时 (7200 秒) const ATTR_BONUS_PCT := 10 # 全身穿戴装备属性增幅 +10% var is_energy_active: bool = false var energy_time_remaining: float = 0.0 var applied_bonuses: Dictionary = {} # 计算装备分解产出碎片数量 (40250 energy_system.quest 概率阶梯) static func calculate_fragment_yield(equip_level_limit: int, rand_val: int = -1) -> int: var r: int = rand_val if rand_val >= 0 else (randi() % 100) if equip_level_limit >= 35 and equip_level_limit <= 50: # 35~50 级装备产出 1~5 颗 return clampi(1 + (r % 5), 1, 5) elif equip_level_limit > 50 and equip_level_limit <= 70: # 51~70 级装备产出 3~8 颗 return clampi(3 + (r % 6), 3, 8) else: # 70 级以上顶级装备产出 5~15 颗 return clampi(5 + (r % 11), 5, 15) # 在炼金术士处分解淘汰装备提取能量碎片 func extract_energy_fragments(inventory: Array, slot_index: int, player_level: int) -> Dictionary: if player_level < MIN_PLAYER_LEVEL: return {"ok": false, "reason": "PLAYER_LEVEL_TOO_LOW", "msg": "炼金术士只为 35 级以上的强者提取能量!"} if slot_index < 0 or slot_index >= inventory.size() or inventory[slot_index] == null: return {"ok": false, "reason": "INVALID_SLOT"} var item: Dictionary = inventory[slot_index] var limit_lv := int(item.get("level_limit", item.get("limit_level", 0))) if limit_lv < MIN_EQUIP_LEVEL: return {"ok": false, "reason": "EQUIP_LEVEL_TOO_LOW", "msg": "只能分解 35 级以上的武器、防具或首饰!"} # 检查箭矢等消耗品 if item.get("type", 0) == 1 and item.get("subtype", 0) == 5: # WEAPON_ARROW return {"ok": false, "reason": "ARROW_NOT_ALLOWED", "msg": "箭矢无法提取能量!"} var item_name: String = str(item.get("name", "淘汰装备")) var count_yield := calculate_fragment_yield(limit_lv) # 移除装备 inventory[slot_index] = null # 发放能量碎片 (查找已有堆叠或找空位) var placed := false for i in range(inventory.size()): if inventory[i] != null and int(inventory[i].get("vnum", 0)) == VNUM_ENERGY_STONE: var cur_c := int(inventory[i].get("count", 1)) inventory[i]["count"] = cur_c + count_yield placed = true break if not placed: for i in range(inventory.size()): if inventory[i] == null: inventory[i] = { "vnum": VNUM_ENERGY_STONE, "name": "能量碎片", "count": count_yield } placed = true break fragments_extracted.emit(count_yield, item_name) return { "ok": true, "yield": count_yield, "item_name": item_name, "msg": "成功分解【%s】,提取出 %d 颗【能量碎片】!" % [item_name, count_yield] } # 熔炼能量水晶 (30 碎片 + 1000 金币) func fuse_energy_crystal(inventory: Array, player_gold: Dictionary) -> Dictionary: var cur_gold := int(player_gold.get("gold", 0)) if cur_gold < FUSION_COST_GOLD: return {"ok": false, "reason": "NOT_ENOUGH_GOLD", "msg": "金币不足!熔炼需要 1,000 金币。"} # 统计能量碎片总数 var total_stones := 0 var stone_slots: Array = [] for i in range(inventory.size()): if inventory[i] != null and int(inventory[i].get("vnum", 0)) == VNUM_ENERGY_STONE: total_stones += int(inventory[i].get("count", 1)) stone_slots.append(i) if total_stones < FUSION_NEED_STONES: return { "ok": false, "reason": "NOT_ENOUGH_STONES", "msg": "能量碎片不足!需要 %d 颗,当前拥有 %d 颗。" % [FUSION_NEED_STONES, total_stones] } # 寻找放置能量水晶的槽位 var crystal_slot := -1 for i in range(inventory.size()): if inventory[i] == null: crystal_slot = i break # 扣除 30 颗碎片 var need_to_deduct := FUSION_NEED_STONES for slot_idx in stone_slots: var it: Dictionary = inventory[slot_idx] var c := int(it.get("count", 1)) if c <= need_to_deduct: need_to_deduct -= c inventory[slot_idx] = null if crystal_slot == -1: crystal_slot = slot_idx # 复用清空的格子 else: it["count"] = c - need_to_deduct need_to_deduct = 0 break player_gold["gold"] = cur_gold - FUSION_COST_GOLD var crystal := { "vnum": VNUM_CHARGING_STONE, "name": "能量水晶", "count": 1 } inventory[crystal_slot] = crystal crystal_fused.emit(crystal) return { "ok": true, "crystal": crystal, "slot": crystal_slot, "msg": "熔炼成功!纯净的【能量水晶】已凝聚成型。" } # 使用能量水晶激活 2 小时全身穿戴装备 10% 增幅 (40250 CBuffOnAttributes) func use_energy_crystal(inventory: Array, slot_index: int, equipped_items: Array, player_stats: Dictionary) -> Dictionary: if slot_index < 0 or slot_index >= inventory.size() or inventory[slot_index] == null: return {"ok": false, "reason": "INVALID_SLOT"} var item: Dictionary = inventory[slot_index] if int(item.get("vnum", 0)) != VNUM_CHARGING_STONE: return {"ok": false, "reason": "NOT_A_CHARGING_STONE", "msg": "此物品不是能量水晶!"} # 消耗 1 颗能量水晶 var c := int(item.get("count", 1)) if c > 1: item["count"] = c - 1 else: inventory[slot_index] = null # 如果已激活,先清除旧加成 if is_energy_active: _clear_energy_buffs(player_stats) # 计算穿戴装备基础属性总和的 10% applied_bonuses.clear() for eq in equipped_items: if eq == null: continue var atk = int(eq.get("attack_power", 0)) var def = int(eq.get("defense", 0)) var hp = int(eq.get("max_hp", 0)) if atk > 0: applied_bonuses["attack_power"] = int(applied_bonuses.get("attack_power", 0)) + int(ceil(atk * 0.10)) if def > 0: applied_bonuses["defense"] = int(applied_bonuses.get("defense", 0)) + int(ceil(def * 0.10)) if hp > 0: applied_bonuses["max_hp"] = int(applied_bonuses.get("max_hp", 0)) + int(ceil(hp * 0.10)) # 保底全属性增益 (即使没有极品装备,也提供保底加成) applied_bonuses["attack_power"] = maxi(10, int(applied_bonuses.get("attack_power", 0))) applied_bonuses["defense"] = maxi(10, int(applied_bonuses.get("defense", 0))) # 注入玩家属性 for k in applied_bonuses.keys(): player_stats[k] = int(player_stats.get(k, 0)) + applied_bonuses[k] is_energy_active = true energy_time_remaining = ENERGY_DURATION energy_activated.emit(ENERGY_DURATION, ATTR_BONUS_PCT) return { "ok": true, "duration": ENERGY_DURATION, "bonuses": applied_bonuses, "msg": "能量水晶激活!全身装备属性增幅 +10%%,持续 2 小时。" } # 清除能量属性 func _clear_energy_buffs(player_stats: Dictionary) -> void: for k in applied_bonuses.keys(): player_stats[k] = int(player_stats.get(k, 0)) - applied_bonuses[k] applied_bonuses.clear() is_energy_active = false energy_time_remaining = 0.0 # 时钟心跳推进 func update(delta: float, player_stats: Dictionary) -> void: if not is_energy_active: return energy_time_remaining -= delta if energy_time_remaining <= 0.0: _clear_energy_buffs(player_stats) energy_expired.emit() # 序列化 func serialize() -> Dictionary: return { "is_energy_active": is_energy_active, "energy_time_remaining": energy_time_remaining, "applied_bonuses": applied_bonuses.duplicate(true) } # 反序列化 func deserialize(data: Dictionary) -> void: is_energy_active = bool(data.get("is_energy_active", false)) energy_time_remaining = float(data.get("energy_time_remaining", 0.0)) applied_bonuses = data.get("applied_bonuses", {}).duplicate(true)