using System; using System.Collections.Generic; using UnityEngine; /// /// For our upgrade system, we have 3 ways that an upgrade should work /// * Upgrade should only last for the next level /// * Upgrade should last forever /// * Upgrade needs to be readded at the start of each level. /// In my game, we can divide upgrades into groups based on events that they want /// to listen to. /// The RuntimeModifierManager child classes will define a function that listens /// to that event to do something with it, like block damage /// We use this RuntimeModifierManager class because it allows us to support the /// 3 types of upgrades we want without having to define it for each class. /// public abstract class RuntimeModifierManager : MonoBehaviour where TModifier : class, IRuntimeModifier { protected readonly List> activeModifiers = new(); protected readonly List> renewingModifiers = new(); protected readonly List> permanentModifiers = new(); private bool _initialized; protected virtual void OnDisable() { Unsubscribe(); _initialized = false; } protected void Initialize() { if (_initialized) return; _initialized = true; GameStateManager.OnStateChanged += HandleStateChanged; Subscribe(); } protected virtual void Subscribe() { } protected virtual void Unsubscribe() { GameStateManager.OnStateChanged -= HandleStateChanged; } private void HandleStateChanged(GameState previousState,GameState newState) { if (newState == GameStateManager.LevelStartState) { RestoreRenewingModifiers(); } if (newState == GameStateManager.LevelEndState) { RemoveTemporaryModifiers(); } OnGameStateChanged(previousState, newState); if(newState == GameState.DeathSequence) { ClearAllModifiers(); } } protected virtual void OnGameStateChanged(GameState previousState,GameState newState){} /// /// Adds a temporary modifier that is removed at level end. /// Its inventory card is also removed when it expires. /// public virtual void Register(TModifier modifier,UpgradeBase sourceUpgrade) { if (modifier == null) return; Initialize(); RuntimeModifierEntry entry = new RuntimeModifierEntry( modifier, sourceUpgrade, removeCardWhenExpired: true ); AddActiveModifier(entry); } /// /// Adds a modifier template that creates a fresh active modifier /// at the beginning of each level. /// Its inventory card remains between levels. /// public virtual void RegisterRenewing(TModifier modifier,UpgradeBase sourceUpgrade) { if (modifier == null) return; Initialize(); RuntimeModifierEntry entry = new RuntimeModifierEntry( modifier, sourceUpgrade, removeCardWhenExpired: false ); renewingModifiers.Add(entry); } /// /// Adds a modifier immediately and keeps it active between levels. /// Its inventory card remains. /// public virtual void RegisterPermanent(TModifier modifier,UpgradeBase sourceUpgrade = null) { if (modifier == null) return; Initialize(); RuntimeModifierEntry entry = new RuntimeModifierEntry( modifier, sourceUpgrade, removeCardWhenExpired: false ); permanentModifiers.Add(entry); AddActiveModifier(entry); } /// /// Renewing modifiers only exist while the level is active. They get removed at /// the end of the level, and restored at the begining of the next level. /// Use this for things like once per level effects /// protected virtual void RestoreRenewingModifiers() { foreach (RuntimeModifierEntry template in renewingModifiers) { TModifier clone = template.Modifier.Clone() as TModifier; if (clone == null) continue; RuntimeModifierEntry activeEntry = new RuntimeModifierEntry( clone, template.SourceUpgrade, removeCardWhenExpired: false ); AddActiveModifier(activeEntry); } } /// /// Renewing modifiers only exist while the level is active. They get removed at /// the end of the level. When the midifier is called to be removed, the Destroy() /// function is called. Use this Destroy() for cleaning up the modifier, like unsubscribing /// from events. /// protected virtual void RemoveTemporaryModifiers() { for (int i = activeModifiers.Count - 1; i >= 0; i--) { RuntimeModifierEntry entry = activeModifiers[i]; // Permanent modifiers stay active and keep their runtime state. if (permanentModifiers.Contains(entry)) continue; entry.Modifier.Destroy(); activeModifiers.RemoveAt(i); // Renewing modifier instances end each level, but their // inventory cards remain because they return next level. if (entry.RemoveCardWhenExpired && entry.SourceUpgrade != null) { ShopManager.Instance.RemoveUpgrade(entry.SourceUpgrade); } } SortActiveModifiers(); } protected void SortActiveModifiers() { activeModifiers.Sort((a, b) =>b.Modifier.Priority.CompareTo(a.Modifier.Priority)); } /// /// This is how the modifier actually gets added to the activeModifiers. When /// a modifier is activated we call its OnActivate(). Use OnActivte() as essentially /// a start function. /// protected virtual void AddActiveModifier(RuntimeModifierEntry entry) { if (entry == null || entry.Modifier == null) return; activeModifiers.Add(entry); OnModifierActivated(entry.Modifier); entry.Modifier.OnActivate(); SortActiveModifiers(); } protected virtual void OnModifierActivated(TModifier modifier) { } protected void ExpireModifier(RuntimeModifierEntry entry) { if (entry == null) return; entry.Modifier.Destroy(); activeModifiers.Remove(entry); if (entry.RemoveCardWhenExpired && entry.SourceUpgrade != null) { ShopManager.Instance.RemoveUpgrade(entry.SourceUpgrade); } } protected virtual void OnDestroy() { Unsubscribe(); DestroyAllModifiers(); } protected void DestroyAllModifiers() { foreach (var entry in activeModifiers) { entry.Modifier?.Destroy(); } permanentModifiers.Clear(); renewingModifiers.Clear(); activeModifiers.Clear(); } public virtual void ClearAllModifiers() { foreach (RuntimeModifierEntry entry in activeModifiers) { entry.Modifier.Destroy(); } activeModifiers.Clear(); renewingModifiers.Clear(); permanentModifiers.Clear(); } }