// ============================================================================
// 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