// ============================================================================ // RUNTIME ABILITY WRAPPER - DROP-IN REPLACEMENT FOR BaseAbility // ============================================================================ using Kryz.CharacterStats.Examples; using System; using System.Collections.Generic; using System.Linq; using UnityEngine; // ============================================================================ // 1. RUNTIME ABILITY INSTANCE - Wraps Your Existing BaseAbility // ============================================================================ /// /// Runtime wrapper for BaseAbility that adds modifiers and behaviors /// Can be used anywhere you currently use BaseAbility references /// public class RuntimeAbilityInstance { // ======================================================================== // CORE DATA - Wraps your existing ScriptableObject // ======================================================================== [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(); // In case you add charges later RecalculateModifiedValues(); } // ======================================================================== // PROPERTIES - Direct access to original + modified values // ======================================================================== // Original values (unchanged) 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; // Modified values (affected by modifiers) public float manaCost { get; private set; } public float healthCost { get; private set; } public float classResourceCost { get; private set; } public float spiritPowerReserveCost { get; private set; } public float percentMaxManaCost { get; private set; } public float percentMaxHealthCost { get; private set; } public float castTime { get; private set; } public float cooldown { get; private set; } // 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); // ======================================================================== // EXECUTION METHODS - Same signature as your BaseAbility // ======================================================================== public virtual void Execute(Taggable user) { if (!CanExecute(user)) return; // Execute pre-cast behaviors ExecuteBehaviors(BehaviorTrigger.PreCast, user, null, Vector3.zero); // Execute original ability sourceAbility.Execute(user); // Execute post-cast behaviors ExecuteBehaviors(BehaviorTrigger.PostCast, user, null, Vector3.zero); // Update runtime state lastUsedTime = Time.time; } public virtual void Execute(Taggable user, Vector3 point) { if (!CanExecute(user)) return; ExecuteBehaviors(BehaviorTrigger.PreCast, user, null, point); sourceAbility.Execute(user, 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); sourceAbility.Execute(user, target); ExecuteBehaviors(BehaviorTrigger.PostCast, user, target, target.position); lastUsedTime = Time.time; } // ======================================================================== // ABILITY STATE CHECKS // ======================================================================== public bool CanExecute(Taggable user) { // Check cooldown if (IsOnCooldown) return false; // Check resources using modified costs return CanAffordResources(user); } private bool CanAffordResources(Taggable user) { // Use the modified costs, not original var userMana = user.GetComponent(); if (userMana != null) { float finalManaCost = manaCost + userMana.GetMaxValue() * percentMaxManaCost; if (!userMana.EnoughMana(finalManaCost)) return false; } var userHealth = user.GetComponent(); if (userHealth != null) { float finalHealthCost = healthCost + userHealth.GetMaxValue() * percentMaxHealthCost; if (userHealth.GetCurrentValue() <= finalHealthCost) return false; } var userClassResource = user.GetComponent(); if (userClassResource != null && classResourceCost > 0) { if (userClassResource.GetCurrentValue() < classResourceCost) return false; } return true; } public float GetFinalManaCost(Mana userMana) { return manaCost + userMana.GetMaxValue() * percentMaxManaCost; } public float GetFinalHealthCost(Health userHealth) { return healthCost + userHealth.GetMaxValue() * percentMaxHealthCost; } // ======================================================================== // ENHANCED MODIFIER SYSTEM - Source-Aware Management // ======================================================================== public void AddModifier(AbilityModifier modifier) { activeModifiers.Add(modifier); RecalculateModifiedValues(); } public void AddModifier(AbilityModifier modifier, object source) { // Create a copy with the specified source var modifierWithSource = new AbilityModifier(modifier, source); activeModifiers.Add(modifierWithSource); RecalculateModifiedValues(); } public void RemoveModifier(AbilityModifier modifier) { activeModifiers.Remove(modifier); RecalculateModifiedValues(); } public bool RemoveAllModifiersFromSource(object source) { int numRemovals = activeModifiers.RemoveAll(mod => mod.Source != null && mod.Source.Equals(source)); if (numRemovals > 0) { RecalculateModifiedValues(); return true; } return false; } 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(); RecalculateModifiedValues(); } // ======================================================================== // CONVENIENCE METHODS - Common source-based operations // ======================================================================== // Add modifier from equipment public void AddEquipmentModifier(AbilityModifier modifier, EquippableItem equipment) { AddModifier(modifier, equipment); } // Add modifier from buff/effect public void AddEffectModifier(AbilityModifier modifier, BaseEffect effect) { AddModifier(modifier, effect); } // Add modifier from skill/talent public void AddSkillModifier(AbilityModifier modifier, string skillSource) { AddModifier(modifier, skillSource); } // Remove equipment modifiers when unequipping public void RemoveEquipmentModifiers(EquippableItem equipment) { RemoveAllModifiersFromSource(equipment); } // Remove effect modifiers when effect expires public void RemoveEffectModifiers(BaseEffect effect) { RemoveAllModifiersFromSource(effect); } // ======================================================================== // MODIFIER CALCULATION // ======================================================================== private void RecalculateModifiedValues() { // Start with original values manaCost = sourceAbility.manaCost; healthCost = sourceAbility.healthCost; classResourceCost = sourceAbility.classResourceCost; spiritPowerReserveCost = sourceAbility.spiritPowerReserveCost; percentMaxManaCost = sourceAbility.percentMaxManaCost; percentMaxHealthCost = sourceAbility.percentMaxHealthCost; castTime = sourceAbility.castTime; cooldown = sourceAbility.cooldown; // Apply all modifiers foreach (var modifier in activeModifiers) { ApplyModifier(modifier); } } private void ApplyModifier(AbilityModifier modifier) { // Resource cost modifiers manaCost = manaCost * modifier.manaCostMultiplier + modifier.manaCostFlat; healthCost = healthCost * modifier.healthCostMultiplier + modifier.healthCostFlat; classResourceCost = classResourceCost * modifier.classResourceCostMultiplier + modifier.classResourceCostFlat; // Timing modifiers castTime *= modifier.castTimeMultiplier; cooldown *= modifier.cooldownMultiplier; // Ensure values don't go negative manaCost = Mathf.Max(0f, manaCost); healthCost = Mathf.Max(0f, healthCost); classResourceCost = Mathf.Max(0f, classResourceCost); castTime = Mathf.Max(0f, castTime); cooldown = Mathf.Max(0f, cooldown); } // ======================================================================== // RUNTIME BEHAVIOR SYSTEM // ======================================================================== 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); } } } // ======================================================================== // DEBUG/UTILITY METHODS // ======================================================================== public void PrintActiveModifiers() { Debug.Log($"=== Active Modifiers for {displayName} ==="); foreach (var modifier in activeModifiers) { string sourceStr = modifier.Source?.ToString() ?? "No Source"; Debug.Log($"- {modifier.modifierName} (Source: {sourceStr})"); } } public Dictionary GetModifierCountBySource() { var counts = new Dictionary(); foreach (var modifier in activeModifiers) { var source = modifier.Source ?? "No Source"; counts[source] = counts.ContainsKey(source) ? counts[source] + 1 : 1; } return counts; } // ======================================================================== // UTILITY METHODS // ======================================================================== public RuntimeAbilityInstance Clone() { var clone = new RuntimeAbilityInstance(sourceAbility); // Copy modifiers foreach (var modifier in activeModifiers) { clone.AddModifier(modifier); } // Copy behaviors foreach (var behavior in runtimeBehaviors) { clone.AddBehavior(behavior.Clone()); } return clone; } private int GetMaxCharges() { // Future: add charge system return 1; } // ======================================================================== // IMPLICIT CONVERSION - Makes it work seamlessly // ======================================================================== public static implicit operator BaseAbility(RuntimeAbilityInstance instance) { return instance.sourceAbility; } public static implicit operator RuntimeAbilityInstance(BaseAbility ability) { return new RuntimeAbilityInstance(ability); } }