using System; using System.Collections.Generic; using UnityEngine; using Animancer; using BaseGames.Player; namespace BaseGames.Skills { /// /// 技能数值修改维度(架构 09_ProgressionModule §10)。 /// public enum SkillStat { Damage, Cost, Cooldown, Range, Duration } /// /// 插槽覆盖描述符:某护符将指定形态的某个技能槽替换为另一技能。 /// [Serializable] public struct SkillSlotOverride { public FormSO targetForm; // null = 所有形态 public string targetSlot; // 使用 SkillSlotNames 中的常量(SoulSkill / SpiritSkill1 / SpiritSkill2) public FormSkillSO replacementSkill; // 替换目标技能 public int priority; // 高优先级覆盖低优先级 } /// /// 所有数值修改器叠加后的运行时参数快照(架构 09_ProgressionModule §10)。 /// 由 SkillModifierRegistry.GetEffectiveParams() 生成,传入 SkillManager 使用。 /// public struct EffectiveSkillParams { public FormSkillSO baseSkill; // 原始 SO 引用(不变) public int effectiveCost; // 修改后消耗 public float effectiveCooldown; // 修改后冷却(秒) public float damageMult; // 伤害倍率(1.0 = 无增益) public float rangeMult; // 范围倍率 public FeedbackPresetSO effectiveFeedback; // 最终特效预设(null = 回退原始) public ClipTransition effectiveAnimation; // 最终施法动画(null = 回退原始) /// 以技能 SO 默认值初始化,无任何修改器加成。 public static EffectiveSkillParams FromBase(FormSkillSO skill) => new() { baseSkill = skill, effectiveCost = skill.baseCost, effectiveCooldown = skill.cooldown, damageMult = 1f, rangeMult = 1f, effectiveFeedback = null, effectiveAnimation = default, }; } // 内部记录结构,存储单条数值修改 internal struct SkillStatEntry { public SkillStat stat; public float delta; public bool isPercent; } /// /// 技能修改器注册表(架构 09_ProgressionModule §10)。 /// 挂在 Player 上,收集所有护符对技能数值的修改。 /// SkillManager 在施放技能时调用 GetEffectiveParams() 获取最终参数。 /// public class SkillModifierRegistry : MonoBehaviour { // skillId → 一组数值修改 private readonly Dictionary> _modifiers = new(); // 插槽覆盖列表(按 priority 降序排列) private readonly List _slotOverrides = new(); // ── 数值修改 ──────────────────────────────────────────────────────── public void Register(string skillId, SkillStat stat, float delta, bool isPercent) { if (!_modifiers.TryGetValue(skillId, out var list)) { list = new List(); _modifiers[skillId] = list; } list.Add(new SkillStatEntry { stat = stat, delta = delta, isPercent = isPercent }); } public void Unregister(string skillId, SkillStat stat, float delta, bool isPercent) { if (!_modifiers.TryGetValue(skillId, out var list)) return; list.RemoveAll(e => e.stat == stat && Mathf.Approximately(e.delta, delta) && e.isPercent == isPercent); } /// /// 对给定技能叠加所有已注册修改器,返回一次性快照供 SkillManager 使用。 /// public EffectiveSkillParams GetEffectiveParams(FormSkillSO skill) { var p = EffectiveSkillParams.FromBase(skill); if (!_modifiers.TryGetValue(skill.skillId, out var entries)) return p; float flatCost = 0, pctCost = 1f; float flatCooldown = 0, pctCooldown = 1f; float flatDamage = 0, pctDamage = 1f; float flatRange = 0, pctRange = 1f; foreach (var e in entries) { switch (e.stat) { case SkillStat.Cost: if (e.isPercent) pctCost += e.delta; else flatCost += e.delta; break; case SkillStat.Cooldown: if (e.isPercent) pctCooldown += e.delta; else flatCooldown += e.delta; break; case SkillStat.Damage: if (e.isPercent) pctDamage += e.delta; else flatDamage += e.delta; break; case SkillStat.Range: if (e.isPercent) pctRange += e.delta; else flatRange += e.delta; break; } } p.effectiveCost = Mathf.Max(0, Mathf.RoundToInt(skill.baseCost * pctCost + flatCost)); p.effectiveCooldown = Mathf.Max(0, skill.cooldown * pctCooldown + flatCooldown); p.damageMult = 1f + (pctDamage - 1f) + flatDamage; p.rangeMult = 1f + (pctRange - 1f) + flatRange; return p; } // ── 插槽覆盖 ──────────────────────────────────────────────────────── public void AddSlotOverride(SkillSlotOverride data) { _slotOverrides.Add(data); _slotOverrides.Sort((a, b) => b.priority.CompareTo(a.priority)); } public void RemoveSlotOverride(SkillSlotOverride data) { _slotOverrides.RemoveAll(o => o.targetForm == data.targetForm && o.targetSlot == data.targetSlot && o.replacementSkill == data.replacementSkill); } /// /// 获取当前形态某槽位的实际技能(考虑插槽覆盖)。 /// 没有覆盖时返回 null(调用方保留原始技能)。 /// public FormSkillSO GetOverriddenSkill(FormSO form, string slotName) { foreach (var o in _slotOverrides) { bool formMatch = o.targetForm == null || o.targetForm == form; if (formMatch && o.targetSlot == slotName) return o.replacementSkill; } return null; } } }