private void TurnChange(IHsGameState gameState, IGame game)
		{
			if(!gameState.SetupDone || game.PlayerEntity == null)
				return;
			var activePlayer = game.PlayerEntity.HasTag(CURRENT_PLAYER) ? ActivePlayer.Player : ActivePlayer.Opponent;
			gameState.GameHandler.TurnStart(activePlayer, gameState.GetTurnNumber());
			if(activePlayer == ActivePlayer.Player)
				gameState.PlayerUsedHeroPower = false;
			else
				gameState.OpponentUsedHeroPower = false;
		}
        private void ZoneChangeFromOther(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
        {
            if (!game.Entities.TryGetValue(id, out var entity))
            {
                return;
            }
            var currentBlockCardId = gameState.CurrentBlock?.CardId ?? "";

            if (entity.Info.OriginalZone == DECK && value != (int)DECK)
            {
                //This entity was moved from DECK to SETASIDE to HAND, e.g. by Tracking
                entity.Info.Discarded = false;
                ZoneChangeFromDeck(gameState, id, game, value, prevValue, controller, cardId);
                return;
            }
            entity.Info.Created = true;
            switch ((Zone)value)
            {
            case PLAY:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerCreateInPlay(entity, cardId, gameState.GetTurnNumber());
                }
                if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentCreateInPlay(entity, cardId, gameState.GetTurnNumber());
                }
                break;

            case DECK:
                if (controller == game.Player.Id)
                {
                    if (gameState.JoustReveals > 0)
                    {
                        break;
                    }
                    gameState.GameHandler.HandlePlayerGetToDeck(entity, cardId, gameState.GetTurnNumber());
                }
                if (controller == game.Opponent.Id)
                {
                    if (gameState.JoustReveals > 0)
                    {
                        break;
                    }
                    gameState.GameHandler.HandleOpponentGetToDeck(entity, gameState.GetTurnNumber());
                }
                break;

            case HAND:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerGet(entity, cardId, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentGet(entity, gameState.GetTurnNumber(), id);
                }
                break;

            case Zone.SECRET:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerSecretPlayed(entity, cardId, gameState.GetTurnNumber(), (Zone)prevValue, currentBlockCardId);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentSecretPlayed(entity, cardId, -1, gameState.GetTurnNumber(), (Zone)prevValue, id);
                }
                break;

            case SETASIDE:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerCreateInSetAside(entity, gameState.GetTurnNumber());
                }
                if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentCreateInSetAside(entity, gameState.GetTurnNumber());
                    if (gameState.CurrentBlock?.CardId == Collectible.Neutral.GrandArchivist &&
                        gameState.CurrentBlock.EntityDiscardedByArchivist != null)
                    {
                        gameState.CurrentBlock.EntityDiscardedByArchivist.CardId = entity.Info.LatestCardId;
                    }
                }
                break;

            default:
                Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
                break;
            }
        }
