329 lines
11 KiB
C#
329 lines
11 KiB
C#
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<HeroCard> heroHandPlayability;
|
|
private HandPlayability<MonsterCard> monsterHandPlayability;
|
|
private TurnIntentions<MonsterCard> 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<HeroCard>(
|
|
this.state, this.state.HeroEnergy, TeamChoice.Heroes, this.state.HeroDeck);
|
|
this.OnBattleStateChanged += this.heroHandPlayability.NotifyBattleStateChanged;
|
|
this.monsterHandPlayability = new HandPlayability<MonsterCard>(
|
|
this.state, this.state.MonsterEnergy, TeamChoice.Monsters, this.state.MonsterDeck);
|
|
this.OnBattleStateChanged += this.monsterHandPlayability.NotifyBattleStateChanged;
|
|
|
|
this.monsterIntentions = new TurnIntentions<MonsterCard>(
|
|
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<CheatBattle>();
|
|
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();
|
|
|
|
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);
|
|
}
|
|
|
|
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();
|
|
|
|
// 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.
|
|
this.state.TriggerManager.RunActivatedTriggers(this);
|
|
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.
|
|
this.state.TriggerManager.RunActivatedTriggers(this);
|
|
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);
|
|
|
|
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 new WaitForSeconds(0.5f);
|
|
|
|
// Monsters play an emotion
|
|
List<MonsterCard> 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.
|
|
this.state.TriggerManager.RunActivatedTriggers(this);
|
|
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.
|
|
this.state.TriggerManager.RunActivatedTriggers(this);
|
|
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();
|
|
}
|
|
|
|
private MonsterCard FirstMonsterActionToPlay() {
|
|
List<MonsterCard> 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);
|
|
}
|
|
}
|