using BulletHellTemplate.Core.Events; using Cysharp.Threading.Tasks; using System; using System.Collections.Generic; using System.Threading; using UnityEngine; namespace BulletHellTemplate { /// /// Handles temporary or passive characterBuffsComponent that modify character characterStatsComponent. /// public partial class CharacterBuffsComponent : MonoBehaviour { private CharacterStatsComponent characterStatsComponent; private CharacterControllerComponent characterControllerComponent; private CharacterEntity characterOwner; // Active buffs and debuffs tracking private readonly List activeBuffs = new(); private readonly List activeDebuffs = new(); private void Awake() { characterStatsComponent = GetComponent(); characterControllerComponent = GetComponent(); characterOwner = GetComponent(); } public void Initialize() { if (characterStatsComponent == null) characterStatsComponent = GetComponent(); if (characterControllerComponent == null) characterControllerComponent = GetComponent(); if (characterOwner == null) characterOwner = GetComponent(); } #region Move Speed /// /// Applies a temporary move speed buff (increases speed). /// public void ApplyMoveSpeedBuff(float amount, float duration) { float buffAmount = Mathf.Abs(amount); string id = Guid.NewGuid().ToString(); characterStatsComponent.AlterMoveSpeed(buffAmount); RemoveModifierAfterDelayAsync(id, StatType.MoveSpeed, BuffCategory.Buff, buffAmount, duration, this.GetCancellationTokenOnDestroy()).Forget(); activeBuffs.Add(new ActiveBuff(id, StatType.MoveSpeed, BuffCategory.Buff, buffAmount, duration)); EventBus.Publish(new BuffReceivedEvent(characterOwner, StatType.MoveSpeed, buffAmount, duration, activeBuffs.Count)); } /// /// Applies a temporary move speed debuff (decreases speed). /// public void ApplyMoveSpeedDebuff(float amount, float duration) { float debuffAmount = -Mathf.Abs(amount); string id = Guid.NewGuid().ToString(); characterStatsComponent.AlterMoveSpeed(debuffAmount); RemoveModifierAfterDelayAsync(id, StatType.MoveSpeed, BuffCategory.Debuff, debuffAmount, duration, this.GetCancellationTokenOnDestroy()).Forget(); activeDebuffs.Add(new ActiveBuff(id, StatType.MoveSpeed, BuffCategory.Debuff, debuffAmount, duration)); EventBus.Publish(new DebuffReceivedEvent(characterOwner, StatType.MoveSpeed, debuffAmount, duration, activeDebuffs.Count)); } #endregion #region Attack Speed /// /// Applies a temporary attack speed buff (increases attack speed). /// public void ApplyAttackSpeedBuff(float amount, float duration) { float buffAmount = Mathf.Abs(amount); string id = Guid.NewGuid().ToString(); characterStatsComponent.AlterAttackSpeed(buffAmount); RemoveModifierAfterDelayAsync(id, StatType.AttackSpeed, BuffCategory.Buff, buffAmount, duration, this.GetCancellationTokenOnDestroy()).Forget(); activeBuffs.Add(new ActiveBuff(id, StatType.AttackSpeed, BuffCategory.Buff, buffAmount, duration)); EventBus.Publish(new BuffReceivedEvent(characterOwner, StatType.AttackSpeed, buffAmount, duration, activeBuffs.Count)); } /// /// Applies a temporary attack speed debuff (decreases attack speed). /// public void ApplyAttackSpeedDebuff(float amount, float duration) { float debuffAmount = -Mathf.Abs(amount); string id = Guid.NewGuid().ToString(); characterStatsComponent.AlterAttackSpeed(debuffAmount); RemoveModifierAfterDelayAsync(id, StatType.AttackSpeed, BuffCategory.Debuff, debuffAmount, duration, this.GetCancellationTokenOnDestroy()).Forget(); activeDebuffs.Add(new ActiveBuff(id, StatType.AttackSpeed, BuffCategory.Debuff, debuffAmount, duration)); EventBus.Publish(new DebuffReceivedEvent(characterOwner, StatType.AttackSpeed, debuffAmount, duration, activeDebuffs.Count)); } #endregion #region Defense /// /// Applies a temporary defense buff (increases defense). /// public void ApplyDefenseBuff(float amount, float duration) { float buffAmount = Mathf.Abs(amount); string id = Guid.NewGuid().ToString(); characterStatsComponent.AlterDefense(buffAmount); RemoveModifierAfterDelayAsync(id, StatType.Defense, BuffCategory.Buff, buffAmount, duration, this.GetCancellationTokenOnDestroy()).Forget(); activeBuffs.Add(new ActiveBuff(id, StatType.Defense, BuffCategory.Buff, buffAmount, duration)); EventBus.Publish(new BuffReceivedEvent(characterOwner, StatType.Defense, buffAmount, duration, activeBuffs.Count)); } /// /// Applies a temporary defense debuff (decreases defense). /// public void ApplyDefenseDebuff(float amount, float duration) { float debuffAmount = -Mathf.Abs(amount); string id = Guid.NewGuid().ToString(); characterStatsComponent.AlterDefense(debuffAmount); RemoveModifierAfterDelayAsync(id, StatType.Defense, BuffCategory.Debuff, debuffAmount, duration, this.GetCancellationTokenOnDestroy()).Forget(); activeDebuffs.Add(new ActiveBuff(id, StatType.Defense, BuffCategory.Debuff, debuffAmount, duration)); EventBus.Publish(new DebuffReceivedEvent(characterOwner, StatType.Defense, debuffAmount, duration, activeDebuffs.Count)); } #endregion #region Damage /// /// Applies a temporary damage buff (increases damage). /// public void ApplyDamageBuff(float amount, float duration) { float buffAmount = Mathf.Abs(amount); string id = Guid.NewGuid().ToString(); characterStatsComponent.AlterDamage(buffAmount); RemoveModifierAfterDelayAsync(id, StatType.Damage, BuffCategory.Buff, buffAmount, duration, this.GetCancellationTokenOnDestroy()).Forget(); activeBuffs.Add(new ActiveBuff(id, StatType.Damage, BuffCategory.Buff, buffAmount, duration)); EventBus.Publish(new BuffReceivedEvent(characterOwner, StatType.Damage, buffAmount, duration, activeBuffs.Count)); } /// /// Applies a temporary damage debuff (decreases damage). /// public void ApplyDamageDebuff(float amount, float duration) { float debuffAmount = -Mathf.Abs(amount); string id = Guid.NewGuid().ToString(); characterStatsComponent.AlterDamage(debuffAmount); RemoveModifierAfterDelayAsync(id, StatType.Damage, BuffCategory.Debuff, debuffAmount, duration, this.GetCancellationTokenOnDestroy()).Forget(); activeDebuffs.Add(new ActiveBuff(id, StatType.Damage, BuffCategory.Debuff, debuffAmount, duration)); EventBus.Publish(new DebuffReceivedEvent(characterOwner, StatType.Damage, debuffAmount, duration, activeDebuffs.Count)); } #endregion #region Shield /// /// Applies shield to the character. If temporary, acts as a buff and reverts after duration. /// public void ApplyShield(int amount, float duration = 0f, bool isTemporary = false) { if (amount <= 0 || characterStatsComponent == null) return; characterStatsComponent.AddShield(amount); if (isTemporary && duration > 0f) { string id = Guid.NewGuid().ToString(); StatType statType = StatType.Shield; BuffCategory buffCategory = BuffCategory.Buff; RemoveShieldAfterDelayAsync(id, statType, buffCategory, amount, duration, this.GetCancellationTokenOnDestroy()).Forget(); activeBuffs.Add(new ActiveBuff(id, statType, buffCategory, amount, duration)); int remainingBuffsCount = activeBuffs.Count; EventBus.Publish(new BuffReceivedEvent(characterOwner, statType, amount, duration, remainingBuffsCount)); } } /// /// Async task that waits for the specified duration and then removes the shield buff. /// private async UniTask RemoveShieldAfterDelayAsync( string id, StatType statType, BuffCategory buffCategory, int amount, float duration, CancellationToken token = default) { float elapsed = 0f; while (elapsed < duration) { token.ThrowIfCancellationRequested(); if (GameplayManager.Singleton.IsPaused()) { await UniTask.WaitWhile(() => GameplayManager.Singleton.IsPaused(), cancellationToken: token); } elapsed += Time.deltaTime; await UniTask.Yield(PlayerLoopTiming.Update, token); } characterStatsComponent.RemoveShield(amount); activeBuffs.RemoveAll(b => b.Id == id); int remainingBuffsCount = activeBuffs.Count; EventBus.Publish(new BuffRemovedEvent(characterOwner, statType, remainingBuffsCount)); } #endregion public void ApplyHealHp(int amount) { amount = Mathf.Abs(amount); characterStatsComponent.HealHP(amount); } /// /// Waits for a duration (while handling pause) and then removes the stat modifier. /// /// Unique ID of the modifier. /// Stat affected by the modifier. /// Type of modifier (Buff or Debuff). /// Value applied by the modifier. /// Duration in seconds before removal. /// Optional cancellation token. private async UniTask RemoveModifierAfterDelayAsync( string id, StatType statType, BuffCategory buffCategory, float amount, float duration, CancellationToken token = default) { float elapsed = 0f; while (elapsed < duration) { token.ThrowIfCancellationRequested(); if (GameplayManager.Singleton.IsPaused()) await UniTask.WaitWhile(() => GameplayManager.Singleton.IsPaused(), cancellationToken: token); elapsed += Time.deltaTime; await UniTask.Yield(PlayerLoopTiming.Update, token); } RemoveModifier(statType, amount); if (buffCategory == BuffCategory.Buff) { activeBuffs.RemoveAll(b => b.Id == id); int remaining = activeBuffs.Count; EventBus.Publish(new BuffRemovedEvent(characterOwner, statType, remaining)); } else { activeDebuffs.RemoveAll(b => b.Id == id); int remaining = activeDebuffs.Count; EventBus.Publish(new DebuffRemovedEvent(characterOwner, statType, remaining)); } } public void RemoveModifier(StatType statType, float amount) { switch (statType) { case StatType.MoveSpeed: characterStatsComponent.AlterMoveSpeed(-amount); break; case StatType.AttackSpeed: characterStatsComponent.AlterAttackSpeed(-amount); break; case StatType.Damage: characterStatsComponent.AlterDamage(-amount); break; case StatType.Defense: characterStatsComponent.AlterDefense(-amount); break; } } } }