Exemple #1
0
        /// <summary>
        /// Convert string code list to deck
        /// </summary>
        /// <param name="cardCodes">List of card codes</param>
        /// <returns>Deck contents</returns>
        public static List <CardWithCount> DeckFromStringCodeList(string[] cardCodes)
        {
            List <CardWithCount> cards = new List <CardWithCount>();

            if (cardCodes == null)
            {
                return(cards);
            }
            foreach (var cardCode in cardCodes)
            {
                int index = cards.FindIndex(item => item.Code.Equals(cardCode));
                if (index >= 0)
                {
                    cards[index].Count++;
                }
                else
                {
                    Card card = CardLibrary.GetCard(cardCode);
                    cards.Add(new CardWithCount(card, 1, true));
                }
            }
            cards = cards.OrderBy(card => card.Cost).ThenBy(card => card.Name).ToList();

            return(cards);
        }
        /// <summary>
        /// Receives notification that set download was completed
        /// </summary>
        /// <param name="sender">Sender</param>
        /// <param name="e">Arguments</param>
        private void OnDownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if (e != null && e.Error != null && e.Error.HResult != 0)
            {
                string localFile = Constants.GetSetZipPath(MissingSets[CurrentDownloadIndex].Item1);
                if (File.Exists(localFile))
                {
                    File.Delete(localFile);
                }

                // Error occured, finish up
                Callback.OnDownloadCanceled(true);
            }
            else
            {
                // Success, finish up and queue up the next one

                ProcessDownloadedSet(MissingSets[CurrentDownloadIndex].Item1, MissingSets[CurrentDownloadIndex].Item2);
                CurrentDownloadIndex++;
                if (CurrentDownloadIndex < MissingSets.Count)
                {
                    CardLibrary.DownloadSet(MissingSets[CurrentDownloadIndex].Item1,
                                            new DownloadProgressChangedEventHandler(OnDownloadProgressChanged),
                                            new AsyncCompletedEventHandler(OnDownloadFileCompleted));
                }
                else
                {
                    // Finished
                    Callback.OnAllSetsDownloaded();
                }
            }
        }
 private void ApplyDraftPick(ref List <CardWithCount> cards, ExpeditionPick pick)
 {
     if (pick.IsSwap)
     {
         foreach (var cardCode in pick.SwappedOut)
         {
             int index = cards.FindIndex(item => item.Code.Equals(cardCode));
             if (0 == --cards[index].Count)
             {
                 cards.RemoveAt(index);
             }
         }
         foreach (var cardCode in pick.SwappedIn)
         {
             int index = cards.FindIndex(item => item.Code.Equals(pick.SwappedOut));
             if (index >= 0)
             {
                 cards[index].Count++;
             }
             else
             {
                 cards.Add(new CardWithCount(CardLibrary.GetCard(cardCode), 1, true));
             }
         }
     }
     else
     {
         foreach (var cardCode in pick.DraftPicks)
         {
             int index = cards.FindIndex(item => item.Code.Equals(cardCode));
             if (index >= 0)
             {
                 cards[index].Count++;
             }
             else
             {
                 cards.Add(new CardWithCount(CardLibrary.GetCard(cardCode), 1, true));
             }
         }
     }
 }
