559 lines
18 KiB
C#
559 lines
18 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
|
|
public class BattleController : MonoBehaviour, IBattleEffectApplier, IBattleEffectResolver, MonsterViewpoint
|
|
{
|
|
// Inspector public
|
|
public HeroInfoUI heroUIPrefab;
|
|
public GameObject uiCanvas;
|
|
public BattlePlayerControls battlePlayerControls;
|
|
public EnergyUI heroEnergyUI;
|
|
public EnergyUI monsterEnergyUI;
|
|
|
|
// Private
|
|
private HeroCardDatabase heroCardDatabase;
|
|
private MonsterCardDatabase monsterCardDatabase;
|
|
private BattleDeck<HeroCard> heroDeck;
|
|
private BattleDeck<MonsterCard> monsterDeck;
|
|
private PlayArea playArea;
|
|
|
|
private BattleHero[] heroes;
|
|
private Dictionary<BattleHero, GameObject> heroSprites;
|
|
private Dictionary<BattleHero, HeroInfoUI> heroUIs;
|
|
|
|
private BattleMonster[] monsters;
|
|
private Dictionary<BattleMonster, GameObject> monsterSprites;
|
|
private Dictionary<BattleMonster, HeroInfoUI> monsterUIs;
|
|
|
|
private bool heroTurn;
|
|
private bool heroPassedTurn;
|
|
private EnergyPool heroEnergy;
|
|
private EnergyPool monsterEnergy;
|
|
private InsightLevel insightLevel;
|
|
|
|
void Awake() {
|
|
HeroId[] heroIds = new HeroId[3] {
|
|
HeroId.Brute,
|
|
HeroId.Alchemist,
|
|
HeroId.Shaman,
|
|
};
|
|
|
|
HCardId[] cardIds = new HCardId[10] {
|
|
HCardId.CrashingPunch,
|
|
HCardId.CrashingPunch,
|
|
HCardId.BlindFury,
|
|
HCardId.Cover,
|
|
HCardId.Cover,
|
|
HCardId.Thunderstorm,
|
|
HCardId.Scarring,
|
|
HCardId.Scarring,
|
|
HCardId.Meditate,
|
|
HCardId.IronskinDraught,
|
|
};
|
|
|
|
MonsterId[] monsterIds = new MonsterId[3] {
|
|
MonsterId.Golem,
|
|
MonsterId.Bat,
|
|
MonsterId.Bat,
|
|
};
|
|
|
|
MCardId[] mCardIds = new MCardId[10] {
|
|
MCardId.GraniteDefense,
|
|
MCardId.GraniteDefense,
|
|
MCardId.GolemCharge,
|
|
MCardId.GolemFist,
|
|
|
|
MCardId.VampireFangs,
|
|
MCardId.VampireFangs,
|
|
MCardId.Whirlwind,
|
|
|
|
MCardId.VampireFangs,
|
|
MCardId.VampireFangs,
|
|
MCardId.Whirlwind,
|
|
};
|
|
int[] mCardOwnerIndexes = new int[10] {
|
|
0, 0, 0, 0,
|
|
1, 1, 1,
|
|
2, 2, 2,
|
|
};
|
|
// MCardId[] mCardIds = new MCardId[5] {
|
|
// MCardId.Whirlwind,
|
|
// MCardId.Whirlwind,
|
|
// MCardId.Whirlwind,
|
|
// MCardId.Whirlwind,
|
|
// MCardId.Whirlwind,
|
|
// };
|
|
// int[] mCardOwnerIndexes = new int[5] {
|
|
// 1, 1,
|
|
// 2, 2, 2,
|
|
// };
|
|
|
|
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;
|
|
|
|
this.insightLevel = new InsightLevel(1);
|
|
|
|
this.heroes = new BattleHero[heroIds.Length];
|
|
this.heroSprites = new Dictionary<BattleHero, GameObject>();
|
|
this.heroUIs = new Dictionary<BattleHero, HeroInfoUI>();
|
|
for (int i = 0; i < this.heroes.Length; i++) {
|
|
HeroId id = heroIds[i];
|
|
|
|
BattleHero hero = new BattleHero(id);
|
|
hero.Init();
|
|
this.heroes[i] = hero;
|
|
|
|
GameObject sprite = HeroDatabase.createHeroSprite(id);
|
|
this.heroSprites[hero] = sprite;
|
|
sprite.name = "Hero" + i + "_Sprite";
|
|
PositionSprite(TeamChoice.Heroes, sprite, i);
|
|
BattleParticipantSprite bps = sprite.AddComponent<BattleParticipantSprite>();
|
|
bps.Init(battlePlayerControls, hero);
|
|
bps.OnSpriteClicked += battlePlayerControls.NotifyParticipantSpriteClicked;
|
|
hero.OnDeath += bps.NotifyDeath;
|
|
|
|
HeroInfoUI heroUI = (HeroInfoUI) Instantiate(this.heroUIPrefab.gameObject, this.uiCanvas.transform).GetComponent(typeof(HeroInfoUI));
|
|
heroUI.Init(sprite, hero);
|
|
this.heroUIs[hero] = heroUI;
|
|
}
|
|
|
|
this.monsters = new BattleMonster[monsterIds.Length];
|
|
this.monsterSprites = new Dictionary<BattleMonster, GameObject>();
|
|
this.monsterUIs = new Dictionary<BattleMonster, HeroInfoUI>();
|
|
for (int i = 0; i < 3; i++) {
|
|
MonsterId id = monsterIds[i];
|
|
|
|
BattleMonster monster = MonsterDatabase.createMonsterScript(id);
|
|
monster.Init();
|
|
this.monsters[i] = monster;
|
|
|
|
GameObject sprite = MonsterDatabase.createMonsterSprite(id);
|
|
this.monsterSprites[monster] = sprite;
|
|
sprite.name = "Monster" + i + "_Sprite";
|
|
PositionSprite(TeamChoice.Monsters, sprite, i);
|
|
BattleParticipantSprite bps = sprite.AddComponent<BattleParticipantSprite>();
|
|
bps.Init(battlePlayerControls, monster);
|
|
bps.OnSpriteClicked += battlePlayerControls.NotifyParticipantSpriteClicked;
|
|
monster.OnDeath += bps.NotifyDeath;
|
|
|
|
HeroInfoUI monsterUI = (HeroInfoUI) Instantiate(this.heroUIPrefab.gameObject, this.uiCanvas.transform).GetComponent(typeof(HeroInfoUI));
|
|
monsterUI.Init(sprite, monster);
|
|
this.monsterUIs[monster] = monsterUI;
|
|
}
|
|
|
|
BattleParticipant[] cardOwners = new BattleParticipant[cardIds.Length];
|
|
for (int i = 0; i < cardIds.Length; i++) {
|
|
cardOwners[i] = FindHeroByClass(HeroCardDatabase.GetHeroIdOfCard(cardIds[i]));
|
|
}
|
|
this.heroDeck = new BattleDeck<HeroCard>(
|
|
this.heroCardDatabase, cardIds.Cast<int>().ToArray(), cardOwners);
|
|
|
|
BattleParticipant[] mCardOwners = new BattleParticipant[mCardIds.Length];
|
|
if (mCardIds.Length != mCardOwnerIndexes.Length) {
|
|
throw new System.ArgumentException(
|
|
"mCardIds and mCardOwnerIndexes need to have the same length");
|
|
}
|
|
for (int i = 0; i < mCardIds.Length; i++) {
|
|
mCardOwners[i] = this.monsters[mCardOwnerIndexes[i]];
|
|
}
|
|
this.monsterDeck = new BattleDeck<MonsterCard>(
|
|
this.monsterCardDatabase, mCardIds.Cast<int>().ToArray(), mCardOwners);
|
|
|
|
this.playArea = new PlayArea();
|
|
|
|
this.battlePlayerControls.Init(
|
|
this.heroDeck, this.monsterDeck, this.playArea, this.insightLevel);
|
|
this.battlePlayerControls.OnCardUsed += this.NotifyHeroCardUsed;
|
|
this.battlePlayerControls.OnTurnPassed += this.NotifyHeroTurnPassed;
|
|
this.insightLevel.OnChanged += this.battlePlayerControls.NotifyInsightLevelChanged;
|
|
this.heroDeck.OnHandPileChanged += this.battlePlayerControls.NotifyHeroHandPileChanged;
|
|
this.monsterDeck.OnHandPileChanged += this.battlePlayerControls.NotifyMonsterHandPileChanged;
|
|
}
|
|
|
|
private void PositionSprite(TeamChoice team, GameObject sprite, int i) {
|
|
Vector3 pos;
|
|
if (team == TeamChoice.Heroes) {
|
|
pos = new Vector3(-1 + -3 + 3 * i, -1.8f, 0);
|
|
} else {
|
|
pos = new Vector3(+1 + -3 + 3 * i, 1.2f, 0);
|
|
}
|
|
sprite.transform.position = pos;
|
|
}
|
|
|
|
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();
|
|
foreach (BattleHero hero in this.heroes) {
|
|
hero.TickStatuses();
|
|
}
|
|
}
|
|
|
|
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<MonsterCard> 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();
|
|
foreach (BattleMonster monster in this.monsters) {
|
|
monster.TickStatuses();
|
|
}
|
|
}
|
|
|
|
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) {
|
|
this.heroEnergy.PayForCard(card);
|
|
this.heroDeck.RemoveCardFromHand(card);
|
|
yield return card.Use(this, this, battlePlayerControls);
|
|
if (card.IsPermanent()) {
|
|
this.playArea.AddPlayedHeroPermanent(card);
|
|
} else {
|
|
this.heroDeck.MoveCardToDiscard(card);
|
|
}
|
|
|
|
yield return new WaitForSeconds(0.5f);
|
|
CheckState();
|
|
}
|
|
|
|
public IEnumerator UseMonsterCard(MonsterCard card) {
|
|
this.monsterEnergy.PayForCard(card);
|
|
this.monsterDeck.RemoveCardFromHand(card);
|
|
yield return card.Use(this, this);
|
|
if (card.IsPermanent()) {
|
|
this.playArea.AddPlayedMonsterPermanent(card);
|
|
} else {
|
|
this.monsterDeck.MoveCardToDiscard(card);
|
|
}
|
|
|
|
yield return new WaitForSeconds(0.5f);
|
|
CheckState();
|
|
}
|
|
|
|
public void NotifyHeroTurnPassed() {
|
|
if (this.heroTurn) {
|
|
this.heroPassedTurn = true;
|
|
}
|
|
}
|
|
|
|
public bool CanUseHeroCard(HeroCard card) {
|
|
return this.heroTurn && card.Owner.CanUseCard(card) && this.heroEnergy.CanPayForCard(card);
|
|
}
|
|
|
|
public bool CanUseMonsterCard(MonsterCard card) {
|
|
return !this.heroTurn && card.Owner.CanUseCard(card) && this.monsterEnergy.CanPayForCard(card);
|
|
}
|
|
|
|
public BattleDeck<HeroCard> 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);
|
|
}
|
|
|
|
private void CheckState() {
|
|
foreach (BattleParticipant p in this.heroes) {
|
|
p.CheckState();
|
|
}
|
|
foreach (BattleParticipant p in this.monsters) {
|
|
p.CheckState();
|
|
}
|
|
if (System.Array.TrueForAll(this.heroes, h => !h.IsAlive())) {
|
|
Debug.Log("You lost!");
|
|
}
|
|
if (System.Array.TrueForAll(this.monsters, m => !m.IsAlive())) {
|
|
Debug.Log("You won!");
|
|
}
|
|
}
|
|
|
|
private BattleParticipant[] GetTeam(TeamChoice team) {
|
|
return (team == TeamChoice.Heroes
|
|
? this.heroes.Cast<BattleParticipant>().ToArray()
|
|
: this.monsters.Cast<BattleParticipant>().ToArray());
|
|
}
|
|
|
|
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) {
|
|
if (effect.Target == null) return;
|
|
effect.Target.IncreaseMaxHP(effect.Amount);
|
|
}
|
|
|
|
public void ModifyInsight(ModifyInsightEffect effect) {
|
|
this.insightLevel.Modify(effect.Amount);
|
|
}
|
|
|
|
public void DrawCards(DrawCardsEffect effect) {
|
|
if (effect.Team == TeamChoice.Heroes) {
|
|
for (int i = 0; i < effect.Amount; i++) {
|
|
this.heroDeck.DrawCard();
|
|
}
|
|
} else {
|
|
for (int i = 0; i < effect.Amount; i++) {
|
|
this.monsterDeck.DrawCard();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void AcquireCards(AcquireCardsEffect effect) {
|
|
if (effect.Participant == null) return;
|
|
|
|
if (effect.Participant.Team == TeamChoice.Heroes) {
|
|
this.AcquireCardsTyped(effect, this.heroDeck, this.heroCardDatabase);
|
|
} else {
|
|
this.AcquireCardsTyped(effect, this.monsterDeck, this.monsterCardDatabase);
|
|
}
|
|
}
|
|
|
|
private void AcquireCardsTyped<BaseCardT>(
|
|
AcquireCardsEffect effect,
|
|
BattleDeck<BaseCardT> deck,
|
|
CardDatabase<BaseCardT> cardDatabase)
|
|
where BaseCardT : BaseCard {
|
|
foreach (int cardId in effect.CardIds) {
|
|
BaseCardT acquiredCard = cardDatabase.CreateCard(cardId, effect.Participant);
|
|
switch (effect.Zone) {
|
|
case BattleZone.DrawPile:
|
|
deck.PutCardOnTopOfDrawPile(acquiredCard);
|
|
break;
|
|
case BattleZone.DiscardPile:
|
|
deck.MoveCardToDiscard(acquiredCard);
|
|
break;
|
|
default:
|
|
throw new System.NotImplementedException();
|
|
}
|
|
}
|
|
|
|
if (effect.Zone == BattleZone.DrawPile) {
|
|
deck.ShuffleDrawPile();
|
|
}
|
|
}
|
|
|
|
private List<Trigger> activatedTriggers = new List<Trigger>();
|
|
private List<DamageTrigger> damageTriggers = new List<DamageTrigger>();
|
|
|
|
public int DealDamage(DealDamageEffect effect) {
|
|
if (effect.Target == null) return 0;
|
|
|
|
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 DealMassDamage(MassDamageEffect effect) {
|
|
int damageDealt = 0;
|
|
BattleParticipant[] affectedTeam = GetTeam(effect.Team);
|
|
foreach (BattleParticipant bp in affectedTeam) {
|
|
damageDealt += bp.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) {
|
|
if (effect.Target == null) return 0;
|
|
|
|
int healedAmount = effect.Target.Heal(effect.Amount);
|
|
return healedAmount;
|
|
}
|
|
|
|
public void AddBlock(BlockEffect effect) {
|
|
if (effect.Target == null) return;
|
|
effect.Target.AddBlock(effect.Amount);
|
|
}
|
|
|
|
public void AddStatus(StatusEffect effect) {
|
|
if (effect.Target == null) return;
|
|
effect.Target.AddStatus(effect.Status, effect.Amount);
|
|
}
|
|
|
|
public void Rotate(RotateEffect effect) {
|
|
if (effect.Positions == 0) return;
|
|
|
|
BattleParticipant[] currentPositions = GetTeam(effect.Team);
|
|
|
|
int start = int.MaxValue;
|
|
int end = int.MinValue;
|
|
for (int i = 0; i < currentPositions.Length; i++) {
|
|
BattleParticipant bp = currentPositions[i];
|
|
if (bp != null) {
|
|
start = System.Math.Min(start, i);
|
|
end = System.Math.Max(end, i);
|
|
}
|
|
}
|
|
|
|
int[] newPositions = new int[currentPositions.Length];
|
|
for (int i = start; i <= end; i++) {
|
|
if (i < start || i > end) {
|
|
newPositions[i] = i;
|
|
} else {
|
|
int newI = start + TrueMod(
|
|
i - start - effect.Positions,
|
|
end - start + 1);
|
|
newPositions[newI] = i;
|
|
}
|
|
}
|
|
|
|
MoveTeamToNewPositions(effect.Team, newPositions);
|
|
}
|
|
|
|
private static int TrueMod(int a, int b) {
|
|
return (a % b + b) % b;
|
|
}
|
|
|
|
private void MoveTeamToNewPositions(TeamChoice team, int[] newPositions) {
|
|
int n = newPositions.Length;
|
|
if (team == TeamChoice.Heroes) {
|
|
BattleHero[] newHeroes = new BattleHero[n];
|
|
for (int i = 0; i < n; i++) {
|
|
BattleHero hero = this.heroes[i];
|
|
newHeroes[newPositions[i]] = hero;
|
|
PositionSprite(team, heroSprites[hero], newPositions[i]);
|
|
}
|
|
this.heroes = newHeroes;
|
|
} else {
|
|
BattleMonster[] newMonsters = new BattleMonster[n];
|
|
for (int i = 0; i < n; i++) {
|
|
BattleMonster monster = this.monsters[i];
|
|
newMonsters[newPositions[i]] = monster;
|
|
PositionSprite(team, monsterSprites[monster], newPositions[i]);
|
|
}
|
|
this.monsters = newMonsters;
|
|
}
|
|
}
|
|
|
|
public void ArmDamageTrigger(DamageTrigger damageTrigger) {
|
|
this.damageTriggers.Add(damageTrigger);
|
|
}
|
|
|
|
// MonsterViewpoint
|
|
public BattleHero[] Heroes {get => this.heroes;}
|
|
public BattleMonster[] Monsters {get => this.monsters;}
|
|
}
|