Ejemplo n.º 3
0
        public void Handle(string logLine, IHsGameState gameState, IGame game)
        {
            var creationTag = false;

            if (GameEntityRegex.IsMatch(logLine))
            {
                var match = GameEntityRegex.Match(logLine);
                var id    = int.Parse(match.Groups["id"].Value);
                if (!game.Entities.ContainsKey(id))
                {
                    game.Entities.Add(id, new Entity(id)
                    {
                        Name = "GameEntity"
                    });
                }
                gameState.SetCurrentEntity(id);
                if (gameState.DeterminedPlayers)
                {
                    _tagChangeHandler.InvokeQueuedActions(game);
                }
                return;
            }
            if (PlayerEntityRegex.IsMatch(logLine))
            {
                var match = PlayerEntityRegex.Match(logLine);
                var id    = int.Parse(match.Groups["id"].Value);
                if (!game.Entities.ContainsKey(id))
                {
                    game.Entities.Add(id, new Entity(id));
                }
                if (gameState.WasInProgress)
                {
                    game.Entities[id].Name = game.GetStoredPlayerName(id);
                }
                gameState.SetCurrentEntity(id);
                if (gameState.DeterminedPlayers)
                {
                    _tagChangeHandler.InvokeQueuedActions(game);
                }
                return;
            }
            if (TagChangeRegex.IsMatch(logLine))
            {
                var match     = TagChangeRegex.Match(logLine);
                var rawEntity = match.Groups["entity"].Value.Replace("UNKNOWN ENTITY ", "");
                if (rawEntity.StartsWith("[") && EntityRegex.IsMatch(rawEntity))
                {
                    var entity = EntityRegex.Match(rawEntity);
                    var id     = int.Parse(entity.Groups["id"].Value);
                    _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, id, match.Groups["value"].Value, game);
                }
                else if (int.TryParse(rawEntity, out int entityId))
                {
                    _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entityId, match.Groups["value"].Value, game);
                }
                else
                {
                    var entity = game.Entities.FirstOrDefault(x => x.Value.Name == rawEntity);

                    if (entity.Value == null)
                    {
                        var players            = game.Entities.Where(x => x.Value.HasTag(GameTag.PLAYER_ID)).Take(2).ToList();
                        var unnamedPlayers     = players.Where(x => string.IsNullOrEmpty(x.Value.Name)).ToList();
                        var unknownHumanPlayer = players.FirstOrDefault(x => x.Value.Name == "UNKNOWN HUMAN PLAYER");
                        if (unnamedPlayers.Count == 0 && unknownHumanPlayer.Value != null)
                        {
                            Log.Info("Updating UNKNOWN HUMAN PLAYER");
                            entity = unknownHumanPlayer;
                        }

                        //while the id is unknown, store in tmp entities
                        var tmpEntity = _tmpEntities.FirstOrDefault(x => x.Name == rawEntity);
                        if (tmpEntity == null)
                        {
                            tmpEntity = new Entity(_tmpEntities.Count + 1)
                            {
                                Name = rawEntity
                            };
                            _tmpEntities.Add(tmpEntity);
                        }
                        Enum.TryParse(match.Groups["tag"].Value, out GameTag tag);
                        var value = GameTagHelper.ParseTag(tag, match.Groups["value"].Value);
                        if (unnamedPlayers.Count == 1)
                        {
                            entity = unnamedPlayers.Single();
                        }
                        else if (unnamedPlayers.Count == 2 && tag == GameTag.CURRENT_PLAYER && value == 0)
                        {
                            entity = game.Entities.FirstOrDefault(x => x.Value?.HasTag(GameTag.CURRENT_PLAYER) ?? false);
                        }
                        if (entity.Value != null)
                        {
                            entity.Value.Name = tmpEntity.Name;
                            foreach (var t in tmpEntity.Tags)
                            {
                                _tagChangeHandler.TagChange(gameState, t.Key, entity.Key, t.Value, game);
                            }
                            _tmpEntities.Remove(tmpEntity);
                            _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entity.Key, match.Groups["value"].Value, game);
                        }
                        if (_tmpEntities.Contains(tmpEntity))
                        {
                            tmpEntity.SetTag(tag, value);
                            var player = game.Player.Name == tmpEntity.Name ? game.Player
                                                                                : (game.Opponent.Name == tmpEntity.Name ? game.Opponent : null);
                            if (player != null)
                            {
                                var playerEntity = game.Entities.FirstOrDefault(x => x.Value.GetTag(GameTag.PLAYER_ID) == player.Id).Value;
                                if (playerEntity != null)
                                {
                                    playerEntity.Name = tmpEntity.Name;
                                    foreach (var t in tmpEntity.Tags)
                                    {
                                        _tagChangeHandler.TagChange(gameState, t.Key, playerEntity.Id, t.Value, game);
                                    }
                                    _tmpEntities.Remove(tmpEntity);
                                }
                            }
                        }
                    }
                    else
                    {
                        _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entity.Key, match.Groups["value"].Value, game);
                    }
                }
            }
            else if (CreationRegex.IsMatch(logLine))
            {
                var match         = CreationRegex.Match(logLine);
                var id            = int.Parse(match.Groups["id"].Value);
                var cardId        = EnsureValidCardID(match.Groups["cardId"].Value);
                var zone          = GameTagHelper.ParseEnum <Zone>(match.Groups["zone"].Value);
                var guessedCardId = false;
                if (!game.Entities.ContainsKey(id))
                {
                    if (string.IsNullOrEmpty(cardId) && zone != Zone.SETASIDE)
                    {
                        var blockId = gameState.CurrentBlock?.Id;
                        if (blockId.HasValue && gameState.KnownCardIds.ContainsKey(blockId.Value))
                        {
                            cardId = gameState.KnownCardIds[blockId.Value].FirstOrDefault();
                            if (!string.IsNullOrEmpty(cardId))
                            {
                                Log.Info($"Found known cardId for entity {id}: {cardId}");
                                gameState.KnownCardIds[blockId.Value].Remove(cardId);
                                guessedCardId = true;
                            }
                        }
                    }
                    var entity = new Entity(id)
                    {
                        CardId = cardId
                    };
                    if (guessedCardId)
                    {
                        entity.Info.GuessedCardState = GuessedCardState.Guessed;
                    }
                    game.Entities.Add(id, entity);

                    if (gameState.CurrentBlock != null && (entity.CardId?.ToUpper().Contains("HERO") ?? false))
                    {
                        gameState.CurrentBlock.HasFullEntityHeroPackets = true;
                    }
                }
                gameState.SetCurrentEntity(id);
                if (gameState.DeterminedPlayers)
                {
                    _tagChangeHandler.InvokeQueuedActions(game);
                }
                gameState.CurrentEntityHasCardId = !string.IsNullOrEmpty(cardId);
                gameState.CurrentEntityZone      = zone;
                return;
            }
            else if (UpdatingEntityRegex.IsMatch(logLine))
            {
                var match     = UpdatingEntityRegex.Match(logLine);
                var cardId    = EnsureValidCardID(match.Groups["cardId"].Value);
                var rawEntity = match.Groups["entity"].Value;
                var type      = match.Groups["type"].Value;
                int entityId;
                if (rawEntity.StartsWith("[") && EntityRegex.IsMatch(rawEntity))
                {
                    var entity = EntityRegex.Match(rawEntity);
                    entityId = int.Parse(entity.Groups["id"].Value);
                }
                else if (!int.TryParse(rawEntity, out entityId))
                {
                    entityId = -1;
                }
                if (entityId != -1)
                {
                    if (!game.Entities.ContainsKey(entityId))
                    {
                        game.Entities.Add(entityId, new Entity(entityId));
                    }
                    var entity = game.Entities[entityId];
                    if (string.IsNullOrEmpty(entity.CardId))
                    {
                        entity.CardId = cardId;
                    }
                    entity.Info.LatestCardId = cardId;
                    if (type == "SHOW_ENTITY")
                    {
                        if (entity.Info.GuessedCardState != GuessedCardState.None)
                        {
                            entity.Info.GuessedCardState = GuessedCardState.Revealed;
                        }
                    }
                    if (type == "CHANGE_ENTITY")
                    {
                        if (!entity.Info.OriginalEntityWasCreated.HasValue)
                        {
                            entity.Info.OriginalEntityWasCreated = entity.Info.Created;
                        }
                        if (entity.GetTag(GameTag.TRANSFORMED_FROM_CARD) == 46706)
                        {
                            gameState.ChameleosReveal = new Tuple <int, string>(entityId, cardId);
                        }
                    }
                    gameState.SetCurrentEntity(entityId);
                    if (gameState.DeterminedPlayers)
                    {
                        _tagChangeHandler.InvokeQueuedActions(game);
                    }
                }
                if (gameState.JoustReveals > 0)
                {
                    if (game.Entities.TryGetValue(entityId, out Entity currentEntity))
                    {
                        if (currentEntity.IsControlledBy(game.Opponent.Id))
                        {
                            gameState.GameHandler.HandleOpponentJoust(currentEntity, cardId, gameState.GetTurnNumber());
                        }
                        else if (currentEntity.IsControlledBy(game.Player.Id))
                        {
                            gameState.GameHandler.HandlePlayerJoust(currentEntity, cardId, gameState.GetTurnNumber());
                        }
                    }
                }
                return;
            }
            else if (CreationTagRegex.IsMatch(logLine) && !logLine.Contains("HIDE_ENTITY"))
            {
                var match = CreationTagRegex.Match(logLine);
                _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, gameState.CurrentEntityId, match.Groups["value"].Value, game, true);
                creationTag = true;
            }
            else if (logLine.Contains("HIDE_ENTITY"))
            {
                var match = HideEntityRegex.Match(logLine);
                if (match.Success)
                {
                    var id = int.Parse(match.Groups["id"].Value);
                    if (game.Entities.TryGetValue(id, out var entity))
                    {
                        if (entity.Info.GuessedCardState == GuessedCardState.Revealed)
                        {
                            entity.Info.GuessedCardState = GuessedCardState.Guessed;
                        }
                        if (gameState.CurrentBlock?.CardId == Collectible.Neutral.KingTogwaggle ||
                            gameState.CurrentBlock?.CardId == NonCollectible.Neutral.KingTogwaggle_KingsRansomToken)
                        {
                            entity.Info.Hidden = true;
                        }
                    }
                }
            }
            if (logLine.Contains("End Spectator") && !game.IsInMenu)
            {
                gameState.GameHandler.HandleGameEnd(false);
            }
            else if (logLine.Contains("BLOCK_START"))
            {
                var match            = BlockStartRegex.Match(logLine);
                var blockType        = match.Success ? match.Groups["type"].Value : null;
                var cardId           = match.Success ? match.Groups["Id"].Value : null;
                var target           = GetTargetCardId(match);
                var correspondPlayer = match.Success ? int.Parse(match.Groups["player"].Value) : -1;
                gameState.BlockStart(blockType, cardId, target);

                if (match.Success && (blockType == "TRIGGER" || blockType == "POWER"))
                {
                    var playerEntity =
                        game.Entities.FirstOrDefault(
                            e => e.Value.HasTag(GameTag.PLAYER_ID) && e.Value.GetTag(GameTag.PLAYER_ID) == game.Player.Id);
                    var opponentEntity =
                        game.Entities.FirstOrDefault(
                            e => e.Value.HasTag(GameTag.PLAYER_ID) && e.Value.GetTag(GameTag.PLAYER_ID) == game.Opponent.Id);

                    var    actionStartingCardId   = match.Groups["cardId"].Value.Trim();
                    var    actionStartingEntityId = int.Parse(match.Groups["id"].Value);
                    Entity actionStartingEntity   = null;

                    if (string.IsNullOrEmpty(actionStartingCardId))
                    {
                        if (game.Entities.TryGetValue(actionStartingEntityId, out actionStartingEntity))
                        {
                            actionStartingCardId = actionStartingEntity.CardId;
                        }
                    }
                    if (string.IsNullOrEmpty(actionStartingCardId))
                    {
                        return;
                    }
                    if (actionStartingCardId == Collectible.Shaman.Shudderwock)
                    {
                        var effectCardId = match.Groups["effectCardId"].Value;
                        if (!string.IsNullOrEmpty(effectCardId))
                        {
                            actionStartingCardId = effectCardId;
                        }
                    }
                    if (actionStartingCardId == NonCollectible.Rogue.ValeeratheHollow_ShadowReflectionToken)
                    {
                        actionStartingCardId = cardId;
                    }
                    if (blockType == "TRIGGER" && actionStartingCardId == Collectible.Neutral.AugmentedElekk)
                    {
                        if (gameState.CurrentBlock.Parent != null)
                        {
                            actionStartingCardId = gameState.CurrentBlock.Parent.CardId;
                            blockType            = gameState.CurrentBlock.Parent.Type;
                            target = gameState.CurrentBlock.Parent.Target;
                        }
                    }
                    if (blockType == "TRIGGER")
                    {
                        switch (actionStartingCardId)
                        {
                        // Todo: Add Demon Hunter
                        case Collectible.Rogue.TradePrinceGallywix:
                            if (!game.Entities.TryGetValue(gameState.LastCardPlayed, out var lastPlayed))
                            {
                                break;
                            }
                            AddKnownCardId(gameState, lastPlayed.CardId);
                            AddKnownCardId(gameState, NonCollectible.Neutral.TradePrinceGallywix_GallywixsCoinToken);
                            break;

                        case Collectible.Shaman.WhiteEyes:
                            AddKnownCardId(gameState, NonCollectible.Shaman.WhiteEyes_TheStormGuardianToken);
                            break;

                        case Collectible.Hunter.RaptorHatchling:
                            AddKnownCardId(gameState, NonCollectible.Hunter.RaptorHatchling_RaptorPatriarchToken);
                            break;

                        case Collectible.Warrior.DirehornHatchling:
                            AddKnownCardId(gameState, NonCollectible.Warrior.DirehornHatchling_DirehornMatriarchToken);
                            break;

                        case Collectible.Mage.FrozenClone:
                            AddKnownCardId(gameState, target, 2);
                            break;

                        case Collectible.Shaman.Moorabi:
                        case Collectible.Rogue.SonyaShadowdancer:
                            AddKnownCardId(gameState, target);
                            break;

                        case Collectible.Neutral.HoardingDragon:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheCoinCore, 2);
                            break;

                        case Collectible.Priest.GildedGargoyle:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheCoinCore);
                            break;

                        case Collectible.Druid.AstralTiger:
                            AddKnownCardId(gameState, Collectible.Druid.AstralTiger);
                            break;

                        case Collectible.Rogue.Kingsbane:
                            AddKnownCardId(gameState, Collectible.Rogue.Kingsbane);
                            break;

                        case Collectible.Neutral.WeaselTunneler:
                            AddKnownCardId(gameState, Collectible.Neutral.WeaselTunneler);
                            break;

                        case Collectible.Neutral.SparkDrill:
                            AddKnownCardId(gameState, NonCollectible.Neutral.SparkDrill_SparkToken, 2);
                            break;

                        case NonCollectible.Neutral.HakkartheSoulflayer_CorruptedBloodToken:
                            AddKnownCardId(gameState, NonCollectible.Neutral.HakkartheSoulflayer_CorruptedBloodToken, 2);
                            break;

                        //TODO: Gral, the Shark?
                        case Collectible.Paladin.ImmortalPrelate:
                            AddKnownCardId(gameState, Collectible.Paladin.ImmortalPrelate);
                            break;

                        case Collectible.Warrior.Wrenchcalibur:
                            AddKnownCardId(gameState, NonCollectible.Neutral.SeaforiumBomber_BombToken);
                            break;

                        case Collectible.Priest.SpiritOfTheDead:
                            if (correspondPlayer == game.Player.Id)
                            {
                                AddKnownCardId(gameState, game.Player.LastDiedMinionCardId);
                            }
                            else if (correspondPlayer == game.Opponent.Id)
                            {
                                AddKnownCardId(gameState, game.Opponent.LastDiedMinionCardId);
                            }
                            break;

                        case Collectible.Druid.SecureTheDeck:
                            AddKnownCardId(gameState, Collectible.Druid.ClawLegacy, 3);
                            break;

                        case Collectible.Rogue.Waxadred:
                            AddKnownCardId(gameState, NonCollectible.Rogue.Waxadred_WaxadredsCandleToken);
                            break;

                        case Collectible.Neutral.BadLuckAlbatross:
                            AddKnownCardId(gameState, NonCollectible.Neutral.BadLuckAlbatross_AlbatrossToken, 2);
                            break;

                        case Collectible.Priest.ReliquaryOfSouls:
                            AddKnownCardId(gameState, NonCollectible.Priest.ReliquaryofSouls_ReliquaryPrimeToken);
                            break;

                        case Collectible.Mage.AstromancerSolarian:
                            AddKnownCardId(gameState, NonCollectible.Mage.AstromancerSolarian_SolarianPrimeToken);
                            break;

                        case Collectible.Warlock.KanrethadEbonlocke:
                            AddKnownCardId(gameState, NonCollectible.Warlock.KanrethadEbonlocke_KanrethadPrimeToken);
                            break;

                        case Collectible.Paladin.MurgurMurgurgle:
                            AddKnownCardId(gameState, NonCollectible.Paladin.MurgurMurgurgle_MurgurglePrimeToken);
                            break;

                        case Collectible.Rogue.Akama:
                            AddKnownCardId(gameState, NonCollectible.Rogue.Akama_AkamaPrimeToken);
                            break;

                        case Collectible.Druid.ArchsporeMsshifn:
                            AddKnownCardId(gameState, NonCollectible.Druid.ArchsporeMsshifn_MsshifnPrimeToken);
                            break;

                        case Collectible.Shaman.LadyVashj:
                            AddKnownCardId(gameState, NonCollectible.Shaman.LadyVashj_VashjPrimeToken);
                            break;

                        case Collectible.Hunter.ZixorApexPredator:
                            AddKnownCardId(gameState, NonCollectible.Hunter.ZixorApexPredator_ZixorPrimeToken);
                            break;

                        case Collectible.Warrior.KargathBladefist:
                            AddKnownCardId(gameState, NonCollectible.Warrior.KargathBladefist_KargathPrimeToken);
                            break;

                        case Collectible.Neutral.SneakyDelinquent:
                            AddKnownCardId(gameState, NonCollectible.Neutral.SneakyDelinquent_SpectralDelinquentToken);
                            break;

                        case Collectible.Neutral.FishyFlyer:
                            AddKnownCardId(gameState, NonCollectible.Neutral.FishyFlyer_SpectralFlyerToken);
                            break;

                        case Collectible.Neutral.SmugSenior:
                            AddKnownCardId(gameState, NonCollectible.Neutral.SmugSenior_SpectralSeniorToken);
                            break;

                        case Collectible.Rogue.Plagiarize:
                            if (actionStartingEntity != null)
                            {
                                var player = actionStartingEntity.IsControlledBy(game.Player.Id) ? game.Opponent : game.Player;
                                foreach (var card in player.CardsPlayedThisTurn)
                                {
                                    AddKnownCardId(gameState, card);
                                }
                            }
                            break;

                        case Collectible.Neutral.KeymasterAlabaster:
                            // The player controlled side of this is handled by TagChangeActions.OnCardCopy
                            if (actionStartingEntity != null && actionStartingEntity.IsControlledBy(game.Opponent.Id) && game.Player.LastDrawnCardId != null)
                            {
                                AddKnownCardId(gameState, game.Player.LastDrawnCardId);
                            }
                            break;

                        case Collectible.Neutral.EducatedElekk:
                            if (actionStartingEntity != null)
                            {
                                if (actionStartingEntity.IsInGraveyard)
                                {
                                    foreach (var card in actionStartingEntity.Info.StoredCardIds)
                                    {
                                        AddKnownCardId(gameState, card);
                                    }
                                }
                                else if (game.Entities.TryGetValue(gameState.LastCardPlayed, out var lastPlayedEntity) && lastPlayedEntity.CardId != null)
                                {
                                    actionStartingEntity.Info.StoredCardIds.Add(lastPlayedEntity.CardId);
                                }
                            }
                            break;

                        case Collectible.Shaman.DiligentNotetaker:
                            if (game.Entities.TryGetValue(gameState.LastCardPlayed, out var lastPlayedEntity1) && lastPlayedEntity1.CardId != null)
                            {
                                AddKnownCardId(gameState, lastPlayedEntity1.CardId);
                            }
                            break;

                        case Collectible.Neutral.CthunTheShattered:
                            // The pieces are created in random order. So we can not assign predicted ids to entities the way we usually do.
                            if (actionStartingEntity != null)
                            {
                                var player = actionStartingEntity.IsControlledBy(game.Player.Id) ? game.Player : game.Opponent;
                                player.PredictUniqueCardInDeck(NonCollectible.Neutral.CThuntheShattered_EyeOfCthunToken, true);
                                player.PredictUniqueCardInDeck(NonCollectible.Neutral.CThuntheShattered_BodyOfCthunToken, true);
                                player.PredictUniqueCardInDeck(NonCollectible.Neutral.CThuntheShattered_MawOfCthunToken, true);
                                player.PredictUniqueCardInDeck(NonCollectible.Neutral.CThuntheShattered_HeartOfCthunToken, true);
                            }
                            break;
                        }
                    }
                    else                     //POWER
                    {
                        switch (actionStartingCardId)
                        {
                        // Todo: Add Demon Hunter
                        case Collectible.Rogue.GangUp:
                        case Collectible.Hunter.DireFrenzy:
                        case Collectible.Rogue.LabRecruiter:
                            AddKnownCardId(gameState, target, 3);
                            break;

                        case Collectible.Rogue.BeneathTheGrounds:
                            AddKnownCardId(gameState, NonCollectible.Rogue.BeneaththeGrounds_NerubianAmbushToken, 3);
                            break;

                        case Collectible.Warrior.IronJuggernaut:
                            AddKnownCardId(gameState, NonCollectible.Warrior.IronJuggernaut_BurrowingMineToken);
                            break;

                        case Collectible.Druid.Recycle:
                        case Collectible.Mage.ManicSoulcaster:
                        case Collectible.Neutral.ZolaTheGorgon:
                        case Collectible.Druid.Splintergraft:
                        //case Collectible.Priest.HolyWater: -- TODO
                        case Collectible.Neutral.BalefulBanker:
                        case Collectible.Neutral.DollmasterDorian:
                        case Collectible.Priest.Seance:
                            AddKnownCardId(gameState, target);
                            break;

                        case Collectible.Druid.MarkOfTheSpikeshell:
                            AddKnownCardId(gameState, target);
                            break;

                        case Collectible.Mage.ForgottenTorch:
                            AddKnownCardId(gameState, NonCollectible.Mage.ForgottenTorch_RoaringTorchToken);
                            break;

                        case Collectible.Warlock.CurseOfRafaam:
                            AddKnownCardId(gameState, NonCollectible.Warlock.CurseofRafaam_CursedToken);
                            break;

                        case Collectible.Neutral.AncientShade:
                            AddKnownCardId(gameState, NonCollectible.Neutral.AncientShade_AncientCurseToken);
                            break;

                        case Collectible.Priest.ExcavatedEvil:
                            AddKnownCardId(gameState, Collectible.Priest.ExcavatedEvil);
                            break;

                        case Collectible.Neutral.EliseStarseeker:
                            AddKnownCardId(gameState, NonCollectible.Neutral.EliseStarseeker_MapToTheGoldenMonkeyToken);
                            break;

                        case NonCollectible.Neutral.EliseStarseeker_MapToTheGoldenMonkeyToken:
                            AddKnownCardId(gameState, NonCollectible.Neutral.EliseStarseeker_GoldenMonkeyToken);
                            break;

                        case Collectible.Neutral.Doomcaller:
                            AddKnownCardId(gameState, NonCollectible.Neutral.Cthun);
                            break;

                        case Collectible.Druid.JadeIdol:
                            AddKnownCardId(gameState, Collectible.Druid.JadeIdol, 3);
                            break;

                        case NonCollectible.Hunter.TheMarshQueen_QueenCarnassaToken:
                            AddKnownCardId(gameState, NonCollectible.Hunter.TheMarshQueen_CarnassasBroodToken, 15);
                            break;

                        case Collectible.Neutral.EliseTheTrailblazer:
                            AddKnownCardId(gameState, NonCollectible.Neutral.ElisetheTrailblazer_UngoroPackToken);
                            break;

                        case Collectible.Mage.GhastlyConjurer:
                            AddKnownCardId(gameState, Collectible.Mage.MirrorImageLegacy);
                            break;

                        case Collectible.Druid.ThorngrowthSentries:
                            AddKnownCardId(gameState, NonCollectible.Druid.ThorngrowthSentries_ThornguardTurtleToken, 2);
                            break;

                        case Collectible.Mage.DeckOfWonders:
                            AddKnownCardId(gameState, NonCollectible.Mage.DeckofWonders_ScrollOfWonderToken, 5);
                            break;

                        case Collectible.Neutral.TheDarkness:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheDarkness_DarknessCandleToken, 3);
                            break;

                        case Collectible.Rogue.FaldoreiStrider:
                            AddKnownCardId(gameState, NonCollectible.Rogue.FaldoreiStrider_SpiderAmbushEnchantment, 3);
                            break;

                        case Collectible.Neutral.KingTogwaggle:
                            AddKnownCardId(gameState, NonCollectible.Neutral.KingTogwaggle_KingsRansomToken);
                            break;

                        case NonCollectible.Neutral.TheCandle:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheCandle);
                            break;

                        case NonCollectible.Neutral.CoinPouchGILNEAS:
                            AddKnownCardId(gameState, NonCollectible.Neutral.SackOfCoinsGILNEAS);
                            break;

                        case NonCollectible.Neutral.SackOfCoinsGILNEAS:
                            AddKnownCardId(gameState, NonCollectible.Neutral.HeftySackOfCoinsGILNEAS);
                            break;

                        case NonCollectible.Neutral.CreepyCurioGILNEAS:
                            AddKnownCardId(gameState, NonCollectible.Neutral.HauntedCurioGILNEAS);
                            break;

                        case NonCollectible.Neutral.HauntedCurioGILNEAS:
                            AddKnownCardId(gameState, NonCollectible.Neutral.CursedCurioGILNEAS);
                            break;

                        case NonCollectible.Neutral.OldMilitiaHornGILNEAS:
                            AddKnownCardId(gameState, NonCollectible.Neutral.MilitiaHornGILNEAS);
                            break;

                        case NonCollectible.Neutral.MilitiaHornGILNEAS:
                            AddKnownCardId(gameState, NonCollectible.Neutral.VeteransMilitiaHornGILNEAS);
                            break;

                        case NonCollectible.Neutral.SurlyMobGILNEAS:
                            AddKnownCardId(gameState, NonCollectible.Neutral.AngryMobGILNEAS);
                            break;

                        case NonCollectible.Neutral.AngryMobGILNEAS:
                            AddKnownCardId(gameState, NonCollectible.Neutral.CrazedMobGILNEAS);
                            break;

                        case Collectible.Neutral.SparkEngine:
                            AddKnownCardId(gameState, NonCollectible.Neutral.SparkDrill_SparkToken);
                            break;

                        case Collectible.Priest.ExtraArms:
                            AddKnownCardId(gameState, NonCollectible.Priest.ExtraArms_MoreArmsToken);
                            break;

                        case Collectible.Neutral.SeaforiumBomber:
                        case Collectible.Warrior.ClockworkGoblin:
                            AddKnownCardId(gameState, NonCollectible.Neutral.SeaforiumBomber_BombToken);
                            break;

                        //case Collectible.Rogue.Wanted: -- TODO
                        //	AddKnownCardId(gameState, NonCollectible.Neutral.TheCoin);
                        //	break;
                        //TODO: Hex Lord Malacrass
                        //TODO: Krag'wa, the Frog
                        case Collectible.Hunter.HalazziTheLynx:
                            AddKnownCardId(gameState, NonCollectible.Hunter.Springpaw_LynxToken, 10);
                            break;

                        case Collectible.Neutral.BananaVendor:
                            AddKnownCardId(gameState, NonCollectible.Neutral.BananaBuffoon_BananasToken, 4);
                            break;

                        case Collectible.Neutral.BananaBuffoon:
                            AddKnownCardId(gameState, NonCollectible.Neutral.BananaBuffoon_BananasToken, 2);
                            break;

                        case Collectible.Neutral.BootyBayBookie:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheCoinCore);
                            break;

                        case Collectible.Neutral.PortalKeeper:
                        case Collectible.Neutral.PortalOverfiend:
                            AddKnownCardId(gameState, NonCollectible.Neutral.PortalKeeper_FelhoundPortalToken);
                            break;

                        case Collectible.Rogue.TogwagglesScheme:
                            AddKnownCardId(gameState, target);
                            break;

                        case Collectible.Paladin.SandwaspQueen:
                            AddKnownCardId(gameState, NonCollectible.Paladin.SandwaspQueen_SandwaspToken, 2);
                            break;

                        case Collectible.Rogue.ShadowOfDeath:
                            AddKnownCardId(gameState, NonCollectible.Rogue.ShadowofDeath_ShadowToken, 3);
                            break;

                        case Collectible.Warlock.Impbalming:
                            AddKnownCardId(gameState, NonCollectible.Warlock.Impbalming_WorthlessImpToken, 3);
                            break;

                        case Collectible.Druid.YseraUnleashed:
                            AddKnownCardId(gameState, NonCollectible.Druid.YseraUnleashed_DreamPortalToken, 7);
                            break;

                        case Collectible.Rogue.BloodsailFlybooter:
                            AddKnownCardId(gameState, NonCollectible.Rogue.BloodsailFlybooter_SkyPirateToken, 2);
                            break;

                        case Collectible.Rogue.UmbralSkulker:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheCoinCore, 3);
                            break;

                        case Collectible.Neutral.Sathrovarr:
                            AddKnownCardId(gameState, target, 3);
                            break;

                        case Collectible.Neutral.DragonBreeder:
                            AddKnownCardId(gameState, target);
                            break;

                        case Collectible.Warlock.SchoolSpirits:
                        case Collectible.Warlock.SoulShear:
                        case Collectible.Warlock.SpiritJailer:
                        case Collectible.Demonhunter.Marrowslicer:
                            AddKnownCardId(gameState, NonCollectible.Warlock.SchoolSpirits_SoulFragmentToken, 2);
                            break;

                        case Collectible.Mage.ConfectionCyclone:
                            AddKnownCardId(gameState, NonCollectible.Mage.ConfectionCyclone_SugarElementalToken, 2);
                            break;

                        case Collectible.Druid.KiriChosenOfElune:
                            AddKnownCardId(gameState, Collectible.Druid.LunarEclipse);
                            AddKnownCardId(gameState, Collectible.Druid.SolarEclipse);
                            break;

                        case NonCollectible.Neutral.CThuntheShattered_EyeOfCthunToken:
                        case NonCollectible.Neutral.CThuntheShattered_HeartOfCthunToken:
                        case NonCollectible.Neutral.CThuntheShattered_BodyOfCthunToken:
                        case NonCollectible.Neutral.CThuntheShattered_MawOfCthunToken:
                            // A new copy of C'Thun is created in the last of these POWER blocks.
                            // This currently leads to a duplicate copy of C'Thun showing up in the
                            // opponents deck list, but it will have to do for now.
                            AddKnownCardId(gameState, Collectible.Neutral.CthunTheShattered);
                            break;

                        case Collectible.Hunter.SunscaleRaptor:
                            AddKnownCardId(gameState, Collectible.Hunter.SunscaleRaptor);
                            break;

                        case Collectible.Neutral.Mankrik:
                            AddKnownCardId(gameState, NonCollectible.Neutral.Mankrik_OlgraMankriksWifeToken);
                            break;

                        case Collectible.Neutral.ShadowHunterVoljin:
                            AddKnownCardId(gameState, target);
                            break;

                        default:
                            if (playerEntity.Value != null && playerEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1 &&
                                !gameState.PlayerUsedHeroPower ||
                                opponentEntity.Value != null && opponentEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1 &&
                                !gameState.OpponentUsedHeroPower)
                            {
                                var card = Database.GetCardFromId(actionStartingCardId);
                                if (card.Type == "Hero Power")
                                {
                                    if (playerEntity.Value != null && playerEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1)
                                    {
                                        gameState.GameHandler.HandlePlayerHeroPower(actionStartingCardId, gameState.GetTurnNumber());
                                        gameState.PlayerUsedHeroPower = true;
                                    }
                                    else if (opponentEntity.Value != null)
                                    {
                                        gameState.GameHandler.HandleOpponentHeroPower(actionStartingCardId, gameState.GetTurnNumber());
                                        gameState.OpponentUsedHeroPower = true;
                                    }
                                }
                            }
                            break;
                        }
                    }
                }
                else if (logLine.Contains("BlockType=JOUST"))
                {
                    gameState.JoustReveals = 2;
                }
                else if (logLine.Contains("BlockType=REVEAL_CARD"))
                {
                    gameState.JoustReveals = 1;
                }
                else if (gameState.GameTriggerCount == 0 && logLine.Contains("BLOCK_START BlockType=TRIGGER Entity=GameEntity"))
                {
                    gameState.GameTriggerCount++;
                }
            }
            else if (logLine.Contains("CREATE_GAME"))
            {
                _tagChangeHandler.ClearQueuedActions();
            }
            else if (logLine.Contains("BLOCK_END"))
            {
                if (gameState.GameTriggerCount < 10 && (game.GameEntity?.HasTag(GameTag.TURN) ?? false))
                {
                    gameState.GameTriggerCount += 10;
                    _tagChangeHandler.InvokeQueuedActions(game);
                    game.SetupDone = true;
                }
                if (gameState.CurrentBlock?.Type == "JOUST" || gameState.CurrentBlock?.Type == "REVEAL_CARD")
                {
                    //make sure there are no more queued actions that might depend on JoustReveals
                    _tagChangeHandler.InvokeQueuedActions(game);
                    gameState.JoustReveals = 0;
                }

                if (gameState.CurrentBlock?.Type == "TRIGGER" &&
                    (gameState.CurrentBlock?.CardId == NonCollectible.Neutral.Chameleos_ShiftingEnchantment ||
                     gameState.CurrentBlock?.CardId == Collectible.Priest.Chameleos) &&
                    gameState.ChameleosReveal != null &&
                    game.Entities.TryGetValue(gameState.ChameleosReveal.Item1, out var chameleos) &&
                    chameleos.HasTag(GameTag.SHIFTING))
                {
                    gameState.GameHandler.HandleChameleosReveal(gameState.ChameleosReveal.Item2);
                }
                gameState.ChameleosReveal = null;

                if (gameState.CurrentBlock?.Type == "TRIGGER" &&
                    gameState.CurrentBlock?.CardId == NonCollectible.Neutral.Baconshop8playerenchantTavernBrawl &&
                    gameState.CurrentBlock?.HasFullEntityHeroPackets == true &&
                    gameState.Turn % 2 == 0)
                {
                    game.SnapshotBattlegroundsBoardState();
                    if (game.CurrentGameStats != null)
                    {
                        BobsBuddyInvoker.GetInstance(game.CurrentGameStats.GameId, gameState.GetTurnNumber())?
                        .StartCombat();
                    }
                }

                gameState.BlockEnd();
            }


            if (game.IsInMenu)
            {
                return;
            }
            if (!creationTag && gameState.DeterminedPlayers)
            {
                _tagChangeHandler.InvokeQueuedActions(game);
            }
            if (!creationTag)
            {
                gameState.ResetCurrentEntity();
            }
        }
        private void ZoneChangeFromHand(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
        {
            if (!game.Entities.TryGetValue(id, out var entity))
            {
                return;
            }
            switch ((Zone)value)
            {
            case PLAY:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerPlay(entity, cardId, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentPlay(entity, cardId, entity.GetTag(ZONE_POSITION),
                                                             gameState.GetTurnNumber());
                }
                break;

            case REMOVEDFROMGAME:
            case SETASIDE:
            case GRAVEYARD:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerHandDiscard(entity, cardId, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentHandDiscard(entity, cardId, entity.GetTag(ZONE_POSITION),
                                                                    gameState.GetTurnNumber());
                }
                break;

            case Zone.SECRET:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerSecretPlayed(entity, cardId, gameState.GetTurnNumber(), (Zone)prevValue);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentSecretPlayed(entity, cardId, entity.GetTag(ZONE_POSITION),
                                                                     gameState.GetTurnNumber(), (Zone)prevValue, id);
                }
                break;

            case DECK:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerMulligan(entity, cardId);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentMulligan(entity, entity.GetTag(ZONE_POSITION));
                }
                break;

            default:
                Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
                break;
            }
        }
        private void ZoneChangeFromSecret(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
        {
            switch ((Zone)value)
            {
            case Zone.SECRET:
            case GRAVEYARD:
                if (controller == game.Opponent.Id)
                {
                    if (!game.Entities.TryGetValue(id, out var entity))
                    {
                        return;
                    }
                    gameState.GameHandler.HandleOpponentSecretTrigger(entity, cardId, gameState.GetTurnNumber(), id);
                }
                break;

            default:
                Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
                break;
            }
        }
		private static void ZonePositionChange(IHsGameState gameState, int id, IGame game, int controller)
		{
			var entity = game.Entities[id];
			var zone = entity.GetTag(ZONE);
			if(zone == (int)HAND)
			{
				if(controller == game.Player.Id)
				{
					ReplayMaker.Generate(HandPos, id, ActivePlayer.Player, game);
					gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Player, entity, HAND, gameState.GetTurnNumber());
				}
				else if(controller == game.Opponent.Id)
				{
					ReplayMaker.Generate(HandPos, id, ActivePlayer.Opponent, game);
					gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Opponent, entity, HAND, gameState.GetTurnNumber());
				}
			}
			else if(zone == (int)PLAY)
			{
				if(controller == game.Player.Id)
				{
					ReplayMaker.Generate(BoardPos, id, ActivePlayer.Player, game);
					gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Player, entity, PLAY, gameState.GetTurnNumber());
				}
				else if(controller == game.Opponent.Id)
				{
					ReplayMaker.Generate(BoardPos, id, ActivePlayer.Opponent, game);
					gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Opponent, entity, PLAY, gameState.GetTurnNumber());
				}
			}
		}
		private void ZoneChangeFromSecret(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
		{
			switch((Zone)value)
			{
				case Zone.SECRET:
				case GRAVEYARD:
					if(controller == game.Player.Id)
						gameState.ProposeKeyPoint(SecretTriggered, id, ActivePlayer.Player);
					if(controller == game.Opponent.Id)
					{
						Entity entity;
						if(!game.Entities.TryGetValue(id, out entity))
							return;
						gameState.GameHandler.HandleOpponentSecretTrigger(entity, cardId, gameState.GetTurnNumber(), id);
						gameState.ProposeKeyPoint(SecretTriggered, id, ActivePlayer.Opponent);
					}
					break;
				default:
					Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
					break;
			}
		}
        public void Handle(string logLine, IHsGameState gameState, IGame game)
        {
            if (logLine.Contains("CREATE_GAME"))
            {
                gameState.GameHandler.HandleGameStart();
                gameState.GameEnded = false;
                gameState.AddToTurn = -1;
                gameState.GameLoaded = true;
                gameState.LastGameStart = DateTime.Now;
            }
            else if (HsLogReaderConstants.GameEntityRegex.IsMatch(logLine))
            {
                gameState.GameHandler.HandleGameStart();
                gameState.GameEnded = false;
                gameState.AddToTurn = -1;
                var match = HsLogReaderConstants.GameEntityRegex.Match(logLine);
                var id = int.Parse(match.Groups["id"].Value);
                if (!game.Entities.ContainsKey(id))
                    game.Entities.Add(id, new Entity(id));
                gameState.CurrentEntityId = id;
            }
            else if (HsLogReaderConstants.PlayerEntityRegex.IsMatch(logLine))
            {
                var match = HsLogReaderConstants.PlayerEntityRegex.Match(logLine);
                var id = int.Parse(match.Groups["id"].Value);
                if (!game.Entities.ContainsKey(id))
                    game.Entities.Add(id, new Entity(id));
                gameState.CurrentEntityId = id;
            }
            else if (HsLogReaderConstants.TagChangeRegex.IsMatch(logLine))
            {
                var match = HsLogReaderConstants.TagChangeRegex.Match(logLine);
                var rawEntity = match.Groups["entity"].Value.Replace("UNKNOWN ENTITY ", "");
                int entityId;
                if (rawEntity.StartsWith("[") && HsLogReaderConstants.EntityRegex.IsMatch(rawEntity))
                {
                    var entity = HsLogReaderConstants.EntityRegex.Match(rawEntity);
                    var id = int.Parse(entity.Groups["id"].Value);
                    _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, id, match.Groups["value"].Value, game);
                }
                else if (int.TryParse(rawEntity, out entityId))
                    _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entityId, match.Groups["value"].Value, game);
                else
                {
                    var entity = game.Entities.FirstOrDefault(x => x.Value.Name == rawEntity);

	                if(entity.Value == null)
	                {
		                entity = game.Entities.FirstOrDefault(x => x.Value.Name == "UNKNOWN HUMAN PLAYER");
		                if(entity.Value != null)
			                entity.Value.Name = rawEntity;
	                }

                    if (entity.Value == null)
                    {
                        //while the id is unknown, store in tmp entities
                        var tmpEntity = _tmpEntities.FirstOrDefault(x => x.Name == rawEntity);
                        if (tmpEntity == null)
                        {
                            tmpEntity = new Entity(_tmpEntities.Count + 1);
                            tmpEntity.Name = rawEntity;
                            _tmpEntities.Add(tmpEntity);
                        }
                        GAME_TAG tag;
                        Enum.TryParse(match.Groups["tag"].Value, out tag);
                        var value = HsLogReaderV2.ParseTagValue(tag, match.Groups["value"].Value);
                        tmpEntity.SetTag(tag, value);
                        if (tmpEntity.HasTag(GAME_TAG.ENTITY_ID))
                        {
                            var id = tmpEntity.GetTag(GAME_TAG.ENTITY_ID);
                            if (game.Entities.ContainsKey(id))
                            {
                                game.Entities[id].Name = tmpEntity.Name;
                                foreach (var t in tmpEntity.Tags)
                                    game.Entities[id].SetTag(t.Key, t.Value);
                                _tmpEntities.Remove(tmpEntity);
                                //Logger.WriteLine("COPIED TMP ENTITY (" + rawEntity + ")");
                            }
                            else
                                Logger.WriteLine(
                                    "TMP ENTITY (" + rawEntity + ") NOW HAS A KEY, BUT GAME.ENTITIES DOES NOT CONTAIN THIS KEY",
                                    "LogReader");
                        }
                    }
                    else
                        _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entity.Key, match.Groups["value"].Value, game);
                }

                if (HsLogReaderConstants.EntityNameRegex.IsMatch(logLine))
                {
                    match = HsLogReaderConstants.EntityNameRegex.Match(logLine);
                    var name = match.Groups["name"].Value;
                    var player = int.Parse(match.Groups["value"].Value);
                    if (player == 1)
                        gameState.GameHandler.HandlePlayerName(name);
                    else if (player == 2)
                        gameState.GameHandler.HandleOpponentName(name);
                }
            }
            else if (HsLogReaderConstants.CreationRegex.IsMatch(logLine))
            {
                var match = HsLogReaderConstants.CreationRegex.Match(logLine);
                var id = int.Parse(match.Groups["id"].Value);
                var cardId = match.Groups["cardId"].Value;
	            if(!game.Entities.ContainsKey(id))
	            {
		            if(string.IsNullOrEmpty(cardId))
		            {
			            if(gameState.KnownCardIds.TryGetValue(id, out cardId))
			            {
				            Logger.WriteLine(string.Format("Found known cardId for entity {0}: {1}", id, cardId));
				            gameState.KnownCardIds.Remove(id);
			            }
		            }
		            game.Entities.Add(id, new Entity(id) { CardId = cardId });
	            }
                gameState.CurrentEntityId = id;
                gameState.CurrentEntityHasCardId = !string.IsNullOrEmpty(cardId);
            }
            else if (HsLogReaderConstants.UpdatingEntityRegex.IsMatch(logLine))
            {
                var match = HsLogReaderConstants.UpdatingEntityRegex.Match(logLine);
                var cardId = match.Groups["cardId"].Value;
                var rawEntity = match.Groups["entity"].Value;
                int entityId;
                if (rawEntity.StartsWith("[") && HsLogReaderConstants.EntityRegex.IsMatch(rawEntity))
                {
                    var entity = HsLogReaderConstants.EntityRegex.Match(rawEntity);
                    entityId = int.Parse(entity.Groups["id"].Value);
                }
                else if (!int.TryParse(rawEntity, out entityId))
                    entityId = -1;
                if (entityId != -1)
                {
                    gameState.CurrentEntityId = entityId;
                    if (!game.Entities.ContainsKey(entityId))
                        game.Entities.Add(entityId, new Entity(entityId));
                    game.Entities[entityId].CardId = cardId;
                }
				if(gameState.JoustReveals > 0)
				{
					Entity currentEntity;
					if(game.Entities.TryGetValue(entityId, out currentEntity))
					{
						if(currentEntity.IsControlledBy(game.Opponent.Id))
							gameState.GameHandler.HandleOpponentJoust(currentEntity, cardId, gameState.GetTurnNumber());
						else if(currentEntity.IsControlledBy(game.Player.Id))
							gameState.GameHandler.HandlePlayerJoust(currentEntity, cardId, gameState.GetTurnNumber());
					}
					//gameState.JoustReveals--;
				}
			}
            else if (HsLogReaderConstants.CreationTagRegex.IsMatch(logLine) && !logLine.Contains("HIDE_ENTITY"))
            {
                var match = HsLogReaderConstants.CreationTagRegex.Match(logLine);
                _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, gameState.CurrentEntityId, match.Groups["value"].Value, game);
            }
            else if ((logLine.Contains("Begin Spectating") || logLine.Contains("Start Spectator")) && game.IsInMenu)
                gameState.GameHandler.SetGameMode(GameMode.Spectator);
            else if (logLine.Contains("End Spectator"))
            {
                gameState.GameHandler.SetGameMode(GameMode.Spectator);
                gameState.GameHandler.HandleGameEnd();
            }
            else if (HsLogReaderConstants.ActionStartRegex.IsMatch(logLine))
            {
                var playerEntity =
                    game.Entities.FirstOrDefault(
                        e => e.Value.HasTag(GAME_TAG.PLAYER_ID) && e.Value.GetTag(GAME_TAG.PLAYER_ID) == game.Player.Id);
                var opponentEntity =
                    game.Entities.FirstOrDefault(
                        e => e.Value.HasTag(GAME_TAG.PLAYER_ID) && e.Value.GetTag(GAME_TAG.PLAYER_ID) == game.Opponent.Id);

                var match = HsLogReaderConstants.ActionStartRegex.Match(logLine);
                var actionStartingCardId = match.Groups["cardId"].Value.Trim();
                var actionStartingEntityId = int.Parse(match.Groups["id"].Value);

                if (string.IsNullOrEmpty(actionStartingCardId))
                {
                    Entity tmpEntity;
                    if (game.Entities.TryGetValue(actionStartingEntityId, out tmpEntity))
                        actionStartingCardId = tmpEntity.CardId;
                }
                if (!string.IsNullOrEmpty(actionStartingCardId))
                {
                    if (actionStartingCardId == "BRM_007") //Gang Up
                    {
                        //if (playerEntity.Value != null && playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) == 1)
                        //{
                            var target = match.Groups["target"].Value.Trim();
                            if (target.StartsWith("[") && HsLogReaderConstants.EntityRegex.IsMatch(target))
                            {
                                var cardIdMatch = HsLogReaderConstants.CardIdRegex.Match(target);
	                            if(cardIdMatch.Success)
	                            {
		                            var targetCardId = cardIdMatch.Groups["cardId"].Value.Trim();

		                            for(int i = 0; i < 3; i++)
									{
										var id = game.Entities.Count + i + 1;
			                            if(!gameState.KnownCardIds.ContainsKey(id))
				                            gameState.KnownCardIds.Add(id, targetCardId);
		                            }
	                            }
                            }
                        //}
                    }
                    else if (actionStartingCardId == "GVG_056") //Iron Juggernaut
                    {
	                    // burrowing mine will be the entity created next
                        int id = game.Entities.Count + 1;
	                    //if(playerEntity.Value == null || playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) != 1)
	                    //{
		                    if(!gameState.KnownCardIds.ContainsKey(id))
			                    gameState.KnownCardIds.Add(id, "GVG_056t");
	                    //}
                    }
                    else if (actionStartingCardId == "GVG_031") //Recycle
                    {
	                    // Recycled card will be the entity created next
                        int id = game.Entities.Count + 1;
	                    //if(playerEntity.Value == null || playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) != 1)
	                    //{
		                    gameState.ProposeKeyPoint(KeyPointType.CreateToDeck, id, ActivePlayer.Player);
		                    var target = match.Groups["target"].Value.Trim();
		                    if(target.StartsWith("[") && HsLogReaderConstants.EntityRegex.IsMatch(target))
		                    {
			                    var cardIdMatch = HsLogReaderConstants.CardIdRegex.Match(target);
			                    if(cardIdMatch.Success)
			                    {
				                    var targetCardId = cardIdMatch.Groups["cardId"].Value.Trim();
				                    if(!gameState.KnownCardIds.ContainsKey(id))
					                    gameState.KnownCardIds.Add(id, targetCardId);
			                    }
		                    }
	                    //}
                    }
                    else if (actionStartingCardId == "GVG_035") //Malorne
                    {
	                    // Malorne will be the entity created next
                        int id = game.Entities.Count + 1;
	                    //if(playerEntity.Value == null || playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) != 1)
	                    //{
							if(!gameState.KnownCardIds.ContainsKey(id))
			                    gameState.KnownCardIds.Add(id, "GVG_035");
	                   // }
                    }


                    else
                    {
                        if (playerEntity.Value != null && playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) == 1
                            && !gameState.PlayerUsedHeroPower
                            || opponentEntity.Value != null && opponentEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) == 1
                            && !gameState.OpponentUsedHeroPower)
                        {
                            var card = Database.GetCardFromId(actionStartingCardId);
                            if (card.Type == "Hero Power")
                            {
                                if (playerEntity.Value != null && playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) == 1)
                                {
                                    gameState.GameHandler.HandlePlayerHeroPower(actionStartingCardId, gameState.GetTurnNumber());
                                    gameState.PlayerUsedHeroPower = true;
                                }
                                else if (opponentEntity.Value != null)
                                {
                                    gameState.GameHandler.HandleOpponentHeroPower(actionStartingCardId, gameState.GetTurnNumber());
                                    gameState.OpponentUsedHeroPower = true;
                                }
                            }
                        }
                    }
                }
            }
            else if (logLine.Contains("BlockType=JOUST"))
            {
                gameState.JoustReveals = 2;
            }
        }
		private static void ZoneChangeFromDeck(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller,
													  string cardId)
		{
			switch((TAG_ZONE)value)
			{
				case HAND:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerDraw(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(Draw, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						if(!string.IsNullOrEmpty(game.Entities[id].CardId) && gameState.SetupDone)
						{
#if DEBUG
							Log.Debug($"Opponent Draw (EntityID={id}) already has a CardID. Removing. Blizzard Pls.");
#endif
							game.Entities[id].CardId = string.Empty;
						}
						gameState.GameHandler.HandleOpponentDraw(game.Entities[id], gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(Draw, id, ActivePlayer.Opponent);
					}
					break;
				case SETASIDE:
				case REMOVEDFROMGAME:
					if(controller == game.Player.Id)
					{
						if(gameState.JoustReveals > 0)
						{
							gameState.JoustReveals--;
							break;
						}
						gameState.GameHandler.HandlePlayerRemoveFromDeck(game.Entities[id], gameState.GetTurnNumber());
					}
					else if(controller == game.Opponent.Id)
					{
						if(gameState.JoustReveals > 0)
						{
							gameState.JoustReveals--;
							break;
						}
						gameState.GameHandler.HandleOpponentRemoveFromDeck(game.Entities[id], gameState.GetTurnNumber());
					}
					break;
				case GRAVEYARD:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerDeckDiscard(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentDeckDiscard(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Opponent);
					}
					break;
				case PLAY:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerDeckToPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentDeckToPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Opponent);
					}
					break;
				case TAG_ZONE.SECRET:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerSecretPlayed(game.Entities[id], cardId, gameState.GetTurnNumber(), true);
						gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentSecretPlayed(game.Entities[id], cardId, -1, gameState.GetTurnNumber(), true, id);
						gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
					}
					break;
				default:
					Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
					break;
			}
		}
		private static void ZoneChangeFromHand(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller,
												string cardId)
		{
			switch((TAG_ZONE)value)
			{
				case PLAY:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(Play, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentPlay(game.Entities[id], cardId, game.Entities[id].GetTag(ZONE_POSITION),
																 gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(Play, id, ActivePlayer.Opponent);
					}
					break;
				case REMOVEDFROMGAME:
				case SETASIDE:
				case GRAVEYARD:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerHandDiscard(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(HandDiscard, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentHandDiscard(game.Entities[id], cardId, game.Entities[id].GetTag(ZONE_POSITION),
																		gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(HandDiscard, id, ActivePlayer.Opponent);
					}
					break;
				case TAG_ZONE.SECRET:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerSecretPlayed(game.Entities[id], cardId, gameState.GetTurnNumber(), false);
						gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentSecretPlayed(game.Entities[id], cardId, game.Entities[id].GetTag(ZONE_POSITION),
																		 gameState.GetTurnNumber(), false, id);
						gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Opponent);
					}
					break;
				case DECK:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerMulligan(game.Entities[id], cardId);
						gameState.ProposeKeyPoint(Mulligan, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentMulligan(game.Entities[id], game.Entities[id].GetTag(ZONE_POSITION));
						gameState.ProposeKeyPoint(Mulligan, id, ActivePlayer.Opponent);
					}
					break;
				default:
					Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
					break;
			}
		}
		private static void ZoneChangeFromPlay(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller,
												string cardId)
		{
			switch((TAG_ZONE)value)
			{
				case HAND:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerBackToHand(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(PlayToHand, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentPlayToHand(game.Entities[id], cardId, gameState.GetTurnNumber(), id);
						gameState.ProposeKeyPoint(PlayToHand, id, ActivePlayer.Opponent);
					}
					break;
				case DECK:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerPlayToDeck(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(PlayToDeck, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentPlayToDeck(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(PlayToDeck, id, ActivePlayer.Opponent);
					}
					break;
				case REMOVEDFROMGAME:
				case SETASIDE:
				case GRAVEYARD:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerPlayToGraveyard(game.Entities[id], cardId, gameState.GetTurnNumber());
						if(game.Entities[id].HasTag(HEALTH))
							gameState.ProposeKeyPoint(Death, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentPlayToGraveyard(game.Entities[id], cardId, gameState.GetTurnNumber(), gameState.PlayersTurn());
						if(game.Entities[id].HasTag(HEALTH))
							gameState.ProposeKeyPoint(Death, id, ActivePlayer.Opponent);
					}
					break;
				default:
					Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
					break;
			}
		}
		private static void ZoneChangeFromSecret(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller,
												  string cardId)
		{
			switch((TAG_ZONE)value)
			{
				case TAG_ZONE.SECRET:
				case GRAVEYARD:
					if(controller == game.Player.Id)
						gameState.ProposeKeyPoint(SecretTriggered, id, ActivePlayer.Player);
					if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentSecretTrigger(game.Entities[id], cardId, gameState.GetTurnNumber(), id);
						gameState.ProposeKeyPoint(SecretTriggered, id, ActivePlayer.Opponent);
					}
					break;
				default:
					Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
					break;
			}
		}
		private static void ZoneChangeFromOther(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller,
												 string cardId)
		{
			switch((TAG_ZONE)value)
			{
				case PLAY:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerCreateInPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(Summon, id, ActivePlayer.Player);
					}
					if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentCreateInPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(Summon, id, ActivePlayer.Opponent);
					}
					break;
				case DECK:
					if(controller == game.Player.Id)
					{
						if(gameState.JoustReveals > 0)
							break;
						gameState.GameHandler.HandlePlayerGetToDeck(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(CreateToDeck, id, ActivePlayer.Player);
					}
					if(controller == game.Opponent.Id)
					{
						if(gameState.JoustReveals > 0)
							break;
						gameState.GameHandler.HandleOpponentGetToDeck(game.Entities[id], gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(CreateToDeck, id, ActivePlayer.Opponent);
					}
					break;
				case HAND:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerGet(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(Obtain, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentGet(game.Entities[id], gameState.GetTurnNumber(), id);
						gameState.ProposeKeyPoint(Obtain, id, ActivePlayer.Opponent);
					}
					break;
				default:
					Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
					break;
			}
		}
		private static void CurrentPlayerChange(IHsGameState gameState, int id, IGame game, int value)
		{
			if(value == 1)
			{
				var activePlayer = game.Entities[id].IsPlayer ? ActivePlayer.Player : ActivePlayer.Opponent;
				gameState.GameHandler.TurnStart(activePlayer, gameState.GetTurnNumber());
				if(activePlayer == ActivePlayer.Player)
					gameState.PlayerUsedHeroPower = false;
				else
					gameState.OpponentUsedHeroPower = false;
			}
		}
        private void ZoneChangeFromOther(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
        {
            var entity = game.Entities[id];

            if (entity.Info.OriginalZone == DECK && value != (int)DECK)
            {
                //This entity was moved from DECK to SETASIDE to HAND, e.g. by Tracking
                entity.Info.Discarded = false;
                ZoneChangeFromDeck(gameState, id, game, value, prevValue, controller, cardId);
                return;
            }
            entity.Info.Created = true;
            switch ((Zone)value)
            {
            case PLAY:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerCreateInPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(Summon, id, ActivePlayer.Player);
                }
                if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentCreateInPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(Summon, id, ActivePlayer.Opponent);
                }
                break;

            case DECK:
                if (controller == game.Player.Id)
                {
                    if (gameState.JoustReveals > 0)
                    {
                        break;
                    }
                    gameState.GameHandler.HandlePlayerGetToDeck(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(CreateToDeck, id, ActivePlayer.Player);
                }
                if (controller == game.Opponent.Id)
                {
                    if (gameState.JoustReveals > 0)
                    {
                        break;
                    }
                    gameState.GameHandler.HandleOpponentGetToDeck(game.Entities[id], gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(CreateToDeck, id, ActivePlayer.Opponent);
                }
                break;

            case HAND:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerGet(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(Obtain, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentGet(game.Entities[id], gameState.GetTurnNumber(), id);
                    gameState.ProposeKeyPoint(Obtain, id, ActivePlayer.Opponent);
                }
                break;

            case Zone.SECRET:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerSecretPlayed(game.Entities[id], cardId, gameState.GetTurnNumber(), (Zone)prevValue);
                    gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentSecretPlayed(game.Entities[id], cardId, -1, gameState.GetTurnNumber(), (Zone)prevValue, id);
                    gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Opponent);
                }
                break;

            default:
                Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
                break;
            }
        }
		public void Handle(string logLine, IHsGameState gameState, IGame game)
		{
			if(logLine.Contains("CREATE_GAME"))
			{
				gameState.GameHandler.HandleGameStart();
				gameState.GameEnded = false;
				gameState.AddToTurn = -1;
				gameState.GameLoaded = true;
				gameState.LastGameStart = DateTime.Now;
			}
			else if(GameEntityRegex.IsMatch(logLine))
			{
				gameState.GameHandler.HandleGameStart();
				gameState.GameEnded = false;
				gameState.AddToTurn = -1;
				var match = GameEntityRegex.Match(logLine);
				var id = int.Parse(match.Groups["id"].Value);
				if(!game.Entities.ContainsKey(id))
					game.Entities.Add(id, new Entity(id));
				gameState.CurrentEntityId = id;
			}
			else if(PlayerEntityRegex.IsMatch(logLine))
			{
				var match = PlayerEntityRegex.Match(logLine);
				var id = int.Parse(match.Groups["id"].Value);
				if(!game.Entities.ContainsKey(id))
					game.Entities.Add(id, new Entity(id));
				gameState.CurrentEntityId = id;
			}
			else if(TagChangeRegex.IsMatch(logLine))
			{
				var match = TagChangeRegex.Match(logLine);
				var rawEntity = match.Groups["entity"].Value.Replace("UNKNOWN ENTITY ", "");
				int entityId;
				if(rawEntity.StartsWith("[") && EntityRegex.IsMatch(rawEntity))
				{
					var entity = EntityRegex.Match(rawEntity);
					var id = int.Parse(entity.Groups["id"].Value);
					_tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, id, match.Groups["value"].Value, game);
				}
				else if(int.TryParse(rawEntity, out entityId))
					_tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entityId, match.Groups["value"].Value, game);
				else
				{
					var entity = game.Entities.FirstOrDefault(x => x.Value.Name == rawEntity);

					if(entity.Value == null)
					{
						entity = game.Entities.FirstOrDefault(x => x.Value.Name == "UNKNOWN HUMAN PLAYER");
						if(entity.Value != null)
							entity.Value.Name = rawEntity;
					}

					if(entity.Value == null)
					{
						//while the id is unknown, store in tmp entities
						var tmpEntity = _tmpEntities.FirstOrDefault(x => x.Name == rawEntity);
						if(tmpEntity == null)
						{
							tmpEntity = new Entity(_tmpEntities.Count + 1) {Name = rawEntity};
							_tmpEntities.Add(tmpEntity);
						}
						GAME_TAG tag;
						Enum.TryParse(match.Groups["tag"].Value, out tag);
						var value = LogReaderHelper.ParseTagValue(tag, match.Groups["value"].Value);
						tmpEntity.SetTag(tag, value);
						if(tmpEntity.HasTag(GAME_TAG.ENTITY_ID))
						{
							var id = tmpEntity.GetTag(GAME_TAG.ENTITY_ID);
							if(game.Entities.ContainsKey(id))
							{
								game.Entities[id].Name = tmpEntity.Name;
								foreach(var t in tmpEntity.Tags)
									game.Entities[id].SetTag(t.Key, t.Value);
								_tmpEntities.Remove(tmpEntity);
								//Logger.WriteLine("COPIED TMP ENTITY (" + rawEntity + ")");
							}
							else
								Logger.WriteLine("TMP ENTITY (" + rawEntity + ") NOW HAS A KEY, BUT GAME.ENTITIES DOES NOT CONTAIN THIS KEY", "LogReader");
						}
					}
					else
						_tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entity.Key, match.Groups["value"].Value, game);
				}

				if(EntityNameRegex.IsMatch(logLine))
				{
					match = EntityNameRegex.Match(logLine);
					var name = match.Groups["name"].Value;
					var player = int.Parse(match.Groups["value"].Value);
					if(player == game.Player.Id)
						game.Player.Name = name;
					else if(player == game.Opponent.Id)
						game.Opponent.Name = name;
				}
			}
			else if(CreationRegex.IsMatch(logLine))
			{
				var match = CreationRegex.Match(logLine);
				var id = int.Parse(match.Groups["id"].Value);
				var cardId = match.Groups["cardId"].Value;
				if(!game.Entities.ContainsKey(id))
				{
					if(string.IsNullOrEmpty(cardId))
					{
						if(gameState.KnownCardIds.TryGetValue(id, out cardId))
						{
							Logger.WriteLine($"Found known cardId for entity {id}: {cardId}");
							gameState.KnownCardIds.Remove(id);
						}
					}
					game.Entities.Add(id, new Entity(id) {CardId = cardId});
				}
				gameState.CurrentEntityId = id;
				gameState.CurrentEntityHasCardId = !string.IsNullOrEmpty(cardId);
			}
			else if(UpdatingEntityRegex.IsMatch(logLine))
			{
				var match = UpdatingEntityRegex.Match(logLine);
				var cardId = match.Groups["cardId"].Value;
				var rawEntity = match.Groups["entity"].Value;
				int entityId;
				if(rawEntity.StartsWith("[") && EntityRegex.IsMatch(rawEntity))
				{
					var entity = EntityRegex.Match(rawEntity);
					entityId = int.Parse(entity.Groups["id"].Value);
				}
				else if(!int.TryParse(rawEntity, out entityId))
					entityId = -1;
				if(entityId != -1)
				{
					gameState.CurrentEntityId = entityId;
					if(!game.Entities.ContainsKey(entityId))
						game.Entities.Add(entityId, new Entity(entityId));
					game.Entities[entityId].CardId = cardId;
				}
				if(gameState.JoustReveals > 0)
				{
					Entity currentEntity;
					if(game.Entities.TryGetValue(entityId, out currentEntity))
					{
						if(currentEntity.IsControlledBy(game.Opponent.Id))
							gameState.GameHandler.HandleOpponentJoust(currentEntity, cardId, gameState.GetTurnNumber());
						else if(currentEntity.IsControlledBy(game.Player.Id))
							gameState.GameHandler.HandlePlayerJoust(currentEntity, cardId, gameState.GetTurnNumber());
					}
					//gameState.JoustReveals--;
				}
			}
			else if(CreationTagRegex.IsMatch(logLine) && !logLine.Contains("HIDE_ENTITY"))
			{
				var match = CreationTagRegex.Match(logLine);
				_tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, gameState.CurrentEntityId, match.Groups["value"].Value, game);
			}
			else if((logLine.Contains("Begin Spectating") || logLine.Contains("Start Spectator")) && game.IsInMenu)
				gameState.GameHandler.SetGameMode(GameMode.Spectator);
			else if(logLine.Contains("End Spectator"))
			{
				gameState.GameHandler.SetGameMode(GameMode.Spectator);
				gameState.GameHandler.HandleGameEnd();
			}
			else if(ActionStartRegex.IsMatch(logLine))
			{
				Entity actionEntity;
				var playerEntity =
					game.Entities.FirstOrDefault(e => e.Value.HasTag(GAME_TAG.PLAYER_ID) && e.Value.GetTag(GAME_TAG.PLAYER_ID) == game.Player.Id);
				var opponentEntity =
					game.Entities.FirstOrDefault(e => e.Value.HasTag(GAME_TAG.PLAYER_ID) && e.Value.GetTag(GAME_TAG.PLAYER_ID) == game.Opponent.Id);

				var match = ActionStartRegex.Match(logLine);
				var actionStartingCardId = match.Groups["cardId"].Value.Trim();
				var actionStartingEntityId = int.Parse(match.Groups["id"].Value);

				if(string.IsNullOrEmpty(actionStartingCardId))
				{
					if(game.Entities.TryGetValue(actionStartingEntityId, out actionEntity))
						actionStartingCardId = actionEntity.CardId;
				}
				if(game.Entities.TryGetValue(actionStartingEntityId, out actionEntity))
				{
					// spell owned by the player
					if(actionEntity.HasTag(GAME_TAG.CONTROLLER) && actionEntity.GetTag(GAME_TAG.CONTROLLER) == game.Player.Id
					   && actionEntity.GetTag(GAME_TAG.CARDTYPE) == (int)TAG_CARDTYPE.SPELL)
					{
						int targetEntityId = actionEntity.GetTag(GAME_TAG.CARD_TARGET);
						Entity targetEntity;
						var targetsMinion = game.Entities.TryGetValue(targetEntityId, out targetEntity) && targetEntity.IsMinion;
						gameState.GameHandler.HandlePlayerSpellPlayed(targetsMinion);
					}
				}
				if(string.IsNullOrEmpty(actionStartingCardId))
					return;
				if(match.Groups["type"].Value == "TRIGGER")
				{
					switch(actionStartingCardId)
					{
						case Collectible.Rogue.TradePrinceGallywix:
							AddKnownCardId(gameState, game, game.Entities[gameState.LastCardPlayed].CardId);
							AddKnownCardId(gameState, game, NonCollectible.Neutral.GallywixsCoinToken);
							break;
					}
				}
				else //POWER
				{
					switch(actionStartingCardId)
					{
						case Collectible.Rogue.GangUp:
							AddTargetAsKnownCardId(gameState, game, match, 3);
							break;
						case Collectible.Rogue.BeneathTheGrounds:
							AddKnownCardId(gameState, game, NonCollectible.Rogue.AmbushToken, 3);
							break;
						case Collectible.Warrior.IronJuggernaut:
							AddKnownCardId(gameState, game, NonCollectible.Warrior.BurrowingMineToken);
							break;
						case Collectible.Druid.Recycle:
							AddTargetAsKnownCardId(gameState, game, match);
							break;
						case Collectible.Mage.ForgottenTorch:
							AddKnownCardId(gameState, game, NonCollectible.Mage.RoaringTorchToken);
							break;
						case Collectible.Warlock.CurseOfRafaam:
							AddKnownCardId(gameState, game, NonCollectible.Warlock.CursedToken);
							break;
						case Collectible.Neutral.AncientShade:
							AddKnownCardId(gameState, game, NonCollectible.Neutral.AncientCurseToken);
							break;
						case Collectible.Priest.ExcavatedEvil:
							AddKnownCardId(gameState, game, Collectible.Priest.ExcavatedEvil);
							break;
						case Collectible.Neutral.EliseStarseeker:
							AddKnownCardId(gameState, game, NonCollectible.Neutral.MapToTheGoldenMonkeyToken);
							break;
						case NonCollectible.Neutral.MapToTheGoldenMonkeyToken:
							AddKnownCardId(gameState, game, NonCollectible.Neutral.GoldenMonkeyToken);
							break;
						default:
							if(playerEntity.Value != null && playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) == 1 && !gameState.PlayerUsedHeroPower
							   || opponentEntity.Value != null && opponentEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) == 1
							   && !gameState.OpponentUsedHeroPower)
							{
								var card = Database.GetCardFromId(actionStartingCardId);
								if(card.Type == "Hero Power")
								{
									if(playerEntity.Value != null && playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) == 1)
									{
										gameState.GameHandler.HandlePlayerHeroPower(actionStartingCardId, gameState.GetTurnNumber());
										gameState.PlayerUsedHeroPower = true;
									}
									else if(opponentEntity.Value != null)
									{
										gameState.GameHandler.HandleOpponentHeroPower(actionStartingCardId, gameState.GetTurnNumber());
										gameState.OpponentUsedHeroPower = true;
									}
								}
							}
							break;
					}
				}
			}
			else if(logLine.Contains("BlockType=JOUST"))
				gameState.JoustReveals = 2;
		}
        public void TagChange(IHsGameState gameState, string rawTag, int id, string rawValue, IGame game, bool isRecursive = false)
        {
            if (gameState.LastId != id)
            {
                game.SecondToLastUsedId = gameState.LastId;
                if (gameState.ProposedKeyPoint != null)
                {
                    ReplayMaker.Generate(gameState.ProposedKeyPoint.Type, gameState.ProposedKeyPoint.Id, gameState.ProposedKeyPoint.Player, game);
                    gameState.ProposedKeyPoint = null;
                }
            }
            gameState.LastId = id;
            if (!game.Entities.ContainsKey(id))
            {
                game.Entities.Add(id, new Entity(id));
            }
            GAME_TAG tag;

            if (!Enum.TryParse(rawTag, out tag))
            {
                int tmp;
                if (int.TryParse(rawTag, out tmp) && Enum.IsDefined(typeof(GAME_TAG), tmp))
                {
                    tag = (GAME_TAG)tmp;
                }
            }
            var value    = HsLogReaderV2.ParseTagValue(tag, rawValue);
            var prevZone = game.Entities[id].GetTag(GAME_TAG.ZONE);

            game.Entities[id].SetTag(tag, value);

            if (tag == GAME_TAG.CONTROLLER && gameState.WaitForController != null && game.PlayerId == -1)
            {
                var p1 = game.Entities.FirstOrDefault(e => e.Value.GetTag(GAME_TAG.PLAYER_ID) == 1).Value;
                var p2 = game.Entities.FirstOrDefault(e => e.Value.GetTag(GAME_TAG.PLAYER_ID) == 2).Value;
                if (gameState.CurrentEntityHasCardId)
                {
                    if (p1 != null)
                    {
                        p1.IsPlayer = value == 1;
                    }
                    if (p2 != null)
                    {
                        p2.IsPlayer = value != 1;
                    }
                    game.PlayerId   = value;
                    game.OpponentId = value == 1 ? 2 : 1;
                }
                else
                {
                    if (p1 != null)
                    {
                        p1.IsPlayer = value != 1;
                    }
                    if (p2 != null)
                    {
                        p2.IsPlayer = value == 1;
                    }
                    game.PlayerId   = value == 1 ? 2 : 1;
                    game.OpponentId = value;
                }
            }
            var controller = game.Entities[id].GetTag(GAME_TAG.CONTROLLER);
            var player     = game.Entities[id].HasTag(GAME_TAG.CONTROLLER) ? (controller == game.PlayerId ? "FRIENDLY" : "OPPOSING") : "";
            var cardId     = game.Entities[id].CardId;

            if (tag == GAME_TAG.ZONE)
            {
                //Logger.WriteLine("--------" + player + " " + game.Entities[id].CardId + " " + (TAG_ZONE)prevZone + " -> " +
                //                 (TAG_ZONE)value);

                if (((TAG_ZONE)value == TAG_ZONE.HAND || ((TAG_ZONE)value == TAG_ZONE.PLAY) && game.IsMulliganDone) && gameState.WaitForController == null)
                {
                    if (!game.IsMulliganDone)
                    {
                        prevZone = (int)TAG_ZONE.DECK;
                    }
                    if (controller == 0)
                    {
                        game.Entities[id].SetTag(GAME_TAG.ZONE, prevZone);
                        gameState.WaitForController = new { Tag = rawTag, Id = id, Value = rawValue };
                        //Logger.WriteLine("CURRENTLY NO CONTROLLER SET FOR CARD, WAITING...");
                        return;
                    }
                }
                switch ((TAG_ZONE)prevZone)
                {
                case TAG_ZONE.DECK:
                    switch ((TAG_ZONE)value)
                    {
                    case TAG_ZONE.HAND:
                        if (controller == game.PlayerId)
                        {
                            gameState.GameHandler.HandlePlayerDraw(cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.Draw, id, ActivePlayer.Player);
                        }
                        else if (controller == game.OpponentId)
                        {
                            gameState.GameHandler.HandleOpponentDraw(gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.Draw, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.REMOVEDFROMGAME:
                    case TAG_ZONE.GRAVEYARD:
                    case TAG_ZONE.SETASIDE:
                    case TAG_ZONE.PLAY:
                        if (controller == game.PlayerId)
                        {
                            gameState.GameHandler.HandlePlayerDeckDiscard(cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.DeckDiscard, id, ActivePlayer.Player);
                        }
                        else if (controller == game.OpponentId)
                        {
                            gameState.GameHandler.HandleOpponentDeckDiscard(cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.DeckDiscard, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.SECRET:
                        if (controller == game.PlayerId)
                        {
                            gameState.GameHandler.HandlePlayerSecretPlayed(cardId, gameState.GetTurnNumber(), true);
                            gameState.ProposeKeyPoint(KeyPointType.SecretPlayed, id, ActivePlayer.Player);
                        }
                        else if (controller == game.OpponentId)
                        {
                            gameState.GameHandler.HandleOpponentSecretPlayed(cardId, -1, gameState.GetTurnNumber(), true, id);
                            gameState.ProposeKeyPoint(KeyPointType.SecretPlayed, id, ActivePlayer.Player);
                        }
                        break;
                    }
                    break;

                case TAG_ZONE.HAND:
                    switch ((TAG_ZONE)value)
                    {
                    case TAG_ZONE.PLAY:
                        if (controller == game.PlayerId)
                        {
                            gameState.GameHandler.HandlePlayerPlay(cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.Play, id, ActivePlayer.Player);
                        }
                        else if (controller == game.OpponentId)
                        {
                            gameState.GameHandler.HandleOpponentPlay(cardId, game.Entities[id].GetTag(GAME_TAG.ZONE_POSITION), gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.Play, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.REMOVEDFROMGAME:
                    case TAG_ZONE.GRAVEYARD:
                        if (controller == game.PlayerId)
                        {
                            gameState.GameHandler.HandlePlayerHandDiscard(cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.HandDiscard, id, ActivePlayer.Player);
                        }
                        else if (controller == game.OpponentId)
                        {
                            gameState.GameHandler.HandleOpponentHandDiscard(cardId, game.Entities[id].GetTag(GAME_TAG.ZONE_POSITION), gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.HandDiscard, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.SECRET:
                        if (controller == game.PlayerId)
                        {
                            gameState.GameHandler.HandlePlayerSecretPlayed(cardId, gameState.GetTurnNumber(), false);
                            gameState.ProposeKeyPoint(KeyPointType.SecretPlayed, id, ActivePlayer.Player);
                        }
                        else if (controller == game.OpponentId)
                        {
                            gameState.GameHandler.HandleOpponentSecretPlayed(cardId, game.Entities[id].GetTag(GAME_TAG.ZONE_POSITION), gameState.GetTurnNumber(), false, id);
                            gameState.ProposeKeyPoint(KeyPointType.SecretPlayed, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.DECK:
                        if (controller == game.PlayerId)
                        {
                            gameState.GameHandler.HandlePlayerMulligan(cardId);
                            gameState.ProposeKeyPoint(KeyPointType.Mulligan, id, ActivePlayer.Player);
                        }
                        else if (controller == game.OpponentId)
                        {
                            gameState.GameHandler.HandleOpponentMulligan(game.Entities[id].GetTag(GAME_TAG.ZONE_POSITION));
                            gameState.ProposeKeyPoint(KeyPointType.Mulligan, id, ActivePlayer.Opponent);
                        }
                        break;
                    }
                    break;

                case TAG_ZONE.PLAY:
                    switch ((TAG_ZONE)value)
                    {
                    case TAG_ZONE.HAND:
                        if (controller == game.PlayerId)
                        {
                            gameState.GameHandler.HandlePlayerBackToHand(cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.PlayToHand, id, ActivePlayer.Player);
                        }
                        else if (controller == game.OpponentId)
                        {
                            gameState.GameHandler.HandleOpponentPlayToHand(cardId, gameState.GetTurnNumber(), id);
                            gameState.ProposeKeyPoint(KeyPointType.PlayToHand, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.DECK:
                        if (controller == game.PlayerId)
                        {
                            gameState.GameHandler.HandlePlayerPlayToDeck(cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.PlayToDeck, id, ActivePlayer.Player);
                        }
                        else if (controller == game.OpponentId)
                        {
                            gameState.GameHandler.HandleOpponentPlayToDeck(cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.PlayToDeck, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.GRAVEYARD:
                        if (game.Entities[id].HasTag(GAME_TAG.HEALTH))
                        {
                            if (controller == game.PlayerId)
                            {
                                gameState.ProposeKeyPoint(KeyPointType.Death, id, ActivePlayer.Player);
                            }
                            else if (controller == game.OpponentId)
                            {
                                gameState.ProposeKeyPoint(KeyPointType.Death, id, ActivePlayer.Opponent);
                            }
                        }
                        break;
                    }
                    break;

                case TAG_ZONE.SECRET:
                    switch ((TAG_ZONE)value)
                    {
                    case TAG_ZONE.SECRET:
                    case TAG_ZONE.GRAVEYARD:
                        if (controller == game.PlayerId)
                        {
                            gameState.ProposeKeyPoint(KeyPointType.SecretTriggered, id, ActivePlayer.Player);
                        }
                        if (controller == game.OpponentId)
                        {
                            gameState.GameHandler.HandleOpponentSecretTrigger(cardId, gameState.GetTurnNumber(), id);
                            gameState.ProposeKeyPoint(KeyPointType.SecretTriggered, id, ActivePlayer.Opponent);
                        }
                        break;
                    }
                    break;

                case TAG_ZONE.GRAVEYARD:
                case TAG_ZONE.SETASIDE:
                case TAG_ZONE.CREATED:
                case TAG_ZONE.INVALID:
                case TAG_ZONE.REMOVEDFROMGAME:
                    switch ((TAG_ZONE)value)
                    {
                    case TAG_ZONE.PLAY:
                        if (controller == game.PlayerId)
                        {
                            gameState.ProposeKeyPoint(KeyPointType.Summon, id, ActivePlayer.Player);
                        }
                        if (controller == game.OpponentId)
                        {
                            gameState.ProposeKeyPoint(KeyPointType.Summon, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.HAND:
                        if (controller == game.PlayerId)
                        {
                            gameState.GameHandler.HandlePlayerGet(cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.Obtain, id, ActivePlayer.Player);
                        }
                        else if (controller == game.OpponentId)
                        {
                            gameState.GameHandler.HandleOpponentGet(gameState.GetTurnNumber(), id);
                            gameState.ProposeKeyPoint(KeyPointType.Obtain, id, ActivePlayer.Opponent);
                        }
                        break;
                    }
                    break;
                }
            }
            else if (tag == GAME_TAG.PLAYSTATE)
            {
                if (value == (int)TAG_PLAYSTATE.QUIT)
                {
                    gameState.GameHandler.HandleConcede();
                }
                if (!gameState.GameEnded)
                {
                    if (game.Entities[id].IsPlayer)
                    {
                        if (value == (int)TAG_PLAYSTATE.WON)
                        {
                            gameState.GameEndKeyPoint(true, id);
                            gameState.GameHandler.HandleWin();
                            gameState.GameHandler.HandleGameEnd();
                            gameState.GameEnded = true;
                        }
                        else if (value == (int)TAG_PLAYSTATE.LOST)
                        {
                            gameState.GameEndKeyPoint(false, id);
                            gameState.GameHandler.HandleLoss();
                            gameState.GameHandler.HandleGameEnd();
                            gameState.GameEnded = true;
                        }
                        else if (value == (int)TAG_PLAYSTATE.TIED)
                        {
                            gameState.GameEndKeyPoint(false, id);
                            gameState.GameHandler.HandleTied();
                            gameState.GameHandler.HandleGameEnd();
                        }
                    }
                }
            }
            else if (tag == GAME_TAG.CURRENT_PLAYER && value == 1)
            {
                var activePlayer = game.Entities[id].IsPlayer ? ActivePlayer.Player : ActivePlayer.Opponent;
                gameState.GameHandler.TurnStart(activePlayer, gameState.GetTurnNumber());
                if (activePlayer == ActivePlayer.Player)
                {
                    gameState.PlayerUsedHeroPower = false;
                }
                else
                {
                    gameState.OpponentUsedHeroPower = false;
                }
            }
            else if (tag == GAME_TAG.NUM_ATTACKS_THIS_TURN && value > 0)
            {
                if (controller == game.PlayerId)
                {
                    gameState.ProposeKeyPoint(KeyPointType.Attack, id, ActivePlayer.Player);
                }
                else if (controller == game.OpponentId)
                {
                    gameState.ProposeKeyPoint(KeyPointType.Attack, id, ActivePlayer.Opponent);
                }
            }
            else if (tag == GAME_TAG.ZONE_POSITION)
            {
                var zone = game.Entities[id].GetTag(GAME_TAG.ZONE);
                if (zone == (int)TAG_ZONE.HAND)
                {
                    if (controller == game.PlayerId)
                    {
                        ReplayMaker.Generate(KeyPointType.HandPos, id, ActivePlayer.Player, game);
                    }
                    else if (controller == game.OpponentId)
                    {
                        ReplayMaker.Generate(KeyPointType.HandPos, id, ActivePlayer.Opponent, game);
                    }
                }
                else if (zone == (int)TAG_ZONE.PLAY)
                {
                    if (controller == game.PlayerId)
                    {
                        ReplayMaker.Generate(KeyPointType.BoardPos, id, ActivePlayer.Player, game);
                    }
                    else if (controller == game.OpponentId)
                    {
                        ReplayMaker.Generate(KeyPointType.BoardPos, id, ActivePlayer.Opponent, game);
                    }
                }
            }
            else if (tag == GAME_TAG.CARD_TARGET && value > 0)
            {
                if (controller == game.PlayerId)
                {
                    gameState.ProposeKeyPoint(KeyPointType.PlaySpell, id, ActivePlayer.Player);
                }
                else if (controller == game.OpponentId)
                {
                    gameState.ProposeKeyPoint(KeyPointType.PlaySpell, id, ActivePlayer.Opponent);
                }
            }
            else if (tag == GAME_TAG.EQUIPPED_WEAPON && value == 0)
            {
                if (controller == game.PlayerId)
                {
                    gameState.ProposeKeyPoint(KeyPointType.WeaponDestroyed, id, ActivePlayer.Player);
                }
                else if (controller == game.OpponentId)
                {
                    gameState.ProposeKeyPoint(KeyPointType.WeaponDestroyed, id, ActivePlayer.Opponent);
                }
            }
            else if (tag == GAME_TAG.EXHAUSTED && value > 0)
            {
                if (game.Entities[id].GetTag(GAME_TAG.CARDTYPE) == (int)TAG_CARDTYPE.HERO_POWER)
                {
                    if (controller == game.PlayerId)
                    {
                        gameState.ProposeKeyPoint(KeyPointType.HeroPower, id, ActivePlayer.Player);
                    }
                    else if (controller == game.OpponentId)
                    {
                        gameState.ProposeKeyPoint(KeyPointType.HeroPower, id, ActivePlayer.Opponent);
                    }
                }
            }
            else if (tag == GAME_TAG.CONTROLLER && game.Entities[id].IsInZone(TAG_ZONE.SECRET))
            {
                if (value == game.PlayerId)
                {
                    gameState.GameHandler.HandleOpponentSecretTrigger(cardId, gameState.GetTurnNumber(), id);
                    gameState.ProposeKeyPoint(KeyPointType.SecretStolen, id, ActivePlayer.Player);
                }
                else if (value == game.OpponentId)
                {
                    gameState.ProposeKeyPoint(KeyPointType.SecretStolen, id, ActivePlayer.Player);
                }
            }
            else if (tag == GAME_TAG.FATIGUE)
            {
                if (controller == game.PlayerId)
                {
                    gameState.GameHandler.HandlePlayerFatigue(Convert.ToInt32(rawValue));
                }
                else if (controller == game.OpponentId)
                {
                    gameState.GameHandler.HandleOpponentFatigue(Convert.ToInt32(rawValue));
                }
            }
            if (gameState.WaitForController != null)
            {
                if (!isRecursive)
                {
                    TagChange(gameState, (string)gameState.WaitForController.Tag, (int)gameState.WaitForController.Id, (string)gameState.WaitForController.Value, game, true);
                    gameState.WaitForController = null;
                }
            }
        }
		private void ControllerChange(IHsGameState gameState, int id, IGame game, int prevValue, int value)
		{
			Entity entity;
			if(!game.Entities.TryGetValue(id, out entity))
				return;
			if(prevValue <= 0)
			{
				entity.Info.OriginalController = value;
				return;
			}
			if(entity.HasTag(PLAYER_ID))
				return;
			if(value == game.Player.Id)
			{
				if(entity.IsInZone(Zone.SECRET))
				{
					gameState.GameHandler.HandleOpponentStolen(entity, entity.CardId, gameState.GetTurnNumber());
					gameState.ProposeKeyPoint(SecretStolen, id, ActivePlayer.Player);
				}
				else if(entity.IsInZone(PLAY))
					gameState.GameHandler.HandleOpponentStolen(entity, entity.CardId, gameState.GetTurnNumber());
			}
			else if(value == game.Opponent.Id)
			{
				if(entity.IsInZone(Zone.SECRET))
				{
					gameState.GameHandler.HandlePlayerStolen(entity, entity.CardId, gameState.GetTurnNumber());
					gameState.ProposeKeyPoint(SecretStolen, id, ActivePlayer.Player);
				}
				else if(entity.IsInZone(PLAY))
					gameState.GameHandler.HandlePlayerStolen(entity, entity.CardId, gameState.GetTurnNumber());
			}
		}
Ejemplo n.º 19
0
        public void Handle(string logLine, IHsGameState gameState, IGame game)
        {
            var setup       = false;
            var creationTag = false;

            if (GameEntityRegex.IsMatch(logLine))
            {
                var match = GameEntityRegex.Match(logLine);
                var id    = int.Parse(match.Groups["id"].Value);
                if (!game.Entities.ContainsKey(id))
                {
                    game.Entities.Add(id, new Entity(id)
                    {
                        Name = "GameEntity"
                    });
                }
                gameState.SetCurrentEntity(id);
                if (gameState.DeterminedPlayers)
                {
                    _tagChangeHandler.InvokeQueuedActions(game);
                }
                return;
            }
            else if (PlayerEntityRegex.IsMatch(logLine))
            {
                var match = PlayerEntityRegex.Match(logLine);
                var id    = int.Parse(match.Groups["id"].Value);
                if (!game.Entities.ContainsKey(id))
                {
                    game.Entities.Add(id, new Entity(id));
                }
                if (gameState.WasInProgress)
                {
                    game.Entities[id].Name = game.GetStoredPlayerName(id);
                }
                gameState.SetCurrentEntity(id);
                if (gameState.DeterminedPlayers)
                {
                    _tagChangeHandler.InvokeQueuedActions(game);
                }
                return;
            }
            else if (TagChangeRegex.IsMatch(logLine))
            {
                var match     = TagChangeRegex.Match(logLine);
                var rawEntity = match.Groups["entity"].Value.Replace("UNKNOWN ENTITY ", "");
                int entityId;
                if (rawEntity.StartsWith("[") && EntityRegex.IsMatch(rawEntity))
                {
                    var entity = EntityRegex.Match(rawEntity);
                    var id     = int.Parse(entity.Groups["id"].Value);
                    _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, id, match.Groups["value"].Value, game);
                }
                else if (int.TryParse(rawEntity, out entityId))
                {
                    _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entityId, match.Groups["value"].Value, game);
                }
                else
                {
                    var entity = game.Entities.FirstOrDefault(x => x.Value.Name == rawEntity);

                    if (entity.Value == null)
                    {
                        var players            = game.Entities.Where(x => x.Value.HasTag(GameTag.PLAYER_ID)).Take(2).ToList();
                        var unnamedPlayers     = players.Where(x => string.IsNullOrEmpty(x.Value.Name)).ToList();
                        var unknownHumanPlayer = players.FirstOrDefault(x => x.Value.Name == "UNKNOWN HUMAN PLAYER");
                        if (unnamedPlayers.Count == 0 && unknownHumanPlayer.Value != null)
                        {
                            Log.Info("Updating UNKNOWN HUMAN PLAYER");
                            entity = unknownHumanPlayer;
                            SetPlayerName(game, entity.Value.GetTag(GameTag.PLAYER_ID), rawEntity);
                        }

                        //while the id is unknown, store in tmp entities
                        var tmpEntity = _tmpEntities.FirstOrDefault(x => x.Name == rawEntity);
                        if (tmpEntity == null)
                        {
                            tmpEntity = new Entity(_tmpEntities.Count + 1)
                            {
                                Name = rawEntity
                            };
                            _tmpEntities.Add(tmpEntity);
                        }
                        GameTag tag;
                        Enum.TryParse(match.Groups["tag"].Value, out tag);
                        var value = LogReaderHelper.ParseTag(tag, match.Groups["value"].Value);
                        if (unnamedPlayers.Count == 1)
                        {
                            entity = unnamedPlayers.Single();
                        }
                        else if (unnamedPlayers.Count == 2 && tag == GameTag.CURRENT_PLAYER && value == 0)
                        {
                            entity = game.Entities.FirstOrDefault(x => x.Value?.HasTag(GameTag.CURRENT_PLAYER) ?? false);
                        }
                        if (entity.Value != null)
                        {
                            entity.Value.Name = tmpEntity.Name;
                            foreach (var t in tmpEntity.Tags)
                            {
                                _tagChangeHandler.TagChange(gameState, t.Key, entity.Key, t.Value, game);
                            }
                            SetPlayerName(game, entity.Value.GetTag(GameTag.PLAYER_ID), tmpEntity.Name);
                            _tmpEntities.Remove(tmpEntity);
                            _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entity.Key, match.Groups["value"].Value, game);
                        }
                        if (_tmpEntities.Contains(tmpEntity))
                        {
                            tmpEntity.SetTag(tag, value);
                            if (tmpEntity.HasTag(GameTag.ENTITY_ID))
                            {
                                var id = tmpEntity.GetTag(GameTag.ENTITY_ID);
                                if (game.Entities.ContainsKey(id))
                                {
                                    game.Entities[id].Name = tmpEntity.Name;
                                    foreach (var t in tmpEntity.Tags)
                                    {
                                        _tagChangeHandler.TagChange(gameState, t.Key, id, t.Value, game);
                                    }
                                    _tmpEntities.Remove(tmpEntity);
                                }
                                else
                                {
                                    Log.Warn("TMP ENTITY (" + rawEntity + ") NOW HAS A KEY, BUT GAME.ENTITIES DOES NOT CONTAIN THIS KEY");
                                }
                            }
                        }
                    }
                    else
                    {
                        _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entity.Key, match.Groups["value"].Value, game);
                    }
                }

                if (EntityNameRegex.IsMatch(logLine))
                {
                    match = EntityNameRegex.Match(logLine);
                    var name   = match.Groups["name"].Value;
                    var player = int.Parse(match.Groups["value"].Value);
                    SetPlayerName(game, player, name);
                }
            }
            else if (CreationRegex.IsMatch(logLine))
            {
                var match  = CreationRegex.Match(logLine);
                var id     = int.Parse(match.Groups["id"].Value);
                var cardId = match.Groups["cardId"].Value;
                if (!game.Entities.ContainsKey(id))
                {
                    if (string.IsNullOrEmpty(cardId))
                    {
                        if (gameState.KnownCardIds.TryGetValue(id, out cardId))
                        {
                            Log.Info($"Found known cardId for entity {id}: {cardId}");
                            gameState.KnownCardIds.Remove(id);
                        }
                    }
                    game.Entities.Add(id, new Entity(id)
                    {
                        CardId = cardId
                    });
                }
                gameState.SetCurrentEntity(id);
                if (gameState.DeterminedPlayers)
                {
                    _tagChangeHandler.InvokeQueuedActions(game);
                }
                gameState.CurrentEntityHasCardId = !string.IsNullOrEmpty(cardId);
                gameState.CurrentEntityZone      = LogReaderHelper.ParseEnum <Zone>(match.Groups["zone"].Value);
                return;
            }
            else if (UpdatingEntityRegex.IsMatch(logLine))
            {
                var match     = UpdatingEntityRegex.Match(logLine);
                var cardId    = match.Groups["cardId"].Value;
                var rawEntity = match.Groups["entity"].Value;
                int entityId;
                if (rawEntity.StartsWith("[") && EntityRegex.IsMatch(rawEntity))
                {
                    var entity = EntityRegex.Match(rawEntity);
                    entityId = int.Parse(entity.Groups["id"].Value);
                }
                else if (!int.TryParse(rawEntity, out entityId))
                {
                    entityId = -1;
                }
                if (entityId != -1)
                {
                    if (!game.Entities.ContainsKey(entityId))
                    {
                        game.Entities.Add(entityId, new Entity(entityId));
                    }
                    game.Entities[entityId].CardId = cardId;
                    gameState.SetCurrentEntity(entityId);
                    if (gameState.DeterminedPlayers)
                    {
                        _tagChangeHandler.InvokeQueuedActions(game);
                    }
                }
                if (gameState.JoustReveals > 0)
                {
                    Entity currentEntity;
                    if (game.Entities.TryGetValue(entityId, out currentEntity))
                    {
                        if (currentEntity.IsControlledBy(game.Opponent.Id))
                        {
                            gameState.GameHandler.HandleOpponentJoust(currentEntity, cardId, gameState.GetTurnNumber());
                        }
                        else if (currentEntity.IsControlledBy(game.Player.Id))
                        {
                            gameState.GameHandler.HandlePlayerJoust(currentEntity, cardId, gameState.GetTurnNumber());
                        }
                    }
                    //gameState.JoustReveals--;
                }
                return;
            }
            else if (CreationTagRegex.IsMatch(logLine) && !logLine.Contains("HIDE_ENTITY"))
            {
                var match = CreationTagRegex.Match(logLine);
                _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, gameState.CurrentEntityId, match.Groups["value"].Value, game, true);
                setup       = true;
                creationTag = true;
            }
            else if ((logLine.Contains("Begin Spectating") || logLine.Contains("Start Spectator")) && game.IsInMenu)
            {
                gameState.GameHandler.SetGameMode(GameMode.Spectator);
            }
            else if (logLine.Contains("End Spectator"))
            {
                gameState.GameHandler.SetGameMode(GameMode.Spectator);
                gameState.GameHandler.HandleGameEnd();
            }
            else if (BlockStartRegex.IsMatch(logLine))
            {
                var playerEntity =
                    game.Entities.FirstOrDefault(e => e.Value.HasTag(GameTag.PLAYER_ID) && e.Value.GetTag(GameTag.PLAYER_ID) == game.Player.Id);
                var opponentEntity =
                    game.Entities.FirstOrDefault(e => e.Value.HasTag(GameTag.PLAYER_ID) && e.Value.GetTag(GameTag.PLAYER_ID) == game.Opponent.Id);

                var match = BlockStartRegex.Match(logLine);
                var actionStartingCardId   = match.Groups["cardId"].Value.Trim();
                var actionStartingEntityId = int.Parse(match.Groups["id"].Value);

                if (string.IsNullOrEmpty(actionStartingCardId))
                {
                    Entity actionEntity;
                    if (game.Entities.TryGetValue(actionStartingEntityId, out actionEntity))
                    {
                        actionStartingCardId = actionEntity.CardId;
                    }
                }
                if (string.IsNullOrEmpty(actionStartingCardId))
                {
                    return;
                }
                if (match.Groups["type"].Value == "TRIGGER")
                {
                    switch (actionStartingCardId)
                    {
                    case Collectible.Rogue.TradePrinceGallywix:
                        AddKnownCardId(gameState, game, game.Entities[gameState.LastCardPlayed].CardId);
                        AddKnownCardId(gameState, game, NonCollectible.Neutral.TradePrinceGallywix_GallywixsCoinToken);
                        break;
                    }
                }
                else                 //POWER
                {
                    switch (actionStartingCardId)
                    {
                    case Collectible.Rogue.GangUp:
                        AddTargetAsKnownCardId(gameState, game, match, 3);
                        break;

                    case Collectible.Rogue.BeneathTheGrounds:
                        AddKnownCardId(gameState, game, NonCollectible.Rogue.BeneaththeGrounds_AmbushToken, 3);
                        break;

                    case Collectible.Warrior.IronJuggernaut:
                        AddKnownCardId(gameState, game, NonCollectible.Warrior.IronJuggernaut_BurrowingMineToken);
                        break;

                    case Collectible.Druid.Recycle:
                        AddTargetAsKnownCardId(gameState, game, match);
                        break;

                    case Collectible.Mage.ForgottenTorch:
                        AddKnownCardId(gameState, game, NonCollectible.Mage.ForgottenTorch_RoaringTorchToken);
                        break;

                    case Collectible.Warlock.CurseOfRafaam:
                        AddKnownCardId(gameState, game, NonCollectible.Warlock.CurseofRafaam_CursedToken);
                        break;

                    case Collectible.Neutral.AncientShade:
                        AddKnownCardId(gameState, game, NonCollectible.Neutral.AncientShade_AncientCurseToken);
                        break;

                    case Collectible.Priest.ExcavatedEvil:
                        AddKnownCardId(gameState, game, Collectible.Priest.ExcavatedEvil);
                        break;

                    case Collectible.Neutral.EliseStarseeker:
                        AddKnownCardId(gameState, game, NonCollectible.Neutral.EliseStarseeker_MapToTheGoldenMonkeyToken);
                        break;

                    case NonCollectible.Neutral.EliseStarseeker_MapToTheGoldenMonkeyToken:
                        AddKnownCardId(gameState, game, NonCollectible.Neutral.EliseStarseeker_GoldenMonkeyToken);
                        break;

                    default:
                        if (playerEntity.Value != null && playerEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1 && !gameState.PlayerUsedHeroPower ||
                            opponentEntity.Value != null && opponentEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1 &&
                            !gameState.OpponentUsedHeroPower)
                        {
                            var card = Database.GetCardFromId(actionStartingCardId);
                            if (card.Type == "Hero Power")
                            {
                                if (playerEntity.Value != null && playerEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1)
                                {
                                    gameState.GameHandler.HandlePlayerHeroPower(actionStartingCardId, gameState.GetTurnNumber());
                                    gameState.PlayerUsedHeroPower = true;
                                }
                                else if (opponentEntity.Value != null)
                                {
                                    gameState.GameHandler.HandleOpponentHeroPower(actionStartingCardId, gameState.GetTurnNumber());
                                    gameState.OpponentUsedHeroPower = true;
                                }
                            }
                        }
                        break;
                    }
                }
            }
            else if (logLine.Contains("BlockType=JOUST"))
            {
                gameState.JoustReveals = 2;
            }
            else if (logLine.Contains("CREATE_GAME"))
            {
                setup = true;
                _tagChangeHandler.ClearQueuedActions();
            }


            if (game.IsInMenu)
            {
                return;
            }
            if (!creationTag && gameState.DeterminedPlayers)
            {
                _tagChangeHandler.InvokeQueuedActions(game);
            }
            if (!creationTag)
            {
                gameState.ResetCurrentEntity();
            }
            if (!gameState.DeterminedPlayers && gameState.SetupDone)
            {
                if ((DateTime.Now - _lastDeterminePlayersWarning1).TotalSeconds > 5)
                {
                    Log.Warn("Could not determine players by checking for opponent hand.");
                    _lastDeterminePlayersWarning1 = DateTime.Now;
                }
                var playerCard = game.Entities.FirstOrDefault(x => x.Value.IsInHand && !string.IsNullOrEmpty(x.Value.CardId)).Value;
                if (playerCard != null)
                {
                    _tagChangeHandler.DeterminePlayers(gameState, game, playerCard.GetTag(GameTag.CONTROLLER), false);
                }
                else if ((DateTime.Now - _lastDeterminePlayersWarning2).TotalSeconds > 5)
                {
                    Log.Warn("Could not determine players by checking for player hand either... waiting for draws...");
                    _lastDeterminePlayersWarning2 = DateTime.Now;
                }
            }
            if (!setup)
            {
                gameState.SetupDone = true;
            }
        }
		private void ZoneChangeFromHand(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
		{
			Entity entity;
			if(!game.Entities.TryGetValue(id, out entity))
				return;
			switch((Zone)value)
			{
				case PLAY:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerPlay(entity, cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(Play, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentPlay(entity, cardId, entity.GetTag(ZONE_POSITION),
																 gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(Play, id, ActivePlayer.Opponent);
					}
					break;
				case REMOVEDFROMGAME:
				case SETASIDE:
				case GRAVEYARD:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerHandDiscard(entity, cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(HandDiscard, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentHandDiscard(entity, cardId, entity.GetTag(ZONE_POSITION),
																		gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(HandDiscard, id, ActivePlayer.Opponent);
					}
					break;
				case Zone.SECRET:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerSecretPlayed(entity, cardId, gameState.GetTurnNumber(), (Zone)prevValue);
						gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentSecretPlayed(entity, cardId, entity.GetTag(ZONE_POSITION),
																		 gameState.GetTurnNumber(), (Zone)prevValue, id);
						gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Opponent);
					}
					break;
				case DECK:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerMulligan(entity, cardId);
						gameState.ProposeKeyPoint(KeyPointType.Mulligan, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentMulligan(entity, entity.GetTag(ZONE_POSITION));
						gameState.ProposeKeyPoint(KeyPointType.Mulligan, id, ActivePlayer.Opponent);
					}
					break;
				default:
					Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
					break;
			}
		}
        public void Handle(string logLine, IHsGameState gameState, IGame game)
        {
            if (GameEntityRegex.IsMatch(logLine))
            {
                var match = GameEntityRegex.Match(logLine);
                var id    = int.Parse(match.Groups["id"].Value);
                if (!game.Entities.ContainsKey(id))
                {
                    game.Entities.Add(id, new Entity(id)
                    {
                        Name = "GameEntity"
                    });
                }
                gameState.CurrentEntityId = id;
            }
            else if (PlayerEntityRegex.IsMatch(logLine))
            {
                var match = PlayerEntityRegex.Match(logLine);
                var id    = int.Parse(match.Groups["id"].Value);
                if (!game.Entities.ContainsKey(id))
                {
                    game.Entities.Add(id, new Entity(id));
                }
                gameState.CurrentEntityId = id;
            }
            else if (TagChangeRegex.IsMatch(logLine))
            {
                var match     = TagChangeRegex.Match(logLine);
                var rawEntity = match.Groups["entity"].Value.Replace("UNKNOWN ENTITY ", "");
                int entityId;
                if (rawEntity.StartsWith("[") && EntityRegex.IsMatch(rawEntity))
                {
                    var entity = EntityRegex.Match(rawEntity);
                    var id     = int.Parse(entity.Groups["id"].Value);
                    _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, id, match.Groups["value"].Value, game);
                }
                else if (int.TryParse(rawEntity, out entityId))
                {
                    _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entityId, match.Groups["value"].Value, game);
                }
                else
                {
                    var entity = game.Entities.FirstOrDefault(x => x.Value.Name == rawEntity);

                    if (entity.Value == null)
                    {
                        entity = game.Entities.FirstOrDefault(x => x.Value.Name == "UNKNOWN HUMAN PLAYER");
                        if (entity.Value != null)
                        {
                            entity.Value.Name = rawEntity;
                        }
                    }

                    if (entity.Value == null)
                    {
                        //while the id is unknown, store in tmp entities
                        var tmpEntity = _tmpEntities.FirstOrDefault(x => x.Name == rawEntity);
                        if (tmpEntity == null)
                        {
                            tmpEntity = new Entity(_tmpEntities.Count + 1)
                            {
                                Name = rawEntity
                            };
                            _tmpEntities.Add(tmpEntity);
                        }
                        GAME_TAG tag;
                        Enum.TryParse(match.Groups["tag"].Value, out tag);
                        var value = LogReaderHelper.ParseTagValue(tag, match.Groups["value"].Value);
                        tmpEntity.SetTag(tag, value);
                        if (tmpEntity.HasTag(GAME_TAG.ENTITY_ID))
                        {
                            var id = tmpEntity.GetTag(GAME_TAG.ENTITY_ID);
                            if (game.Entities.ContainsKey(id))
                            {
                                game.Entities[id].Name = tmpEntity.Name;
                                foreach (var t in tmpEntity.Tags)
                                {
                                    game.Entities[id].SetTag(t.Key, t.Value);
                                }
                                _tmpEntities.Remove(tmpEntity);
                                //Logger.WriteLine("COPIED TMP ENTITY (" + rawEntity + ")");
                            }
                            else
                            {
                                Log.Warn("TMP ENTITY (" + rawEntity + ") NOW HAS A KEY, BUT GAME.ENTITIES DOES NOT CONTAIN THIS KEY", "LogReader");
                            }
                        }
                    }
                    else
                    {
                        _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entity.Key, match.Groups["value"].Value, game);
                    }
                }

                if (EntityNameRegex.IsMatch(logLine))
                {
                    match = EntityNameRegex.Match(logLine);
                    var name   = match.Groups["name"].Value;
                    var player = int.Parse(match.Groups["value"].Value);
                    if (player == game.Player.Id)
                    {
                        game.Player.Name = name;
                    }
                    else if (player == game.Opponent.Id)
                    {
                        game.Opponent.Name = name;
                    }
                }
            }
            else if (CreationRegex.IsMatch(logLine))
            {
                var match  = CreationRegex.Match(logLine);
                var id     = int.Parse(match.Groups["id"].Value);
                var cardId = match.Groups["cardId"].Value;
                if (!game.Entities.ContainsKey(id))
                {
                    if (string.IsNullOrEmpty(cardId))
                    {
                        if (gameState.KnownCardIds.TryGetValue(id, out cardId))
                        {
                            Log.Info($"Found known cardId for entity {id}: {cardId}");
                            gameState.KnownCardIds.Remove(id);
                        }
                    }
                    game.Entities.Add(id, new Entity(id)
                    {
                        CardId = cardId
                    });
                }
                gameState.CurrentEntityId        = id;
                gameState.CurrentEntityHasCardId = !string.IsNullOrEmpty(cardId);
            }
            else if (UpdatingEntityRegex.IsMatch(logLine))
            {
                var match     = UpdatingEntityRegex.Match(logLine);
                var cardId    = match.Groups["cardId"].Value;
                var rawEntity = match.Groups["entity"].Value;
                int entityId;
                if (rawEntity.StartsWith("[") && EntityRegex.IsMatch(rawEntity))
                {
                    var entity = EntityRegex.Match(rawEntity);
                    entityId = int.Parse(entity.Groups["id"].Value);
                }
                else if (!int.TryParse(rawEntity, out entityId))
                {
                    entityId = -1;
                }
                if (entityId != -1)
                {
                    gameState.CurrentEntityId = entityId;
                    if (!game.Entities.ContainsKey(entityId))
                    {
                        game.Entities.Add(entityId, new Entity(entityId));
                    }
                    game.Entities[entityId].CardId = cardId;
                }
                if (gameState.JoustReveals > 0)
                {
                    Entity currentEntity;
                    if (game.Entities.TryGetValue(entityId, out currentEntity))
                    {
                        if (currentEntity.IsControlledBy(game.Opponent.Id))
                        {
                            gameState.GameHandler.HandleOpponentJoust(currentEntity, cardId, gameState.GetTurnNumber());
                        }
                        else if (currentEntity.IsControlledBy(game.Player.Id))
                        {
                            gameState.GameHandler.HandlePlayerJoust(currentEntity, cardId, gameState.GetTurnNumber());
                        }
                    }
                    //gameState.JoustReveals--;
                }
            }
            else if (CreationTagRegex.IsMatch(logLine) && !logLine.Contains("HIDE_ENTITY"))
            {
                var match = CreationTagRegex.Match(logLine);
                _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, gameState.CurrentEntityId, match.Groups["value"].Value, game);
            }
            else if ((logLine.Contains("Begin Spectating") || logLine.Contains("Start Spectator")) && game.IsInMenu)
            {
                gameState.GameHandler.SetGameMode(GameMode.Spectator);
            }
            else if (logLine.Contains("End Spectator"))
            {
                gameState.GameHandler.SetGameMode(GameMode.Spectator);
                gameState.GameHandler.HandleGameEnd();
            }
            else if (ActionStartRegex.IsMatch(logLine))
            {
                var playerEntity =
                    game.Entities.FirstOrDefault(e => e.Value.HasTag(GAME_TAG.PLAYER_ID) && e.Value.GetTag(GAME_TAG.PLAYER_ID) == game.Player.Id);
                var opponentEntity =
                    game.Entities.FirstOrDefault(e => e.Value.HasTag(GAME_TAG.PLAYER_ID) && e.Value.GetTag(GAME_TAG.PLAYER_ID) == game.Opponent.Id);

                var match = ActionStartRegex.Match(logLine);
                var actionStartingCardId   = match.Groups["cardId"].Value.Trim();
                var actionStartingEntityId = int.Parse(match.Groups["id"].Value);

                if (string.IsNullOrEmpty(actionStartingCardId))
                {
                    Entity actionEntity;
                    if (game.Entities.TryGetValue(actionStartingEntityId, out actionEntity))
                    {
                        actionStartingCardId = actionEntity.CardId;
                    }
                }
                if (string.IsNullOrEmpty(actionStartingCardId))
                {
                    return;
                }
                if (match.Groups["type"].Value == "TRIGGER")
                {
                    switch (actionStartingCardId)
                    {
                    case Collectible.Rogue.TradePrinceGallywix:
                        AddKnownCardId(gameState, game, game.Entities[gameState.LastCardPlayed].CardId);
                        AddKnownCardId(gameState, game, NonCollectible.Neutral.GallywixsCoinToken);
                        break;
                    }
                }
                else                 //POWER
                {
                    switch (actionStartingCardId)
                    {
                    case Collectible.Rogue.GangUp:
                        AddTargetAsKnownCardId(gameState, game, match, 3);
                        break;

                    case Collectible.Rogue.BeneathTheGrounds:
                        AddKnownCardId(gameState, game, NonCollectible.Rogue.AmbushToken, 3);
                        break;

                    case Collectible.Warrior.IronJuggernaut:
                        AddKnownCardId(gameState, game, NonCollectible.Warrior.BurrowingMineToken);
                        break;

                    case Collectible.Druid.Recycle:
                        AddTargetAsKnownCardId(gameState, game, match);
                        break;

                    case Collectible.Mage.ForgottenTorch:
                        AddKnownCardId(gameState, game, NonCollectible.Mage.RoaringTorchToken);
                        break;

                    case Collectible.Warlock.CurseOfRafaam:
                        AddKnownCardId(gameState, game, NonCollectible.Warlock.CursedToken);
                        break;

                    case Collectible.Neutral.AncientShade:
                        AddKnownCardId(gameState, game, NonCollectible.Neutral.AncientCurseToken);
                        break;

                    case Collectible.Priest.ExcavatedEvil:
                        AddKnownCardId(gameState, game, Collectible.Priest.ExcavatedEvil);
                        break;

                    case Collectible.Neutral.EliseStarseeker:
                        AddKnownCardId(gameState, game, NonCollectible.Neutral.MapToTheGoldenMonkeyToken);
                        break;

                    case NonCollectible.Neutral.MapToTheGoldenMonkeyToken:
                        AddKnownCardId(gameState, game, NonCollectible.Neutral.GoldenMonkeyToken);
                        break;

                    default:
                        if (playerEntity.Value != null && playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) == 1 && !gameState.PlayerUsedHeroPower ||
                            opponentEntity.Value != null && opponentEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) == 1 &&
                            !gameState.OpponentUsedHeroPower)
                        {
                            var card = Database.GetCardFromId(actionStartingCardId);
                            if (card.Type == "Hero Power")
                            {
                                if (playerEntity.Value != null && playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) == 1)
                                {
                                    gameState.GameHandler.HandlePlayerHeroPower(actionStartingCardId, gameState.GetTurnNumber());
                                    gameState.PlayerUsedHeroPower = true;
                                }
                                else if (opponentEntity.Value != null)
                                {
                                    gameState.GameHandler.HandleOpponentHeroPower(actionStartingCardId, gameState.GetTurnNumber());
                                    gameState.OpponentUsedHeroPower = true;
                                }
                            }
                        }
                        break;
                    }
                }
            }
            else if (logLine.Contains("BlockType=JOUST"))
            {
                gameState.JoustReveals = 2;
            }
        }
Ejemplo n.º 22
0
        public void Handle(string logLine, IHsGameState gameState, IGame game)
        {
            var creationTag = false;

            if (GameEntityRegex.IsMatch(logLine))
            {
                var match = GameEntityRegex.Match(logLine);
                var id    = int.Parse(match.Groups["id"].Value);
                if (!game.Entities.ContainsKey(id))
                {
                    game.Entities.Add(id, new Entity(id)
                    {
                        Name = "GameEntity"
                    });
                }
                gameState.SetCurrentEntity(id);
                if (gameState.DeterminedPlayers)
                {
                    _tagChangeHandler.InvokeQueuedActions(game);
                }
                return;
            }
            if (PlayerEntityRegex.IsMatch(logLine))
            {
                var match = PlayerEntityRegex.Match(logLine);
                var id    = int.Parse(match.Groups["id"].Value);
                if (!game.Entities.ContainsKey(id))
                {
                    game.Entities.Add(id, new Entity(id));
                }
                if (gameState.WasInProgress)
                {
                    game.Entities[id].Name = game.GetStoredPlayerName(id);
                }
                gameState.SetCurrentEntity(id);
                if (gameState.DeterminedPlayers)
                {
                    _tagChangeHandler.InvokeQueuedActions(game);
                }
                return;
            }
            if (TagChangeRegex.IsMatch(logLine))
            {
                var match     = TagChangeRegex.Match(logLine);
                var rawEntity = match.Groups["entity"].Value.Replace("UNKNOWN ENTITY ", "");
                if (rawEntity.StartsWith("[") && EntityRegex.IsMatch(rawEntity))
                {
                    var entity = EntityRegex.Match(rawEntity);
                    var id     = int.Parse(entity.Groups["id"].Value);
                    _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, id, match.Groups["value"].Value, game);
                }
                else if (int.TryParse(rawEntity, out int entityId))
                {
                    _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entityId, match.Groups["value"].Value, game);
                }
                else
                {
                    var entity = game.Entities.FirstOrDefault(x => x.Value.Name == rawEntity);

                    if (entity.Value == null)
                    {
                        var players            = game.Entities.Where(x => x.Value.HasTag(GameTag.PLAYER_ID)).Take(2).ToList();
                        var unnamedPlayers     = players.Where(x => string.IsNullOrEmpty(x.Value.Name)).ToList();
                        var unknownHumanPlayer = players.FirstOrDefault(x => x.Value.Name == "UNKNOWN HUMAN PLAYER");
                        if (unnamedPlayers.Count == 0 && unknownHumanPlayer.Value != null)
                        {
                            Log.Info("Updating UNKNOWN HUMAN PLAYER");
                            entity = unknownHumanPlayer;
                        }

                        //while the id is unknown, store in tmp entities
                        var tmpEntity = _tmpEntities.FirstOrDefault(x => x.Name == rawEntity);
                        if (tmpEntity == null)
                        {
                            tmpEntity = new Entity(_tmpEntities.Count + 1)
                            {
                                Name = rawEntity
                            };
                            _tmpEntities.Add(tmpEntity);
                        }
                        Enum.TryParse(match.Groups["tag"].Value, out GameTag tag);
                        var value = GameTagHelper.ParseTag(tag, match.Groups["value"].Value);
                        if (unnamedPlayers.Count == 1)
                        {
                            entity = unnamedPlayers.Single();
                        }
                        else if (unnamedPlayers.Count == 2 && tag == GameTag.CURRENT_PLAYER && value == 0)
                        {
                            entity = game.Entities.FirstOrDefault(x => x.Value?.HasTag(GameTag.CURRENT_PLAYER) ?? false);
                        }
                        if (entity.Value != null)
                        {
                            entity.Value.Name = tmpEntity.Name;
                            foreach (var t in tmpEntity.Tags)
                            {
                                _tagChangeHandler.TagChange(gameState, t.Key, entity.Key, t.Value, game);
                            }
                            _tmpEntities.Remove(tmpEntity);
                            _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entity.Key, match.Groups["value"].Value, game);
                        }
                        if (_tmpEntities.Contains(tmpEntity))
                        {
                            tmpEntity.SetTag(tag, value);
                            var player = game.Player.Name == tmpEntity.Name ? game.Player
                                                                                : (game.Opponent.Name == tmpEntity.Name ? game.Opponent : null);
                            if (player != null)
                            {
                                var playerEntity = game.Entities.FirstOrDefault(x => x.Value.GetTag(GameTag.PLAYER_ID) == player.Id).Value;
                                if (playerEntity != null)
                                {
                                    playerEntity.Name = tmpEntity.Name;
                                    foreach (var t in tmpEntity.Tags)
                                    {
                                        _tagChangeHandler.TagChange(gameState, t.Key, playerEntity.Id, t.Value, game);
                                    }
                                    _tmpEntities.Remove(tmpEntity);
                                }
                            }
                        }
                    }
                    else
                    {
                        _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entity.Key, match.Groups["value"].Value, game);
                    }
                }
            }
            else if (CreationRegex.IsMatch(logLine))
            {
                var match  = CreationRegex.Match(logLine);
                var id     = int.Parse(match.Groups["id"].Value);
                var cardId = match.Groups["cardId"].Value;
                var zone   = GameTagHelper.ParseEnum <Zone>(match.Groups["zone"].Value);
                if (!game.Entities.ContainsKey(id))
                {
                    if (string.IsNullOrEmpty(cardId) && zone != Zone.SETASIDE)
                    {
                        var blockId = gameState.CurrentBlock?.Id;
                        if (blockId.HasValue && gameState.KnownCardIds.ContainsKey(blockId.Value))
                        {
                            cardId = gameState.KnownCardIds[blockId.Value].FirstOrDefault();
                            if (!string.IsNullOrEmpty(cardId))
                            {
                                Log.Info($"Found known cardId for entity {id}: {cardId}");
                                gameState.KnownCardIds[blockId.Value].Remove(cardId);
                            }
                        }
                    }
                    game.Entities.Add(id, new Entity(id)
                    {
                        CardId = cardId
                    });
                }
                gameState.SetCurrentEntity(id);
                if (gameState.DeterminedPlayers)
                {
                    _tagChangeHandler.InvokeQueuedActions(game);
                }
                gameState.CurrentEntityHasCardId = !string.IsNullOrEmpty(cardId);
                gameState.CurrentEntityZone      = zone;
                return;
            }
            else if (UpdatingEntityRegex.IsMatch(logLine))
            {
                var match     = UpdatingEntityRegex.Match(logLine);
                var cardId    = match.Groups["cardId"].Value;
                var rawEntity = match.Groups["entity"].Value;
                var type      = match.Groups["type"].Value;
                int entityId;
                if (rawEntity.StartsWith("[") && EntityRegex.IsMatch(rawEntity))
                {
                    var entity = EntityRegex.Match(rawEntity);
                    entityId = int.Parse(entity.Groups["id"].Value);
                }
                else if (!int.TryParse(rawEntity, out entityId))
                {
                    entityId = -1;
                }
                if (entityId != -1)
                {
                    if (!game.Entities.ContainsKey(entityId))
                    {
                        game.Entities.Add(entityId, new Entity(entityId));
                    }
                    if (type != "CHANGE_ENTITY" || string.IsNullOrEmpty(game.Entities[entityId].CardId))
                    {
                        game.Entities[entityId].CardId = cardId;
                    }
                    gameState.SetCurrentEntity(entityId);
                    if (gameState.DeterminedPlayers)
                    {
                        _tagChangeHandler.InvokeQueuedActions(game);
                    }
                }
                if (gameState.JoustReveals > 0)
                {
                    if (game.Entities.TryGetValue(entityId, out Entity currentEntity))
                    {
                        if (currentEntity.IsControlledBy(game.Opponent.Id))
                        {
                            gameState.GameHandler.HandleOpponentJoust(currentEntity, cardId, gameState.GetTurnNumber());
                        }
                        else if (currentEntity.IsControlledBy(game.Player.Id))
                        {
                            gameState.GameHandler.HandlePlayerJoust(currentEntity, cardId, gameState.GetTurnNumber());
                        }
                    }
                }
                return;
            }
            else if (CreationTagRegex.IsMatch(logLine) && !logLine.Contains("HIDE_ENTITY"))
            {
                var match = CreationTagRegex.Match(logLine);
                _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, gameState.CurrentEntityId, match.Groups["value"].Value, game, true);
                creationTag = true;
            }
            else if (logLine.Contains("HIDE_ENTITY"))
            {
                if (gameState.CurrentBlock?.CardId == Collectible.Neutral.KingTogwaggle ||
                    gameState.CurrentBlock?.CardId == NonCollectible.Neutral.KingTogwaggle_KingsRansomToken)
                {
                    var match = HideEntityRegex.Match(logLine);
                    if (match.Success)
                    {
                        var id = int.Parse(match.Groups["id"].Value);
                        if (game.Entities.TryGetValue(id, out var entity))
                        {
                            entity.Info.Hidden = true;
                        }
                    }
                }
            }
            if (logLine.Contains("End Spectator") && !game.IsInMenu)
            {
                gameState.GameHandler.HandleGameEnd();
            }
            else if (logLine.Contains("BLOCK_START"))
            {
                var match     = BlockStartRegex.Match(logLine);
                var blockType = match.Success ? match.Groups["type"].Value : null;
                var cardId    = match.Success ? match.Groups["Id"].Value : null;
                gameState.BlockStart(blockType, cardId);

                if (match.Success && (blockType == "TRIGGER" || blockType == "POWER"))
                {
                    var playerEntity =
                        game.Entities.FirstOrDefault(
                            e => e.Value.HasTag(GameTag.PLAYER_ID) && e.Value.GetTag(GameTag.PLAYER_ID) == game.Player.Id);
                    var opponentEntity =
                        game.Entities.FirstOrDefault(
                            e => e.Value.HasTag(GameTag.PLAYER_ID) && e.Value.GetTag(GameTag.PLAYER_ID) == game.Opponent.Id);

                    var actionStartingCardId   = match.Groups["cardId"].Value.Trim();
                    var actionStartingEntityId = int.Parse(match.Groups["id"].Value);

                    if (string.IsNullOrEmpty(actionStartingCardId))
                    {
                        if (game.Entities.TryGetValue(actionStartingEntityId, out Entity actionEntity))
                        {
                            actionStartingCardId = actionEntity.CardId;
                        }
                    }
                    if (string.IsNullOrEmpty(actionStartingCardId))
                    {
                        return;
                    }
                    if (blockType == "TRIGGER")
                    {
                        switch (actionStartingCardId)
                        {
                        case Collectible.Rogue.TradePrinceGallywix:
                            AddKnownCardId(gameState, game.Entities[gameState.LastCardPlayed].CardId);
                            AddKnownCardId(gameState, NonCollectible.Neutral.TradePrinceGallywix_GallywixsCoinToken);
                            break;

                        case Collectible.Shaman.WhiteEyes:
                            AddKnownCardId(gameState, NonCollectible.Shaman.WhiteEyes_TheStormGuardianToken);
                            break;

                        case Collectible.Hunter.RaptorHatchling:
                            AddKnownCardId(gameState, NonCollectible.Hunter.RaptorHatchling_RaptorPatriarchToken);
                            break;

                        case Collectible.Warrior.DirehornHatchling:
                            AddKnownCardId(gameState, NonCollectible.Warrior.DirehornHatchling_DirehornMatriarchToken);
                            break;

                        case Collectible.Mage.FrozenClone:
                            AddKnownCardId(gameState, GetTargetCardId(match), 2);
                            break;

                        case Collectible.Shaman.Moorabi:
                        case Collectible.Rogue.SonyaShadowdancer:
                            AddKnownCardId(gameState, GetTargetCardId(match));
                            break;

                        case Collectible.Neutral.HoardingDragon:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheCoin, 2);
                            break;

                        case Collectible.Priest.GildedGargoyle:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheCoin);
                            break;

                        case Collectible.Druid.AstralTiger:
                            AddKnownCardId(gameState, Collectible.Druid.AstralTiger);
                            break;

                        case Collectible.Rogue.Kingsbane:
                            AddKnownCardId(gameState, Collectible.Rogue.Kingsbane);
                            break;

                        case Collectible.Neutral.WeaselTunneler:
                            AddKnownCardId(gameState, Collectible.Neutral.WeaselTunneler);
                            break;
                        }
                    }
                    else                     //POWER
                    {
                        switch (actionStartingCardId)
                        {
                        case Collectible.Rogue.GangUp:
                        case Collectible.Hunter.DireFrenzy:
                            AddKnownCardId(gameState, GetTargetCardId(match), 3);
                            break;

                        case Collectible.Rogue.BeneathTheGrounds:
                            AddKnownCardId(gameState, NonCollectible.Rogue.BeneaththeGrounds_NerubianAmbushToken, 3);
                            break;

                        case Collectible.Warrior.IronJuggernaut:
                            AddKnownCardId(gameState, NonCollectible.Warrior.IronJuggernaut_BurrowingMineToken);
                            break;

                        case Collectible.Druid.Recycle:
                        case Collectible.Mage.ManicSoulcaster:
                        case Collectible.Neutral.ZolaTheGorgon:
                        case Collectible.Druid.Splintergraft:
                        //case Collectible.Priest.HolyWater: -- TODO
                        case Collectible.Neutral.BalefulBanker:
                        case Collectible.Neutral.DollmasterDorian:
                            AddKnownCardId(gameState, GetTargetCardId(match));
                            break;

                        case Collectible.Mage.ForgottenTorch:
                            AddKnownCardId(gameState, NonCollectible.Mage.ForgottenTorch_RoaringTorchToken);
                            break;

                        case Collectible.Warlock.CurseOfRafaam:
                            AddKnownCardId(gameState, NonCollectible.Warlock.CurseofRafaam_CursedToken);
                            break;

                        case Collectible.Neutral.AncientShade:
                            AddKnownCardId(gameState, NonCollectible.Neutral.AncientShade_AncientCurseToken);
                            break;

                        case Collectible.Priest.ExcavatedEvil:
                            AddKnownCardId(gameState, Collectible.Priest.ExcavatedEvil);
                            break;

                        case Collectible.Neutral.EliseStarseeker:
                            AddKnownCardId(gameState, NonCollectible.Neutral.EliseStarseeker_MapToTheGoldenMonkeyToken);
                            break;

                        case NonCollectible.Neutral.EliseStarseeker_MapToTheGoldenMonkeyToken:
                            AddKnownCardId(gameState, NonCollectible.Neutral.EliseStarseeker_GoldenMonkeyToken);
                            break;

                        case Collectible.Neutral.Doomcaller:
                            AddKnownCardId(gameState, NonCollectible.Neutral.Cthun);
                            break;

                        case Collectible.Druid.JadeIdol:
                            AddKnownCardId(gameState, Collectible.Druid.JadeIdol, 3);
                            break;

                        case NonCollectible.Hunter.TheMarshQueen_QueenCarnassaToken:
                            AddKnownCardId(gameState, NonCollectible.Hunter.TheMarshQueen_CarnassasBroodToken, 15);
                            break;

                        case Collectible.Neutral.EliseTheTrailblazer:
                            AddKnownCardId(gameState, NonCollectible.Neutral.ElisetheTrailblazer_UngoroPackToken);
                            break;

                        case Collectible.Mage.GhastlyConjurer:
                            AddKnownCardId(gameState, Collectible.Mage.MirrorImage);
                            break;

                        case Collectible.Mage.DeckOfWonders:
                            AddKnownCardId(gameState, NonCollectible.Mage.DeckofWonders_ScrollOfWonderToken, 5);
                            break;

                        case Collectible.Neutral.TheDarkness:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheDarkness_DarknessCandleToken, 3);
                            break;

                        case Collectible.Rogue.FaldoreiStrider:
                            AddKnownCardId(gameState, NonCollectible.Rogue.FaldoreiStrider_SpiderAmbushEnchantment, 3);
                            break;

                        case Collectible.Neutral.KingTogwaggle:
                            AddKnownCardId(gameState, NonCollectible.Neutral.KingTogwaggle_KingsRansomToken);
                            break;

                        case NonCollectible.Neutral.TheCandle:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheCandle);
                            break;

                        //case Collectible.Rogue.Wanted: -- TODO
                        //	AddKnownCardId(gameState, NonCollectible.Neutral.TheCoin);
                        //	break;
                        default:
                            if (playerEntity.Value != null && playerEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1 &&
                                !gameState.PlayerUsedHeroPower ||
                                opponentEntity.Value != null && opponentEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1 &&
                                !gameState.OpponentUsedHeroPower)
                            {
                                var card = Database.GetCardFromId(actionStartingCardId);
                                if (card.Type == "Hero Power")
                                {
                                    if (playerEntity.Value != null && playerEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1)
                                    {
                                        gameState.GameHandler.HandlePlayerHeroPower(actionStartingCardId, gameState.GetTurnNumber());
                                        gameState.PlayerUsedHeroPower = true;
                                    }
                                    else if (opponentEntity.Value != null)
                                    {
                                        gameState.GameHandler.HandleOpponentHeroPower(actionStartingCardId, gameState.GetTurnNumber());
                                        gameState.OpponentUsedHeroPower = true;
                                    }
                                }
                            }
                            break;
                        }
                    }
                }
                else if (logLine.Contains("BlockType=JOUST"))
                {
                    gameState.JoustReveals = 2;
                }
                else if (logLine.Contains("BlockType=REVEAL_CARD"))
                {
                    gameState.JoustReveals = 1;
                }
                else if (gameState.GameTriggerCount == 0 && logLine.Contains("BLOCK_START BlockType=TRIGGER Entity=GameEntity"))
                {
                    gameState.GameTriggerCount++;
                }
            }
            else if (logLine.Contains("CREATE_GAME"))
            {
                _tagChangeHandler.ClearQueuedActions();
            }
            else if (logLine.Contains("BLOCK_END"))
            {
                if (gameState.GameTriggerCount < 10 && (game.GameEntity?.HasTag(GameTag.TURN) ?? false))
                {
                    gameState.GameTriggerCount += 10;
                    _tagChangeHandler.InvokeQueuedActions(game);
                    gameState.SetupDone = true;
                }
                if (gameState.CurrentBlock?.Type == "JOUST" || gameState.CurrentBlock?.Type == "REVEAL_CARD")
                {
                    //make sure there are no more queued actions that might depend on JoustReveals
                    _tagChangeHandler.InvokeQueuedActions(game);
                    gameState.JoustReveals = 0;
                }

                gameState.BlockEnd();
            }


            if (game.IsInMenu)
            {
                return;
            }
            if (!creationTag && gameState.DeterminedPlayers)
            {
                _tagChangeHandler.InvokeQueuedActions(game);
            }
            if (!creationTag)
            {
                gameState.ResetCurrentEntity();
            }
        }