Exemple #4
0
        private void LoadFromJSON(Dictionary <string, JsonElement> deck)
        {
            Cards.Clear();
            DeckCode = deck["DeckCode"].ToString();
            try { DeckName = GameHistory.DeckNames[DeckCode]; } catch { DeckName = GameRecord.DefaultConstructedDeckName; }
            var deckList = deck["CardsInDeck"].ToObject <Dictionary <string, int> >();

            if (deckList != null)
            {
                foreach (var j in deckList)
                {
                    string cardCode = j.Key;
                    Card   card     = CardLibrary.GetCard(cardCode);
                    int    count    = j.Value;
                    Cards.Add(new CardWithCount(card, count, true));
                }

                // Sort the deck
                Cards = Cards.OrderBy(card => card.Cost).ThenBy(card => card.Name).ToList();
            }
        }
        /// <summary>
        /// Find all missing sets and download them
        /// </summary>
        /// <param name="progressDisplay"></param>
        /// <returns></returns>
        public bool DownloadAllSets(IProgressDisplay progressDisplay)
        {
            MyProgressDisplay = progressDisplay;

            MissingSets = FindMissingSets();
            if (MissingSets.Count > 0)
            {
                long totalDownloadSize = MissingSets.Sum(x => x.Item2);
                var  result            = MessageBox.Show(
                    string.Format("Card sets have been updated. Download size is {0} MB. Download new sets?", totalDownloadSize / 1024 / 1024),
                    "Sets Out of Date",
                    MessageBoxButtons.YesNo);
                if (result != DialogResult.Yes)
                {
                    return(false);
                }

                // Delete existing outdated sets
                foreach (var set in MissingSets)
                {
                    if (Directory.Exists(Constants.GetSetPath(set.Item1)))
                    {
                        Directory.Delete(Constants.GetSetPath(set.Item1), true);
                    }
                }

                CurrentDownloadIndex = 0;
                CardLibrary.DownloadSet(MissingSets[CurrentDownloadIndex].Item1,
                                        new DownloadProgressChangedEventHandler(OnDownloadProgressChanged),
                                        new AsyncCompletedEventHandler(OnDownloadFileCompleted));
            }
            else
            {
                Callback.OnAllSetsDownloaded();
            }
            return(true);
        }
        void DrawElement(CardInPlay card, Graphics g, Color borderColor, Rectangle screenRect, bool flip)
        {
            float y = card.NormalizedBoundingBox.Y;

            if (flip)
            {
                y = 1 - y - card.NormalizedBoundingBox.Height;
            }
            Rectangle r = new Rectangle(
                (int)(0.5f + screenRect.Width / 2 + card.NormalizedBoundingBox.X * screenRect.Height),
                (int)(0.5f + y * screenRect.Height),
                (int)(0.5f + card.NormalizedBoundingBox.Width * screenRect.Height),
                (int)(0.5f + card.NormalizedBoundingBox.Height * screenRect.Height));

            r.Offset(screenRect.X, screenRect.Y);
            if (FullArtView)
            {
                card.TheCard.LoadCardArt();
                if (card.CurrentZone == PlayZone.Zoom || card.CurrentZone == PlayZone.Stage || card.CurrentZone == PlayZone.Hand || card.CurrentZone == PlayZone.Field)
                {
                    g.DrawImage(card.TheCard.CardArt, r);
                }
                else if (card.CurrentZone == PlayZone.Cast || card.CurrentZone == PlayZone.Battle || card.CurrentZone == PlayZone.Windup || card.CurrentZone == PlayZone.Attack)
                {
                    card.TheCard.DrawCardBanner(g, r);
                }
            }
            else
            {
                r.Offset(screenRect.X, screenRect.Y);
                g.DrawRectangle(new Pen(borderColor, 2), r);
                string text = string.Format("{0}\r\n{1}\r\n{2}\r\n{3}", CardLibrary.GetCard(card.CardCode).Name, card.CardCode,
                                            card.NormalizedCenter.Y, card.NormalizedBoundingBox.Height);
                TextRenderer.DrawText(g, text, this.Font, r, borderColor, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
            }
        }
Exemple #7
0
        /// <summary>
        /// Runs after all sets are downloaded and processed.
        /// Initializes the StaticDeck windows and deck tracking objects.
        /// </summary>
        public async void OnAllSetsDownloaded()
        {
            await Task.Run(() => CardLibrary.LoadAllCards(MyProgressDisplay));

            await Task.Run(() => GameHistory.LoadAllGames(MyProgressDisplay));

            DeckControl.DeckScale deckScale = DeckControl.DeckScale.Medium;
            if (Properties.Settings.Default.DeckDrawSize == 0)
            {
                deckScale = DeckControl.DeckScale.Small;
            }
            if (Properties.Settings.Default.DeckDrawSize == 2)
            {
                deckScale = DeckControl.DeckScale.Large;
            }

            double deckOpacity = Properties.Settings.Default.DeckTransparency / 100.0;

            PlayerActiveDeckWindow = CreateDeckWindow("No Active Deck", deckScale, deckOpacity,
                                                      Properties.Settings.Default.ShowPlayerDeck,
                                                      Properties.Settings.Default.PlayerDeckLocation.X,
                                                      Properties.Settings.Default.PlayerDeckLocation.Y);
            PlayerDrawnCardsWindow = CreateDeckWindow("Drawn Cards", deckScale, deckOpacity,
                                                      Properties.Settings.Default.ShowPlayerDrawnCards,
                                                      Properties.Settings.Default.PlayerDrawnCardsLocation.X,
                                                      Properties.Settings.Default.PlayerDrawnCardsLocation.Y);
            PlayerGraveyardWindow = CreateDeckWindow("Graveyard", deckScale, deckOpacity,
                                                     Properties.Settings.Default.ShowPlayerGraveyard,
                                                     Properties.Settings.Default.PlayerPlayedCardsLocation.X,
                                                     Properties.Settings.Default.PlayerPlayedCardsLocation.Y);
            OpponentGraveyardWindow = CreateDeckWindow("Opponent Graveyard", deckScale, deckOpacity,
                                                       Properties.Settings.Default.ShowOpponentGraveyard,
                                                       Properties.Settings.Default.OpponentPlayedCardsLocation.X,
                                                       Properties.Settings.Default.OpponentPlayedCardsLocation.Y);

            PlayerActiveDeckWindow.HideZeroCountCards = Properties.Settings.Default.HideZeroCountInDeck;

            ActiveLogWindow = new LogWindow();
            ActiveLogWindow.CreateControl();
            if (Properties.Settings.Default.ActiveLogWindowBounds.Width > 0 &&
                Properties.Settings.Default.ActiveLogWindowBounds.Left > 0)
            {
                ActiveLogWindow.StartPosition = FormStartPosition.Manual;
                ActiveLogWindow.SetBounds(
                    Properties.Settings.Default.ActiveLogWindowBounds.X,
                    Properties.Settings.Default.ActiveLogWindowBounds.Y,
                    Properties.Settings.Default.ActiveLogWindowBounds.Width,
                    Properties.Settings.Default.ActiveLogWindowBounds.Height,
                    BoundsSpecified.All);
            }

            {
                // Show and hide active log window to make sure it's loaded ahead of time
                ActiveLogWindow.Show();
                ActiveLogWindow.Hide();
            }
            Log.SetLogWindow(ActiveLogWindow);

            // Special debigging window is shown if D key is held during load
            if (!string.IsNullOrEmpty(Constants.PlayBackDeckPath) || Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
            {
                OverlayWindow = new GameBoardWatchWindow();
                OverlayWindow.Show();
            }

            CurrentPlayState = new CardsInPlayWorker(this);
            if (string.IsNullOrEmpty(Constants.PlayBackDeckPath))
            {
                CurrentExpedition = new Expedition(this);
            }

            // Hide the progress display and make all the other UI elements visible
            MyProgressDisplay.Visible         = false;
            DecksListCtrl.Visible             = true;
            HighlightedGameLogControl.Visible = true;
            TheMenuBar.Visible = true;

            // Load all games. We do this in reverse as AddDeckToList expects them
            // in chronological order
            for (int i = GameHistory.Games.Count - 1; i >= 0; i--)
            {
                DecksListCtrl.AddToDeckList(GameHistory.Games[i]);
            }

            Utilities.CallActionSafelyAndWait(DecksListCtrl, new Action(() => { DecksListCtrl.SwitchDeckView(false); }));

            CurrentPlayState.Start(string.IsNullOrEmpty(Constants.PlayBackDeckPath)
                ? null : Constants.GetLocalGamesPath() + "\\" + Constants.PlayBackDeckPath);
        }
        void MoveToNext(ref CardList <CardInPlay>[] current, CardList <CardInPlay> next, double timestamp, bool isInitialDraw, bool isLocalPlayer)
        {
            CardList <CardInPlay> stationaryResult = new CardList <CardInPlay>();
            CardList <CardInPlay> movedResult      = new CardList <CardInPlay>();

            // For each card in 'next', look for a card in 'current' that's in the same zone
            // If found, remove from both and add to stationaryCards set
            for (int i = 0; i < NumZones; i++)
            {
                stationaryResult.AddRange(CardList <CardInPlay> .Extract(ref next, ref current[i], (x, y) =>
                {
                    // Skip values in next that are incorrect zone
                    if ((int)x.CurrentZone != i)
                    {
                        return(-1);
                    }
                    int z = x.TheCard.Cost - y.TheCard.Cost;
                    if (z == 0)
                    {
                        z = x.TheCard.Name.CompareTo(y.TheCard.Name);
                    }
                    return(z);
                }, (x, y) =>
                {
                    y.BoundingBox           = x.BoundingBox;
                    y.NormalizedBoundingBox = x.NormalizedBoundingBox;
                    y.NormalizedCenter      = x.NormalizedCenter;
                    return(y);
                }));
            }

            // For each card in next, look for a card in 'current' Ether zone that may have returned to the same zone
            movedResult.AddRange(CardList <CardInPlay> .Extract(ref next, ref current[(int)PlayZone.Ether], (x, y) =>
            {
                // Skip values in next that are incorrect zone
                if (y.CurrentZone != PlayZone.Ether)
                {
                    return(-1);
                }
                int z = x.TheCard.Cost - y.TheCard.Cost;
                if (z == 0)
                {
                    z = x.TheCard.Name.CompareTo(y.TheCard.Name);
                }
                // Skip values in current that are incorrect zone
                if (z == 0 && x.CurrentZone != y.LastNonEtherZone)
                {
                    z = 1;
                }
                return(z);
            }, (x, y) =>
            {
                y.BoundingBox           = x.BoundingBox;
                y.NormalizedBoundingBox = x.NormalizedBoundingBox;
                y.NormalizedCenter      = x.NormalizedCenter;
                y.MoveToZone(x.CurrentZone, timestamp);
                return(y);
            }));

            // For each card in next, look for a card in 'current' Ether zone that may has an approved transition
            movedResult.AddRange(CardList <CardInPlay> .Extract(ref next, ref current[(int)PlayZone.Ether], (x, y) =>
            {
                // Skip values in next that are incorrect zone
                if (y.CurrentZone != PlayZone.Ether)
                {
                    return(-1);
                }
                int z = x.TheCard.Cost - y.TheCard.Cost;
                if (z == 0)
                {
                    z = x.TheCard.Name.CompareTo(y.TheCard.Name);
                }
                // Skip values in current that are incorrect zone
                if (z == 0 && GameBoard.TransitionResult.Proceed != GameBoard.TransitionAllowed(y.LastNonEtherZone, PlayZone.Unknown, x.CurrentZone, isInitialDraw, isLocalPlayer))
                {
                    z = -1;
                }
                return(z);
            }, (x, y) =>
            {
                x.LastNonEtherZone = y.LastNonEtherZone;
                x.LastZone         = PlayZone.Ether;
                x.IsFromDeck       = y.IsFromDeck;
                x.ChampionCode     = y.ChampionCode;
                return(x);
            }));

            // For each card in next, look for approved transitions, skipping current in deck
            for (int i = 0; i < NumZones; i++)
            {
                if (i == (int)PlayZone.Deck)
                {
                    continue;
                }

                movedResult.AddRange(CardList <CardInPlay> .Extract(ref next, ref current[i], (x, y) =>
                {
                    // Skip values in next that are incorrect zone
                    int z = x.TheCard.Cost - y.TheCard.Cost;
                    if (z == 0)
                    {
                        z = x.TheCard.Name.CompareTo(y.TheCard.Name);
                    }
                    if (z == 0 && GameBoard.TransitionResult.Proceed != GameBoard.TransitionAllowed(y.CurrentZone, y.LastNonEtherZone, x.CurrentZone, isInitialDraw, isLocalPlayer))
                    {
                        z = -1;
                    }
                    return(z);
                }, (x, y) =>
                {
                    x.SetLastZone(y.CurrentZone);
                    x.IsFromDeck   = y.IsFromDeck;
                    x.ChampionCode = y.ChampionCode;
                    return(x);
                }));
            }

            // For each card in next, look for declined transitions
            for (int i = 0; i < NumZones; i++)
            {
                stationaryResult.AddRange(CardList <CardInPlay> .Extract(ref next, ref current[i], (x, y) =>
                {
                    // Skip values in next that are incorrect zone
                    int z = x.TheCard.Cost - y.TheCard.Cost;
                    if (z == 0)
                    {
                        z = x.TheCard.Name.CompareTo(y.TheCard.Name);
                    }
                    if (z == 0 && GameBoard.TransitionResult.Stay != GameBoard.TransitionAllowed(y.CurrentZone, y.LastNonEtherZone, x.CurrentZone, isInitialDraw, isLocalPlayer))
                    {
                        z = -1;
                    }
                    return(z);
                }, (x, y) =>
                {
                    y.BoundingBox           = x.BoundingBox;
                    y.NormalizedBoundingBox = x.NormalizedBoundingBox;
                    y.NormalizedCenter      = x.NormalizedCenter;
                    return(y);
                }));
            }

            // For each card in 'hand', look for champion transformations
            stationaryResult.AddRange(CardList <CardInPlay> .Extract(ref next, ref current[(int)PlayZone.Hand], (x, y) =>
            {
                // Skip values in next that are incorrect zone
                if ((int)x.CurrentZone != (int)PlayZone.Hand)
                {
                    return(-1);
                }
                if (y.ChampionCode.Length == 0)
                {
                    return(-1);
                }
                if (y.TheCard.SuperType == "Champion")
                {
                    var spellCode = y.TheCard.AssociatedCardCodes.Last();
                    var spellCard = CardLibrary.GetCard(spellCode);
                    if (spellCard.FlavorText == x.TheCard.FlavorText)
                    {
                        return(0);
                    }
                }
                else if (x.TheCard.SuperType == "Champion")
                {
                    if (x.CardCode == y.ChampionCode)
                    {
                        return(0);
                    }
                }
                return(-1);
            }, (x, y) =>
            {
                x.LastZone     = y.CurrentZone;
                x.IsFromDeck   = y.IsFromDeck;
                x.ChampionCode = y.ChampionCode;
                return(x);
            }));


            // For each card in next, look for approved transitions from deck
            movedResult.AddRange(CardList <CardInPlay> .Extract(ref next, ref current[(int)PlayZone.Deck], (x, y) =>
            {
                // Skip values in next that are incorrect zone
                int z = x.TheCard.Cost - y.TheCard.Cost;
                if (z == 0)
                {
                    z = x.TheCard.Name.CompareTo(y.TheCard.Name);
                }
                if (z == 0 && GameBoard.TransitionResult.Proceed != GameBoard.TransitionAllowed(y.CurrentZone, y.LastNonEtherZone, x.CurrentZone, isInitialDraw, isLocalPlayer))
                {
                    z = -1;
                }
                return(z);
            }, (x, y) =>
            {
                x.SetLastZone(y.CurrentZone);
                x.IsFromDeck   = y.IsFromDeck;
                x.ChampionCode = y.ChampionCode;
                return(x);
            }));

            // Move not in deck to ether
            for (int i = 0; i < NumZones; i++)
            {
                PlayZone newZone = PlayZone.Ether;
                if (i == (int)PlayZone.Deck || i == (int)PlayZone.Graveyard || i == (int)PlayZone.Ether)
                {
                    continue;
                }
                if (i == (int)PlayZone.Stage && isInitialDraw)
                {
                    newZone = PlayZone.Deck;
                }

                for (int j = 0; j < current[i].Count; j++)
                {
                    current[i][j].MoveToZone(newZone, timestamp);
                }
                movedResult.AddRange(current[i]);
                current[i].Clear();
            }

            // Remove cards in next that are in a zone that does not accept from Unknown
            next = next.GetSubset(x => GameBoard.TransitionResult.Proceed == GameBoard.TransitionAllowed(x.LastNonEtherZone, PlayZone.Unknown, x.CurrentZone, isInitialDraw, isLocalPlayer));

            // Add remaining cards to the moved set
            movedResult.AddRange(next);

            // Log all moves
            foreach (var card in movedResult)
            {
                LogMove(card, card.CurrentZone != PlayZone.Ether);
            }

            // Re-add all the cars to the zones
            for (int i = 0; i < NumZones; i++)
            {
                current[i].AddRange(stationaryResult.GetSubset(x => (int)x.CurrentZone == i));
                current[i].AddRange(movedResult.GetSubset(x => (int)x.CurrentZone == i));
            }
        }
        /// <summary>
        /// Process next rectangle layout
        /// </summary>
        /// <param name="overlay"></param>
        /// <param name="timestamp"></param>
        public void ProcessNext(Dictionary <string, JsonElement> overlay, double timestamp)
        {
            TimeCounter++;

            if (!TestMode && PlayerCards[(int)PlayZone.Deck].Count == 0 && PlayerCards[(int)PlayZone.Graveyard].Count == 0)
            {
                // Have not received the deck yet
                return;
            }
            CardList <CardInPlay> cardsInPlay = new CardList <CardInPlay>();

            bool isChampionUpgrading = false;

            if (overlay != null)
            {
                var screen = overlay["Screen"].ToObject <Dictionary <string, JsonElement> >();
                ScreenWidth  = screen["ScreenWidth"].GetInt32();
                ScreenHeight = screen["ScreenHeight"].GetInt32();

                // We normalize elements' bounding box based on screen height. However, if screen ratio becomes
                // too high, screen expands height-wise. To make sure we have same behavior as before,
                // We adjust the height accordingly.
                int normalizedScreenHeight = (int)(0.5 + GameBoard.ComputeNormalizedScreenHeight(ScreenWidth, ScreenHeight));

                Point correctionOffset = new Point(0, 0);
                var   rectangles       = overlay["Rectangles"].ToObject <Dictionary <string, JsonElement>[]>();
                foreach (var dict in rectangles)
                {
                    string cardCode = dict["CardCode"].GetString();
                    if (cardCode == "face")
                    {
                        if (dict["LocalPlayer"].GetBoolean())
                        {
                            int x = dict["TopLeftX"].GetInt32();
                            int y = dict["TopLeftY"].GetInt32();
                            if (IsInitialDraw)
                            {
                                LocalPlayerFace = new Point(x, y);
                            }
                            else
                            {
                                correctionOffset.X = LocalPlayerFace.X - x;
                                correctionOffset.Y = LocalPlayerFace.Y - y;
                            }
                        }

                        // We don't process face
                        continue;
                    }
                    Card card = CardLibrary.GetCard(cardCode);

                    // Also ignore abilities
                    if (card.Type != "Ability")
                    {
                        CardInPlay c = new CardInPlay(dict, ScreenWidth, ScreenHeight, correctionOffset, normalizedScreenHeight);
                        cardsInPlay.Add(card.Cost, card.Name, c);
                        if (c.CurrentZone == PlayZone.Hand && c.NormalizedCenter.Y > 1.3f)
                        {
                            // Champion is upgrading
                            isChampionUpgrading = true;
                        }
                    }
                }
            }

            // Split next elements between owners. Also, disregard cards with unknown zone
            CardList <CardInPlay> nextPlayerCards   = new CardList <CardInPlay>();
            CardList <CardInPlay> nextOpponentCards = new CardList <CardInPlay>();

            cardsInPlay.Split(ref nextPlayerCards, ref nextOpponentCards, x => x.Owner == PlayerType.LocalPlayer && x.CurrentZone != PlayZone.Unknown);
            Callback.OnElementsUpdate(nextPlayerCards, nextOpponentCards, ScreenWidth, ScreenHeight);
            if (isChampionUpgrading)
            {
                // Bail, champion is upgrading
                return;
            }

            if (nextPlayerCards.FindIndex(x => x.CurrentZone == PlayZone.Stage) >= 0)
            {
                CardsAreOnStage = true;
            }
            else if (CardsAreOnStage)
            {
                // Transition, reset ether timers
                for (int i = 0; i < PlayerCards[(int)PlayZone.Ether].Count; i++)
                {
                    PlayerCards[(int)PlayZone.Ether][i].EtherStartTime = timestamp;
                }
                CardsAreOnStage = false;
            }

            // Mark all opponent cards as "from deck" for now
            // This is because we cannot reliably know which ones are not from deck yet
            for (int i = 0; i < nextOpponentCards.Count; i++)
            {
                nextOpponentCards[i].IsFromDeck = nextOpponentCards[i].TheCard.IsCollectible;
            }

            MoveToNext(ref PlayerCards, nextPlayerCards, timestamp, IsInitialDraw, true);
            if (IsInitialDraw)
            {
                // Initial draw until we add some cards to hand
                IsInitialDraw = (PlayerCards[(int)PlayZone.Hand].Count() == 0);
            }


            MoveToNext(ref OpponentCards, nextOpponentCards, timestamp, false, false);

            // Purge Ether of spells that have been cast
            bool thoroughCleanUp = false;
            int  handCardIndex   = PlayerCards[(int)PlayZone.Hand].FindIndex(x => x.CurrentZone == PlayZone.Hand);

            if (PlayerCards[(int)PlayZone.Hand].Count() > 0 && PlayerCards[(int)PlayZone.Hand][0].NormalizedBoundingBox.Height < 0.235f)
            {
                thoroughCleanUp = true;
            }

            // Clean up ether -- generally move expired cards to graveyard, but also send cast champion spells back to deck
            if (!CardsAreOnStage)
            {
                // Find all the expiring
                CardList <CardInPlay> cardsGoingToGraveyard = CleanUpEther(ref PlayerCards[(int)PlayZone.Ether], timestamp, thoroughCleanUp);
                for (int i = 0; i < cardsGoingToGraveyard.Count; i++)
                {
                    if (cardsGoingToGraveyard[i].ChampionCode.Length > 0 && cardsGoingToGraveyard[i].TheCard.Type == "Spell")
                    {
                        var championCard       = CardLibrary.GetCard(cardsGoingToGraveyard[i].ChampionCode);
                        var championCardInPlay = new CardInPlay(championCard);
                        championCardInPlay.LastZone         = PlayZone.Ether;
                        championCardInPlay.LastNonEtherZone = cardsGoingToGraveyard[i].LastNonEtherZone;
                        championCardInPlay.CurrentZone      = PlayZone.Deck;
                        PlayerCards[(int)PlayZone.Deck].Add(championCard.Cost, championCard.Name, championCardInPlay);
                        cardsGoingToGraveyard[i].IsFromDeck = false;
                        Log.WriteLine(LogType.Player, "[{0}{1}] Shuffled back: {2}", championCardInPlay.LastNonEtherZone.ToString()[0],
                                      championCardInPlay.CurrentZone.ToString()[0], championCard.Name);
                    }
                }

                PlayerCards[(int)PlayZone.Graveyard].AddRange(cardsGoingToGraveyard);
            }
            OpponentCards[(int)PlayZone.Graveyard].AddRange(CleanUpEther(ref OpponentCards[(int)PlayZone.Ether], timestamp, true));

            NotifyCardSetUpdates();

            // Update attacking player
            int numPlayerAttackers   = PlayerCards[(int)PlayZone.Battle].Count + PlayerCards[(int)PlayZone.Windup].Count + PlayerCards[(int)PlayZone.Attack].Count;
            int numOpponentAttackers = OpponentCards[(int)PlayZone.Battle].Count + OpponentCards[(int)PlayZone.Windup].Count + OpponentCards[(int)PlayZone.Attack].Count;

            if (AttackingPlayer == PlayerType.None)
            {
                if (numPlayerAttackers != numOpponentAttackers)
                {
                    AttackingPlayer = numPlayerAttackers > numOpponentAttackers ? PlayerType.LocalPlayer : PlayerType.Opponent;
                }
            }
            else if (numPlayerAttackers == 0 && numOpponentAttackers == 0)
            {
                AttackingPlayer = PlayerType.None;
            }

            // Here we try to identify opponent's cards that did not start in deck
            // If a card appears in hand and we can see it, it was likely generated
            // Also, if a card appears in the field without going through stage first
            // it was likely generated by spell or another unit
            // Mark these cards as not from deck
            // Unfortunately, we have no way of tracking nabbed cards, so those will show up
            // as from deck
            for (int i = 0; i < OpponentCards[(int)PlayZone.Field].Count; i++)
            {
                if (OpponentCards[(int)PlayZone.Field][i].LastNonEtherZone == PlayZone.Unknown)
                {
                    OpponentCards[(int)PlayZone.Field][i].IsFromDeck = false;
                }
            }
            for (int i = 0; i < OpponentCards[(int)PlayZone.Hand].Count; i++)
            {
                if (OpponentCards[(int)PlayZone.Hand][i].LastNonEtherZone == PlayZone.Unknown)
                {
                    OpponentCards[(int)PlayZone.Hand][i].IsFromDeck = false;
                }
            }
        }