From 00b86fa97c916b0caceb080d7e568df24f4aa2b8 Mon Sep 17 00:00:00 2001 From: Pedro Gomes Date: Fri, 13 Jun 2025 21:56:16 +0100 Subject: [PATCH] Runtime ability system --- .../Panoramics/FS002/FS002_Night.mat | 2 +- .../AbilityInstance/AbilityMigrationHelper.cs | 52 +++ .../AbilityMigrationHelper.cs.meta | 11 + .../AbilityInstance/AbilityModifier.cs | 60 ++++ .../AbilityInstance/AbilityModifier.cs.meta | 11 + .../AbilityInstance/AbilityUsageExamples.cs | 59 ++++ .../AbilityUsageExamples.cs.meta | 11 + .../AbilityInstance/Extensions.meta | 8 + .../Extensions/ChainLightningBehavior.cs | 74 +++++ .../Extensions/ChainLightningBehavior.cs.meta | 11 + .../Extensions/ExplodeOnHitBehavior.cs | 58 ++++ .../Extensions/ExplodeOnHitBehavior.cs.meta | 11 + .../Extensions/LifeStealBehavior.cs | 34 ++ .../Extensions/LifeStealBehavior.cs.meta | 11 + .../AbilityInstance/RuntimeAbilityInstance.cs | 308 ++++++++++++++++++ .../RuntimeAbilityInstance.cs.meta | 11 + .../AbilityInstance/RuntimeBehavior.cs | 19 ++ .../AbilityInstance/RuntimeBehavior.cs.meta | 11 + 18 files changed, 761 insertions(+), 1 deletion(-) create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/AbilityMigrationHelper.cs create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/AbilityMigrationHelper.cs.meta create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/AbilityModifier.cs create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/AbilityModifier.cs.meta create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/AbilityUsageExamples.cs create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/AbilityUsageExamples.cs.meta create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/Extensions.meta create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ChainLightningBehavior.cs create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ChainLightningBehavior.cs.meta create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ExplodeOnHitBehavior.cs create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ExplodeOnHitBehavior.cs.meta create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/LifeStealBehavior.cs create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/LifeStealBehavior.cs.meta create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeAbilityInstance.cs create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeAbilityInstance.cs.meta create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeBehavior.cs create mode 100644 Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeBehavior.cs.meta diff --git a/Assets/Fantasy Skybox FREE/Panoramics/FS002/FS002_Night.mat b/Assets/Fantasy Skybox FREE/Panoramics/FS002/FS002_Night.mat index 1753aa7d..3dde5691 100644 --- a/Assets/Fantasy Skybox FREE/Panoramics/FS002/FS002_Night.mat +++ b/Assets/Fantasy Skybox FREE/Panoramics/FS002/FS002_Night.mat @@ -78,7 +78,7 @@ Material: - _Mode: 0 - _OcclusionStrength: 1 - _Parallax: 0.02 - - _Rotation: 6.6876717 + - _Rotation: 7.162766 - _SmoothnessTextureChannel: 0 - _SpecularHighlights: 1 - _SrcBlend: 1 diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityMigrationHelper.cs b/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityMigrationHelper.cs new file mode 100644 index 00000000..20fae4f1 --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityMigrationHelper.cs @@ -0,0 +1,52 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +// ============================================================================ +// 5. EASY MIGRATION HELPER - Update Your Existing Components +// ============================================================================ + +/// +/// Helper to easily convert your existing ability references +/// Just change "BaseAbility" to "RuntimeAbilityInstance" in your existing scripts +/// +public static class AbilityMigrationHelper +{ + /// + /// Convert a BaseAbility to RuntimeAbilityInstance + /// + public static RuntimeAbilityInstance ToRuntime(this BaseAbility ability) + { + return new RuntimeAbilityInstance(ability); + } + + /// + /// Create a modified version of an ability + /// + public static RuntimeAbilityInstance CreateModifiedVersion(BaseAbility baseAbility, params AbilityModifier[] modifiers) + { + var runtime = new RuntimeAbilityInstance(baseAbility); + + foreach (var modifier in modifiers) + { + runtime.AddModifier(modifier); + } + + return runtime; + } + + /// + /// Create legendary version with special behaviors + /// + public static RuntimeAbilityInstance CreateLegendaryVersion(BaseAbility baseAbility, params RuntimeBehavior[] behaviors) + { + var runtime = new RuntimeAbilityInstance(baseAbility); + + foreach (var behavior in behaviors) + { + runtime.AddBehavior(behavior); + } + + return runtime; + } +} \ No newline at end of file diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityMigrationHelper.cs.meta b/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityMigrationHelper.cs.meta new file mode 100644 index 00000000..9fd2322a --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityMigrationHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fddd81fa3189fcf43a2207ea59bdae95 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityModifier.cs b/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityModifier.cs new file mode 100644 index 00000000..9e87ae2f --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityModifier.cs @@ -0,0 +1,60 @@ +// ============================================================================ +// 2. ABILITY MODIFIER SYSTEM +// ============================================================================ + +using UnityEngine; + +[System.Serializable] +public class AbilityModifier +{ + [Header("Resource Cost Modifiers")] + public float manaCostMultiplier = 1f; + public float manaCostFlat = 0f; + public float healthCostMultiplier = 1f; + public float healthCostFlat = 0f; + public float classResourceCostMultiplier = 1f; + public float classResourceCostFlat = 0f; + + [Header("Timing Modifiers")] + public float castTimeMultiplier = 1f; + public float cooldownMultiplier = 1f; + + [Header("Effect Modifiers")] + public float damageMultiplier = 1f; + public float healingMultiplier = 1f; + public float durationMultiplier = 1f; + public float rangeMultiplier = 1f; + + [Header("Meta Info")] + public string modifierName = "Unnamed Modifier"; + public string description = ""; + public float duration = -1f; // -1 = permanent + + // Factory methods for common modifiers + public static AbilityModifier CreateDamageBoost(float multiplier, string name = "Damage Boost") + { + return new AbilityModifier + { + damageMultiplier = multiplier, + modifierName = name + }; + } + + public static AbilityModifier CreateCooldownReduction(float reductionPercent, string name = "Cooldown Reduction") + { + return new AbilityModifier + { + cooldownMultiplier = 1f - (reductionPercent / 100f), + modifierName = name + }; + } + + public static AbilityModifier CreateManaCostReduction(float reductionPercent, string name = "Mana Cost Reduction") + { + return new AbilityModifier + { + manaCostMultiplier = 1f - (reductionPercent / 100f), + modifierName = name + }; + } +} \ No newline at end of file diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityModifier.cs.meta b/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityModifier.cs.meta new file mode 100644 index 00000000..6433510b --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityModifier.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 759877a48198a3549b2a51623fe20a7c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityUsageExamples.cs b/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityUsageExamples.cs new file mode 100644 index 00000000..e6637caf --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityUsageExamples.cs @@ -0,0 +1,59 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +// ============================================================================ +// 6. USAGE EXAMPLES - How to Use in Your Existing Code +// ============================================================================ + +public class AbilityUsageExamples : MonoBehaviour +{ + [Header("Your Existing Abilities")] + public BaseAbility fireballAbility; + public BaseAbility healingAbility; + + // These can now be RuntimeAbilityInstances! + private RuntimeAbilityInstance runtimeFireball; + private RuntimeAbilityInstance runtimeHealing; + + private void Start() + { + // Convert your existing abilities + runtimeFireball = fireballAbility.ToRuntime(); + runtimeHealing = healingAbility.ToRuntime(); + + // Example: Add legendary item effect + AddLegendaryFireballEffect(); + } + + private void AddLegendaryFireballEffect() + { + // This is how you'd handle your legendary item! + var explosionBehavior = new ExplodeOnHitBehavior + { + explosionRadius = 5f, + explosionDamage = 50f + }; + + runtimeFireball.AddBehavior(explosionBehavior); + + // Also add a damage boost + var damageBoost = AbilityModifier.CreateDamageBoost(1.5f, "Legendary Damage"); + runtimeFireball.AddModifier(damageBoost); + } + + // Your existing ability usage code works exactly the same! + public void CastFireball() + { + var user = GetComponent(); + + // This works exactly like your old BaseAbility.Execute() + runtimeFireball.Execute(user); + } + + public void CastFireballAtTarget(Transform target) + { + var user = GetComponent(); + runtimeFireball.Execute(user, target); + } +} \ No newline at end of file diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityUsageExamples.cs.meta b/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityUsageExamples.cs.meta new file mode 100644 index 00000000..37a37df9 --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/AbilityUsageExamples.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d1d5a488d66722a468d8af337e46e1e1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions.meta b/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions.meta new file mode 100644 index 00000000..60f65cc0 --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 30b6d22e7928d8047a51992b3fda64b6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ChainLightningBehavior.cs b/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ChainLightningBehavior.cs new file mode 100644 index 00000000..06e7178a --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ChainLightningBehavior.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using UnityEngine; + +public class ChainLightningBehavior : RuntimeBehavior +{ + public int maxChains = 3; + public float chainRange = 8f; + public float damageMultiplier = 0.5f; // Each chain does 50% of original damage + + public ChainLightningBehavior() + { + Trigger = BehaviorTrigger.OnHit; + BehaviorName = "Chain Lightning"; + } + + public override void Execute(RuntimeAbilityInstance ability, Taggable user, Transform target, Vector3 point) + { + if (target == null) return; + + var currentTarget = target.GetComponent(); + var chainedTargets = new List { currentTarget }; + + for (int i = 0; i < maxChains; i++) + { + var nextTarget = FindNextChainTarget(currentTarget.transform.position, chainedTargets); + if (nextTarget == null) break; + + chainedTargets.Add(nextTarget); + + // Apply reduced damage + var health = nextTarget.GetComponent(); + if (health != null) + { + float chainDamage = CalculateChainDamage(ability, i + 1); + health.ChangeValue(-chainDamage); + } + + currentTarget = nextTarget; + } + } + + private Taggable FindNextChainTarget(Vector3 position, List excludeTargets) + { + var colliders = Physics.OverlapSphere(position, chainRange); + + foreach (var collider in colliders) + { + var target = collider.GetComponent(); + if (target != null && !excludeTargets.Contains(target)) + { + return target; + } + } + + return null; + } + + private float CalculateChainDamage(RuntimeAbilityInstance ability, int chainNumber) + { + // This would need to be adapted to your damage system + // For now, just return a reduced amount + return 50f * Mathf.Pow(damageMultiplier, chainNumber); + } + + public override RuntimeBehavior Clone() + { + return new ChainLightningBehavior + { + maxChains = this.maxChains, + chainRange = this.chainRange, + damageMultiplier = this.damageMultiplier + }; + } +} \ No newline at end of file diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ChainLightningBehavior.cs.meta b/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ChainLightningBehavior.cs.meta new file mode 100644 index 00000000..8311b6c8 --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ChainLightningBehavior.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3d7ec80971f1d2d4b93d7395e6dcf78c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ExplodeOnHitBehavior.cs b/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ExplodeOnHitBehavior.cs new file mode 100644 index 00000000..fa6dad15 --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ExplodeOnHitBehavior.cs @@ -0,0 +1,58 @@ +// ============================================================================ +// 4. COMMON RUNTIME BEHAVIORS - Your Legendary Item Effects! +// ============================================================================ + +using UnityEngine; + +public class ExplodeOnHitBehavior : RuntimeBehavior +{ + public GameObject explosionPrefab; + public float explosionRadius = 5f; + public float explosionDamage = 30f; + public LayerMask targetLayers = -1; + + public ExplodeOnHitBehavior() + { + Trigger = BehaviorTrigger.OnHit; + BehaviorName = "Explode on Hit"; + } + + public override void Execute(RuntimeAbilityInstance ability, Taggable user, Transform target, Vector3 point) + { + Vector3 explosionPoint = target != null ? target.position : point; + + // Spawn explosion visual if prefab exists + if (explosionPrefab != null) + { + Object.Instantiate(explosionPrefab, explosionPoint, Quaternion.identity); + } + + // Find targets in explosion radius + var colliders = Physics.OverlapSphere(explosionPoint, explosionRadius, targetLayers); + + foreach (var collider in colliders) + { + var explosionTarget = collider.GetComponent(); + if (explosionTarget != null && explosionTarget != user) + { + // Apply explosion damage + var health = explosionTarget.GetComponent(); + if (health != null) + { + health.ChangeValue(-explosionDamage); + } + } + } + } + + public override RuntimeBehavior Clone() + { + return new ExplodeOnHitBehavior + { + explosionPrefab = this.explosionPrefab, + explosionRadius = this.explosionRadius, + explosionDamage = this.explosionDamage, + targetLayers = this.targetLayers + }; + } +} \ No newline at end of file diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ExplodeOnHitBehavior.cs.meta b/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ExplodeOnHitBehavior.cs.meta new file mode 100644 index 00000000..c042a187 --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/ExplodeOnHitBehavior.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b92d75ddf02f444fa969dcb95660e60 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/LifeStealBehavior.cs b/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/LifeStealBehavior.cs new file mode 100644 index 00000000..a905bc9e --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/LifeStealBehavior.cs @@ -0,0 +1,34 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public class LifeStealBehavior : RuntimeBehavior +{ + public float lifeStealPercent = 0.15f; // 15% life steal + + public LifeStealBehavior() + { + Trigger = BehaviorTrigger.OnHit; + BehaviorName = "Life Steal"; + } + + public override void Execute(RuntimeAbilityInstance ability, Taggable user, Transform target, Vector3 point) + { + // This would need to track the damage dealt to calculate life steal + // For now, just heal a fixed amount + var userHealth = user.GetComponent(); + if (userHealth != null) + { + float healAmount = 20f * lifeStealPercent; // Placeholder calculation + userHealth.ChangeValue(healAmount); + } + } + + public override RuntimeBehavior Clone() + { + return new LifeStealBehavior + { + lifeStealPercent = this.lifeStealPercent + }; + } +} \ No newline at end of file diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/LifeStealBehavior.cs.meta b/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/LifeStealBehavior.cs.meta new file mode 100644 index 00000000..479d7e4c --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/Extensions/LifeStealBehavior.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 091400d6a45eaac4782ee5636a0f0d1f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeAbilityInstance.cs b/Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeAbilityInstance.cs new file mode 100644 index 00000000..d1586ed2 --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeAbilityInstance.cs @@ -0,0 +1,308 @@ +// ============================================================================ +// RUNTIME ABILITY WRAPPER - DROP-IN REPLACEMENT FOR BaseAbility +// ============================================================================ + +using System; +using System.Collections.Generic; +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; + } + + // ======================================================================== + // MODIFIER SYSTEM + // ======================================================================== + + public void AddModifier(AbilityModifier modifier) + { + activeModifiers.Add(modifier); + RecalculateModifiedValues(); + } + + public void RemoveModifier(AbilityModifier modifier) + { + activeModifiers.Remove(modifier); + RecalculateModifiedValues(); + } + + public void RemoveAllModifiers() + { + activeModifiers.Clear(); + RecalculateModifiedValues(); + } + + 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); + } + } + } + + // ======================================================================== + // 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); + } +} \ No newline at end of file diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeAbilityInstance.cs.meta b/Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeAbilityInstance.cs.meta new file mode 100644 index 00000000..99b31578 --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeAbilityInstance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 63ca0a6b1e4e799418c4e8dd0cf4ace5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeBehavior.cs b/Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeBehavior.cs new file mode 100644 index 00000000..7e5f2d16 --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeBehavior.cs @@ -0,0 +1,19 @@ +using UnityEngine; + +public enum BehaviorTrigger +{ + PreCast, // Before ability executes + PostCast, // After ability executes + OnHit, // When ability hits target + OnKill, // When ability kills target + OnCrit // When ability crits +} + +public abstract class RuntimeBehavior +{ + public BehaviorTrigger Trigger { get; set; } + public string BehaviorName { get; set; } + + public abstract void Execute(RuntimeAbilityInstance ability, Taggable user, Transform target, Vector3 point); + public abstract RuntimeBehavior Clone(); +} diff --git a/Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeBehavior.cs.meta b/Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeBehavior.cs.meta new file mode 100644 index 00000000..421cd5f0 --- /dev/null +++ b/Assets/Scripts/AbilitySystem/AbilityInstance/RuntimeBehavior.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0a8f3389f97c1f343ba02dc9b50a1848 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: