public static void ShuffleTMs(Random random, ItemShufflerSettings settings, ExtractedGame extractedGame)
        {
            if (settings.RandomizeTMs)
            {
                Logger.Log("=============================== TMs ===============================\n\n");
                var tms = extractedGame.TMs;
                // use set to avoid dupes
                var newTMSet   = new HashSet <ushort>();
                var validMoves = extractedGame.ValidMoves;

                if (settings.TMForceGoodDamagingMove)
                {
                    // determine what percent of TMs should be good
                    var count = (int)Math.Round(settings.TMGoodDamagingMovePercent * tms.Length);
                    // keep picking until it's not a dupe
                    // this could probably be done smarterly
                    var goodDamagingMoves = extractedGame.GoodDamagingMoves;
                    while (newTMSet.Count < count)
                    {
                        var newMove = goodDamagingMoves[random.Next(0, goodDamagingMoves.Length)];
                        newTMSet.Add((ushort)newMove.MoveIndex);
                    }
                }

                // keep picking while we haven't picked enough TMs
                while (newTMSet.Count < tms.Length)
                {
                    newTMSet.Add((ushort)validMoves[random.Next(0, validMoves.Length)].MoveIndex);
                }

                // set them to the actual TM item
                for (int i = 0; i < tms.Length; i++)
                {
                    var tm = tms[i];
                    tm.Move = newTMSet.ElementAt(i);
                    Logger.Log($"TM{tm.TMIndex}: {extractedGame.MoveList[tm.Move].Name}\n");
                }
                Logger.Log($"\n\n");
            }
        }
        public static void ShufflePokeSpots(Random random, PokeSpotShufflerSettings settings, PokeSpotPokemon[] pokeSpotPokemon, ExtractedGame extractedGame)
        {
            var validPokemon = extractedGame.ValidPokemon;
            var validItems   = settings.BanBadHeldItems ? extractedGame.GoodItems : extractedGame.NonKeyItems;

            Logger.Log("=============================== Poke Spots ===============================\n\n");
            foreach (var pokeSpotPoke in pokeSpotPokemon)
            {
                Logger.Log($"PokeSpot Type: {pokeSpotPoke.PokeSpot.PokeSpotType}\n\n");
                var pokemon = extractedGame.PokemonList[pokeSpotPoke.Pokemon];
                if (pokeSpotPoke.Pokemon == RandomizerConstants.BonslyIndex)
                {
                    // I don't know if this'll work or not...
                    pokeSpotPoke.EncounterPercentage = settings.EasyBonsly ? 100 : pokeSpotPoke.EncounterPercentage;
                    Logger.Log("Bonsly damnit stop running away!\n");
                    continue;
                }

                if (settings.RandomizePokeSpotPokemon)
                {
                    var newPokemon = validPokemon[random.Next(0, validPokemon.Length)];
                    Logger.Log($"Pokemon {pokemon.Name} -> {newPokemon.Name}\n");
                    pokeSpotPoke.Pokemon = (ushort)newPokemon.Index;
                    pokemon = newPokemon;
                }

                if (settings.RandomizeHeldItems)
                {
                    var newItem = validItems[random.Next(0, validItems.Length)];
                    Logger.Log($"Pokemon {pokemon.HeldItem} -> {newItem.Name}\n");
                    pokemon.HeldItem = (ushort)newItem.Index;
                }

                if (settings.BoostPokeSpotLevel)
                {
                    var maxLevel         = pokeSpotPoke.MaxLevel;
                    var minLevel         = pokeSpotPoke.MinLevel;
                    var maxLevelIncrease = (byte)Math.Clamp(maxLevel + maxLevel * settings.BoostPokeSpotLevelPercent, 1, 100);
                    var minLevelIncrease = (byte)Math.Clamp(minLevel + minLevel * settings.BoostPokeSpotLevelPercent, 1, 100);
                    Logger.Log($"Max Level {maxLevel} -> {maxLevelIncrease}\n");
                    Logger.Log($"Min Level {minLevel} -> {minLevelIncrease}\n");
                    pokeSpotPoke.MaxLevel = maxLevelIncrease;
                    pokeSpotPoke.MinLevel = minLevelIncrease;
                }

                if (settings.SetMinimumCatchRate)
                {
                    var catchRate         = Math.Max(pokemon.CatchRate, settings.MinimumCatchRate);
                    var catchRateIncrease = (byte)Math.Clamp(catchRate, 0, byte.MaxValue);
                    Logger.Log($"Catch Rate {pokemon.CatchRate} -> {catchRateIncrease}\n");
                    pokemon.CatchRate = catchRateIncrease;
                }
            }
        }
        public static ushort[] GetNewMoveset(Random random, RandomMoveSetOptions options, ushort pokemon, ushort level, ExtractedGame extractedGame)
        {
            var poke       = extractedGame.PokemonList[pokemon];
            var moveSet    = new HashSet <ushort>();
            var moveFilter = options.BanShadowMoves ? extractedGame.ValidMoves.Where(m => !m.IsShadowMove) : extractedGame.ValidMoves;

            if (options.BanEarlyDragonRage)
            {
                moveFilter = moveFilter.Where(m => !(m.MoveIndex == RandomizerConstants.DragonRageIndex && level < RandomizerConstants.BanDragonRageUnderLevel));
            }

            var typeFilter = moveFilter;

            if (options.PreferType)
            {
                // allow 20% chance for move to not be same type
                typeFilter = typeFilter.Where(m => m.Type == poke.Type1 || m.Type == poke.Type2 || random.Next(0, 10) >= 8).ToArray();
                if (!moveFilter.Any())
                {
                    typeFilter = moveFilter;
                }
            }

            var potentialMoves = typeFilter.ToArray();

            if (options.UseLevelUpMoves || !options.RandomizeMovesets)
            {
                // not randomizing moves? pick level up moves then
                var levelUpMoves = extractedGame.PokemonList[pokemon].CurrentLevelMoves(level++);

                // this *could* happen so increase the level until there's at least one move for them
                while (!levelUpMoves.Any() && level <= 100)
                {
                    levelUpMoves = extractedGame.PokemonList[pokemon].CurrentLevelMoves(level++);
                }

                // still nothing, add a random move
                if (!levelUpMoves.Any())
                {
                    var newMove = potentialMoves[random.Next(0, potentialMoves.Length)];
                    moveSet.Add((ushort)newMove.MoveIndex);
                }
                else
                {
                    foreach (var levelUpMove in levelUpMoves)
                    {
                        moveSet.Add(levelUpMove.Move);
                    }
                }
            }
            else if (options.MinimumGoodMoves > 0)
            {
                var goodMoves = potentialMoves.Where(m => m.BasePower >= Configuration.GoodDamagingMovePower).ToArray();
                while (moveSet.Count < options.MinimumGoodMoves)
                {
                    var newMove = goodMoves[random.Next(0, goodMoves.Length)];
                    moveSet.Add((ushort)newMove.MoveIndex);
                }
            }

            if (options.RandomizeMovesets || options.ForceFourMoves)
            {
                while (moveSet.Count < Constants.NumberOfPokemonMoves)
                {
                    var newMove = potentialMoves[random.Next(0, potentialMoves.Length)];
                    moveSet.Add((ushort)newMove.MoveIndex);
                }
            }

            return(moveSet.ToArray());
        }
        public static void RandomizeMoves(Random random, MoveShufflerSettings settings, ExtractedGame extractedGame)
        {
            Logger.Log("=============================== Moves ===============================\n\n");
            foreach (var move in extractedGame.ValidMoves)
            {
                Logger.Log($"{move.Name}: \n");
                if (settings.RandomMovePower && move.BasePower > 0)
                {
                    // sample move power with the power of math!!
                    // basically moves will average at 80 power but can be from 10 to 150
                    move.BasePower = (byte)random.Sample(80, 70);
                }
                Logger.Log($"Power: {move.BasePower}\n");

                if (settings.RandomMoveAcc && move.Accuracy > 0)
                {
                    // randomize accuracy, repick if the moves is OHKO and 100% accurate cause that's a yikes
                    byte acc;
                    do
                    {
                        acc = (byte)Math.Min(100, random.Sample(80, 30));
                    } while (acc == 100 && move.EffectType == MoveEffectTypes.OHKO);
                    move.Accuracy = acc;
                }
                Logger.Log($"Accuracy: {move.Accuracy}\n");

                if (settings.RandomMovePP)
                {
                    // pick from 1, 8, multiply by 5 to get the 5 - 40 range for PP
                    move.PP = (byte)Math.Max(5, Math.Round(random.Sample(4, 4)) * 5);
                }
                Logger.Log($"PP: {move.PP}\n");

                if (settings.RandomMoveTypes && move.Type != PokemonTypes.None)
                {
                    var          typesCount = Enum.GetValues <PokemonTypes>();
                    PokemonTypes newType;
                    // pick new move type but not the none type
                    do
                    {
                        newType = typesCount[random.Next(0, typesCount.Length)];
                    }while (newType == PokemonTypes.None);
                    move.Type = newType;
                }
                Logger.Log($"Type: {move.Type}\n");

                // I don't know if this will work for colo, probably not
                if (settings.RandomMoveCategory && move.Category != MoveCategories.None)
                {
                    var newCategory = (MoveCategories)random.Next(1, 3);
                    move.Category = newCategory;
                }
                Logger.Log($"Category: {move.Category}\n\n");
            }
        }