Ejemplo n.º 23
0
        private void ZonePositionChange(IHsGameState gameState, int id, IGame game)
        {
            var entity     = game.Entities[id];
            var zone       = entity.GetTag(ZONE);
            var controller = entity.GetTag(CONTROLLER);

            if (zone == (int)HAND)
            {
                if (controller == game.Player.Id)
                {
                    ReplayMaker.Generate(HandPos, id, ActivePlayer.Player, game);
                    gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Player, entity, HAND, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    ReplayMaker.Generate(HandPos, id, ActivePlayer.Opponent, game);
                    gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Opponent, entity, HAND, gameState.GetTurnNumber());
                }
            }
            else if (zone == (int)PLAY)
            {
                if (controller == game.Player.Id)
                {
                    ReplayMaker.Generate(BoardPos, id, ActivePlayer.Player, game);
                    gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Player, entity, PLAY, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    ReplayMaker.Generate(BoardPos, id, ActivePlayer.Opponent, game);
                    gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Opponent, entity, PLAY, gameState.GetTurnNumber());
                }
            }
        }
        private void ZoneChangeFromPlay(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
        {
            if (!game.Entities.TryGetValue(id, out var entity))
            {
                return;
            }
            switch ((Zone)value)
            {
            case HAND:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerBackToHand(entity, cardId, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentPlayToHand(entity, cardId, gameState.GetTurnNumber(), id);
                }
                break;

            case DECK:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerPlayToDeck(entity, cardId, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentPlayToDeck(entity, cardId, gameState.GetTurnNumber());
                }
                break;

            case GRAVEYARD:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerPlayToGraveyard(entity, cardId, gameState.GetTurnNumber(), game.PlayerEntity?.IsCurrentPlayer ?? false);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentPlayToGraveyard(entity, cardId, gameState.GetTurnNumber(), game.PlayerEntity?.IsCurrentPlayer ?? false);
                }
                break;

            case REMOVEDFROMGAME:
            case SETASIDE:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerRemoveFromPlay(entity, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentRemoveFromPlay(entity, gameState.GetTurnNumber());
                }
                break;

            case PLAY:
                break;

            default:
                Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
                break;
            }
        }
