using Kryz.CharacterStats.Examples; using System; using System.Collections.Generic; using System.Linq; using UnityEngine; // ============================================================================ // RUNTIME ABILITY INSTANCE - Context-Aware Version // ============================================================================ /// /// Runtime wrapper for BaseAbility with context-aware cost calculations /// Modifiers now properly affect total costs (flat + percentage combined) /// public class RuntimeAbilityInstance { // ======================================================================== // CORE DATA // ======================================================================== [SerializeField] private BaseAbility sourceAbility; private List activeModifiers = new List(); private List runtimeBehaviors = new List(); // Runtime state private float lastUsedTime; private int currentCharges; // ======================================================================== // CONSTRUCTOR // ======================================================================== public RuntimeAbilityInstance(BaseAbility source) { sourceAbility = source; currentCharges = GetMaxCharges(); } // ======================================================================== // PROPERTIES - Direct access to original values // ======================================================================== public BaseAbility SourceAbility => sourceAbility; public string displayName => sourceAbility.displayName; public Sprite Icon => sourceAbility.Icon; public List targettingTags => sourceAbility.targettingTags; public List tags => sourceAbility.tags; public List abilityEffects => sourceAbility.abilityEffects; public bool castableWhileMoving => sourceAbility.castableWhileMoving; public AbilityAnimationType animationType => sourceAbility.animationType; // Base costs (for display when no context available) public float baseManaFlat => sourceAbility.manaCost; public float baseHealthFlat => sourceAbility.healthCost; public float baseClassResourceFlat => sourceAbility.classResourceCost; public float baseManaPercent => sourceAbility.percentMaxManaCost; public float baseHealthPercent => sourceAbility.percentMaxHealthCost; public float baseCastTime => sourceAbility.castTime; public float baseCooldown => sourceAbility.cooldown; // Modified timing values (these don't need user context) public float castTime => GetModifiedCastTime(); public float cooldown => GetModifiedCooldown(); // Runtime state properties public float LastUsedTime => lastUsedTime; public bool IsOnCooldown => Time.time < lastUsedTime + cooldown; public float CooldownRemaining => Mathf.Max(0f, (lastUsedTime + cooldown) - Time.time); public List ActiveModifiers => new List(activeModifiers); public List RuntimeBehaviors => new List(runtimeBehaviors); // ======================================================================== // CONTEXT-AWARE COST CALCULATIONS // ======================================================================== /// /// Calculate final mana cost including modifiers (affects flat + percentage total) /// public float GetFinalManaCost(CostCalculationContext context) { // Calculate total base cost (flat + percentage) float totalBaseCost = sourceAbility.manaCost + (context.maxMana * sourceAbility.percentMaxManaCost); // Apply modifiers to the total cost float modifiedCost = (totalBaseCost + GetTotalManaCostFlat()) * GetTotalManaCostMultiplier(); return Mathf.Max(0f, modifiedCost); } /// /// Calculate final health cost including modifiers (affects flat + percentage total) /// public float GetFinalHealthCost(CostCalculationContext context) { // Calculate total base cost (flat + percentage) float totalBaseCost = sourceAbility.healthCost + (context.maxHealth * sourceAbility.percentMaxHealthCost); // Apply modifiers to the total cost float modifiedCost = (totalBaseCost + GetTotalHealthCostFlat()) * GetTotalHealthCostMultiplier(); return Mathf.Max(0f, modifiedCost); } /// /// Calculate final class resource cost (flat only, no percentage version) /// public float GetFinalClassResourceCost() { float modifiedCost = (sourceAbility.classResourceCost + GetTotalClassResourceFlat()) * GetTotalClassResourceMultiplier(); return Mathf.Max(0f, modifiedCost); } // Convenience overloads for when you have a user reference public float GetFinalManaCost(Taggable user) => GetFinalManaCost(CostCalculationContext.FromUser(user)); public float GetFinalHealthCost(Taggable user) => GetFinalHealthCost(CostCalculationContext.FromUser(user)); // ======================================================================== // DISPLAY METHODS - For UI when no user context available // ======================================================================== /// /// Get display-friendly mana cost text /// public string GetManaCostDisplayText(CostCalculationContext? context = null) { if (context.HasValue) { return $"{GetFinalManaCost(context.Value):F0} Mana"; } else { // Show base cost + modifiers when no context float flatCost = baseManaFlat * GetTotalManaCostMultiplier() + GetTotalManaCostFlat(); string text = $"{Mathf.Max(0f, flatCost):F0}"; if (baseManaPercent > 0) text += $" + {baseManaPercent:P0} Max"; return text + " Mana"; } } /// /// Get display-friendly health cost text /// public string GetHealthCostDisplayText(CostCalculationContext? context = null) { if (context.HasValue) { return $"{GetFinalHealthCost(context.Value):F0} Health"; } else { float flatCost = baseHealthFlat * GetTotalHealthCostMultiplier() + GetTotalHealthCostFlat(); string text = $"{Mathf.Max(0f, flatCost):F0}"; if (baseHealthPercent > 0) text += $" + {baseHealthPercent:P0} Max"; return text + " Health"; } } /// /// Get all cost information as formatted string /// public string GetAllCostsDisplayText(CostCalculationContext? context = null) { var costs = new List(); // Mana cost if (baseManaFlat > 0 || baseManaPercent > 0) costs.Add(GetManaCostDisplayText(context)); // Health cost if (baseHealthFlat > 0 || baseHealthPercent > 0) costs.Add(GetHealthCostDisplayText(context)); // Class resource cost float classResourceCost = GetFinalClassResourceCost(); if (classResourceCost > 0) costs.Add($"{classResourceCost:F0} Energy"); return costs.Count > 0 ? string.Join(", ", costs) : "No Cost"; } // ======================================================================== // EXECUTION METHODS // ======================================================================== Mana userMana; Health userHealth; public virtual void SpendResourcesNecessary(Taggable user) { if (userMana == null) userMana = user.GetComponent(); if (userHealth == null) userHealth = user.GetComponent(); userMana.ChangeValue(-GetFinalManaCost(user)); userHealth.ChangeValue(-GetFinalHealthCost(user)); //user.GetComponent()?.ChangeValue(-GetFinalClassResourceCost()); } public virtual void Execute(Taggable user) { if (!CanExecute(user)) return; ExecuteBehaviors(BehaviorTrigger.PreCast, user, null, Vector3.zero); SpendResourcesNecessary(user); ExecuteBehaviors(BehaviorTrigger.Execute, user, null, Vector3.zero); ExecuteBehaviors(BehaviorTrigger.PostCast, user, null, Vector3.zero); lastUsedTime = Time.time; } public virtual void Execute(Taggable user, Vector3 point) { if (!CanExecute(user)) return; ExecuteBehaviors(BehaviorTrigger.PreCast, user, null, point); SpendResourcesNecessary(user); ExecuteBehaviors(BehaviorTrigger.Execute, user, null, point); ExecuteBehaviors(BehaviorTrigger.PostCast, user, null, point); lastUsedTime = Time.time; } public virtual void Execute(Taggable user, Transform target) { if (!CanExecute(user)) return; ExecuteBehaviors(BehaviorTrigger.PreCast, user, target, target.position); SpendResourcesNecessary(user); ExecuteBehaviors(BehaviorTrigger.Execute, user, target, target.position); ExecuteBehaviors(BehaviorTrigger.PostCast, user, target, target.position); lastUsedTime = Time.time; } // ======================================================================== // ABILITY STATE CHECKS // ======================================================================== public bool CanExecute(Taggable user) { if (IsOnCooldown) return false; return CanAffordResources(CostCalculationContext.FromUser(user)); } public bool CanAffordResources(CostCalculationContext context) { // Check mana cost if (context.maxMana > 0) { float finalManaCost = GetFinalManaCost(context); if (context.currentMana < finalManaCost) return false; } // Check health cost if (context.maxHealth > 0) { float finalHealthCost = GetFinalHealthCost(context); if (context.currentHealth <= finalHealthCost) return false; } // Check class resource cost if (context.currentClassResource > 0) { float finalClassResourceCost = GetFinalClassResourceCost(); if (context.currentClassResource < finalClassResourceCost) return false; } return true; } // Convenience overload public bool CanAffordResources(Taggable user) => CanAffordResources(CostCalculationContext.FromUser(user)); // ======================================================================== // MODIFIER CALCULATION HELPERS // ======================================================================== private float GetTotalManaCostMultiplier() { float multiplier = 1f; foreach (var modifier in activeModifiers) multiplier *= modifier.manaCostMultiplier; return multiplier; } private float GetTotalManaCostFlat() { float flat = 0f; foreach (var modifier in activeModifiers) flat += modifier.manaCostFlat; return flat; } private float GetTotalHealthCostMultiplier() { float multiplier = 1f; foreach (var modifier in activeModifiers) multiplier *= modifier.healthCostMultiplier; return multiplier; } private float GetTotalHealthCostFlat() { float flat = 0f; foreach (var modifier in activeModifiers) flat += modifier.healthCostFlat; return flat; } private float GetTotalClassResourceMultiplier() { float multiplier = 1f; foreach (var modifier in activeModifiers) multiplier *= modifier.classResourceCostMultiplier; return multiplier; } private float GetTotalClassResourceFlat() { float flat = 0f; foreach (var modifier in activeModifiers) flat += modifier.classResourceCostFlat; return flat; } private float GetModifiedCastTime() { float time = sourceAbility.castTime; foreach (var modifier in activeModifiers) time *= modifier.castTimeMultiplier; return Mathf.Max(0f, time); } private float GetModifiedCooldown() { float cd = sourceAbility.cooldown; foreach (var modifier in activeModifiers) cd *= modifier.cooldownMultiplier; return Mathf.Max(0f, cd); } // ======================================================================== // MODIFIER MANAGEMENT (same as before) // ======================================================================== public void AddModifier(AbilityModifier modifier) { activeModifiers.Add(modifier); } public void AddModifier(AbilityModifier modifier, object source) { var modifierWithSource = new AbilityModifier(modifier, source); activeModifiers.Add(modifierWithSource); } public void RemoveModifier(AbilityModifier modifier) { activeModifiers.Remove(modifier); } public bool RemoveAllModifiersFromSource(object source) { int numRemovals = activeModifiers.RemoveAll(mod => mod.Source != null && mod.Source.Equals(source)); return numRemovals > 0; } public bool HasModifiersFromSource(object source) { return activeModifiers.Any(mod => mod.Source != null && mod.Source.Equals(source)); } public List GetModifiersFromSource(object source) { return activeModifiers.Where(mod => mod.Source != null && mod.Source.Equals(source)).ToList(); } public void RemoveAllModifiers() { activeModifiers.Clear(); } // ======================================================================== // CONVENIENCE METHODS // ======================================================================== public void AddEquipmentModifier(AbilityModifier modifier, EquippableItem equipment) { AddModifier(modifier, equipment); } public void AddEffectModifier(AbilityModifier modifier, BaseEffect effect) { AddModifier(modifier, effect); } public void AddSkillModifier(AbilityModifier modifier, string skillSource) { AddModifier(modifier, skillSource); } public void RemoveEquipmentModifiers(EquippableItem equipment) { RemoveAllModifiersFromSource(equipment); } public void RemoveEffectModifiers(BaseEffect effect) { RemoveAllModifiersFromSource(effect); } // ======================================================================== // RUNTIME BEHAVIOR SYSTEM (same as before) // ======================================================================== public void AddBehavior(RuntimeBehavior behavior) { runtimeBehaviors.Add(behavior); } public void RemoveBehavior(RuntimeBehavior behavior) { runtimeBehaviors.Remove(behavior); } public T GetBehavior() where T : RuntimeBehavior { return runtimeBehaviors.Find(b => b is T) as T; } public void RemoveBehavior() where T : RuntimeBehavior { for (int i = runtimeBehaviors.Count - 1; i >= 0; i--) { if (runtimeBehaviors[i] is T) { runtimeBehaviors.RemoveAt(i); } } } private void ExecuteBehaviors(BehaviorTrigger trigger, Taggable user, Transform target, Vector3 point) { foreach (var behavior in runtimeBehaviors) { if (behavior.Trigger == trigger) { behavior.Execute(this, user, target, point); } } } // ======================================================================== // UTILITY METHODS // ======================================================================== public RuntimeAbilityInstance Clone() { var clone = new RuntimeAbilityInstance(sourceAbility); foreach (var modifier in activeModifiers) clone.AddModifier(modifier); foreach (var behavior in runtimeBehaviors) clone.AddBehavior(behavior.Clone()); return clone; } private int GetMaxCharges() { return 1; // Future: add charge system } // ======================================================================== // DEBUG METHODS // ======================================================================== public void PrintCostAnalysis(CostCalculationContext? context = null) { Debug.Log($"=== Cost Analysis for {displayName} ==="); if (context.HasValue) { var ctx = context.Value; Debug.Log($"With Context (Max Mana: {ctx.maxMana}, Max Health: {ctx.maxHealth}):"); Debug.Log($" Final Mana Cost: {GetFinalManaCost(ctx):F1}"); Debug.Log($" Final Health Cost: {GetFinalHealthCost(ctx):F1}"); } else { Debug.Log("Without Context:"); Debug.Log($" Base Flat Mana: {baseManaFlat} + {baseManaPercent:P0} Max"); Debug.Log($" Base Flat Health: {baseHealthFlat} + {baseHealthPercent:P0} Max"); } Debug.Log($" Final Class Resource Cost: {GetFinalClassResourceCost():F1}"); Debug.Log($" Active Modifiers: {activeModifiers.Count}"); } // ======================================================================== // IMPLICIT CONVERSION // ======================================================================== public static implicit operator BaseAbility(RuntimeAbilityInstance instance) { return instance.sourceAbility; } public static implicit operator RuntimeAbilityInstance(BaseAbility ability) { return new RuntimeAbilityInstance(ability); } }