Beispiel #5
0
 public static void RandomizeColoStatics(Random random, StaticPokemonShufflerSettings settings, IGiftPokemon[] starters, ExtractedGame extractedGame)
 {
 }
Beispiel #6
0
        public static void RandomizeXDStatics(Random random, StaticPokemonShufflerSettings settings, XDStarterPokemon starter, ISO iso, ExtractedGame extractedGame)
        {
            int       index = 0;
            Evolution secondStage;
            bool      condition = false;

            Logger.Log("=============================== Statics ===============================\n\n");
            // pick starters
            // basically set a condition based on the setting, keep looping till you meet it
            switch (settings.Starter)
            {
            case StarterRandomSetting.Custom:
            {
                index = extractedGame.PokemonList.FirstOrDefault(p => p.Name.ToLower() == settings.Starter1.ToLower())?.Index ?? starter.Pokemon;
            }
            break;

            case StarterRandomSetting.Random:
            {
                while (index == 0 || RandomizerConstants.SpecialPokemon.Contains(index))
                {
                    index = random.Next(1, extractedGame.PokemonList.Length);
                }
            }
            break;

            case StarterRandomSetting.RandomThreeStage:
                while (!condition)
                {
                    var newStarter = extractedGame.PokemonList[random.Next(0, extractedGame.ValidPokemon.Length)];
                    index       = newStarter.Index;
                    secondStage = extractedGame.PokemonList[index].Evolutions[0];
                    condition   = !PokemonTraitShuffler.CheckForSplitOrEndEvolution(newStarter, out int _) &&
                                  !PokemonTraitShuffler.CheckForSplitOrEndEvolution(extractedGame.PokemonList[secondStage.EvolvesInto], out int _)
                                  // check if any pokemon evolve into this one, less likely for three stages but probably good to check anyway
                                  && !extractedGame.ValidPokemon.Any(p =>
                    {
                        for (int i = 0; i < p.Evolutions.Length; i++)
                        {
                            if (p.Evolutions[i].EvolvesInto == index)
                            {
                                return(true);
                            }
                        }
                        return(false);
                    });
                }
                break;

            case StarterRandomSetting.RandomTwoStage:
                while (!condition)
                {
                    var newStarter = extractedGame.PokemonList[random.Next(0, extractedGame.ValidPokemon.Length)];
                    index       = newStarter.Index;
                    secondStage = extractedGame.PokemonList[index].Evolutions[0];
                    condition   = !PokemonTraitShuffler.CheckForSplitOrEndEvolution(newStarter, out int _) &&
                                  PokemonTraitShuffler.CheckForSplitOrEndEvolution(extractedGame.PokemonList[secondStage.EvolvesInto], out int count) &&
                                  count == 0
                                  // check if any pokemon evolve into this one
                                  && !extractedGame.ValidPokemon.Any(p =>
                    {
                        for (int i = 0; i < p.Evolutions.Length; i++)
                        {
                            if (p.Evolutions[i].EvolvesInto == index)
                            {
                                return(true);
                            }
                        }
                        return(false);
                    });
                }
                break;

            case StarterRandomSetting.RandomSingleStage:
                while (!condition)
                {
                    var newStarter = extractedGame.PokemonList[random.Next(0, extractedGame.ValidPokemon.Length)];
                    index     = newStarter.Index;
                    condition = PokemonTraitShuffler.CheckForSplitOrEndEvolution(newStarter, out int count) &&
                                count == 0
                                // check if any pokemon evolve into this one
                                && !extractedGame.ValidPokemon.Any(p =>
                    {
                        for (int i = 0; i < p.Evolutions.Length; i++)
                        {
                            if (p.Evolutions[i].EvolvesInto == index)
                            {
                                return(true);
                            }
                        }
                        return(false);
                    });
                }
                break;

            default:
            case StarterRandomSetting.Unchanged:
                index = starter.Pokemon;
                break;
            }
            starter.Pokemon = (ushort)index;
            Logger.Log($"Your new starter is {extractedGame.PokemonList[index].Name}\n");

            // instance pokemon have separate movesets than the pool
            // i.e. if you don't update the moveset than your starter will have Eevee's move set
            ushort[] moves;
            if (settings.MoveSetOptions.RandomizeMovesets || settings.Starter != StarterRandomSetting.Unchanged)
            {
                Logger.Log($"It knows:\n");
                moves = MoveShuffler.GetNewMoveset(random, settings.MoveSetOptions, starter.Pokemon, starter.Level, extractedGame);
                for (int i = 0; i < moves.Length; i++)
                {
                    var move = moves[i];
                    Logger.Log($"{extractedGame.MoveList[move].Name}\n");
                    starter.SetMove(i, move);
                }
            }

            List <Pokemon> newRequestedPokemon = new List <Pokemon>();
            List <Pokemon> newGivenPokemon     = new List <Pokemon>();
            var            pokemon             = extractedGame.ValidPokemon;

            // set given
            switch (settings.Trade)
            {
            default:
            case TradeRandomSetting.Unchanged:
                Logger.Log("Unchanged\n\n");
                return;

            case TradeRandomSetting.Given:
            case TradeRandomSetting.Both:
                for (int i = 0; i < extractedGame.GiftPokemonList.Length; i++)
                {
                    var giftPoke = extractedGame.GiftPokemonList[i];
                    var newPoke  = pokemon[random.Next(0, pokemon.Length)];
                    giftPoke.Pokemon = (ushort)newPoke.Index;

                    if (giftPoke.GiftType.Contains("Duking"))
                    {
                        newGivenPokemon.Add(newPoke);
                    }

                    ushort[] newMoveSet = MoveShuffler.GetNewMoveset(random, settings.MoveSetOptions, giftPoke.Pokemon, extractedGame.GiftPokemonList[i].Level, extractedGame);
                    for (int j = 0; j < newMoveSet.Length; j++)
                    {
                        extractedGame.GiftPokemonList[i].SetMove(j, newMoveSet[j]);
                    }
                }
                break;

            case TradeRandomSetting.Requested:
                for (int i = 0; i < 3; i++)
                {
                    newGivenPokemon.Add(extractedGame.PokemonList[new XDTradePokemon((byte)(i + 2), iso).Pokemon]);
                }
                break;
            }

            // set requested
            for (int i = 0; i < 3; i++)
            {
                var pokeSpot         = new PokeSpotPokemon(2, (PokeSpotType)i, iso);
                var newRequestedPoke = settings.UsePokeSpotPokemonInTrade || settings.Trade == TradeRandomSetting.Requested || settings.Trade == TradeRandomSetting.Both
                    ? extractedGame.PokemonList[pokeSpot.Pokemon]
                    : pokemon[random.Next(0, pokemon.Length)];
                newRequestedPokemon.Add(newRequestedPoke);
            }

            Logger.Log($"Requested Pokemon: {string.Join(", ", newRequestedPokemon.Select(p => p.Name))}\n");
            Logger.Log($"Given Pokemon: {string.Join(", ", newGivenPokemon.Select(p => p.Name))}\n");

            XDTradePokemon.UpdateTrades(iso, newRequestedPokemon.ToArray(), newGivenPokemon.ToArray());
        }
        public static void ShuffleOverworldItems(Random random, ItemShufflerSettings settings, ExtractedGame extractedGame)
        {
            // there are a lot of battle cds, so only add them to one location and then
            // block that same cd from being put in another location (only if they haven't banned cds entirely)
            var battleCDsUsed  = new List <int>();
            var potentialItems = settings.BanBadItems ? extractedGame.GoodItems : extractedGame.NonKeyItems;

            if (settings.BanBattleCDs)
            {
                potentialItems = potentialItems.Where(i => !RandomizerConstants.BattleCDList.Contains(i.Index)).ToArray();
            }

            Logger.Log("=============================== Overworld Items ===============================\n\n");
            foreach (var item in extractedGame.OverworldItemList)
            {
                // i'm *assuming* the devs didn't place any invalid items on the overworld
                if (item.Item > extractedGame.ItemList.Length || extractedGame.ItemList[item.Item].BagSlot == BagSlots.KeyItems)
                {
                    continue;
                }

                if (settings.RandomizeItems)
                {
                    ushort newItem = 0;
                    while (newItem == 0 || battleCDsUsed.Contains(newItem))
                    {
                        newItem = (ushort)potentialItems[random.Next(0, potentialItems.Length)].Index;
                    }

                    if (RandomizerConstants.BattleCDList.Contains(newItem))
                    {
                        battleCDsUsed.Add(newItem);
                    }

                    Logger.Log($"{extractedGame.ItemList[item.Item].Name} -> ");
                    item.Item = newItem;
                    Logger.Log($"{extractedGame.ItemList[newItem].Name}\n\n");
                }

                if (settings.RandomizeItemQuantity)
                {
                    Logger.Log($"Quantity: {item.Quantity} -> ");
                    item.Quantity = (byte)random.Next(1, 6);
                    Logger.Log($"{item.Quantity}\n\n");
                }
            }
        }
        public static void ShuffleTutorMoves(Random random, ItemShufflerSettings settings, TutorMove[] tutorMoves, ExtractedGame extractedGame)
        {
            if (settings.RandomizeTutorMoves)
            {
                Logger.Log("=============================== Tutor Moves ===============================\n\n");
                var newTutorMoveSet = new HashSet <ushort>();
                var validMoves      = extractedGame.ValidMoves;

                if (settings.TutorForceGoodDamagingMove)
                {
                    // determine what percent of TMs should be good
                    var count = (int)Math.Round(settings.TutorGoodDamagingMovePercent * tutorMoves.Length);
                    // filter the move list by moves that are deemed good
                    var goodDamagingMoves = extractedGame.GoodDamagingMoves;
                    while (newTutorMoveSet.Count < count)
                    {
                        var newMove = goodDamagingMoves[random.Next(0, goodDamagingMoves.Length)];
                        newTutorMoveSet.Add((ushort)newMove.MoveIndex);
                    }
                }

                // keep picking while we haven't picked enough TMs or we picked a dupe
                while (newTutorMoveSet.Count < tutorMoves.Length)
                {
                    newTutorMoveSet.Add((ushort)validMoves[random.Next(0, validMoves.Length)].MoveIndex);
                }

                // set them to the actual TM item
                for (int i = 0; i < tutorMoves.Length; i++)
                {
                    tutorMoves[i].Move = newTutorMoveSet.ElementAt(i);
                    Logger.Log($"Tutor Move {i + 1}: {extractedGame.MoveList[tutorMoves[i].Move].Name}\n");
                }
                Logger.Log($"\n\n");
            }
        }
        public static void UpdatePokemarts(Random random, ItemShufflerSettings settings, ExtractedGame extractedGame)
        {
            if (settings.RandomizeMarts)
            {
                Logger.Log("=============================== Mart Items ===============================\n\n");
                var potentialItems = settings.BanBadItems ? extractedGame.NonKeyItems : extractedGame.GoodItems;
                potentialItems = potentialItems.Where(i => !RandomizerConstants.BattleCDList.Contains(i.Index)).ToArray();

                foreach (var mart in extractedGame.Pokemarts)
                {
                    Logger.Log($"Mart {mart.Index}:\n");
                    for (int i = 0; i < mart.Items.Count; i++)
                    {
                        var item = mart.Items[i];
                        if (item < extractedGame.ItemList.Length)
                        {
                            Logger.Log($"{extractedGame.ItemList[item].Name} -> ");
                        }
                        else
                        {
                            Logger.Log($"{item} -> ");
                        }

                        mart.Items[i] = (ushort)potentialItems[random.Next(0, potentialItems.Length)].Index;
                        Logger.Log($"{extractedGame.ItemList[mart.Items[i]].Name}\n");
                    }
                    mart.SaveItems();
                    Logger.Log($"\n");
                }
                Logger.Log($"\n");
            }

            if (settings.MartsSellEvoStones)
            {
                Logger.Log($"Adding Evolution Stones to Agate Village Mart.\n\n");
                foreach (var agateMartIndex in RandomizerConstants.AgateVillageMartIndices)
                {
                    var agateMart = extractedGame.Pokemarts[agateMartIndex];
                    agateMart.Items.AddRange(RandomizerConstants.EvoStoneItemList);
                    for (int i = agateMartIndex + 1; i < extractedGame.Pokemarts.Length; i++)
                    {
                        extractedGame.Pokemarts[i].FirstItemIndex += (ushort)(RandomizerConstants.EvoStoneItemList.Length);
                    }
                    agateMart.SaveItems();
                }
            }
        }
        private static void ChangeCompatibility(Random random, MoveCompatibility moveCompatibility, Pokemon pokemon, ExtractedGame extractedGame, bool tms)
        {
            switch (moveCompatibility)
            {
            case MoveCompatibility.Full:
            {
                if (tms)
                {
                    Logger.Log($"TM Compatibility: Full\n");
                    for (int i = 0; i < pokemon.LearnableTMs.Length; i++)
                    {
                        pokemon.SetLearnableTMS(i, true);
                    }
                }
                else
                {
                    Logger.Log($"Tutor Move Compatibility: Full\n");
                    for (int i = 0; i < pokemon.TutorMoves.Length; i++)
                    {
                        pokemon.SetTutorMoves(i, true);
                    }
                }
            }
            break;

            case MoveCompatibility.Random:
            {
                if (tms)
                {
                    Logger.Log($"TM Compatibility:\n");
                    for (int i = 0; i < pokemon.LearnableTMs.Length; i++)
                    {
                        var compatible = random.Next(0, 2) == 0;
                        pokemon.SetLearnableTMS(i, compatible);
                        Logger.Log($"TM{extractedGame.TMs[i].TMIndex} - {compatible}\n");
                    }
                }
                else
                {
                    Logger.Log($"Tutor Move Compatibility:\n");
                    for (int i = 0; i < pokemon.TutorMoves.Length; i++)
                    {
                        var compatible = random.Next(0, 2) == 0;
                        pokemon.SetTutorMoves(i, compatible);
                        Logger.Log($"{i + 1} - {compatible}\n");
                    }
                }
                Logger.Log($"\n");
            }
            break;

            case MoveCompatibility.RandomPreferType:
            {
                if (tms)
                {
                    Logger.Log($"TM Compatibility:\n");
                    var tmMoves = extractedGame.TMs.Select(t => extractedGame.MoveList[t.Move]).ToArray();
                    for (int i = 0; i < pokemon.LearnableTMs.Length; i++)
                    {
                        var isCompatible = tmMoves[i].Type == pokemon.Type1 || tmMoves[i].Type == pokemon.Type2 || random.Next(0, 10) >= 8;
                        pokemon.SetLearnableTMS(i, isCompatible);
                        Logger.Log($"TM{extractedGame.TMs[i].TMIndex} - {isCompatible}\n");
                    }
                }
                else
                {
                    Logger.Log($"Tutor Move Compatibility:\n");
                    var tutorMoves = extractedGame.TutorMoves.Select(t => extractedGame.MoveList[t.Move]).ToArray();
                    for (int i = 0; i < pokemon.TutorMoves.Length; i++)
                    {
                        var isCompatible = tutorMoves[i].Type == pokemon.Type1 || tutorMoves[i].Type == pokemon.Type2 || random.Next(0, 10) >= 8;
                        pokemon.SetTutorMoves(i, isCompatible);
                        Logger.Log($"{i + 1} - {isCompatible}\n");
                    }
                }
                Logger.Log($"\n");
            }
            break;

            default:
            case MoveCompatibility.Unchanged:
                break;
            }
        }
        public static void RandomizePokemonTraits(Random random, PokemonTraitShufflerSettings settings, ExtractedGame extractedGame)
        {
            // store pokemon we've randomized already in a list, for follows evolution
            var pokeBaseStatsRandomized = new List <string>();
            var pokeAbilitiesRandomized = new List <string>();
            var pokeTypesRandomized     = new List <string>();
            var pokeEvosRandomized      = new List <int>();
            var easyEvolutions          = new List <string>();

            Logger.Log("=============================== Pokemon ===============================\n\n");

            // set up filtered lists here to avoid recalculating it every loop
            IEnumerable <Move> movefilter = extractedGame.ValidMoves;

            if (settings.MoveSetOptions.BanShadowMoves)
            {
                movefilter = movefilter.Where(m => !m.IsShadowMove);
            }

            IEnumerable <Ability> abilitiesFilter = extractedGame.Abilities;

            if (!settings.AllowWonderGuard)
            {
                abilitiesFilter = abilitiesFilter.Where(a => a.Index != RandomizerConstants.WonderGuardIndex);
            }

            if (settings.BanNegativeAbilities)
            {
                abilitiesFilter = abilitiesFilter.Where(a => !RandomizerConstants.BadAbilityList.Contains(a.Index));
            }


            // do this first for "follow evolution" checks
            if (settings.RandomizeEvolutions)
            {
                Logger.Log($"Setting New Evolutions\n\n");
                foreach (var poke in extractedGame.PokemonList)
                {
                    // prevent loops and multiple pokemon evolving into the same pokemon
                    pokeEvosRandomized.Add(poke.Index);
                    var pokeFilter = extractedGame.PokemonList.Where(p => !pokeEvosRandomized.Contains(p.Index));

                    if (settings.EvolutionHasSameType)
                    {
                        pokeFilter = pokeFilter.Where(p => p.Type1 == poke.Type1 || p.Type2 == poke.Type2 || p.Type1 == poke.Type2);
                    }

                    for (int i = 0; i < poke.Evolutions.Length; i++)
                    {
                        var evolution = poke.Evolutions[i];
                        if (evolution.EvolutionMethod == EvolutionMethods.None)
                        {
                            continue;
                        }

                        if (settings.EvolutionHasSimilarStrength)
                        {
                            var count            = 1;
                            var similarStrengths = pokeFilter.Where(p => p.BST >= poke.BST - BSTRange && p.BST <= poke.BST + BSTRange);
                            while (!similarStrengths.Any() && count < 3)
                            {
                                // anybody? hello?
                                count++;
                                similarStrengths = pokeFilter.Where(p => p.BST >= poke.BST - (count * BSTRange) && p.BST <= poke.BST + (count * BSTRange));
                            }
                            pokeFilter = similarStrengths;
                        }

                        var potentialPokes = pokeFilter.ToArray();
                        if (potentialPokes.Length == 0)
                        {
                            // null it out
                            Logger.Log($"End of the line for {poke.Name}.\n");
                            poke.SetEvolution(i, 0, 0, 0);
                        }
                        else
                        {
                            var newPoke = potentialPokes[random.Next(0, potentialPokes.Length)];
                            // same evolution, just evolves into something else
                            Logger.Log($"{poke.Name} evolves into {newPoke.Name} instead of {extractedGame.PokemonList[evolution.EvolvesInto].Name}.\n");
                            poke.SetEvolution(i, (byte)evolution.EvolutionMethod, evolution.EvolutionCondition, (ushort)newPoke.Index);
                            pokeEvosRandomized.Add(newPoke.Index);
                        }
                        Logger.Log($"\n");
                    }
                }
            }

            foreach (var poke in extractedGame.PokemonList)
            {
                if (RandomizerConstants.SpecialPokemon.Contains(poke.Index))
                {
                    continue;
                }

                Logger.Log($"{poke.Name}'s Traits:\n\n");

                ChangeCompatibility(random, settings.TMCompatibility, poke, extractedGame, true);
                if (extractedGame.TutorMoves.Length > 0)
                {
                    ChangeCompatibility(random, settings.TutorCompatibility, poke, extractedGame, false);
                }

                // todo: use enum for bst option
                // and follow evolution
                if (settings.RandomizeBaseStats > 0) // || (settings.BaseStatsFollowEvolution && !pokeBaseStatsRandomized.Contains(poke.Name)))
                {
                    IList <byte> newBsts;
                    if (settings.RandomizeBaseStats == 1)
                    {
                        // shuffle
                        newBsts = new List <byte>
                        {
                            poke.HP,
                            poke.Attack,
                            poke.Defense,
                            poke.SpecialAttack,
                            poke.SpecialDefense,
                            poke.Speed
                        };

                        // everyone do the the fisher-yates shuffle
                        for (int i = newBsts.Count; i > 0; i--)
                        {
                            var j = random.Next(0, i + 1);

                            // apparently xor swapping is slower than using a temp variable
                            // take that john mcaffee
                            var temp = newBsts[j];
                            newBsts[j] = newBsts[i];
                            newBsts[i] = temp;
                        }
                    }
                    else
                    {
                        // random within total
                        var pokeBst    = poke.BST;
                        var randomBsts = new byte[6];
                        newBsts = new byte[6];
                        random.NextBytes(randomBsts);

                        var randomSum = randomBsts.Sum(b => b);
                        for (int i = 0; i < newBsts.Count; i++)
                        {
                            newBsts[i] = (byte)(((float)randomBsts[i] / randomSum) * pokeBst);
                        }
                    }

                    poke.HP             = newBsts[0];
                    poke.Attack         = newBsts[1];
                    poke.Defense        = newBsts[2];
                    poke.SpecialAttack  = newBsts[3];
                    poke.SpecialDefense = newBsts[4];
                    poke.Speed          = newBsts[5];

                    Logger.Log($"Setting BSTs: H {newBsts[0]}/A {newBsts[1]}/D {newBsts[2]}/SpA {newBsts[3]}/SpD {newBsts[4]}/S {newBsts[5]}.\n");

                    if (settings.BaseStatsFollowEvolution)
                    {
                        pokeBaseStatsRandomized.Add(poke.Name);
                    }
                }

                if (settings.UpdateBaseStats)
                {
                    // todo
                    // need some way of loading them without doing something... regrettable
                }

                if (settings.StandardizeEXPCurves)
                {
                    poke.LevelUpRate = RandomizerConstants.Legendaries.Contains(poke.Index)
                        ? ExpRate.Slow
                        : ExpRate.Fast;

                    Logger.Log($"Setting EXP rate to {poke.LevelUpRate}.\n");
                }

                if (settings.RandomizeAbilities && !pokeAbilitiesRandomized.Contains(poke.Name))
                {
                    var abilities = abilitiesFilter.ToArray();
                    RandomizeAbility(random, abilities, poke);

                    if (settings.AbilitiesFollowEvolution)
                    {
                        var     endOrSplitEvolution = false;
                        Pokemon currentPoke         = poke;
                        pokeAbilitiesRandomized.Add(currentPoke.Name);
                        while (!endOrSplitEvolution)
                        {
                            endOrSplitEvolution = CheckForSplitOrEndEvolution(currentPoke, out var _);

                            if (!endOrSplitEvolution)
                            {
                                var evoPoke = extractedGame.PokemonList[currentPoke.Evolutions[0].EvolvesInto];
                                evoPoke.Ability1 = poke.Ability1;
                                evoPoke.Ability2 = poke.Ability2;

                                Logger.Log($"Setting {evoPoke.Name}'s Ability to match {poke.Name}.\n");

                                pokeAbilitiesRandomized.Add(evoPoke.Name);
                                currentPoke = evoPoke;
                            }
                        }
                    }
                }

                if (settings.RandomizeTypes && !pokeTypesRandomized.Contains(poke.Name))
                {
                    RandomizeTypes(random, poke);

                    if (settings.TypesFollowEvolution)
                    {
                        var     endOrSplitEvolution = false;
                        Pokemon currentPoke         = poke;
                        pokeTypesRandomized.Add(currentPoke.Name);
                        while (!endOrSplitEvolution)
                        {
                            endOrSplitEvolution = CheckForSplitOrEndEvolution(currentPoke, out var _);

                            if (!endOrSplitEvolution)
                            {
                                var evoPoke = extractedGame.PokemonList[currentPoke.Evolutions[0].EvolvesInto];
                                evoPoke.Type1 = currentPoke.Type1;
                                evoPoke.Type2 = currentPoke.Type2;

                                Logger.Log($"Setting {evoPoke.Name}'s Type to match {poke.Name}.\n");

                                pokeTypesRandomized.Add(evoPoke.Name);
                                currentPoke = evoPoke;
                            }
                        }
                    }
                }

                if (settings.FixImpossibleEvolutions || settings.EasyEvolutions)
                {
                    for (int i = 0; i < poke.Evolutions.Length; i++)
                    {
                        if (settings.FixImpossibleEvolutions)
                        {
                            var method = poke.Evolutions[i].EvolutionMethod;
                            if (method == EvolutionMethods.TradeWithItem || method == EvolutionMethods.Trade || method == EvolutionMethods.HappinessDay || method == EvolutionMethods.HappinessNight)
                            {
                                poke.SetEvolution(i, (byte)EvolutionMethods.LevelUp, (ushort)Configuration.TradePokemonEvolutionLevel, poke.Evolutions[i].EvolvesInto);
                                Logger.Log($"Setting to evolve via level up at level {Configuration.TradePokemonEvolutionLevel}\n");
                                break;
                            }
                        }

                        if (settings.EasyEvolutions)
                        {
                            if (!CheckForSplitOrEndEvolution(poke, out int _))
                            {
                                var evolution = poke.Evolutions[0];
                                var evoPoke   = extractedGame.PokemonList[evolution.EvolvesInto];

                                // check if we evolve into something else
                                // i.e. if three stage
                                if (!CheckForSplitOrEndEvolution(evoPoke, out int count))
                                {
                                    var evoPokeEvolution = evoPoke.Evolutions[0];
                                    if (evoPokeEvolution.EvolutionMethod == EvolutionMethods.LevelUp && evoPokeEvolution.EvolutionCondition > 40)
                                    {
                                        // make a bold assumption that if the second stage evolves by level up then the first does too
                                        evoPoke.SetEvolution(0, (byte)evoPokeEvolution.EvolutionMethod, 40, evoPokeEvolution.EvolvesInto);
                                        poke.SetEvolution(0, (byte)evolution.EvolutionMethod, 30, evolution.EvolvesInto);

                                        Logger.Log($"Setting {poke.Name} to evolve at level 30.\n");
                                        Logger.Log($"Setting {evoPoke.Name} to evolve at level 40.\n");
                                    }
                                }
                                else if (count == 0 && evolution.EvolutionMethod == EvolutionMethods.LevelUp && evolution.EvolutionCondition > 40)
                                {
                                    // this is the last stage
                                    poke.SetEvolution(0, (byte)evolution.EvolutionMethod, 40, evolution.EvolvesInto);
                                    Logger.Log($"Setting to evolve at level 40.\n");
                                }
                            }
                        }
                    }
                    Logger.Log($"\n");
                }

                // so I heard you like a challenge...
                if (settings.NoEXP)
                {
                    poke.BaseExp = 0;
                }

                // randomize level up moves
                if (settings.MoveSetOptions.RandomizeMovesets)
                {
                    var typeFilter = movefilter;
                    if (settings.MoveSetOptions.PreferType)
                    {
                        // allow 20% chance for move to not be same type
                        typeFilter = typeFilter.Where(m => m.Type == poke.Type1 || m.Type == poke.Type2 || random.Next(0, 10) >= 8).ToArray();
                        if (!typeFilter.Any())
                        {
                            typeFilter = movefilter;
                        }
                    }

                    var potentialMoves = typeFilter.ToArray();
                    for (int i = 0; i < poke.LevelUpMoves.Length; i++)
                    {
                        var move = poke.LevelUpMoves[i];
                        if (move.Level == 0)
                        {
                            continue;
                        }

                        var newMove = settings.MoveSetOptions.MetronomeOnly
                            ? extractedGame.MoveList[RandomizerConstants.MetronomeIndex]
                            : potentialMoves[random.Next(0, potentialMoves.Length)];

                        poke.SetLevelUpMove(i, move.Level, (ushort)newMove.MoveIndex);
                        Logger.Log($"Level Up Move at level {move.Level}: {newMove.Name}\n");
                    }
                }

                Logger.Log($"\n");
            }
        }
        public static void ShuffleCards(Random random, BingoCardShufflerSettings settings, BattleBingoCard[] bingoCards, ExtractedGame extractedGame)
        {
            Logger.Log("=============================== Bingo Cards ===============================\n\n");
            var potentialPokes = extractedGame.PokemonList;

            if (settings.RandomizeBattleBingoPokemon && settings.ForceStrongPokemon)
            {
                potentialPokes = potentialPokes.Where(p => p.BST >= Configuration.StrongPokemonBST).ToArray();
            }

            var potentialMoves = extractedGame.MoveList.Where(m => m.BasePower > 0);

            if (settings.RandomizeBattleBingoMoveSets)
            {
                if (settings.ForceGoodDamagingMove)
                {
                    potentialMoves = potentialMoves.Where(m => m.BasePower >= Configuration.GoodDamagingMovePower);
                }

                if (settings.BanShadowMoves)
                {
                    potentialMoves = potentialMoves.Where(m => !m.IsShadowMove);
                }
            }

            foreach (var card in bingoCards)
            {
                for (int i = 0; i <= card.Panels.Length; i++)
                {
                    BattleBingoPokemon battleBingoPokemon;
                    Pokemon            newPoke;
                    if (i == card.Panels.Length)
                    {
                        // randomize starter
                        battleBingoPokemon = card.StartingPokemon;
                        Logger.Log($"Starter: {extractedGame.PokemonList[battleBingoPokemon.Pokemon].Name} -> ");
                    }
                    else
                    {
                        var panel = card.Panels[i];
                        if (panel.PanelType != BattleBingoPanelType.Pokemon)
                        {
                            continue;
                        }
                        battleBingoPokemon = panel.BingoPokemon;
                        Logger.Log($"Panel Pokemon: {extractedGame.PokemonList[battleBingoPokemon.Pokemon].Name} -> ");
                    }

                    newPoke = extractedGame.PokemonList[battleBingoPokemon.Pokemon];
                    if (settings.RandomizeBattleBingoPokemon)
                    {
                        newPoke = potentialPokes[random.Next(0, potentialPokes.Length)];
                        battleBingoPokemon.Pokemon = (ushort)newPoke.Index;
                    }
                    Logger.Log($"{extractedGame.PokemonList[battleBingoPokemon.Pokemon].Name}\n");

                    if (settings.RandomizeBattleBingoMoveSets)
                    {
                        // filter again by stab moves
                        var movePool = potentialMoves;
                        if (settings.ForceSTABMove)
                        {
                            var stabMoves = potentialMoves.Where(m => m.Type == newPoke.Type1 || m.Type == newPoke.Type2);
                            // don't use stab moves if there aren't any that match
                            if (stabMoves.Any())
                            {
                                movePool = stabMoves;
                            }
                        }
                        // pick good moves
                        battleBingoPokemon.Move = (ushort)movePool.ElementAt(random.Next(0, movePool.Count())).MoveIndex;
                    }
                    Logger.Log($"Move: {extractedGame.MoveList[battleBingoPokemon.Move].Name}\n\n");
                }
                // if you shuffle the panels do you have to write back the offset?
            }
        }
        public static void ShuffleTeams(Random random, TeamShufflerSettings settings, ExtractedGame extractedGame)
        {
            var potentialItems = settings.BanBadItems ? extractedGame.GoodItems : extractedGame.NonKeyItems;
            var potentialMoves = extractedGame.MoveList;

            if (settings.MoveSetOptions.RandomizeMovesets && settings.MoveSetOptions.ForceGoodMoves)
            {
                potentialMoves = potentialMoves.Where(m => m.BasePower >= Configuration.GoodDamagingMovePower).ToArray();
            }
            Logger.Log("=============================== Trainers ===============================\n\n");

            // yikes
            foreach (var pool in extractedGame.TrainerPools)
            {
                if (pool.TeamType == TrainerPoolType.DarkPokemon)
                {
                    continue;
                }

                foreach (var trainer in pool.AllTrainers)
                {
                    if (!trainer.IsSet)
                    {
                        continue;
                    }

                    Logger.Log($"Trainer {trainer.Name}\nWith:\n");
                    foreach (var pokemon in trainer.Pokemon)
                    {
                        if (pokemon.Pokemon == 0)
                        {
                            continue;
                        }

                        RandomizePokemon(random, settings, pokemon, extractedGame.PokemonList);
                        Logger.Log($"{extractedGame.PokemonList[pokemon.Pokemon].Name}\n");
                        Logger.Log($"Is a shadow Pokemon: {pokemon.IsShadow}\n");

                        if (settings.RandomizeHeldItems)
                        {
                            var item = potentialItems[random.Next(0, potentialItems.Length)];
                            pokemon.Item = (ushort)item.Index;
                            Logger.Log($"Holding a(n) {item.Name}\n");
                        }

                        if (settings.SetMinimumShadowCatchRate && pokemon.IsShadow)
                        {
                            if (pokemon.ShadowCatchRate == 0)
                            {
                                pokemon.ShadowCatchRate = extractedGame.PokemonList[pokemon.Pokemon].CatchRate;
                            }
                            var catchRate         = Math.Max(pokemon.ShadowCatchRate, settings.ShadowCatchRateMinimum);
                            var catchRateIncrease = (byte)Math.Clamp(catchRate, 0, byte.MaxValue);

                            Logger.Log($"Setting catch rate to {catchRateIncrease}\n");
                            pokemon.ShadowCatchRate = catchRateIncrease;
                        }
                        if (settings.BoostTrainerLevel)
                        {
                            var level         = pokemon.Level;
                            var levelIncrease = (byte)Math.Clamp(level + level * settings.BoostTrainerLevelPercent, 1, 100);
                            Logger.Log($"Boosting level from {pokemon.Level} to {levelIncrease}\n");
                            pokemon.Level = levelIncrease;
                        }

                        RandomizeMoveSet(random, settings, pokemon, extractedGame);
                        Logger.Log($"\n");
                    }
                    Logger.Log($"\n");
                }
                Logger.Log($"\n");
            }
        }
        public static void RandomizeMoveSet(Random random, TeamShufflerSettings settings, ITrainerPokemon pokemon, ExtractedGame extractedGame)
        {
            ushort[] moveSet = null;

            if (settings.MoveSetOptions.MetronomeOnly)
            {
                moveSet = Enumerable.Repeat(RandomizerConstants.MetronomeIndex, Constants.NumberOfPokemonMoves).ToArray();
            }
            else if (settings.MoveSetOptions.RandomizeMovesets || settings.RandomizePokemon)
            {
                moveSet = MoveShuffler.GetNewMoveset(random, settings.MoveSetOptions, pokemon.Pokemon, pokemon.Level, extractedGame);
            }

            if (moveSet != null)
            {
                Logger.Log($"It knows:\n");
                for (int i = 0; i < moveSet.Length; i++)
                {
                    var move = moveSet[i];
                    Logger.Log($"{extractedGame.MoveList[move].Name}\n");
                    pokemon.SetMove(i, move);
                }
            }
        }