using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.SceneManagement; public class BattleController : MonoBehaviour, IBattleEffectApplier { private bool STABLE_HAND_STYLE = true; // Inspector public public GameObject uiCanvas; public BattlePlayerControls battlePlayerControls; public EnergyUI heroEnergyUI; public EnergyUI monsterEnergyUI; public BattleSprites battleSprites; // Private private BattleState state; private HandPlayability heroHandPlayability; private HandPlayability monsterHandPlayability; private TurnIntentions monsterIntentions; private IBattleEffectResolver effectResolver; private delegate void BattleStateChangedAction(); private event BattleStateChangedAction OnBattleStateChanged; void Awake() { BattleParameters parameters = BattleSetup.RetrieveParameters(); if (parameters == null) { Debug.Log("Using default battle"); parameters = CheatBattle.CreateParameters(); } this.state = new BattleState(parameters); this.heroEnergyUI.Init(this.state.HeroEnergy); this.monsterEnergyUI.Init(this.state.MonsterEnergy); this.battleSprites.Init(this.state.Heroes, this.state.Monsters, this.state); this.heroHandPlayability = new HandPlayability( this.state, this.state.HeroEnergy, TeamChoice.Heroes, this.state.HeroDeck); this.OnBattleStateChanged += this.heroHandPlayability.NotifyBattleStateChanged; this.monsterHandPlayability = new HandPlayability( this.state, this.state.MonsterEnergy, TeamChoice.Monsters, this.state.MonsterDeck); this.OnBattleStateChanged += this.monsterHandPlayability.NotifyBattleStateChanged; this.monsterIntentions = new TurnIntentions( this.state.MonsterDeck, this.state.MonsterEnergy, this.monsterHandPlayability); this.battlePlayerControls.Init( this.state.HeroDeck, this.state.MonsterDeck, this.state.PlayArea, this.state.InsightLevel, this.heroHandPlayability, this.monsterHandPlayability, this.monsterIntentions, this.uiCanvas.transform.localScale.x); this.effectResolver = new EffectResolver(this.state); } // Start is called before the first frame update void Start() { StartCoroutine(RunBattleRounds()); CheatBattle cheat = new GameObject("CheatBattle").AddComponent(); cheat.OnWinCode += delegate { BattleResult.FinishBattle(new BattleResult(true)); }; cheat.OnInsightCode += delegate { this.state.InsightLevel.Modify(+5); }; cheat.OnOneHPCode += delegate { foreach (BattleMonster m in this.state.Monsters) { if (m.IsAlive()) { m.DealDamage(m.GetHP() - 1); } } OnBattleStateChanged(); }; } // Update is called once per frame void Update() { } private IEnumerator RunBattleRounds() { if (STABLE_HAND_STYLE) { this.state.HeroDeck.DrawCards(6); this.state.MonsterDeck.DrawCards(6); } while (true) { yield return RunBattleRound(); } } private IEnumerator RunBattleRound() { this.state.Clock.AdvanceRound(); yield return this.RunActivatedTriggers(); this.state.HeroEnergy.Replenish(); this.state.MonsterEnergy.Replenish(); if (STABLE_HAND_STYLE) { if (this.state.Clock.Now.RoundNumber > 1) { this.state.HeroDeck.DrawCards(3); this.state.MonsterDeck.DrawCards(3); } } else { this.state.HeroDeck.DrawCards(5); this.state.MonsterDeck.DrawCards(5); } yield return RunHeroTurn(); yield return RunMonsterTurn(); this.state.Clock.AdvanceToStep(RoundStep.EndOfRound); yield return this.RunActivatedTriggers(); } private IEnumerator RunHeroTurn() { // Start of hero turn this.state.Clock.AdvanceToStep(RoundStep.StartOfHeroTurn); this.state.ClearEmotionPlayed(); OnBattleStateChanged(); this.monsterIntentions.ResumeIntentions(); foreach (BattleHero hero in this.state.Heroes) { hero.ResetBlock(); } OnBattleStateChanged(); yield return this.RunActivatedTriggers(); // Heroes act until they pass while (true) { PlayerAction playerAction = this.battlePlayerControls.RetrievePlayerAction(); if (playerAction != null) { if (playerAction.IsTurnPassed()) { break; } else { HeroCard card = playerAction.GetCardUsed(); yield return UseHeroCard(card); // TODO: Implement a stack/queue to resolve triggers in order. // Do this after the card resolution is over to avoid reentraint effects. yield return this.RunActivatedTriggers(); CheckState(); OnBattleStateChanged(); } } else { yield return null; } } // End of hero turn yield return this.state.PlayArea.UseHeroDefaults(this.state.Heroes, this.state.ParticipantActivity, UseHeroDefault); // TODO: Implement a stack/queue to resolve triggers in order. // Do this after the card resolution is over to avoid reentraint effects. yield return this.RunActivatedTriggers(); CheckState(); OnBattleStateChanged(); if (STABLE_HAND_STYLE) { this.state.HeroDeck.DiscardInExcessOf(3); } else { this.state.HeroDeck.DiscardHand(); } foreach (BattleHero hero in this.state.Heroes) { hero.TickStatuses(); } this.state.Clock.AdvanceToStep(RoundStep.EndOfHeroTurn); yield return this.RunActivatedTriggers(); OnBattleStateChanged(); this.monsterIntentions.PauseIntentions(); } private IEnumerator RunMonsterTurn() { yield return new WaitForSeconds(0.5f); // Start of monster turn this.state.Clock.AdvanceToStep(RoundStep.StartOfMonsterTurn); this.state.ClearEmotionPlayed(); foreach (BattleMonster monster in this.state.Monsters) { monster.ResetBlock(); } OnBattleStateChanged(); yield return this.RunActivatedTriggers(); yield return new WaitForSeconds(0.5f); // Monsters play an emotion List monsterHand = this.state.MonsterDeck.GetHandPile(); for (int i = 0; i < monsterHand.Count; i++) { MonsterCard monsterCard = monsterHand[i]; if (monsterCard.Type == CardType.Emotion && CanUseMonsterCard(monsterCard)) { yield return UseMonsterCard(monsterCard); // TODO: Implement a stack/queue to resolve triggers in order. // Do this after the card resolution is over to avoid reentraint effects. yield return this.RunActivatedTriggers(); CheckState(); OnBattleStateChanged(); } } // Monsters play as many actions from the top as possible bool anyCardPlayed = true; while (anyCardPlayed) { anyCardPlayed = false; MonsterCard monsterCard = FirstMonsterActionToPlay(); if (monsterCard != null) { yield return UseMonsterCard(monsterCard); anyCardPlayed = true; // TODO: Implement a stack/queue to resolve triggers in order. // Do this after the card resolution is over to avoid reentraint effects. yield return this.RunActivatedTriggers(); CheckState(); OnBattleStateChanged(); } } // End of monster turn if (STABLE_HAND_STYLE) { this.state.MonsterDeck.DiscardInExcessOf(3); } else { this.state.MonsterDeck.DiscardHand(); } foreach (BattleMonster monster in this.state.Monsters) { monster.TickStatuses(); } this.state.Clock.AdvanceToStep(RoundStep.EndOfMonsterTurn); OnBattleStateChanged(); yield return this.RunActivatedTriggers(); } private IEnumerator RunActivatedTriggers() { yield return this.state.TriggerManager.RunActivatedTriggers( this, this.state, this.battlePlayerControls); } private MonsterCard FirstMonsterActionToPlay() { List monsterHand = monsterHand = this.state.MonsterDeck.GetHandPile(); for (int i = 0; i < monsterHand.Count; i++) { MonsterCard monsterCard = monsterHand[i]; if (monsterCard.Type != CardType.Emotion && CanUseMonsterCard(monsterCard)) { return monsterCard; } } return null; } public IEnumerator UseHeroCard(HeroCard card) { if (!CanUseHeroCard(card)) { throw new System.InvalidOperationException("Cannot use card " + card.GetDisplayName()); } this.state.HeroEnergy.PayForCard(card); this.state.HeroDeck.RemoveCardFromHand(card); this.state.ParticipantActivity.NotifyParticipantUsedCard(card.Owner, card); yield return card.Use(this, this.state, this.battlePlayerControls); if (card.Type == CardType.Default) { // Already added to playArea by effect } else if (card.IsPermanent()) { this.state.PlayArea.AddPlayedHeroPermanent(card); } else if (card.IsEphemeral()) { this.state.HeroDeck.MoveCardToOblivion(card); } else { this.state.HeroDeck.MoveCardToDiscard(card); } CheckState(); OnBattleStateChanged(); } public IEnumerator UseHeroDefault(DefaultHeroCard card) { if (!card.Owner.CanUseDefault()) { yield break; } yield return card.UseDefault(this, this.state, this.battlePlayerControls); CheckState(); OnBattleStateChanged(); } public IEnumerator UseMonsterCard(MonsterCard card) { this.state.MonsterEnergy.PayForCard(card); this.state.MonsterDeck.RemoveCardFromHand(card); this.state.ParticipantActivity.NotifyParticipantUsedCard(card.Owner, card); yield return card.Use(this, this.state); if (card.IsPermanent()) { this.state.PlayArea.AddPlayedMonsterPermanent(card); } else if (card.IsEphemeral()) { this.state.MonsterDeck.MoveCardToOblivion(card); } else { this.state.MonsterDeck.MoveCardToDiscard(card); } yield return new WaitForSeconds(0.5f); CheckState(); OnBattleStateChanged(); } public bool CanUseHeroCard(HeroCard card) { return this.heroHandPlayability.IsPlayableImmediately(card); } public bool CanUseMonsterCard(MonsterCard card) { return this.monsterHandPlayability.IsPlayableImmediately(card); } private void CheckState() { foreach (BattleParticipant p in this.state.Heroes) { p.CheckState(); } foreach (BattleParticipant p in this.state.Monsters) { p.CheckState(); } if (System.Array.TrueForAll(this.state.Heroes, h => !h.IsAlive())) { BattleResult.FinishBattle(new BattleResult(false)); } else if (System.Array.TrueForAll(this.state.Monsters, m => !m.IsAlive())) { BattleResult.FinishBattle(new BattleResult(true)); } } public BattleState State {get => this.state;} // IBattleEffectApplier public void Apply(IBattleEffect effect) { effect.Apply(this.effectResolver); } }