// WARNING: The Game must not be changing during this, // ie. it is not thread-safe unless the game is inactive public Game CloneState() { var entities = new EntityController(Entities); Game game = entities.FindGame(); // Set references to the new player proxies (no additional cloning) game.Player1 = entities.FindPlayer(1); game.Player2 = entities.FindPlayer(2); if (game.CurrentPlayer != null) { game.CurrentPlayer = (CurrentPlayer.Id == game.Player1.Id ? game.Player1 : game.Player2); } // Generate zones owned by game for (int i = 0; i < 2; i++) { game.Players[i].Deck = new Deck(game, Players[i].Deck.HeroClass, game.Players[i]); } // Clone queue, stack and events game.ActionQueue = ((ActionQueue)ActionQueue.Clone()); game.ActionQueue.Attach(game); // Clone triggers game.ActiveTriggers = ((TriggerManager)ActiveTriggers.Clone()); game.ActiveTriggers.Game = game; // Clone environment game.Environment = (Environment)Environment.Clone(); // Link PowerHistory if (PowerHistory != null) { game.PowerHistory = new PowerHistory(game, this); } #if _GAME_DEBUG DebugLog.WriteLine("Cloned game " + GameId + " => " + game.GameId); #endif return(game); }
internal void OnBlockEmpty(BlockStart Block) { #if _GAME_DEBUG DebugLog.WriteLine("Game " + GameId + ": Action block " + Block.Type + " for " + Entities[Block.Source].ShortDescription + " resolved"); #endif PowerHistory?.Add(new BlockEnd(Block.Type)); if (Block.Type == BlockType.TRIGGER) { ActiveTriggers.TriggerResolved(); } // Post-ATTACK or Post-final TRIGGER DEATHS block if (Block.Type == BlockType.ATTACK || (Block.Type == BlockType.TRIGGER && ActiveTriggers.QueuedTriggersCount == 0)) { RunDeathCreationStepIfNeeded(); } }
internal void OnQueueEmpty() { #if _GAME_DEBUG DebugLog.WriteLine("Game " + GameId + ": Action queue resolved"); #endif // Don't do anything if the game state hasn't changed // Check if one of the other players is waiting for the other one to mulligan var step = Step; if (step == Step.BEGIN_MULLIGAN) { foreach (var p in Players) { if (p.MulliganState == MulliganState.WAITING) { ActiveTriggers.Queue(TriggerType.OnMulliganWaiting, p); return; } } } // Advance game step if necessary (probably setting off new triggers) var nextStep = NextStep; // Only advance to end turn when current player chooses to #if _GAME_DEBUG if (nextStep == Step.MAIN_END && ActionQueue.IsEmpty) { DebugLog.WriteLine("Game " + GameId + ": Waiting for player to select next option"); } #endif if (nextStep != step && nextStep != Step.MAIN_END) { #if _GAME_DEBUG DebugLog.WriteLine("Game " + GameId + ": Advancing game step from " + step + " to " + nextStep); #endif Step = nextStep; } }
// Death checking phase internal void RunDeathCreationStepIfNeeded() { #if _GAME_DEBUG DebugLog.WriteLine("Game " + GameId + ": Checking for death creation step"); #endif if (_deathCheckQueue.Count == 0) { return; } // We only have to check health because ToBeDestroyed cannot be reversed without the minion leaving play var dyingEntities = _deathCheckQueue.Where( id => ((ICharacter)Entities[id]).MortallyWounded && Entities[id].Zone.Type == Brimstone.Zone.PLAY) .Select(id => (ICharacter)Entities[id]).ToList(); if (dyingEntities.Count > 0) { #if _GAME_DEBUG DebugLog.WriteLine("Game " + GameId + ": Running death creation step"); #endif PowerHistory?.Add(new BlockStart(BlockType.DEATHS, this)); } // Death Creation Step bool gameEnd = false; foreach (var e in dyingEntities) { #if _ACTIONS_DEBUG DebugLog.WriteLine("Game {0}: {1} dies", GameId, e.ShortDescription); #endif // Queue deathrattles and OnDeath triggers before moving mortally wounded minion to graveyard // (they will be executed after the zone move) // TODO: Test that each queue resolves before the next one populates. If it doesn't, we can make queue populating lazy if (e is Minion) { ActiveTriggers.Queue(TriggerType.OnDeath, e); } // NumMinionsPlayerKilledThisTurn seems to be the number of minions that died this turn // regardless of who or what killed what e.Controller.NumMinionsPlayerKilledThisTurn++; NumMinionsKilledThisTurn++; e.IsExhausted = false; // Move dead character to graveyard e.Zone = e.Controller.Graveyard; // TODO: Reset all minion tags to default if (e is Minion) { var minion = ((Minion)e); minion.Damage = 0; } // Hero death if (e is Hero) { e.Controller.PlayState = PlayState.LOSING; gameEnd = true; } } if (gameEnd) { GameWon(); } if (dyingEntities.Count > 0) { PowerHistory?.Add(new BlockEnd(BlockType.DEATHS)); } _deathCheckQueue.Clear(); }