using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; public class BattleController : MonoBehaviour, IBattleEffectApplier, IBattleEffectResolver, MonsterViewpoint { public HeroId[] heroIds; public MonsterId[] monsterIds; public CardId[] cardIds; public MCardId[] mCardIds; public int[] mCardOwnerIndexes; public HeroInfoUI heroUIPrefab; public GameObject uiCanvas; public BattlePlayerControls battlePlayerControls; public EnergyUI heroEnergyUI; public EnergyUI monsterEnergyUI; private HeroCardDatabase heroCardDatabase; private MonsterCardDatabase monsterCardDatabase; private BattleDeck heroDeck; private BattleDeck monsterDeck; private PlayArea playArea; public BattleHero[] heroes; private GameObject[] heroSprites; private HeroInfoUI[] heroUIs; public BattleMonster[] monsters; private GameObject[] monsterSprites; private HeroInfoUI[] monsterUIs; private bool heroTurn; private bool heroPassedTurn; private EnergyPool heroEnergy; private EnergyPool monsterEnergy; void Awake() { this.heroCardDatabase = new HeroCardDatabase(); this.monsterCardDatabase = new MonsterCardDatabase(); this.heroEnergy = new EnergyPool(); this.heroEnergyUI.Init(this.heroEnergy); this.heroEnergy.OnEnergyChanged += this.heroEnergyUI.NotifyEnergyChanged; this.monsterEnergy = new EnergyPool(); this.monsterEnergyUI.Init(this.monsterEnergy); this.monsterEnergy.OnEnergyChanged += this.monsterEnergyUI.NotifyEnergyChanged; heroes = new BattleHero[3]; heroSprites = new GameObject[3]; heroUIs = new HeroInfoUI[3]; for (int i = 0; i < 3; i++) { HeroId id = heroIds[i]; heroes[i] = new BattleHero(id); heroes[i].Init(); heroSprites[i] = HeroDatabase.createHeroSprite(id); heroSprites[i].name = "Hero" + i + "_Sprite"; heroSprites[i].transform.position = new Vector3(-1 + -3 + 3 * i, -1.8f, 0); BattleParticipantSprite bps = heroSprites[i].AddComponent(); bps.Init(battlePlayerControls, heroes[i]); bps.OnSpriteClicked += battlePlayerControls.NotifyParticipantSpriteClicked; heroes[i].OnDeath += bps.NotifyDeath; heroUIs[i] = (HeroInfoUI) Instantiate(heroUIPrefab.gameObject, uiCanvas.transform).GetComponent(typeof(HeroInfoUI)); heroUIs[i].Init(heroSprites[i], heroes[i]); } monsters = new BattleMonster[3]; monsterSprites = new GameObject[3]; monsterUIs = new HeroInfoUI[3]; for (int i = 0; i < 3; i++) { MonsterId id = monsterIds[i]; monsters[i] = MonsterDatabase.createMonsterScript(id); monsters[i].Init(); monsterSprites[i] = MonsterDatabase.createMonsterSprite(id); monsterSprites[i].name = "Monster" + i + "_Sprite"; monsterSprites[i].transform.position = new Vector3(+1 + -3 + 3 * i, 1.2f, 0); BattleParticipantSprite bps = monsterSprites[i].AddComponent(); bps.Init(battlePlayerControls, monsters[i]); bps.OnSpriteClicked += battlePlayerControls.NotifyParticipantSpriteClicked; monsters[i].OnDeath += bps.NotifyDeath; monsterUIs[i] = (HeroInfoUI) Instantiate(heroUIPrefab.gameObject, uiCanvas.transform).GetComponent(typeof(HeroInfoUI)); monsterUIs[i].Init(monsterSprites[i], monsters[i]); } BattleParticipant[] cardOwners = new BattleParticipant[this.cardIds.Length]; for (int i = 0; i < this.cardIds.Length; i++) { cardOwners[i] = FindHeroByClass(HeroCardDatabase.GetHeroIdOfCard(this.cardIds[i])); } this.heroDeck = new BattleDeck( this.heroCardDatabase, this.cardIds.Cast().ToArray(), cardOwners); BattleParticipant[] mCardOwners = new BattleParticipant[this.mCardIds.Length]; if (this.mCardIds.Length != this.mCardOwnerIndexes.Length) { throw new System.ArgumentException( "mCardIds and mCardOwnerIndexes need to have the same length"); } for (int i = 0; i < this.mCardIds.Length; i++) { mCardOwners[i] = this.monsters[this.mCardOwnerIndexes[i]]; } this.monsterDeck = new BattleDeck( this.monsterCardDatabase, this.mCardIds.Cast().ToArray(), mCardOwners); this.playArea = new PlayArea(); this.battlePlayerControls.Init(this.heroDeck, this.monsterDeck, this.playArea); this.battlePlayerControls.OnCardUsed += this.NotifyHeroCardUsed; this.battlePlayerControls.OnTurnPassed += this.NotifyHeroTurnPassed; this.heroDeck.OnHandPileChanged += this.battlePlayerControls.NotifyHeroHandPileChanged; this.monsterDeck.OnHandPileChanged += this.battlePlayerControls.NotifyMonsterHandPileChanged; } private BattleParticipant FindHeroByClass(HeroId heroId) { foreach (BattleHero battleHero in this.heroes) { if (battleHero.GetId() == heroId) return battleHero; } return null; } // Start is called before the first frame update void Start() { StartCoroutine(RunBattleRounds()); } // Update is called once per frame void Update() { } private IEnumerator RunBattleRounds() { while (true) { yield return RunBattleRound(); } } private IEnumerator RunBattleRound() { this.heroTurn = true; this.damageTriggers.RemoveAll(t => t.Expiration == TriggerExpiration.StartOfNextHeroTurn); heroEnergy.Replenish(); monsterEnergy.Replenish(); this.heroDeck.DrawHand(); this.monsterDeck.DrawHand(); yield return RunHeroTurn(); this.heroTurn = false; yield return RunMonsterTurn(); } private IEnumerator RunHeroTurn() { // Start of hero turn // TODO: Play emotions from hand if (this.heroEnergy.MaxEnergy < 9) { this.heroEnergy.DropOneEmotion(); } foreach (BattleHero hero in this.heroes) { hero.ResetBlock(); } // Heroes act until they pass while (!this.heroPassedTurn) { yield return null; } this.heroPassedTurn = false; // End of hero turn this.heroDeck.DiscardHand(); } private IEnumerator RunMonsterTurn() { yield return new WaitForSeconds(0.5f); // Start of monster turn if (this.monsterEnergy.MaxEnergy < 9) { this.monsterEnergy.DropOneEmotion(); } foreach (BattleMonster monster in this.monsters) { monster.ResetBlock(); } // Monsters act until they pass List monsterHand = this.monsterDeck.GetHandPile(); for (int i = 0; i < monsterHand.Count; i++) { MonsterCard monsterCard = monsterHand[i]; if (CanUseMonsterCard(monsterCard)) { yield return UseMonsterCard(monsterCard); } } // End of monster turn this.monsterDeck.DiscardHand(); } public void NotifyHeroCardUsed(HeroCard card) { if (!CanUseHeroCard(card)) { throw new System.InvalidOperationException("Cannot use card " + card.GetDisplayName()); } StartCoroutine(UseHeroCard(card)); } public IEnumerator UseHeroCard(HeroCard card) { yield return card.Use(this, battlePlayerControls); this.heroEnergy.PayForCard(card); this.heroDeck.RemoveCardFromHand(card); if (card.IsPermanent()) { this.playArea.AddPlayedHeroPermanent(card); } else { this.heroDeck.MoveCardToDiscard(card); } } public IEnumerator UseMonsterCard(MonsterCard card) { yield return card.Use(this, this); this.monsterEnergy.PayForCard(card); this.monsterDeck.RemoveCardFromHand(card); if (card.IsPermanent()) { this.playArea.AddPlayedMonsterPermanent(card); } else { this.monsterDeck.MoveCardToDiscard(card); } yield return new WaitForSeconds(0.5f); } public void NotifyHeroTurnPassed() { if (this.heroTurn) { this.heroPassedTurn = true; } } public bool CanUseHeroCard(HeroCard card) { return this.heroTurn && card.Owner.IsAlive() && this.heroEnergy.CanPayForCard(card); } public bool CanUseMonsterCard(MonsterCard card) { return !this.heroTurn && card.Owner.IsAlive() && this.monsterEnergy.CanPayForCard(card); } public BattleDeck GetHeroDeck() { return this.heroDeck; } public PlayArea GetPlayArea() { return this.playArea; } private void EnqueueActivatedTrigger(Trigger trigger) { this.activatedTriggers.Add(trigger); } private void RunActivatedTriggers() { foreach (Trigger t in this.activatedTriggers) { t.Run(this); } this.activatedTriggers.Clear(); // Cleanup expired triggers this.damageTriggers.RemoveAll(t => t.Expired); } public EnergyPool HeroEnergy {get => this.heroEnergy;} public EnergyPool MonsterEnergy {get => this.monsterEnergy;} // IBattleEffectApplier public void Apply(IBattleEffect effect) { effect.Apply(this); } // IBattleEffectResolver public void IncreaseHeroMaxEnergy(IncreaseMaxEnergyEffect effect) { this.heroEnergy.MaxEnergy += effect.Amount; } public void IncreaseMaxHP(IncreaseMaxHPEffect effect) { effect.Target.IncreaseMaxHP(effect.Amount); } public void AcquireHeroCards(AcquireCardsEffect effect) { BattleHero hero = (BattleHero) effect.Participant; foreach (CardId cardId in effect.CardIds) { HeroCard acquiredCard = this.heroCardDatabase.CreateCard((int) cardId, hero); switch (effect.Zone) { case BattleZone.DrawPile: this.heroDeck.PutCardOnTopOfDrawPile(acquiredCard); break; default: throw new System.NotImplementedException(); } } if (effect.Zone == BattleZone.DrawPile) { this.heroDeck.ShuffleDrawPile(); } } private List activatedTriggers = new List(); private List damageTriggers = new List(); public int DealDamage(DealDamageEffect effect) { int damageDealt = effect.Target.DealDamage(effect.Amount); foreach (DamageTrigger dt in this.damageTriggers) { if (dt.IsTriggeredBy(effect)) { EnqueueActivatedTrigger(dt); } } // Do this after the card resolution is over to avoid reentraint effects. RunActivatedTriggers(); return damageDealt; } public int Heal(HealEffect effect) { int healedAmount = effect.Target.Heal(effect.Amount); return healedAmount; } public void AddBlock(BlockEffect effect) { effect.Target.AddBlock(effect.Amount); } public void ArmDamageTrigger(DamageTrigger damageTrigger) { this.damageTriggers.Add(damageTrigger); } // MonsterViewpoint public BattleHero[] Heroes {get => this.heroes;} public BattleMonster[] Monsters {get => this.monsters;} }