Ejemplo n.º 25
0
        private void ZoneChangeFromDeck(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
        {
            switch ((TAG_ZONE)value)
            {
            case HAND:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerDraw(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(Draw, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    if (!string.IsNullOrEmpty(game.Entities[id].CardId) && gameState.SetupDone)
                    {
#if DEBUG
                        Log.Debug($"Opponent Draw (EntityID={id}) already has a CardID. Removing. Blizzard Pls.");
#endif
                        game.Entities[id].CardId = string.Empty;
                    }
                    gameState.GameHandler.HandleOpponentDraw(game.Entities[id], gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(Draw, id, ActivePlayer.Opponent);
                }
                break;

            case SETASIDE:
            case REMOVEDFROMGAME:
                if (controller == game.Player.Id)
                {
                    if (gameState.JoustReveals > 0)
                    {
                        gameState.JoustReveals--;
                        break;
                    }
                    gameState.GameHandler.HandlePlayerRemoveFromDeck(game.Entities[id], gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    if (gameState.JoustReveals > 0)
                    {
                        gameState.JoustReveals--;
                        break;
                    }
                    gameState.GameHandler.HandleOpponentRemoveFromDeck(game.Entities[id], gameState.GetTurnNumber());
                }
                break;

            case GRAVEYARD:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerDeckDiscard(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentDeckDiscard(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Opponent);
                }
                break;

            case PLAY:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerDeckToPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentDeckToPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Opponent);
                }
                break;

            case TAG_ZONE.SECRET:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerSecretPlayed(game.Entities[id], cardId, gameState.GetTurnNumber(), true);
                    gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentSecretPlayed(game.Entities[id], cardId, -1, gameState.GetTurnNumber(), true, id);
                    gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
                }
                break;

            default:
                Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
                break;
            }
        }
        private void ZoneChangeFromDeck(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
        {
            if (!game.Entities.TryGetValue(id, out var entity))
            {
                return;
            }
            switch ((Zone)value)
            {
            case HAND:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerDraw(entity, cardId, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentDraw(entity, gameState.GetTurnNumber());
                }
                break;

            case SETASIDE:
            case REMOVEDFROMGAME:
                if (!game.SetupDone)
                {
                    entity.Info.Created = true;
                    return;
                }
                if (controller == game.Player.Id)
                {
                    if (gameState.JoustReveals > 0)
                    {
                        gameState.JoustReveals--;
                        break;
                    }
                    gameState.GameHandler.HandlePlayerRemoveFromDeck(entity, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    if (gameState.JoustReveals > 0)
                    {
                        gameState.JoustReveals--;
                        break;
                    }
                    if (gameState.CurrentBlock?.CardId == Collectible.Neutral.GrandArchivist)
                    {
                        gameState.CurrentBlock.EntityDiscardedByArchivist = entity;
                    }
                    gameState.GameHandler.HandleOpponentRemoveFromDeck(entity, gameState.GetTurnNumber());
                }
                break;

            case GRAVEYARD:
                var parentId = gameState?.CurrentBlock?.CardId;

                if (parentId != null)
                {
                    if (parentId == ClassicTrackingCardId)
                    {
                        entity.Info.Hidden = true;
                        entity.SetTag(GameTag.ZONE, (int)Zone.DECK);
                    }
                }

                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerDeckDiscard(entity, cardId, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentDeckDiscard(entity, cardId, gameState.GetTurnNumber());
                }
                break;

            case PLAY:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerDeckToPlay(entity, cardId, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentDeckToPlay(entity, cardId, gameState.GetTurnNumber());
                }
                break;

            case Zone.SECRET:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerSecretPlayed(entity, cardId, gameState.GetTurnNumber(), (Zone)prevValue);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentSecretPlayed(entity, cardId, -1, gameState.GetTurnNumber(), (Zone)prevValue, id);
                }
                break;

            default:
                Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
                break;
            }
        }
        public void Handle(string logLine, IHsGameState gameState, IGame game)
        {
            if (logLine.Contains("CREATE_GAME"))
            {
                gameState.GameHandler.HandleGameStart();
                gameState.GameEnded     = false;
                gameState.AddToTurn     = -1;
                gameState.GameLoaded    = true;
                gameState.LastGameStart = DateTime.Now;
            }
            else if (HsLogReaderConstants.PowerTaskList.GameEntityRegex.IsMatch(logLine))
            {
                gameState.GameHandler.HandleGameStart();
                gameState.GameEnded = false;
                gameState.AddToTurn = -1;
                var match = HsLogReaderConstants.PowerTaskList.GameEntityRegex.Match(logLine);
                var id    = int.Parse(match.Groups["id"].Value);
                if (!game.Entities.ContainsKey(id))
                {
                    game.Entities.Add(id, new Entity(id));
                }
                gameState.CurrentEntityId = id;
            }
            else if (HsLogReaderConstants.PowerTaskList.PlayerEntityRegex.IsMatch(logLine))
            {
                var match = HsLogReaderConstants.PowerTaskList.PlayerEntityRegex.Match(logLine);
                var id    = int.Parse(match.Groups["id"].Value);
                if (!game.Entities.ContainsKey(id))
                {
                    game.Entities.Add(id, new Entity(id));
                }
                gameState.CurrentEntityId = id;
            }
            else if (HsLogReaderConstants.PowerTaskList.TagChangeRegex.IsMatch(logLine))
            {
                var match     = HsLogReaderConstants.PowerTaskList.TagChangeRegex.Match(logLine);
                var rawEntity = match.Groups["entity"].Value.Replace("UNKNOWN ENTITY ", "");
                int entityId;
                if (rawEntity.StartsWith("[") && HsLogReaderConstants.PowerTaskList.EntityRegex.IsMatch(rawEntity))
                {
                    var entity = HsLogReaderConstants.PowerTaskList.EntityRegex.Match(rawEntity);
                    var id     = int.Parse(entity.Groups["id"].Value);
                    _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, id, match.Groups["value"].Value, game);
                }
                else if (int.TryParse(rawEntity, out entityId))
                {
                    _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entityId, match.Groups["value"].Value, game);
                }
                else
                {
                    var entity = game.Entities.FirstOrDefault(x => x.Value.Name == rawEntity);

                    if (entity.Value == null)
                    {
                        entity = game.Entities.FirstOrDefault(x => x.Value.Name == "UNKNOWN HUMAN PLAYER");
                        if (entity.Value != null)
                        {
                            entity.Value.Name = rawEntity;
                        }
                    }

                    if (entity.Value == null)
                    {
                        //while the id is unknown, store in tmp entities
                        var tmpEntity = _tmpEntities.FirstOrDefault(x => x.Name == rawEntity);
                        if (tmpEntity == null)
                        {
                            tmpEntity      = new Entity(_tmpEntities.Count + 1);
                            tmpEntity.Name = rawEntity;
                            _tmpEntities.Add(tmpEntity);
                        }
                        GAME_TAG tag;
                        Enum.TryParse(match.Groups["tag"].Value, out tag);
                        var value = HsLogReaderV2.ParseTagValue(tag, match.Groups["value"].Value);
                        tmpEntity.SetTag(tag, value);
                        if (tmpEntity.HasTag(GAME_TAG.ENTITY_ID))
                        {
                            var id = tmpEntity.GetTag(GAME_TAG.ENTITY_ID);
                            if (game.Entities.ContainsKey(id))
                            {
                                game.Entities[id].Name = tmpEntity.Name;
                                foreach (var t in tmpEntity.Tags)
                                {
                                    game.Entities[id].SetTag(t.Key, t.Value);
                                }
                                _tmpEntities.Remove(tmpEntity);
                                //Logger.WriteLine("COPIED TMP ENTITY (" + rawEntity + ")");
                            }
                            else
                            {
                                Logger.WriteLine(
                                    "TMP ENTITY (" + rawEntity + ") NOW HAS A KEY, BUT GAME.ENTITIES DOES NOT CONTAIN THIS KEY",
                                    "LogReader");
                            }
                        }
                    }
                    else
                    {
                        _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entity.Key, match.Groups["value"].Value, game);
                    }
                }

                if (HsLogReaderConstants.PowerTaskList.EntityNameRegex.IsMatch(logLine))
                {
                    match = HsLogReaderConstants.PowerTaskList.EntityNameRegex.Match(logLine);
                    var name   = match.Groups["name"].Value;
                    var player = int.Parse(match.Groups["value"].Value);
                    if (player == 1)
                    {
                        gameState.GameHandler.HandlePlayerName(name);
                    }
                    else if (player == 2)
                    {
                        gameState.GameHandler.HandleOpponentName(name);
                    }
                }
            }
            else if (HsLogReaderConstants.PowerTaskList.CreationRegex.IsMatch(logLine))
            {
                var match  = HsLogReaderConstants.PowerTaskList.CreationRegex.Match(logLine);
                var id     = int.Parse(match.Groups["id"].Value);
                var cardId = match.Groups["cardId"].Value;
                if (!game.Entities.ContainsKey(id))
                {
                    if (string.IsNullOrEmpty(cardId))
                    {
                        if (gameState.KnownCardIds.TryGetValue(id, out cardId))
                        {
                            Logger.WriteLine(string.Format("Found known cardId for entity {0}: {1}", id, cardId));
                            gameState.KnownCardIds.Remove(id);
                        }
                    }
                    game.Entities.Add(id, new Entity(id)
                    {
                        CardId = cardId
                    });
                }
                gameState.CurrentEntityId        = id;
                gameState.CurrentEntityHasCardId = !string.IsNullOrEmpty(cardId);
            }
            else if (HsLogReaderConstants.PowerTaskList.UpdatingEntityRegex.IsMatch(logLine))
            {
                var match     = HsLogReaderConstants.PowerTaskList.UpdatingEntityRegex.Match(logLine);
                var cardId    = match.Groups["cardId"].Value;
                var rawEntity = match.Groups["entity"].Value;
                int entityId;
                if (rawEntity.StartsWith("[") && HsLogReaderConstants.PowerTaskList.EntityRegex.IsMatch(rawEntity))
                {
                    var entity = HsLogReaderConstants.PowerTaskList.EntityRegex.Match(rawEntity);
                    entityId = int.Parse(entity.Groups["id"].Value);
                }
                else if (!int.TryParse(rawEntity, out entityId))
                {
                    entityId = -1;
                }
                if (entityId != -1)
                {
                    gameState.CurrentEntityId = entityId;
                    if (!game.Entities.ContainsKey(entityId))
                    {
                        game.Entities.Add(entityId, new Entity(entityId));
                    }
                    game.Entities[entityId].CardId = cardId;
                }
                if (gameState.JoustReveals > 0)
                {
                    Entity currentEntity;
                    if (game.Entities.TryGetValue(entityId, out currentEntity))
                    {
                        if (currentEntity.IsControlledBy(game.Opponent.Id))
                        {
                            gameState.GameHandler.HandleOpponentJoust(currentEntity, cardId, gameState.GetTurnNumber());
                        }
                        else if (currentEntity.IsControlledBy(game.Player.Id))
                        {
                            gameState.GameHandler.HandlePlayerJoust(currentEntity, cardId, gameState.GetTurnNumber());
                        }
                    }
                    //gameState.JoustReveals--;
                }
            }
            else if (HsLogReaderConstants.PowerTaskList.CreationTagRegex.IsMatch(logLine) && !logLine.Contains("HIDE_ENTITY"))
            {
                var match = HsLogReaderConstants.PowerTaskList.CreationTagRegex.Match(logLine);
                _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, gameState.CurrentEntityId, match.Groups["value"].Value, game);
            }
            else if ((logLine.Contains("Begin Spectating") || logLine.Contains("Start Spectator")) && game.IsInMenu)
            {
                gameState.GameHandler.SetGameMode(GameMode.Spectator);
            }
            else if (logLine.Contains("End Spectator"))
            {
                gameState.GameHandler.SetGameMode(GameMode.Spectator);
                gameState.GameHandler.HandleGameEnd();
            }
            else if (HsLogReaderConstants.PowerTaskList.ActionStartRegex.IsMatch(logLine))
            {
                Entity actionEntity;
                var    playerEntity =
                    game.Entities.FirstOrDefault(
                        e => e.Value.HasTag(GAME_TAG.PLAYER_ID) && e.Value.GetTag(GAME_TAG.PLAYER_ID) == game.Player.Id);
                var opponentEntity =
                    game.Entities.FirstOrDefault(
                        e => e.Value.HasTag(GAME_TAG.PLAYER_ID) && e.Value.GetTag(GAME_TAG.PLAYER_ID) == game.Opponent.Id);

                var match = HsLogReaderConstants.PowerTaskList.ActionStartRegex.Match(logLine);
                var actionStartingCardId   = match.Groups["cardId"].Value.Trim();
                var actionStartingEntityId = int.Parse(match.Groups["id"].Value);

                if (string.IsNullOrEmpty(actionStartingCardId))
                {
                    if (game.Entities.TryGetValue(actionStartingEntityId, out actionEntity))
                    {
                        actionStartingCardId = actionEntity.CardId;
                    }
                }
                if (game.Entities.TryGetValue(actionStartingEntityId, out actionEntity))
                {
                    // spell owned by the player
                    if (actionEntity.HasTag(GAME_TAG.CONTROLLER) &&
                        actionEntity.GetTag(GAME_TAG.CONTROLLER) == game.Player.Id &&
                        actionEntity.GetTag(GAME_TAG.CARDTYPE) == (int)TAG_CARDTYPE.ABILITY)
                    {
                        int    targetEntityId = actionEntity.GetTag(GAME_TAG.CARD_TARGET);
                        Entity targetEntity;

                        bool targetsMinion = false;

                        if (game.Entities.TryGetValue(targetEntityId, out targetEntity))
                        {
                            targetsMinion = true;
                        }

                        gameState.GameHandler.HandlePlayerSpellPlayed(targetsMinion);
                    }
                }
                if (!string.IsNullOrEmpty(actionStartingCardId))
                {
                    if (actionStartingCardId == "BRM_007") //Gang Up
                    {
                        //if (playerEntity.Value != null && playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) == 1)
                        //{
                        var target = match.Groups["target"].Value.Trim();
                        if (target.StartsWith("[") && HsLogReaderConstants.PowerTaskList.EntityRegex.IsMatch(target))
                        {
                            var cardIdMatch = HsLogReaderConstants.PowerTaskList.CardIdRegex.Match(target);
                            if (cardIdMatch.Success)
                            {
                                var targetCardId = cardIdMatch.Groups["cardId"].Value.Trim();

                                for (int i = 0; i < 3; i++)
                                {
                                    var id = game.Entities.Count + i + 1;
                                    if (!gameState.KnownCardIds.ContainsKey(id))
                                    {
                                        gameState.KnownCardIds.Add(id, targetCardId);
                                    }
                                }
                            }
                        }
                        //}
                    }
                    else if (actionStartingCardId == "GVG_056") //Iron Juggernaut
                    {
                        // burrowing mine will be the entity created next
                        int id = game.Entities.Count + 1;
                        //if(playerEntity.Value == null || playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) != 1)
                        //{
                        if (!gameState.KnownCardIds.ContainsKey(id))
                        {
                            gameState.KnownCardIds.Add(id, "GVG_056t");
                        }
                        //}
                    }
                    else if (actionStartingCardId == "GVG_031") //Recycle
                    {
                        // Recycled card will be the entity created next
                        int id = game.Entities.Count + 1;
                        //if(playerEntity.Value == null || playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) != 1)
                        //{
                        gameState.ProposeKeyPoint(KeyPointType.CreateToDeck, id, ActivePlayer.Player);
                        var target = match.Groups["target"].Value.Trim();
                        if (target.StartsWith("[") && HsLogReaderConstants.PowerTaskList.EntityRegex.IsMatch(target))
                        {
                            var cardIdMatch = HsLogReaderConstants.PowerTaskList.CardIdRegex.Match(target);
                            if (cardIdMatch.Success)
                            {
                                var targetCardId = cardIdMatch.Groups["cardId"].Value.Trim();
                                if (!gameState.KnownCardIds.ContainsKey(id))
                                {
                                    gameState.KnownCardIds.Add(id, targetCardId);
                                }
                            }
                        }
                        //}
                    }
                    else if (actionStartingCardId == "GVG_035") //Malorne
                    {
                        // Malorne will be the entity created next
                        int id = game.Entities.Count + 1;
                        //if(playerEntity.Value == null || playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) != 1)
                        //{
                        if (!gameState.KnownCardIds.ContainsKey(id))
                        {
                            gameState.KnownCardIds.Add(id, "GVG_035");
                        }
                        // }
                    }


                    else
                    {
                        if (playerEntity.Value != null && playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) == 1 &&
                            !gameState.PlayerUsedHeroPower ||
                            opponentEntity.Value != null && opponentEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) == 1 &&
                            !gameState.OpponentUsedHeroPower)
                        {
                            var card = Database.GetCardFromId(actionStartingCardId);
                            if (card.Type == "Hero Power")
                            {
                                if (playerEntity.Value != null && playerEntity.Value.GetTag(GAME_TAG.CURRENT_PLAYER) == 1)
                                {
                                    gameState.GameHandler.HandlePlayerHeroPower(actionStartingCardId, gameState.GetTurnNumber());
                                    gameState.PlayerUsedHeroPower = true;
                                }
                                else if (opponentEntity.Value != null)
                                {
                                    gameState.GameHandler.HandleOpponentHeroPower(actionStartingCardId, gameState.GetTurnNumber());
                                    gameState.OpponentUsedHeroPower = true;
                                }
                            }
                        }
                    }
                }
            }
            else if (logLine.Contains("BlockType=JOUST"))
            {
                gameState.JoustReveals = 2;
            }
        }
 private void ControllerChange(IHsGameState gameState, int id, IGame game, int prevValue, int value)
 {
     if (!game.Entities.TryGetValue(id, out var entity))
     {
         return;
     }
     if (prevValue <= 0)
     {
         entity.Info.OriginalController = value;
         return;
     }
     if (entity.HasTag(PLAYER_ID))
     {
         return;
     }
     if (value == game.Player.Id)
     {
         if (entity.IsInZone(Zone.SECRET))
         {
             gameState.GameHandler.HandleOpponentStolen(entity, entity.Info.LatestCardId, gameState.GetTurnNumber());
         }
         else if (entity.IsInZone(PLAY))
         {
             gameState.GameHandler.HandleOpponentStolen(entity, entity.Info.LatestCardId, gameState.GetTurnNumber());
         }
     }
     else if (value == game.Opponent.Id)
     {
         if (entity.IsInZone(Zone.SECRET))
         {
             gameState.GameHandler.HandlePlayerStolen(entity, entity.Info.LatestCardId, gameState.GetTurnNumber());
         }
         else if (entity.IsInZone(PLAY))
         {
             gameState.GameHandler.HandlePlayerStolen(entity, entity.Info.LatestCardId, gameState.GetTurnNumber());
         }
     }
 }
        public void Handle(string logLine, IHsGameState gameState, IGame game)
        {
            var creationTag = false;

            if (GameEntityRegex.IsMatch(logLine))
            {
                var match = GameEntityRegex.Match(logLine);
                var id    = int.Parse(match.Groups["id"].Value);
                if (!game.Entities.ContainsKey(id))
                {
                    game.Entities.Add(id, new Entity(id)
                    {
                        Name = "GameEntity"
                    });
                }
                gameState.SetCurrentEntity(id);
                if (gameState.DeterminedPlayers)
                {
                    _tagChangeHandler.InvokeQueuedActions(game);
                }
                return;
            }
            if (PlayerEntityRegex.IsMatch(logLine))
            {
                var match = PlayerEntityRegex.Match(logLine);
                var id    = int.Parse(match.Groups["id"].Value);
                if (!game.Entities.ContainsKey(id))
                {
                    game.Entities.Add(id, new Entity(id));
                }
                if (gameState.WasInProgress)
                {
                    game.Entities[id].Name = game.GetStoredPlayerName(id);
                }
                gameState.SetCurrentEntity(id);
                if (gameState.DeterminedPlayers)
                {
                    _tagChangeHandler.InvokeQueuedActions(game);
                }
                return;
            }
            if (TagChangeRegex.IsMatch(logLine))
            {
                var match     = TagChangeRegex.Match(logLine);
                var rawEntity = match.Groups["entity"].Value.Replace("UNKNOWN ENTITY ", "");
                if (rawEntity.StartsWith("[") && EntityRegex.IsMatch(rawEntity))
                {
                    var entity = EntityRegex.Match(rawEntity);
                    var id     = int.Parse(entity.Groups["id"].Value);
                    _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, id, match.Groups["value"].Value, game);
                }
                else if (int.TryParse(rawEntity, out int entityId))
                {
                    _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entityId, match.Groups["value"].Value, game);
                }
                else
                {
                    var entity = game.Entities.FirstOrDefault(x => x.Value.Name == rawEntity);

                    if (entity.Value == null)
                    {
                        var players            = game.Entities.Where(x => x.Value.HasTag(GameTag.PLAYER_ID)).Take(2).ToList();
                        var unnamedPlayers     = players.Where(x => string.IsNullOrEmpty(x.Value.Name)).ToList();
                        var unknownHumanPlayer = players.FirstOrDefault(x => x.Value.Name == "UNKNOWN HUMAN PLAYER");
                        if (unnamedPlayers.Count == 0 && unknownHumanPlayer.Value != null)
                        {
                            Log.Info("Updating UNKNOWN HUMAN PLAYER");
                            entity = unknownHumanPlayer;
                        }

                        //while the id is unknown, store in tmp entities
                        var tmpEntity = _tmpEntities.FirstOrDefault(x => x.Name == rawEntity);
                        if (tmpEntity == null)
                        {
                            tmpEntity = new Entity(_tmpEntities.Count + 1)
                            {
                                Name = rawEntity
                            };
                            _tmpEntities.Add(tmpEntity);
                        }
                        Enum.TryParse(match.Groups["tag"].Value, out GameTag tag);
                        var value = GameTagHelper.ParseTag(tag, match.Groups["value"].Value);
                        if (unnamedPlayers.Count == 1)
                        {
                            entity = unnamedPlayers.Single();
                        }
                        else if (unnamedPlayers.Count == 2 && tag == GameTag.CURRENT_PLAYER && value == 0)
                        {
                            entity = game.Entities.FirstOrDefault(x => x.Value?.HasTag(GameTag.CURRENT_PLAYER) ?? false);
                        }
                        if (entity.Value != null)
                        {
                            entity.Value.Name = tmpEntity.Name;
                            foreach (var t in tmpEntity.Tags)
                            {
                                _tagChangeHandler.TagChange(gameState, t.Key, entity.Key, t.Value, game);
                            }
                            _tmpEntities.Remove(tmpEntity);
                            _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entity.Key, match.Groups["value"].Value, game);
                        }
                        if (_tmpEntities.Contains(tmpEntity))
                        {
                            tmpEntity.SetTag(tag, value);
                            var player = game.Player.Name == tmpEntity.Name ? game.Player
                                                                                : (game.Opponent.Name == tmpEntity.Name ? game.Opponent : null);
                            if (player != null)
                            {
                                var playerEntity = game.Entities.FirstOrDefault(x => x.Value.GetTag(GameTag.PLAYER_ID) == player.Id).Value;
                                if (playerEntity != null)
                                {
                                    playerEntity.Name = tmpEntity.Name;
                                    foreach (var t in tmpEntity.Tags)
                                    {
                                        _tagChangeHandler.TagChange(gameState, t.Key, playerEntity.Id, t.Value, game);
                                    }
                                    _tmpEntities.Remove(tmpEntity);
                                }
                            }
                        }
                    }
                    else
                    {
                        _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entity.Key, match.Groups["value"].Value, game);
                    }
                }
            }
            else if (CreationRegex.IsMatch(logLine))
            {
                var match         = CreationRegex.Match(logLine);
                var id            = int.Parse(match.Groups["id"].Value);
                var cardId        = match.Groups["cardId"].Value;
                var zone          = GameTagHelper.ParseEnum <Zone>(match.Groups["zone"].Value);
                var guessedCardId = false;
                if (!game.Entities.ContainsKey(id))
                {
                    if (string.IsNullOrEmpty(cardId) && zone != Zone.SETASIDE)
                    {
                        var blockId = gameState.CurrentBlock?.Id;
                        if (blockId.HasValue && gameState.KnownCardIds.ContainsKey(blockId.Value))
                        {
                            cardId = gameState.KnownCardIds[blockId.Value].FirstOrDefault();
                            if (!string.IsNullOrEmpty(cardId))
                            {
                                Log.Info($"Found known cardId for entity {id}: {cardId}");
                                gameState.KnownCardIds[blockId.Value].Remove(cardId);
                                guessedCardId = true;
                            }
                        }
                    }
                    var entity = new Entity(id)
                    {
                        CardId = cardId
                    };
                    if (guessedCardId)
                    {
                        entity.Info.GuessedCardState = GuessedCardState.Guessed;
                    }
                    game.Entities.Add(id, entity);
                }
                gameState.SetCurrentEntity(id);
                if (gameState.DeterminedPlayers)
                {
                    _tagChangeHandler.InvokeQueuedActions(game);
                }
                gameState.CurrentEntityHasCardId = !string.IsNullOrEmpty(cardId);
                gameState.CurrentEntityZone      = zone;
                return;
            }
            else if (UpdatingEntityRegex.IsMatch(logLine))
            {
                var match     = UpdatingEntityRegex.Match(logLine);
                var cardId    = match.Groups["cardId"].Value;
                var rawEntity = match.Groups["entity"].Value;
                var type      = match.Groups["type"].Value;
                int entityId;
                if (rawEntity.StartsWith("[") && EntityRegex.IsMatch(rawEntity))
                {
                    var entity = EntityRegex.Match(rawEntity);
                    entityId = int.Parse(entity.Groups["id"].Value);
                }
                else if (!int.TryParse(rawEntity, out entityId))
                {
                    entityId = -1;
                }
                if (entityId != -1)
                {
                    if (!game.Entities.ContainsKey(entityId))
                    {
                        game.Entities.Add(entityId, new Entity(entityId));
                    }
                    var entity = game.Entities[entityId];
                    if (type != "CHANGE_ENTITY" || string.IsNullOrEmpty(entity.CardId))
                    {
                        entity.CardId = cardId;
                        if (entity.Info.GuessedCardState != GuessedCardState.None)
                        {
                            entity.Info.GuessedCardState = GuessedCardState.Revealed;
                        }
                    }
                    if (type == "CHANGE_ENTITY")
                    {
                        if (!entity.Info.OriginalEntityWasCreated.HasValue)
                        {
                            entity.Info.OriginalEntityWasCreated = entity.Info.Created;
                        }
                        if (entity.GetTag(GameTag.TRANSFORMED_FROM_CARD) == 46706)
                        {
                            gameState.ChameleosReveal = new Tuple <int, string>(entityId, cardId);
                        }
                    }
                    gameState.SetCurrentEntity(entityId);
                    if (gameState.DeterminedPlayers)
                    {
                        _tagChangeHandler.InvokeQueuedActions(game);
                    }
                }
                if (gameState.JoustReveals > 0)
                {
                    if (game.Entities.TryGetValue(entityId, out Entity currentEntity))
                    {
                        if (currentEntity.IsControlledBy(game.Opponent.Id))
                        {
                            gameState.GameHandler.HandleOpponentJoust(currentEntity, cardId, gameState.GetTurnNumber());
                        }
                        else if (currentEntity.IsControlledBy(game.Player.Id))
                        {
                            gameState.GameHandler.HandlePlayerJoust(currentEntity, cardId, gameState.GetTurnNumber());
                        }
                    }
                }
                return;
            }
            else if (CreationTagRegex.IsMatch(logLine) && !logLine.Contains("HIDE_ENTITY"))
            {
                var match = CreationTagRegex.Match(logLine);
                _tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, gameState.CurrentEntityId, match.Groups["value"].Value, game, true);
                creationTag = true;
            }
            else if (logLine.Contains("HIDE_ENTITY"))
            {
                var match = HideEntityRegex.Match(logLine);
                if (match.Success)
                {
                    var id = int.Parse(match.Groups["id"].Value);
                    if (game.Entities.TryGetValue(id, out var entity))
                    {
                        if (entity.Info.GuessedCardState == GuessedCardState.Revealed)
                        {
                            entity.Info.GuessedCardState = GuessedCardState.Guessed;
                        }
                        if (gameState.CurrentBlock?.CardId == Collectible.Neutral.KingTogwaggle ||
                            gameState.CurrentBlock?.CardId == NonCollectible.Neutral.KingTogwaggle_KingsRansomToken)
                        {
                            entity.Info.Hidden = true;
                        }
                    }
                }
            }
            if (logLine.Contains("End Spectator") && !game.IsInMenu)
            {
                gameState.GameHandler.HandleGameEnd();
            }
            else if (logLine.Contains("BLOCK_START"))
            {
                var match     = BlockStartRegex.Match(logLine);
                var blockType = match.Success ? match.Groups["type"].Value : null;
                var cardId    = match.Success ? match.Groups["Id"].Value : null;
                var target    = GetTargetCardId(match);
                gameState.BlockStart(blockType, cardId, target);

                if (match.Success && (blockType == "TRIGGER" || blockType == "POWER"))
                {
                    var playerEntity =
                        game.Entities.FirstOrDefault(
                            e => e.Value.HasTag(GameTag.PLAYER_ID) && e.Value.GetTag(GameTag.PLAYER_ID) == game.Player.Id);
                    var opponentEntity =
                        game.Entities.FirstOrDefault(
                            e => e.Value.HasTag(GameTag.PLAYER_ID) && e.Value.GetTag(GameTag.PLAYER_ID) == game.Opponent.Id);

                    var actionStartingCardId   = match.Groups["cardId"].Value.Trim();
                    var actionStartingEntityId = int.Parse(match.Groups["id"].Value);

                    if (string.IsNullOrEmpty(actionStartingCardId))
                    {
                        if (game.Entities.TryGetValue(actionStartingEntityId, out var actionEntity))
                        {
                            actionStartingCardId = actionEntity.CardId;
                        }
                    }
                    if (string.IsNullOrEmpty(actionStartingCardId))
                    {
                        return;
                    }
                    if (actionStartingCardId == Collectible.Shaman.Shudderwock)
                    {
                        var effectCardId = match.Groups["effectCardId"].Value;
                        if (!string.IsNullOrEmpty(effectCardId))
                        {
                            actionStartingCardId = effectCardId;
                        }
                    }
                    if (actionStartingCardId == NonCollectible.Rogue.ValeeratheHollow_ShadowReflectionToken)
                    {
                        actionStartingCardId = cardId;
                    }
                    if (blockType == "TRIGGER" && actionStartingCardId == Collectible.Neutral.AugmentedElekk)
                    {
                        if (gameState.CurrentBlock.Parent != null)
                        {
                            actionStartingCardId = gameState.CurrentBlock.Parent.CardId;
                            blockType            = gameState.CurrentBlock.Parent.Type;
                            target = gameState.CurrentBlock.Parent.Target;
                        }
                    }
                    if (blockType == "TRIGGER")
                    {
                        switch (actionStartingCardId)
                        {
                        case Collectible.Rogue.TradePrinceGallywix:
                            if (!game.Entities.TryGetValue(gameState.LastCardPlayed, out var lastPlayed))
                            {
                                break;
                            }
                            AddKnownCardId(gameState, lastPlayed.CardId);
                            AddKnownCardId(gameState, NonCollectible.Neutral.TradePrinceGallywix_GallywixsCoinToken);
                            break;

                        case Collectible.Shaman.WhiteEyes:
                            AddKnownCardId(gameState, NonCollectible.Shaman.WhiteEyes_TheStormGuardianToken);
                            break;

                        case Collectible.Hunter.RaptorHatchling:
                            AddKnownCardId(gameState, NonCollectible.Hunter.RaptorHatchling_RaptorPatriarchToken);
                            break;

                        case Collectible.Warrior.DirehornHatchling:
                            AddKnownCardId(gameState, NonCollectible.Warrior.DirehornHatchling_DirehornMatriarchToken);
                            break;

                        case Collectible.Mage.FrozenClone:
                            AddKnownCardId(gameState, target, 2);
                            break;

                        case Collectible.Shaman.Moorabi:
                        case Collectible.Rogue.SonyaShadowdancer:
                            AddKnownCardId(gameState, target);
                            break;

                        case Collectible.Neutral.HoardingDragon:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheCoin, 2);
                            break;

                        case Collectible.Priest.GildedGargoyle:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheCoin);
                            break;

                        case Collectible.Druid.AstralTiger:
                            AddKnownCardId(gameState, Collectible.Druid.AstralTiger);
                            break;

                        case Collectible.Rogue.Kingsbane:
                            AddKnownCardId(gameState, Collectible.Rogue.Kingsbane);
                            break;

                        case Collectible.Neutral.WeaselTunneler:
                            AddKnownCardId(gameState, Collectible.Neutral.WeaselTunneler);
                            break;

                        case Collectible.Neutral.SparkDrill:
                            AddKnownCardId(gameState, NonCollectible.Neutral.SparkDrill_SparkToken, 2);
                            break;

                        case NonCollectible.Neutral.HakkartheSoulflayer_CorruptedBloodToken:
                            AddKnownCardId(gameState, NonCollectible.Neutral.HakkartheSoulflayer_CorruptedBloodToken, 2);
                            break;

                        //TODO: Gral, the Shark?
                        case Collectible.Paladin.ImmortalPrelate:
                            AddKnownCardId(gameState, Collectible.Paladin.ImmortalPrelate);
                            break;

                        case Collectible.Warrior.Wrenchcalibur:
                            AddKnownCardId(gameState, NonCollectible.Neutral.SeaforiumBomber_BombToken);
                            break;
                        }
                    }
                    else                     //POWER
                    {
                        switch (actionStartingCardId)
                        {
                        case Collectible.Rogue.GangUp:
                        case Collectible.Hunter.DireFrenzy:
                        case Collectible.Rogue.LabRecruiter:
                            AddKnownCardId(gameState, target, 3);
                            break;

                        case Collectible.Rogue.BeneathTheGrounds:
                            AddKnownCardId(gameState, NonCollectible.Rogue.BeneaththeGrounds_NerubianAmbushToken, 3);
                            break;

                        case Collectible.Warrior.IronJuggernaut:
                            AddKnownCardId(gameState, NonCollectible.Warrior.IronJuggernaut_BurrowingMineToken);
                            break;

                        case Collectible.Druid.Recycle:
                        case Collectible.Mage.ManicSoulcaster:
                        case Collectible.Neutral.ZolaTheGorgon:
                        case Collectible.Druid.Splintergraft:
                        //case Collectible.Priest.HolyWater: -- TODO
                        case Collectible.Neutral.BalefulBanker:
                        case Collectible.Neutral.DollmasterDorian:
                        case Collectible.Priest.Seance:
                            AddKnownCardId(gameState, target);
                            break;

                        case Collectible.Mage.ForgottenTorch:
                            AddKnownCardId(gameState, NonCollectible.Mage.ForgottenTorch_RoaringTorchToken);
                            break;

                        case Collectible.Warlock.CurseOfRafaam:
                            AddKnownCardId(gameState, NonCollectible.Warlock.CurseofRafaam_CursedToken);
                            break;

                        case Collectible.Neutral.AncientShade:
                            AddKnownCardId(gameState, NonCollectible.Neutral.AncientShade_AncientCurseToken);
                            break;

                        case Collectible.Priest.ExcavatedEvil:
                            AddKnownCardId(gameState, Collectible.Priest.ExcavatedEvil);
                            break;

                        case Collectible.Neutral.EliseStarseeker:
                            AddKnownCardId(gameState, NonCollectible.Neutral.EliseStarseeker_MapToTheGoldenMonkeyToken);
                            break;

                        case NonCollectible.Neutral.EliseStarseeker_MapToTheGoldenMonkeyToken:
                            AddKnownCardId(gameState, NonCollectible.Neutral.EliseStarseeker_GoldenMonkeyToken);
                            break;

                        case Collectible.Neutral.Doomcaller:
                            AddKnownCardId(gameState, NonCollectible.Neutral.Cthun);
                            break;

                        case Collectible.Druid.JadeIdol:
                            AddKnownCardId(gameState, Collectible.Druid.JadeIdol, 3);
                            break;

                        case NonCollectible.Hunter.TheMarshQueen_QueenCarnassaToken:
                            AddKnownCardId(gameState, NonCollectible.Hunter.TheMarshQueen_CarnassasBroodToken, 15);
                            break;

                        case Collectible.Neutral.EliseTheTrailblazer:
                            AddKnownCardId(gameState, NonCollectible.Neutral.ElisetheTrailblazer_UngoroPackToken);
                            break;

                        case Collectible.Mage.GhastlyConjurer:
                            AddKnownCardId(gameState, Collectible.Mage.MirrorImage);
                            break;

                        case Collectible.Mage.DeckOfWonders:
                            AddKnownCardId(gameState, NonCollectible.Mage.DeckofWonders_ScrollOfWonderToken, 5);
                            break;

                        case Collectible.Neutral.TheDarkness:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheDarkness_DarknessCandleToken, 3);
                            break;

                        case Collectible.Rogue.FaldoreiStrider:
                            AddKnownCardId(gameState, NonCollectible.Rogue.FaldoreiStrider_SpiderAmbushEnchantment, 3);
                            break;

                        case Collectible.Neutral.KingTogwaggle:
                            AddKnownCardId(gameState, NonCollectible.Neutral.KingTogwaggle_KingsRansomToken);
                            break;

                        case NonCollectible.Neutral.TheCandle:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheCandle);
                            break;

                        case NonCollectible.Neutral.CoinPouch:
                            AddKnownCardId(gameState, NonCollectible.Neutral.SackOfCoins);
                            break;

                        case NonCollectible.Neutral.SackOfCoins:
                            AddKnownCardId(gameState, NonCollectible.Neutral.HeftySackOfCoins);
                            break;

                        case NonCollectible.Neutral.CreepyCurio:
                            AddKnownCardId(gameState, NonCollectible.Neutral.HauntedCurio);
                            break;

                        case NonCollectible.Neutral.HauntedCurio:
                            AddKnownCardId(gameState, NonCollectible.Neutral.CursedCurio);
                            break;

                        case NonCollectible.Neutral.OldMilitiaHorn:
                            AddKnownCardId(gameState, NonCollectible.Neutral.MilitiaHorn);
                            break;

                        case NonCollectible.Neutral.MilitiaHorn:
                            AddKnownCardId(gameState, NonCollectible.Neutral.VeteransMilitiaHorn);
                            break;

                        case NonCollectible.Neutral.SurlyMob:
                            AddKnownCardId(gameState, NonCollectible.Neutral.AngryMob);
                            break;

                        case NonCollectible.Neutral.AngryMob:
                            AddKnownCardId(gameState, NonCollectible.Neutral.CrazedMob);
                            break;

                        case Collectible.Neutral.SparkEngine:
                            AddKnownCardId(gameState, NonCollectible.Neutral.SparkDrill_SparkToken);
                            break;

                        case Collectible.Priest.ExtraArms:
                            AddKnownCardId(gameState, NonCollectible.Priest.ExtraArms_MoreArmsToken);
                            break;

                        case Collectible.Neutral.SeaforiumBomber:
                        case Collectible.Warrior.ClockworkGoblin:
                            AddKnownCardId(gameState, NonCollectible.Neutral.SeaforiumBomber_BombToken);
                            break;

                        //case Collectible.Rogue.Wanted: -- TODO
                        //	AddKnownCardId(gameState, NonCollectible.Neutral.TheCoin);
                        //	break;
                        //TODO: Hex Lord Malacrass
                        //TODO: Krag'wa, the Frog
                        case Collectible.Hunter.HalazziTheLynx:
                            AddKnownCardId(gameState, NonCollectible.Hunter.Springpaw_LynxToken, 10);
                            break;

                        case Collectible.Neutral.BananaBuffoon:
                            AddKnownCardId(gameState, NonCollectible.Neutral.BananaBuffoon_BananasToken, 2);
                            break;

                        case Collectible.Neutral.BootyBayBookie:
                            AddKnownCardId(gameState, NonCollectible.Neutral.TheCoin);
                            break;

                        case Collectible.Neutral.PortalKeeper:
                        case Collectible.Neutral.PortalOverfiend:
                            AddKnownCardId(gameState, NonCollectible.Neutral.PortalKeeper_FelhoundPortalToken);
                            break;

                        case Collectible.Rogue.TogwagglesScheme:
                            AddKnownCardId(gameState, target);
                            break;

                        case Collectible.Paladin.SandwaspQueen:
                            AddKnownCardId(gameState, NonCollectible.Paladin.SandwaspQueen_SandwaspToken, 2);
                            break;

                        case Collectible.Rogue.ShadowOfDeath:
                            AddKnownCardId(gameState, NonCollectible.Rogue.ShadowofDeath_ShadowToken, 3);
                            break;

                        case Collectible.Warlock.Impbalming:
                            AddKnownCardId(gameState, NonCollectible.Warlock.Impbalming_WorthlessImpToken, 3);
                            break;

                        default:
                            if (playerEntity.Value != null && playerEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1 &&
                                !gameState.PlayerUsedHeroPower ||
                                opponentEntity.Value != null && opponentEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1 &&
                                !gameState.OpponentUsedHeroPower)
                            {
                                var card = Database.GetCardFromId(actionStartingCardId);
                                if (card.Type == "Hero Power")
                                {
                                    if (playerEntity.Value != null && playerEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1)
                                    {
                                        gameState.GameHandler.HandlePlayerHeroPower(actionStartingCardId, gameState.GetTurnNumber());
                                        gameState.PlayerUsedHeroPower = true;
                                    }
                                    else if (opponentEntity.Value != null)
                                    {
                                        gameState.GameHandler.HandleOpponentHeroPower(actionStartingCardId, gameState.GetTurnNumber());
                                        gameState.OpponentUsedHeroPower = true;
                                    }
                                }
                            }
                            break;
                        }
                    }
                }
                else if (logLine.Contains("BlockType=JOUST"))
                {
                    gameState.JoustReveals = 2;
                }
                else if (logLine.Contains("BlockType=REVEAL_CARD"))
                {
                    gameState.JoustReveals = 1;
                }
                else if (gameState.GameTriggerCount == 0 && logLine.Contains("BLOCK_START BlockType=TRIGGER Entity=GameEntity"))
                {
                    gameState.GameTriggerCount++;
                }
            }
            else if (logLine.Contains("CREATE_GAME"))
            {
                _tagChangeHandler.ClearQueuedActions();
            }
            else if (logLine.Contains("BLOCK_END"))
            {
                if (gameState.GameTriggerCount < 10 && (game.GameEntity?.HasTag(GameTag.TURN) ?? false))
                {
                    gameState.GameTriggerCount += 10;
                    _tagChangeHandler.InvokeQueuedActions(game);
                    game.SetupDone = true;
                }
                if (gameState.CurrentBlock?.Type == "JOUST" || gameState.CurrentBlock?.Type == "REVEAL_CARD")
                {
                    //make sure there are no more queued actions that might depend on JoustReveals
                    _tagChangeHandler.InvokeQueuedActions(game);
                    gameState.JoustReveals = 0;
                }

                if (gameState.CurrentBlock?.Type == "TRIGGER" &&
                    (gameState.CurrentBlock?.CardId == NonCollectible.Neutral.Chameleos_ShiftingEnchantment ||
                     gameState.CurrentBlock?.CardId == Collectible.Priest.Chameleos) &&
                    gameState.ChameleosReveal != null &&
                    game.Entities.TryGetValue(gameState.ChameleosReveal.Item1, out var chameleos) &&
                    chameleos.HasTag(GameTag.SHIFTING))
                {
                    gameState.GameHandler.HandleChameleosReveal(gameState.ChameleosReveal.Item2);
                }

                gameState.ChameleosReveal = null;

                gameState.BlockEnd();
            }


            if (game.IsInMenu)
            {
                return;
            }
            if (!creationTag && gameState.DeterminedPlayers)
            {
                _tagChangeHandler.InvokeQueuedActions(game);
            }
            if (!creationTag)
            {
                gameState.ResetCurrentEntity();
            }
        }
		public void Handle(string logLine, IHsGameState gameState, IGame game)
		{
			var creationTag = false;
			if(GameEntityRegex.IsMatch(logLine))
			{
				var match = GameEntityRegex.Match(logLine);
				var id = int.Parse(match.Groups["id"].Value);
				if(!game.Entities.ContainsKey(id))
					game.Entities.Add(id, new Entity(id) {Name = "GameEntity"});
				gameState.SetCurrentEntity(id);
				if(gameState.DeterminedPlayers)
					_tagChangeHandler.InvokeQueuedActions(game);
				return;
			}
			else if(PlayerEntityRegex.IsMatch(logLine))
			{
				var match = PlayerEntityRegex.Match(logLine);
				var id = int.Parse(match.Groups["id"].Value);
				if(!game.Entities.ContainsKey(id))
					game.Entities.Add(id, new Entity(id));
				if(gameState.WasInProgress)
					game.Entities[id].Name = game.GetStoredPlayerName(id);
				gameState.SetCurrentEntity(id);
				if(gameState.DeterminedPlayers)
					_tagChangeHandler.InvokeQueuedActions(game);
				return;
			}
			else if(TagChangeRegex.IsMatch(logLine))
			{
				var match = TagChangeRegex.Match(logLine);
				var rawEntity = match.Groups["entity"].Value.Replace("UNKNOWN ENTITY ", "");
				int entityId;
				if(rawEntity.StartsWith("[") && EntityRegex.IsMatch(rawEntity))
				{
					var entity = EntityRegex.Match(rawEntity);
					var id = int.Parse(entity.Groups["id"].Value);
					_tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, id, match.Groups["value"].Value, game);
				}
				else if(int.TryParse(rawEntity, out entityId))
					_tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entityId, match.Groups["value"].Value, game);
				else
				{
					var entity = game.Entities.FirstOrDefault(x => x.Value.Name == rawEntity);

					if(entity.Value == null)
					{
						var players = game.Entities.Where(x => x.Value.HasTag(GameTag.PLAYER_ID)).Take(2).ToList();
						var unnamedPlayers = players.Where(x => string.IsNullOrEmpty(x.Value.Name)).ToList();
						var unknownHumanPlayer = players.FirstOrDefault(x => x.Value.Name == "UNKNOWN HUMAN PLAYER");
						if(unnamedPlayers.Count == 0 && unknownHumanPlayer.Value != null)
						{
							Log.Info("Updating UNKNOWN HUMAN PLAYER");
							entity = unknownHumanPlayer;
						}

						//while the id is unknown, store in tmp entities
						var tmpEntity = _tmpEntities.FirstOrDefault(x => x.Name == rawEntity);
						if(tmpEntity == null)
						{
							tmpEntity = new Entity(_tmpEntities.Count + 1) {Name = rawEntity};
							_tmpEntities.Add(tmpEntity);
						}
						GameTag tag;
						Enum.TryParse(match.Groups["tag"].Value, out tag);
						var value = LogReaderHelper.ParseTag(tag, match.Groups["value"].Value);
						if(unnamedPlayers.Count == 1)
							entity = unnamedPlayers.Single();
						else if(unnamedPlayers.Count == 2 && tag == GameTag.CURRENT_PLAYER && value == 0)
							entity = game.Entities.FirstOrDefault(x => x.Value?.HasTag(GameTag.CURRENT_PLAYER) ?? false);
						if(entity.Value != null)
						{
							entity.Value.Name = tmpEntity.Name;
							foreach(var t in tmpEntity.Tags)
								_tagChangeHandler.TagChange(gameState, t.Key, entity.Key, t.Value, game);
							_tmpEntities.Remove(tmpEntity);
							_tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entity.Key, match.Groups["value"].Value, game);
						}
						if(_tmpEntities.Contains(tmpEntity))
						{
							tmpEntity.SetTag(tag, value);
							var player = game.Player.Name == tmpEntity.Name ? game.Player 
										: (game.Opponent.Name == tmpEntity.Name ? game.Opponent : null);
							if(player != null)
							{
								var playerEntity = game.Entities.FirstOrDefault(x => x.Value.GetTag(GameTag.PLAYER_ID) == player.Id).Value;
								if(playerEntity != null)
								{
									playerEntity.Name = tmpEntity.Name;
									foreach(var t in tmpEntity.Tags)
										_tagChangeHandler.TagChange(gameState, t.Key, playerEntity.Id, t.Value, game);
									_tmpEntities.Remove(tmpEntity);
								}
							}
						}
					}
					else
						_tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, entity.Key, match.Groups["value"].Value, game);
				}
			}
			else if(CreationRegex.IsMatch(logLine))
			{
				var match = CreationRegex.Match(logLine);
				var id = int.Parse(match.Groups["id"].Value);
				var cardId = match.Groups["cardId"].Value;
				if(!game.Entities.ContainsKey(id))
				{
					if(string.IsNullOrEmpty(cardId))
					{
						if(gameState.KnownCardIds.TryGetValue(id, out cardId))
						{
							Log.Info($"Found known cardId for entity {id}: {cardId}");
							gameState.KnownCardIds.Remove(id);
						}
					}
					game.Entities.Add(id, new Entity(id) {CardId = cardId});
				}
				gameState.SetCurrentEntity(id);
				if(gameState.DeterminedPlayers)
					_tagChangeHandler.InvokeQueuedActions(game);
				gameState.CurrentEntityHasCardId = !string.IsNullOrEmpty(cardId);
				gameState.CurrentEntityZone = LogReaderHelper.ParseEnum<Zone>(match.Groups["zone"].Value);
				return;
			}
			else if(UpdatingEntityRegex.IsMatch(logLine))
			{
				var match = UpdatingEntityRegex.Match(logLine);
				var cardId = match.Groups["cardId"].Value;
				var rawEntity = match.Groups["entity"].Value;
				int entityId;
				if(rawEntity.StartsWith("[") && EntityRegex.IsMatch(rawEntity))
				{
					var entity = EntityRegex.Match(rawEntity);
					entityId = int.Parse(entity.Groups["id"].Value);
				}
				else if(!int.TryParse(rawEntity, out entityId))
					entityId = -1;
				if(entityId != -1)
				{
					if(!game.Entities.ContainsKey(entityId))
						game.Entities.Add(entityId, new Entity(entityId));
					game.Entities[entityId].CardId = cardId;
					gameState.SetCurrentEntity(entityId);
					if(gameState.DeterminedPlayers)
						_tagChangeHandler.InvokeQueuedActions(game);
				}
				if(gameState.JoustReveals > 0)
				{
					Entity currentEntity;
					if(game.Entities.TryGetValue(entityId, out currentEntity))
					{
						if(currentEntity.IsControlledBy(game.Opponent.Id))
							gameState.GameHandler.HandleOpponentJoust(currentEntity, cardId, gameState.GetTurnNumber());
						else if(currentEntity.IsControlledBy(game.Player.Id))
							gameState.GameHandler.HandlePlayerJoust(currentEntity, cardId, gameState.GetTurnNumber());
					}
					//gameState.JoustReveals--;
				}
				return;
			}
			else if(CreationTagRegex.IsMatch(logLine) && !logLine.Contains("HIDE_ENTITY"))
			{
				var match = CreationTagRegex.Match(logLine);
				_tagChangeHandler.TagChange(gameState, match.Groups["tag"].Value, gameState.CurrentEntityId, match.Groups["value"].Value, game, true);
				creationTag = true;
			}
			if(logLine.Contains("End Spectator"))
				gameState.GameHandler.HandleGameEnd();
			else if(BlockStartRegex.IsMatch(logLine))
			{
				var playerEntity =
					game.Entities.FirstOrDefault(
						e => e.Value.HasTag(GameTag.PLAYER_ID) && e.Value.GetTag(GameTag.PLAYER_ID) == game.Player.Id);
				var opponentEntity =
					game.Entities.FirstOrDefault(
						e => e.Value.HasTag(GameTag.PLAYER_ID) && e.Value.GetTag(GameTag.PLAYER_ID) == game.Opponent.Id);

				var match = BlockStartRegex.Match(logLine);
				var actionStartingCardId = match.Groups["cardId"].Value.Trim();
				var actionStartingEntityId = int.Parse(match.Groups["id"].Value);

				if(string.IsNullOrEmpty(actionStartingCardId))
				{
					Entity actionEntity;
					if(game.Entities.TryGetValue(actionStartingEntityId, out actionEntity))
						actionStartingCardId = actionEntity.CardId;
				}
				if(string.IsNullOrEmpty(actionStartingCardId))
					return;
				if(match.Groups["type"].Value == "TRIGGER")
				{
					switch(actionStartingCardId)
					{
						case Collectible.Rogue.TradePrinceGallywix:
							AddKnownCardId(gameState, game, game.Entities[gameState.LastCardPlayed].CardId);
							AddKnownCardId(gameState, game, NonCollectible.Neutral.TradePrinceGallywix_GallywixsCoinToken);
							break;
					}
				}
				else //POWER
				{
					switch(actionStartingCardId)
					{
						case Collectible.Rogue.GangUp:
							AddTargetAsKnownCardId(gameState, game, match, 3);
							break;
						case Collectible.Rogue.BeneathTheGrounds:
							AddKnownCardId(gameState, game, NonCollectible.Rogue.BeneaththeGrounds_AmbushToken, 3);
							break;
						case Collectible.Warrior.IronJuggernaut:
							AddKnownCardId(gameState, game, NonCollectible.Warrior.IronJuggernaut_BurrowingMineToken);
							break;
						case Collectible.Druid.Recycle:
							AddTargetAsKnownCardId(gameState, game, match);
							break;
						case Collectible.Mage.ForgottenTorch:
							AddKnownCardId(gameState, game, NonCollectible.Mage.ForgottenTorch_RoaringTorchToken);
							break;
						case Collectible.Warlock.CurseOfRafaam:
							AddKnownCardId(gameState, game, NonCollectible.Warlock.CurseofRafaam_CursedToken);
							break;
						case Collectible.Neutral.AncientShade:
							AddKnownCardId(gameState, game, NonCollectible.Neutral.AncientShade_AncientCurseToken);
							break;
						case Collectible.Priest.ExcavatedEvil:
							AddKnownCardId(gameState, game, Collectible.Priest.ExcavatedEvil);
							break;
						case Collectible.Neutral.EliseStarseeker:
							AddKnownCardId(gameState, game, NonCollectible.Neutral.EliseStarseeker_MapToTheGoldenMonkeyToken);
							break;
						case NonCollectible.Neutral.EliseStarseeker_MapToTheGoldenMonkeyToken:
							AddKnownCardId(gameState, game, NonCollectible.Neutral.EliseStarseeker_GoldenMonkeyToken);
							break;
						case Collectible.Neutral.Doomcaller:
							AddKnownCardId(gameState, game, NonCollectible.Neutral.Cthun);
							break;
						default:
							if(playerEntity.Value != null && playerEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1
								&& !gameState.PlayerUsedHeroPower
								|| opponentEntity.Value != null && opponentEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1
								&& !gameState.OpponentUsedHeroPower)
							{
								var card = Database.GetCardFromId(actionStartingCardId);
								if(card.Type == "Hero Power")
								{
									if(playerEntity.Value != null && playerEntity.Value.GetTag(GameTag.CURRENT_PLAYER) == 1)
									{
										gameState.GameHandler.HandlePlayerHeroPower(actionStartingCardId, gameState.GetTurnNumber());
										gameState.PlayerUsedHeroPower = true;
									}
									else if(opponentEntity.Value != null)
									{
										gameState.GameHandler.HandleOpponentHeroPower(actionStartingCardId, gameState.GetTurnNumber());
										gameState.OpponentUsedHeroPower = true;
									}
								}
							}
							break;
					}
				}
			}
			else if(logLine.Contains("BlockType=JOUST"))
				gameState.JoustReveals = 2;
			else if(logLine.Contains("CREATE_GAME"))
				_tagChangeHandler.ClearQueuedActions();
			else if(gameState.GameTriggerCount == 0 && logLine.Contains("BLOCK_START BlockType=TRIGGER Entity=GameEntity"))
				gameState.GameTriggerCount++;
			else if(gameState.GameTriggerCount < 10 && logLine.Contains("BLOCK_END") && (game.GameEntity?.HasTag(GameTag.TURN) ?? false))
			{
				gameState.GameTriggerCount += 10;
				_tagChangeHandler.InvokeQueuedActions(game);
				gameState.SetupDone = true;
			}


			if(game.IsInMenu)
				return;
			if(!creationTag && gameState.DeterminedPlayers)
				_tagChangeHandler.InvokeQueuedActions(game);
			if(!creationTag)
				gameState.ResetCurrentEntity();
		}
Ejemplo n.º 31
0
        private void ZoneChangeFromSecret(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
        {
            switch ((Zone)value)
            {
            case Zone.SECRET:
            case GRAVEYARD:
                if (controller == game.Player.Id)
                {
                    gameState.ProposeKeyPoint(SecretTriggered, id, ActivePlayer.Player);
                }
                if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentSecretTrigger(game.Entities[id], cardId, gameState.GetTurnNumber(), id);
                    gameState.ProposeKeyPoint(SecretTriggered, id, ActivePlayer.Opponent);
                }
                break;

            default:
                Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
                break;
            }
        }
		public void TagChange(IHsGameState gameState, string rawTag, int id, string rawValue, IGame game, bool isRecursive = false)
		{
			if(gameState.LastId != id)
			{
				//game.SecondToLastUsedId = gameState.LastId;
				if(gameState.ProposedKeyPoint != null)
				{
					ReplayMaker.Generate(gameState.ProposedKeyPoint.Type, gameState.ProposedKeyPoint.Id, gameState.ProposedKeyPoint.Player, game);
					gameState.ProposedKeyPoint = null;
				}
			}
			gameState.LastId = id;
			if(!game.Entities.ContainsKey(id))
				game.Entities.Add(id, new Entity(id));
			GAME_TAG tag;
			if(!Enum.TryParse(rawTag, out tag))
			{
				int tmp;
				if(int.TryParse(rawTag, out tmp) && Enum.IsDefined(typeof(GAME_TAG), tmp))
					tag = (GAME_TAG)tmp;
			}
			var value = LogReaderHelper.ParseTagValue(tag, rawValue);
			var prevValue = game.Entities[id].GetTag(tag);
			game.Entities[id].SetTag(tag, value);


			if(tag == CONTROLLER && gameState.WaitForController != null && game.Player.Id == -1)
			{
				var p1 = game.Entities.FirstOrDefault(e => e.Value.GetTag(PLAYER_ID) == 1).Value;
				var p2 = game.Entities.FirstOrDefault(e => e.Value.GetTag(PLAYER_ID) == 2).Value;
				if(gameState.CurrentEntityHasCardId)
				{
					if(p1 != null)
						p1.IsPlayer = value == 1;
					if(p2 != null)
						p2.IsPlayer = value != 1;
					game.Player.Id = value;
					game.Opponent.Id = value % 2 + 1;
				}
				else
				{
					if(p1 != null)
						p1.IsPlayer = value != 1;
					if(p2 != null)
						p2.IsPlayer = value == 1;
					game.Player.Id = value % 2 + 1;
					game.Opponent.Id = value;
				}
			}
			var controller = game.Entities[id].GetTag(CONTROLLER);
			var cardId = game.Entities[id].CardId;
			if(tag == ZONE)
			{
				if(((TAG_ZONE)value == HAND
				    || ((TAG_ZONE)value == PLAY || (TAG_ZONE)value == DECK) && game.IsMulliganDone)
				   && gameState.WaitForController == null)
				{
					if(!game.IsMulliganDone)
						prevValue = (int)DECK;
					if(controller == 0)
					{
						game.Entities[id].SetTag(ZONE, prevValue);
						gameState.WaitForController = new {Tag = rawTag, Id = id, Value = rawValue};
						return;
					}
				}
				switch((TAG_ZONE)prevValue)
				{
					case DECK:
						switch((TAG_ZONE)value)
						{
							case HAND:
								if(controller == game.Player.Id)
								{
									gameState.GameHandler.HandlePlayerDraw(game.Entities[id], cardId, gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(Draw, id, ActivePlayer.Player);
								}
								else if(controller == game.Opponent.Id)
								{
									if(!string.IsNullOrEmpty(game.Entities[id].CardId))
									{
#if DEBUG
										Logger.WriteLine($"Opponent Draw (EntityID={id}) already has a CardID. Removing. Blizzard Pls.", "TagChange");
#endif
										game.Entities[id].CardId = string.Empty;
									}
									gameState.GameHandler.HandleOpponentDraw(game.Entities[id], gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(Draw, id, ActivePlayer.Opponent);
								}
								break;
							case SETASIDE:
							case REMOVEDFROMGAME:
								if(controller == game.Player.Id)
								{
									if(gameState.JoustReveals > 0)
									{
										gameState.JoustReveals--;
										break;
									}
									gameState.GameHandler.HandlePlayerRemoveFromDeck(game.Entities[id], gameState.GetTurnNumber());
								}
								else if(controller == game.Opponent.Id)
								{
									if(gameState.JoustReveals > 0)
									{
										gameState.JoustReveals--;
										break;
									}
									gameState.GameHandler.HandleOpponentRemoveFromDeck(game.Entities[id], gameState.GetTurnNumber());
								}
								break;
							case GRAVEYARD:
								if(controller == game.Player.Id)
								{
									gameState.GameHandler.HandlePlayerDeckDiscard(game.Entities[id], cardId, gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Player);
								}
								else if(controller == game.Opponent.Id)
								{
									gameState.GameHandler.HandleOpponentDeckDiscard(game.Entities[id], cardId, gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Opponent);
								}
								break;
							case PLAY:
								if(controller == game.Player.Id)
								{
									gameState.GameHandler.HandlePlayerDeckToPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Player);
								}
								else if(controller == game.Opponent.Id)
								{
									gameState.GameHandler.HandleOpponentDeckToPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Opponent);
								}
								break;
							case TAG_ZONE.SECRET:
								if(controller == game.Player.Id)
								{
									gameState.GameHandler.HandlePlayerSecretPlayed(game.Entities[id], cardId, gameState.GetTurnNumber(), true);
									gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
								}
								else if(controller == game.Opponent.Id)
								{
									gameState.GameHandler.HandleOpponentSecretPlayed(game.Entities[id], cardId, -1, gameState.GetTurnNumber(), true, id);
									gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
								}
								break;
							default:
								Logger.WriteLine($"WARNING - unhandled zone change (id={id}): {prevValue} -> {value}", "TagChange");
								break;
						}
						break;
					case HAND:
						switch((TAG_ZONE)value)
						{
							case PLAY:
								if(controller == game.Player.Id)
								{
									gameState.GameHandler.HandlePlayerPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(Play, id, ActivePlayer.Player);
								}
								else if(controller == game.Opponent.Id)
								{
									gameState.GameHandler.HandleOpponentPlay(game.Entities[id], cardId, game.Entities[id].GetTag(ZONE_POSITION),
									                                         gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(Play, id, ActivePlayer.Opponent);
								}
								break;
							case REMOVEDFROMGAME:
							case SETASIDE:
							case GRAVEYARD:
								if(controller == game.Player.Id)
								{
									gameState.GameHandler.HandlePlayerHandDiscard(game.Entities[id], cardId, gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(HandDiscard, id, ActivePlayer.Player);
								}
								else if(controller == game.Opponent.Id)
								{
									gameState.GameHandler.HandleOpponentHandDiscard(game.Entities[id], cardId, game.Entities[id].GetTag(ZONE_POSITION),
									                                                gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(HandDiscard, id, ActivePlayer.Opponent);
								}
								break;
							case TAG_ZONE.SECRET:
								if(controller == game.Player.Id)
								{
									gameState.GameHandler.HandlePlayerSecretPlayed(game.Entities[id], cardId, gameState.GetTurnNumber(), false);
									gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
								}
								else if(controller == game.Opponent.Id)
								{
									gameState.GameHandler.HandleOpponentSecretPlayed(game.Entities[id], cardId, game.Entities[id].GetTag(ZONE_POSITION),
									                                                 gameState.GetTurnNumber(), false, id);
									gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Opponent);
								}
								break;
							case DECK:
								if(controller == game.Player.Id)
								{
									gameState.GameHandler.HandlePlayerMulligan(game.Entities[id], cardId);
									gameState.ProposeKeyPoint(Mulligan, id, ActivePlayer.Player);
								}
								else if(controller == game.Opponent.Id)
								{
									gameState.GameHandler.HandleOpponentMulligan(game.Entities[id], game.Entities[id].GetTag(ZONE_POSITION));
									gameState.ProposeKeyPoint(Mulligan, id, ActivePlayer.Opponent);
								}
								break;
							default:
								Logger.WriteLine($"WARNING - unhandled zone change (id={id}): {prevValue} -> {value}", "TagChange");
								break;
						}
						break;
					case PLAY:
						switch((TAG_ZONE)value)
						{
							case HAND:
								if(controller == game.Player.Id)
								{
									gameState.GameHandler.HandlePlayerBackToHand(game.Entities[id], cardId, gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(PlayToHand, id, ActivePlayer.Player);
								}
								else if(controller == game.Opponent.Id)
								{
									gameState.GameHandler.HandleOpponentPlayToHand(game.Entities[id], cardId, gameState.GetTurnNumber(), id);
									gameState.ProposeKeyPoint(PlayToHand, id, ActivePlayer.Opponent);
								}
								break;
							case DECK:
								if(controller == game.Player.Id)
								{
									gameState.GameHandler.HandlePlayerPlayToDeck(game.Entities[id], cardId, gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(PlayToDeck, id, ActivePlayer.Player);
								}
								else if(controller == game.Opponent.Id)
								{
									gameState.GameHandler.HandleOpponentPlayToDeck(game.Entities[id], cardId, gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(PlayToDeck, id, ActivePlayer.Opponent);
								}
								break;
							case REMOVEDFROMGAME:
							case SETASIDE:
							case GRAVEYARD:
								if(controller == game.Player.Id)
								{
									gameState.GameHandler.HandlePlayerPlayToGraveyard(game.Entities[id], cardId, gameState.GetTurnNumber());
									if(game.Entities[id].HasTag(HEALTH))
										gameState.ProposeKeyPoint(Death, id, ActivePlayer.Player);
								}
								else if(controller == game.Opponent.Id)
								{
									gameState.GameHandler.HandleOpponentPlayToGraveyard(game.Entities[id], cardId, gameState.GetTurnNumber(),
									                                                    gameState.PlayersTurn());
									if(game.Entities[id].HasTag(HEALTH))
										gameState.ProposeKeyPoint(Death, id, ActivePlayer.Opponent);
								}
								break;
							default:
								Logger.WriteLine($"WARNING - unhandled zone change (id={id}): {prevValue} -> {value}", "TagChange");
								break;
						}
						break;
					case TAG_ZONE.SECRET:
						switch((TAG_ZONE)value)
						{
							case TAG_ZONE.SECRET:
							case GRAVEYARD:
								if(controller == game.Player.Id)
									gameState.ProposeKeyPoint(SecretTriggered, id, ActivePlayer.Player);
								if(controller == game.Opponent.Id)
								{
									gameState.GameHandler.HandleOpponentSecretTrigger(game.Entities[id], cardId, gameState.GetTurnNumber(), id);
									gameState.ProposeKeyPoint(SecretTriggered, id, ActivePlayer.Opponent);
								}
								break;
							default:
								Logger.WriteLine($"WARNING - unhandled zone change (id={id}): {prevValue} -> {value}", "TagChange");
								break;
						}
						break;
					case GRAVEYARD:
					case SETASIDE:
					case CREATED:
					case TAG_ZONE.INVALID:
					case REMOVEDFROMGAME:
						switch((TAG_ZONE)value)
						{
							case PLAY:
								if(controller == game.Player.Id)
								{
									gameState.GameHandler.HandlePlayerCreateInPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(Summon, id, ActivePlayer.Player);
								}
								if(controller == game.Opponent.Id)
								{
									gameState.GameHandler.HandleOpponentCreateInPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(Summon, id, ActivePlayer.Opponent);
								}
								break;
							case DECK:
								if(controller == game.Player.Id)
								{
									if(gameState.JoustReveals > 0)
										break;
									gameState.GameHandler.HandlePlayerGetToDeck(game.Entities[id], cardId, gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(CreateToDeck, id, ActivePlayer.Player);
								}
								if(controller == game.Opponent.Id)
								{
									if(gameState.JoustReveals > 0)
										break;
									gameState.GameHandler.HandleOpponentGetToDeck(game.Entities[id], gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(CreateToDeck, id, ActivePlayer.Opponent);
								}
								break;
							case HAND:
								if(controller == game.Player.Id)
								{
									gameState.GameHandler.HandlePlayerGet(game.Entities[id], cardId, gameState.GetTurnNumber());
									gameState.ProposeKeyPoint(Obtain, id, ActivePlayer.Player);
								}
								else if(controller == game.Opponent.Id)
								{
									gameState.GameHandler.HandleOpponentGet(game.Entities[id], gameState.GetTurnNumber(), id);
									gameState.ProposeKeyPoint(Obtain, id, ActivePlayer.Opponent);
								}
								break;
							default:
								Logger.WriteLine($"WARNING - unhandled zone change (id={id}): {prevValue} -> {value}", "TagChange");
								break;
						}
						break;
					default:
						Logger.WriteLine($"WARNING - unhandled zone change (id={id}): {prevValue} -> {value}", "TagChange");
						break;
				}
			}
			else if(tag == PLAYSTATE)
			{
				if(value == (int)CONCEDED)
					gameState.GameHandler.HandleConcede();
				if(!gameState.GameEnded)
				{
					if(game.Entities[id].IsPlayer)
					{
						switch((TAG_PLAYSTATE)value)
						{
							case WON:
								gameState.GameEndKeyPoint(true, id);
								gameState.GameHandler.HandleWin();
								gameState.GameHandler.HandleGameEnd();
								gameState.GameEnded = true;
								break;
							case LOST:
								gameState.GameEndKeyPoint(false, id);
								gameState.GameHandler.HandleLoss();
								gameState.GameHandler.HandleGameEnd();
								gameState.GameEnded = true;
								break;
							case TIED:
								gameState.GameEndKeyPoint(false, id);
								gameState.GameHandler.HandleTied();
								gameState.GameHandler.HandleGameEnd();
								break;
						}
					}
				}
			}
			else if(tag == CARDTYPE && value == (int)TAG_CARDTYPE.HERO)
				SetHeroAsync(id, game, gameState);
			else if(tag == CURRENT_PLAYER && value == 1)
			{
				var activePlayer = game.Entities[id].IsPlayer ? ActivePlayer.Player : ActivePlayer.Opponent;
				gameState.GameHandler.TurnStart(activePlayer, gameState.GetTurnNumber());
				if(activePlayer == ActivePlayer.Player)
					gameState.PlayerUsedHeroPower = false;
				else
					gameState.OpponentUsedHeroPower = false;
			}
			else if(tag == LAST_CARD_PLAYED)
				gameState.LastCardPlayed = value;
			else if(tag == DEFENDING)
			{
				if(controller == game.Opponent.Id)
					gameState.GameHandler.HandleDefendingEntity(value == 1 ? game.Entities[id] : null);
			}
			else if(tag == ATTACKING)
			{
				if(controller == game.Player.Id)
					gameState.GameHandler.HandleAttackingEntity(value == 1 ? game.Entities[id] : null);
			}
			else if(tag == PROPOSED_DEFENDER)
				game.OpponentSecrets.ProposedDefenderEntityId = value;
			else if(tag == PROPOSED_ATTACKER)
				game.OpponentSecrets.ProposedAttackerEntityId = value;
			else if(tag == NUM_MINIONS_PLAYED_THIS_TURN && value > 0)
			{
				if(gameState.PlayersTurn())
					gameState.GameHandler.HandlePlayerMinionPlayed();
			}
			else if(tag == PREDAMAGE && value > 0)
			{
				if(gameState.PlayersTurn())
					gameState.GameHandler.HandleOpponentDamage(game.Entities[id]);
			}
			else if(tag == NUM_TURNS_IN_PLAY && value > 0)
			{
				if(!gameState.PlayersTurn())
					gameState.GameHandler.HandleOpponentTurnStart(game.Entities[id]);
			}
			else if(tag == NUM_ATTACKS_THIS_TURN && value > 0)
			{
				if(controller == game.Player.Id)
					gameState.ProposeKeyPoint(Attack, id, ActivePlayer.Player);
				else if(controller == game.Opponent.Id)
					gameState.ProposeKeyPoint(Attack, id, ActivePlayer.Opponent);
			}
			else if(tag == ZONE_POSITION)
			{
				var zone = game.Entities[id].GetTag(ZONE);
				if(zone == (int)HAND)
				{
					if(controller == game.Player.Id)
					{
						ReplayMaker.Generate(HandPos, id, ActivePlayer.Player, game);
						gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Player, HAND, gameState.GetTurnNumber());
					}
					else if(controller == game.Opponent.Id)
					{
						ReplayMaker.Generate(HandPos, id, ActivePlayer.Opponent, game);
						gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Opponent, HAND, gameState.GetTurnNumber());
					}
				}
				else if(zone == (int)PLAY)
				{
					if(controller == game.Player.Id)
					{
						ReplayMaker.Generate(BoardPos, id, ActivePlayer.Player, game);
						gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Player, PLAY, gameState.GetTurnNumber());
					}
					else if(controller == game.Opponent.Id)
					{
						ReplayMaker.Generate(BoardPos, id, ActivePlayer.Opponent, game);
						gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Opponent, PLAY, gameState.GetTurnNumber());
					}
				}
			}
			else if(tag == CARD_TARGET && value > 0)
			{
				if(controller == game.Player.Id)
					gameState.ProposeKeyPoint(PlaySpell, id, ActivePlayer.Player);
				else if(controller == game.Opponent.Id)
					gameState.ProposeKeyPoint(PlaySpell, id, ActivePlayer.Opponent);
			}
			else if(tag == EQUIPPED_WEAPON && value == 0)
			{
				if(controller == game.Player.Id)
					gameState.ProposeKeyPoint(WeaponDestroyed, id, ActivePlayer.Player);
				else if(controller == game.Opponent.Id)
					gameState.ProposeKeyPoint(WeaponDestroyed, id, ActivePlayer.Opponent);
			}
			else if(tag == EXHAUSTED && value > 0)
			{
				if(game.Entities[id].GetTag(CARDTYPE) == (int)TAG_CARDTYPE.HERO_POWER)
				{
					if(controller == game.Player.Id)
						gameState.ProposeKeyPoint(HeroPower, id, ActivePlayer.Player);
					else if(controller == game.Opponent.Id)
						gameState.ProposeKeyPoint(HeroPower, id, ActivePlayer.Opponent);
				}
			}
			else if(tag == CONTROLLER && prevValue > 0)
			{
				if(value == game.Player.Id)
				{
					if(game.Entities[id].IsInZone(TAG_ZONE.SECRET))
					{
						gameState.GameHandler.HandleOpponentStolen(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(SecretStolen, id, ActivePlayer.Player);
					}
					else if(game.Entities[id].IsInZone(PLAY))
						gameState.GameHandler.HandleOpponentStolen(game.Entities[id], cardId, gameState.GetTurnNumber());
				}
				else if(value == game.Opponent.Id)
				{
					if(game.Entities[id].IsInZone(TAG_ZONE.SECRET))
					{
						gameState.GameHandler.HandleOpponentStolen(game.Entities[id], cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(SecretStolen, id, ActivePlayer.Player);
					}
					else if(game.Entities[id].IsInZone(PLAY))
						gameState.GameHandler.HandlePlayerStolen(game.Entities[id], cardId, gameState.GetTurnNumber());
				}
			}
			else if(tag == FATIGUE)
			{
				if(controller == game.Player.Id)
					gameState.GameHandler.HandlePlayerFatigue(Convert.ToInt32(rawValue));
				else if(controller == game.Opponent.Id)
					gameState.GameHandler.HandleOpponentFatigue(Convert.ToInt32(rawValue));
			}
			if(gameState.WaitForController != null)
			{
				if(!isRecursive)
				{
					TagChange(gameState, (string)gameState.WaitForController.Tag, (int)gameState.WaitForController.Id,
					          (string)gameState.WaitForController.Value, game, true);
					gameState.WaitForController = null;
				}
			}
		}
Ejemplo n.º 33
0
        private void ZoneChangeFromPlay(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
        {
            switch ((Zone)value)
            {
            case HAND:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerBackToHand(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(PlayToHand, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentPlayToHand(game.Entities[id], cardId, gameState.GetTurnNumber(), id);
                    gameState.ProposeKeyPoint(PlayToHand, id, ActivePlayer.Opponent);
                }
                break;

            case DECK:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerPlayToDeck(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(PlayToDeck, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentPlayToDeck(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(PlayToDeck, id, ActivePlayer.Opponent);
                }
                break;

            case GRAVEYARD:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerPlayToGraveyard(game.Entities[id], cardId, gameState.GetTurnNumber());
                    if (game.Entities[id].HasTag(HEALTH))
                    {
                        gameState.ProposeKeyPoint(Death, id, ActivePlayer.Player);
                    }
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentPlayToGraveyard(game.Entities[id], cardId, gameState.GetTurnNumber(), game.PlayerEntity?.IsCurrentPlayer ?? false);
                    if (game.Entities[id].HasTag(HEALTH))
                    {
                        gameState.ProposeKeyPoint(Death, id, ActivePlayer.Opponent);
                    }
                }
                break;

            case REMOVEDFROMGAME:
            case SETASIDE:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerRemoveFromPlay(game.Entities[id], gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentRemoveFromPlay(game.Entities[id], gameState.GetTurnNumber());
                }
                break;

            case PLAY:
                break;

            default:
                Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
                break;
            }
        }
        public void TagChange(IHsGameState gameState, string rawTag, int id, string rawValue, IGame game, bool isRecursive = false)
        {
            if (gameState.LastId != id)
            {
                game.SecondToLastUsedId = gameState.LastId;
                if (gameState.ProposedKeyPoint != null)
                {
                    ReplayMaker.Generate(gameState.ProposedKeyPoint.Type, gameState.ProposedKeyPoint.Id, gameState.ProposedKeyPoint.Player, game);
                    gameState.ProposedKeyPoint = null;
                }
            }
            gameState.LastId = id;
            if (!game.Entities.ContainsKey(id))
                game.Entities.Add(id, new Entity(id));
            GAME_TAG tag;
            if (!Enum.TryParse(rawTag, out tag))
            {
                int tmp;
                if (int.TryParse(rawTag, out tmp) && Enum.IsDefined(typeof(GAME_TAG), tmp))
                    tag = (GAME_TAG)tmp;
            }
            var value = HsLogReaderV2.ParseTagValue(tag, rawValue);
            var prevZone = game.Entities[id].GetTag(GAME_TAG.ZONE);
            game.Entities[id].SetTag(tag, value);

            if (tag == GAME_TAG.CONTROLLER && gameState.WaitForController != null && game.PlayerId == -1)
            {
                var p1 = game.Entities.FirstOrDefault(e => e.Value.GetTag(GAME_TAG.PLAYER_ID) == 1).Value;
                var p2 = game.Entities.FirstOrDefault(e => e.Value.GetTag(GAME_TAG.PLAYER_ID) == 2).Value;
                if (gameState.CurrentEntityHasCardId)
                {
                    if (p1 != null)
                        p1.IsPlayer = value == 1;
                    if (p2 != null)
                        p2.IsPlayer = value != 1;
                    game.PlayerId = value;
                    game.OpponentId = value == 1 ? 2 : 1;
                }
                else
                {
                    if (p1 != null)
                        p1.IsPlayer = value != 1;
                    if (p2 != null)
                        p2.IsPlayer = value == 1;
                    game.PlayerId = value == 1 ? 2 : 1;
                    game.OpponentId = value;
                }
            }
            var controller = game.Entities[id].GetTag(GAME_TAG.CONTROLLER);
            var player = game.Entities[id].HasTag(GAME_TAG.CONTROLLER) ? (controller == game.PlayerId ? "FRIENDLY" : "OPPOSING") : "";
            var cardId = game.Entities[id].CardId;
            if (tag == GAME_TAG.ZONE)
            {
                //Logger.WriteLine("--------" + player + " " + game.Entities[id].CardId + " " + (TAG_ZONE)prevZone + " -> " +
                //                 (TAG_ZONE)value);

                if (((TAG_ZONE)value == TAG_ZONE.HAND || ((TAG_ZONE)value == TAG_ZONE.PLAY) && game.IsMulliganDone) && gameState.WaitForController == null)
                {
                    if (!game.IsMulliganDone)
                        prevZone = (int)TAG_ZONE.DECK;
                    if (controller == 0)
                    {
                        game.Entities[id].SetTag(GAME_TAG.ZONE, prevZone);
                        gameState.WaitForController = new { Tag = rawTag, Id = id, Value = rawValue };
                        //Logger.WriteLine("CURRENTLY NO CONTROLLER SET FOR CARD, WAITING...");
                        return;
                    }
                }
                switch ((TAG_ZONE)prevZone)
                {
                    case TAG_ZONE.DECK:
                        switch ((TAG_ZONE)value)
                        {
                            case TAG_ZONE.HAND:
                                if (controller == game.PlayerId)
                                {
                                    gameState.GameHandler.HandlePlayerDraw(cardId, gameState.GetTurnNumber());
                                    gameState.ProposeKeyPoint(KeyPointType.Draw, id, ActivePlayer.Player);
                                }
                                else if (controller == game.OpponentId)
                                {
                                    gameState.GameHandler.HandleOpponentDraw(gameState.GetTurnNumber());
                                    gameState.ProposeKeyPoint(KeyPointType.Draw, id, ActivePlayer.Opponent);
                                }
                                break;
                            case TAG_ZONE.REMOVEDFROMGAME:
                            case TAG_ZONE.GRAVEYARD:
                            case TAG_ZONE.SETASIDE:
                            case TAG_ZONE.PLAY:
                                if (controller == game.PlayerId)
                                {
                                    gameState.GameHandler.HandlePlayerDeckDiscard(cardId, gameState.GetTurnNumber());
                                    gameState.ProposeKeyPoint(KeyPointType.DeckDiscard, id, ActivePlayer.Player);
                                }
                                else if (controller == game.OpponentId)
                                {
                                    gameState.GameHandler.HandleOpponentDeckDiscard(cardId, gameState.GetTurnNumber());
                                    gameState.ProposeKeyPoint(KeyPointType.DeckDiscard, id, ActivePlayer.Opponent);
                                }
                                break;
                            case TAG_ZONE.SECRET:
                                if (controller == game.PlayerId)
                                {
                                    gameState.GameHandler.HandlePlayerSecretPlayed(cardId, gameState.GetTurnNumber(), true);
                                    gameState.ProposeKeyPoint(KeyPointType.SecretPlayed, id, ActivePlayer.Player);
                                }
                                else if (controller == game.OpponentId)
                                {
                                    gameState.GameHandler.HandleOpponentSecretPlayed(cardId, -1, gameState.GetTurnNumber(), true, id);
                                    gameState.ProposeKeyPoint(KeyPointType.SecretPlayed, id, ActivePlayer.Player);
                                }
                                break;
                        }
                        break;
                    case TAG_ZONE.HAND:
                        switch ((TAG_ZONE)value)
                        {
                            case TAG_ZONE.PLAY:
                                if (controller == game.PlayerId)
                                {
                                    gameState.GameHandler.HandlePlayerPlay(cardId, gameState.GetTurnNumber());
                                    gameState.ProposeKeyPoint(KeyPointType.Play, id, ActivePlayer.Player);
                                }
                                else if (controller == game.OpponentId)
                                {
                                    gameState.GameHandler.HandleOpponentPlay(cardId, game.Entities[id].GetTag(GAME_TAG.ZONE_POSITION), gameState.GetTurnNumber());
                                    gameState.ProposeKeyPoint(KeyPointType.Play, id, ActivePlayer.Opponent);
                                }
                                break;
                            case TAG_ZONE.REMOVEDFROMGAME:
                            case TAG_ZONE.GRAVEYARD:
                                if (controller == game.PlayerId)
                                {
                                    gameState.GameHandler.HandlePlayerHandDiscard(cardId, gameState.GetTurnNumber());
                                    gameState.ProposeKeyPoint(KeyPointType.HandDiscard, id, ActivePlayer.Player);
                                }
                                else if (controller == game.OpponentId)
                                {
                                    gameState.GameHandler.HandleOpponentHandDiscard(cardId, game.Entities[id].GetTag(GAME_TAG.ZONE_POSITION), gameState.GetTurnNumber());
                                    gameState.ProposeKeyPoint(KeyPointType.HandDiscard, id, ActivePlayer.Opponent);
                                }
                                break;
                            case TAG_ZONE.SECRET:
                                if (controller == game.PlayerId)
                                {
                                    gameState.GameHandler.HandlePlayerSecretPlayed(cardId, gameState.GetTurnNumber(), false);
                                    gameState.ProposeKeyPoint(KeyPointType.SecretPlayed, id, ActivePlayer.Player);
                                }
                                else if (controller == game.OpponentId)
                                {
                                    gameState.GameHandler.HandleOpponentSecretPlayed(cardId, game.Entities[id].GetTag(GAME_TAG.ZONE_POSITION), gameState.GetTurnNumber(), false, id);
                                    gameState.ProposeKeyPoint(KeyPointType.SecretPlayed, id, ActivePlayer.Opponent);
                                }
                                break;
                            case TAG_ZONE.DECK:
                                if (controller == game.PlayerId)
                                {
                                    gameState.GameHandler.HandlePlayerMulligan(cardId);
                                    gameState.ProposeKeyPoint(KeyPointType.Mulligan, id, ActivePlayer.Player);
                                }
                                else if (controller == game.OpponentId)
                                {
                                    gameState.GameHandler.HandleOpponentMulligan(game.Entities[id].GetTag(GAME_TAG.ZONE_POSITION));
                                    gameState.ProposeKeyPoint(KeyPointType.Mulligan, id, ActivePlayer.Opponent);
                                }
                                break;
                        }
                        break;
                    case TAG_ZONE.PLAY:
                        switch ((TAG_ZONE)value)
                        {
                            case TAG_ZONE.HAND:
                                if (controller == game.PlayerId)
                                {
                                    gameState.GameHandler.HandlePlayerBackToHand(cardId, gameState.GetTurnNumber());
                                    gameState.ProposeKeyPoint(KeyPointType.PlayToHand, id, ActivePlayer.Player);
                                }
                                else if (controller == game.OpponentId)
                                {
                                    gameState.GameHandler.HandleOpponentPlayToHand(cardId, gameState.GetTurnNumber(), id);
                                    gameState.ProposeKeyPoint(KeyPointType.PlayToHand, id, ActivePlayer.Opponent);
                                }
                                break;
                            case TAG_ZONE.DECK:
                                if (controller == game.PlayerId)
                                {
                                    gameState.GameHandler.HandlePlayerPlayToDeck(cardId, gameState.GetTurnNumber());
                                    gameState.ProposeKeyPoint(KeyPointType.PlayToDeck, id, ActivePlayer.Player);
                                }
                                else if (controller == game.OpponentId)
                                {
                                    gameState.GameHandler.HandleOpponentPlayToDeck(cardId, gameState.GetTurnNumber());
                                    gameState.ProposeKeyPoint(KeyPointType.PlayToDeck, id, ActivePlayer.Opponent);
                                }
                                break;
                            case TAG_ZONE.GRAVEYARD:
                                if (game.Entities[id].HasTag(GAME_TAG.HEALTH))
                                {
                                    if (controller == game.PlayerId)
                                        gameState.ProposeKeyPoint(KeyPointType.Death, id, ActivePlayer.Player);
                                    else if (controller == game.OpponentId)
                                        gameState.ProposeKeyPoint(KeyPointType.Death, id, ActivePlayer.Opponent);
                                }
                                break;
                        }
                        break;
                    case TAG_ZONE.SECRET:
                        switch ((TAG_ZONE)value)
                        {
                            case TAG_ZONE.SECRET:
                            case TAG_ZONE.GRAVEYARD:
                                if (controller == game.PlayerId)
                                    gameState.ProposeKeyPoint(KeyPointType.SecretTriggered, id, ActivePlayer.Player);
                                if (controller == game.OpponentId)
                                {
                                    gameState.GameHandler.HandleOpponentSecretTrigger(cardId, gameState.GetTurnNumber(), id);
                                    gameState.ProposeKeyPoint(KeyPointType.SecretTriggered, id, ActivePlayer.Opponent);
                                }
                                break;
                        }
                        break;
                    case TAG_ZONE.GRAVEYARD:
                    case TAG_ZONE.SETASIDE:
                    case TAG_ZONE.CREATED:
                    case TAG_ZONE.INVALID:
                    case TAG_ZONE.REMOVEDFROMGAME:
                        switch ((TAG_ZONE)value)
                        {
                            case TAG_ZONE.PLAY:
                                if (controller == game.PlayerId)
                                    gameState.ProposeKeyPoint(KeyPointType.Summon, id, ActivePlayer.Player);
                                if (controller == game.OpponentId)
                                    gameState.ProposeKeyPoint(KeyPointType.Summon, id, ActivePlayer.Opponent);
                                break;
                            case TAG_ZONE.HAND:
                                if (controller == game.PlayerId)
                                {
                                    gameState.GameHandler.HandlePlayerGet(cardId, gameState.GetTurnNumber());
                                    gameState.ProposeKeyPoint(KeyPointType.Obtain, id, ActivePlayer.Player);
                                }
                                else if (controller == game.OpponentId)
                                {
                                    gameState.GameHandler.HandleOpponentGet(gameState.GetTurnNumber(), id);
                                    gameState.ProposeKeyPoint(KeyPointType.Obtain, id, ActivePlayer.Opponent);
                                }
                                break;
                        }
                        break;
                }
            }
            else if (tag == GAME_TAG.PLAYSTATE)
            {
                if (value == (int)TAG_PLAYSTATE.QUIT)
                    gameState.GameHandler.HandleConcede();
                if (!gameState.GameEnded)
                {
                    if (game.Entities[id].IsPlayer)
                    {
                        if (value == (int)TAG_PLAYSTATE.WON)
                        {
                            gameState.GameEndKeyPoint(true, id);
                            gameState.GameHandler.HandleWin();
                            gameState.GameHandler.HandleGameEnd();
                            gameState.GameEnded = true;
                        }
                        else if (value == (int)TAG_PLAYSTATE.LOST)
                        {
                            gameState.GameEndKeyPoint(false, id);
                            gameState.GameHandler.HandleLoss();
                            gameState.GameHandler.HandleGameEnd();
                            gameState.GameEnded = true;
                        }
                        else if (value == (int)TAG_PLAYSTATE.TIED)
                        {
                            gameState.GameEndKeyPoint(false, id);
                            gameState.GameHandler.HandleTied();
                            gameState.GameHandler.HandleGameEnd();
                        }
                    }
                }
            }
            else if (tag == GAME_TAG.CURRENT_PLAYER && value == 1)
            {
                var activePlayer = game.Entities[id].IsPlayer ? ActivePlayer.Player : ActivePlayer.Opponent;
                gameState.GameHandler.TurnStart(activePlayer, gameState.GetTurnNumber());
                if (activePlayer == ActivePlayer.Player)
                    gameState.PlayerUsedHeroPower = false;
                else
                    gameState.OpponentUsedHeroPower = false;
            }
            else if (tag == GAME_TAG.NUM_ATTACKS_THIS_TURN && value > 0)
            {
                if (controller == game.PlayerId)
                    gameState.ProposeKeyPoint(KeyPointType.Attack, id, ActivePlayer.Player);
                else if (controller == game.OpponentId)
                    gameState.ProposeKeyPoint(KeyPointType.Attack, id, ActivePlayer.Opponent);
            }
            else if (tag == GAME_TAG.ZONE_POSITION)
            {
                var zone = game.Entities[id].GetTag(GAME_TAG.ZONE);
                if (zone == (int)TAG_ZONE.HAND)
                {
                    if (controller == game.PlayerId)
                        ReplayMaker.Generate(KeyPointType.HandPos, id, ActivePlayer.Player, game);
                    else if (controller == game.OpponentId)
                        ReplayMaker.Generate(KeyPointType.HandPos, id, ActivePlayer.Opponent, game);
                }
                else if (zone == (int)TAG_ZONE.PLAY)
                {
                    if (controller == game.PlayerId)
                        ReplayMaker.Generate(KeyPointType.BoardPos, id, ActivePlayer.Player, game);
                    else if (controller == game.OpponentId)
                        ReplayMaker.Generate(KeyPointType.BoardPos, id, ActivePlayer.Opponent, game);
                }
            }
            else if (tag == GAME_TAG.CARD_TARGET && value > 0)
            {
                if (controller == game.PlayerId)
                    gameState.ProposeKeyPoint(KeyPointType.PlaySpell, id, ActivePlayer.Player);
                else if (controller == game.OpponentId)
                    gameState.ProposeKeyPoint(KeyPointType.PlaySpell, id, ActivePlayer.Opponent);
            }
            else if (tag == GAME_TAG.EQUIPPED_WEAPON && value == 0)
            {
                if (controller == game.PlayerId)
                    gameState.ProposeKeyPoint(KeyPointType.WeaponDestroyed, id, ActivePlayer.Player);
                else if (controller == game.OpponentId)
                    gameState.ProposeKeyPoint(KeyPointType.WeaponDestroyed, id, ActivePlayer.Opponent);
            }
            else if (tag == GAME_TAG.EXHAUSTED && value > 0)
            {
                if (game.Entities[id].GetTag(GAME_TAG.CARDTYPE) == (int)TAG_CARDTYPE.HERO_POWER)
                {
                    if (controller == game.PlayerId)
                        gameState.ProposeKeyPoint(KeyPointType.HeroPower, id, ActivePlayer.Player);
                    else if (controller == game.OpponentId)
                        gameState.ProposeKeyPoint(KeyPointType.HeroPower, id, ActivePlayer.Opponent);
                }
            }
            else if (tag == GAME_TAG.CONTROLLER && game.Entities[id].IsInZone(TAG_ZONE.SECRET))
            {
                if (value == game.PlayerId)
                {
                    gameState.GameHandler.HandleOpponentSecretTrigger(cardId, gameState.GetTurnNumber(), id);
                    gameState.ProposeKeyPoint(KeyPointType.SecretStolen, id, ActivePlayer.Player);
                }
                else if (value == game.OpponentId)
                    gameState.ProposeKeyPoint(KeyPointType.SecretStolen, id, ActivePlayer.Player);
            }
            else if (tag == GAME_TAG.FATIGUE)
            {
                if (controller == game.PlayerId)
                    gameState.GameHandler.HandlePlayerFatigue(Convert.ToInt32(rawValue));
                else if (controller == game.OpponentId)
                    gameState.GameHandler.HandleOpponentFatigue(Convert.ToInt32(rawValue));
            }
            if (gameState.WaitForController != null)
            {
                if (!isRecursive)
                {
                    TagChange(gameState, (string)gameState.WaitForController.Tag, (int)gameState.WaitForController.Id, (string)gameState.WaitForController.Value, game, true);
                    gameState.WaitForController = null;
                }
            }
        }
Ejemplo n.º 35
0
        private void ZoneChangeFromHand(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
        {
            switch ((Zone)value)
            {
            case PLAY:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(Play, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentPlay(game.Entities[id], cardId, game.Entities[id].GetTag(ZONE_POSITION),
                                                             gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(Play, id, ActivePlayer.Opponent);
                }
                break;

            case REMOVEDFROMGAME:
            case SETASIDE:
            case GRAVEYARD:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerHandDiscard(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(HandDiscard, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentHandDiscard(game.Entities[id], cardId, game.Entities[id].GetTag(ZONE_POSITION),
                                                                    gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(HandDiscard, id, ActivePlayer.Opponent);
                }
                break;

            case Zone.SECRET:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerSecretPlayed(game.Entities[id], cardId, gameState.GetTurnNumber(), false);
                    gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentSecretPlayed(game.Entities[id], cardId, game.Entities[id].GetTag(ZONE_POSITION),
                                                                     gameState.GetTurnNumber(), false, id);
                    gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Opponent);
                }
                break;

            case DECK:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerMulligan(game.Entities[id], cardId);
                    gameState.ProposeKeyPoint(KeyPointType.Mulligan, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentMulligan(game.Entities[id], game.Entities[id].GetTag(ZONE_POSITION));
                    gameState.ProposeKeyPoint(KeyPointType.Mulligan, id, ActivePlayer.Opponent);
                }
                break;

            default:
                Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
                break;
            }
        }
		private void NumTurnsInPlayChange(IHsGameState gameState, int id, IGame game, int value)
		{
			if(value <= 0)
				return;
			Entity entity;
			if(!game.Entities.TryGetValue(id, out entity))
				return;
			gameState.GameHandler.HandleTurnsInPlayChange(entity, gameState.GetTurnNumber());
		}
Ejemplo n.º 37
0
        private void ZoneChangeFromDeck(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
        {
            switch ((Zone)value)
            {
            case HAND:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerDraw(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(Draw, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentDraw(game.Entities[id], gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(Draw, id, ActivePlayer.Opponent);
                }
                break;

            case SETASIDE:
            case REMOVEDFROMGAME:
                if (controller == game.Player.Id)
                {
                    if (gameState.JoustReveals > 0)
                    {
                        gameState.JoustReveals--;
                        break;
                    }
                    gameState.GameHandler.HandlePlayerRemoveFromDeck(game.Entities[id], gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    if (gameState.JoustReveals > 0)
                    {
                        gameState.JoustReveals--;
                        break;
                    }
                    gameState.GameHandler.HandleOpponentRemoveFromDeck(game.Entities[id], gameState.GetTurnNumber());
                }
                break;

            case GRAVEYARD:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerDeckDiscard(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentDeckDiscard(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Opponent);
                }
                break;

            case PLAY:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerDeckToPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentDeckToPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
                    gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Opponent);
                }
                break;

            case Zone.SECRET:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerSecretPlayed(game.Entities[id], cardId, gameState.GetTurnNumber(), true);
                    gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentSecretPlayed(game.Entities[id], cardId, -1, gameState.GetTurnNumber(), true, id);
                    gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
                }
                break;

            default:
                Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
                break;
            }
        }
		private void ZoneChangeFromOther(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
		{
			Entity entity;
			if(!game.Entities.TryGetValue(id, out entity))
				return;
			if(entity.Info.OriginalZone == DECK && value != (int)DECK)
			{
				//This entity was moved from DECK to SETASIDE to HAND, e.g. by Tracking
				entity.Info.Discarded = false;
				ZoneChangeFromDeck(gameState, id, game, value, prevValue, controller, cardId);
				return;
			}
			entity.Info.Created = true;
			switch((Zone)value)
			{
				case PLAY:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerCreateInPlay(entity, cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(Summon, id, ActivePlayer.Player);
					}
					if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentCreateInPlay(entity, cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(Summon, id, ActivePlayer.Opponent);
					}
					break;
				case DECK:
					if(controller == game.Player.Id)
					{
						if(gameState.JoustReveals > 0)
							break;
						gameState.GameHandler.HandlePlayerGetToDeck(entity, cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(CreateToDeck, id, ActivePlayer.Player);
					}
					if(controller == game.Opponent.Id)
					{
						if(gameState.JoustReveals > 0)
							break;
						gameState.GameHandler.HandleOpponentGetToDeck(entity, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(CreateToDeck, id, ActivePlayer.Opponent);
					}
					break;
				case HAND:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerGet(entity, cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(Obtain, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentGet(entity, gameState.GetTurnNumber(), id);
						gameState.ProposeKeyPoint(Obtain, id, ActivePlayer.Opponent);
					}
					break;
				case Zone.SECRET:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerSecretPlayed(entity, cardId, gameState.GetTurnNumber(), (Zone)prevValue);
						gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentSecretPlayed(entity, cardId, -1, gameState.GetTurnNumber(), (Zone)prevValue, id);
						gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Opponent);
					}
					break;
				case SETASIDE:
					if(controller == game.Player.Id)
						gameState.GameHandler.HandlePlayerCreateInSetAside(entity, gameState.GetTurnNumber());
					if(controller == game.Opponent.Id)
						gameState.GameHandler.HandleOpponentCreateInSetAside(entity, gameState.GetTurnNumber());
					break;
				default:
					Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
					break;
			}
		}
        public void TagChange(IHsGameState gameState, string rawTag, int id, string rawValue, IGame game, bool isRecursive = false)
        {
            if (gameState.LastId != id)
            {
                //game.SecondToLastUsedId = gameState.LastId;
                if (gameState.ProposedKeyPoint != null)
                {
                    ReplayMaker.Generate(gameState.ProposedKeyPoint.Type, gameState.ProposedKeyPoint.Id, gameState.ProposedKeyPoint.Player, game);
                    gameState.ProposedKeyPoint = null;
                }
            }
            gameState.LastId = id;
            if (!game.Entities.ContainsKey(id))
            {
                game.Entities.Add(id, new Entity(id));
            }
            GAME_TAG tag;

            if (!Enum.TryParse(rawTag, out tag))
            {
                int tmp;
                if (int.TryParse(rawTag, out tmp) && Enum.IsDefined(typeof(GAME_TAG), tmp))
                {
                    tag = (GAME_TAG)tmp;
                }
            }
            var value     = LogReaderHelper.ParseTagValue(tag, rawValue);
            var prevValue = game.Entities[id].GetTag(tag);

            game.Entities[id].SetTag(tag, value);

            if (tag == GAME_TAG.CONTROLLER && gameState.WaitForController != null && game.Player.Id == -1)
            {
                var p1 = game.Entities.FirstOrDefault(e => e.Value.GetTag(GAME_TAG.PLAYER_ID) == 1).Value;
                var p2 = game.Entities.FirstOrDefault(e => e.Value.GetTag(GAME_TAG.PLAYER_ID) == 2).Value;
                if (gameState.CurrentEntityHasCardId)
                {
                    if (p1 != null)
                    {
                        p1.IsPlayer = value == 1;
                    }
                    if (p2 != null)
                    {
                        p2.IsPlayer = value != 1;
                    }
                    game.Player.Id   = value;
                    game.Opponent.Id = value % 2 + 1;
                }
                else
                {
                    if (p1 != null)
                    {
                        p1.IsPlayer = value != 1;
                    }
                    if (p2 != null)
                    {
                        p2.IsPlayer = value == 1;
                    }
                    game.Player.Id   = value % 2 + 1;
                    game.Opponent.Id = value;
                }
            }
            var controller = game.Entities[id].GetTag(GAME_TAG.CONTROLLER);
            var player     = game.Entities[id].HasTag(GAME_TAG.CONTROLLER) ? (controller == game.Player.Id ? "FRIENDLY" : "OPPOSING") : "";
            var cardId     = game.Entities[id].CardId;

            if (tag == GAME_TAG.ZONE)
            {
                if (((TAG_ZONE)value == TAG_ZONE.HAND || ((TAG_ZONE)value == TAG_ZONE.PLAY || (TAG_ZONE)value == TAG_ZONE.DECK) && game.IsMulliganDone) && gameState.WaitForController == null)
                {
                    if (!game.IsMulliganDone)
                    {
                        prevValue = (int)TAG_ZONE.DECK;
                    }
                    if (controller == 0)
                    {
                        game.Entities[id].SetTag(GAME_TAG.ZONE, prevValue);
                        gameState.WaitForController = new { Tag = rawTag, Id = id, Value = rawValue };
                        return;
                    }
                }
                switch ((TAG_ZONE)prevValue)
                {
                case TAG_ZONE.DECK:
                    switch ((TAG_ZONE)value)
                    {
                    case TAG_ZONE.HAND:
                        if (controller == game.Player.Id)
                        {
                            gameState.GameHandler.HandlePlayerDraw(game.Entities[id], cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.Draw, id, ActivePlayer.Player);
                        }
                        else if (controller == game.Opponent.Id)
                        {
                            if (!string.IsNullOrEmpty(game.Entities[id].CardId))
                            {
#if DEBUG
                                Logger.WriteLine(string.Format("Opponent Draw (EntityID={0}) already has a CardID. Removing. Blizzard Pls.", id), "TagChange");
#endif
                                game.Entities[id].CardId = string.Empty;
                            }
                            gameState.GameHandler.HandleOpponentDraw(game.Entities[id], gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.Draw, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.SETASIDE:
                    case TAG_ZONE.REMOVEDFROMGAME:
                        if (controller == game.Player.Id)
                        {
                            if (gameState.JoustReveals > 0)
                            {
                                gameState.JoustReveals--;
                                break;
                            }
                            gameState.GameHandler.HandlePlayerRemoveFromDeck(game.Entities[id], gameState.GetTurnNumber());
                        }
                        else if (controller == game.Opponent.Id)
                        {
                            if (gameState.JoustReveals > 0)
                            {
                                gameState.JoustReveals--;
                                break;
                            }
                            gameState.GameHandler.HandleOpponentRemoveFromDeck(game.Entities[id], gameState.GetTurnNumber());
                        }
                        break;

                    case TAG_ZONE.GRAVEYARD:
                        if (controller == game.Player.Id)
                        {
                            gameState.GameHandler.HandlePlayerDeckDiscard(game.Entities[id], cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.DeckDiscard, id, ActivePlayer.Player);
                        }
                        else if (controller == game.Opponent.Id)
                        {
                            gameState.GameHandler.HandleOpponentDeckDiscard(game.Entities[id], cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.DeckDiscard, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.PLAY:
                        if (controller == game.Player.Id)
                        {
                            gameState.GameHandler.HandlePlayerDeckToPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.DeckDiscard, id, ActivePlayer.Player);
                        }
                        else if (controller == game.Opponent.Id)
                        {
                            gameState.GameHandler.HandleOpponentDeckToPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.DeckDiscard, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.SECRET:
                        if (controller == game.Player.Id)
                        {
                            gameState.GameHandler.HandlePlayerSecretPlayed(game.Entities[id], cardId, gameState.GetTurnNumber(), true);
                            gameState.ProposeKeyPoint(KeyPointType.SecretPlayed, id, ActivePlayer.Player);
                        }
                        else if (controller == game.Opponent.Id)
                        {
                            gameState.GameHandler.HandleOpponentSecretPlayed(game.Entities[id], cardId, -1, gameState.GetTurnNumber(), true, id);
                            gameState.ProposeKeyPoint(KeyPointType.SecretPlayed, id, ActivePlayer.Player);
                        }
                        break;

                    default:
                        Logger.WriteLine(string.Format("WARNING - unhandled zone change (id={0}): {1} -> {2}", id, (TAG_ZONE)prevValue, (TAG_ZONE)value), "TagChange");
                        break;
                    }
                    break;

                case TAG_ZONE.HAND:
                    switch ((TAG_ZONE)value)
                    {
                    case TAG_ZONE.PLAY:
                        if (controller == game.Player.Id)
                        {
                            gameState.GameHandler.HandlePlayerPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.Play, id, ActivePlayer.Player);
                        }
                        else if (controller == game.Opponent.Id)
                        {
                            gameState.GameHandler.HandleOpponentPlay(game.Entities[id], cardId, game.Entities[id].GetTag(GAME_TAG.ZONE_POSITION), gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.Play, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.REMOVEDFROMGAME:
                    case TAG_ZONE.GRAVEYARD:
                        if (controller == game.Player.Id)
                        {
                            gameState.GameHandler.HandlePlayerHandDiscard(game.Entities[id], cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.HandDiscard, id, ActivePlayer.Player);
                        }
                        else if (controller == game.Opponent.Id)
                        {
                            gameState.GameHandler.HandleOpponentHandDiscard(game.Entities[id], cardId, game.Entities[id].GetTag(GAME_TAG.ZONE_POSITION), gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.HandDiscard, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.SECRET:
                        if (controller == game.Player.Id)
                        {
                            gameState.GameHandler.HandlePlayerSecretPlayed(game.Entities[id], cardId, gameState.GetTurnNumber(), false);
                            gameState.ProposeKeyPoint(KeyPointType.SecretPlayed, id, ActivePlayer.Player);
                        }
                        else if (controller == game.Opponent.Id)
                        {
                            gameState.GameHandler.HandleOpponentSecretPlayed(game.Entities[id], cardId, game.Entities[id].GetTag(GAME_TAG.ZONE_POSITION), gameState.GetTurnNumber(), false, id);
                            gameState.ProposeKeyPoint(KeyPointType.SecretPlayed, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.DECK:
                        if (controller == game.Player.Id)
                        {
                            gameState.GameHandler.HandlePlayerMulligan(game.Entities[id], cardId);
                            gameState.ProposeKeyPoint(KeyPointType.Mulligan, id, ActivePlayer.Player);
                        }
                        else if (controller == game.Opponent.Id)
                        {
                            gameState.GameHandler.HandleOpponentMulligan(game.Entities[id], game.Entities[id].GetTag(GAME_TAG.ZONE_POSITION));
                            gameState.ProposeKeyPoint(KeyPointType.Mulligan, id, ActivePlayer.Opponent);
                        }
                        break;

                    default:
                        Logger.WriteLine(string.Format("WARNING - unhandled zone change (id={0}): {1} -> {2}", id, (TAG_ZONE)prevValue, (TAG_ZONE)value), "TagChange");
                        break;
                    }
                    break;

                case TAG_ZONE.PLAY:
                    switch ((TAG_ZONE)value)
                    {
                    case TAG_ZONE.HAND:
                        if (controller == game.Player.Id)
                        {
                            gameState.GameHandler.HandlePlayerBackToHand(game.Entities[id], cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.PlayToHand, id, ActivePlayer.Player);
                        }
                        else if (controller == game.Opponent.Id)
                        {
                            gameState.GameHandler.HandleOpponentPlayToHand(game.Entities[id], cardId, gameState.GetTurnNumber(), id);
                            gameState.ProposeKeyPoint(KeyPointType.PlayToHand, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.DECK:
                        if (controller == game.Player.Id)
                        {
                            gameState.GameHandler.HandlePlayerPlayToDeck(game.Entities[id], cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.PlayToDeck, id, ActivePlayer.Player);
                        }
                        else if (controller == game.Opponent.Id)
                        {
                            gameState.GameHandler.HandleOpponentPlayToDeck(game.Entities[id], cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.PlayToDeck, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.GRAVEYARD:
                        if (controller == game.Player.Id)
                        {
                            gameState.GameHandler.HandlePlayerPlayToGraveyard(game.Entities[id], cardId,
                                                                              gameState.GetTurnNumber());
                            if (game.Entities[id].HasTag(GAME_TAG.HEALTH))
                            {
                                gameState.ProposeKeyPoint(KeyPointType.Death, id, ActivePlayer.Player);
                            }
                        }
                        else if (controller == game.Opponent.Id)
                        {
                            gameState.GameHandler.HandleOpponentPlayToGraveyard(game.Entities[id], cardId,
                                                                                gameState.GetTurnNumber(), gameState.PlayersTurn());
                            if (game.Entities[id].HasTag(GAME_TAG.HEALTH))
                            {
                                gameState.ProposeKeyPoint(KeyPointType.Death, id, ActivePlayer.Opponent);
                            }
                        }
                        break;

                    default:
                        Logger.WriteLine(string.Format("WARNING - unhandled zone change (id={0}): {1} -> {2}", id, (TAG_ZONE)prevValue, (TAG_ZONE)value), "TagChange");
                        break;
                    }
                    break;

                case TAG_ZONE.SECRET:
                    switch ((TAG_ZONE)value)
                    {
                    case TAG_ZONE.SECRET:
                    case TAG_ZONE.GRAVEYARD:
                        if (controller == game.Player.Id)
                        {
                            gameState.ProposeKeyPoint(KeyPointType.SecretTriggered, id, ActivePlayer.Player);
                        }
                        if (controller == game.Opponent.Id)
                        {
                            gameState.GameHandler.HandleOpponentSecretTrigger(game.Entities[id], cardId, gameState.GetTurnNumber(), id);
                            gameState.ProposeKeyPoint(KeyPointType.SecretTriggered, id, ActivePlayer.Opponent);
                        }
                        break;

                    default:
                        Logger.WriteLine(string.Format("WARNING - unhandled zone change (id={0}): {1} -> {2}", id, (TAG_ZONE)prevValue, (TAG_ZONE)value), "TagChange");
                        break;
                    }
                    break;

                case TAG_ZONE.GRAVEYARD:
                case TAG_ZONE.SETASIDE:
                case TAG_ZONE.CREATED:
                case TAG_ZONE.INVALID:
                case TAG_ZONE.REMOVEDFROMGAME:
                    switch ((TAG_ZONE)value)
                    {
                    case TAG_ZONE.PLAY:
                        if (controller == game.Player.Id)
                        {
                            gameState.GameHandler.HandlePlayerCreateInPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.Summon, id, ActivePlayer.Player);
                        }
                        if (controller == game.Opponent.Id)
                        {
                            gameState.GameHandler.HandleOpponentCreateInPlay(game.Entities[id], cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.Summon, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.DECK:
                        if (controller == game.Player.Id)
                        {
                            if (gameState.JoustReveals > 0)
                            {
                                break;
                            }
                            gameState.GameHandler.HandlePlayerGetToDeck(game.Entities[id], cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.CreateToDeck, id, ActivePlayer.Player);
                        }
                        if (controller == game.Opponent.Id)
                        {
                            if (gameState.JoustReveals > 0)
                            {
                                break;
                            }
                            gameState.GameHandler.HandleOpponentGetToDeck(game.Entities[id], gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.CreateToDeck, id, ActivePlayer.Opponent);
                        }
                        break;

                    case TAG_ZONE.HAND:
                        if (controller == game.Player.Id)
                        {
                            gameState.GameHandler.HandlePlayerGet(game.Entities[id], cardId, gameState.GetTurnNumber());
                            gameState.ProposeKeyPoint(KeyPointType.Obtain, id, ActivePlayer.Player);
                        }
                        else if (controller == game.Opponent.Id)
                        {
                            gameState.GameHandler.HandleOpponentGet(game.Entities[id], gameState.GetTurnNumber(), id);
                            gameState.ProposeKeyPoint(KeyPointType.Obtain, id, ActivePlayer.Opponent);
                        }
                        break;

                    default:
                        Logger.WriteLine(string.Format("WARNING - unhandled zone change (id={0}): {1} -> {2}", id, (TAG_ZONE)prevValue, (TAG_ZONE)value), "TagChange");
                        break;
                    }
                    break;

                default:
                    Logger.WriteLine(string.Format("WARNING - unhandled zone change (id={0}): {1} -> {2}", id, (TAG_ZONE)prevValue, (TAG_ZONE)value), "TagChange");
                    break;
                }
            }
            else if (tag == GAME_TAG.PLAYSTATE)
            {
                if (value == (int)TAG_PLAYSTATE.CONCEDED)
                {
                    gameState.GameHandler.HandleConcede();
                }
                if (!gameState.GameEnded)
                {
                    if (game.Entities[id].IsPlayer)
                    {
                        if (value == (int)TAG_PLAYSTATE.WON)
                        {
                            gameState.GameEndKeyPoint(true, id);
                            gameState.GameHandler.HandleWin();
                            gameState.GameHandler.HandleGameEnd();
                            gameState.GameEnded = true;
                        }
                        else if (value == (int)TAG_PLAYSTATE.LOST)
                        {
                            gameState.GameEndKeyPoint(false, id);
                            gameState.GameHandler.HandleLoss();
                            gameState.GameHandler.HandleGameEnd();
                            gameState.GameEnded = true;
                        }
                        else if (value == (int)TAG_PLAYSTATE.TIED)
                        {
                            gameState.GameEndKeyPoint(false, id);
                            gameState.GameHandler.HandleTied();
                            gameState.GameHandler.HandleGameEnd();
                        }
                    }
                }
            }
            else if (tag == GAME_TAG.CURRENT_PLAYER && value == 1)
            {
                var activePlayer = game.Entities[id].IsPlayer ? ActivePlayer.Player : ActivePlayer.Opponent;
                gameState.GameHandler.TurnStart(activePlayer, gameState.GetTurnNumber());
                if (activePlayer == ActivePlayer.Player)
                {
                    gameState.PlayerUsedHeroPower = false;
                }
                else
                {
                    gameState.OpponentUsedHeroPower = false;
                }
            }
            else if (tag == GAME_TAG.LAST_CARD_PLAYED)
            {
                gameState.LastCardPlayed = value;
            }
            else if (tag == GAME_TAG.DEFENDING)
            {
                if (player == "OPPOSING")
                {
                    gameState.GameHandler.HandleDefendingEntity(value == 1 ? game.Entities[id] : null);
                }
            }
            else if (tag == GAME_TAG.ATTACKING)
            {
                if (player == "FRIENDLY")
                {
                    gameState.GameHandler.HandleAttackingEntity(value == 1 ? game.Entities[id] : null);
                }
            }
            else if (tag == GAME_TAG.PROPOSED_DEFENDER)
            {
                game.OpponentSecrets.ProposedDefenderEntityId = value;
            }
            else if (tag == GAME_TAG.PROPOSED_ATTACKER)
            {
                game.OpponentSecrets.ProposedAttackerEntityId = value;
            }
            else if (tag == GAME_TAG.NUM_MINIONS_PLAYED_THIS_TURN && value > 0)
            {
                if (gameState.PlayersTurn())
                {
                    gameState.GameHandler.HandlePlayerMinionPlayed();
                }
            }
            else if (tag == GAME_TAG.PREDAMAGE && value > 0)
            {
                if (gameState.PlayersTurn())
                {
                    gameState.GameHandler.HandleOpponentDamage(game.Entities[id]);
                }
            }
            else if (tag == GAME_TAG.NUM_TURNS_IN_PLAY && value > 0)
            {
                if (!gameState.PlayersTurn())
                {
                    gameState.GameHandler.HandleOpponentTurnStart(game.Entities[id]);
                }
            }
            else if (tag == GAME_TAG.NUM_ATTACKS_THIS_TURN && value > 0)
            {
                if (controller == game.Player.Id)
                {
                    gameState.ProposeKeyPoint(KeyPointType.Attack, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.ProposeKeyPoint(KeyPointType.Attack, id, ActivePlayer.Opponent);
                }
            }
            else if (tag == GAME_TAG.ZONE_POSITION)
            {
                var zone = game.Entities[id].GetTag(GAME_TAG.ZONE);
                if (zone == (int)TAG_ZONE.HAND)
                {
                    if (controller == game.Player.Id)
                    {
                        ReplayMaker.Generate(KeyPointType.HandPos, id, ActivePlayer.Player, game);
                        gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Player, TAG_ZONE.HAND, gameState.GetTurnNumber());
                    }
                    else if (controller == game.Opponent.Id)
                    {
                        ReplayMaker.Generate(KeyPointType.HandPos, id, ActivePlayer.Opponent, game);
                        gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Opponent, TAG_ZONE.HAND, gameState.GetTurnNumber());
                    }
                }
                else if (zone == (int)TAG_ZONE.PLAY)
                {
                    if (controller == game.Player.Id)
                    {
                        ReplayMaker.Generate(KeyPointType.BoardPos, id, ActivePlayer.Player, game);
                        gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Player, TAG_ZONE.PLAY, gameState.GetTurnNumber());
                    }
                    else if (controller == game.Opponent.Id)
                    {
                        ReplayMaker.Generate(KeyPointType.BoardPos, id, ActivePlayer.Opponent, game);
                        gameState.GameHandler.HandleZonePositionUpdate(ActivePlayer.Opponent, TAG_ZONE.PLAY, gameState.GetTurnNumber());
                    }
                }
            }
            else if (tag == GAME_TAG.CARD_TARGET && value > 0)
            {
                if (controller == game.Player.Id)
                {
                    gameState.ProposeKeyPoint(KeyPointType.PlaySpell, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.ProposeKeyPoint(KeyPointType.PlaySpell, id, ActivePlayer.Opponent);
                }
            }
            else if (tag == GAME_TAG.EQUIPPED_WEAPON && value == 0)
            {
                if (controller == game.Player.Id)
                {
                    gameState.ProposeKeyPoint(KeyPointType.WeaponDestroyed, id, ActivePlayer.Player);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.ProposeKeyPoint(KeyPointType.WeaponDestroyed, id, ActivePlayer.Opponent);
                }
            }
            else if (tag == GAME_TAG.EXHAUSTED && value > 0)
            {
                if (game.Entities[id].GetTag(GAME_TAG.CARDTYPE) == (int)TAG_CARDTYPE.HERO_POWER)
                {
                    if (controller == game.Player.Id)
                    {
                        gameState.ProposeKeyPoint(KeyPointType.HeroPower, id, ActivePlayer.Player);
                    }
                    else if (controller == game.Opponent.Id)
                    {
                        gameState.ProposeKeyPoint(KeyPointType.HeroPower, id, ActivePlayer.Opponent);
                    }
                }
            }
            else if (tag == GAME_TAG.CONTROLLER && prevValue > 0)
            {
                if (value == game.Player.Id)
                {
                    if (game.Entities[id].IsInZone(TAG_ZONE.SECRET))
                    {
                        gameState.GameHandler.HandleOpponentStolen(game.Entities[id], cardId, gameState.GetTurnNumber());
                        gameState.ProposeKeyPoint(KeyPointType.SecretStolen, id, ActivePlayer.Player);
                    }
                    else if (game.Entities[id].IsInZone(TAG_ZONE.PLAY))
                    {
                        gameState.GameHandler.HandleOpponentStolen(game.Entities[id], cardId, gameState.GetTurnNumber());
                    }
                }
                else if (value == game.Opponent.Id)
                {
                    if (game.Entities[id].IsInZone(TAG_ZONE.SECRET))
                    {
                        gameState.GameHandler.HandleOpponentStolen(game.Entities[id], cardId, gameState.GetTurnNumber());
                        gameState.ProposeKeyPoint(KeyPointType.SecretStolen, id, ActivePlayer.Player);
                    }
                    else if (game.Entities[id].IsInZone(TAG_ZONE.PLAY))
                    {
                        gameState.GameHandler.HandlePlayerStolen(game.Entities[id], cardId, gameState.GetTurnNumber());
                    }
                }
            }
            else if (tag == GAME_TAG.FATIGUE)
            {
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerFatigue(Convert.ToInt32(rawValue));
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentFatigue(Convert.ToInt32(rawValue));
                }
            }
            if (gameState.WaitForController != null)
            {
                if (!isRecursive)
                {
                    TagChange(gameState, (string)gameState.WaitForController.Tag, (int)gameState.WaitForController.Id, (string)gameState.WaitForController.Value, game, true);
                    gameState.WaitForController = null;
                }
            }
        }
		private void ZoneChangeFromPlay(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
		{
			Entity entity;
			if(!game.Entities.TryGetValue(id, out entity))
				return;
			switch((Zone)value)
			{
				case HAND:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerBackToHand(entity, cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(PlayToHand, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentPlayToHand(entity, cardId, gameState.GetTurnNumber(), id);
						gameState.ProposeKeyPoint(PlayToHand, id, ActivePlayer.Opponent);
					}
					break;
				case DECK:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerPlayToDeck(entity, cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(PlayToDeck, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentPlayToDeck(entity, cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(PlayToDeck, id, ActivePlayer.Opponent);
					}
					break;
				case GRAVEYARD:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerPlayToGraveyard(entity, cardId, gameState.GetTurnNumber());
						if(entity.HasTag(HEALTH))
							gameState.ProposeKeyPoint(Death, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentPlayToGraveyard(entity, cardId, gameState.GetTurnNumber(), game.PlayerEntity?.IsCurrentPlayer ?? false);
						if(entity.HasTag(HEALTH))
							gameState.ProposeKeyPoint(Death, id, ActivePlayer.Opponent);
					}
					break;
				case REMOVEDFROMGAME:
				case SETASIDE:
					if(controller == game.Player.Id)
						gameState.GameHandler.HandlePlayerRemoveFromPlay(entity, gameState.GetTurnNumber());
					else if(controller == game.Opponent.Id)
						gameState.GameHandler.HandleOpponentRemoveFromPlay(entity, gameState.GetTurnNumber());
					break;
				case PLAY:
					break;
				default:
					Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
					break;
			}
		}
        private void ZoneChangeFromDeck(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
        {
            if (!game.Entities.TryGetValue(id, out var entity))
            {
                return;
            }
            switch ((Zone)value)
            {
            case HAND:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerDraw(entity, cardId, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentDraw(entity, gameState.GetTurnNumber());
                }
                break;

            case SETASIDE:
            case REMOVEDFROMGAME:
                if (!game.SetupDone)
                {
                    entity.Info.Created = true;
                    return;
                }
                if (controller == game.Player.Id)
                {
                    if (gameState.JoustReveals > 0)
                    {
                        gameState.JoustReveals--;
                        break;
                    }
                    gameState.GameHandler.HandlePlayerRemoveFromDeck(entity, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    if (gameState.JoustReveals > 0)
                    {
                        gameState.JoustReveals--;
                        break;
                    }
                    gameState.GameHandler.HandleOpponentRemoveFromDeck(entity, gameState.GetTurnNumber());
                }
                break;

            case GRAVEYARD:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerDeckDiscard(entity, cardId, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentDeckDiscard(entity, cardId, gameState.GetTurnNumber());
                }
                break;

            case PLAY:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerDeckToPlay(entity, cardId, gameState.GetTurnNumber());
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentDeckToPlay(entity, cardId, gameState.GetTurnNumber());
                }
                break;

            case Zone.SECRET:
                if (controller == game.Player.Id)
                {
                    gameState.GameHandler.HandlePlayerSecretPlayed(entity, cardId, gameState.GetTurnNumber(), (Zone)prevValue);
                }
                else if (controller == game.Opponent.Id)
                {
                    gameState.GameHandler.HandleOpponentSecretPlayed(entity, cardId, -1, gameState.GetTurnNumber(), (Zone)prevValue, id);
                }
                break;

            default:
                Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
                break;
            }
        }
		private void ZoneChangeFromDeck(IHsGameState gameState, int id, IGame game, int value, int prevValue, int controller, string cardId)
		{
			Entity entity;
			if(!game.Entities.TryGetValue(id, out entity))
				return;
			switch((Zone)value)
			{
				case HAND:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerDraw(entity, cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(Draw, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentDraw(entity, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(Draw, id, ActivePlayer.Opponent);
					}
					break;
				case SETASIDE:
				case REMOVEDFROMGAME:
					if(!gameState.SetupDone)
					{
						entity.Info.Created = true;
						return;
					}
					if(controller == game.Player.Id)
					{
						if(gameState.JoustReveals > 0)
						{
							gameState.JoustReveals--;
							break;
						}
						gameState.GameHandler.HandlePlayerRemoveFromDeck(entity, gameState.GetTurnNumber());
					}
					else if(controller == game.Opponent.Id)
					{
						if(gameState.JoustReveals > 0)
						{
							gameState.JoustReveals--;
							break;
						}
						gameState.GameHandler.HandleOpponentRemoveFromDeck(entity, gameState.GetTurnNumber());
					}
					break;
				case GRAVEYARD:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerDeckDiscard(entity, cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentDeckDiscard(entity, cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Opponent);
					}
					break;
				case PLAY:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerDeckToPlay(entity, cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentDeckToPlay(entity, cardId, gameState.GetTurnNumber());
						gameState.ProposeKeyPoint(DeckDiscard, id, ActivePlayer.Opponent);
					}
					break;
				case Zone.SECRET:
					if(controller == game.Player.Id)
					{
						gameState.GameHandler.HandlePlayerSecretPlayed(entity, cardId, gameState.GetTurnNumber(), (Zone)prevValue);
						gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
					}
					else if(controller == game.Opponent.Id)
					{
						gameState.GameHandler.HandleOpponentSecretPlayed(entity, cardId, -1, gameState.GetTurnNumber(), (Zone)prevValue, id);
						gameState.ProposeKeyPoint(SecretPlayed, id, ActivePlayer.Player);
					}
					break;
				default:
					Log.Warn($"unhandled zone change (id={id}): {prevValue} -> {value}");
					break;
			}
		}
		private static void ControllerChange(IHsGameState gameState, int id, IGame game, int prevValue, int value, string cardId)
		{
			if(prevValue <= 0)
				return;
			if(value == game.Player.Id)
			{
				if(game.Entities[id].IsInZone(TAG_ZONE.SECRET))
				{
					gameState.GameHandler.HandleOpponentStolen(game.Entities[id], cardId, gameState.GetTurnNumber());
					gameState.ProposeKeyPoint(SecretStolen, id, ActivePlayer.Player);
				}
				else if(game.Entities[id].IsInZone(PLAY))
					gameState.GameHandler.HandleOpponentStolen(game.Entities[id], cardId, gameState.GetTurnNumber());
			}
			else if(value == game.Opponent.Id)
			{
				if(game.Entities[id].IsInZone(TAG_ZONE.SECRET))
				{
					gameState.GameHandler.HandleOpponentStolen(game.Entities[id], cardId, gameState.GetTurnNumber());
					gameState.ProposeKeyPoint(SecretStolen, id, ActivePlayer.Player);
				}
				else if(game.Entities[id].IsInZone(PLAY))
					gameState.GameHandler.HandlePlayerStolen(game.Entities[id], cardId, gameState.GetTurnNumber());
			}
		}