// PATHS
private static string DEST_MASTER = @"";
private static string PATH_MASTER = @"";
private static string PATH_TRPFD = @"";
private static string DEST_LOG = @"";
private static string LANGUAGE = "English";

// MAXIMUMS
private const int MAX_SPECIES_ID = 1025;
private const int MAX_MOVE_ID = 919;
private const int MAX_ABILITY_ID = 310;
private const int MAX_TM_COUNT = 229;
private const int MAX_TYPE_ID = 17;
private const int MAX_BALL_ID = 26;
private const int MAX_RIBBON_ID = 111;

// RANDOMIZER GENERATION SETTINGS
private static bool GENERATE_RANDOMIZER_LOG_FILE = true;
private static bool AUTOMATICALLY_PATCH_TRPFD = true;
private static bool RANDOMIZE_RANDOMIZER_SETTINGS = false;
private const string RANDOMIZER_SEED = ""; // Leave empty for pure random!

// POKÉMON AVAILABILITY SETTINGS
private static bool ALLOW_SPECIES_GEN_1 = true;
private static bool ALLOW_SPECIES_GEN_2 = true;
private static bool ALLOW_SPECIES_GEN_3 = true;
private static bool ALLOW_SPECIES_GEN_4 = true;
private static bool ALLOW_SPECIES_GEN_5 = true;
private static bool ALLOW_SPECIES_GEN_6 = true;
private static bool ALLOW_SPECIES_GEN_7 = true;
private static bool ALLOW_SPECIES_GEN_8 = true;
private static bool ALLOW_SPECIES_GEN_9 = true;

private static bool ALLOW_SPECIES_LEGENDARY = true;
private static bool ALLOW_SPECIES_MYTHICAL = true;
private static bool ALLOW_SPECIES_PARADOX = true;

// SWAP CERTAIN POKÉMON FOR OTHER POKÉMON OF THAT SAME GROUP
private static bool FORCE_SWAP_SPECIES_LEGENDARY = true;
private static bool FORCE_SWAP_SPECIES_MYTHICAL = true;
private static bool FORCE_SWAP_SPECIES_PARADOX = true;
private static bool FORCE_SWAP_SPECIES_ALL_SPECIAL_POKEMON = false;

// PERSONAL (POKÉMON STATS) SETTINGS
private static bool RANDOMIZE_PERSONAL_ABILITY = false;
private static bool RANDOMIZE_PERSONAL_TYPE = false;
private static bool RANDOMIZE_PERSONAL_EVOLUTION = false;
private static bool RANDOMIZE_PERSONAL_LEARNSET = false;
private static bool RANDOMIZE_PERSONAL_BASE_STATS_WITHIN_BST = false;
private static bool RANDOMIZE_PERSONAL_BASE_STATS_COMPLETELY_RANDOM = false;
private static bool RANDOMIZE_PERSONAL_BASE_STATS_SHUFFLED = false;

// EVOLUTION SETTINGS
private static bool ABILITIES_TYPES_FOLLOW_EVOLUTIONS = false;
private static byte GAIN_RANDOM_TYPE_EVO_1_CHANCE_PERCENT = 35;
private static byte GAIN_RANDOM_TYPE_EVO_2_CHANCE_PERCENT = 55;
private static byte RANDOM_DUAL_TYPE_CHANCE_PERCENT = 40;

// LEARNSET SETTINGS
private static bool ALLOW_STARMOBILE_TORQUE_MOVES = false;
private static bool GUARANTEE_FOUR_MOVES_AT_LEVEL_ONE = false;
private static bool RANDOMIZE_LEARNSETS_WITH_TYPE_BIAS = false;
private static byte RANDOM_LEARNSET_TYPE_BIAS_PERCENT = 60;
private static bool STANDARDIZE_LEVELS_FOR_LEARNING_MOVES = false;
private static bool TECHNICAL_MACHINE_LEARNSET_SANITY = false;

// MOVE PROPERTIES SETTINGS
private static bool RANDOMIZE_MOVE_PROPERTIES_TYPE = false;
private static bool RANDOMIZE_MOVE_PROPERTIES_CATEGORY = false;
private static bool RANDOMIZE_MOVE_PROPERTIES_POWER = false;
private static bool RANDOMIZE_MOVE_PROPERTIES_ACCURACY = false;
private static bool RANDOMIZE_MOVE_PROPERTIES_PP = false;

// EXTRA MODIFICATIONS
private static bool FORCE_FULLY_EVOLVE = false;
private static byte FORCE_FULLY_EVOLVE_LEVEL = 40;

private static bool MODIFY_POKEMON_LEVELS = false;
private static sbyte LEVEL_MODIFIER_PERCENT = 10;

private static bool ALLOW_FULLY_RANDOM_TERA_TYPES = false;
private static byte STELLAR_TERA_TYPE_CHANCE_PERCENT = 2;

private static byte ADD_NUMBER_OF_POKEMON_TO_IMPORTANT_TRAINERS = 0;
private static byte TRAINER_MINIMUM_POKEMON_COUNT = 1;
private static byte TRAINER_MAXIMUM_POKEMON_COUNT = 6;

// ADDITIONAL SETTINGS
private static bool ALLOW_BATTLE_ONLY_FORMS = false;
private static bool ALLOW_RANDOM_HIDDEN_ABILITIES = false;
private static bool ALLOW_RANDOM_PICNIC_ITEMS = false;
private static bool ALL_POKEMON_ONLY_KNOW_METRONOME = false;
private static bool BAN_ABILITY_WONDER_GUARD = false;
private static bool DO_NOT_RANDOMIZE_TERA_BLAST_TM = false;
private static bool ENSURE_STARTERS_HAVE_TWO_EVOLUTIONS = false;
private static bool FORCE_FULLY_EVOLVE_TITAN_POKEMON = false;
private static bool GIVE_IMPORTANT_TRAINERS_SIX_POKEMON = false;
private static bool GIVE_POKEMON_HELD_ITEMS = false;
private static bool GIVE_POKEMON_RANDOM_POKE_BALLS = false;
private static bool GIVE_POKEMON_RANDOM_RIBBONS = false;
private static bool GIVE_TRAINERS_MAXIMUM_AI = false;
private static bool GIVE_TRAINERS_MAX_INDIVIDUAL_VALUES = false;
private static bool GIVE_TRAINERS_SMART_EFFORT_VALUES = false;
private static bool MAKE_ALL_TMS_CRAFTABLE_FROM_START = false;
private static bool MAKE_ALL_TRAINER_BATTLES_DOUBLE_BATTLES = false;
private static bool MAKE_POKEMON_EVOLVE_EVERY_LEVEL = false;
private static bool MAKE_WILD_POKEMON_TIME_OF_DAY_DEPENDENT = false;
private static bool PRESERVE_FORM_CHANGE_ABILITIES = false;
private static bool PRESERVE_OGERPON_EMBODY_ASPECT_ABILITIES = false;
private static bool RANDOMIZE_POKEMON_TO_HAVE_SIMILAR_STRENGTH = false;
private static bool RANDOMIZE_ROAMING_FORM_GIMMIGHOUL = false;
private static bool RANDOMIZE_TERA_RAID_ITEM_DROPS_BONUS = false;
private static bool RANDOMIZE_TERA_RAID_ITEM_DROPS_FIXED = false;
private static bool REDUCE_EGG_HATCH_CYCLES = false;
private static bool REMOVE_EFFORT_VALUE_YIELDS = false;
private static bool RIVAL_CARRIES_STARTER_THROUGHOUT_GAME = false;
private static byte TARGET_STAR_BARRAGE_KO_COUNT_INITIAL = 30;
private static byte TARGET_STAR_BARRAGE_KO_COUNT_REMATCH = 50;
private static byte TRAINER_POKEMON_SHINY_CHANCE_PERCENT = 2;
private static bool TYPE_THEMED_IMPORTANT_TRAINERS = false;

// IF YOU WANT RANDOM STARTERS, KEEP THESE VALUES UNTOUCHED
// SPECIES INPUT TAKES NATIONAL POKÉDEX NUMBERS
// FOR A SPECIFIC FORM, INPUT ITS FORM INDEX, OTHERWISE KEEP -1 FOR A RANDOM FORM
// FOR A SPECIFIC GENDER, INPUT 1 FOR MALE, 2 FOR FEMALE, OTHERWISE KEEP -1 FOR A RANDOM GENDER
// FOR A SPECIFIC ABILITY, INPUT 1 FOR FIRST ABILITY, 2 FOR SECOND ABILITY, 3 FOR HIDDEN ABILITY, OTHERWISE KEEP -1 FOR A RANDOM ABILITY
private static ushort FORCE_STARTER_GRASS_SPECIES = 0;
private static sbyte FORCE_STARTER_GRASS_FORM = -1;
private static sbyte FORCE_STARTER_GRASS_GENDER = -1;
private static sbyte FORCE_STARTER_GRASS_ABILITY = -1;
private static bool FORCE_STARTER_GRASS_SHINY = false;

private static ushort FORCE_STARTER_FIRE_SPECIES = 0;
private static sbyte FORCE_STARTER_FIRE_FORM = -1;
private static sbyte FORCE_STARTER_FIRE_GENDER = -1;
private static sbyte FORCE_STARTER_FIRE_ABILITY = -1;
private static bool FORCE_STARTER_FIRE_SHINY = false;

private static ushort FORCE_STARTER_WATER_SPECIES = 0;
private static sbyte FORCE_STARTER_WATER_FORM = -1;
private static sbyte FORCE_STARTER_WATER_GENDER = -1;
private static sbyte FORCE_STARTER_WATER_ABILITY = -1;
private static bool FORCE_STARTER_WATER_SHINY = false;

// POTENTIAL CRASHES AND SOFTLOCKS -- PROCEED WITH CAUTION
private static bool ALLOW_ALL_TRAINERS_TO_TERASTALLIZE = false;
private static bool ALLOW_ALL_UNUSABLE_MOVES_TO_BE_USED = false;
private static bool RANDOMIZE_KORAIDON_MIRAIDON = false;
private static bool RANDOMIZE_GENERIC_OVERWORLD_POKEMON = false;
private static byte GENERIC_OVERWORLD_POKEMON_SHINY_CHANCE_PERCENT = 2;

// INITIALIZE -- DO NOT MODIFY THESE LINES!
private static PersonalTable PersonalInfoVanilla = GetVanillaPersonalTable();
private static PersonalTable PersonalInfoEdited = GetVanillaPersonalTable();
private static WazaTable MoveInfoVanilla = GetVanillaMoveTable();
private static WazaTable MoveInfoEdited = GetVanillaMoveTable();
private static List<ushort> Legendary = GetLegendaryList();
private static List<ushort> Mythical = GetMythicalList();
private static List<int> Paradox = GetParadoxList();
private static List<ushort> SpecialPokemon = GetSpecialPokemonList();
private static List<DevID> SpeciesBanlist = GetSpeciesBanlist();
private static List<DevID> SpeciesList = GetSpeciesList();
private static List<int> PermittedMoves = GetPermittedMoves();
private static List<int> AbilityBanlist = GetAbilityBanlist();
private static List<int> PermittedAbilities = GetPermittedAbilities();
private static List<int> TechnicalMachineList = GetTMList();
private static List<ushort> ItemList = GetItemList();
private static List<string> RandomizerLog = new();
private static List<byte> ThemedTypes = new();
private static int RANDOMIZER_SEED_VALUE = GetRandomizerSeedValue();
private static TextConfig TextConfig = new(GameVersion.SV);
private static string[] StringsNamesAbility = GetStringsCommon(LANGUAGE, "tokusei");
private static string[] StringsNamesForm = GetStringsCommon(LANGUAGE, "zkn_form");
private static string[] StringsNamesItem = GetStringsCommon(LANGUAGE, "itemname");
private static string[] StringsNamesMove = GetStringsCommon(LANGUAGE, "wazaname");
private static string[] StringsNamesSpecies = GetStringsCommon(LANGUAGE, "monsname");
private static string[] StringsNamesTrainer = GetStringsCommon(LANGUAGE, "trname");
private static string[] StringsNamesType = GetStringsCommon(LANGUAGE, "typename");

private static void Main()
{
    if (!GetArePathsValid())
        "Path definitions are invalid. Please revise your setup at the top of the LINQPad query. Aborting randomization.".Dump();

    else
    {
        RandomizerLog.Clear(); // sometimes running LINQ query won't clear a prior list

        if (RANDOMIZE_RANDOMIZER_SETTINGS)
            RandomizeRandomizerSettings();

        PrepareInitialChanges();

        RandomizeMoveProperties();
        RandomizePokemonStats();

        "".Dump();

        RandomizeWildPokemon();
        RandomizeFixedSymbolEncounters();
        RandomizeStaticEncounters();
        RandomizeGiftEncounters();
        RandomizeTrainerPokemon();
        RandomizeFieldItems();
        RandomizeHiddenItems();
        RandomizePickupItemTables();
        RandomizeRummageItems();
        RandomizeTeraRaidEncounters();
        RandomizeTechnicalMachines();
        RandomizeTradeEncounters();
        RandomizeAcademyAceTournamentRewards();
        RandomizePokedexMilestoneRewards();
        RandomizeStarBarrageEncounters();
        RandomizeStarmobileStats();
        RandomizeOgreOustinRewards();
        RandomizeSpecialCoachRewards();
        RandomizePortoMarinadaAuctions();
        RandomizeItemPrinterItems();
        RandomizeTeraRaidItemDrops();

        "".Dump();

        EnhanceShopLineups();
        EnhanceItemData();
        UpdatePlibConversionTable();
        EnhanceEvolutionParameters();
        EnhanceBlueberryQuests();
        EnhanceBoutiqueAndSalonLineups();
        ExpandPaldeaPokedex();
        UpdateItemSortTables();
        ApplyLuaChanges();
        ExtraChanges();

        "".Dump();

        if (GENERATE_RANDOMIZER_LOG_FILE && RandomizerLog.Count is not 0)
            GenerateRandomizerLogFile();

        FinalizeRandomizerData();
    }
}

private static void RandomizeWildPokemon()
{
    List<string> PokeData =
    [
        "world/data/encount/pokedata/pokedata/pokedata_array.bin",
        "world/data/encount/pokedata/pokedata_su1/pokedata_su1_array.bin",
        "world/data/encount/pokedata/pokedata_su2/pokedata_su2_array.bin",
    ];

    var vt = new VersionTable() { A = true, B = true };
    foreach (var t in PokeData)
    {
        var index = t.LastIndexOf("/") + 1;
        var f = t[index..];
        var d = t[..index];

        var location = f switch
        {
            "pokedata_array.bin" => "Paldea",
            "pokedata_su1_array.bin" => "Kitakami",
            "pokedata_su2_array.bin" => "Blueberry Academy",
            _ => throw new ArgumentException($"Invalid file: {f}"),
        };

        RandomizerLog.Add($"== WILD POKÉMON ({location.ToUpper()}) ==");
        $"Randomizing wild Pokémon ({location})...".Dump();

        var file = Path.Combine(PATH_MASTER, d, f);
        var obj = FlatBufferConverter.DeserializeFrom<EncountPokeDataArray>(file);
        var blueberry = location is "Blueberry Academy";
        var maxBiome = blueberry ? 23 : 22; // DENKI_ISHI does not exist in Paldea/Kitakami
        var area = PokeData.IndexOf(t);
        obj.Table.Clear(); // now we reconstruct PokeData from scratch!

        for (ushort i = 0001; i <= 1025; i++)
        {
            if (SpeciesBanlist.Contains((DevID)i))
                continue;
            if (i is 1011 && RANDOMIZE_PERSONAL_TYPE) // randomized types may lead to an invalid Tera Type for Ogerpon, skip it altogether
                continue;
            if (i is 1021) // prevent wild Terapagos due to the game generating an invalid Tera Type (Normal, not Stellar)
                continue;

            bool IsBisharp = i is 0625;
            var forms = GetFormCount(i);
            for (byte c = 0; c < forms; c++)
            {
                if (!GetIsPresentInGame(i, c))
                    continue;
                if (!IsValidForm(i, c))
                    continue;

                var band = IsBisharp ? BandType.BOSS : (BandType)GetRandom(1, 3);
                List<Biome> Biomes = GetRandomBiomes(maxBiome);
                EncountPokeData entry = new()
                {
                    DevId = (DevID)i,
                    Sex = SexType.DEFAULT,
                    Form = (sbyte)c,
                    MinLevel = 1,
                    MaxLevel = 100,

                    LotValue = GetLotValue(i, c, blueberry),
                    Biome1 = Biomes[0],
                    LotValue1 = GetLotValueBiome(),
                    Biome2 = Biomes[1],
                    LotValue2 = GetLotValueBiome(),
                    Biome3 = Biomes[2],
                    LotValue3 = GetLotValueBiome(),
                    Biome4 = Biomes[3],
                    LotValue4 = GetLotValueBiome(),
                    Area = GenerateAreaList(area),
                    LocationName = string.Empty,

                    MinHeight = 0,
                    MaxHeight = 0,
                    Enable = GetEnableTable(i, c),
                    Time = GetTimeTable(),
                    FlagName = string.Empty,

                    BandRate = IsBisharp ? (short)100 : GetLotValue(i, c, blueberry),
                    Band = band,
                    BandPoke = IsBisharp ? DevID.DEV_KOMATANA : (DevID)i,
                    BandSex = SexType.DEFAULT,
                    BandForm = IsBisharp ? (sbyte)0 : (sbyte)c,
                    OutbreakLotValue = (sbyte)GetLotValue(i, c, blueberry),
                    PokeVoiceClassification = GetPokeVoiceClassification(),
                    Version = vt,
                    Item = GetBringItem((DevID)i, (sbyte)c, area),
                };

                obj.Table.Add(entry);
            }
        }

        // apply identical spawning conditions for all forms of Arceus, Scatterbug, Spewpa, Vivillon, Flabébé, Floette, Florges, Minior, and Alcremie
        foreach (var k in obj.Table.Where(z => (int)z.DevId is 0493 or 0664 or 0665 or 0666 or 0669 or 0670 or 0671 or 0774 or 0869 && z.Form is not 0))
        {
            var pk = obj.Table.FirstOrDefault(z => z.DevId == k.DevId); // source from base form, then copy to all others
            k.LotValue  = pk.LotValue;
            k.Biome1    = pk.Biome1;
            k.LotValue1 = pk.LotValue1;
            k.Biome2    = pk.Biome2;
            k.LotValue2 = pk.LotValue2;
            k.Biome3    = pk.Biome3;
            k.LotValue3 = pk.LotValue3;
            k.Biome4    = pk.Biome4;
            k.LotValue4 = pk.LotValue4;
            k.Area      = pk.Area;
        }

        RandomizerLog.Add("===================================================================================================================================================");
        RandomizerLog.Add("| No.     | Pokémon      | Lot | Biome 1           | Biome 2           | Biome 3           | Biome 4           | Held Item                        |");
        RandomizerLog.Add("===================================================================================================================================================");

        var ordered = obj.Table.OrderBy(z => GetNationalDexID((ushort)z.DevId));
        foreach (var pk in ordered)
        {
            var name = StringsNamesSpecies[(int)pk.DevId];
            var dex = GetNationalDexID((ushort)pk.DevId);
            var biome1 = $"{pk.LotValue1, -3} ({BiomeNames[(int)pk.Biome1]})";
            var biome2 = $"{pk.LotValue2, -3} ({BiomeNames[(int)pk.Biome2]})";
            var biome3 = $"{pk.LotValue3, -3} ({BiomeNames[(int)pk.Biome3]})";
            var biome4 = $"{pk.LotValue4, -3} ({BiomeNames[(int)pk.Biome4]})";
            var item = GetBringItemDisplayText((BringItem)pk.Item, StringsNamesItem);

            RandomizerLog.Add($"| {dex:0000}-{pk.Form:00} | {name.PadRight(12)} | {pk.LotValue, -3} | {biome1.PadRight(17)} | {biome2.PadRight(17)} | {biome3.PadRight(17)} | {biome4.PadRight(17)} | {item.PadRight(32)} |");
        }

        RandomizerLog.Add("===================================================================================================================================================");
        RandomizerLog.Add("");

        // Area Zero spawns must have a blank area list, and there are only 5 possible biomes, so let's add an additional 200 spawns with special logic specifically for Area Zero
        if (location is "Paldea")
        {
            RandomizerLog.Add("== WILD POKÉMON (AREA ZERO) ==");
            "Randomizing wild Pokémon (Area Zero)...".Dump();

            RandomizerLog.Add("===================================================================================================================================================");
            RandomizerLog.Add("| No.     | Pokémon      | Lot | Biome 1           | Biome 2           | Biome 3           | Biome 4           | Held Item                        |");
            RandomizerLog.Add("===================================================================================================================================================");

            for (int i = 0; i < 200; i++)
            {
                List<Biome> Biomes = GetAreaZeroBiomes();
                var dev = GetRandomSpecies(0);

                // prevent wild Ogerpon/Terapagos due to Tera Type edge cases
                while (dev is DevID.DEV_KODAIGAME || dev is DevID.DEV_KAMENONI && RANDOMIZE_PERSONAL_TYPE)
                    dev = GetRandomSpecies(0);

                var form = (sbyte)GetRandomForm(dev);
                bool IsBisharp = dev is DevID.DEV_KIRIKIZAN;
                BandType band = IsBisharp ? BandType.BOSS : (BandType)GetRandom(1, 3);

                EncountPokeData entry = new()
                {
                    DevId = dev,
                    Sex = SexType.DEFAULT,
                    Form = form,
                    MinLevel = 1,
                    MaxLevel = 100,

                    LotValue = GetWeightedLotValue(),
                    Biome1 = Biomes[0],
                    LotValue1 = GetLotValueBiome(),
                    Biome2 = Biomes[1],
                    LotValue2 = GetLotValueBiome(),
                    Biome3 = Biomes[2],
                    LotValue3 = GetLotValueBiome(),
                    Biome4 = Biomes[3],
                    LotValue4 = GetLotValueBiome(),
                    Area = string.Empty,
                    LocationName = "a_w23_d01,a_w23_d02,a_w23_d03,a_w23_d04,a_w23_field_1,a_w23_field_2,a_w23_field_3",

                    MinHeight = 0,
                    MaxHeight = 0,
                    Enable = GetEnableTable((ushort)dev, (byte)form),
                    Time = GetTimeTable(),
                    FlagName = string.Empty,

                    BandRate = IsBisharp ? (short)100 : GetWeightedLotValue(),
                    Band = band,
                    BandPoke = IsBisharp ? DevID.DEV_KOMATANA : dev,
                    BandSex = SexType.DEFAULT,
                    BandForm = form,
                    OutbreakLotValue = 0, // no outbreaks in Area Zero
                    PokeVoiceClassification = GetPokeVoiceClassification(),
                    Version = vt,
                    Item = GetBringItem(dev, form, 0),
                };

                obj.Table.Add(entry);
                var name = StringsNamesSpecies[(int)dev];
                var dex = GetNationalDexID((ushort)dev);
                var biome1 = $"{entry.LotValue1, -3} ({BiomeNames[(int)entry.Biome1]})";
                var biome2 = $"{entry.LotValue2, -3} ({BiomeNames[(int)entry.Biome2]})";
                var biome3 = $"{entry.LotValue3, -3} ({BiomeNames[(int)entry.Biome3]})";
                var biome4 = $"{entry.LotValue4, -3} ({BiomeNames[(int)entry.Biome4]})";
                var item = GetBringItemDisplayText((BringItem)entry.Item, StringsNamesItem);

                RandomizerLog.Add($"| {dex:0000}-{form:00} | {name.PadRight(12)} | {entry.LotValue, -3} | {biome1.PadRight(17)} | {biome2.PadRight(17)} | {biome3.PadRight(17)} | {biome4.PadRight(17)} | {item.PadRight(32)} |");
            }

            RandomizerLog.Add("===================================================================================================================================================");
            RandomizerLog.Add("");
        }

        var data = FlatBufferConverter.SerializeFrom(obj);
        var dest = Path.Combine(DEST_MASTER, d);
        if (!Directory.Exists(dest))
            Directory.CreateDirectory(dest);
        File.WriteAllBytes(Path.Combine(dest, f), data);
    }

    if (MODIFY_POKEMON_LEVELS)
        ModifyWildPokemonLevels();
}

private static short GetLotValue(ushort species, byte form, bool blueberry)
{
    if (!RANDOMIZE_POKEMON_TO_HAVE_SIMILAR_STRENGTH || RANDOMIZE_PERSONAL_BASE_STATS_COMPLETELY_RANDOM || blueberry)
        return GetWeightedLotValue();

    var bst = GetBaseStatTotal(species, form);
    return bst switch
    {
        >= 001 and <= 300 => (short)GetRandom(40, 81),
        >= 301 and <= 450 => (short)GetRandom(20, 46),
        >= 451 and <= 520 => (short)GetRandom(15, 21),
        >= 521 and <= 549 => (short)GetRandom(07, 12),
                        _ => (short)GetRandom(01, 05),
    };
}

private static short GetWeightedLotValue() => GetRandom(100) switch
{
    <= 99 and >= 75 => (short)GetRandom(60, 81),
    <= 74 and >= 50 => (short)GetRandom(44, 60),
    <= 49 and >= 25 => (short)GetRandom(25, 44),
                  _ => (short)GetRandom(10, 25),
};

private static short GetLotValueBiome() => (short)GetRandom(20, 101);

private static string GenerateAreaList(int area)
{
    // total number of areas per map
    var maxAreas = area switch
    {
        1 => 11,
        2 => 4,
        _ => 27,
    };

    // total number of areas to assign per entry
    var maxSelection = area switch
    {
        1 => 6,
        2 => 2,
        _ => 10,
    };

    List<int> Areas = Enumerable.Range(1, maxAreas).ToList();
    pkNX.Randomization.Util.Shuffle(Areas);
    Areas = Areas[0..maxSelection].OrderBy(z => z).ToList();
    return string.Join(",", Areas);
}

private static EnableTable GetEnableTable(ushort dev, byte form)
{
    var species = GetNationalDexID(dev);
    var enable = new EnableTable() { Land = true };

    if (GetCanSwimOrBeAboveWater(species, form))
        enable.UpWater = true;

    if (GetCanFlyOrLevitate(species, form))
        enable.Air1 = enable.Air2 = true;

    return enable;
}

private static TimeTable GetTimeTable()
{
    if (!MAKE_WILD_POKEMON_TIME_OF_DAY_DEPENDENT)
        return new() { Morning = true, Noon = true, Evening = true, Night = true };

    var rand = GetRandom(2);
    var time = new TimeTable();

    if (rand is 0) time.Morning = time.Noon = true;
    if (rand is 1) time.Evening = time.Night = true;

    return time;
}

private static string GetPokeVoiceClassification()
{
    List<string> Voices = [ "ANIMAL", "ANIMAL_CARNIVORE", "ANIMAL_LITTLE", "BIRD", "BIRD_LITTLE", "CROWS", "INSECT", "LARGE_CREATURES", "OTHERS" ];
    return Voices[GetRandom(Voices.Count)];
}

private static string GetBringItemDisplayText(BringItem item, string[] names)
{
    if (item is { ItemID: ItemID.ITEMID_NONE } or { BringRate: 0 })
        return string.Empty;
    return $"{item.BringRate}% {names[(int)item.ItemID]}";
}

private static BringItem GetBringItem(DevID species, sbyte form, int area)
{
    if (species is DevID.DEV_KIRIKIZAN)
        return new BringItem() { ItemID = ItemID.ITEMID_KASIRANOAKASI, BringRate = 100 }; // always give Leader’s Crest

    if (IsFormHeldItemDependent(species))
        return new BringItem() { ItemID = GetFormSpecificItem(species, (short)form), BringRate = 100 };

    if (GIVE_POKEMON_HELD_ITEMS)
        return new BringItem() { ItemID = GetRandomItem(), BringRate = (sbyte)GetRandom(1, 101) };

    return GetVanillaBringItem(species, form, area);
}

private static BringItem GetVanillaBringItem(DevID species, sbyte form, int area) => GetNationalDexID((ushort)species) switch
{
    0025 => new BringItem() { ItemID = (ItemID)0236, BringRate = 05 }, // Pikachu: Light Ball (5%)
    0113 => new BringItem() { ItemID = (ItemID)0110, BringRate = 30 }, // Chansey: Oval Stone (30%)
    0116 => new BringItem() { ItemID = (ItemID)0235, BringRate = 05 }, // Horsea: Dragon Scale (5%)
    0117 => new BringItem() { ItemID = (ItemID)0235, BringRate = 10 }, // Seadra: Dragon Scale (10%)
    0216 => new BringItem() { ItemID = (ItemID)0094, BringRate = 05 }, // Teddiursa: Honey (5%)
    0242 => new BringItem() { ItemID = (ItemID)0110, BringRate = 30 }, // Blissey: Oval Stone (30%)
    0283 => new BringItem() { ItemID = (ItemID)0094, BringRate = 05 }, // Surskit: Honey (5%)
    0285 => new BringItem() { ItemID = (ItemID)0086, BringRate = 05 }, // Shroomish: Tiny Mushroom (5%)
    0286 => new BringItem() { ItemID = (ItemID)0086, BringRate = 30 }, // Breloom: Tiny Mushroom (30%)
    0316 => new BringItem() { ItemID = (ItemID)0155, BringRate = 30 }, // Gulpin: Oran Berry (30%)
    0317 => new BringItem() { ItemID = (ItemID)0158, BringRate = 05 }, // Swalot: Sitrus Berry (5%)
    0415 => new BringItem() { ItemID = (ItemID)0094, BringRate = 30 }, // Combee: Honey (30%)
    0440 => new BringItem() { ItemID = (ItemID)0110, BringRate = 05 }, // Happiny: Oval Stone (5%)
    0590 => new BringItem() { ItemID = (ItemID)0086, BringRate = 05 }, // Foongus: Tiny Mushroom (5%)
    0591 => new BringItem() { ItemID = (ItemID)0086, BringRate = 30 }, // Amoonguss: Tiny Mushroom (30%)
    0731 => new BringItem() { ItemID = (ItemID)0155, BringRate = 30 }, // Pikipek: Oran Berry (30%)
    0732 => new BringItem() { ItemID = (ItemID)0158, BringRate = 10 }, // Trumbeak: Sitrus Berry (10%)
    0733 => new BringItem() { ItemID = (ItemID)0158, BringRate = 30 }, // Toucannon: Sitrus Berry (30%)
    0734 => new BringItem() { ItemID = (ItemID)0151, BringRate = 05 }, // Yungoos: Pecha Berry (5%)
    0735 => new BringItem() { ItemID = (ItemID)0151, BringRate = 05 }, // Gumshoos: Pecha Berry (5%)
    0739 => new BringItem() { ItemID = (ItemID)0153, BringRate = 05 }, // Crabrawler: Aspear Berry (5%)
    0740 => new BringItem() { ItemID = (ItemID)0149, BringRate = 05 }, // Crabominable: Cheri Berry (5%)
    0742 => new BringItem() { ItemID = (ItemID)0094, BringRate = 30 }, // Cutiefly: Honey (30%)
    0743 => new BringItem() { ItemID = (ItemID)0094, BringRate = 20 }, // Ribombee: Honey (20%)
    0778 => new BringItem() { ItemID = (ItemID)0150, BringRate = 05 }, // Mimikyu: Chesto Berry (5%)
    0948 => new BringItem() { ItemID = (ItemID)0086, BringRate = 05 }, // Toedscool: Tiny Mushroom (5%)
    0949 => new BringItem() { ItemID = (ItemID)0086, BringRate = 30 }, // Toedscruel: Tiny Mushroom (30%)
    0984 => new BringItem() { ItemID = (ItemID)1880, BringRate = 05 }, // Great Tusk: Booster Energy (5%)
    0985 => new BringItem() { ItemID = (ItemID)1880, BringRate = 05 }, // Scream Tail: Booster Energy (5%)
    0986 => new BringItem() { ItemID = (ItemID)1880, BringRate = 05 }, // Brute Bonnet: Booster Energy (5%)
    0987 => new BringItem() { ItemID = (ItemID)1880, BringRate = 05 }, // Flutter Mane: Booster Energy (5%)
    0988 => new BringItem() { ItemID = (ItemID)1880, BringRate = 05 }, // Slither Wing: Booster Energy (5%)
    0989 => new BringItem() { ItemID = (ItemID)1880, BringRate = 05 }, // Sandy Shocks: Booster Energy (5%)
    0990 => new BringItem() { ItemID = (ItemID)1880, BringRate = 05 }, // Iron Treads: Booster Energy (5%)
    0991 => new BringItem() { ItemID = (ItemID)1880, BringRate = 05 }, // Iron Bundle: Booster Energy (5%)
    0992 => new BringItem() { ItemID = (ItemID)1880, BringRate = 05 }, // Iron Hands: Booster Energy (5%)
    0993 => new BringItem() { ItemID = (ItemID)1880, BringRate = 05 }, // Iron Jugulis: Booster Energy (5%)
    0994 => new BringItem() { ItemID = (ItemID)1880, BringRate = 05 }, // Iron Moth: Booster Energy (5%)
    0995 => new BringItem() { ItemID = (ItemID)1880, BringRate = 05 }, // Iron Thorns: Booster Energy (5%)
    1005 => new BringItem() { ItemID = (ItemID)1880, BringRate = 05 }, // Roaring Moon: Booster Energy (5%)
    1006 => new BringItem() { ItemID = (ItemID)1880, BringRate = 05 }, // Iron Valiant: Booster Energy (5%)

    // Paldea, Blueberry Academy
    0819 when area is 0 or 2 => new BringItem() { ItemID = (ItemID)0155, BringRate = 05 }, // Skwovet: Oran Berry (5%)
    0820 when area is 0 or 2 => new BringItem() { ItemID = (ItemID)0158, BringRate = 05 }, // Greedent: Sitrus Berry (5%)

    // Kitakami
    0819 when area is 1 => new BringItem() { ItemID = (ItemID)0154, BringRate = 05 }, // Skwovet: Leppa Berry (5%)
    0820 when area is 1 => new BringItem() { ItemID = (ItemID)0154, BringRate = 10 }, // Greedent: Leppa Berry (10%)

    // Form specific
    0741 when form is 0 => new BringItem() { ItemID = (ItemID)0853, BringRate = 05 }, // Oricorio (Baile): Red Nectar (5%)
    0741 when form is 1 => new BringItem() { ItemID = (ItemID)0854, BringRate = 05 }, // Oricorio (Pom-Pom): Yellow Nectar (5%)
    0741 when form is 2 => new BringItem() { ItemID = (ItemID)0855, BringRate = 05 }, // Oricorio (Pa’u): Pink Nectar (5%)
    0741 when form is 3 => new BringItem() { ItemID = (ItemID)0856, BringRate = 05 }, // Oricorio (Sensu): Purple Nectar (5%)
    0774 when form is 0 => new BringItem() { ItemID = (ItemID)0238, BringRate = 30 }, // Minior (R): Hard Stone (30%)
    0774 when form is 1 => new BringItem() { ItemID = (ItemID)0238, BringRate = 30 }, // Minior (O): Hard Stone (30%)
    0774 when form is 2 => new BringItem() { ItemID = (ItemID)0539, BringRate = 40 }, // Minior (Y): Float Stone (40%)
    0774 when form is 3 => new BringItem() { ItemID = (ItemID)0539, BringRate = 40 }, // Minior (G): Float Stone (40%)
    0774 when form is 4 => new BringItem() { ItemID = (ItemID)0238, BringRate = 30 }, // Minior (B): Hard Stone (30%)
    0774 when form is 5 => new BringItem() { ItemID = (ItemID)0238, BringRate = 30 }, // Minior (I): Hard Stone (30%)
    0774 when form is 6 => new BringItem() { ItemID = (ItemID)0539, BringRate = 40 }, // Minior (V): Float Stone (40%)

    _ => new BringItem(),
};

private static List<Biome> GetRandomBiomes(int max)
{
    List<int> Biomes = Enumerable.Range(1, max).ToList();
    List<Biome> Result = new();
    pkNX.Randomization.Util.Shuffle(Biomes);

    for (int i = 0; i <= 3; i++)
        Result.Add((Biome)Biomes[i]);

    return Result;
}

private static List<Biome> GetAreaZeroBiomes()
{
    List<Biome> EligibleBiomes = [ Biome.GRASS, Biome.RIVER, Biome.UNDERGROUND, Biome.ROCKY, Biome.CAVE ];
    List<Biome> Biomes = new();

    // only pick 4 of the 5 eligible biomes
    var exclude = GetRandom(5);
    foreach (var b in EligibleBiomes)
    {
        if (b == (Biome)exclude)
            continue;
        Biomes.Add(b);
    }

    return Biomes;
}

private static void ModifyWildPokemonLevels()
{
    List<string> FieldAreaData =
    [
        // Paldea
        "world/data/field/area/field_dungeon_area/field_dungeon_area_array.bin",
        "world/data/field/area/field_inside_area/field_inside_area_array.bin",
        "world/data/field/area/field_location/field_location_array.bin",
        "world/data/field/area/field_main_area/field_main_area_array.bin",
        "world/data/field/area/field_sub_area/field_sub_area_array.bin",

        // Kitakami
        "world/data/field/area_su1/field_dungeon_area_su1/field_dungeon_area_su1_array.bin",
        "world/data/field/area_su1/field_inside_area_su1/field_inside_area_su1_array.bin",
        "world/data/field/area_su1/field_location_su1/field_location_su1_array.bin",
        "world/data/field/area_su1/field_main_area_su1/field_main_area_su1_array.bin",
        "world/data/field/area_su1/field_sub_area_su1/field_sub_area_su1_array.bin",

        // Blueberry Academy
        "world/data/field/area_su2/field_dungeon_area_su2/field_dungeon_area_su2_array.bin",
        "world/data/field/area_su2/field_inside_area_su2/field_inside_area_su2_array.bin",
        "world/data/field/area_su2/field_main_area_su2/field_main_area_su2_array.bin",
        "world/data/field/area_su2/field_sub_area_su2/field_sub_area_su2_array.bin",
    ];

    List<string> PointData =
    [
        "world/data/encount/point_data/point_data/encount_data_100000.bin",
        "world/data/encount/point_data/point_data/encount_data_atlantis.bin",
        "world/data/encount/point_data/point_data/encount_data_su1.bin",
        "world/data/encount/point_data/point_data/encount_data_su2.bin",
    ];

    // general areas
    foreach (var a in FieldAreaData)
    {
        var index = a.LastIndexOf("/") + 1;
        var f = a[index..];
        var d = a[..index];

        var file = Path.Combine(PATH_MASTER, d, f);
        var obj = FlatBufferConverter.DeserializeFrom<FieldMainAreaArray>(file); // schema doesn't actually matter, since they all use an identical schema with AreaInfo objects

        foreach (var t in obj.Table.Where(z => z.Info.MinEncLv is not 0 && z.Info.MaxEncLv is not 0))
        {
            t.Info.MinEncLv = GetModifiedLevel(t.Info.MinEncLv);
            t.Info.MaxEncLv = GetModifiedLevel(t.Info.MaxEncLv);
        }

        var data = FlatBufferConverter.SerializeFrom(obj);
        var dest = Path.Combine(DEST_MASTER, d);
        if (!Directory.Exists(dest))
            Directory.CreateDirectory(dest);
        File.WriteAllBytes(Path.Combine(dest, f), data);
    }

    // individual spawners
    foreach (var e in PointData)
    {
        var index = e.LastIndexOf("/") + 1;
        var f = e[index..];
        var d = e[..index];

        var file = Path.Combine(PATH_MASTER, d, f);
        var obj = FlatBufferConverter.DeserializeFrom<PointDataArray>(file);
        List<PointData> PointDataEntries = new();

        foreach (var t in obj.Table)
        {
            var min = GetModifiedLevel((int)t.LevelRange.X);
            var max = GetModifiedLevel((int)t.LevelRange.Y);
            var pd = new PointData
            {
                Position = t.Position,
                LevelRange = new PackedVec2f { X = min, Y = max },
                Biome = t.Biome,
                Substance = t.Substance,
                AreaNo = t.AreaNo,
            };

            PointDataEntries.Add(pd);
        }

        obj.Table.Clear();
        foreach (var t in PointDataEntries)
            obj.Table.Add(t);

        var data = FlatBufferConverter.SerializeFrom(obj);
        var dest = Path.Combine(DEST_MASTER, d);
        if (!Directory.Exists(dest))
            Directory.CreateDirectory(dest);
        File.WriteAllBytes(Path.Combine(dest, f), data);
    }
}

private static void RandomizeFixedSymbolEncounters()
{
    RandomizerLog.Add("== FIXED SYMBOL ENCOUNTERS ==");
    "Randomizing fixed symbol encounters...".Dump();

    const string file = "fixed_symbol_table_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/field/fixed_symbol/fixed_symbol_table/");
    var path = Path.Combine(PATH_MASTER, "world/data/field/fixed_symbol/fixed_symbol_table/");
    var obj = FlatBufferConverter.DeserializeFrom<FixedSymbolTableArray>(Path.Combine(path, file));
    bool IsWaterFixedSymbol(string key) => key.StartsWith("area18_") || key is "area06_13" or "area11_16" or "area11_17" or "area11_18" or "area11_19" or "area15_04" or "area15_19" or "area15_21" or "area19_19"
                                                                            or "su1_area07_04" or "su1_area08_07" or "su1_area09_02" or "su1_area09_03" or "su1_area09_07" or "su1_area09_10" or "su1_area09_12" or "su1_area11_21" or "su1_area11_24"
                                                                            or "su2_area02_01" or "su2_area02_02" or "su2_area02_03" or "su2_area02_04" or "su2_area02_05" or "su2_area02_06" or "su2_area02_07" or "su2_area02_38" or "su2_area04_21" or "su2_area04_23" or "su2_area04_25" or "su2_area04_27"
                                                                            or "t_su1_area08_10" or "t_su1_area11_14" or "t_su1_area11_23"
                                                                            or "t_su2_area02_27" or "t_su2_area02_28" or "t_su2_area02_33";

    foreach (var enc in obj.Table)
    {
        var t = enc.Symbol;
        var oldDev = t.DevId;
        var oldForm = t.FormId;
        var ctr = 0;

        if (RANDOMIZE_POKEMON_TO_HAVE_SIMILAR_STRENGTH)
        {
            (DevID Species, short Form) pk = GetSimilarStrengthPokemon((ushort)t.DevId, (byte)t.FormId);
            t.DevId = pk.Species;
            t.FormId = pk.Form;
        }

        else
        {
            t.DevId = GetRandomSpecies(t.DevId);
            t.FormId = GetRandomForm(t.DevId);
        }

        // keep randomizing water fixed symbols until we get an eligible species/form pair
        while (IsWaterFixedSymbol(enc.TableKey) && !GetCanSwimOrBeAboveWater((ushort)t.DevId, t.FormId) && ctr < 100)
        {
            ctr++;
            t.DevId = GetRandomSpecies(0);
            t.FormId = GetRandomForm(t.DevId);
        }

        // no item in schema, force base form
        if (IsFormHeldItemDependent(t.DevId))
            t.FormId = 0;

        t.Sex = SexType.DEFAULT;
        t.RareType = RareType.DEFAULT;
        t.WazaType = WazaType.DEFAULT;
        t.Waza1 = GetCleanMoveSlot();
        t.Waza2 = GetCleanMoveSlot();
        t.Waza3 = GetCleanMoveSlot();
        t.Waza4 = GetCleanMoveSlot();
        t.TokuseiIndex = GetRandomAbilitySetter();

        // preserve Stellar, random if wild Tera, otherwise randomize according to setting
        t.GemType = t.GemType switch
        {
            GemType.NIJI => GemType.NIJI,
            not (GemType.DEFAULT or GemType.RANDOM) => (GemType)GetRandom(2, 20),
            _ => GetTeraType(t.DevId, (byte)t.FormId, t.GemType, false, false),
        };

        if (MODIFY_POKEMON_LEVELS)
            t.Level = GetModifiedLevel(t.Level);

        if (t.DevId is DevID.DEV_MAHOIPPU)
            t.MahoippuViewId = (MahoippuViewID)GetRandom(7);

        var oForm = oldForm is 0 ? string.Empty : $"-{oldForm}";
        var nForm = t.FormId is 0 ? string.Empty : $"-{t.FormId}";
        RandomizerLog.Add($"{StringsNamesSpecies[(int)oldDev]}{oForm} -> {StringsNamesSpecies[(int)t.DevId]}{nForm}");
    }

    RandomizerLog.Add("");
    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void RandomizeStaticEncounters()
{
    RandomizerLog.Add("== STATIC ENCOUNTERS ==");
    "Randomizing static encounters...".Dump();

    List<EventBattlePokemon> OldSpecies = GetOldStaticEncounters();
    List<EventBattlePokemon> NewSpecies = new();
    List<string> Blacklist = new();
    List<string> LogDuplicates =
    [
        "coin_976_05",
        "coin_976_10",
        "coin_976_15",
        "coin_976_20",
        "coin_976_25",
        "coin_976_30",
        "coin_976_35",
        "coin_976_40",
        "coin_976_45",
        "coin_976_50",
        "coin_976_55",
        "coin_976_60",
        "gym_kusa_020_KIMAWARI_02",
        "gym_kusa_020_KIMAWARI_03",
        "gym_kusa_020_KIMAWARI_04",
        "gym_kusa_020_KIMAWARI_05",
        "lastbattle_AIGUANA",
        "lastbattle_BIGUANA",
        "nusi_931_02",
        "nusi_944_02",
        "nusi_952_dummy_pink",
        "nusi_952_dummy_red",
        "nusi_952_dummy_yellow",
        "nusi_959_02",
        "nusi_962_02",
        "nusi_978_02",
        "nusi_986_02",
        "SDC01_dokuzaru_01_strong",
        "SDC01_get_dokuzaru",
        "SDC01_kamenoni_1",
        "SDC01_kamenoni_1_strong",
        "SDC01_kamenoni_2",
        "SDC01_kamenoni_2_strong",
        "SDC01_kamenoni_3",
        "SDC01_kamenoni_3_strong",
        "SDC01_kamenoni_4_strong",
        "SDC01_midoriikenushi_strong",
        "SDC01_onitaizi_dokuinu",
        "SDC01_onitaizi_dokuinu_strong",
        "SDC01_onitaizi_bird",
        "SDC01_onitaizi_bird_strong",
        "SDC01_onitaizi_monkey",
        "SDC01_onitaizi_monkey_strong",
        "SDC02_0310_kodaikame",

        // Violet exclusives, handled separately
        "1075_multi_b_01",
        "1095_multi_b_02",
        "1180_multi_b_01",
        "1180_multi_b_02",
        "1180_multi_b_03",
        "nusi_986_01",
        "SDC02_area0_tetunoibara",
        "SDC02_sub_Bkobaruon",
        "SDC02_sub_Bterakion",
        "sub018_BIGUANA",
    ];

    List<string> ExclusiveDuplicates =
    [
        "1075_multi_a_01",
        "1095_multi_a_02",
        "1180_multi_a_01",
        "1180_multi_a_02",
        "1180_multi_a_03",
        "nusi_978_01",
        "SDC02_area0_sunanokegawa",
        "SDC02_sub_Aentei",
        "SDC02_sub_Araikou",
        "sub018_AIGUANA",
    ];

    const string file = "eventBattlePokemon_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/battle/eventBattlePokemon/");
    var path = Path.Combine(PATH_MASTER, "world/data/battle/eventBattlePokemon/");
    var obj = FlatBufferConverter.DeserializeFrom<EventBattlePokemonArray>(Path.Combine(path, file));
    var table = obj.Table;

    if (!RANDOMIZE_KORAIDON_MIRAIDON)
    {
        Blacklist.Add(table[031].Label);
        Blacklist.Add(table[032].Label);
        Blacklist.Add(table[115].Label);
        Blacklist.Add(table[116].Label);
    }

    byte GetAppropriateType(string id) => id switch
    {
        "gym_kusa_020_KIMAWARI_03" => ThemedTypes[01], // Sunflora
        "nusi_962_02" => ThemedTypes[13], // Klawf
        "nusi_959_02" => ThemedTypes[14], // Bombirdier
        "nusi_944_02" => ThemedTypes[15], // Orthworm
        "nusi_986_02" => ThemedTypes[16], // Great Tusk / Iron Treads
        "nusi_952_01" or "nusi_931_02" => ThemedTypes[17], // Tatsugiri / Dondozo
        _ => throw new ArgumentException($"Invalid encounter: {id}"),
    };

    bool IsSpecificBoss(string id) => !id.StartsWith("coin_") && !id.Contains("_dummy") && !id.Contains("semi_") && !id.Contains("su2_") && !id.Contains("SDC02_sub");
    bool IsTitan(string id) => id is "nusi_962_02" or "nusi_959_02" or "nusi_944_02" or "nusi_986_02" or "nusi_952_01" or "nusi_931_02";
    bool IsTypeThemedEncounter(string id) => id is "gym_kusa_020_KIMAWARI_03" || IsTitan(id);
    bool ShouldRandomizeEncounterAgain(string id, DevID species, short form, int level, (byte Type1, byte Type2) Types) => id switch
    {
        "SDC01_kamenoni_4_strong" when !ALLOW_FULLY_RANDOM_TERA_TYPES && !(Types.Type1 == 11 || Types.Type2 == 11) => true, // phase 4 against Ogerpon requires Tera Grass to prevent a softlock, so if Tera Types must be default, force a Grass type
        "SDC01_kamenoni_4_strong" when species is DevID.DEV_KAMENONI or DevID.DEV_KODAIGAME => true, // Ogerpon encounter should not be randomized into a Pokémon that requires a specific Tera Type
        "nusi_952_01" when species == obj.Table[034].PokeData.DevId => true, // ensure Dondozo and Tatsugiri randomize into unique species
        _ when TYPE_THEMED_IMPORTANT_TRAINERS && IsTitan(id) && ((FORCE_FULLY_EVOLVE && level >= FORCE_FULLY_EVOLVE_LEVEL) || FORCE_FULLY_EVOLVE_TITAN_POKEMON) && !(Types.Type1 == GetAppropriateType(id) || Types.Type2 == GetAppropriateType(id)) => true, // evolution may break the type theme
        _ when GetIsInvalidScenePokemon((ushort)species) => true, // softlock prevention
        _ => false,
    };

    foreach (var enc in table.Where(z => !Blacklist.Contains(z.Label)))
    {
        var t = enc.PokeData;
        var ctr = 0;

        if (RANDOMIZE_POKEMON_TO_HAVE_SIMILAR_STRENGTH)
        {
            (DevID Species, short Form) pk = GetSimilarStrengthPokemon((ushort)t.DevId, (byte)t.FormId);
            t.DevId = pk.Species;
            t.FormId = pk.Form;
        }

        else
        {
            t.DevId = GetRandomSpecies(t.DevId);
            t.FormId = GetRandomForm(t.DevId);
        }

        // set Sunflora and Titans to match a type theme
        if (TYPE_THEMED_IMPORTANT_TRAINERS && IsTypeThemedEncounter(enc.Label))
        {
            var type = GetAppropriateType(enc.Label);
            (DevID Species, short Form) pk = GetTypeThemedPokemon(type);
            t.DevId = pk.Species;
            t.FormId = pk.Form;
        }

        (byte Type1, byte Type2) Types = GetPokemonTypes((ushort)t.DevId, (byte)t.FormId);
        t.Sex = IsSpecificBoss(enc.Label) || IsGenderVariant((ushort)t.DevId, t.FormId) ? GetRandomGender(t.DevId, t.FormId) : SexType.DEFAULT;
        t.Seikaku = t.SeikakuHosei = IsSpecificBoss(enc.Label) ? GetRandomNature(t.DevId, (byte)t.FormId) : SeikakuType.DEFAULT;

        t.RareType = IsSpecificBoss(enc.Label) ? RollForRandomShiny() : RareType.DEFAULT;
        t.WazaType = WazaType.DEFAULT;
        t.Waza1 = GetCleanMoveSlot();
        t.Waza2 = GetCleanMoveSlot();
        t.Waza3 = GetCleanMoveSlot();
        t.Waza4 = GetCleanMoveSlot();
        t.Tokusei = IsSpecificBoss(enc.Label) ? GetRandomAbilitySlot() : GetRandomAbilitySetter();
        t.DropItem = GetRandomItem();
        t.DropItemNum = 1;

        if (MODIFY_POKEMON_LEVELS)
            t.Level = GetModifiedLevel(t.Level);

        if ((FORCE_FULLY_EVOLVE && t.Level >= FORCE_FULLY_EVOLVE_LEVEL) || (FORCE_FULLY_EVOLVE_TITAN_POKEMON && IsTitan(enc.Label)))
            t = EvolveStaticEncounter(t, IsSpecificBoss(enc.Label));

        // re-randomize if the encounter requires it
        while (ShouldRandomizeEncounterAgain(enc.Label, t.DevId, t.FormId, t.Level, Types) && ctr < 100)
        {
            ctr++;
            if (TYPE_THEMED_IMPORTANT_TRAINERS && IsTypeThemedEncounter(enc.Label))
            {
                var type = GetAppropriateType(enc.Label);
                (DevID Species, short Form) pk = GetTypeThemedPokemon(type);
                t.DevId = pk.Species;
                t.FormId = pk.Form;

                if ((FORCE_FULLY_EVOLVE && t.Level >= FORCE_FULLY_EVOLVE_LEVEL) || (FORCE_FULLY_EVOLVE_TITAN_POKEMON && IsTitan(enc.Label)))
                    t = EvolveStaticEncounter(t, IsSpecificBoss(enc.Label));

                Types = GetPokemonTypes((ushort)t.DevId, (byte)t.FormId); // refresh
            }

            else
            {
                t.DevId = GetRandomSpecies(t.DevId);
                t.FormId = GetRandomForm(t.DevId);

                if ((FORCE_FULLY_EVOLVE && t.Level >= FORCE_FULLY_EVOLVE_LEVEL) || (FORCE_FULLY_EVOLVE_TITAN_POKEMON && IsTitan(enc.Label)))
                    t = EvolveStaticEncounter(t, IsSpecificBoss(enc.Label));

                Types = GetPokemonTypes((ushort)t.DevId, (byte)t.FormId); // refresh
            }
        }

        // keep Area Zero Underdepths Stellars as Stellar, handle Ogerpon edge case, otherwise, pick fitting Tera Type
        t.GemType = t.GemType is GemType.NIJI ? GemType.NIJI : enc.Label switch
        {
            "SDC01_kamenoni_1" or "SDC01_kamenoni_1_strong" => GemType.HONOO,
            "SDC01_kamenoni_2" or "SDC01_kamenoni_2_strong" => GemType.MIZU,
            "SDC01_kamenoni_3" or "SDC01_kamenoni_3_strong" => GemType.IWA,
            "SDC01_kamenoni_4" or "SDC01_kamenoni_4_strong" => GemType.KUSA,
            _ => GetTeraType(t.DevId, (byte)t.FormId, t.GemType, false, IsSpecificBoss(enc.Label)),
        };

        if (GIVE_POKEMON_RANDOM_RIBBONS)
            t.SetRibbon = GetRandomRibbon();

        if (GIVE_POKEMON_HELD_ITEMS)
            t.Item = GetRandomItem();

        if (enc.Label is "sub018_BIGUANA")
            t = ApplyParadiseSoftlockPrevention(t);
        if (enc.Label is "SDC02_0330_kodaikame")
            t = ApplyTerapagosSoftlockPrevention(t);

        if (IsFormHeldItemDependent(t.DevId))
            t.Item = GetFormSpecificItem(t.DevId, t.FormId);

        NewSpecies.Add(enc);
    }

    obj = StandardizeStaticEncounters(obj);
    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);

    List<DevID> BossBattles = [ table[044].PokeData.DevId, table[036].PokeData.DevId, table[042].PokeData.DevId, table[037].PokeData.DevId, table[034].PokeData.DevId, table[048].PokeData.DevId, table[115].PokeData.DevId, table[116].PokeData.DevId ];
    UpdateBossText(BossBattles);
    UpdateAllStaticScenes(obj);
    UpdateAllKitakamiScenes(obj);

    var count = table.Count - Blacklist.Count;
    for (int i = 0; i < count; i++)
    {
        var pair = string.Empty;
        if (LogDuplicates.Contains(OldSpecies[i].Label))
            continue;

        var o = OldSpecies[i].PokeData;
        var n = NewSpecies[i].PokeData;
        var oForm = o.FormId is 0 ? string.Empty : $"-{o.FormId}";
        var nForm = n.FormId is 0 ? string.Empty : $"-{n.FormId}";
        var nRare = n.RareType is RareType.RARE ? " ★" : string.Empty;

        if (ExclusiveDuplicates.Contains(OldSpecies[i].Label))
        {
            var index = GetOppositeVersionEncounterIndex(OldSpecies[i].Label);
            pair = $" / {StringsNamesSpecies[index]}";
        }

        RandomizerLog.Add($"{StringsNamesSpecies[(int)o.DevId]}{oForm}{pair} -> {StringsNamesSpecies[(int)n.DevId]}{nForm}{nRare}");
    }

    RandomizerLog.Add("");
}

private static PokeDataEventBattle EvolveStaticEncounter(PokeDataEventBattle t, bool specific)
{
    (DevID Species, short Form) pk = ForceEvolveSpeciesFully((ushort)t.DevId, (byte)t.FormId);
    t.DevId = pk.Species;
    t.FormId = pk.Form;
    t.Sex = specific || IsGenderVariant((ushort)t.DevId, (byte)t.FormId) ? GetRandomGender(t.DevId, t.FormId) : SexType.DEFAULT; // refresh

    // refresh Toxtricity nature
    if (t.DevId is DevID.DEV_SUTORINDAA && specific)
        t.Seikaku = GetRandomNature(t.DevId, (byte)t.FormId);

    return t;
}

private static List<EventBattlePokemon> GetOldStaticEncounters()
{
    List<EventBattlePokemon> StaticEncounters = new();
    List<string> Blacklist = new();

    const string file = "eventBattlePokemon_array.bin";
    var path = Path.Combine(PATH_MASTER, "world/data/battle/eventBattlePokemon/");
    var obj = FlatBufferConverter.DeserializeFrom<EventBattlePokemonArray>(Path.Combine(path, file));

    if (!RANDOMIZE_KORAIDON_MIRAIDON)
    {
        Blacklist.Add(obj.Table[031].Label);
        Blacklist.Add(obj.Table[032].Label);
        Blacklist.Add(obj.Table[115].Label);
        Blacklist.Add(obj.Table[116].Label);
    }

    foreach (var t in obj.Table.Where(z => !Blacklist.Contains(z.Label)))
        StaticEncounters.Add(t);

    return StaticEncounters;
}

private static int GetOppositeVersionEncounterIndex(string id) => id switch
{
    "1075_multi_a_01" => 0992, // Iron Bundle
    "1095_multi_a_02" => 0986, // Iron Treads
    "1180_multi_a_01" => 0986, // Iron Treads
    "1180_multi_a_02" => 0989, // Iron Hands
    "1180_multi_a_03" => 0990, // Iron Jugulis
    "nusi_978_01" => 0986, // Iron Treads
    "SDC02_area0_sunanokegawa" => 0991, // Iron Thorns
    "SDC02_sub_Aentei" => 1020, // Iron Boulder
    "SDC02_sub_Araikou" => 1019, // Iron Crown
    "sub018_AIGUANA" => 0999, // Miraidon
    _ => throw new ArgumentException($"Invalid encounter: {id}"),
};

private static void RandomizeGiftEncounters()
{
    RandomizerLog.Add("== GIFT ENCOUNTERS ==");
    "Randomizing gift encounters...".Dump();

    const string file = "eventAddPokemon_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/event/event_add_pokemon/eventAddPokemon/");
    var path = Path.Combine(PATH_MASTER, "world/data/event/event_add_pokemon/eventAddPokemon/");
    var obj = FlatBufferConverter.DeserializeFrom<EventAddPokemonArray>(Path.Combine(path, file));
    List<EventAddPokemon> OldSpecies = GetOldGiftEncounters();
    List<EventAddPokemon> NewSpecies = new();
    List<string> Blacklist = [ "add_0195", "add_0193" ];
    bool IsStarter(string id) => id is "common_0065_hono" or "common_0065_kusa" or "common_0065_mizu";
    bool IsSceneEncounter(string id) => IsStarter(id) || id is "s1_side02_0060_hisuiGardie";

    if (!RANDOMIZE_KORAIDON_MIRAIDON)
    {
        Blacklist.Add(obj.Table[005].Label);
        Blacklist.Add(obj.Table[006].Label);
    }

    foreach (var enc in obj.Table.Where(z => !Blacklist.Contains(z.Label)))
    {
        var t = enc.PokeData;

        if (RANDOMIZE_POKEMON_TO_HAVE_SIMILAR_STRENGTH)
        {
            (DevID Species, short Form) pk = GetSimilarStrengthPokemon((ushort)t.DevId, (byte)t.FormId);
            t.DevId = pk.Species;
            t.FormId = pk.Form;
        }

        else
        {
            t.DevId = GetRandomSpecies(t.DevId);
            t.FormId = GetRandomForm(t.DevId);
        }

        // keep rolling for a starter with two evolutions
        while (ENSURE_STARTERS_HAVE_TWO_EVOLUTIONS && IsStarter(enc.Label) && !GetIsThreeStageFamily(GetNationalDexID((ushort)t.DevId)))
        {
            t.DevId = GetRandomSpecies(0);
            t.FormId = GetRandomForm(t.DevId);
        }

        // get another random species to avoid softlock
        while (GetIsInvalidScenePokemon((ushort)t.DevId) && IsSceneEncounter(enc.Label))
        {
            t.DevId = GetRandomSpecies(0);
            t.FormId = GetRandomForm(t.DevId);
        }

        t.RareType = t.RareType is RareType.RARE ? RareType.RARE : RareType.DEFAULT;
        t.Sex = IsGenderVariant((ushort)t.DevId, t.FormId) ? GetRandomGender(t.DevId, t.FormId) : SexType.DEFAULT;
        t.Tokusei = GetRandomAbilitySetter();

        // special handling for forced starter Pokémon
        if (FORCE_STARTER_GRASS_SPECIES is not 0 && enc.Label is "common_0065_kusa")
            t = ForceStarterByCriteria(t, FORCE_STARTER_GRASS_SPECIES, FORCE_STARTER_GRASS_FORM, FORCE_STARTER_GRASS_GENDER, FORCE_STARTER_GRASS_ABILITY, FORCE_STARTER_GRASS_SHINY);

        if (FORCE_STARTER_FIRE_SPECIES is not 0 && enc.Label is "common_0065_hono")
            t = ForceStarterByCriteria(t, FORCE_STARTER_FIRE_SPECIES, FORCE_STARTER_FIRE_FORM, FORCE_STARTER_FIRE_GENDER, FORCE_STARTER_FIRE_ABILITY, FORCE_STARTER_FIRE_SHINY);

        if (FORCE_STARTER_WATER_SPECIES is not 0 && enc.Label is "common_0065_mizu")
            t = ForceStarterByCriteria(t, FORCE_STARTER_WATER_SPECIES, FORCE_STARTER_WATER_FORM, FORCE_STARTER_WATER_GENDER, FORCE_STARTER_WATER_ABILITY, FORCE_STARTER_WATER_SHINY);

        t.Seikaku = t.SeikakuHosei = SeikakuType.DEFAULT;
        t.WazaType = WazaType.DEFAULT;
        t.Waza1 = GetCleanMoveSlot();
        t.Waza2 = GetCleanMoveSlot();
        t.Waza3 = GetCleanMoveSlot();
        t.Waza4 = GetCleanMoveSlot();
        t.GemType = GetTeraType(t.DevId, (byte)t.FormId, t.GemType, false, false);

        if (GIVE_POKEMON_RANDOM_POKE_BALLS)
            t.BallId = GetRandomBall();

        if (GIVE_POKEMON_RANDOM_RIBBONS)
            t.SetRibbon = GetRandomRibbon();

        if (MODIFY_POKEMON_LEVELS)
            t.Level = GetModifiedLevel(t.Level);

        if (GIVE_POKEMON_HELD_ITEMS)
            t.Item = GetRandomItem();

        if (IsFormHeldItemDependent(t.DevId))
            t.Item = GetFormSpecificItem(t.DevId, t.FormId);

        NewSpecies.Add(enc);
    }

    if (RANDOMIZE_KORAIDON_MIRAIDON)
    {
        var statics = Path.Combine(DEST_MASTER, "world/data/battle/eventBattlePokemon/eventBattlePokemon_array.bin");
        var objS = FlatBufferConverter.DeserializeFrom<EventBattlePokemonArray>(statics);
        obj = StandardizeGiftEncounters(obj, objS);
        UpdateAllIguanaScenes(obj);
    }

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
    UpdateAllGiftScenes(obj);

    var count = obj.Table.Count - Blacklist.Count;
    for (int i = 0; i < count; i++)
    {
        var pair = string.Empty;
        var o = OldSpecies[i].PokeData;
        var n = NewSpecies[i].PokeData;
        var oForm = o.FormId is 0 ? string.Empty : $"-{o.FormId}";
        var nForm = n.FormId is 0 ? string.Empty : $"-{n.FormId}";
        var oRare = o.RareType is RareType.RARE ? " ★" : string.Empty;
        var nRare = n.RareType is RareType.RARE ? " ★" : string.Empty;

        if (OldSpecies[i].Label is "gameclear_BIGUANA")
            continue;
        if (OldSpecies[i].Label is "gameclear_AIGUANA")
            pair = $" / {StringsNamesSpecies[0999]}";

        RandomizerLog.Add($"{StringsNamesSpecies[(int)o.DevId]}{oForm}{oRare}{pair} -> {StringsNamesSpecies[(int)n.DevId]}{nForm}{nRare}");
    }

    RandomizerLog.Add("");
}

private static List<EventAddPokemon> GetOldGiftEncounters()
{
    List<EventAddPokemon> GiftEncounters = new();
    List<string> Blacklist = [ "add_0195", "add_0193" ];
    const string file = "eventAddPokemon_array.bin";
    var path = Path.Combine(PATH_MASTER, "world/data/event/event_add_pokemon/eventAddPokemon/");
    var obj = FlatBufferConverter.DeserializeFrom<EventAddPokemonArray>(Path.Combine(path, file));

    if (!RANDOMIZE_KORAIDON_MIRAIDON)
    {
        Blacklist.Add(obj.Table[005].Label);
        Blacklist.Add(obj.Table[006].Label);
    }

    foreach (var t in obj.Table.Where(z => !Blacklist.Contains(z.Label)))
        GiftEncounters.Add(t);

    return GiftEncounters;
}

private static PokeDataFull ForceStarterByCriteria(PokeDataFull pk, ushort species, sbyte form, sbyte sex, sbyte ability, bool shiny)
{
    var dev = (DevID)GetDevID(species);
    var temp = form is -1 ? 0 : form;

    // if any user input is invalid, do not attempt to force the starter
    if (species > 1025)
        return pk;
    if (form >= GetFormCount((ushort)dev))
        return pk;
    if (form < -1)
        return pk;
    if (sex is not (-1 or 1 or 2))
        return pk;
    if (ability is not (-1 or 1 or 2 or 3))
        return pk;

    // check for special cases that should not be permitted
    if (species is 1007 or 1008 && form > 0)
        return pk;
    if (species is 1017 && form is >= 4)
        return pk;
    if (GetIsInvalidScenePokemon(species))
        return pk;
    if (!GetIsPresentInGame((ushort)dev, (byte)temp))
        return pk;

    pk.DevId = dev;
    pk.FormId = form is -1 ? GetRandomForm(pk.DevId) : form;
    pk.Sex = sex is -1 ? pk.Sex : (SexType)sex;
    pk.Tokusei = ability is -1 ? pk.Tokusei : (TokuseiType)(ability + 1);
    pk.RareType = shiny ? RareType.RARE : RareType.DEFAULT;

    return pk;
}

private static void RandomizeTrainerPokemon()
{
    "Randomizing trainer Pokémon...".Dump();

    const string file = "trdata_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/trainer/trdata/");
    var path = Path.Combine(PATH_MASTER, "world/data/trainer/trdata/");
    var obj = FlatBufferConverter.DeserializeFrom<TrDataMainArray>(Path.Combine(path, file));

    List<string> IncreasedCountBlacklist =
    [
        "brother_02_02",
        "pepper_00",
        "pepper_nusi_01",
        "pepper_nusi_02",
        "pepper_nusi_03",
        "pepper_nusi_04",
        "pepper_nusi_05",
        "professor_A_02",
        "professor_B_02",
        "rival_01_hono",
        "rival_01_kusa",
        "rival_01_mizu",
        "sister_02_02",
    ];

    bool IsNemona(string id) => id.Contains("rival_") || id.Contains("nemo_");
    bool IsClavell(string id) => id.Contains("clavel_") || id.Contains("claver_");
    bool IsValidDoubleBattle(string id) => !id.StartsWith("rival_01") && !id.EndsWith("_boss_01") && !id.Contains("_multi") && !id.StartsWith("pepper_nusi_") && !id.StartsWith("professor_") && !id.Contains("raid_assist_NPC_") && id is not ("pepper_00" or "brother_02_02");

    foreach (var t in obj.Table)
    {
        List<PokeDataBattle> Team = [ t.Poke1, t.Poke2, t.Poke3, t.Poke4, t.Poke5, t.Poke6 ];
        List<DevID> Used = new();
        var count = Team.Count(z => z.DevId is not DevID.DEV_NULL);

        // add specific number of Pokémon to party
        if (ADD_NUMBER_OF_POKEMON_TO_IMPORTANT_TRAINERS is not 0 && ImportantTrainers.Contains(t.TrId) && count is not 6)
        {
            var sel = Team.FirstOrDefault(z => z.DevId is DevID.DEV_NULL);
            var last = Team.IndexOf(sel);
            var add = Math.Clamp((int)ADD_NUMBER_OF_POKEMON_TO_IMPORTANT_TRAINERS, 1, 5);

            for (int i = 0; i < ADD_NUMBER_OF_POKEMON_TO_IMPORTANT_TRAINERS; i++)
            {
                // they now have a full party
                if (Team[5].DevId is not DevID.DEV_NULL)
                    continue;

                Team[last] = GetBlankPokemon(Team[0].Level, Team[0].BallId, Team[0].TalentValue, Team[0].EffortValue);
                last++;
            }
        }

        // add Pokémon to party to meet required minimum
        if (count < TRAINER_MINIMUM_POKEMON_COUNT && !IncreasedCountBlacklist.Contains(t.TrId) && !t.TrId.Contains("raid_assist_NPC_"))
        {
            var min = Math.Clamp((int)TRAINER_MINIMUM_POKEMON_COUNT, 1, 6);
            if (min is 6 && t.TrId.EndsWith("_boss_01"))
                min = 5;

            for (int i = 0; i < min; i++)
                Team[i] = GetBlankPokemon(Team[0].Level, Team[0].BallId, Team[0].TalentValue, Team[0].EffortValue);
        }

        // remove Pokémon from party to meet required maximum
        if (count > TRAINER_MAXIMUM_POKEMON_COUNT)
        {
            var max = Math.Clamp((int)TRAINER_MAXIMUM_POKEMON_COUNT, 1, 6);
            for (int i = max; i < 6; i++)
                Team[i].DevId = DevID.DEV_NULL;
        }

        // turn all battles into double battles, with some exceptions
        if (MAKE_ALL_TRAINER_BATTLES_DOUBLE_BATTLES && IsValidDoubleBattle(t.TrId))
        {
            if ((count & 1) is not 0)
                Team[count] = GetBlankPokemon(Team[0].Level, Team[0].BallId, Team[0].TalentValue, Team[0].EffortValue);

            t.BattleType = BattleType.DOUBLE;
            t.AiDouble = true;
        }

        if (GIVE_IMPORTANT_TRAINERS_SIX_POKEMON && ImportantTrainers.Contains(t.TrId))
        {
            // fill empty slots with a placeholder
            var max = t.TrId.EndsWith("_boss_01") ? 5 : 6;
            for (int i = 0; i < max; i++)
            {
                if (Team[i].DevId is not DevID.DEV_NULL)
                    continue;
                Team[i] = GetBlankPokemon(Team[0].Level, Team[0].BallId, Team[0].TalentValue, Team[0].EffortValue);
            }
        }

        if (ALLOW_ALL_TRAINERS_TO_TERASTALLIZE && !t.TrId.StartsWith("pepper_nusi_"))
            t.ChangeGem = true;

        // refresh team data before randomizing, and sort by level ascending, with empty slots at the end
        Team = Team.OrderBy(z => z.Level is 0).ThenBy(z => z.Level).ToList();
        t.Poke1 = Team[0];
        t.Poke2 = Team[1];
        t.Poke3 = Team[2];
        t.Poke4 = Team[3];
        t.Poke5 = Team[4];
        t.Poke6 = Team[5];

        foreach (var pk in Team.Where(z => z.DevId is not DevID.DEV_NULL))
        {
            if (!RANDOMIZE_KORAIDON_MIRAIDON && t.TrId is "professor_A_02" or "professor_B_02")
                continue;
            if (TYPE_THEMED_IMPORTANT_TRAINERS && TypeThemedTrainers.Any(z => z.Contains(t.TrId)))
                continue;
            RandomizeTrainerPokemonSingle(pk, t.ChangeGem, false);

            // ensure all teams consist of unique species
            while (Used.Contains(pk.DevId))
                RandomizeTrainerPokemonSingle(pk, t.ChangeGem, true);

            Used.Add(pk.DevId);
        }

        // if AI Sada/Turo's final Pokémon is a Paradox Pokémon and it has Protosynthesis or Quark Drive, give it a Booster Energy, true to the vanilla game
        if (FORCE_SWAP_SPECIES_PARADOX && ALLOW_SPECIES_GEN_9 && ALLOW_SPECIES_PARADOX && !RANDOMIZE_PERSONAL_ABILITY && t.TrId is "professor_A_01" or "professor_B_01")
        {
            var sel = Team.FirstOrDefault(z => z.DevId is DevID.DEV_NULL);
            var last = sel is null ? 5 : Team.IndexOf(sel) - 1;
            Team[last].Item = ItemID.ITEMID_BUUSUTOENAJII;
        }

        if (RIVAL_CARRIES_STARTER_THROUGHOUT_GAME)
            obj = ForceStarterSelectionsOnRivals(obj);

        if (GIVE_TRAINERS_MAXIMUM_AI)
            t.AiBasic = t.AiHigh = t.AiExpert = t.AiChange = true;
    }

    if (TYPE_THEMED_IMPORTANT_TRAINERS)
    {
        foreach (var set in TypeThemedTrainers)
        {
            var idx = Array.IndexOf(TypeThemedTrainers, set);
            var type = idx switch
            {
                >= 0 and <= 12 => ThemedTypes[idx], // Gym Leaders, Team Star
                13 => ThemedTypes[16], // Rika
                14 => ThemedTypes[15], // Poppy
                15 => ThemedTypes[14], // Larry
                16 => ThemedTypes[17], // Hassel
                17 => ThemedTypes[13], // Tyme
                18 => ThemedTypes[12], // Dendra
                19 => ThemedTypes[09], // Crispin
                20 => ThemedTypes[15], // Amarys
                21 => ThemedTypes[11], // Lacey
                22 => ThemedTypes[17], // Drayton
                _ => throw new ArgumentException($"Invalid index: {idx}"),
            };

            foreach (var t in set)
            {
                var tr = obj.Table.FirstOrDefault(z => z.TrId == t);
                List<PokeDataBattle> Team = [ tr.Poke1, tr.Poke2, tr.Poke3, tr.Poke4, tr.Poke5, tr.Poke6 ];
                var sel = Team.FirstOrDefault(z => z.DevId is DevID.DEV_NULL);
                var last = sel is null ? 5 : Team.IndexOf(sel) - 1;
                List<DevID> Used = new();

                foreach (var pk in Team.Where(z => z.DevId is not DevID.DEV_NULL))
                {
                    bool tera = tr.ChangeGem && Team.IndexOf(pk) == last;
                    RandomizeTrainerPokemonTypeThemed(pk, type, tera, false);

                    // ensure all teams consist of unique species
                    while (Used.Contains(pk.DevId))
                        RandomizeTrainerPokemonTypeThemed(pk, type, tera, true);

                    // ensure Pokémon that are forced to evolve still abide by the themed type
                    (byte Type1, byte Type2) Types = GetPokemonTypes((ushort)pk.DevId, (byte)pk.FormId);
                    while (!tera && Types.Type1 != type && Types.Type2 != type)
                    {
                        RandomizeTrainerPokemonTypeThemed(pk, type, tera, true);
                        Types = GetPokemonTypes((ushort)pk.DevId, (byte)pk.FormId); // refresh
                    }

                    Used.Add(pk.DevId);
                }
            }
        }
    }

    var statics = Path.Combine(DEST_MASTER, "world/data/battle/eventBattlePokemon/eventBattlePokemon_array.bin");
    var objS = FlatBufferConverter.DeserializeFrom<EventBattlePokemonArray>(statics);
    obj = StandardizeTrainerData(obj, objS);
    WriteTrainerChangesToLog(obj);

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
    UpdateAllTrainerScenes(obj);
}

private static PokeDataBattle GetBlankPokemon(int level, BallType ball, ParamSet talent, ParamSet effort)
{
    PokeDataBattle pk = new()
    {
        DevId = DevID.DEV_HUSIGIDANE,
        Level = level,
        BallId = ball,
        Waza1 = GetCleanMoveSlot(),
        Waza2 = GetCleanMoveSlot(),
        Waza3 = GetCleanMoveSlot(),
        Waza4 = GetCleanMoveSlot(),
        TalentValue = talent,
        EffortValue = effort,
        ScaleType = SizeType.VALUE,
        ScaleValue = 128,
    };

    return pk;
}

private static PokeDataBattle RandomizeTrainerPokemonSingle(PokeDataBattle pk, bool tera, bool repeat)
{
    if (RANDOMIZE_POKEMON_TO_HAVE_SIMILAR_STRENGTH)
    {
        (DevID Species, short Form) ss = GetSimilarStrengthPokemon((ushort)pk.DevId, (byte)pk.FormId);
        pk.DevId = ss.Species;
        pk.FormId = ss.Form;
    }

    else
    {
        pk.DevId = GetRandomSpecies(pk.DevId);
        pk.FormId = GetRandomForm(pk.DevId);
    }

    pk.Sex = GetRandomGender(pk.DevId, pk.FormId);
    pk.WazaType = WazaType.DEFAULT;
    pk.Waza1 = GetCleanMoveSlot();
    pk.Waza2 = GetCleanMoveSlot();
    pk.Waza3 = GetCleanMoveSlot();
    pk.Waza4 = GetCleanMoveSlot();
    pk.GemType = tera ? GetTeraType(pk.DevId, (byte)pk.FormId, pk.GemType, false, true) : GemType.DEFAULT;
    pk.Seikaku = GetRandomNature(pk.DevId, (byte)pk.FormId);
    pk.Tokusei = GetRandomAbilitySlot();
    pk.RareType = GetRandom(100) < TRAINER_POKEMON_SHINY_CHANCE_PERCENT ? RareType.RARE : RareType.NO_RARE;

    if (GIVE_POKEMON_RANDOM_POKE_BALLS)
        pk.BallId = GetRandomBall();

    if (MODIFY_POKEMON_LEVELS && !repeat)
        pk.Level = GetModifiedLevel(pk.Level);

    if (GIVE_POKEMON_HELD_ITEMS)
        pk.Item = GetRandomItem();

    if (GIVE_TRAINERS_MAX_INDIVIDUAL_VALUES)
    {
        pk.TalentType = TalentType.VALUE;
        pk.TalentValue = new ParamSet { HP = 31, ATK = 31, DEF = 31, SPA = 31, SPD = 31, SPE = 31 };
    }

    if (FORCE_FULLY_EVOLVE && pk.Level >= FORCE_FULLY_EVOLVE_LEVEL)
    {
        (DevID Species, short Form) ss = ForceEvolveSpeciesFully((ushort)pk.DevId, (byte)pk.FormId);
        pk.DevId = ss.Species;
        pk.FormId = ss.Form;
        pk.Sex = GetRandomGender(pk.DevId, pk.FormId); // refresh

        // refresh Toxtricity nature
        if (pk.DevId is DevID.DEV_SUTORINDAA)
            pk.Seikaku = GetRandomNature(pk.DevId, (byte)pk.FormId);
    }

    if (GIVE_TRAINERS_SMART_EFFORT_VALUES)
        pk.EffortValue = SetSmartEVs((ushort)pk.DevId, pk.FormId);

    if (IsFormHeldItemDependent(pk.DevId))
        pk.Item = GetFormSpecificItem(pk.DevId, pk.FormId);

    return pk;
}

private static PokeDataBattle RandomizeTrainerPokemonTypeThemed(PokeDataBattle pk, byte type, bool tera, bool repeat)
{
    // Tera Type for ace will match themed type, but the original types will not, just like base game
    if (tera)
    {
        pk.DevId = GetRandomSpecies(pk.DevId);
        pk.FormId = GetRandomForm(pk.DevId);

        // ensure the original types do not match the themed type
        (byte Type1, byte Type2) Types = GetPokemonTypes((ushort)pk.DevId, (byte)pk.FormId);
        while (Types.Type1 == type || Types.Type2 == type)
        {
            pk.DevId = GetRandomSpecies(pk.DevId);
            pk.FormId = GetRandomForm(pk.DevId);
            Types = GetPokemonTypes((ushort)pk.DevId, (byte)pk.FormId); // refresh
        }

        pk.GemType = (GemType)(type + 2);
    }

    else
    {
        (DevID Species, short Form) Pokemon = GetTypeThemedPokemon(type);
        pk.DevId = Pokemon.Species;
        pk.FormId = Pokemon.Form;
        pk.GemType = GemType.DEFAULT;
    }

    pk.Sex = GetRandomGender(pk.DevId, pk.FormId);
    pk.WazaType = WazaType.DEFAULT;
    pk.Waza1 = GetCleanMoveSlot();
    pk.Waza2 = GetCleanMoveSlot();
    pk.Waza3 = GetCleanMoveSlot();
    pk.Waza4 = GetCleanMoveSlot();
    pk.Seikaku = GetRandomNature(pk.DevId, (byte)pk.FormId);
    pk.Tokusei = GetRandomAbilitySlot();
    pk.RareType = GetRandom(100) < TRAINER_POKEMON_SHINY_CHANCE_PERCENT ? RareType.RARE : RareType.NO_RARE;

    if (GIVE_POKEMON_RANDOM_POKE_BALLS)
        pk.BallId = GetRandomBall();

    if (MODIFY_POKEMON_LEVELS && !repeat)
        pk.Level = GetModifiedLevel(pk.Level);

    if (GIVE_POKEMON_HELD_ITEMS)
        pk.Item = GetRandomItem();

    if (GIVE_TRAINERS_MAX_INDIVIDUAL_VALUES)
    {
        pk.TalentType = TalentType.VALUE;
        pk.TalentValue = new ParamSet { HP = 31, ATK = 31, DEF = 31, SPA = 31, SPD = 31, SPE = 31 };
    }

    if (FORCE_FULLY_EVOLVE && pk.Level >= FORCE_FULLY_EVOLVE_LEVEL)
    {
        (DevID Species, short Form) ss = ForceEvolveSpeciesFully((ushort)pk.DevId, (byte)pk.FormId);
        pk.DevId = ss.Species;
        pk.FormId = ss.Form;
        pk.Sex = GetRandomGender(pk.DevId, pk.FormId); // refresh

        // refresh Toxtricity nature
        if (pk.DevId is DevID.DEV_SUTORINDAA)
            pk.Seikaku = GetRandomNature(pk.DevId, (byte)pk.FormId);
    }

    if (GIVE_TRAINERS_SMART_EFFORT_VALUES)
        pk.EffortValue = SetSmartEVs((ushort)pk.DevId, pk.FormId);

    if (IsFormHeldItemDependent(pk.DevId))
        pk.Item = GetFormSpecificItem(pk.DevId, pk.FormId);

    return pk;
}

private static TrDataMainArray ForceStarterSelectionsOnRivals(TrDataMainArray obj)
{
    var gifts = Path.Combine(DEST_MASTER, "world/data/event/event_add_pokemon/eventAddPokemon/eventAddPokemon_array.bin");
    var objG = FlatBufferConverter.DeserializeFrom<EventAddPokemonArray>(gifts);

    List<SexType> Genders = new();
    List<GemType> TeraTypes = new();
    List<SeikakuType> Natures = new();
    List<TokuseiType> Abilities = new();

    // convert from random to a fixed selection
    for (int i = 0; i <= 2; i++)
    {
        var pk = objG.Table[i].PokeData;
        Genders.Add(GetRandomGender(pk.DevId, (byte)pk.FormId));
        TeraTypes.Add(GetTeraType(pk.DevId, (byte)pk.FormId, pk.GemType, false, true));
        Natures.Add(GetRandomNature(pk.DevId, (byte)pk.FormId));
        Abilities.Add(GetRandomAbilitySlot());
    }

    // initialize
    var G = objG.Table[001].PokeData;
    var F = objG.Table[000].PokeData;
    var W = objG.Table[002].PokeData;

    var StarterG1 = new PokeDataFull { DevId = G.DevId, FormId = G.FormId, Sex = Genders[1], Item = G.Item, BallId = G.BallId, GemType = TeraTypes[1], Seikaku = Natures[1], Tokusei = FORCE_STARTER_GRASS_ABILITY is -1 ? Abilities[1] : (TokuseiType)(FORCE_STARTER_GRASS_ABILITY + 1), RareType = G.RareType };
    var StarterF1 = new PokeDataFull { DevId = F.DevId, FormId = F.FormId, Sex = Genders[0], Item = F.Item, BallId = F.BallId, GemType = TeraTypes[0], Seikaku = Natures[0], Tokusei = FORCE_STARTER_FIRE_ABILITY  is -1 ? Abilities[0] : (TokuseiType)(FORCE_STARTER_FIRE_ABILITY  + 1), RareType = F.RareType };
    var StarterW1 = new PokeDataFull { DevId = W.DevId, FormId = W.FormId, Sex = Genders[2], Item = W.Item, BallId = W.BallId, GemType = TeraTypes[2], Seikaku = Natures[2], Tokusei = FORCE_STARTER_WATER_ABILITY is -1 ? Abilities[2] : (TokuseiType)(FORCE_STARTER_WATER_ABILITY + 1), RareType = W.RareType };

    var StarterG2 = ForceEvolveSpeciesOnce(new PokeDataFull { DevId = StarterG1.DevId, FormId = StarterG1.FormId, Sex = StarterG1.Sex, Item = StarterG1.Item, BallId = StarterG1.BallId, GemType = StarterG1.GemType, Seikaku = StarterG1.Seikaku, Tokusei = StarterG1.Tokusei, RareType = StarterG1.RareType });
    var StarterF2 = ForceEvolveSpeciesOnce(new PokeDataFull { DevId = StarterF1.DevId, FormId = StarterF1.FormId, Sex = StarterF1.Sex, Item = StarterF1.Item, BallId = StarterF1.BallId, GemType = StarterF1.GemType, Seikaku = StarterF1.Seikaku, Tokusei = StarterF1.Tokusei, RareType = StarterF1.RareType });
    var StarterW2 = ForceEvolveSpeciesOnce(new PokeDataFull { DevId = StarterW1.DevId, FormId = StarterW1.FormId, Sex = StarterW1.Sex, Item = StarterW1.Item, BallId = StarterW1.BallId, GemType = StarterW1.GemType, Seikaku = StarterW1.Seikaku, Tokusei = StarterW1.Tokusei, RareType = StarterW1.RareType });

    var StarterG3 = ForceEvolveSpeciesOnce(new PokeDataFull { DevId = StarterG2.DevId, FormId = StarterG2.FormId, Sex = StarterG2.Sex, Item = StarterG2.Item, BallId = StarterG2.BallId, GemType = StarterG2.GemType, Seikaku = StarterG2.Seikaku, Tokusei = StarterG2.Tokusei, RareType = StarterG2.RareType });
    var StarterF3 = ForceEvolveSpeciesOnce(new PokeDataFull { DevId = StarterF2.DevId, FormId = StarterF2.FormId, Sex = StarterF2.Sex, Item = StarterF2.Item, BallId = StarterF2.BallId, GemType = StarterF2.GemType, Seikaku = StarterF2.Seikaku, Tokusei = StarterF2.Tokusei, RareType = StarterF2.RareType });
    var StarterW3 = ForceEvolveSpeciesOnce(new PokeDataFull { DevId = StarterW2.DevId, FormId = StarterW2.FormId, Sex = StarterW2.Sex, Item = StarterW2.Item, BallId = StarterW2.BallId, GemType = StarterW2.GemType, Seikaku = StarterW2.Seikaku, Tokusei = StarterW2.Tokusei, RareType = StarterW2.RareType });

    bool IsNemona(string id) => id.Contains("rival_") || id.Contains("nemo_");
    bool IsClavell(string id) => id.Contains("clavel_") || id.Contains("claver_");
    foreach (var t in obj.Table.Where(z => IsNemona(z.TrId) || IsClavell(z.TrId)))
    {
        // unused rival battles
        if (!t.TrId.Contains("kusa") && !t.TrId.Contains("hono") && !t.TrId.Contains("mizu"))
            continue;

        List<PokeDataBattle> Team = [ t.Poke1, t.Poke2, t.Poke3, t.Poke4, t.Poke5, t.Poke6 ];
        var ace = Team.IndexOf(Team.OrderBy(z => z.Level).Last());
        var index = !t.TrId.Contains("rival_02_01") && t.TrId.StartsWith("rival_02_") ? 0 : ace; // starter is always last slot, except for Nemona's Terastal tutorial battle
        bool IsNemonaStage1(string id) => IsNemona(id) && !id.Contains("rival_02_01") && (id.Contains("rival_01_") || id.Contains("rival_02_") || id.Contains("rival_03_"));
        bool IsNemonaStage2(string id) => IsNemona(id) && id.Contains("rival_04_");

        PokeDataFull Starter = t.TrId switch
        {
            // Clavell
            _ when IsClavell(t.TrId) && t.TrId.Contains("kusa") => StarterF3,
            _ when IsClavell(t.TrId) && t.TrId.Contains("hono") => StarterW3,
            _ when IsClavell(t.TrId) && t.TrId.Contains("mizu") => StarterG3,

            // Nemona (initial)
            _ when IsNemonaStage1(t.TrId) && t.TrId.Contains("kusa") => StarterW1,
            _ when IsNemonaStage1(t.TrId) && t.TrId.Contains("hono") => StarterG1,
            _ when IsNemonaStage1(t.TrId) && t.TrId.Contains("mizu") => StarterF1,

            // Nemona (first evolution)
            _ when IsNemonaStage2(t.TrId) && t.TrId.Contains("kusa") => StarterW2,
            _ when IsNemonaStage2(t.TrId) && t.TrId.Contains("hono") => StarterG2,
            _ when IsNemonaStage2(t.TrId) && t.TrId.Contains("mizu") => StarterF2,

            // Nemona (second evolution)
            _ when t.TrId.Contains("kusa") => StarterW3,
            _ when t.TrId.Contains("hono") => StarterG3,
            _ when t.TrId.Contains("mizu") => StarterF3,

            _ => new(),
        };

        Team[index].DevId    = Starter.DevId;
        Team[index].FormId   = Starter.FormId;
        Team[index].Sex      = Starter.Sex;
        Team[index].Item     = Starter.Item;
        Team[index].BallId   = Starter.BallId;
        Team[index].GemType  = Starter.GemType;
        Team[index].Seikaku  = Starter.Seikaku;
        Team[index].Tokusei  = Starter.Tokusei;
        Team[index].RareType = Starter.RareType is RareType.RARE ? RareType.RARE : RareType.NO_RARE;

        if (GIVE_TRAINERS_SMART_EFFORT_VALUES)
            Team[index].EffortValue = SetSmartEVs((ushort)Team[index].DevId, Team[index].FormId);
    }

    return obj;
}

private static ParamSet SetSmartEVs(ushort species, short form)
{
    var sel = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesInternal == species && z.Info.Form == form);
    List<byte> BaseStats = [ sel.Base.HP, sel.Base.ATK, sel.Base.DEF, sel.Base.SPA, sel.Base.SPD, sel.Base.SPE ];

    // get highest stat
    var index1 = GetIndexOfStat(BaseStats);
    BaseStats[index1] = 0;

    // get second-highest stat
    var index2 = GetIndexOfStat(BaseStats);
    BaseStats[index2] = 0;

    // get third-highest stat
    var index3 = GetIndexOfStat(BaseStats);
    BaseStats[index3] = 0;

    // generate list of EVs, then convert into ParamSet
    List<byte> EVs = [ 0, 0, 0, 0, 0, 0 ];
    EVs[index1] = 252;
    EVs[index2] = 252;
    EVs[index3] = 6;

    var spread = new ParamSet() { HP = EVs[0], ATK = EVs[1], DEF = EVs[2], SPA = EVs[3], SPD = EVs[4], SPE = EVs[5] };
    return spread;
}

private static int GetIndexOfStat(List<byte> BaseStats)
{
    var highest = BaseStats.Max();
    bool hasDuplicateStat = BaseStats.Where(z => z == highest).GroupBy(z => z).Any(z => z.Count() > 1);

    // if we have more than 1 stat with the same value, randomly select one of them, instead of defaulting to the first instance of it
    if (hasDuplicateStat)
    {
        List<int> DuplicateIndexes = new();
        for (int i = 0; i < 6; i++)
        {
            if (BaseStats[i] == highest)
                DuplicateIndexes.Add(i);
        }

        var rand = DuplicateIndexes[GetRandom(DuplicateIndexes.Count)];
        return rand;
    }

    return BaseStats.IndexOf(highest);
}

private static void RandomizeFieldItems()
{
    "Randomizing field items...".Dump();

    List<string> FieldItems =
    [
        "world/scene/parts/field/streaming_event/world_item_/world_item_0.trscn",                                    // Paldea
        "world/scene/parts/field/streaming_event/c01_item_/c01_item_0.trscn",                                        // Mesagoza
        "world/scene/parts/field/room/a_w23_field/a_w23_field_event/a_w23_field_item_/a_w23_field_item_0.trscn",     // Area Zero
        "world/scene/parts/field/room/a_w23_i21/a_w23_i21_event/a_w23_i21_item_/a_w23_i21_item_0.trscn",             // Zero Lab
        "world/scene/parts/field/streaming_event/su1_world_item_/su1_world_item_0.trscn",                            // Kitakami
        "world/scene/parts/field/streaming_event/su2_world_item_/su2_world_item_0.trscn",                            // Blueberry Academy
        "world/scene/parts/field/room/a_w23_d10/a_w23_d10_event/a_w23_d10_field_item_/a_w23_d10_field_item_0.trscn", // Area Zero Underdepths
    ];

    bool IsTM(ulong item) => TechnicalMachineList.Contains((int)item);
    bool IsTeraShard(ulong item) => item is (>= 1862 and <= 1879) or 2549;
    foreach (var t in FieldItems)
    {
        var index = t.LastIndexOf("/") + 1;
        var f = t[index..];
        var d = t[..index];

        var location = f switch
        {
            "world_item_0.trscn" => "PALDEA",
            "c01_item_0.trscn" => "MESAGOZA",
            "a_w23_field_item_0.trscn" => "AREA ZERO",
            "a_w23_i21_item_0.trscn" => "ZERO LAB",
            "su1_world_item_0.trscn" => "KITAKAMI",
            "su2_world_item_0.trscn" => "BLUEBERRY ACADEMY",
            "a_w23_d10_field_item_0.trscn" => "AREA ZERO UNDERDEPTHS",
            _ => throw new ArgumentException($"Invalid file: {f}"),
        };

        RandomizerLog.Add($"== FIELD ITEMS ({location}) ==");
        var file = Path.Combine(PATH_MASTER, d, f);
        var obj = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(file);
        foreach (var o in obj.Objects)
        {
            foreach (var s in o.SubObjects.Where(z => z.Type is "trinity_PropertySheet"))
            {
                var tps = FlatBufferConverter.DeserializeFrom<TrinityPropertySheet>(s.Data);
                foreach (var p in tps.Properties)
                {
                    foreach (var i in p.Fields.Where(z => z.Name is "itemInfo"))
                    {
                        bool IsModifyQuantity = false;
                        var item = i.Data.Item9.Fields.FirstOrDefault(z => z.Name is "itemNo").Data.TrinityPropertySheetField1;
                        var quantity = i.Data.Item9.Fields.FirstOrDefault(z => z.Name is "num").Data.TrinityPropertySheetField1;

                        bool IsBlueberryTeraShard = location is "BLUEBERRY ACADEMY" or "AREA ZERO UNDERDEPTHS" && IsTeraShard(item.Value);
                        if (IsBlueberryTeraShard) // do not randomize due to excessive quantity
                            continue;

                        var oItem = StringsNamesItem[item.Value];
                        item.Value = IsTM(item.Value) ? GetRandomTM() : (ulong)GetRandomItem();
                        var nItem = StringsNamesItem[item.Value];
                        IsModifyQuantity = IsTeraShard(item.Value);

                        var oQuantity = $"×{quantity.Value}";
                        if (IsModifyQuantity)
                            quantity.Value = GetRandomWeightedQuantity(); // give Tera Shards a special, weighted quantity
                        var nQuantity = $"×{quantity.Value}";

                        RandomizerLog.Add($"- {oItem} ({oQuantity}) -> {nItem} ({nQuantity})");
                    }
                }

                s.Data = FlatBufferConverter.SerializeFrom(tps);
            }
        }

        RandomizerLog.Add("");
        var data = FlatBufferConverter.SerializeFrom(obj);
        var dest = Path.Combine(DEST_MASTER, d);
        if (!Directory.Exists(dest))
            Directory.CreateDirectory(dest);
        File.WriteAllBytes(Path.Combine(dest, f), data); // Scarlet
        File.WriteAllBytes(Path.Combine(dest, f.Replace("_0.", "_1.")), data); // copy to Violet, since files are identical as-is
    }
}

private static ulong GetRandomWeightedQuantity() => GetRandom(100) switch
{
    <= 99 and >= 65 => 15, // 35%
    <= 64 and >= 40 => 20, // 25%
    <= 39 and >= 20 => 25, // 20%
    <= 19 and >= 10 => 30, // 10%
    <=  9 and >=  5 => 35, // 5%
    <=  4 and >=  1 => 40, // 4%
                  _ => 50, // 1% -- enough to change a Pokémon's Tera Type!
};

private static void RandomizeHiddenItems()
{
    "Randomizing hidden items...".Dump();

    List<string> HiddenItems =
    [
        "world/data/item/hiddenItemDataTable/hiddenItemDataTable_array.bin",
        "world/data/item/hiddenItemDataTable_lc/hiddenItemDataTable_lc_array.bin",
        "world/data/item/hiddenItemDataTable_su1/hiddenItemDataTable_su1_array.bin",
        "world/data/item/hiddenItemDataTable_su2/hiddenItemDataTable_su2_array.bin",
    ];

    foreach (var h in HiddenItems)
    {
        var index = h.LastIndexOf("/") + 1;
        var f = h[index..];
        var d = h[..index];

        var location = f switch
        {
            "hiddenItemDataTable_array.bin" => "PALDEA",
            "hiddenItemDataTable_lc_array.bin" => "AREA ZERO",
            "hiddenItemDataTable_su1_array.bin" => "KITAKAMI",
            "hiddenItemDataTable_su2_array.bin" => "BLUEBERRY ACADEMY",
            _ => throw new ArgumentException($"Invalid file: {f}"),
        };

        RandomizerLog.Add($"== HIDDEN ITEMS ({location}) ==");
        var file = Path.Combine(PATH_MASTER, d, f);
        var obj = FlatBufferConverter.DeserializeFrom<HiddenItemDataTableArray>(file);
        foreach (var t in obj.Table)
        {
            List<HiddenItemDataTableInfo> Items = [ t.Item1, t.Item2, t.Item3, t.Item4, t.Item5, t.Item6, t.Item7, t.Item8, t.Item9, t.Item10 ];
            Items = Items.OrderByDescending(z => z.EmergePercent).ToList();

            RandomizerLog.Add($"Table {obj.Table.IndexOf(t) + 1}");
            foreach (var item in Items.Where(z => z.ItemId is not 0 && z.EmergePercent is not 0))
            {
                float rate = item.EmergePercent / 10f;
                var oldItem = $"{StringsNamesItem[(int)item.ItemId]} (×{item.DropCount})";
                item.ItemId = GetRandomItem();
                var newItem = $"{StringsNamesItem[(int)item.ItemId]} (×{item.DropCount})";
                var line = $"- {rate:00.00}% {oldItem} -> {newItem}";
                RandomizerLog.Add(line);
            }

            RandomizerLog.Add("");
        }

        var data = FlatBufferConverter.SerializeFrom(obj);
        var dest = Path.Combine(DEST_MASTER, d);
        if (!Directory.Exists(dest))
            Directory.CreateDirectory(dest);
        File.WriteAllBytes(Path.Combine(dest, f), data);
    }
}

private static void RandomizePickupItemTables()
{
    RandomizerLog.Add("== PICKUP ITEMS ==");
    "Randomizing Pickup item tables...".Dump();

    const string file = "monohiroiItemData_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/item/monohiroiItemData/");
    var path = Path.Combine(PATH_MASTER, "world/data/item/monohiroiItemData/");
    var obj = FlatBufferConverter.DeserializeFrom<MonohiroiItemArray>(Path.Combine(path, file));
    List<string> Level = [ "Level", "Niveau", "Level", "Livello", "Lv", "Lv", "레벨", "等级", "Nivel", "等級" ];
    List<string> Tables = [ "1-10", "11-20", "21-30", "31-40", "41-50", "51-60", "61-70", "71-80", "81-90", "91-100" ];

    for (int i = 0; i < obj.Table.Count; i++)
    {
        var t = obj.Table[i];
        var lv = Level[Languages.IndexOf(LANGUAGE)] + " " + Tables[i];
        RandomizerLog.Add(lv);

        List<OneMonohiroiItem> Items = [ t.Item01, t.Item02, t.Item03, t.Item04, t.Item05, t.Item06, t.Item07, t.Item08, t.Item09, t.Item10,
                                         t.Item11, t.Item12, t.Item13, t.Item14, t.Item15, t.Item16, t.Item17, t.Item18, t.Item19, t.Item20,
                                         t.Item21, t.Item22, t.Item23, t.Item24, t.Item25, t.Item26, t.Item27, t.Item28, t.Item29, t.Item30,
                                       ];

        Items = Items.OrderByDescending(z => z.Rate).ToList();
        foreach (var item in Items.Where(z => z.ItemId is not ItemID.ITEMID_NONE))
        {
            item.ItemId = GetRandomItem();
            RandomizerLog.Add($"- {item.Rate}% {StringsNamesItem[(int)item.ItemId]}");
        }

        RandomizerLog.Add("");
    }

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void RandomizeRummageItems()
{
    "Randomizing rummaging items...".Dump();

    const string file = "rummagingItemDataTable_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/item/rummagingItemDataTable/");
    var path = Path.Combine(PATH_MASTER, "world/data/item/rummagingItemDataTable/");
    var obj = FlatBufferConverter.DeserializeFrom<RummagingItemDataTableArray>(Path.Combine(path, file));

    foreach (var t in obj.Table)
    {
        if (t.Item00 is not ItemID.ITEMID_NONE) t.Item00 = GetRandomItem();
        if (t.Item01 is not ItemID.ITEMID_NONE) t.Item01 = GetRandomItem();
        if (t.Item02 is not ItemID.ITEMID_NONE) t.Item02 = GetRandomItem();
        if (t.Item03 is not ItemID.ITEMID_NONE) t.Item03 = GetRandomItem();
        if (t.Item04 is not ItemID.ITEMID_NONE) t.Item04 = GetRandomItem();
    }

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void RandomizeTeraRaidEncounters()
{
    "Randomizing Tera Raid Battle encounters...".Dump();

    List<string> RaidBosses =
    [
        // Paldea
        "world/data/raid/raid_enemy_01/raid_enemy_01_array.bin",
        "world/data/raid/raid_enemy_02/raid_enemy_02_array.bin",
        "world/data/raid/raid_enemy_03/raid_enemy_03_array.bin",
        "world/data/raid/raid_enemy_04/raid_enemy_04_array.bin",
        "world/data/raid/raid_enemy_05/raid_enemy_05_array.bin",
        "world/data/raid/raid_enemy_06/raid_enemy_06_array.bin",

        // Kitakami
        "world/data/raid/su1_raid_enemy_01/su1_raid_enemy_01_array.bin",
        "world/data/raid/su1_raid_enemy_02/su1_raid_enemy_02_array.bin",
        "world/data/raid/su1_raid_enemy_03/su1_raid_enemy_03_array.bin",
        "world/data/raid/su1_raid_enemy_04/su1_raid_enemy_04_array.bin",
        "world/data/raid/su1_raid_enemy_05/su1_raid_enemy_05_array.bin",
        "world/data/raid/su1_raid_enemy_06/su1_raid_enemy_06_array.bin",

        // Blueberry Academy
        "world/data/raid/su2_raid_enemy_01/su2_raid_enemy_01_array.bin",
        "world/data/raid/su2_raid_enemy_02/su2_raid_enemy_02_array.bin",
        "world/data/raid/su2_raid_enemy_03/su2_raid_enemy_03_array.bin",
        "world/data/raid/su2_raid_enemy_04/su2_raid_enemy_04_array.bin",
        "world/data/raid/su2_raid_enemy_05/su2_raid_enemy_05_array.bin",
        "world/data/raid/su2_raid_enemy_06/su2_raid_enemy_06_array.bin",
    ];

    foreach (var r in RaidBosses)
    {
        var index = r.LastIndexOf("/") + 1;
        var f = r[index..];
        var d = r[..index];;

        var file = Path.Combine(PATH_MASTER, d, f);
        var obj = FlatBufferConverter.DeserializeFrom<RaidEnemyTableArray>(file);
        bool IsFiveOrSixStar = file.Contains("enemy_05") || file.Contains("enemy_06");

        foreach (var boss in obj.Table)
        {
            var t = boss.Info.BossPokePara;
            var e = boss.Info.BossDesc;
            boss.Info.RomVer = RaidRomType.BOTH;

            if (RANDOMIZE_POKEMON_TO_HAVE_SIMILAR_STRENGTH)
            {
                (DevID Species, short Form) pk = GetSimilarStrengthPokemon((ushort)t.DevId, (byte)t.FormId);
                t.DevId = pk.Species;
                t.FormId = pk.Form;
            }

            else
            {
                t.DevId = GetRandomSpecies(t.DevId);
                t.FormId = GetRandomForm(t.DevId);
            }

            // Terapagos can only be Stellar Tera Type, and the game does not load Stellar raid crystals
            while (t.DevId is DevID.DEV_KODAIGAME)
            {
                t.DevId = GetRandomSpecies(0);
                t.FormId = GetRandomForm(t.DevId);
            }

            // enforce Terastallized State forms so that Embody Aspect activates
            if (t.DevId is DevID.DEV_KAMENONI)
                t.FormId = (short)(t.FormId + 4);

            t.Sex = SexType.DEFAULT;
            t.WazaType = WazaType.DEFAULT;
            t.Waza1 = GetCleanMoveSlot();
            t.Waza2 = GetCleanMoveSlot();
            t.Waza3 = GetCleanMoveSlot();
            t.Waza4 = GetCleanMoveSlot();
            t.GemType = GetTeraType(t.DevId, (byte)t.FormId, t.GemType, true, false);
            t.Seikaku = SeikakuType.DEFAULT;
            t.Tokusei = GetRandomAbilitySetter();

            // fix Game Freak bug where 3-Star Pachirisu has no flawless IVs
            if (file.Contains("enemy_03") && t.TalentVnum is 0)
                t.TalentVnum = 2;

            if (GIVE_POKEMON_HELD_ITEMS)
                t.Item = GetRandomItem();

            if (MODIFY_POKEMON_LEVELS)
            {
                t.Level = GetModifiedLevel(t.Level);
                boss.Info.CaptureLv = (sbyte)GetModifiedLevel(boss.Info.CaptureLv);
            }

            if (FORCE_FULLY_EVOLVE && IsFiveOrSixStar)
            {
                (DevID Species, short Form) pk = ForceEvolveSpeciesFully((ushort)t.DevId, (byte)t.FormId);
                t.DevId = pk.Species;
                t.FormId = pk.Form;
            }

            if (IsFormHeldItemDependent(t.DevId))
                t.Item = GetFormSpecificItem(t.DevId, t.FormId);

            // if any "use move" extra actions exist, modify the move to be Tera Blast
            List<RaidBossExtraData> ExtraActions = [ e.ExtraAction1, e.ExtraAction2, e.ExtraAction3, e.ExtraAction4, e.ExtraAction5, e.ExtraAction6 ];
            if (ExtraActions.Any(z => z.Wazano is not WazaID.WAZA_NULL))
            {
                foreach (var a in ExtraActions.Where(z => IsExtraActionValid(z) && z.Action is RaidBossExtraActType.WAZA && z.Wazano is not WazaID.WAZA_NULL))
                    a.Wazano = WazaID.WAZA_TERABAASUTO;
            }
        }

        var data = FlatBufferConverter.SerializeFrom(obj);
        var dest = Path.Combine(DEST_MASTER, d);
        if (!Directory.Exists(dest))
            Directory.CreateDirectory(dest);
        File.WriteAllBytes(Path.Combine(dest, f), data);
    }
}

private static bool IsExtraActionValid(RaidBossExtraData action)
{
    if (action.Action is RaidBossExtraActType.NONE)
        return false;
    if (action.Value is 0)
        return false;
    if (action.Action is RaidBossExtraActType.WAZA && action.Wazano is WazaID.WAZA_NULL)
        return false;
    return true;
}

private static void RandomizeTechnicalMachines()
{
    RandomizerLog.Add("== TECHNICAL MACHINES ==");
    "Randomizing TMs...".Dump();

    const string file = "itemdata_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/item/itemdata/");
    var path = Path.Combine(PATH_MASTER, "world/data/item/itemdata/");

    List<int> Moves = GetRandomMoveList();
    List<int> TMs = new();
    var machine = StringsNamesItem[0328].Remove(StringsNamesItem[0328].Length - 3);
    var ctr = 0;
    var idx = 1;

    // keep TM171 unchanged
    if (DO_NOT_RANDOMIZE_TERA_BLAST_TM)
    {
        Moves.Remove(851);
        Moves.Insert(171, 851);
    }

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var obj = FlatBufferConverter.DeserializeFrom<ItemDataArray>(Path.Combine(path, file));

    foreach (var t in obj.Table)
    {
        if (ctr > MAX_TM_COUNT)
        {
            var data = FlatBufferConverter.SerializeFrom(obj);
            File.WriteAllBytes(Path.Combine(dest, file), data);
            break;
        }

        if (t.MachineWaza is WazaID.WAZA_NULL)
            continue;

        t.MachineWaza = (WazaID)Moves[ctr];
        TMs.Add(Moves[ctr]);
        ctr++;
    }

    foreach (var t in obj.Table)
    {
        if (t.MachineWaza is not WazaID.WAZA_NULL && t.Id is not 1230)
        {
            RandomizerLog.Add($"{machine}{idx:000} {StringsNamesMove[(ushort)t.MachineWaza]}");
            idx++;
        }
    }

    RandomizerLog.Add("");
    FixTMCompatibility(TMs);
    FixTMMachine(TMs);
    FixTMIcons();
}

private static void FixTMCompatibility(List<int> TMs)
{
    foreach (var t in PersonalInfoEdited.Table)
    {
        if (ALL_POKEMON_ONLY_KNOW_METRONOME)
            t.TechnicalMachine.Clear();

        else
        {
            List<ushort> Revised = new();
            List<int> Indexes = new();
            IList<ushort> Learnable = t.TechnicalMachine;
            IList<ushort> Moves = PersonalDumperSV.TMIndexes;

            for (int i = 0; i < Moves.Count; i++)
            {
                if (Learnable.Contains(Moves[i]))
                    Indexes.Add(i);
            }

            for (int i = 1; i < TMs.Count; i++)
            {
                if (!Indexes.Contains(i))
                    continue;
                var index = i > 100 ? i : i - 1;
                Revised.Add((ushort)TMs[index]);
            }

            t.TechnicalMachine = Revised.ToList();

            if (TECHNICAL_MACHINE_LEARNSET_SANITY)
            {
                foreach (var m in t.Learnset)
                    t.TechnicalMachine.Add(m.Move);
            }

            t.TechnicalMachine = t.TechnicalMachine.Distinct().ToList();
        }
    }

    UpdatePersonalTable();
}

private static void FixTMMachine(List<int> TMs)
{
    const string file = "shop_wazamachine_data_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/ui/shop/shop_wazamachine/shop_wazamachine_data/");
    var path = Path.Combine(PATH_MASTER, "world/data/ui/shop/shop_wazamachine/shop_wazamachine_data/");
    var obj = FlatBufferConverter.DeserializeFrom<ShopWazamachineDataArray>(Path.Combine(path, file));

    for (int i = 0; i < obj.Table.Count; i++)
    {
        // DLC TMs are not appended to the bottom of the table... they're placed at the start!
        var index = i switch
        {
            >= 000 and <= 057 => i + 172, // TM172 to TM229
            >= 058 and <= 156 => i - 058, // TM001 to TM099
            >= 157 and <= 228 => i - 057, // TM100 to TM171
            _ => 0,
        };

        // write back to table
        var item = obj.Table[i];
        item.WazaNo = TMs[index];

        if (MAKE_ALL_TMS_CRAFTABLE_FROM_START)
        {
            item.Cond = CondEnum.NONE;
            item.CondValue = string.Empty;
        }
    }

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void FixTMIcons()
{
    const string file = "itemdata_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/item/itemdata/");
    var destIcon = Path.Combine(DEST_MASTER, "appli/tex/icon_item/");
    var pathIcon = Path.Combine(PATH_MASTER, "appli/tex/icon_item/");
    var obj = FlatBufferConverter.DeserializeFrom<ItemDataArray>(Path.Combine(dest, file));
    foreach (var t in obj.Table.Where(z => z.MachineWaza is not WazaID.WAZA_NULL && z.Id is not 1230))
    {
        var current = $"item_{t.Id:0000}";
        var desired = GetIconName((ushort)t.MachineWaza);

        if (!Directory.Exists(Path.Combine(destIcon, current)))
            Directory.CreateDirectory(Path.Combine(destIcon, current));
        File.Copy(Path.Combine(pathIcon, desired, desired + ".bntx"), Path.Combine(destIcon, current, current + ".bntx"), true);
    }
}

private static void RandomizePokemonStats()
{
    PersonalInfoEdited = AddRegionalFormEvolutions(PersonalInfoEdited);
    PersonalInfoEdited = AddAlternateEvolutionMethods(PersonalInfoEdited);

    // give evolution moves to Stantler and Dipplin, regardless of randomizer settings
    var st = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesNational is 0234);
    var di = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesNational is 1011);
    st.Learnset.Add(new PersonalInfoMove { Move = 828, Level = 30 });
    st.Learnset = st.Learnset.OrderBy(z => z.Level).ToList();
    di.ReminderMoves.Add(913);

    if (RANDOMIZE_PERSONAL_ABILITY)
    {
        "Randomizing Pokémon abilities...".Dump();
        if (ABILITIES_TYPES_FOLLOW_EVOLUTIONS && !RANDOMIZE_PERSONAL_EVOLUTION)
        {
            foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame))
            {
                if (PRESERVE_FORM_CHANGE_ABILITIES && FormChangeAbilities.Contains(t.Ability1))
                    continue;

                t.Ability1 = GetRandomAbility();
                t.Ability2 = GetRandomAbility();
                t.AbilityH = GetRandomAbility();

                // ensure all 3 are unique
                while (t.Ability2 == t.Ability1 || t.Ability2 == t.AbilityH) t.Ability2 = GetRandomAbility();
                while (t.AbilityH == t.Ability1 || t.AbilityH == t.Ability2) t.AbilityH = GetRandomAbility();
            }

            foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame && z.Info.SpeciesNational is not 0133 && z.Evolutions.Count is not 0))
            {
                // first evo
                foreach (var e in t.Evolutions.Where(z => z.SpeciesInternal is not 0))
                {
                    var evoS1 = e.SpeciesInternal;
                    var evoF1 = e.Form;
                    var evo1 = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesInternal == evoS1 && z.Info.Form == evoF1);

                    if (PRESERVE_FORM_CHANGE_ABILITIES && FormChangeAbilities.Contains(evo1.Ability1))
                        continue;

                    evo1.Ability1 = t.Ability1;
                    evo1.Ability2 = t.Ability2;
                    evo1.AbilityH = t.AbilityH;

                    // second evo
                    foreach (var f in evo1.Evolutions.Where(z => z.SpeciesInternal is not 0))
                    {
                        var evoS2 = f.SpeciesInternal;
                        var evoF2 = f.Form;
                        var evo2 = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesInternal == evoS2 && z.Info.Form == evoF2);

                        if (PRESERVE_FORM_CHANGE_ABILITIES && FormChangeAbilities.Contains(evo2.Ability1))
                            continue;

                        evo2.Ability1 = t.Ability1;
                        evo2.Ability2 = t.Ability2;
                        evo2.AbilityH = t.AbilityH;
                    }
                }
            }

            // Gimmighoul edge case
            var gimmi = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesNational is 0999 && z.Info.Form is 0);
            var ghold = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesNational is 1000);
            gimmi.Ability1 = ghold.Ability1;
            gimmi.Ability2 = ghold.Ability2;
            gimmi.AbilityH = ghold.AbilityH;
        }

        else
        {
            foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame))
            {
                if (PRESERVE_FORM_CHANGE_ABILITIES && FormChangeAbilities.Contains(t.Ability1))
                    continue;

                t.Ability1 = GetRandomAbility();
                t.Ability2 = GetRandomAbility();
                t.AbilityH = GetRandomAbility();

                // ensure all 3 are unique
                while (t.Ability2 == t.Ability1 || t.Ability2 == t.AbilityH) t.Ability2 = GetRandomAbility();
                while (t.AbilityH == t.Ability1 || t.AbilityH == t.Ability2) t.AbilityH = GetRandomAbility();
            }
        }
    }

    if (RANDOMIZE_PERSONAL_TYPE)
    {
        "Randomizing Pokémon types...".Dump();
        if (ABILITIES_TYPES_FOLLOW_EVOLUTIONS && !RANDOMIZE_PERSONAL_EVOLUTION)
        {
            foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame && z.Info.SpeciesNational is not 0493))
            {
                var type1 = GetRandomType();
                var type2 = type1;

                // chance at dual-type
                if (GetRandom(100) < RANDOM_DUAL_TYPE_CHANCE_PERCENT)
                {
                    while (type2 == type1)
                        type2 = GetRandomType();
                }

                t.Type1 = type1;
                t.Type2 = type2;
            }

            foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame && z.Info.SpeciesNational is not 0133 && z.Evolutions.Count is not 0))
            {
                // first evo
                bool GainNewTypeFirst = GetRandom(100) < GAIN_RANDOM_TYPE_EVO_1_CHANCE_PERCENT;
                foreach (var e in t.Evolutions.Where(z => z.SpeciesInternal is not 0))
                {
                    var evoS1 = e.SpeciesInternal;
                    var evoF1 = e.Form;
                    var evo1 = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesInternal == evoS1 && z.Info.Form == evoF1);
                    evo1.Type1 = t.Type1;
                    evo1.Type2 = t.Type2;

                    // gain a second type upon first evolution
                    if (evo1.Type1 == evo1.Type2 && GainNewTypeFirst)
                    {
                        while (evo1.Type2 == evo1.Type1)
                            evo1.Type2 = GetRandomType();
                    }

                    // second evo
                    foreach (var f in evo1.Evolutions.Where(z => z.SpeciesInternal is not 0))
                    {
                        bool GainNewTypeSecond = GetRandom(100) < GAIN_RANDOM_TYPE_EVO_2_CHANCE_PERCENT;
                        var evoS2 = f.SpeciesInternal;
                        var evoF2 = f.Form;
                        var evo2 = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesInternal == evoS2 && z.Info.Form == evoF2);
                        evo2.Type1 = evo1.Type1;
                        evo2.Type2 = evo1.Type2;

                        // gain a second type upon second evolution
                        if (evo2.Type1 == evo2.Type2 && GainNewTypeSecond)
                        {
                            while (evo2.Type2 == evo2.Type1)
                                evo2.Type2 = GetRandomType();
                        }
                    }
                }
            }

            // Gimmighoul edge case
            var gimmi = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesNational is 0999 && z.Info.Form is 0);
            var ghold = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesNational is 1000);
            gimmi.Type1 = ghold.Type1;
            gimmi.Type2 = ghold.Type2;
        }

        else
        {
            foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame && z.Info.SpeciesNational is not 0493))
            {
                var type1 = GetRandomType();
                var type2 = type1;

                // chance at dual-type
                if (GetRandom(100) < RANDOM_DUAL_TYPE_CHANCE_PERCENT)
                {
                    while (type2 == type1)
                        type2 = GetRandomType();
                }

                t.Type1 = type1;
                t.Type2 = type2;
            }
        }

        // we only write here so that type biased learnsets can referenced newly-randomized types
        UpdatePersonalTable();
    }

    if (RANDOMIZE_PERSONAL_EVOLUTION)
    {
        "Randomizing Pokémon evolutions...".Dump();
        foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame))
        {
            t.EXPGrowth = 5; // standardize growth rates to Slow
            var evos = t.Evolutions;

            foreach (var evo in evos.Where(z => z.SpeciesInternal is not 0))
            {
                evo.SpeciesInternal = (ushort)GetRandomSpecies(0);
                evo.Form = GetRandomForm((DevID)evo.SpeciesInternal);
            }
        }
    }

    if (MAKE_POKEMON_EVOLVE_EVERY_LEVEL)
    {
        "Making Pokémon evolve every level...".Dump();
        foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame))
        {
            t.EXPGrowth = 5;
            var evos = t.Evolutions;
            var s = GetRandomSpecies(0);
            var f = GetRandomForm(s);

            PersonalInfoEvolution evo = new()
            {
                Level = 1,
                Method = (int)EvolutionType.LevelUp,
                Argument = 0,
                SpeciesInternal = (ushort)s,
                Form = f,
            };

            evos.Clear();
            evos.Add(evo);
        }
    }

    if (RANDOMIZE_PERSONAL_LEARNSET && !ALL_POKEMON_ONLY_KNOW_METRONOME)
    {
        "Randomizing Pokémon learnsets...".Dump();

        bool HasExclusiveMove(ushort species, ushort form) => species is 0491 or 0877 || species is 0720 && form is 1;
        ushort GetExclusiveMove(ushort species, ushort form) => species switch
        {
            0491 => 464, // Darkrai: Dark Void
            0877 => 783, // Morpeko: Aura Wheel
            0720 when form is 1 => 621, // Hoopa Unbound: Hyperspace Fury
            _ => 0,
        };

        List<int> PokemonWithImportantMoves =
        [
            0057, // Primeape
            0190, // Aipom
            0193, // Yanma
            0203, // Girafarig
            0206, // Dunsparce
            0211, // Qwilfish-1
            0221, // Piloswine
            0234, // Stantler
            0438, // Bonsly
            0647, // Keldeo
            0648, // Meloetta
            0762, // Steenee
        ];

        foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame))
        {
            var learnset = t.Learnset;
            var reminder = t.ReminderMoves;

            if (STANDARDIZE_LEVELS_FOR_LEARNING_MOVES)
            {
                t.Learnset.Clear();

                for (int i = 0; i <= 100; i += 10)
                {
                    var level = i is 0 ? (sbyte)1 : (sbyte)i;
                    learnset.Add(new PersonalInfoMove { Move = 001, Level = level });
                }

                // give all Pokémon four moves to start, just to be fair
                for (int i = 0; i <= 2; i++)
                    learnset.Add(new PersonalInfoMove { Move = 001, Level = 1 });
            }

            if (RANDOMIZE_LEARNSETS_WITH_TYPE_BIAS)
            {
                learnset = GetLearnsetWithTypeBias(learnset, t.Info.SpeciesInternal, (byte)t.Info.Form);
                reminder = GetReminderMovesWithTypeBias(reminder, t.Info.SpeciesInternal, (byte)t.Info.Form);
            }

            else
            {
                List<int> Moves = GetRandomMoveList();
                var ctr = 0;

                // moves only usable by a certain species/form
                if (HasExclusiveMove(t.Info.SpeciesNational, t.Info.Form))
                {
                    var exclusive = GetExclusiveMove(t.Info.SpeciesNational, t.Info.Form);
                    Moves.Add(exclusive);
                    pkNX.Randomization.Util.Shuffle(Moves);
                }

                // level up
                for (int i = 0; i < learnset.Count; i++)
                {
                    learnset[i].Move = (ushort)Moves[i];
                    ctr++;
                }

                // reminder
                for (int i = 0; i < reminder.Count; i++)
                {
                    if (t.Info.SpeciesNational is 1011 && reminder[i] is 913) // keep Dragon Cheer for Dipplin
                        continue;

                    reminder[i] = (ushort)Moves[ctr];
                    ctr++;
                }

                if (GUARANTEE_FOUR_MOVES_AT_LEVEL_ONE && learnset.Count(z => z.Level is 1) is < 4)
                {
                    while (learnset.Count(z => z.Level is 1) is < 4)
                    {
                        learnset.Add(new PersonalInfoMove { Move = (ushort)Moves[ctr], Level = 1 });
                        ctr++;
                    }
                }
            }

            // add evolution moves and form changing moves to reminders, now that they may be inaccessible
            if (PokemonWithImportantMoves.Contains(t.Info.SpeciesNational))
            {
                // only Qwilfish-1 can evolve
                if (t.Info.SpeciesNational is 0211 && t.Info.Form is not 1)
                    continue;

                var move = t.Info.SpeciesNational switch
                {
                    0057         => 889, // Rage Fist
                    0190         => 458, // Double Hit
                    0193 or 0221 => 246, // Ancient Power
                    0438         => 102, // Mimic
                    0203         => 888, // Twin Beam
                    0206         => 887, // Hyper Drill
                    0211         => 839, // Barb Barrage
                    0234         => 828, // Psyshield Bash
                    0647         => 548, // Secret Sword
                    0648         => 547, // Relic Song
                    0762         => 023, // Stomp
                    _ => throw new ArgumentException($"Invalid species: {t.Info.SpeciesNational}"),
                };

                reminder.Add((ushort)move);
            }

            t.Learnset = t.Learnset.OrderBy(z => z.Level).ToList();
        }
    }

    if (ALL_POKEMON_ONLY_KNOW_METRONOME)
    {
        "Modifying all learnsets to only include Metronome...".Dump();
        foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame))
        {
            t.TechnicalMachine.Clear();
            t.EggMoves.Clear();
            t.ReminderMoves.Clear();
            t.Learnset.Clear();

            var metronome = new PersonalInfoMove() { Move = 118, Level = 1 };
            t.Learnset.Add(metronome);
        }
    }

    if (RANDOMIZE_PERSONAL_BASE_STATS_WITHIN_BST)
    {
        "Randomizing Pokémon base stats (within BST)...".Dump();
        foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame))
        {
            List<int> BaseStats = [ t.Base.HP, t.Base.ATK, t.Base.DEF, t.Base.SPA, t.Base.SPD, t.Base.SPE ];
            List<float> Weighted = new();
            List<int> Final = new();
            var BaseStatTotal = BaseStats.Sum();

            for (int i = 0; i < 6; i++)
                Weighted.Add(GetRandom(5, BaseStats[i]));

            float factor = Weighted.Sum() / BaseStatTotal;

            for (int i = 0; i < 6; i++)
                Final.Add((byte)(Weighted[i] /= factor));

            // throw in some extra points to meet the original BST
            if (Final.Sum() < BaseStatTotal)
            {
                var diff = BaseStatTotal - Final.Sum();
                for (int i = 0; i < diff; i++)
                {
                    var index = GetRandom(Final.Count);
                    Final[index]++;
                }
            }

            t.Base.HP  = (byte)Final[0];
            t.Base.ATK = (byte)Final[1];
            t.Base.DEF = (byte)Final[2];
            t.Base.SPA = (byte)Final[3];
            t.Base.SPD = (byte)Final[4];
            t.Base.SPE = (byte)Final[5];
        }
    }

    else if (RANDOMIZE_PERSONAL_BASE_STATS_COMPLETELY_RANDOM)
    {
        "Randomizing Pokémon base stats (completely random)...".Dump();
        foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame))
        {
            t.Base.HP  = (byte)GetRandom(5, 256);
            t.Base.ATK = (byte)GetRandom(5, 256);
            t.Base.DEF = (byte)GetRandom(5, 256);
            t.Base.SPA = (byte)GetRandom(5, 256);
            t.Base.SPD = (byte)GetRandom(5, 256);
            t.Base.SPE = (byte)GetRandom(5, 256);
        }
    }

    else if (RANDOMIZE_PERSONAL_BASE_STATS_SHUFFLED)
    {
        "Shuffling Pokémon base stats...".Dump();
        foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame))
        {
            List<byte> BaseStats = [ t.Base.HP, t.Base.ATK, t.Base.DEF, t.Base.SPA, t.Base.SPD, t.Base.SPE ];
            pkNX.Randomization.Util.Shuffle(BaseStats);
            t.Base.HP  = BaseStats[0];
            t.Base.ATK = BaseStats[1];
            t.Base.DEF = BaseStats[2];
            t.Base.SPA = BaseStats[3];
            t.Base.SPD = BaseStats[4];
            t.Base.SPE = BaseStats[5];
        }
    }

    if (REMOVE_EFFORT_VALUE_YIELDS)
    {
        "Removing EV yields...".Dump();

        var empty = new PersonalInfoStats { HP = 0, ATK = 0, DEF = 0, SPA = 0, SPD = 0, SPE = 0 };
        foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame))
            t.EVYield = empty;
    }

    if (REDUCE_EGG_HATCH_CYCLES)
    {
        "Reducing Egg hatch cycles...".Dump();

        foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame))
            t.HatchCycles = 1;
    }

    UpdatePersonalTable();
    WritePersonalChangesToLog(PersonalInfoEdited);
}

private static IList<PersonalInfoMove> GetLearnsetWithTypeBias(IList<PersonalInfoMove> Learnset, ushort species, byte form)
{
    (byte Type1, byte Type2) Types = GetPokemonTypes(species, form);
    List<int> MovesBias = GetRandomMoveListWithBias(species, form, Types.Type1, Types.Type2);
    List<int> MovesNoBias = GetRandomMoveList().Except(MovesBias).ToList();
    var ctr = 0;
    var exhausted = 0;

    for (int i = 0; i < Learnset.Count; i++)
    {
        bool bias = exhausted >= MovesBias.Count ? false : GetRandom(100) < RANDOM_LEARNSET_TYPE_BIAS_PERCENT;
        Learnset[i].Move = bias ? (ushort)MovesBias[exhausted] : (ushort)MovesNoBias[i];
        ctr++;

        if (bias)
            exhausted++;
    }

    if (GUARANTEE_FOUR_MOVES_AT_LEVEL_ONE && Learnset.Count(z => z.Level is 1) is < 4)
    {
        while (Learnset.Count(z => z.Level is 1) is < 4)
        {
            bool bias = exhausted == MovesBias.Count ? false : GetRandom(100) < RANDOM_LEARNSET_TYPE_BIAS_PERCENT;
            var move = bias ? (ushort)MovesBias[exhausted] : (ushort)MovesNoBias[ctr];
            Learnset.Add(new PersonalInfoMove { Move = move, Level = 1 });
            ctr++;

            if (bias)
                exhausted++;
        }
    }

    return Learnset;
}

private static IList<ushort> GetReminderMovesWithTypeBias(IList<ushort> Reminder, ushort species, byte form)
{
    (byte Type1, byte Type2) Types = GetPokemonTypes(species, form);
    List<int> MovesBias = GetRandomMoveListWithBias(species, form, Types.Type1, Types.Type2);
    List<int> MovesNoBias = GetRandomMoveList().Except(MovesBias).ToList();
    var ctr = 0;
    var exhausted = 0;

    for (int i = 0; i < Reminder.Count; i++)
    {
        // skip Stantler's Psyshield Bash and Dipplin's Dragon Cheer
        if (Reminder[i] is 828 or 913)
            continue;

        bool bias = exhausted >= MovesBias.Count ? false : GetRandom(100) < RANDOM_LEARNSET_TYPE_BIAS_PERCENT;
        Reminder[i] = bias ? (ushort)MovesBias[exhausted] : (ushort)MovesNoBias[i];
        ctr++;

        if (bias)
            exhausted++;
    }

    return Reminder;
}

private static PersonalTable AddRegionalFormEvolutions(PersonalTable obj)
{
    List<int> Alola = [ 0025, 0102 ];
    List<int> Galar = [ 0109 ];
    List<int> Hisui = [ 0156, 0502, 0548, 0627, 0704, 0712, 0723 ];
    List<int> RegionalForms = Alola.Concat(Galar).Concat(Hisui).ToList();

    foreach (var t in RegionalForms)
    {
        var sel = obj.Table.FirstOrDefault(z => z.Info.SpeciesNational == t);
        PersonalInfoEvolution evo = new()
        {
            Level = 0,
            Method = (int)EvolutionType.UseItem,
            Argument = 57,
            SpeciesInternal = (ushort)(t + 1),
            Form = 1,
        };

        sel.Evolutions.Add(evo);
    }

    return obj;
}

private static PersonalTable AddAlternateEvolutionMethods(PersonalTable obj)
{
    // add a Linking Cord variant for trade evolutions
    foreach (var t in obj.Table.Where(z => z.IsPresentInGame && z.Evolutions.Any(x => x.Method is (int)EvolutionType.Trade)))
    {
        PersonalInfoEvolution evo = new()
        {
            Level = 0,
            Method = (int)EvolutionType.UseItem,
            Argument = 58,
            SpeciesInternal = (ushort)(t.Info.SpeciesNational + 1),
            Form = t.Info.Form,
        };

        t.Evolutions.Add(evo);
    }

    // add a use item variant for trade with held item evolutions
    foreach (var t in obj.Table.Where(z => z.IsPresentInGame && z.Evolutions.Any(x => x.Method is (int)EvolutionType.TradeHeldItem)))
    {
        var template = t.Evolutions.FirstOrDefault(z => z.Method is (int)EvolutionType.TradeHeldItem); // finding first instance is fine, since Clamperl is not in the game
        PersonalInfoEvolution evo = new()
        {
            Level = 0,
            Method = (int)EvolutionType.UseItem,
            Argument = template.Argument,
            Reserved3 = 6969, // we'll adjust this later, this is just to differentiate the two
            SpeciesInternal = template.SpeciesInternal,
            Form = template.Form,
        };

        t.Evolutions.Add(evo);
    }

    return obj;
}

private static void WritePersonalChangesToLog(PersonalTable obj)
{
    RandomizerLog.Add("== POKÉMON STATS ==");

    var pathAHTB = Path.Combine(PATH_MASTER, "message/dat/English/common/zkn_form.tbl");
    var dataAHTB = File.ReadAllBytes(pathAHTB);
    var tableAHTB = new AHTB(dataAHTB);
    List<AHTBEntry> FormStringEntries = new AHTB(dataAHTB).Entries.ToList();
    var table = obj.Table.OrderBy(z => z.Info.SpeciesNational).Where(z => z.IsPresentInGame && SpeciesList.Contains((DevID)z.Info.SpeciesInternal));

    foreach (var t in table)
    {
        // skip ride forms
        if (t.Info.SpeciesNational is 1007 or 1008 && t.Info.Form is not 0)
            continue;

        var name = StringsNamesSpecies[t.Info.SpeciesInternal];
        var form = GetFormString(FormStringEntries, StringsNamesForm, t.Info.SpeciesInternal, (byte)t.Info.Form);
        var formDisplay = form switch
        {
            _ when t.Info.SpeciesNational is 1017 && t.Info.Form is >= 4 => $"-{t.Info.Form} ({form}*)",
            not "" when t.Info.Form is 0 => $" ({form})",
            not "" => $"-{t.Info.Form} ({form})",
            _ => string.Empty,
        };

        RandomizerLog.Add("=====");
        RandomizerLog.Add($"{t.Info.SpeciesNational:0000} {name}{formDisplay}");
        RandomizerLog.Add("=====");

        // base stats
        var total = t.Base.HP + t.Base.ATK + t.Base.DEF + t.Base.SPA + t.Base.SPD + t.Base.SPE;
        var stats = $"{t.Base.HP}.{t.Base.ATK}.{t.Base.DEF}.{t.Base.SPA}.{t.Base.SPD}.{t.Base.SPE}";
        RandomizerLog.Add($"- Base Stats: {stats} (BST: {total})");

        // types
        var type = t.Type1 == t.Type2 ? StringsNamesType[t.Type1] : $"{StringsNamesType[t.Type1]} / {StringsNamesType[t.Type2]}";
        RandomizerLog.Add($"- Type: {type}");

        // abilities
        var ability1 = VagueAbilities.Contains(t.Ability1) ? GetDescriptiveAbilityName(t.Ability1) : StringsNamesAbility[t.Ability1];
        var ability2 = VagueAbilities.Contains(t.Ability2) ? GetDescriptiveAbilityName(t.Ability2) : StringsNamesAbility[t.Ability2];
        var abilityH = VagueAbilities.Contains(t.AbilityH) ? GetDescriptiveAbilityName(t.AbilityH) : StringsNamesAbility[t.AbilityH];
        RandomizerLog.Add($"- Abilities: {ability1} (1) / {ability2} (2) / {abilityH} (H)");

        // evolutions
        var evos = t.Evolutions;
        if (evos.Count is not 0)
        {
            RandomizerLog.Add("- Evolutions:");
            foreach (var e in evos.Where(z => z.Method is not ((ushort)EvolutionType.TowerOfDarkness or (ushort)EvolutionType.TowerOfWaters)))
            {
                var evoSpecies = StringsNamesSpecies[e.SpeciesInternal];
                var evoForm = e.Form is 0 ? string.Empty : $"-{e.Form}";
                var index = evos.IndexOf(e);

                if (t.Info.SpeciesNational is 0704 && e.Method is (ushort)EvolutionType.Hisui)
                    continue;

                RandomizerLog.Add($"  [{index}] Evolves into {evoSpecies}{evoForm}");
            }
        }

        // learnsets
        if (t.ReminderMoves.Count is not 0)
        {
            RandomizerLog.Add("- Reminder Moves:");
            foreach (var r in t.ReminderMoves)
                RandomizerLog.Add($"  - {StringsNamesMove[r]}");
        }

        RandomizerLog.Add("- Level Up Moves:");
        foreach (var m in t.Learnset)
        {
            var level = m.Level is -3 ? 0 : m.Level;
            RandomizerLog.Add($"  [{level:000}] {StringsNamesMove[m.Move]}");
        }

        RandomizerLog.Add("");
    }
}

private static void WriteTrainerChangesToLog(TrDataMainArray obj)
{
    RandomizerLog.Add("== TRAINER BATTLES ==");

    const string file = "trtype_array.bin";
    var path = Path.Combine(PATH_MASTER, "world/data/trainer/trtype/");
    var trt = FlatBufferConverter.DeserializeFrom<TrainerTypeArray>(Path.Combine(path, file));

    bool IsNemona(string id) => id.Contains("rival_") || id.Contains("nemo_");
    bool IsClavell(string id) => id.Contains("clavel_") || id.Contains("claver_");

    foreach (var t in obj.Table)
    {
        var type = GetTrainerClass(t.TrainerType);
        var name = GetStringFromAHTBCommon("trname", FnvHash.HashFnv1a_64(t.TrNameLabel), LANGUAGE);
        var display = $"{type} {name}".Trim();

        // unused
        if ((string.IsNullOrWhiteSpace(type) && string.IsNullOrWhiteSpace(name)) || name.Contains("[~ "))
            continue;
        if (display.Contains("INVALID") && t.TrId is not ("professor_A_01" or "professor_A_02" or "professor_B_01" or "professor_B_02"))
            continue;
        if (!t.TrId.Contains("kusa") && !t.TrId.Contains("hono") && !t.TrId.Contains("mizu") && (IsNemona(t.TrId) || IsClavell(t.TrId)))
            continue;

        List<string> Members = new();
        List<PokeDataBattle> Team = [ t.Poke1, t.Poke2, t.Poke3, t.Poke4, t.Poke5, t.Poke6 ];
        foreach (var pk in Team.Where(z => z.DevId is not DevID.DEV_NULL))
        {
            var form = pk.FormId is 0 ? string.Empty : $"-{pk.FormId}";
            var rare = pk.RareType is RareType.RARE ? " ★" : string.Empty;
            var line = $"{StringsNamesSpecies[(int)pk.DevId]}{form}{rare} (Lv. {pk.Level})";
            Members.Add(line);
        }

        RandomizerLog.Add($"{display.Replace("INVALID", " ").Trim()} - {string.Join(" / ", Members)}");
    }

    string GetTrainerClass(string type)
    {
        var tr = trt.Table.FirstOrDefault(z => z.NameLabel == type);
        if (tr is null)
            return string.Empty;
        return GetStringFromAHTBCommon("trtype", FnvHash.HashFnv1a_64(tr.MsgLabel), LANGUAGE);
    }

    RandomizerLog.Add("");
}

private static void RandomizeMoveProperties()
{
    List<byte> PP = [ 5, 10, 15, 20, 25, 30, 35, 40 ];
    bool IsNotStatusMove(byte category) => category is not 0;
    var localePP = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_skill_01"), LANGUAGE);
    var localeCategory = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_skill_category_00"), LANGUAGE);
    var localePower = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_skill_power_00"), LANGUAGE);
    var localeAccuracy = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_skill_hit_00"), LANGUAGE);
    var localeType = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_pokemon_type_00"), LANGUAGE);
    string[][] categories =
    [
        [ "Status", "Physical", "Special" ],
        [ "Statut", "Physique", "Spéciale" ],
        [ "Status", "Physische", "Spezial" ],
        [ "Stato", "Fisica", "Speciale" ],
        [ "変化", "物理", "特殊" ],
        [ "変化", "物理", "特殊" ],
        [ "변화", "물리", "특수" ],
        [ "變化", "物理", "特殊" ],
        [ "Estado", "Físico", "Especial" ],
        [ "變化", "物理", "特殊" ],
    ];

    string[] names = [ "Move", "Attaque", "Attacke", "Mossa", "わざ", "わざ", "기술", "招式", "Movimiento", "招式" ];
    var localeMove = names[Languages.IndexOf(LANGUAGE)];

    if (RANDOMIZE_MOVE_PROPERTIES_TYPE || RANDOMIZE_MOVE_PROPERTIES_POWER || RANDOMIZE_MOVE_PROPERTIES_ACCURACY || RANDOMIZE_MOVE_PROPERTIES_PP)
    {
        RandomizerLog.Add("== MOVE PROPERTIES ==");
        "Randomizing move properties...".Dump();

        foreach (var t in MoveInfoEdited.Table.Where(z => z.CanUseMove && z.MoveID is not 0))
        {
            if (RANDOMIZE_MOVE_PROPERTIES_TYPE)
                t.Type = GetRandomType();

            if (RANDOMIZE_MOVE_PROPERTIES_POWER && IsNotStatusMove(t.Category) && t.Power is not (0 or 1))
                t.Power = GetRandomMovePower();

            if (RANDOMIZE_MOVE_PROPERTIES_ACCURACY && IsNotStatusMove(t.Category))
                t.Accuracy = GetRandomMoveAccuracy();

            if (RANDOMIZE_MOVE_PROPERTIES_CATEGORY && IsNotStatusMove(t.Category))
                t.Category = (byte)GetRandom(1, 3);

            if (RANDOMIZE_MOVE_PROPERTIES_PP && t.PP is not 1)
                t.PP = PP[GetRandom(PP.Count)];
        }

        RandomizerLog.Add("================================================================================================");
        RandomizerLog.Add($"| ID  | {localeMove, -28} | {localeType, -10} | {localeCategory, -9} | {localePower, -9} | {localeAccuracy, -11} | {localePP, -4} |");
        RandomizerLog.Add("================================================================================================");

        foreach (var t in MoveInfoEdited.Table.Where(z => z.CanUseMove && z.MoveID is not 0))
        {
            var pow = t.Power is 0 or 1 ? "———" : $"{t.Power}";
            var acc = t.Accuracy is 101 ? "———" : $"{t.Accuracy}";
            RandomizerLog.Add($"| {t.MoveID:000} | {StringsNamesMove[t.MoveID], -28} | {StringsNamesType[(int)t.Type], -10} | {categories[Languages.IndexOf(LANGUAGE)][t.Category], -9} | {pow, -9} | {acc, -11} | {t.PP, -4} |");
        }

        RandomizerLog.Add("================================================================================================");
        RandomizerLog.Add("");
        UpdateMoveTable();
    }
}

private static byte GetRandomMovePower() => GetRandom(100) switch
{
    <= 99 and >= 65 => 40,  // 35%
    <= 64 and >= 40 => 60,  // 25%
    <= 39 and >= 20 => 80,  // 20%
    <= 19 and >= 10 => 100, // 10%
    <=  9 and >=  5 => 120, // 5%
    <=  4 and >=  2 => 20,  // 3%
                  1 => 10,  // 1%
                  _ => 150, // 1%
};

private static byte GetRandomMoveAccuracy() => GetRandom(100) switch
{
    <= 99 and >= 50 => 100, // 50%
    <= 49 and >= 30 => 90,  // 20%
    <= 29 and >= 20 => 80,  // 10%
    <= 19 and >= 10 => 70,  // 10%
    <=  9 and >=  5 => 50,  // 5%
    <=  4 and >=  2 => 30,  // 3%
                  1 => 10,  // 1%
                  _ => 101, // 1%
};

private static void RandomizeTradeEncounters()
{
    RandomizerLog.Add("== IN-GAME TRADES ==");
    "Randomizing in-game trades...".Dump();

    const string file = "eventTradePokemon_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/event/eventTradePokemon/");
    var path = Path.Combine(PATH_MASTER, "world/data/event/eventTradePokemon/");
    var obj = FlatBufferConverter.DeserializeFrom<EventTradePokemonArray>(Path.Combine(path, file));

    List<string> Coaches =
    [
        string.Empty,
        string.Empty,
        "bb4kings_honoo",
        "gym_nomal",
        "paldea_botan",
        "paldea4kings_zimen",
        "teacher_head",
        "gym_koori",
        "gym_mizu",
        "teacher_dragon",
        "gym_mushi",
        "bb4kings_dragon",
        "teacher_battle",
        "gym_kusa",
        "teacher_health",
        "gym_denki",
        "paldea_rival",
        "bb4kings_hagane",
        "paldea_top",
        "paldea_friend",
        "paldea4kings_hagane",
        "gym_ghost",
        "teacher_history",
        "gym_esper",
        "teacher_home",
        "teacher_language",
        "bbteacher_head",
        "bb_brother",
        "bb4kings_fairy",
        "teacher_math",
        "bb_sister",
        "teacher_ecology",
        string.Empty,
    ];

    bool IsBlueberryTrade(string id) => id is not ("trade_c02_0194" or "trade_c03_0093" or "trade_t03_0872");
    for (int i = 0; i < obj.Table.Count; i++)
    {
        var t = obj.Table[i].PokeData;
        var oldSpecies = t.DevId;
        var oldForm = t.FormId;
        var oldRare = t.RareType;

        if (RANDOMIZE_POKEMON_TO_HAVE_SIMILAR_STRENGTH)
        {
            (DevID Species, short Form) pk = GetSimilarStrengthPokemon((ushort)t.DevId, (byte)t.FormId);
            t.DevId = pk.Species;
            t.FormId = pk.Form;
        }

        else
        {
            t.DevId = GetRandomSpecies(t.DevId);
            t.FormId = GetRandomForm(t.DevId);
        }

        t.Sex = GetRandomGender(t.DevId, t.FormId);
        t.Tokusei = GetRandomAbilitySlot();
        t.GemType = GetTeraType(t.DevId, (byte)t.FormId, t.GemType, false, true);
        t.RareType = t.RareType is RareType.RARE ? RareType.RARE : RareType.DEFAULT;
        t.Seikaku = t.SeikakuHosei = GetRandomNature(t.DevId, (byte)t.FormId);
        t.WazaType = WazaType.DEFAULT;
        t.Waza1 = GetCleanMoveSlot();
        t.Waza2 = GetCleanMoveSlot();
        t.Waza3 = GetCleanMoveSlot();
        t.Waza4 = GetCleanMoveSlot();

        if (GIVE_POKEMON_RANDOM_POKE_BALLS)
            t.BallId = GetRandomBall();

        if (GIVE_POKEMON_RANDOM_RIBBONS)
            t.SetRibbon = GetRandomRibbon();

        if (MODIFY_POKEMON_LEVELS)
            t.Level = GetModifiedLevel(t.Level);

        if (GIVE_POKEMON_HELD_ITEMS)
            t.Item = GetRandomItem();

        if (FORCE_FULLY_EVOLVE && t.Level >= FORCE_FULLY_EVOLVE_LEVEL)
        {
            (DevID Species, short Form) pk = ForceEvolveSpeciesFully((ushort)t.DevId, (byte)t.FormId);
            t.DevId = pk.Species;
            t.FormId = pk.Form;
            t.Sex = GetRandomGender(t.DevId, t.FormId); // refresh

            // refresh Toxtricity nature
            if (t.DevId is DevID.DEV_SUTORINDAA)
                t.Seikaku = GetRandomNature(t.DevId, (byte)t.FormId);
        }

        if (IsFormHeldItemDependent(t.DevId))
            t.Item = GetFormSpecificItem(t.DevId, t.FormId);

        if (IsBlueberryTrade(obj.Table[i].Label))
        {
            t.UseNickName = false;
            FixTradeTextBlueberry(Coaches[i], (ushort)oldSpecies, (ushort)t.DevId);
        }

        var oForm = oldForm is 0 ? string.Empty : $"-{oldForm}";
        var nForm = t.FormId is 0 ? string.Empty : $"-{t.FormId}";
        var oRare = oldRare is RareType.RARE ? " ★" : string.Empty;
        var nRare = t.RareType is RareType.RARE ? " ★" : string.Empty;
        RandomizerLog.Add($"{StringsNamesSpecies[(int)oldSpecies]}{oForm}{oRare} -> {StringsNamesSpecies[(int)t.DevId]}{nForm}{nRare}");
    }

    RandomizerLog.Add("");
    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
    RandomizeTradeRequirements(obj);
}

private static void RandomizeTradeRequirements(EventTradePokemonArray give)
{
    RandomizerLog.Add("== IN-GAME TRADE REQUIREMENTS ==");

    const string file = "eventTradeList_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/event/eventTradeList/");
    var path = Path.Combine(PATH_MASTER, "world/data/event/eventTradeList/");
    var obj = FlatBufferConverter.DeserializeFrom<EventTradeListArray>(Path.Combine(path, file));

    for (int i = 0; i < obj.Table.Count; i++)
    {
        var t = obj.Table[i];
        t.SendPokeDevId = GetRandomSpecies(t.SendPokeDevId);
        t.SendPokeFormId = -1; // be permissive, allow any form
    }

    var give1 = give.Table[00].PokeData;
    var give2 = give.Table[01].PokeData;
    var give3 = give.Table[32].PokeData;
    var take1 = obj.Table[00];
    var take2 = obj.Table[01];
    var take3 = obj.Table[02];
    string GetForm(byte form) => form is 0 ? string.Empty : $"-{form}";

    List<string> Trainers =
    [
        GetStringFromAHTBScript("field_trade", give1.ParentNameLabel, LANGUAGE), // Sue
        GetStringFromAHTBScript("field_trade", give2.ParentNameLabel, LANGUAGE), // Blossom
        GetStringFromAHTBScript("field_trade", give3.ParentNameLabel, LANGUAGE), // Glen
    ];

    List<string> Locations =
    [
        GetStringFromAHTBCommon("ymap_place_name", FnvHash.HashFnv1a_64("ymap_place_name_c02"), LANGUAGE), // Cascarrafa
        GetStringFromAHTBCommon("ymap_place_name", FnvHash.HashFnv1a_64("ymap_place_name_c03"), LANGUAGE), // Cortondo
        GetStringFromAHTBCommon("ymap_place_name", FnvHash.HashFnv1a_64("ymap_place_name_t03"), LANGUAGE), // Levincia
    ];

    RandomizerLog.Add($"- {Trainers[0]} ({Locations[0]}): Trade {StringsNamesSpecies[(int)take1.SendPokeDevId]} for {StringsNamesSpecies[(int)give1.DevId]}{GetForm((byte)give1.FormId)}");
    RandomizerLog.Add($"- {Trainers[1]} ({Locations[1]}): Trade {StringsNamesSpecies[(int)take2.SendPokeDevId]} for {StringsNamesSpecies[(int)give2.DevId]}{GetForm((byte)give2.FormId)}");
    RandomizerLog.Add($"- {Trainers[2]} ({Locations[2]}): Trade {StringsNamesSpecies[(int)take3.SendPokeDevId]} for {StringsNamesSpecies[(int)give3.DevId]}{GetForm((byte)give3.FormId)}");
    RandomizerLog.Add("");

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
    FixTradeTextPaldea(give, obj);
}

private static void FixTradeTextPaldea(EventTradePokemonArray give, EventTradeListArray take)
{
    var give1 = (int)give.Table[00].PokeData.DevId;
    var give2 = (int)give.Table[01].PokeData.DevId;
    var give3 = (int)give.Table[32].PokeData.DevId;
    var take1 = (int)take.Table[00].SendPokeDevId;
    var take2 = (int)take.Table[01].SendPokeDevId;
    var take3 = (int)take.Table[02].SendPokeDevId;

    // in the event that one half of the trade randomizes into one of the original species, we swap strings in two steps
    const string TRADE_GIVE = "GIVE_POKEMON";
    const string TRADE_TAKE = "TAKE_POKEMON";

    foreach (var lang in Languages)
    {
        var dest = Path.Combine(DEST_MASTER, $"message/dat/{lang}/script/");
        var lines = GetStringsScript(lang, "field_trade");
        var names = GetStringsCommon(lang, "monsname");
        var flags = GetStringFlagsScript(lang, "field_trade");

        var TRADE_A   = names[0194]; // Wooper
        var TRADE_B_1 = names[0093]; // Haunter
        var TRADE_B_2 = names[0871]; // Pincurchin
        var TRADE_C_1 = names[0872]; // Snom
        var TRADE_C_2 = names[0669]; // Flabébé

        // special handling for Wooper -> Wooper
        List<int> WooperStrings = new();
        var index = lines[09].IndexOf(TRADE_A);
        while (index is not -1)
        {
            WooperStrings.Add(index);
            index = lines[09].IndexOf(TRADE_A, index + 1);
        }

        if (lang is "English")
            lines[09] = lines[09].Remove(WooperStrings[2], TRADE_A.Length).Insert(WooperStrings[2], names[take1]);
        if (lang is not "Italian")
            lines[09] = lines[09].Remove(WooperStrings[1], TRADE_A.Length).Insert(WooperStrings[1], names[give1]);

        lines[09] = lines[09].Remove(WooperStrings[0], TRADE_A.Length).Insert(WooperStrings[0], names[take1]);

        // Wooper -> Wooper
        lines[10] = lines[10].Replace(TRADE_A, TRADE_TAKE);
        lines[10] = lines[10].Replace(TRADE_TAKE, names[take1]);

        lines[11] = lines[11].Replace(TRADE_A, TRADE_TAKE);
        lines[11] = lines[11].Replace(TRADE_TAKE, names[take1]);

        lines[12] = lines[12].Replace(TRADE_A, TRADE_GIVE);
        lines[12] = lines[12].Replace(TRADE_GIVE, names[give1]);

        lines[13] = lines[13].Replace(TRADE_A, TRADE_TAKE);
        lines[13] = lines[13].Replace(TRADE_TAKE, names[take1]);

        for (int t = 0; t < lines.Length; t++)
        {
            // Pincurchin -> Haunter
            lines[t] = lines[t].Replace(TRADE_B_2, TRADE_TAKE).Replace(TRADE_B_1, TRADE_GIVE);
            lines[t] = lines[t].Replace(TRADE_TAKE, names[take2]).Replace(TRADE_GIVE, names[give2]);

            // Flabébé -> Snom
            lines[t] = lines[t].Replace(TRADE_C_2, TRADE_TAKE).Replace(TRADE_C_1, TRADE_GIVE);
            lines[t] = lines[t].Replace(TRADE_TAKE, names[take3]).Replace(TRADE_GIVE, names[give3]);
        }

        if (!Directory.Exists(dest))
            Directory.CreateDirectory(dest);
        var write = TextFile.GetBytes(lines, flags, TextConfig);
        File.WriteAllBytes(Path.Combine(dest, "field_trade.dat"), write);
    }
}

private static void FixTradeTextBlueberry(string coach, int oldSpecies, int newSpecies)
{
    List<string> Botan = [ "Veevee", "Volivoli", "Evoli", "Vivì", "のブイを", "のブイを", "브이브이", "布布", "Vivi", "布布" ];
    List<string> Ghost = [ "DJ G-Rave", "Toutombe", "Gruff", "DJ G-Rave", "DJ BOCHI", "DJ BOCHI", "DJ MANGMANG-E", "DJ MUZAIGOU", "DJ Nitxo", "DJ GHOSTDOG" ];

    for (int i = 0; i < Languages.Count; i++)
    {
        var lang = Languages[i];
        var dest = Path.Combine(DEST_MASTER, $"message/dat/{lang}/script/");
        var text = coach is "teacher_history" or "bbteacher_head" ? "trade_01_02" : "trade_01_03";

        var hash = FnvHash.HashFnv1a_64($"coach_{coach}_{text}");
        var names = GetStringsCommon(lang, "monsname");
        var lines = GetStringsScript(lang, $"coach_{coach}");
        var flags = GetStringFlagsScript(lang, $"coach_{coach}");
        var data = File.ReadAllBytes(Path.Combine(PATH_MASTER, $"message/dat/{lang}/script/coach_{coach}.tbl"));
        List<AHTBEntry> Entries = new AHTB(data).Entries.ToList();

        var target = Entries.FirstOrDefault(z => z.Hash == hash);
        var index = Entries.IndexOf(target);

        var species = coach switch
        {
            "paldea_botan" => Botan[i], // Veevee
            "gym_ghost" => Ghost[i], // DJ G-Rave
            _ => names[oldSpecies],
        };

        var line = lines[index].Replace(species, names[newSpecies]);
        lines[index] = line;

        var write = TextFile.GetBytes(lines, flags, TextConfig);
        File.WriteAllBytes(Path.Combine(dest, $"coach_{coach}.dat"), write);
    }
}

private static void RandomizeAcademyAceTournamentRewards()
{
    RandomizerLog.Add("== ACADEMY ACE TOURNAMENT REWARDS ==");
    "Randomizing Academy Ace Tournament item rewards...".Dump();

    const string file = "sub_012_reward_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/event/sub_012/sub_012_reward/");
    var path = Path.Combine(PATH_MASTER, "world/data/event/sub_012/sub_012_reward/");
    var obj = FlatBufferConverter.DeserializeFrom<RewardTableArray>(Path.Combine(path, file));

    float totalRate = 0;
    for (int i = 0; i < obj.Table.Count; i++)
        totalRate += obj.Table[i].LotteryWeight;

    var ordered = obj.Table.OrderByDescending(z => z.LotteryWeight);
    foreach (var t in ordered.Where(z => z.ItemId is not 0))
    {
        t.ItemId = GetRandomItem();
        float rate = (float)(Math.Round((t.LotteryWeight / totalRate) * 100f));
        RandomizerLog.Add($"- {rate}% {StringsNamesItem[(int)t.ItemId]} (×{t.ItemNum})");
    }

    RandomizerLog.Add("");
    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void RandomizePokedexMilestoneRewards()
{
    "Randomizing Pokédex milestone rewards...".Dump();

    List<string> RewardData =
    [
        "world/data/ui/pokedex/reward_data/reward_data_array.bin",
        "world/data/ui/pokedex/reward_data_dlc1/reward_data_dlc1_array.bin",
        "world/data/ui/pokedex/reward_data_dlc2/reward_data_dlc2_array.bin",
    ];

    foreach (var r in RewardData)
    {
        var index = r.LastIndexOf("/") + 1;
        var f = r[index..];
        var d = r[..index];

        var dex = f switch
        {
            "reward_data_array.bin" => "PALDEA",
            "reward_data_dlc1_array.bin" => "KITAKAMI",
            "reward_data_dlc2_array.bin" => "BLUEBERRY",
            _ => throw new ArgumentException($"Invalid file: {f}"),
        };

        var file = Path.Combine(PATH_MASTER, d, f);
        var obj = FlatBufferConverter.DeserializeFrom<RewardDataArray>(file);

        // since we expanded the Paldea Pokédex, we need to adjust the milestones to account for 733 available species
        // each page on the UI must have four milestones, and since 733 is not evenly divisible by 4, we need to make the final few milestones smaller
        if (f is "reward_data_array.bin")
        {
            int GetItemQuantity(int num) => num switch
            {
                >= 130 and <= 240 => 3,
                >= 250 and <= 360 => 5,
                >= 370 and <= 480 => 8,
                >= 490 and <= 600 => 10,
                >= 610 and <= 750 => 15,
                _ => 1,
            };

            obj.Table.Clear();
            for (int i = 10; i <= 750; i += 10)
            {
                var req = i switch
                {
                    730 => 723,
                    740 => 726,
                    750 => 729,
                    _ => i,
                };

                RewardData Reward = new()
                {
                    PokedexType = PokedexType.TITAN,
                    CaptureNum = req,
                    ItemId = GetRandomItem(),
                    ItemNum = GetItemQuantity(i),
                };

                obj.Table.Add(Reward);
            }

            // for the final reward, award 100 of a random valuable item, but if it's Gimmighoul Coins, give 999 of them
            var item = (ItemID)ValuableItems[GetRandom(ValuableItems.Count)];
            var num = item is ItemID.ITEMID_SOZAI182 ? 999 : 100;
            RewardData FinalReward = new()
            {
                PokedexType = PokedexType.TITAN,
                CaptureNum = 733,
                ItemId = item,
                ItemNum = num,
            };

            obj.Table.Add(FinalReward);
        }

        else
        {
            var valuable = (ItemID)ValuableItems[GetRandom(ValuableItems.Count)];
            foreach (var t in obj.Table)
            {
                bool last = obj.Table.IndexOf(t) == obj.Table.Count - 1; // valuable item if last milestone, random if not
                t.ItemId = last ? valuable : GetRandomItem();
                t.ItemNum = last ? 3 : 1;
            }
        }

        RandomizerLog.Add($"== POKÉDEX MILESTONE REWARDS ({dex}) ==");
        foreach (var t in obj.Table)
            RandomizerLog.Add($"- {t.CaptureNum} Captured: {StringsNamesItem[(int)t.ItemId]} (×{t.ItemNum})");

        RandomizerLog.Add("");
        var data = FlatBufferConverter.SerializeFrom(obj);
        var dest = Path.Combine(DEST_MASTER, d);
        if (!Directory.Exists(dest))
            Directory.CreateDirectory(dest);
        File.WriteAllBytes(Path.Combine(dest, f), data);
    }

    RandomizerLog.Add("");
}

private static void RandomizeStarBarrageEncounters()
{
    RandomizerLog.Add("== TEAM STAR BASES: STAR BARRAGE ==");
    "Randomizing Star Barrage encounters...".Dump();

    const string file = "AjitoPokemon_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/ajito/AjitoPokemon/");
    var path = Path.Combine(PATH_MASTER, "world/data/ajito/AjitoPokemon/");
    var obj = FlatBufferConverter.DeserializeFrom<AjitoPokemonArray>(Path.Combine(path, file));

    foreach (var enc in obj.Table)
    {
        var t = enc.Table;
        var oldDev = t.DevId;
        var oldForm = t.FormNo;

        if (RANDOMIZE_POKEMON_TO_HAVE_SIMILAR_STRENGTH)
        {
            (DevID Species, short Form) pk = GetSimilarStrengthPokemon((ushort)t.DevId, (byte)t.FormNo);
            t.DevId = pk.Species;
            t.FormNo = pk.Form;
        }

        else
        {
            t.DevId = GetRandomSpecies(t.DevId);
            t.FormNo = GetRandomForm(t.DevId);
        }

        if (TYPE_THEMED_IMPORTANT_TRAINERS)
        {
            byte type = t.PokemonId switch
            {
                >= 001 and <= 100 => ThemedTypes[09], // Fire
                >= 101 and <= 200 => ThemedTypes[08], // Dark
                >= 201 and <= 300 => ThemedTypes[10], // Poison
                >= 301 and <= 400 => ThemedTypes[12], // Fighting
                >= 401 and <= 500 => ThemedTypes[11], // Fairy
                _ => GetRandomType(), // Cyclizar
            };

            (DevID Species, short Form) pk = GetTypeThemedPokemon(type);
            t.DevId = pk.Species;
            t.FormNo = pk.Form;
        }

        var oForm = oldForm is 0 ? string.Empty : $"-{oldForm}";
        var nForm = t.FormNo is 0 ? string.Empty : $"-{t.FormNo}";
        RandomizerLog.Add($"{StringsNamesSpecies[(int)oldDev]}{oForm} -> {StringsNamesSpecies[(int)t.DevId]}{nForm}");
    }

    RandomizerLog.Add("");
    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
    ModifyStarBarrageParameters();
}

private static void ModifyStarBarrageParameters()
{
    const string file = "AjitoCommonLevel_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/ajito/AjitoCommonLevel/");
    var path = Path.Combine(PATH_MASTER, "world/data/ajito/AjitoCommonLevel/");
    var obj = FlatBufferConverter.DeserializeFrom<AjitoCommonLevelArray>(Path.Combine(path, file));

    foreach (var enc in obj.Table)
    {
        var t = enc.Inner;

        if (t.ClearPokemonNum is 30) t.ClearPokemonNum = (int)(Math.Clamp((int)TARGET_STAR_BARRAGE_KO_COUNT_INITIAL, 1, 99));
        if (t.ClearPokemonNum is 50) t.ClearPokemonNum = (int)(Math.Clamp((int)TARGET_STAR_BARRAGE_KO_COUNT_REMATCH, 1, 99));

        if (MODIFY_POKEMON_LEVELS)
        {
            t.MinPokemonLv = GetModifiedLevel(t.MinPokemonLv);
            t.MaxPokemonLv = GetModifiedLevel(t.MaxPokemonLv);
        }
    }

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void RandomizeStarmobileStats()
{
    RandomizerLog.Add("== STARMOBILE STATS ==");
    "Randomizing Starmobile stats...".Dump();

    const string file = "dan_car_battle_data_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/danbattle/boss/dan_car_battle_data/");
    var path = Path.Combine(PATH_MASTER, "world/data/danbattle/boss/dan_car_battle_data/");
    var obj = FlatBufferConverter.DeserializeFrom<DanCarBattleDataArray>(Path.Combine(path, file));

    var cars = GetStringsScript(LANGUAGE, "futatsuna");
    var localeType = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_pokemon_type_00"), LANGUAGE);
    var localeAbility = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_pokemon_chara_00"), LANGUAGE);
    var localeMove = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_skill_05"), LANGUAGE);
    List<string> Level = [ "Level", "Niveau", "Level", "Livello", "Lv", "Lv", "레벨", "等级", "Nivel", "等級" ];
    List<string> StarmobileNames = cars[18..23].ToList();

    for (int i = 0; i < obj.Table.Count; i++)
    {
        List<int> Moves = new();
        var t = obj.Table[i].DanBossCarBattleStruct;
        t.TokuseiId = (TokuseiID)GetRandomAbility();

        t.Type1 = t.Type2 = !TYPE_THEMED_IMPORTANT_TRAINERS ? (MoveType)GetRandomType() : i switch
        {
            1 => (MoveType)ThemedTypes[11], // Fairy
            2 => (MoveType)ThemedTypes[12], // Fighting
            3 => (MoveType)ThemedTypes[09], // Fire
            4 => (MoveType)ThemedTypes[10], // Poison
            _ => (MoveType)ThemedTypes[08], // Dark
        };

        if (ALL_POKEMON_ONLY_KNOW_METRONOME)
            Moves = [ 118, 118, 118, 118 ]; // fill all 4 move slots with Metronome, in case only 1 move breaks the AI

        else if (TYPE_THEMED_IMPORTANT_TRAINERS)
            Moves = GetRandomMoveListWithBias(0, 0, (byte)t.Type1, (byte)t.Type2);

        else
            Moves = GetRandomMoveList();

        t.Waza1 = (WazaID)Moves[0];
        t.Waza2 = (WazaID)Moves[1];
        t.Waza3 = (WazaID)Moves[2];
        t.Waza4 = (WazaID)Moves[3];

        if (MODIFY_POKEMON_LEVELS)
            t.Level = GetModifiedLevel(t.Level);

        var abil = VagueAbilities.Contains((ushort)t.TokuseiId) ? GetDescriptiveAbilityName((ushort)t.TokuseiId) : $"{StringsNamesAbility[(int)t.TokuseiId]}";
        RandomizerLog.Add(StarmobileNames[i]);
        RandomizerLog.Add($"{Level[Languages.IndexOf(LANGUAGE)]}: {t.Level}");
        RandomizerLog.Add($"{localeType}: {StringsNamesType[(int)t.Type1]}");
        RandomizerLog.Add($"{localeAbility}: {abil}");
        RandomizerLog.Add($"{localeMove}:");
        RandomizerLog.Add($"- {StringsNamesMove[(int)t.Waza1]}");

        if (!ALL_POKEMON_ONLY_KNOW_METRONOME)
        {
            RandomizerLog.Add($"- {StringsNamesMove[(int)t.Waza2]}");
            RandomizerLog.Add($"- {StringsNamesMove[(int)t.Waza3]}");
            RandomizerLog.Add($"- {StringsNamesMove[(int)t.Waza4]}");
        }

        RandomizerLog.Add("");
    }

    FixStarmobileText(obj);
    RandomizerLog.Add("");
    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void FixStarmobileText(DanCarBattleDataArray obj)
{
    foreach (var lang in Languages)
    {
        var source = File.ReadAllBytes(Path.Combine(DEST_MASTER, $"message/dat/{lang}/script/futatsuna.dat"));
        var bosses = new TextFile(source, TextConfig).Lines;
        var types = GetStringsCommon(lang, "typename");
        var cars = bosses[18..23];
        var flags = GetStringFlagsScript(lang, "futatsuna");
        var localeType = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_pokemon_type_00"), lang);

        for (int i = 0; i < obj.Table.Count; i++)
        {
            var t = obj.Table[i].DanBossCarBattleStruct;
            var car = cars[i];
            var type = types[(int)t.Type1];
            var line = $"{car} ({localeType}: {type})";
            bosses[18 + i] = line;
        }

        var dest = Path.Combine(DEST_MASTER, $"message/dat/{lang}/script/");
        if (!Directory.Exists(dest))
            Directory.CreateDirectory(dest);
        var write = TextFile.GetBytes(bosses, flags, TextConfig);
        File.WriteAllBytes(Path.Combine(dest, "futatsuna.dat"), write);
    }
}

private static void RandomizeOgreOustinRewards()
{
    RandomizerLog.Add("== OGRE OUSTIN' ITEM REWARDS ==");
    "Randomizing Ogre Oustin' item rewards...".Dump();

    const string fileA = "reward_action_param_array.bin";
    var destA = Path.Combine(DEST_MASTER, "world/data/oniballoon/reward_action_param/");
    var pathA = Path.Combine(PATH_MASTER, "world/data/oniballoon/reward_action_param/");
    var objA = FlatBufferConverter.DeserializeFrom<RewardActionTableArray>(Path.Combine(pathA, fileA));

    const string fileL = "reward_level_param_array.bin";
    var destL = Path.Combine(DEST_MASTER, "world/data/oniballoon/reward_level_param/");
    var pathL = Path.Combine(PATH_MASTER, "world/data/oniballoon/reward_level_param/");
    var objL = FlatBufferConverter.DeserializeFrom<pkNX.Structures.FlatBuffers.SV.Balloon.RewardTableArray>(Path.Combine(pathL, fileL));

    foreach (var t in objA.Table)
    {
        foreach (var item in t.Param.RewardItems.Where(z => z.ItemId is not 0).OrderByDescending(z => z.Rate))
        {
            item.ItemId = GetRandomItem();
            RandomizerLog.Add($"- {StringsNamesItem[(int)item.ItemId]} (Weight: {item.Rate})");
        }
    }

    RandomizerLog.Add("");

    foreach (var t in objL.Table)
    {
        foreach (var item in t.Param.RewardItems.Where(z => z.ItemId is not 0).OrderByDescending(z => z.Num))
        {
            item.ItemId = GetRandomItem();
            RandomizerLog.Add($"- {StringsNamesItem[(int)item.ItemId]} (×{item.Num})");
        }
    }

    RandomizerLog.Add("");

    if (!Directory.Exists(destA)) Directory.CreateDirectory(destA);
    if (!Directory.Exists(destL)) Directory.CreateDirectory(destL);
    var dataA = FlatBufferConverter.SerializeFrom(objA);
    var dataL = FlatBufferConverter.SerializeFrom(objL);
    File.WriteAllBytes(Path.Combine(destA, fileA), dataA);
    File.WriteAllBytes(Path.Combine(destL, fileL), dataL);
}

private static void RandomizeSpecialCoachRewards()
{
    RandomizerLog.Add("== SPECIAL COACH ITEM REWARDS ==");
    "Randomizing Special Coach item rewards...".Dump();

    const string file = "ClubNpcRewardList_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/club/ClubNpcRewardList/");
    var path = Path.Combine(PATH_MASTER, "world/data/club/ClubNpcRewardList/");
    var obj = FlatBufferConverter.DeserializeFrom<ClubNpcRewardListArray>(Path.Combine(path, file));
    List<int> Coaches = [ 015, 013, 009, 014, 007, 011, 010, 012, 008, 006, 034, 027, 029, 030, 033, 028, 005, 032, 031, 002, 016, 017, 001, 406, 405, 403, 404, 365, 364, 407 ];

    foreach (var t in obj.Table)
    {
        var reward1 = t.Reward01;
        var reward2 = t.Reward02;
        var reward3 = t.Reward03;

        if (reward1.RewardType is not ClubRewardType.ITEM && reward2.RewardType is not ClubRewardType.ITEM && reward3.RewardType is not ClubRewardType.ITEM)
            continue;

        var line = "- " + StringsNamesTrainer[Coaches[(int)t.Coach]];
        if (reward1.RewardType is ClubRewardType.ITEM) { reward1.RewardId = (int)GetRandomItem(); line += $": {StringsNamesItem[(int)reward1.RewardId]} (×{reward1.Count})"; }
        if (reward2.RewardType is ClubRewardType.ITEM) { reward2.RewardId = (int)GetRandomItem(); line += $": {StringsNamesItem[(int)reward2.RewardId]} (×{reward2.Count})"; }
        if (reward3.RewardType is ClubRewardType.ITEM) { reward3.RewardId = (int)GetRandomItem(); line += $": {StringsNamesItem[(int)reward3.RewardId]} (×{reward3.Count})"; }

        RandomizerLog.Add(line);
    }

    RandomizerLog.Add("");
    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void RandomizePortoMarinadaAuctions()
{
    RandomizerLog.Add("== PORTO MARINADA AUCTION ITEMS ==");
    "Randomizing Porto Marinada auction items...".Dump();

    const string file = "gym_mizu_seri_item_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/gym/gym_mizu_seri_item/");
    var path = Path.Combine(PATH_MASTER, "world/data/gym/gym_mizu_seri_item/");
    var obj = FlatBufferConverter.DeserializeFrom<SeriItemTableArray>(Path.Combine(path, file));

    foreach (var t in obj.Table.Where(z => z.ItemId is not 0 && z.DevId is 0))
    {
        t.ItemId = GetRandomItem();
        var line = $"- {StringsNamesItem[(int)t.ItemId]} (Min: {t.MinItemNum} / Max: {t.MaxItemNum})";
        RandomizerLog.Add(line);
    }

    RandomizerLog.Add("");
    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void RandomizeItemPrinterItems()
{
    RandomizerLog.Add("== ITEM PRINTER ITEMS ==");
    "Randomizing Item Printer items...".Dump();

    const string file = "item_table_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/ui/item_machine/item_table/");
    var path = Path.Combine(PATH_MASTER, "world/data/ui/item_machine/item_table/");
    var obj = FlatBufferConverter.DeserializeFrom<ItemMachineItemTableArray>(Path.Combine(path, file));
    bool IsTeraShard(ushort item) => item is (>= 1862 and <= 1879) or 2549;

    var ordered = obj.Table.OrderByDescending(z => z.Param.Value.EmergePercent).ToList();
    foreach (var t in ordered.Where(z => !IsTeraShard((ushort)z.Param.Value.ItemId)))
    {
        t.Param.Value.ItemId = GetRandomItem();
        float rate = t.Param.Value.EmergePercent / 100f;
        var item = $"{StringsNamesItem[(int)t.Param.Value.ItemId]}";
        var num = $"Min: {t.Param.Value.LotteryItemNumMin} / Max: {t.Param.Value.LotteryItemNumMax}";
        var line = $"- {rate:0.00}% {item} ({num})";
        RandomizerLog.Add(line);
    }

    RandomizerLog.Add("");
    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void RandomizeTeraRaidItemDrops()
{
    if (!RANDOMIZE_TERA_RAID_ITEM_DROPS_BONUS && !RANDOMIZE_TERA_RAID_ITEM_DROPS_FIXED)
        return;

    "Randomizing Tera Raid Battle item drops...".Dump();

    const string fileB = "raid_lottery_reward_item_array.bin";
    var destB = Path.Combine(DEST_MASTER, "world/data/raid/raid_lottery_reward_item/");
    var pathB = Path.Combine(PATH_MASTER, "world/data/raid/raid_lottery_reward_item/");
    var objB = FlatBufferConverter.DeserializeFrom<RaidLotteryRewardItemArray>(Path.Combine(pathB, fileB));

    const string fileF = "raid_fixed_reward_item_array.bin";
    var destF = Path.Combine(DEST_MASTER, "world/data/raid/raid_fixed_reward_item/");
    var pathF = Path.Combine(PATH_MASTER, "world/data/raid/raid_fixed_reward_item/");
    var objF = FlatBufferConverter.DeserializeFrom<RaidFixedRewardItemArray>(Path.Combine(pathF, fileF));

    if (RANDOMIZE_TERA_RAID_ITEM_DROPS_BONUS)
    {
        RandomizerLog.Add("== TERA RAID BATTLES: BONUS ITEM DROPS ==");

        const int count = RaidLotteryRewardItem.RewardItemCount;
        foreach (var t in objB.Table.Where(z => z.TableName is not 0xAFC4D6C05ABEF19E))
        {
            RandomizerLog.Add($"Table 0x{t.TableName:X16}");
            float totalRate = 0;
            for (int i = 0; i < count; i++)
                totalRate += t.GetRewardItem(i).Rate;

            for (int i = 0; i < count; i++)
            {
                var drop = t.GetRewardItem(i);
                if (drop.ItemID is 0)
                    continue;

                float rate = (float)(Math.Round((drop.Rate / totalRate) * 100f, 2));
                var oldItem = $"{StringsNamesItem[(int)drop.ItemID]} (×{drop.Num})";
                drop.ItemID = GetRandomItem();
                var newItem = $"{StringsNamesItem[(int)drop.ItemID]} (×{drop.Num})";
                RandomizerLog.Add($"- {rate:00.00}% {oldItem} -> {newItem}");
            }

            RandomizerLog.Add("");
        }

        RandomizerLog.Add("");
        if (!Directory.Exists(destB))
            Directory.CreateDirectory(destB);
        var data = FlatBufferConverter.SerializeFrom(objB);
        File.WriteAllBytes(Path.Combine(destB, fileB), data);
    }

    if (RANDOMIZE_TERA_RAID_ITEM_DROPS_FIXED)
    {
        RandomizerLog.Add("== TERA RAID BATTLES: FIXED ITEM DROPS ==");

        const int count = RaidFixedRewardItem.Count;
        foreach (var t in objF.Table.Where(z => z.TableName is not 0xAFC4D6C05ABEF19E))
        {
            RandomizerLog.Add($"Table 0x{t.TableName:X16}");
            for (int i = 0; i < count; i++)
            {
                var drop = t.GetReward(i);
                if (drop.ItemID is 0)
                    continue;

                var oldItem = $"{StringsNamesItem[(int)drop.ItemID]} (×{drop.Num})";
                drop.ItemID = GetRandomItem();
                var newItem = $"{StringsNamesItem[(int)drop.ItemID]} (×{drop.Num})";
                RandomizerLog.Add($"- {oldItem} -> {newItem}");
            }

            RandomizerLog.Add("");
        }

        RandomizerLog.Add("");
        if (!Directory.Exists(destF))
            Directory.CreateDirectory(destF);
        var data = FlatBufferConverter.SerializeFrom(objF);
        File.WriteAllBytes(Path.Combine(destF, fileF), data);
    }
}

private static string GetIconName(ushort move) => GetMoveType(move) switch
{
    00 => "item_0328", // Normal
    01 => "item_0339", // Fighting
    02 => "item_0341", // Flying
    03 => "item_0340", // Poison
    04 => "item_0332", // Ground
    05 => "item_0363", // Rock
    06 => "item_0342", // Bug
    07 => "item_0344", // Ghost
    08 => "item_0358", // Steel
    09 => "item_0335", // Fire
    10 => "item_0338", // Water
    11 => "item_0347", // Grass
    12 => "item_0336", // Electric
    13 => "item_0331", // Psychic
    14 => "item_0337", // Ice
    15 => "item_0371", // Dragon
    16 => "item_0330", // Dark
    17 => "item_0329", // Fairy
    _ => throw new ArgumentException($"Invalid move: {move}"),
};

private static void EnhanceShopLineups()
{
    "Enhancing Poké Mart and Delibird Presents inventories...".Dump();

    const string item = "itemdata_array.bin";
    var pathItem = Path.Combine(PATH_MASTER, "world/data/item/itemdata/");
    var ioa = FlatBufferConverter.DeserializeFrom<ItemDataArray>(Path.Combine(pathItem, item));
    List<int> Blacklist = [ 2100, 2101, 2102, 2124, 2125, 2137, 2138, 2139, 2140, 2141, 2142, 2143, 2144, 2145, 2146, 2147, 2148, 2149, 2150, 2151, 2152, 2153, 2154, 2155 ];
    List<ushort> Materials = ioa.Table.Where(z => z.FieldPocket is FieldPocket.FPOCKET_MATERIAL && !Blacklist.Contains(z.Id)).Select(z => (ushort)z.Id).ToList();

    const string file = "friendlyshop_lineup_data_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/ui/shop/friendlyshop/friendlyshop_lineup_data/");
    var path = Path.Combine(PATH_MASTER, "world/data/ui/shop/friendlyshop/friendlyshop_lineup_data/");
    var obj = FlatBufferConverter.DeserializeFrom<LineupDataArray>(Path.Combine(path, file));
    List<ushort> ItemIDs = [ 2354, 0080, 0081, 0107, 0108, 0109, 0235, 0252, 0321, 0322, 0323, 0324, 0325, 0327, 0537, 0849, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1253, 1254, 1582, 1592, 2351, 2352, 1861, 2344, 2402, 2403, 2404, 2482, 2353 ];
    List<LineupData> Items = new();
    var ctr = 26;

    // remove Steel Bottle R/Y/B and Silver Bottle, will be replaced with our custom items afterwards
    obj.Table.Remove(obj.Table.FirstOrDefault(z => z.Item is ItemID.ITEMID_SUITOU9));
    obj.Table.Remove(obj.Table.FirstOrDefault(z => z.Item is ItemID.ITEMID_SUITOU10));
    obj.Table.Remove(obj.Table.FirstOrDefault(z => z.Item is ItemID.ITEMID_SUITOU11));
    obj.Table.Remove(obj.Table.FirstOrDefault(z => z.Item is ItemID.ITEMID_SUITOU12));

    for (int i = 0; i <= 2; i++)
    {
        var sort = 21;
        for (int k = 0; k < ItemIDs.Count; k++)
        {
            LineupData entry = new()
            {
                ItemCondkind = CondEnum.GYMBADGENUM,
                ItemCondvalue = "BADGE3",
                GymBadgeNum = 3,
                Lineupid = $"shop_delibird_{i:00}_lineup2",
                Item = (ItemID)ItemIDs[k],
                Sortnum = sort,
            };

            obj.Table.Add(entry);
            sort++;
        }
    }

    foreach (var t in Materials)
    {
        LineupData Material = new()
        {
            Lineupid = "shop_00_lineup",
            Sortnum = ctr,
            Item = (ItemID)t,
            ItemCondkind = CondEnum.NONE,
            ItemCondvalue = string.Empty,
            GymBadgeNum = 0,
        };

        obj.Table.Add(Material);
        ctr++;
    }

    foreach (var t in obj.Table.Where(z => z.Lineupid is "shop_syouten_lineup"))
        t.Sortnum++;

    LineupData MythicalPechaBerry = new()
    {
        ItemCondkind = CondEnum.SYSTEM_FLAG,
        ItemCondvalue = "FSYS_SCENARIO_GAME_CLEAR_SU2",
        GymBadgeNum = 8,
        Lineupid = "shop_syouten_lineup",
        Item = ItemID.ITEMID_GOKUZYOUMOMON,
        Sortnum = 1,
    };

    obj.Table.Add(MythicalPechaBerry);

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void EnhanceItemData()
{
    "Enhancing item properties...".Dump();

    const string file = "itemdata_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/item/itemdata/");
    var source = File.Exists(Path.Combine(dest, file)) ? Path.Combine(dest, file) : Path.Combine(PATH_MASTER, "world/data/item/itemdata/", file);
    var obj = FlatBufferConverter.DeserializeFrom<ItemDataArray>(source);
    List<int> TradeEvolutionItems = [ 0221, 0233, 0235, 0252, 0321, 0322, 0323, 0324, 0325, 0537 ];

    // make trade evolution items usable
    foreach (var t in obj.Table.Where(z => TradeEvolutionItems.Contains(z.Id)))
    {
        t.FieldFunctionType = FieldFunctionType.FIELDFUNC_EVOLUTION;
        t.WorkType = WorkType.WORKTYPE_EffectPokemon;
        t.WorkEvolutional = 1;
        t.EquipEffect = EquipEffect.SOUBI_NONE;
        t.EquipPower = 0;
    }

    // adjust Mythical Pecha Berry price
    obj.Table.FirstOrDefault(z => z.Id is 2550).Price = 100000;

    // the game sanitizes invalid items from the bag on boot, so we need to replace existing, valid items to insert our custom items
    // this removes Steel Bottle R/Y/B and Silver Bottle, so nothing of major value was lost
    obj.Table.Remove(obj.Table.FirstOrDefault(z => z.Id is 2351));
    obj.Table.Remove(obj.Table.FirstOrDefault(z => z.Id is 2352));
    obj.Table.Remove(obj.Table.FirstOrDefault(z => z.Id is 2353));
    obj.Table.Remove(obj.Table.FirstOrDefault(z => z.Id is 2354));

    ItemData BlackAugurite = new()
    {
        FieldFunctionType = FieldFunctionType.FIELDFUNC_EVOLUTION,
        FieldPocket = FieldPocket.FPOCKET_OTHER,
        IconName = "item_2351",
        Id = 2351,
        ItemType = ItemType.ITEMTYPE_EQUIP,
        NaturalGiftType = 18,
        Price = 3000,
        SetToPoke = true,
        SlotMaxNum = 999,
        SortNum = 255,
        ThrowPower = 30,
        WorkEvolutional = 1,
        WorkType = WorkType.WORKTYPE_EffectPokemon,
    };

    ItemData PeatBlock = new()
    {
        FieldFunctionType = FieldFunctionType.FIELDFUNC_EVOLUTION,
        FieldPocket = FieldPocket.FPOCKET_OTHER,
        IconName = "item_2352",
        Id = 2352,
        ItemType = ItemType.ITEMTYPE_EQUIP,
        NaturalGiftType = 18,
        Price = 3000,
        SetToPoke = true,
        SlotMaxNum = 999,
        SortNum = 255,
        ThrowPower = 30,
        WorkEvolutional = 1,
        WorkType = WorkType.WORKTYPE_EffectPokemon,
    };

    ItemData ForeignTreat = new()
    {
        FieldFunctionType = FieldFunctionType.FIELDFUNC_EVOLUTION,
        FieldPocket = FieldPocket.FPOCKET_OTHER,
        IconName = "item_2353",
        Id = 2353,
        ItemType = ItemType.ITEMTYPE_EQUIP,
        NaturalGiftType = 18,
        Price = 3000,
        SetToPoke = true,
        SlotMaxNum = 999,
        SortNum = 255,
        ThrowPower = 30,
        WorkEvolutional = 1,
        WorkType = WorkType.WORKTYPE_EffectPokemon,
    };

    ItemData LinkingCord = new()
    {
        FieldFunctionType = FieldFunctionType.FIELDFUNC_EVOLUTION,
        FieldPocket = FieldPocket.FPOCKET_OTHER,
        IconName = "item_2354",
        Id = 2354,
        ItemType = ItemType.ITEMTYPE_EQUIP,
        NaturalGiftType = 18,
        Price = 3000,
        SetToPoke = true,
        SlotMaxNum = 999,
        SortNum = 255,
        ThrowPower = 30,
        WorkEvolutional = 1,
        WorkType = WorkType.WORKTYPE_EffectPokemon,
    };

    // add the custom items!
    obj.Table.Add(BlackAugurite);
    obj.Table.Add(PeatBlock);
    obj.Table.Add(ForeignTreat);
    obj.Table.Add(LinkingCord);

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void UpdatePlibConversionTable()
{
    "Updating plib item conversion table...".Dump();

    const string file = "plib_item_conversion_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/battle/plib_item_conversion/");
    var path = Path.Combine(PATH_MASTER, "world/data/battle/plib_item_conversion/");
    var obj = FlatBufferConverter.DeserializeFrom<ItemTableArray>(Path.Combine(path, file));

    // plib table has a hardcoded max, so need to modify empty slots
    for (int i = 0; i < obj.Table.Count; i++)
    {
        if (obj.Table[i].PlibID is 53) obj.Table[i].ItemID = 0221; // King’s Rock
        if (obj.Table[i].PlibID is 54) obj.Table[i].ItemID = 0233; // Metal Coat
        if (obj.Table[i].PlibID is 55) obj.Table[i].ItemID = 2351; // Black Augurite
        if (obj.Table[i].PlibID is 56) obj.Table[i].ItemID = 2352; // Peat Block
        if (obj.Table[i].PlibID is 57) obj.Table[i].ItemID = 2353; // Foreign Treat
        if (obj.Table[i].PlibID is 58) obj.Table[i].ItemID = 2354; // Linking Cord
    }

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void EnhanceEvolutionParameters()
{
    "Enhancing evolution methods...".Dump();
    foreach (var t in PersonalInfoEdited.Table)
    {
        var evos = t.Evolutions;
        for (int i = 0; i < evos.Count; i++)
        {
            var evo = evos[i];
            if (evo.Method is (int)EvolutionType.Hisui)
            {
                if (t.Info.SpeciesNational is 0123)
                {
                    evo.Method = (int)EvolutionType.UseItem;
                    evo.Argument = 55; // Black Augurite
                }

                if (t.Info.SpeciesNational is 0217)
                {
                    evo.Method = (int)EvolutionType.UseItem;
                    evo.Argument = 56; // Peat Block
                }

                if (t.Info.SpeciesNational is 0234)
                {
                    evo.Method = (int)EvolutionType.LevelUpKnowMove;
                    evo.Argument = 828; // Psyshield Bash
                }
            }

            // trade with held item
            if (evo.Reserved3 is 6969)
            {
                var fbs = FlatBufferConverter.DeserializeFrom<ItemTableArray>(Path.Combine(DEST_MASTER, "world/data/battle/plib_item_conversion/plib_item_conversion_array.bin"));
                var lib = fbs.Table;

                for (int p = 0; p < lib.Count; p++)
                {
                    if (evo.Argument == lib[p].ItemID)
                    {
                        evo.Reserved3 = 0;
                        evo.Argument = (ushort)lib[p].PlibID;
                    }
                }
            }

            // version specific
            if (evo.Method is (int)EvolutionType.LevelUpVersion)
            {
                if (evo.Argument is 50)
                    evo.Method = (int)EvolutionType.LevelUpMorning;
                if (evo.Argument is 51)
                    evo.Method = (int)EvolutionType.LevelUpNight;
            }

            // multiplayer
            if (evo.Method is (int)EvolutionType.LevelUpUnionCircle)
                evo.Method = (int)EvolutionType.LevelUp;
        }
    }

    UpdatePersonalTable();
}

private static void EnhanceBlueberryQuests()
{
    "Updating Snacksworth BBQ requirements...".Dump();

    const string file = "release_poke_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/event/s2_sub_012_legend_poke/release_poke/");
    var path = Path.Combine(PATH_MASTER, "world/data/event/s2_sub_012_legend_poke/release_poke/");
    var obj = FlatBufferConverter.DeserializeFrom<ReleasePokeTableArray>(Path.Combine(path, file));

    foreach (var t in obj.Table)
    {
        t.MissionA = t.MissionB = true;
        t.TeamMissionA = t.TeamMissionB = false;
    }

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void EnhanceBoutiqueAndSalonLineups()
{
    "Unlocking all boutique clothing items and hairstyle options...".Dump();

    const string file = "dressup_shop_data_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/ui/shop/dressup_shop/dressup_shop_data/");
    var path = Path.Combine(PATH_MASTER, "world/data/ui/shop/dressup_shop/dressup_shop_data/");
    var obj = FlatBufferConverter.DeserializeFrom<DressupShopDataArray>(Path.Combine(path, file));

    foreach (var t in obj.Table)
        t.UnlockType = DressupHairsalonUnlockType.NONE;

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void ExpandPaldeaPokedex()
{
    "Expanding Paldea Pokédex data to include all species...".Dump();

    const string dir = "demo/appli/pokedex/zk0000_00/"; // 1024x1024 artwork
    const string dirP = "appli/tex/pokedex_capture_thum"; // 204x238 book cover, Paldea
    const string dirK = "_dlc1"; // 204x238 book cover, Kitakami
    const string dirB = "_dlc2"; // 204x238 book cover, Blueberry
    byte GetForm(ushort species) => species is 0128 or 0194 or 0901 ? (byte)1 : (byte)0;

    // Pokédex images (1024x1024)
    foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame && z.Info.Form == GetForm(z.Info.SpeciesInternal) && z.Dex is null))
    {
        var dev = t.Info.SpeciesInternal;
        var thumb = $"pokedex_capture_thum_{dev:000}";
        var target = Path.Combine(DEST_MASTER, dir, thumb + ".bntx");
        var original = Path.Combine(PATH_MASTER, dir) + t switch
        {
            _ when t.KitakamiDex is not 0 => $"{thumb}_01.bntx",  // demo/appli/pokedex/zk0000_00/pokedex_capture_thum_xxx_01.bntx
            _ when t.BlueberryDex is not 0 => $"{thumb}_02.bntx", // demo/appli/pokedex/zk0000_00/pokedex_capture_thum_xxx_02.bntx
            _ => "pokedex_capture_thum_001.bntx", // placeholder black square
        };

        if (!Directory.Exists(Path.Combine(DEST_MASTER, dir)))
            Directory.CreateDirectory(Path.Combine(DEST_MASTER, dir));
        File.Copy(original, target, true);
    }

    // Pokédex images (204x238)
    foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame && z.Info.Form == GetForm(z.Info.SpeciesInternal) && z.Dex is null))
    {
        var dev = t.Info.SpeciesInternal;
        var thumb = $"pokedex_capture_thum_{dev:000}";
        var targetDir = Path.Combine(DEST_MASTER, dirP, thumb);
        var target = Path.Combine(targetDir, thumb + ".bntx");
        var original = t switch
        {
            _ when t.KitakamiDex is not 0 => Path.Combine(PATH_MASTER, dirP + dirK, thumb, thumb + ".bntx"),  // appli/tex/pokedex_capture_thum_dlc1/pokedex_capture_thum_xxx/pokedex_capture_thum_xxx.bntx
            _ when t.BlueberryDex is not 0 => Path.Combine(PATH_MASTER, dirP + dirB, thumb, thumb + ".bntx"), // appli/tex/pokedex_capture_thum_dlc2/pokedex_capture_thum_xxx/pokedex_capture_thum_xxx.bntx
            _ => Path.Combine(PATH_MASTER, dir, "pokedex_capture_thum_001.bntx"), // placeholder black square
        };

        if (!Directory.Exists(targetDir))
            Directory.CreateDirectory(targetDir);
        File.Copy(original, target, true);
    }

    // Pokédex indexes
    foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame))
        t.Dex = new PersonalInfoDex { Index = t.Info.SpeciesNational, Group = 0 };

    UpdatePersonalTable();
    WipePokedexBlacklistData();
    FillEmptyPokedexEntries();
}

private static void WipePokedexBlacklistData()
{
    "Wiping Pokédex blacklist data...".Dump();

    const string file = "blacklist_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/ui/pokedex/blacklist/");
    var path = Path.Combine(PATH_MASTER, "world/data/ui/pokedex/blacklist/");
    var obj = FlatBufferConverter.DeserializeFrom<BlacklistArray>(Path.Combine(path, file));

    obj.Table.Clear();
    for (int i = 1; i <= 4; i++) obj.Table.Add(new Blacklist { DevNo = 0998, FormNo = i }); // Koraidon
    for (int i = 1; i <= 4; i++) obj.Table.Add(new Blacklist { DevNo = 0999, FormNo = i }); // Miraidon
    for (int i = 4; i <= 7; i++) obj.Table.Add(new Blacklist { DevNo = 1011, FormNo = i }); // Ogerpon

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void FillEmptyPokedexEntries()
{
    "Filling in empty Pokédex entries...".Dump();

    const int MAGIC = 0x42544841; // AHTB
    List<string> MissingEntries =
    [
        "ZKN_COMMENT_A_058_001", // Growlithe (Hisuian Form)
        "ZKN_COMMENT_A_059_001", // Arcanine (Hisuian Form)
        "ZKN_COMMENT_A_100_001", // Voltorb (Hisuian Form)
        "ZKN_COMMENT_A_101_001", // Electrode (Hisuian Form)
        "ZKN_COMMENT_A_157_001", // Typhlosion (Hisuian Form)
        "ZKN_COMMENT_A_215_001", // Sneasel (Hisuian Form)
        "ZKN_COMMENT_A_483_001", // Dialga (Origin Forme)
        "ZKN_COMMENT_A_484_001", // Palkia (Origin Forme)
        "ZKN_COMMENT_A_503_001", // Samurott (Hisuian Form)
        "ZKN_COMMENT_A_549_001", // Lilligant (Hisuian Form)
        "ZKN_COMMENT_A_570_001", // Zorua (Hisuian Form)
        "ZKN_COMMENT_A_571_001", // Zoroark (Hisuian Form)
        "ZKN_COMMENT_A_628_001", // Braviary (Hisuian Form)
        "ZKN_COMMENT_A_705_001", // Sliggoo (Hisuian Form)
        "ZKN_COMMENT_A_706_001", // Goodra (Hisuian Form)
        "ZKN_COMMENT_A_713_001", // Avalugg (Hisuian Form)
        "ZKN_COMMENT_A_724_001", // Decidueye (Hisuian Form)
        "ZKN_COMMENT_A_899_000", // Wyrdeer
        "ZKN_COMMENT_A_901_000", // Ursaluna
        "ZKN_COMMENT_A_903_000", // Sneasler
        "ZKN_COMMENT_A_905_000", // Enamorus (Incarnate Forme)
        "ZKN_COMMENT_A_905_001", // Enamorus (Therian Forme)
    ];

    for (int i = 0; i <= 1; i++)
    {
        var ver = i is 0 ? 'A' : 'B';
        foreach (var lang in Languages)
        {
            var data = File.ReadAllBytes(Path.Combine(PATH_MASTER, $"message/dat/{lang}/common/zukan_comment_{ver}.tbl"));
            List<AHTBEntry> OldEntries = new AHTB(data).Entries.ToList();
            List<AHTBEntry> NewEntries = new();
            for (int k = 0; k < OldEntries.Count - 1; k++)
                NewEntries.Add(OldEntries[k]);

            foreach (var t in MissingEntries)
            {
                var name = t;
                if (i is 1)
                    name = name.Replace("_A_", "_B_");
                var entry = new AHTBEntry(FnvHash.HashFnv1a_64(name), (ushort)(name.Length + 1), name);
                NewEntries.Add(entry);
            }

            NewEntries.Add(OldEntries[OldEntries.Count - 1]); // always last: msg_zukan_comment_{ver}_max

            var dest = Path.Combine(DEST_MASTER, $"message/dat/{lang}/common/zukan_comment_{ver}.tbl");
            using MemoryStream ms = new();
            {
                using BinaryWriter bw = new(File.Open(dest, FileMode.Create));
                {
                    bw.Write(MAGIC);
                    bw.Write(NewEntries.Count);

                    foreach (var t in NewEntries)
                    {
                        bw.Write((ulong)t.Hash);
                        bw.Write((ushort)t.NameLength);
                        bw.Write(Encoding.UTF8.GetBytes(t.Name));
                        bw.Write((byte)0);
                    }
                }
            }
        }
    }

    for (int i = 0; i < Languages.Count; i++)
    {
        var lang = Languages[i];
        var dest = Path.Combine(DEST_MASTER, $"message/dat/{lang}/common/");

        for (int j = 0; j <= 1; j++)
        {
            var ver = j is 0 ? 'A' : 'B';
            var zkn = $"zukan_comment_{ver}";
            var original = GetStringsCommon(lang, $"zukan_comment_{ver}");
            var lines = new string[original.Length + MissingEntries.Count];
            var flags = GetStringFlagsCommon(lang, $"zukan_comment_{ver}");

            // add original entries
            for (int a = 0; a < original.Length; a++)
                lines[a] = original[a];

            // add temporary empty entries
            for (int a = original.Length; a < lines.Length; a++)
                lines[a] = string.Empty;

            for (int k = 0; k < lines.Length; k++)
            {
                if (string.IsNullOrWhiteSpace(lines[k]) || lines[k].StartsWith("[~ "))
                    lines[k] = PlaceholderPokedexEntries[i];
            }

            // clean up form entries
            lines[1228] = lines[0658]; // Greninja
            lines[1330] = lines[0744]; // Rockruff
            for (int l = 1230; l <= 1248; l++) lines[l] = lines[0664]; // Scatterbug
            for (int l = 1249; l <= 1267; l++) lines[l] = lines[0665]; // Spewpa

            if (!Directory.Exists(dest))
                Directory.CreateDirectory(dest);
            var write = TextFile.GetBytes(lines, flags, TextConfig);
            File.WriteAllBytes(Path.Combine(dest, zkn + ".dat"), write);
        }
    }
}

private static void UpdateItemSortTables()
{
    "Updating item sort tables...".Dump();

    foreach (var lang in Languages)
    {
        Dictionary<ushort, string> ItemDictionary = new();
        var items = GetStringsCommon(lang, "itemname");

        for (ushort j = 1; j < items.Length; j++)
            ItemDictionary.Add(j, items[j]);

        ItemDictionary = ItemDictionary.OrderBy(z => z.Value).ToDictionary();
        var dir = Path.Combine(DEST_MASTER, $"message/sort/{lang}/");
        if (!Directory.Exists(dir))
            Directory.CreateDirectory(dir);

        var dest = Path.Combine(dir, "item_sort_table.dat");
        using var ms = new MemoryStream();
        {
            using var bw = new BinaryWriter(File.Open(dest, FileMode.Create));
            {
                foreach (var t in ItemDictionary)
                    bw.Write(t.Key);
            }
        }
    }
}

private static void ApplyLuaChanges()
{
    "Applying changes to Lua...".Dump();

    const string file = "main.blua";
    var dest = Path.Combine(DEST_MASTER, "script/lua/bin/release/main/");
    var path = Path.Combine(PATH_MASTER, "script/lua/bin/release/main/");
    var data = File.ReadAllBytes(Path.Combine(path, file));

    // False Dragon Titan: logic is hardcoded to check for Dondozo DevID, overwrite it with our randomized species
    var statics = Path.Combine(DEST_MASTER, "world/data/battle/eventBattlePokemon/eventBattlePokemon_array.bin");
    var obj = FlatBufferConverter.DeserializeFrom<EventBattlePokemonArray>(statics);
    var dev = obj.Table.FirstOrDefault(z => z.Label is "nusi_931_02").PokeData.DevId;
    WriteUInt16LittleEndian(data.AsSpan(0x4A974D), (ushort)dev);

    // Scatterbug, Spewpa, Vivillon: as of the 1.2.0 update, the game normally forces form 18 (Fancy) for all wild spawns
    // The raw byte replacements below replace the 664,665,666 DevID checks with out of bounds IDs, allowing all forms to spawn
    // Thank you to Aria Seri and Kas Seri for providing the offsets and an explanation for how this works!
    WriteUInt16LittleEndian(data.AsSpan(0x51285B), (ushort)0x5DD);
    WriteUInt16LittleEndian(data.AsSpan(0x516560), (ushort)0x5DD);
    WriteUInt16LittleEndian(data.AsSpan(0x51AFD1), (ushort)0x5DD);
    WriteUInt16LittleEndian(data.AsSpan(0x512864), (ushort)0x5DE);
    WriteUInt16LittleEndian(data.AsSpan(0x516569), (ushort)0x5DE);
    WriteUInt16LittleEndian(data.AsSpan(0x51AFDA), (ushort)0x5DE);
    WriteUInt16LittleEndian(data.AsSpan(0x51286D), (ushort)0x5DF);
    WriteUInt16LittleEndian(data.AsSpan(0x516572), (ushort)0x5DF);
    WriteUInt16LittleEndian(data.AsSpan(0x51AFE3), (ushort)0x5DF);

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static void ExtraChanges()
{
    if (RANDOMIZE_ROAMING_FORM_GIMMIGHOUL)
    {
        "Randomizing Roaming Form Gimmighoul...".Dump();

        var species = GetRandomSpecies(0);
        var form = GetRandomForm(species);
        var sex = GetRandomGender(species, form);
        var rare = RollForRandomShiny();
        UpdateOverworldSceneSingle("world/obj_template/parts/coin_symbol/coin_symbol_walk_/coin_symbol_walk_{0}.trsot", species, form, sex, rare);
    }

    if (ALL_POKEMON_ONLY_KNOW_METRONOME)
    {
        "Increasing Metronome base PP...".Dump();

        MoveInfoEdited.Table.FirstOrDefault(z => z.MoveID is 118).PP = 40;
        UpdateMoveTable();
    }

    if (RANDOMIZE_GENERIC_OVERWORLD_POKEMON)
    {
        "Randomizing all generic overworld Pokémon...".Dump();

        foreach (var t in GenericOverworldPokemonScenes)
            UpdateOverworldSceneGeneric(t);
        RandomizeNPCTraffic();

        // Ditto Blocks
        var dittoS = GetRandomSpecies(0);
        var dittoF = GetRandomForm(dittoS);
        var dittoG = GetRandomGender(dittoS, dittoF);
        UpdateOverworldSceneSingle("world/obj_template/field/cubeparts_2/objects_un_dlc2_normal_poke_objtemp_/objects_un_dlc2_normal_poke_objtemp_{0}.trsot", dittoS, dittoF, dittoG, RareType.NO_RARE);
        UpdateOverworldSceneSingle("world/obj_template/field/cubeparts_2/objects_un_dlc2_rare_poke_objtemp_/objects_un_dlc2_rare_poke_objtemp_{0}.trsot", dittoS, dittoF, dittoG, RareType.RARE);
    }
}

private static void RandomizeNPCTraffic()
{
    const string file = "npc_traffic_generate_array.bin";
    var dest = Path.Combine(DEST_MASTER, "world/data/event/npc_traffic_generate/");
    var path = Path.Combine(PATH_MASTER, "world/data/event/npc_traffic_generate/");
    var obj = FlatBufferConverter.DeserializeFrom<NpcTrafficGenerateTableArray>(Path.Combine(path, file));

    foreach (var t in obj.Table)
    {
        List<NpcTrafficGenerateData> Data = [ t.Data1,  t.Data2,  t.Data3,  t.Data4,  t.Data5,  t.Data6,  t.Data7,  t.Data8,  t.Data9,  t.Data10,
                                              t.Data11, t.Data12, t.Data13, t.Data14, t.Data15, t.Data16, t.Data17, t.Data18, t.Data19, t.Data20,
                                            ];

        foreach (var npc in Data)
        {
            var pk = npc.PartnerPkparam;
            if (pk.DevId is 0)
                continue;

            pk.DevId = GetRandomSpecies(pk.DevId);
            pk.FormId = GetRandomForm(pk.DevId);
            pk.Sex = GetRandomGender(pk.DevId, pk.FormId);
            pk.IsRare = GetRandom(100) < GENERIC_OVERWORLD_POKEMON_SHINY_CHANCE_PERCENT;
            pk.Scale = GetRandom(0, 256);
        }
    }

    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    var data = FlatBufferConverter.SerializeFrom(obj);
    File.WriteAllBytes(Path.Combine(dest, file), data);
}

private static List<DevID> GetSpeciesBanlist()
{
    List<DevID> Banned = new();
    if (!ALLOW_SPECIES_GEN_1) Banned.AddRange(Enumerable.Range(0001, 151).Select(z => (DevID)z));
    if (!ALLOW_SPECIES_GEN_2) Banned.AddRange(Enumerable.Range(0152, 100).Select(z => (DevID)z));
    if (!ALLOW_SPECIES_GEN_3) Banned.AddRange(Enumerable.Range(0252, 135).Select(z => (DevID)z));
    if (!ALLOW_SPECIES_GEN_4) Banned.AddRange(Enumerable.Range(0387, 107).Select(z => (DevID)z));
    if (!ALLOW_SPECIES_GEN_5) Banned.AddRange(Enumerable.Range(0494, 156).Select(z => (DevID)z));
    if (!ALLOW_SPECIES_GEN_6) Banned.AddRange(Enumerable.Range(0650, 072).Select(z => (DevID)z));
    if (!ALLOW_SPECIES_GEN_7) Banned.AddRange(Enumerable.Range(0722, 088).Select(z => (DevID)z));
    if (!ALLOW_SPECIES_GEN_8) Banned.AddRange(Enumerable.Range(0810, 096).Select(z => (DevID)z));
    if (!ALLOW_SPECIES_GEN_9) Banned.AddRange(Enumerable.Range(0906, 120).Select(z => (DevID)z));

    // failsafe if we ban all species
    if (Banned.Count is 1025)
        Banned.Clear();

    foreach (var t in PersonalInfoVanilla.Table.Where(z => !z.IsPresentInGame && z.Info.Form is 0))
        Banned.Add((DevID)t.Info.SpeciesInternal);

    if (!ALLOW_SPECIES_LEGENDARY) Banned.AddRange(Legendary.Select(z => (DevID)GetDevID(z)));
    if (!ALLOW_SPECIES_MYTHICAL)  Banned.AddRange(Mythical.Select(z => (DevID)GetDevID(z)));
    if (!ALLOW_SPECIES_PARADOX)   Banned.AddRange(Paradox.Select(z => (DevID)GetDevID((ushort)z)));

    Banned = Banned.Distinct().OrderBy(z => z).ToList();
    return Banned;
}

private static List<int> GetAbilityBanlist()
{
    List<int> Banned =
    [
        059, // Forecast
        121, // Multitype
        161, // Zen Mode
        176, // Stance Change
        193, // Wimp Out
        194, // Emergency Exit
        197, // Shields Down
        208, // Schooling
        209, // Disguise
        211, // Power Construct
        225, // RKS System
        241, // Gulp Missile
        248, // Ice Face
        258, // Hunger Switch
        278, // Zero to Hero
        301, // Embody Aspect (Teal Mask)
        302, // Embody Aspect (Hearthflame Mask)
        303, // Embody Aspect (Wellspring Mask)
        304, // Embody Aspect (Cornerstone Mask)
        307, // Tera Shift
    ];

    if (BAN_ABILITY_WONDER_GUARD)
        Banned.Add(025);

    return Banned;
}

private static List<DevID> GetSpeciesList() => Enumerable.Range(1, MAX_SPECIES_ID).Select(z => (DevID)z).Except(SpeciesBanlist).ToList();

private static List<ushort> GetLegendaryList() => new List<ushort>
{
    0144, 0145, 0146, 0150,
    0243, 0244, 0245, 0249, 0250,
    0377, 0378, 0379, 0380, 0381, 0382, 0383, 0384,
    0480, 0481, 0482, 0483, 0484, 0485, 0486, 0487, 0488,
    0638, 0639, 0640, 0641, 0642, 0643, 0644, 0645, 0646,
    0716, 0717, 0718,
    0772, 0773, 0785, 0786, 0787, 0788, 0789, 0790, 0791, 0792, 0800,
    0888, 0889, 0890, 0891, 0892, 0894, 0895, 0896, 0897, 0898, 0905,
    1001, 1002, 1003, 1004, 1007, 1008, 1014, 1015, 1016, 1017, 1024,
};

private static List<ushort> GetMythicalList() => new List<ushort>
{
    0151,
    0251,
    0385, 0386,
    0489, 0490, 0491, 0492, 0493,
    0494, 0647, 0648, 0649,
    0719, 0720, 0721,
    0801, 0802, 0807, 0808, 0809,
    0893,
    1025,
};

private static List<int> GetParadoxList() => Enumerable.Range(0984, 12).Concat(Enumerable.Range(1005, 2)).Concat(Enumerable.Range(1009, 2)).Concat(Enumerable.Range(1020, 4)).ToList();

private static List<ushort> GetSpecialPokemonList()
{
    List<ushort> SpecialPokemon = Legendary.Concat(Mythical).ToList();
    foreach (var t in Paradox)
        SpecialPokemon.Add((ushort)t);
    return SpecialPokemon;
}

private static List<int> GetPermittedAbilities() => Enumerable.Range(1, MAX_ABILITY_ID).Except(AbilityBanlist).ToList();

private static List<ushort> GetItemList()
{
    var file = Path.Combine(PATH_MASTER, "world/data/item/itemdata/itemdata_array.bin");
    var obj = FlatBufferConverter.DeserializeFrom<ItemDataArray>(file);
    List<FieldPocket> DisallowedPockets = [ FieldPocket.FPOCKET_WAZA, FieldPocket.FPOCKET_EVENT, FieldPocket.FPOCKET_MATERIAL ];

    // despite having item data, info strings, and icons, the game has error handling that can display them as "None" when received
    // the game does not have any error handling if they're found as field items, but they eventually get removed from the bag anyway
    List<int> DisallowedItems =
    [
        0016, // Cherish Ball
        0111, // Odd Keystone
        0224, // Cleanse Tag
        0485, // Red Apricorn
        0486, // Blue Apricorn
        0487, // Yellow Apricorn
        0488, // Green Apricorn
        0489, // Pink Apricorn
        0490, // White Apricorn
        0491, // Black Apricorn
        0500, // Park Ball
        0708, // Lumiose Galette
        0709, // Shalour Sable
        1785, // Strange Ball
        1888, // Baguette
    ];

    if (!ALLOW_RANDOM_PICNIC_ITEMS)
        DisallowedPockets.Add(FieldPocket.FPOCKET_PICNIC);

    // we only want picks and ingredients in the pool; accessories should be capped at 1
    else
    {
        for (int i = 1947; i <= 1955; i++) DisallowedItems.Add(i); // unused
        for (int i = 2311; i <= 2333; i++) DisallowedItems.Add(i); // Bottles, Cups, Tablecloths, Balls, Picnic Set
        for (int i = 2348; i <= 2384; i++) DisallowedItems.Add(i); // Bottles, Cups, Tablecloths, Balls
        for (int i = 2395; i <= 2400; i++) DisallowedItems.Add(i); // Dishes
        for (int i = 2417; i <= 2437; i++) DisallowedItems.Add(i); // Chairs
        for (int i = 2551; i <= 2552; i++) DisallowedItems.Add(i); // Blueberry Tablecloth, Blueberry Chairs
    }

    List<ushort> Items = obj.Table.Where(z => !DisallowedPockets.Contains(z.FieldPocket) && !DisallowedItems.Contains(z.Id)).Select(z => (ushort)z.Id).ToList();
    Items.Add(2351); // Black Augurite
    Items.Add(2352); // Peat Block
    Items.Add(2353); // Foreign Treat
    Items.Add(2354); // Linking Cord

    Items = Items.Distinct().ToList();
    return Items;
}

private static List<int> GetTMList()
{
    var file = Path.Combine(PATH_MASTER, "world/data/item/itemdata/itemdata_array.bin");
    var obj = FlatBufferConverter.DeserializeFrom<ItemDataArray>(file);
    List<int> TechnicalMachines = obj.Table.Where(z => z.FieldPocket is FieldPocket.FPOCKET_WAZA).Select(z => z.Id).ToList();
    return TechnicalMachines;
}

private static List<int> GetPermittedMoves()
{
    if (ALLOW_ALL_UNUSABLE_MOVES_TO_BE_USED)
    {
        foreach (var t in MoveInfoEdited.Table)
        {
            t.CanUseMove = true;
            t.FlagMetronome = true;
        }

        UpdateMoveTable();
    }

    List<int> Moves = MoveInfoEdited.Table.Where(z => z.CanUseMove).Select(z => (int)z.MoveID).ToList();
    return Moves;
}

private static DevID GetRandomSpecies(DevID dev)
{
    var nat = GetNationalDexID((ushort)dev);
    var species = SpeciesList[GetRandom(SpeciesList.Count)];
    bool swap = FORCE_SWAP_SPECIES_LEGENDARY && Legendary.Contains(nat)
             || FORCE_SWAP_SPECIES_MYTHICAL && Mythical.Contains(nat)
             || FORCE_SWAP_SPECIES_PARADOX && Paradox.Contains(nat)
             || FORCE_SWAP_SPECIES_ALL_SPECIAL_POKEMON && SpecialPokemon.Contains(nat);

    if (swap)
    {
        List<DevID> PossiblePokemon = nat switch
        {
            _ when SpecialPokemon.Contains(nat) && FORCE_SWAP_SPECIES_ALL_SPECIAL_POKEMON => SpecialPokemon.Select(z => (DevID)z).Except(SpeciesBanlist).ToList(),
            _ when Legendary.Contains(nat)      && FORCE_SWAP_SPECIES_LEGENDARY           => Legendary.Select(z => (DevID)z).Except(SpeciesBanlist).ToList(),
            _ when Mythical.Contains(nat)       && FORCE_SWAP_SPECIES_MYTHICAL            => Mythical.Select(z => (DevID)z).Except(SpeciesBanlist).ToList(),
            _ when Paradox.Contains(nat)        && FORCE_SWAP_SPECIES_PARADOX             => Paradox.Select(z => (DevID)z).Except(SpeciesBanlist).ToList(),
            _ => new List<DevID>(),
        };

        // if our ban list does not include any eligible species, return a random species
        if (PossiblePokemon.Count is 0)
            return species;

        species = PossiblePokemon[GetRandom(PossiblePokemon.Count)];
        return (DevID)GetDevID((ushort)species);
    }

    return species;
}

private static (DevID Species, short Form) GetSimilarStrengthPokemon(ushort species, byte form)
{
    // prioritize force swap, don't return similar strength
    if (FORCE_SWAP_SPECIES_LEGENDARY && Legendary.Contains(GetNationalDexID(species))) return (((DevID)GetRandomSpecies((DevID)0150), (short)GetRandomForm((DevID)0150)));
    if (FORCE_SWAP_SPECIES_MYTHICAL && Mythical.Contains(GetNationalDexID(species)))   return (((DevID)GetRandomSpecies((DevID)0151), (short)GetRandomForm((DevID)0151)));
    if (FORCE_SWAP_SPECIES_PARADOX && Paradox.Contains(GetNationalDexID(species)))     return (((DevID)GetRandomSpecies((DevID)0978), (short)GetRandomForm((DevID)0978)));

    var bst = GetBaseStatTotal(species, form);
    List<(DevID Species, short Form)> Possible = new();
    bool HasManyForms(ushort species) => species is 0025 or 0479 or 0493 or 0664 or 0665 or 0666 or 0669 or 0670 or 0671 or 0774 or 0869;

    foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame && !SpeciesBanlist.Contains((DevID)z.Info.SpeciesInternal)))
    {
        // don't randomize into the same species/form
        if (t.Info.SpeciesInternal == species && t.Info.Form == form)
            continue;

        // inaccessible
        if (t.Info.SpeciesNational is 1007 or 1008 && t.Info.Form is not 0 || t.Info.SpeciesNational is 1017 && t.Info.Form >= 4)
            continue;

        // skip battle only forms if settings disallow them
        if (!ALLOW_BATTLE_ONLY_FORMS && BattleOnlyForms.Contains(t.Info.SpeciesNational) && t.Info.Form is not 0)
            continue;

        // species like Vivillon and Minior dilute the pool of possible swaps, so handle their other forms separately
        if (HasManyForms(t.Info.SpeciesNational) && t.Info.Form is not 0)
            continue;

        var total = t.Base.HP + t.Base.ATK + t.Base.DEF + t.Base.SPA + t.Base.SPD + t.Base.SPE;
        var hi = bst + (bst / 16);
        var lo = bst - (bst / 16);
        if (total <= hi && total >= lo)
        {
            var addForm = t.Info.Form;
            if (HasManyForms(t.Info.SpeciesNational))
                addForm = GetRandomForm((DevID)t.Info.SpeciesInternal);
            Possible.Add(((DevID)t.Info.SpeciesInternal, (short)addForm));
        }
    }

    if (Possible.Count is 0)
        return ((DevID)species, (short)form);
    return Possible[GetRandom(Possible.Count)];
}

private static (DevID Species, short Form) GetTypeThemedPokemon(byte type)
{
    List<(DevID Species, short Form)> Possible = new();
    bool HasManyForms(ushort species) => species is 0025 or 0479 or 0664 or 0665 or 0666 or 0669 or 0670 or 0671 or 0774 or 0869;

    foreach (var t in PersonalInfoEdited.Table.Where(z => z.IsPresentInGame && !SpeciesBanlist.Contains((DevID)z.Info.SpeciesInternal) && (z.Type1 == type || z.Type2 == type)))
    {
        // inaccessible
        if (t.Info.SpeciesNational is 1007 or 1008 && t.Info.Form is not 0 || t.Info.SpeciesNational is 1017 && t.Info.Form >= 4)
            continue;

        // skip battle only forms if settings disallow them
        if (!ALLOW_BATTLE_ONLY_FORMS && BattleOnlyForms.Contains(t.Info.SpeciesNational) && t.Info.Form is not 0)
            continue;

        // species like Vivillon and Minior dilute the pool of possible swaps, so handle their other forms separately
        if (HasManyForms(t.Info.SpeciesNational) && t.Info.Form is not 0)
            continue;

        var addForm = (short)t.Info.Form;
        if (HasManyForms(t.Info.SpeciesNational))
            addForm = GetRandomForm((DevID)t.Info.SpeciesInternal);
        Possible.Add(((DevID)t.Info.SpeciesInternal, (short)addForm));
    }

    return Possible[GetRandom(Possible.Count)];
}

private static PokeDataFull ForceEvolveSpeciesOnce(PokeDataFull pk)
{
    // Combee and Salandit can only evolve if they're female
    if (pk.DevId is DevID.DEV_MITUHANII or DevID.DEV_YATOUMORI && pk.Sex is SexType.MALE)
        return pk;

    // female Kirlia can only evolve into Gardevoir
    if (pk.DevId is DevID.DEV_KIRURIA && pk.Sex is SexType.FEMALE)
    {
        pk.DevId = DevID.DEV_SAANAITO;
        return pk;
    }

    // male Snorunt can only evolve into Glalie
    if (pk.DevId is DevID.DEV_YUKIWARASI && pk.Sex is SexType.MALE)
    {
        pk.DevId = DevID.DEV_ONIGOORI;
        return pk;
    }

    // Toxel evolves into a different form of Toxtricity depending on its nature
    if (pk.DevId is DevID.DEV_EREZUN)
    {
        pk.DevId = DevID.DEV_SUTORINDAA;
        pk.FormId = ToxtricityNaturesAmped.Contains((byte)(pk.Seikaku - 1)) ? (short)0 : (short)1;
        return pk;
    }

    // does not evolve
    var sel = PersonalInfoVanilla.Table.FirstOrDefault(z => z.Info.SpeciesInternal == (ushort)pk.DevId && z.Info.Form == pk.FormId);
    if (sel.Evolutions.Count is 0)
        return pk;

    // if an evolution requires a specific gender, abide by that logic, otherwise choose a random evolution index
    var evo = IsGenderedEvolution((ushort)pk.DevId, (byte)pk.FormId) && pk.Sex is not SexType.DEFAULT ? (int)(pk.Sex - 1) : GetRandom(sel.Evolutions.Count);
    pk.DevId = (DevID)sel.Evolutions[evo].SpeciesInternal;
    pk.FormId = (short)sel.Evolutions[evo].Form;

    return pk;
}

private static (DevID Species, short Form) ForceEvolveSpeciesFully(ushort species, byte form)
{
    // does not evolve
    var stage1 = PersonalInfoVanilla.Table.FirstOrDefault(z => z.Info.SpeciesInternal == species && z.Info.Form == form);
    if (stage1.Evolutions.Count is 0)
        return ((DevID)species, (short)form);

    var select1 = GetRandom(stage1.Evolutions.Count);
    var evoS = stage1.Evolutions[select1].SpeciesInternal;
    var evoF = stage1.Evolutions[select1].Form;

    // two-stage family
    var stage2 = PersonalInfoVanilla.Table.FirstOrDefault(z => z.Info.SpeciesInternal == evoS && z.Info.Form == evoF);
    if (stage2.Evolutions.Count is 0)
        return ((DevID)evoS, (short)evoF);

    // three-stage family
    var select2 = GetRandom(stage2.Evolutions.Count);
    return ((DevID)stage2.Evolutions[select2].SpeciesInternal, (short)stage2.Evolutions[select2].Form);
}

private static byte GetRandomForm(DevID dev)
{
    var species = GetNationalDexID((ushort)dev);
    var count = GetFormCount((ushort)dev);

    if (UnavailableForms.Contains(species))
        return 0;

    if (BattleOnlyForms.Contains(species))
    {
        if (!ALLOW_BATTLE_ONLY_FORMS)
            return species is 0774 ? (byte)GetRandom(7) : (byte)0;
        return (byte)GetRandom(count);
    }

    return species switch
    {
        0025 or 0080 => GetValidForm(species), // Partner Pikachu, Mega Slowbro
        0658 => (byte)GetRandom(2), // Ash-Greninja
        0670 => (byte)GetRandom(5), // Eternal Flower Floette
        0800 => (byte)GetRandom(3), // Ultra Necrozma
        0890 or 1007 or 1008 => 0, // Eternatus, Koraidon, Miraidon
        1017 => (byte)GetRandom(4), // Terastallized Ogerpon
        _ => (byte)GetRandom(count),
    };
}

private static SexType GetRandomGender(DevID species, short form)
{
    var sel = PersonalInfoVanilla.Table.FirstOrDefault(z => z.Info.SpeciesInternal == (ushort)species && z.Info.Form == form).Gender;
    return sel.Group switch
    {
        SexGroup.MALE => SexType.MALE,
        SexGroup.FEMALE => SexType.FEMALE,
        SexGroup.UNKNOWN => SexType.DEFAULT,
        _ => GetRandom(100) < sel.Ratio ? SexType.FEMALE : SexType.MALE,
    };
}

private static byte GetSceneGender(ushort species, short form, byte sex)
{
    var sel = PersonalInfoVanilla.Table.FirstOrDefault(z => z.Info.SpeciesInternal == species && z.Info.Form == form).Gender;
    return sel.Group switch
    {
        SexGroup.MALE => 0,
        SexGroup.FEMALE => 1,
        _ when IsGenderVariant(species, form) => (byte)(sex - 1),
        _ => 0,
    };
}

private static TokuseiType GetRandomAbilitySlot()
{
    var max = ALLOW_RANDOM_HIDDEN_ABILITIES ? 5 : 4;
    return (TokuseiType)GetRandom(2, max);
}


private static SeikakuType GetRandomNature(DevID species, byte form)
{
    // Toxtricity's form is dependent on nature
    if (species is DevID.DEV_SUTORINDAA)
    {
        List<byte> Natures = form is 0 ? ToxtricityNaturesAmped : ToxtricityNaturesLowKey;
        return (SeikakuType)((Natures[GetRandom(Natures.Count)]) + 1);
    }

    return (SeikakuType)GetRandom(1, 26);
}

private static TokuseiType GetRandomAbilitySetter() => ALLOW_RANDOM_HIDDEN_ABILITIES ? TokuseiType.RANDOM_123 : TokuseiType.RANDOM_12;
private static byte GetRandomType() => (byte)GetRandom(MAX_TYPE_ID + 1);
private static BallType GetRandomBall() => (BallType)GetRandom(1, MAX_BALL_ID + 1);
private static RibbonType GetRandomRibbon() => (RibbonType)GetRandom(1, MAX_RIBBON_ID + 1);
private static int GetModifiedLevel(int level) => Math.Clamp((int)(level * (1.0f + ((float)LEVEL_MODIFIER_PERCENT / 100.0f))), 1, 100);
private static ushort GetRandomAbility() => (ushort)PermittedAbilities[GetRandom(PermittedAbilities.Count)];
private static ItemID GetRandomItem() => (ItemID)ItemList[GetRandom(ItemList.Count)];
private static WazaSet GetCleanMoveSlot() => new WazaSet() { WazaId = WazaID.WAZA_NULL, PointUp = 0 };
private static RareType RollForRandomShiny() => GetRandom(4096) is 0 ? RareType.RARE : RareType.NO_RARE;

private static ulong GetRandomTM()
{
    List<int> TMs = TechnicalMachineList;
    TMs.Remove(1230); // unused TM00
    return (ulong)TMs[GetRandom(TMs.Count)];
}

private static bool IsFormHeldItemDependent(DevID species) => GetNationalDexID((ushort)species) is 0483 or 0484 or 0487 or 0493 or 0888 or 0889 or 1017;
private static ItemID GetFormSpecificItem(DevID species, short form) => GetNationalDexID((ushort)species) switch
{
    0483 when form is 1 => ItemID.ITEMID_DAIKONGOUDAMA, // Adamant Crystal
    0484 when form is 1 => ItemID.ITEMID_DAISIRATAMA, // Lustrous Globe
    0487 when form is 1 => ItemID.ITEMID_DAIHAKKINDAMA, // Griseous Core
    0888 when form is 1 => ItemID.ITEMID_KUTITATURUGI, // Rusted Sword
    0889 when form is 1 => ItemID.ITEMID_KUTITATATE, // Rusted Shield

    0493 => GetArceusPlate(form),
    1017 => GetOgerponMask(form),

    _ => ItemID.ITEMID_NONE,
};

private static ItemID GetArceusPlate(short form)
{
    List<ushort> Plates = [ 0000, 0303, 0306, 0304, 0305, 0309, 0308, 0310, 0313, 0298, 0299, 0301, 0300, 0307, 0302, 0311, 0312, 0644 ];
    return (ItemID)Plates[form];
}

private static ItemID GetOgerponMask(short form)
{
    List<ushort> Masks = [ 0000, 2407, 2408, 2406, 0000, 2407, 2408, 2406 ];
    return (ItemID)Masks[form];
}

private static List<int> GetRandomMoveList()
{
    List<int> Banned =
    [
        000, // ———
        165, // Struggle
        464, // Dark Void
        621, // Hyperspace Fury
        783, // Aura Wheel
    ];

    if (!ALLOW_STARMOBILE_TORQUE_MOVES)
        Banned.AddRange(Enumerable.Range(896, 5));

    List<int> Moves = PermittedMoves.Except(Banned).ToList();
    pkNX.Randomization.Util.Shuffle(Moves);
    return Moves;
}

private static List<int> GetRandomMoveListWithBias(ushort species, byte form, byte type1, byte type2)
{
    List<int> Moves = new();
    foreach (var t in MoveInfoEdited.Table.Where(z => z.MoveID is not (000 or 165) && z.CanUseMove && (z.Type == type1 || z.Type == type2)))
    {
        if (IsLimitedUseMove(t.MoveID, species, form))
            continue;
        Moves.Add(t.MoveID);
    }

    pkNX.Randomization.Util.Shuffle(Moves);
    return Moves;
}

private static bool IsLimitedUseMove(ushort move, ushort species, byte form) => move switch
{
    464 when species is not 0491 => true, // Darkrai: Dark Void
    783 when species is not 0877 => true, // Morpeko: Aura Wheel
    621 when species is not 0720 && form is not 1 => true, // Hoopa Unbound: Hyperspace Fury
    >= 896 and <= 900 when !ALLOW_STARMOBILE_TORQUE_MOVES => true, // Starmobile: Torque moves
    _ => false,
};

private static byte GetValidForm(ushort species)
{
    var count = GetFormCount(species);
    var banned = species is 0025 ? (byte)8 : (byte)1;
    var form = (byte)GetRandom(count - 1);
    if (form == banned)
        form++;
    return form;
}

private static GemType GetTeraType(DevID species, byte form, GemType gem, bool raid, bool specific)
{
    // Ogerpon: must match form
    if (species is DevID.DEV_KAMENONI)
    {
        return form switch
        {
            0 or 4 => GemType.KUSA, // Teal Mask
            1 or 5 => GemType.MIZU, // Wellspring Mask
            2 or 6 => GemType.HONOO, // Hearthflame Mask
            3 or 7 => GemType.IWA, // Cornerstone Mask
            _ => throw new ArgumentException($"Invalid form: {form}"),
        };
    }

    // Terapagos: only Stellar
    if (species is DevID.DEV_KODAIGAME)
        return GemType.NIJI;

    // Tera Raid Battles: random
    if (raid)
        return GemType.RANDOM;

    // disallow random, abide by standard types
    if (!ALLOW_FULLY_RANDOM_TERA_TYPES)
        return specific ? GetStandardTeraType((ushort)species, form) : GemType.DEFAULT;

    // roll for a chance of Stellar, otherwise random
    bool chance = GetRandom(100) < STELLAR_TERA_TYPE_CHANCE_PERCENT;
    if (chance)
        return GemType.NIJI;
    return specific ? (GemType)GetRandom(2, 20) : GemType.RANDOM;
}

private static GemType GetStandardTeraType(ushort species, byte form)
{
    var sel = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesInternal == species && z.Info.Form == form);
    var rand = GetRandom(2);
    var gem = rand is 0 ? (GemType)(sel.Type1 + 2) : (GemType)(sel.Type2 + 2);
    return gem;
}

private static bool IsValidForm(ushort dev, byte form)
{
    var species = GetNationalDexID(dev);
    if (BattleOnlyForms.Contains(species))
    {
        if (!ALLOW_BATTLE_ONLY_FORMS)
            return species is 0774 ? form is <= 6 : form is 0;
        return true;
    }

    // Koraidon, Miraidon, and Ogerpon should only appear in base form
    if (species is 1007 or 1008 or 1017)
        return form is 0;
    return true;
}

private static PersonalTable GetVanillaPersonalTable()
{
    var file = Path.Combine(PATH_MASTER, "avalon/data/personal_array.bin");
    var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(file);
    return obj;
}

private static void UpdatePersonalTable()
{
    var dest = Path.Combine(DEST_MASTER, "avalon/data/");
    var data = FlatBufferConverter.SerializeFrom(PersonalInfoEdited);
    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    File.WriteAllBytes(Path.Combine(dest, "personal_array.bin"), data);
}

private static WazaTable GetVanillaMoveTable()
{
    var file = Path.Combine(PATH_MASTER, "avalon/data/waza_array.bin");
    var obj = FlatBufferConverter.DeserializeFrom<WazaTable>(file);
    return obj;
}

private static void UpdateMoveTable()
{
    var dest = Path.Combine(DEST_MASTER, "avalon/data/");
    var data = FlatBufferConverter.SerializeFrom(MoveInfoEdited);
    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    File.WriteAllBytes(Path.Combine(dest, "waza_array.bin"), data);
}

private static ushort GetDevID(ushort species) => PersonalInfoVanilla.Table.FirstOrDefault(z => z.Info.SpeciesNational == species).Info.SpeciesInternal;
private static ushort GetNationalDexID(ushort species) => PersonalInfoVanilla.Table.FirstOrDefault(z => z.Info.SpeciesInternal == species).Info.SpeciesNational;
private static bool GetIsPresentInGame(ushort species, byte form) => PersonalInfoVanilla.Table.FirstOrDefault(z => z.Info.SpeciesInternal == species && z.Info.Form == form).IsPresentInGame;
private static byte GetFormCount(ushort species) => (byte)PersonalInfoVanilla.Table.Where(z => z.Info.SpeciesInternal == species).ToArray().Length;
private static byte GetMoveType(ushort move) => MoveInfoEdited.Table.FirstOrDefault(z => z.MoveID == move).Type;

private static int GetBaseStatTotal(ushort species, byte form)
{
    var sel = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesInternal == species && z.Info.Form == form);
    var total = sel.Base.HP + sel.Base.ATK + sel.Base.DEF + sel.Base.SPA + sel.Base.SPD + sel.Base.SPE;
    return total;
}

private static (byte Type1, byte Type2) GetPokemonTypes(ushort species, byte form)
{
    var sel = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesInternal == species && z.Info.Form == form);
    return (sel.Type1, sel.Type2);
}

private static List<WazaID> GetCurrentMoves(ushort species, byte form, byte level)
{
    var sel = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesInternal == species && z.Info.Form == form);
    List<ushort> SelfFaintMoves = [ 120, 153, 262, 361, 461, 515, 802 ];
    List<WazaID> Moves = sel.Learnset.Where(z => !SelfFaintMoves.Contains(z.Move) && z.Move is not 906 && z.Level <= level).Distinct().TakeLast(4).Select(z => (WazaID)z.Move).ToList();

    // always give a list of 4 moves
    if (Moves.Count < 4)
    {
        do Moves.Add(0);
        while (Moves.Count is not 4);
    }

    return Moves;
}

private static string[] GetStringsCommon(string lang, string file)
{
    bool exists = File.Exists(Path.Combine(DEST_MASTER, $"message/dat/{lang}/common/{file}.dat"));
    var source = file.StartsWith("item") && exists ? DEST_MASTER : PATH_MASTER;
    var path = Path.Combine(source, $"message/dat/{lang}/common/{file}.dat");
    var data = File.ReadAllBytes(path);
    return new TextFile(data, TextConfig).Lines;
}

private static string[] GetStringsScript(string lang, string file)
{
    var path = Path.Combine(PATH_MASTER, $"message/dat/{lang}/script/{file}.dat");
    var data = File.ReadAllBytes(path);
    return new TextFile(data, TextConfig).Lines;
}

private static ushort[] GetStringFlagsCommon(string lang, string file)
{
    bool exists = File.Exists(Path.Combine(DEST_MASTER, $"message/dat/{lang}/common/{file}.dat"));
    var source = file.StartsWith("item") && exists ? DEST_MASTER : PATH_MASTER;
    var path = Path.Combine(source, $"message/dat/{lang}/common/{file}.dat");
    var data = File.ReadAllBytes(path);
    return new TextFile(data, TextConfig).Flags;
}

private static ushort[] GetStringFlagsScript(string lang, string file)
{
    var path = Path.Combine(PATH_MASTER, $"message/dat/{lang}/script/{file}.dat");
    var data = File.ReadAllBytes(path);
    return new TextFile(data, TextConfig).Flags;
}

private static string GetStringFromAHTBCommon(string file, ulong hash, string lang)
{
    var path = Path.Combine(PATH_MASTER, $"message/dat/{lang}/common/{file}.dat");
    var ahtb = Path.Combine(PATH_MASTER, $"message/dat/{lang}/common/{file}.tbl");
    var data = File.ReadAllBytes(path);
    var text = new TextFile(data, TextConfig).Lines;

    var dataAHTB = File.ReadAllBytes(ahtb);
    List<AHTBEntry> Entries = new AHTB(dataAHTB).Entries.ToList();

    var target = Entries.FirstOrDefault(z => z.Hash == hash);
    var index = Entries.IndexOf(target);
    if (target is null)
        return "INVALID";
    return text[index];
}

private static string GetStringFromAHTBScript(string file, ulong hash, string lang)
{
    var path = Path.Combine(PATH_MASTER, $"message/dat/{lang}/script/{file}.dat");
    var ahtb = Path.Combine(PATH_MASTER, $"message/dat/{lang}/script/{file}.tbl");
    var data = File.ReadAllBytes(path);
    var text = new TextFile(data, TextConfig).Lines;

    var dataAHTB = File.ReadAllBytes(ahtb);
    List<AHTBEntry> Entries = new AHTB(dataAHTB).Entries.ToList();

    var target = Entries.FirstOrDefault(z => z.Hash == hash);
    var index = Entries.IndexOf(target);
    if (target is null)
        return "INVALID";
    return text[index];
}

private static string GetDescriptiveAbilityName(ushort ability)
{
    List<string> AbilitySuffixes = [ StringsNamesSpecies[0896], StringsNamesSpecies[0897], StringsNamesItem[2405], StringsNamesItem[2408], StringsNamesItem[2407], StringsNamesItem[2406] ];
    var suffix = AbilitySuffixes[VagueAbilities.IndexOf(ability)];
    return $"{StringsNamesAbility[ability]} ({suffix})";
}

private static void UpdateOverworldSceneSingle(string scene, DevID species, short form, SexType sex, RareType rare)
{
    var index = scene.LastIndexOf("/") + 1;
    var d = scene[..index];

    for (int k = 0; k <= 1; k++)
    {
        var f = string.Format(scene[index..], k);
        var dest = Path.Combine(DEST_MASTER, d);
        var path = Path.Combine(PATH_MASTER, d);
        var data = File.ReadAllBytes(Path.Combine(path, f));
        var obj = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(data);

        foreach (var o in obj.Objects)
        {
            foreach (var s in o.SubObjects.Where(z => z.Type is "ti_PokemonModelComponent" or "ti_FieldPokemonComponent"))
            {
                switch (s.Type)
                {
                    case "ti_PokemonModelComponent":
                    {
                        var pk = FlatBufferConverter.DeserializeFrom<TIPokemonModelComponent>(s.Data);
                        pk.DevId = (ushort)species;
                        pk.FormId = (ushort)form;
                        pk.Sex = (sbyte)GetSceneGender(pk.DevId, (short)pk.FormId, (byte)sex);
                        pk.IsRare = rare is RareType.RARE;
                        s.Data = FlatBufferConverter.SerializeFrom(pk);
                        break;
                    }

                    case "ti_FieldPokemonComponent":
                    default:
                    {
                        var pk = FlatBufferConverter.DeserializeFrom<TIFieldPokemonComponent>(s.Data);
                        pk.DevId = (ushort)species;
                        pk.FormId = (ushort)form;
                        pk.Sex = (sbyte)GetSceneGender(pk.DevId, (short)pk.FormId, (byte)sex);
                        s.Data = FlatBufferConverter.SerializeFrom(pk);
                        break;
                    }
                }
            }

            // inner
            foreach (var s in o.SubObjects)
            {
                foreach (var i in s.SubObjects.Where(z => z.Type is "ti_PokemonModelComponent" or "ti_FieldPokemonComponent"))
                {
                    switch (i.Type)
                    {
                        case "ti_PokemonModelComponent":
                        {
                            var pk = FlatBufferConverter.DeserializeFrom<TIPokemonModelComponent>(i.Data);
                            pk.DevId = (ushort)species;
                            pk.FormId = (ushort)form;
                            pk.Sex = (sbyte)GetSceneGender(pk.DevId, (short)pk.FormId, (byte)sex);
                            pk.IsRare = rare is RareType.RARE;
                            i.Data = FlatBufferConverter.SerializeFrom(pk);
                            break;
                        }

                        case "ti_FieldPokemonComponent":
                        default:
                        {
                            var pk = FlatBufferConverter.DeserializeFrom<TIFieldPokemonComponent>(i.Data);
                            pk.DevId = (ushort)species;
                            pk.FormId = (ushort)form;
                            pk.Sex = (sbyte)GetSceneGender(pk.DevId, (short)pk.FormId, (byte)sex);
                            i.Data = FlatBufferConverter.SerializeFrom(pk);
                            break;
                        }
                    }
                }

                // inner
                foreach (var s2 in s.SubObjects)
                {
                    foreach (var i in s2.SubObjects.Where(z => z.Type is "ti_PokemonModelComponent" or "ti_FieldPokemonComponent"))
                    {
                        switch (i.Type)
                        {
                            case "ti_PokemonModelComponent":
                            {
                                var pk = FlatBufferConverter.DeserializeFrom<TIPokemonModelComponent>(i.Data);
                                pk.DevId = (ushort)species;
                                pk.FormId = (ushort)form;
                                pk.Sex = (sbyte)GetSceneGender(pk.DevId, (short)pk.FormId, (byte)sex);
                                pk.IsRare = rare is RareType.RARE;
                                i.Data = FlatBufferConverter.SerializeFrom(pk);
                                break;
                            }

                            case "ti_FieldPokemonComponent":
                            default:
                            {
                                var pk = FlatBufferConverter.DeserializeFrom<TIFieldPokemonComponent>(i.Data);
                                pk.DevId = (ushort)species;
                                pk.FormId = (ushort)form;
                                pk.Sex = (sbyte)GetSceneGender(pk.DevId, (short)pk.FormId, (byte)sex);
                                i.Data = FlatBufferConverter.SerializeFrom(pk);
                                break;
                            }
                        }
                    }
                }
            }
        }

        if (!Directory.Exists(dest))
            Directory.CreateDirectory(dest);
        var write = FlatBufferConverter.SerializeFrom(obj);
        File.WriteAllBytes(Path.Combine(dest, f), write);
    }
}

private static void UpdateOverworldSceneMulti(string scene, List<DevID> Species, List<short> Forms, List<SexType> Sex, List<RareType> Rare, bool IsEditedNushi)
{
    var index = scene.LastIndexOf("/") + 1;
    var d = scene[..index];

    // edge case: scene contains both PokemonModelComponent and FieldPokemonComponent entries for starters - [0,0,1,1,2,2] instead of [0,1,2]
    if (scene.Contains("common_0060_always_"))
    {
        for (int i = 0; i <= 5; i += 2) Species.Insert((i + 1), Species[i]);
        for (int i = 0; i <= 5; i += 2) Forms.Insert((i + 1), Forms[i]);
        for (int i = 0; i <= 5; i += 2) Sex.Insert((i + 1), Sex[i]);
        for (int i = 0; i <= 5; i += 2) Rare.Insert((i + 1), Rare[i]);
    }

    for (int i = 0; i <= 1; i++)
    {
        var f = string.Format(scene[index..], i);
        var dest = Path.Combine(DEST_MASTER, d);
        var path = IsEditedNushi ? dest : Path.Combine(PATH_MASTER, d);
        var data = File.ReadAllBytes(Path.Combine(path, f));
        var obj = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(data);
        var ctr = 0;

        foreach (var o in obj.Objects)
        {
            foreach (var s in o.SubObjects.Where(z => z.Type is "ti_PokemonModelComponent" or "ti_FieldPokemonComponent"))
            {
                // Paradox: unused Bulbasaur leftover, skip
                // Rock/Dragon Titans: mix of both static encounters and trainer Pokémon, skip to the trainer Pokémon
                if (Species[ctr] is DevID.DEV_NULL)
                {
                    ctr++;
                    continue;
                }

                switch (s.Type)
                {
                    case "ti_PokemonModelComponent":
                    {
                        var pk = FlatBufferConverter.DeserializeFrom<TIPokemonModelComponent>(s.Data);
                        pk.DevId = (ushort)Species[ctr];
                        pk.FormId = (ushort)Forms[ctr];
                        pk.Sex = (sbyte)GetSceneGender(pk.DevId, (short)pk.FormId, (byte)Sex[ctr]);
                        pk.IsRare = Rare[ctr] is RareType.RARE;
                        ctr++;
                        s.Data = FlatBufferConverter.SerializeFrom(pk);
                        break;
                    }

                    case "ti_FieldPokemonComponent":
                    default:
                    {
                        var pk = FlatBufferConverter.DeserializeFrom<TIFieldPokemonComponent>(s.Data);
                        pk.DevId = (ushort)Species[ctr];
                        pk.FormId = (ushort)Forms[ctr];
                        pk.Sex = (sbyte)GetSceneGender(pk.DevId, (short)pk.FormId, (byte)Sex[ctr]);
                        ctr++;
                        s.Data = FlatBufferConverter.SerializeFrom(pk);
                        break;
                    }
                }
            }
        }

        if (!Directory.Exists(dest))
            Directory.CreateDirectory(dest);
        var write = FlatBufferConverter.SerializeFrom(obj);
        File.WriteAllBytes(Path.Combine(dest, f), write);
    }
}

private static void UpdateOverworldSceneGeneric(string scene)
{
    var index = scene.LastIndexOf("/") + 1;
    var d = scene[..index];

    for (int k = 0; k <= 1; k++)
    {
        var f = string.Format(scene[index..], k);
        var dest = Path.Combine(DEST_MASTER, d);
        var path = Path.Combine(PATH_MASTER, d);
        var data = File.ReadAllBytes(Path.Combine(path, f));
        var obj = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(data);
        var count = 0;
        var ctr = 0;

        foreach (var o in obj.Objects)
        {
            foreach (var s in o.SubObjects.Where(z => z.Type is "ti_PokemonModelComponent" or "ti_FieldPokemonComponent"))
                count++;

            foreach (var s in o.SubObjects)
            {
                foreach (var i in s.SubObjects.Where(z => z.Type is "ti_PokemonModelComponent" or "ti_FieldPokemonComponent"))
                    count++;

                foreach (var s2 in s.SubObjects)
                {
                    foreach (var i in s2.SubObjects.Where(z => z.Type is "ti_PokemonModelComponent" or "ti_FieldPokemonComponent"))
                        count++;
                }
            }
        }

        List<DevID> Species = new();
        List<short> Forms = new();
        List<SexType> Sex = new();
        List<RareType> Rare = new();

        for (int j = 0; j < count; j++)
        {
            var rSpecies = GetRandomSpecies(0);

            // get another random species to avoid softlock
            while (GetIsInvalidScenePokemon((ushort)rSpecies))
                rSpecies = GetRandomSpecies(0);

            var rForm = GetRandomForm(rSpecies);
            var rGender = GetRandomGender(rSpecies, rForm);
            var rRare = GetRandom(100) < GENERIC_OVERWORLD_POKEMON_SHINY_CHANCE_PERCENT ? RareType.RARE : RareType.NO_RARE;
            Species.Add(rSpecies);
            Forms.Add(rForm);
            Sex.Add(rGender);
            Rare.Add(rRare);
        }

        foreach (var o in obj.Objects)
        {
            foreach (var s in o.SubObjects.Where(z => z.Type is "ti_PokemonModelComponent" or "ti_FieldPokemonComponent"))
            {
                switch (s.Type)
                {
                    case "ti_PokemonModelComponent":
                    {
                        var pk = FlatBufferConverter.DeserializeFrom<TIPokemonModelComponent>(s.Data);
                        pk.DevId = (ushort)Species[ctr];
                        pk.FormId = (ushort)Forms[ctr];
                        pk.Sex = (sbyte)GetSceneGender(pk.DevId, (short)pk.FormId, (byte)Sex[ctr]);
                        pk.IsRare = Rare[ctr] is RareType.RARE;
                        ctr++;
                        s.Data = FlatBufferConverter.SerializeFrom(pk);
                        break;
                    }

                    case "ti_FieldPokemonComponent":
                    default:
                    {
                        var pk = FlatBufferConverter.DeserializeFrom<TIFieldPokemonComponent>(s.Data);
                        pk.DevId = (ushort)Species[ctr];
                        pk.FormId = (ushort)Forms[ctr];
                        pk.Sex = (sbyte)GetSceneGender(pk.DevId, (short)pk.FormId, (byte)Sex[ctr]);
                        ctr++;
                        s.Data = FlatBufferConverter.SerializeFrom(pk);
                        break;
                    }
                }
            }

            // inner
            foreach (var s in o.SubObjects)
            {
                foreach (var i in s.SubObjects.Where(z => z.Type is "ti_PokemonModelComponent" or "ti_FieldPokemonComponent"))
                {
                    switch (i.Type)
                    {
                        case "ti_PokemonModelComponent":
                        {
                            var pk = FlatBufferConverter.DeserializeFrom<TIPokemonModelComponent>(i.Data);
                            pk.DevId = (ushort)Species[ctr];
                            pk.FormId = (ushort)Forms[ctr];
                            pk.Sex = (sbyte)GetSceneGender(pk.DevId, (short)pk.FormId, (byte)Sex[ctr]);
                            pk.IsRare = Rare[ctr] is RareType.RARE;
                            ctr++;
                            i.Data = FlatBufferConverter.SerializeFrom(pk);
                            break;
                        }

                        case "ti_FieldPokemonComponent":
                        default:
                        {
                            var pk = FlatBufferConverter.DeserializeFrom<TIFieldPokemonComponent>(i.Data);
                            pk.DevId = (ushort)Species[ctr];
                            pk.FormId = (ushort)Forms[ctr];
                            pk.Sex = (sbyte)GetSceneGender(pk.DevId, (short)pk.FormId, (byte)Sex[ctr]);
                            ctr++;
                            i.Data = FlatBufferConverter.SerializeFrom(pk);
                            break;
                        }
                    }
                }

                // inner
                foreach (var s2 in s.SubObjects)
                {
                    foreach (var i in s2.SubObjects.Where(z => z.Type is "ti_PokemonModelComponent" or "ti_FieldPokemonComponent"))
                    {
                        switch (i.Type)
                        {
                            case "ti_PokemonModelComponent":
                            {
                                var pk = FlatBufferConverter.DeserializeFrom<TIPokemonModelComponent>(i.Data);
                                pk.DevId = (ushort)Species[ctr];
                                pk.FormId = (ushort)Forms[ctr];
                                pk.Sex = (sbyte)GetSceneGender(pk.DevId, (short)pk.FormId, (byte)Sex[ctr]);
                                pk.IsRare = Rare[ctr] is RareType.RARE;
                                ctr++;
                                i.Data = FlatBufferConverter.SerializeFrom(pk);
                                break;
                            }

                            case "ti_FieldPokemonComponent":
                            default:
                            {
                                var pk = FlatBufferConverter.DeserializeFrom<TIFieldPokemonComponent>(i.Data);
                                pk.DevId = (ushort)Species[ctr];
                                pk.FormId = (ushort)Forms[ctr];
                                pk.Sex = (sbyte)GetSceneGender(pk.DevId, (short)pk.FormId, (byte)Sex[ctr]);
                                ctr++;
                                i.Data = FlatBufferConverter.SerializeFrom(pk);
                                break;
                            }
                        }
                    }
                }
            }
        }

        if (!Directory.Exists(dest))
            Directory.CreateDirectory(dest);
        var write = FlatBufferConverter.SerializeFrom(obj);
        File.WriteAllBytes(Path.Combine(dest, f), write);
    }
}

private static void UpdateBossText(List<DevID> Bosses)
{
    foreach (var lang in Languages)
    {
        var dest = Path.Combine(DEST_MASTER, $"message/dat/{lang}/script/");
        var names = GetStringsCommon(lang, "monsname");
        var lines = GetStringsScript(lang, "futatsuna");
        var flags = GetStringFlagsScript(lang, "futatsuna");

        lines[08] = names[(int)Bosses[0]]; // Klawf
        lines[09] = names[(int)Bosses[1]]; // Orthworm
        lines[10] = names[(int)Bosses[2]]; // Bombirdier
        lines[11] = names[(int)Bosses[3]]; // Tatsugiri
        lines[12] = names[(int)Bosses[4]]; // Dondozo
        lines[13] = names[(int)Bosses[5]]; // Great Tusk
        lines[14] = names[(int)Bosses[5]]; // Iron Treads
        lines[15] = names[(int)Bosses[6]]; // Koraidon
        lines[16] = names[(int)Bosses[7]]; // Miraidon

        // modify Ogerpon boss text to just be species name, without mentioning Mask, since lua only implicitly checks for 0-7, falling back to "Teal Mask" for anything 8 or higher
        lines[23] = "[VAR 0101(0000)]";
        lines[24] = "[VAR 0101(0000)]";
        lines[25] = "[VAR 0101(0000)]";
        lines[26] = "[VAR 0101(0000)]";

        if (!Directory.Exists(dest))
            Directory.CreateDirectory(dest);
        var write = TextFile.GetBytes(lines, flags, TextConfig);
        File.WriteAllBytes(Path.Combine(dest, "futatsuna.dat"), write);
    }
}

private static PokeDataEventBattle ApplyParadiseSoftlockPrevention(PokeDataEventBattle t)
{
    t.GemType = GemType.NIJI; // always deals neutral damage

    // Ogerpon can't be Stellar Tera Type, and we specifically want Cornerstone Mask to avoid additional potential softlocks
    if (t.DevId is DevID.DEV_KAMENONI)
    {
        t.FormId = 3;
        t.GemType = GemType.IWA;
        t.Item = ItemID.ITEMID_ISHIDUENOMEN;
    }

    // Tera Starstorm for Terapagos, Tera Blast for all others
    WazaID tera = t.DevId is DevID.DEV_KODAIGAME ? WazaID.WAZA_TERAKURASUTAA : WazaID.WAZA_TERABAASUTO;

    List<WazaID> Moves = GetCurrentMoves((ushort)t.DevId, (byte)t.FormId, (byte)t.Level);
    t.WazaType = WazaType.MANUAL;
    t.Waza1.WazaId = Moves[0];
    t.Waza2.WazaId = Moves[1];
    t.Waza3.WazaId = Moves[2];
    t.Waza4.WazaId = tera; // the game is scripted to eventually spam move slot 4 if the battle has not yet ended

    // if the Pokémon knows less than 4 moves, we need to fill in the blanks with other moves
    var numEmptyMoves = Moves.Count(z => z is 0);
    if (numEmptyMoves is 3)
    {
        t.Waza2.WazaId = WazaID.WAZA_TURUGINOMAI; // Swords Dance
        t.Waza3.WazaId = WazaID.WAZA_WARUDAKUMI; // Nasty Plot
    }

    if (numEmptyMoves is 2)
        t.Waza3.WazaId = WazaID.WAZA_TUBOWOTUKU; // Acupressure

    // if the Pokémon has Wonder Guard, switch it to another one of its three abilities
    if (!BAN_ABILITY_WONDER_GUARD && RANDOMIZE_PERSONAL_ABILITY)
    {
        var pk = PersonalInfoEdited.Table.FirstOrDefault(z => z.Info.SpeciesInternal == (ushort)t.DevId && z.Info.Form == t.FormId);
        if (pk.Ability1 is 025 && t.Tokusei is TokuseiType.SET_1) t.Tokusei = TokuseiType.SET_2;
        if (pk.Ability2 is 025 && t.Tokusei is TokuseiType.SET_2) t.Tokusei = TokuseiType.SET_1;
        if (pk.AbilityH is 025 && t.Tokusei is TokuseiType.SET_3) t.Tokusei = TokuseiType.SET_1;
    }

    return t;
}

private static PokeDataEventBattle ApplyTerapagosSoftlockPrevention(PokeDataEventBattle t)
{
    // Tera Starstorm for Terapagos, Tera Blast for all others
    var tera = t.DevId is DevID.DEV_KODAIGAME ? WazaID.WAZA_TERAKURASUTAA : WazaID.WAZA_TERABAASUTO;
    List<WazaID> Moves = GetCurrentMoves((ushort)t.DevId, (byte)t.FormId, (byte)t.Level);

    // always put Tera Blast/Starstorm in final move slot
    var numEmptyMoves = Moves.Count(z => z is 0);
    var index = numEmptyMoves switch
    {
        2 => 2,
        3 => 1,
        _ => 3,
    };

    Moves[index] = tera;
    t.WazaType = WazaType.MANUAL;
    t.Waza1.WazaId = Moves[0];
    t.Waza2.WazaId = Moves[1];
    t.Waza3.WazaId = Moves[2];
    t.Waza4.WazaId = Moves[3];

    return t;
}

private static EventBattlePokemonArray StandardizeStaticEncounters(EventBattlePokemonArray obj)
{
    var table = obj.Table;
    obj = CopyStaticEncounterDetails(obj, 2,  [1], true, false, false); // Scream Tail / Iron Bundle
    obj = CopyStaticEncounterDetails(obj, 4,  [3], true, false, false); // Great Tusk / Iron Treads (Area Zero)
    obj = CopyStaticEncounterDetails(obj, 8,  [5], true, false, false); // Great Tusk / Iron Treads (Area Zero Gauntlet)
    obj = CopyStaticEncounterDetails(obj, 9,  [6], true, false, false); // Brute Bonnet / Iron Hands (Area Zero Gauntlet)
    obj = CopyStaticEncounterDetails(obj, 10, [7], true, false, false); // Flutter Mane / Iron Jugulis (Area Zero Gauntlet)
    obj = CopyStaticEncounterDetails(obj, 23, [11,12,13,14,15,16,17,18,19,20,21,22], false, false, false); // Gimmighoul
    obj = CopyStaticEncounterDetails(obj, 28, [26,27,29,30], true, false, false); // Sunflora
    obj = CopyStaticEncounterDetails(obj, 34, [33], true, false, false); // Dondozo
    obj = CopyStaticEncounterDetails(obj, 36, [35], true, false, false); // Orthworm
    obj = CopyStaticEncounterDetails(obj, 37, [38,39,40], false, false, false); // Tatsugiri
    obj = CopyStaticEncounterDetails(obj, 42, [41], true, false, false); // Bombirdier
    obj = CopyStaticEncounterDetails(obj, 44, [43], true, false, false); // Klawf
    obj = CopyStaticEncounterDetails(obj, 48, [45,46,47], true, false, false); // Great Tusk / Iron Treads (Titan)
    obj = CopyStaticEncounterDetails(obj, 73, [53,54,57,72], true, false, false); // Munkidori
    obj = CopyStaticEncounterDetails(obj, 69, [55,68], true, false, false); // Okidogi
    obj = CopyStaticEncounterDetails(obj, 71, [56,70], true, false, false); // Fezandipiti
    obj = CopyStaticEncounterDetails(obj, 65, [58,59,60,61,62,63,64], true, true, false); // Ogerpon
    obj = CopyStaticEncounterDetails(obj, 67, [66], true, false, false); // Milotic
    obj = CopyStaticEncounterDetails(obj, 83, [82], true, false, false); // Sandy Shocks / Iron Thorns
    obj = CopyStaticEncounterDetails(obj, 84, [77], true, false, false); // Raging Bolt / Iron Crown
    obj = CopyStaticEncounterDetails(obj, 85, [76], true, false, false); // Gouging Fire / Iron Boulder
    obj = CopyStaticEncounterDetails(obj, 88, [87], true, false, false); // Terapagos

    if (RANDOMIZE_KORAIDON_MIRAIDON)
        obj = CopyStaticEncounterDetails(obj, 116, [31,32,115], true, false, true);

    return obj;
}

private static EventBattlePokemonArray CopyStaticEncounterDetails(EventBattlePokemonArray obj, int original, int[] copies, bool IsSpecificBoss, bool IsOgerpon, bool IsIguana)
{
    var table = obj.Table;
    for (int i = 0; i < copies.Length; i++)
    {
        var index = copies[i];
        table[index].PokeData.DevId    = table[original].PokeData.DevId;
        table[index].PokeData.FormId   = table[original].PokeData.FormId;
        table[index].PokeData.Sex      = table[original].PokeData.Sex;
        table[index].PokeData.RareType = table[original].PokeData.RareType;

        // refresh Tera Types if not Ogerpon (softlock prevention)
        if (!IsOgerpon)
            table[index].PokeData.GemType  = GetTeraType(table[original].PokeData.DevId, (byte)table[original].PokeData.FormId, table[original].PokeData.GemType, false, false);

        if (IsFormHeldItemDependent(table[original].PokeData.DevId))
            table[index].PokeData.Item = table[original].PokeData.Item;

        if (IsSpecificBoss)
        {
            table[index].PokeData.Item         = table[original].PokeData.Item;
            table[index].PokeData.Seikaku      = table[original].PokeData.Seikaku;
            table[index].PokeData.SeikakuHosei = table[original].PokeData.SeikakuHosei;
            table[index].PokeData.Tokusei      = table[original].PokeData.Tokusei;
            table[index].PokeData.SetRibbon    = table[original].PokeData.SetRibbon;

            // softlock prevention
            if (!IsOgerpon)
                table[index].PokeData.GemType = table[original].PokeData.GemType;
        }

        // softlock prevention
        if (IsIguana)
        {
            table[index].PokeData.WazaType = table[original].PokeData.WazaType;
            table[index].PokeData.Waza1    = table[original].PokeData.Waza1;
            table[index].PokeData.Waza2    = table[original].PokeData.Waza2;
            table[index].PokeData.Waza3    = table[original].PokeData.Waza3;
            table[index].PokeData.Waza4    = table[original].PokeData.Waza4;
        }
    }

    return obj;
}

private static EventAddPokemonArray StandardizeGiftEncounters(EventAddPokemonArray objG, EventBattlePokemonArray objS)
{
    var tableG = objG.Table;
    var tableS = objS.Table;

    // make your Koraidon and Miraidon match the professor's
    tableG[005].PokeData.DevId        = tableG[006].PokeData.DevId        = tableS[116].PokeData.DevId;
    tableG[005].PokeData.FormId       = tableG[006].PokeData.FormId       = tableS[116].PokeData.FormId;
    tableG[005].PokeData.Sex          = tableG[006].PokeData.Sex          = tableS[116].PokeData.Sex;
    tableG[005].PokeData.Item         = tableG[006].PokeData.Item         = tableS[116].PokeData.Item;
    tableG[005].PokeData.Seikaku      = tableG[006].PokeData.Seikaku      = tableS[116].PokeData.Seikaku;
    tableG[005].PokeData.SeikakuHosei = tableG[006].PokeData.SeikakuHosei = tableS[116].PokeData.SeikakuHosei;
    tableG[005].PokeData.Tokusei      = tableG[006].PokeData.Tokusei      = tableS[116].PokeData.Tokusei;
    tableG[005].PokeData.GemType      = tableG[006].PokeData.GemType      = tableS[116].PokeData.GemType;
    tableG[005].PokeData.WazaType     = tableG[006].PokeData.WazaType     = tableS[116].PokeData.WazaType;
    tableG[005].PokeData.Waza1        = tableG[006].PokeData.Waza1        = tableS[116].PokeData.Waza1;
    tableG[005].PokeData.Waza2        = tableG[006].PokeData.Waza2        = tableS[116].PokeData.Waza2;
    tableG[005].PokeData.Waza3        = tableG[006].PokeData.Waza3        = tableS[116].PokeData.Waza3;
    tableG[005].PokeData.Waza4        = tableG[006].PokeData.Waza4        = tableS[116].PokeData.Waza4;
    tableG[005].PokeData.RareType     = tableG[006].PokeData.RareType     = tableS[116].PokeData.RareType;

    return objG;
}

private static TrDataMainArray StandardizeTrainerData(TrDataMainArray objT, EventBattlePokemonArray objS)
{
    var tableT = objT.Table;
    var tableS = objS.Table;

    // make Guardian of Paradise match your legendary
    if (RANDOMIZE_KORAIDON_MIRAIDON)
    {
        var sada = tableT.FirstOrDefault(z => z.TrId is "professor_A_02").Poke1;
        var turo = tableT.FirstOrDefault(z => z.TrId is "professor_B_02").Poke1;
        sada.DevId        = turo.DevId        = tableS[116].PokeData.DevId;
        sada.FormId       = turo.FormId       = tableS[116].PokeData.FormId;
        sada.Sex          = turo.Sex          = tableS[116].PokeData.Sex;
        sada.Item         = turo.Item         = tableS[116].PokeData.Item;
        sada.Seikaku      = turo.Seikaku      = tableS[116].PokeData.Seikaku;
        sada.Tokusei      = turo.Tokusei      = tableS[116].PokeData.Tokusei;
        sada.GemType      = turo.GemType      = tableS[116].PokeData.GemType;
        sada.WazaType     = turo.WazaType     = tableS[116].PokeData.WazaType;
        sada.Waza1        = turo.Waza1        = tableS[116].PokeData.Waza1;
        sada.Waza2        = turo.Waza2        = tableS[116].PokeData.Waza2;
        sada.Waza3        = turo.Waza3        = tableS[116].PokeData.Waza3;
        sada.Waza4        = turo.Waza4        = tableS[116].PokeData.Waza4;
        sada.RareType     = turo.RareType     = tableS[116].PokeData.RareType;
        sada.BallId       = turo.BallId       = BallType.MASUTAABOORU;
    }

    // make Kieran's Terapagos match the static encounter
    var tera = tableT.FirstOrDefault(z => z.TrId is "brother_02_02").Poke1;
    tera.DevId        = tableS[088].PokeData.DevId;
    tera.FormId       = tableS[088].PokeData.FormId;
    tera.Sex          = tableS[088].PokeData.Sex;
    tera.Item         = tableS[088].PokeData.Item;
    tera.Seikaku      = tableS[088].PokeData.Seikaku;
    tera.Tokusei      = tableS[088].PokeData.Tokusei;
    tera.GemType      = tableS[088].PokeData.GemType;
    tera.WazaType     = tableS[088].PokeData.WazaType;
    tera.Waza1        = tableS[088].PokeData.Waza1;
    tera.Waza2        = tableS[088].PokeData.Waza2;
    tera.Waza3        = tableS[088].PokeData.Waza3;
    tera.Waza4        = tableS[088].PokeData.Waza4;
    tera.RareType     = tableS[088].PokeData.RareType;
    tera.BallId       = BallType.MASUTAABOORU;

    return objT;
}

private static void UpdateAllStaticScenes(EventBattlePokemonArray obj)
{
    var table = obj.Table;

    // Area Zero Multi Battles
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/common_1055_/common_1055_main_{0}.trsog", table[000].PokeData.DevId, table[000].PokeData.FormId, table[000].PokeData.Sex, table[000].PokeData.RareType); // Glimmora
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/common_1075_/common_1075_main_{0}.trsog", table[002].PokeData.DevId, table[002].PokeData.FormId, table[002].PokeData.Sex, table[002].PokeData.RareType); // Scream Tail / Iron Bundle
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/common_1095_/common_1095_main_{0}.trsog", table[004].PokeData.DevId, table[004].PokeData.FormId, table[004].PokeData.Sex, table[004].PokeData.RareType); // Great Tusk / Iron Treads

    UpdateOverworldSceneSingle("world/obj_template/parts/coin_symbol/coin_symbol_box_/coin_symbol_box_{0}.trsot",              table[023].PokeData.DevId, table[023].PokeData.FormId, table[023].PokeData.Sex, table[023].PokeData.RareType); // Gimmighoul
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/common_0150_/common_0150_main_{0}.trsog", table[025].PokeData.DevId, table[025].PokeData.FormId, table[025].PokeData.Sex, table[025].PokeData.RareType); // Houndoom
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/gym_kusa_poke_finding_/pokes_{0}.trsog",  table[030].PokeData.DevId, table[030].PokeData.FormId, table[030].PokeData.Sex, table[030].PokeData.RareType); // Sunflora

    // Orthworm
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/hagane/HaganeNushiModel_/HaganeNushiModel_{0}.trsot",                          table[036].PokeData.DevId, table[036].PokeData.FormId, table[036].PokeData.Sex, table[036].PokeData.RareType);
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/hagane/HaganeNushiModel_up_/HaganeNushiModel_up_{0}.trsot",                    table[036].PokeData.DevId, table[036].PokeData.FormId, table[036].PokeData.Sex, table[036].PokeData.RareType);
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/hagane/nushi_hagane_fp_1048_010_/nushi_hagane_fp_1048_010_{0}.trsot",          table[036].PokeData.DevId, table[036].PokeData.FormId, table[036].PokeData.Sex, table[036].PokeData.RareType);
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/hagane/nushi_hagane_fp_1048_020_/nushi_hagane_fp_1048_020_{0}.trsot",          table[036].PokeData.DevId, table[036].PokeData.FormId, table[036].PokeData.Sex, table[036].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/nushi_hagane_010_/nushi_hagane_010_pre_start_{0}.trsog", table[036].PokeData.DevId, table[036].PokeData.FormId, table[036].PokeData.Sex, table[036].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/nushi_hagane_020_/nushi_hagane_020_pre_start_{0}.trsog", table[036].PokeData.DevId, table[036].PokeData.FormId, table[036].PokeData.Sex, table[036].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/sub_039_/sub_039_pre_start_{0}.trsog",                    table[036].PokeData.DevId, table[036].PokeData.FormId, table[036].PokeData.Sex, table[036].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/field/field_contents/nushi/hagane/HaganeNushi_/HaganeNushi_{0}.trscn",                      table[036].PokeData.DevId, table[036].PokeData.FormId, table[036].PokeData.Sex, table[036].PokeData.RareType);

    // Dondozo
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/dragon/nushi_dragon_fp_1035_010_/nushi_dragon_fp_1035_010_{0}.trsot",       table[034].PokeData.DevId, table[034].PokeData.FormId, table[034].PokeData.Sex, table[034].PokeData.RareType);
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/dragon/nushi_dragon_fp_1035_020_/nushi_dragon_fp_1035_020_{0}.trsot",       table[034].PokeData.DevId, table[034].PokeData.FormId, table[034].PokeData.Sex, table[034].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/nushi_dragon_010_/nushi_dragon_010_always_{0}.trsog", table[034].PokeData.DevId, table[034].PokeData.FormId, table[034].PokeData.Sex, table[034].PokeData.RareType);

    // Tatsugiri
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/dragon/nushi_dragon_fp_1056_010_/nushi_dragon_fp_1056_010_{0}.trsot",          table[037].PokeData.DevId, table[037].PokeData.FormId, table[037].PokeData.Sex, table[037].PokeData.RareType);
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/dragon/nushi_dragon_fp_1056_020_/nushi_dragon_fp_1056_020_{0}.trsot",          table[037].PokeData.DevId, table[037].PokeData.FormId, table[037].PokeData.Sex, table[037].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/nushi_dragon_010_/nushi_dragon_010_pre_start_{0}.trsog", table[037].PokeData.DevId, table[037].PokeData.FormId, table[037].PokeData.Sex, table[037].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/sub_040_/sub_040_pre_start_{0}.trsog",                    table[037].PokeData.DevId, table[037].PokeData.FormId, table[037].PokeData.Sex, table[037].PokeData.RareType);

    // Bombirdier
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/hiko/nushi_hiko_fp_1063_010_/nushi_hiko_fp_1063_010_{0}.trsot",              table[042].PokeData.DevId, table[042].PokeData.FormId, table[042].PokeData.Sex, table[042].PokeData.RareType);
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/hiko/nushi_hiko_fp_1063_020_/nushi_hiko_fp_1063_020_{0}.trsot",              table[042].PokeData.DevId, table[042].PokeData.FormId, table[042].PokeData.Sex, table[042].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/nushi_hikou_010_/nushi_hikou_010_pre_start_{0}.trsog", table[042].PokeData.DevId, table[042].PokeData.FormId, table[042].PokeData.Sex, table[042].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/nushi_hikou_020_/nushi_hikou_020_main_{0}.trsog",      table[042].PokeData.DevId, table[042].PokeData.FormId, table[042].PokeData.Sex, table[042].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/nushi_hikou_020_/nushi_hikou_020_pre_start_{0}.trsog", table[042].PokeData.DevId, table[042].PokeData.FormId, table[042].PokeData.Sex, table[042].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/sub_038_/sub_038_pre_start_{0}.trsog",                  table[042].PokeData.DevId, table[042].PokeData.FormId, table[042].PokeData.Sex, table[042].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/field/field_contents/nushi/hiko/HikoNushi_/HikoNushi_{0}.trscn",                          table[042].PokeData.DevId, table[042].PokeData.FormId, table[042].PokeData.Sex, table[042].PokeData.RareType);

    // Klawf
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/iwa/nushi_iwa_fp_1066_/nushi_iwa_fp_1066_{0}.trsot",                     table[044].PokeData.DevId, table[044].PokeData.FormId, table[044].PokeData.Sex, table[044].PokeData.RareType);
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/iwa/nushi_iwa_fp_1066_020_/nushi_iwa_fp_1066_020_{0}.trsot",             table[044].PokeData.DevId, table[044].PokeData.FormId, table[044].PokeData.Sex, table[044].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/nushi_iwa_010_/nushi_iwa_010_pre_start_{0}.trsog", table[044].PokeData.DevId, table[044].PokeData.FormId, table[044].PokeData.Sex, table[044].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/nushi_iwa_020_/nushi_iwa_020_pre_start_{0}.trsog", table[044].PokeData.DevId, table[044].PokeData.FormId, table[044].PokeData.Sex, table[044].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/sub_037_/sub_037_pre_start_{0}.trsog",              table[044].PokeData.DevId, table[044].PokeData.FormId, table[044].PokeData.Sex, table[044].PokeData.RareType);

    // Great Tusk / Iron Treads (Titan)
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/jimen/nushi_jimen_fp_1082_010_/nushi_jimen_fp_1082_010_{0}.trsot",           table[048].PokeData.DevId, table[048].PokeData.FormId, table[048].PokeData.Sex, table[048].PokeData.RareType);
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/jimen/nushi_jimen_fp_1082_020_/nushi_jimen_fp_1082_020_{0}.trsot",           table[048].PokeData.DevId, table[048].PokeData.FormId, table[048].PokeData.Sex, table[048].PokeData.RareType);
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/jimen/nushi_jimen_fp_1090_010_/nushi_jimen_fp_1090_010_{0}.trsot",           table[048].PokeData.DevId, table[048].PokeData.FormId, table[048].PokeData.Sex, table[048].PokeData.RareType);
    UpdateOverworldSceneSingle("world/obj_template/parts/nushi/jimen/nushi_jimen_fp_1090_020_/nushi_jimen_fp_1090_020_{0}.trsot",           table[048].PokeData.DevId, table[048].PokeData.FormId, table[048].PokeData.Sex, table[048].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/nushi_jimen_010_/nushi_jimen_010_pre_start_{0}.trsog", table[048].PokeData.DevId, table[048].PokeData.FormId, table[048].PokeData.Sex, table[048].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/nushi_jimen_020_/nushi_jimen_020_pre_start_{0}.trsog", table[048].PokeData.DevId, table[048].PokeData.FormId, table[048].PokeData.Sex, table[048].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/sub_041_/sub_041_pre_start_{0}.trsog",                  table[048].PokeData.DevId, table[048].PokeData.FormId, table[048].PokeData.Sex, table[048].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/field/field_contents/nushi/jimen/JimenNushi_/JimenNushi_{0}.trscn",                       table[048].PokeData.DevId, table[048].PokeData.FormId, table[048].PokeData.Sex, table[048].PokeData.RareType);

    // Treasures of Ruin
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/sub_014_/sub_014_main_{0}.trsog", table[049].PokeData.DevId, table[049].PokeData.FormId, table[049].PokeData.Sex, table[049].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/sub_015_/sub_015_main_{0}.trsog", table[050].PokeData.DevId, table[050].PokeData.FormId, table[050].PokeData.Sex, table[050].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/sub_016_/sub_016_main_{0}.trsog", table[051].PokeData.DevId, table[051].PokeData.FormId, table[051].PokeData.Sex, table[051].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/sub_017_/sub_017_main_{0}.trsog", table[052].PokeData.DevId, table[052].PokeData.FormId, table[052].PokeData.Sex, table[052].PokeData.RareType);

    // Kitakami
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0300_/sdc01_0300_main_{0}.trsog",        table[067].PokeData.DevId, table[067].PokeData.FormId, table[067].PokeData.Sex, table[067].PokeData.RareType); // Milotic
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s1_side02_0030_/s1_side02_0030_main_{0}.trsog", table[074].PokeData.DevId, table[074].PokeData.FormId, table[074].PokeData.Sex, table[074].PokeData.RareType); // Ariados
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s1_side02_0050_/s1_side02_0050_main_{0}.trsog", table[075].PokeData.DevId, table[075].PokeData.FormId, table[075].PokeData.Sex, table[075].PokeData.RareType); // Bloodmoon Ursaluna

    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s2_side02_0010_/s2_side02_0010_pre_start_{0}.trsog", table[076].PokeData.DevId, table[076].PokeData.FormId, table[076].PokeData.Sex, table[076].PokeData.RareType); // Gouging Fire / Iron Boulder
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s2_side02_0020_/s2_side02_0020_pre_start_{0}.trsog", table[077].PokeData.DevId, table[077].PokeData.FormId, table[077].PokeData.Sex, table[077].PokeData.RareType); // Raging Bolt / Iron Crown

    // Area Zero Underdepths Stellar Pokémon
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s2_sub_005_/s2_sub_005_pre_start_{0}.trsog",  table[078].PokeData.DevId, table[078].PokeData.FormId, table[078].PokeData.Sex, table[078].PokeData.RareType); // Garchomp
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc02_0262_/sdc02_0262_main_{0}.trsog",      table[079].PokeData.DevId, table[079].PokeData.FormId, table[079].PokeData.Sex, table[079].PokeData.RareType); // Glimmora
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc02_0263_/sdc02_0263_pre_start_{0}.trsog", table[080].PokeData.DevId, table[080].PokeData.FormId, table[080].PokeData.Sex, table[080].PokeData.RareType); // Noivern
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc02_0267_/sdc02_0267_pre_start_{0}.trsog", table[081].PokeData.DevId, table[081].PokeData.FormId, table[081].PokeData.Sex, table[081].PokeData.RareType); // Garganacl
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc02_0265_/sdc02_0265_pre_start_{0}.trsog", table[082].PokeData.DevId, table[082].PokeData.FormId, table[082].PokeData.Sex, table[082].PokeData.RareType); // Sandy Shocks / Iron Thorns

    // Pecharunt
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s2_side01_0160_/s2_side01_0160_always_{0}.trsog", table[086].PokeData.DevId, table[086].PokeData.FormId, table[086].PokeData.Sex, table[086].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s2_side01_0180_/s2_side01_0180_always_{0}.trsog", table[086].PokeData.DevId, table[086].PokeData.FormId, table[086].PokeData.Sex, table[086].PokeData.RareType);

    // Terapagos
    UpdateOverworldSceneSingle("world/scene/parts/demo/ev/d730_/d730_{0}.trscn",                                               table[088].PokeData.DevId, table[088].PokeData.FormId, table[088].PokeData.Sex, table[088].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc02_0320_/sdc02_0320_always_{0}.trsog", table[088].PokeData.DevId, table[088].PokeData.FormId, table[088].PokeData.Sex, table[088].PokeData.RareType);

    // Meloetta
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s2_sub_003_pop_/s2_sub_003_pop_{0}.trscn", table[114].PokeData.DevId, table[114].PokeData.FormId, table[114].PokeData.Sex, table[114].PokeData.RareType);

    // Snacksworth Legendary Pokémon
    List<byte> Snacksworth = [ 092, 112, 102, 104, 091, 109, 100, 095, 098, 099, 096, 094, 105, 089, 110, 111, 106, 113, 097, 107, 101, 103, 090, 093, 108 ];
    for (int i = 13, k = 0; i <= 37; i++, k++)
    {
        var dev = table[Snacksworth[k]].PokeData.DevId;
        var form = table[Snacksworth[k]].PokeData.FormId;
        var sex = table[Snacksworth[k]].PokeData.Sex;
        var rare = table[Snacksworth[k]].PokeData.RareType;

        var path = $"world/scene/parts/event/event_scenario/sub_scenario/s2_sub_{i:000}_/s2_sub_{i:000}_";
        var file = "pre_start_{0}.trsog";
        UpdateOverworldSceneSingle(path + file, dev, form, sex, rare);
    }

    // Titan Klawf - Phase 2 (Klawf ×1, Lucario ×1, Shellder ×2)
    List<DevID> RockTitanSpecies = [ table[044].PokeData.DevId,    0, 0, 0 ];
    List<short> RockTitanForm    = [ table[044].PokeData.FormId,   0, 0, 0 ];
    List<SexType> RockTitanSex   = [ table[044].PokeData.Sex,      0, 0, 0 ];
    List<RareType> RockTitanRare = [ table[044].PokeData.RareType, 0, 0, 0 ];
    UpdateOverworldSceneMulti("world/scene/parts/field/field_contents/nushi/common/RockClashEvent_/RockClashEvent_{0}.trscn", RockTitanSpecies, RockTitanForm, RockTitanSex, RockTitanRare, false);

    // Area Zero Paradox Pokémon Gauntlet (Great Tusk/Iron Treads ×2, Brute Bonnet/Iron Hands ×5, Flutter Mane/Iron Jugulis ×1)
    List<DevID> ParadoxSpecies = [ table[008].PokeData.DevId,    table[009].PokeData.DevId,    table[010].PokeData.DevId,    0, table[009].PokeData.DevId,    table[009].PokeData.DevId,    table[008].PokeData.DevId,    table[009].PokeData.DevId,    table[009].PokeData.DevId    ];
    List<short> ParadoxForm    = [ table[008].PokeData.FormId,   table[009].PokeData.FormId,   table[010].PokeData.FormId,   0, table[009].PokeData.FormId,   table[009].PokeData.FormId,   table[008].PokeData.FormId,   table[009].PokeData.FormId,   table[009].PokeData.FormId   ];
    List<SexType> ParadoxSex   = [ table[008].PokeData.Sex,      table[009].PokeData.Sex,      table[010].PokeData.Sex,      0, table[009].PokeData.Sex,      table[009].PokeData.Sex,      table[008].PokeData.Sex,      table[009].PokeData.Sex,      table[009].PokeData.Sex      ];
    List<RareType> ParadoxRare = [ table[008].PokeData.RareType, table[009].PokeData.RareType, table[010].PokeData.RareType, 0, table[009].PokeData.RareType, table[009].PokeData.RareType, table[008].PokeData.RareType, table[009].PokeData.RareType, table[009].PokeData.RareType ];
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/common_1170_/common_1170_always_{0}.trsog", ParadoxSpecies, ParadoxForm, ParadoxSex, ParadoxRare, false);

    // Poco Path Catching Tutorial (Lechonk ×5, Tarountula ×4, Pawmi ×1, Fletchling ×2)
    var T0 = GetRandomSpecies(0);
    var T1 = GetRandomForm(T0);
    var T2 = GetRandomGender(T0, T1);
    var T3 = RollForRandomShiny();

    var P0 = GetRandomSpecies(0);
    var P1 = GetRandomForm(P0);
    var P2 = GetRandomGender(P0, P1);
    var P3 = RollForRandomShiny();

    var F0 = GetRandomSpecies(0);
    var F1 = GetRandomForm(F0);
    var F2 = GetRandomGender(F0, F1);
    var F3 = RollForRandomShiny();

    List<DevID> PocoPathSpecies = [ table[024].PokeData.DevId,    table[024].PokeData.DevId,    table[024].PokeData.DevId,    table[024].PokeData.DevId,    table[024].PokeData.DevId,    T0, T0, T0, T0, P0, F0, F0 ];
    List<short> PocoPathForm    = [ table[024].PokeData.FormId,   table[024].PokeData.FormId,   table[024].PokeData.FormId,   table[024].PokeData.FormId,   table[024].PokeData.FormId,   T1, T1, T1, T1, P1, F1, F1 ];
    List<SexType> PocoPathSex   = [ table[024].PokeData.Sex,      table[024].PokeData.Sex,      table[024].PokeData.Sex,      table[024].PokeData.Sex,      table[024].PokeData.Sex,      T2, T2, T2, T2, P2, F2, F2 ];
    List<RareType> PocoPathRare = [ table[024].PokeData.RareType, table[024].PokeData.RareType, table[024].PokeData.RareType, table[024].PokeData.RareType, table[024].PokeData.RareType, T3, T3, T3, T3, P3, F3, F3 ];
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/common_0100_/common_0100_main_{0}.trsog", PocoPathSpecies, PocoPathForm, PocoPathSex, PocoPathRare, false);

    // Casseroya Lake (Dondozo ×1, Tatsugiri ×1, Greedent ×1)
    List<DevID> CasseroyaSpecies = [ table[034].PokeData.DevId,    table[034].PokeData.DevId,    table[037].PokeData.DevId,    table[037].PokeData.DevId,    DevID.DEV_YOKUBARISU, DevID.DEV_YOKUBARISU ];
    List<short> CasseroyaForm    = [ table[034].PokeData.FormId,   table[034].PokeData.FormId,   table[037].PokeData.FormId,   table[037].PokeData.FormId,   0, 0 ];
    List<SexType> CasseroyaSex   = [ table[034].PokeData.Sex,      table[034].PokeData.Sex,      table[037].PokeData.Sex,      table[037].PokeData.Sex,      0, 0 ];
    List<RareType> CasseroyaRare = [ table[034].PokeData.RareType, table[034].PokeData.RareType, table[037].PokeData.RareType, table[037].PokeData.RareType, 0, 0 ];
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/nushi_dragon_020_/nushi_dragon_020_pre_start_{0}.trsog", CasseroyaSpecies, CasseroyaForm, CasseroyaSex, CasseroyaRare, false);
}

private static void UpdateAllGiftScenes(EventAddPokemonArray obj)
{
    var table = obj.Table;

    List<DevID> StarterSpecies0 = [ table[002].PokeData.DevId, table[001].PokeData.DevId, table[000].PokeData.DevId ];
    List<DevID> StarterSpecies1 = [ table[000].PokeData.DevId, table[001].PokeData.DevId, table[002].PokeData.DevId ];
    List<DevID> StarterSpecies2 = [ table[001].PokeData.DevId, table[000].PokeData.DevId, table[002].PokeData.DevId ];
    List<DevID> StarterSpecies3 = [ table[001].PokeData.DevId, table[002].PokeData.DevId, table[000].PokeData.DevId ];

    List<short> StarterForm0 = [ table[002].PokeData.FormId, table[001].PokeData.FormId, table[000].PokeData.FormId ];
    List<short> StarterForm1 = [ table[000].PokeData.FormId, table[001].PokeData.FormId, table[002].PokeData.FormId ];
    List<short> StarterForm2 = [ table[001].PokeData.FormId, table[000].PokeData.FormId, table[002].PokeData.FormId ];
    List<short> StarterForm3 = [ table[001].PokeData.FormId, table[002].PokeData.FormId, table[000].PokeData.FormId ];

    List<SexType> StarterSex0 = [ table[002].PokeData.Sex, table[001].PokeData.Sex, table[000].PokeData.Sex ];
    List<SexType> StarterSex1 = [ table[000].PokeData.Sex, table[001].PokeData.Sex, table[002].PokeData.Sex ];
    List<SexType> StarterSex2 = [ table[001].PokeData.Sex, table[000].PokeData.Sex, table[002].PokeData.Sex ];
    List<SexType> StarterSex3 = [ table[001].PokeData.Sex, table[002].PokeData.Sex, table[000].PokeData.Sex ];

    List<RareType> StarterRare0 = [ table[002].PokeData.RareType, table[001].PokeData.RareType, table[000].PokeData.RareType ];
    List<RareType> StarterRare1 = [ table[000].PokeData.RareType, table[001].PokeData.RareType, table[002].PokeData.RareType ];
    List<RareType> StarterRare2 = [ table[001].PokeData.RareType, table[000].PokeData.RareType, table[002].PokeData.RareType ];
    List<RareType> StarterRare3 = [ table[001].PokeData.RareType, table[002].PokeData.RareType, table[000].PokeData.RareType ];

    // Starter Pokémon
    UpdateOverworldSceneMulti("world/scene/parts/demo/ev/d030_/d030_{0}.trscn", StarterSpecies0, StarterForm0, StarterSex0, StarterRare0, false);
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/common_0060_/common_0060_always_{0}.trsog", StarterSpecies1, StarterForm1, StarterSex1, StarterRare1, false);
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/common_0060_/common_0060_main_{0}.trsog",   StarterSpecies2, StarterForm2, StarterSex2, StarterRare2, false);
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/common_0070_/common_0070_always_{0}.trsog", StarterSpecies3, StarterForm3, StarterSex3, StarterRare3, false);
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/common_0088_/common_0088_always_{0}.trsog", StarterSpecies3, StarterForm3, StarterSex3, StarterRare3, false);
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/common_0090_/common_0090_main_{0}.trsog",   StarterSpecies3, StarterForm3, StarterSex3, StarterRare3, false);

    // Hisuian Growlithe
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s1_side02_0000_/s1_side02_0000_always_{0}.trsog",    table[008].PokeData.DevId, table[008].PokeData.FormId, table[008].PokeData.Sex, table[008].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s1_side02_0030_/s1_side02_0030_always_{0}.trsog",    table[008].PokeData.DevId, table[008].PokeData.FormId, table[008].PokeData.Sex, table[008].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s1_side02_0060_/s1_side02_0060_pre_start_{0}.trsog", table[008].PokeData.DevId, table[008].PokeData.FormId, table[008].PokeData.Sex, table[008].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s2_side02_0000_/s2_side02_0000_pre_start_{0}.trsog", table[008].PokeData.DevId, table[008].PokeData.FormId, table[008].PokeData.Sex, table[008].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s2_side02_0005_/s2_side02_0005_pre_start_{0}.trsog", table[008].PokeData.DevId, table[008].PokeData.FormId, table[008].PokeData.Sex, table[008].PokeData.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s2_side02_0030_/s2_side02_0030_always_{0}.trsog",    table[008].PokeData.DevId, table[008].PokeData.FormId, table[008].PokeData.Sex, table[008].PokeData.RareType);

    // crashes and softlocks
    // world/scene/parts/event/event_scenario/sub_scenario/s1_side02_0040_/targets_fieldwork_{0}.trsog -- game will crash on starting the fieldwork minigame
}

private static void UpdateAllIguanaScenes(EventAddPokemonArray obj)
{
    var table = obj.Table;
    var IguanaDev  = table[005].PokeData.DevId;
    var IguanaForm = table[005].PokeData.FormId;
    var IguanaSex  = table[005].PokeData.Sex;
    var IguanaRare = table[005].PokeData.RareType;

    List<string> IguanaScenes =
    [
        "world/obj_template/parts/demo/ev/d450_/d450_{0}.trsot",
        "world/obj_template/parts/demo/sd/sd9010_title_/sd9010_title_{0}.trsot",
        "world/scene/parts/demo/ev/d040_/d040_{0}.trscn",
        "world/scene/parts/demo/ev/d400_/d400_{0}.trscn",
        "world/scene/parts/demo/ev/d410_/d410_{0}.trscn",
        "world/scene/parts/demo/ev/d500_/d500_{0}.trscn",
        "world/scene/parts/demo/ev/d740_/d740_{0}.trscn",
        "world/scene/parts/event/event_scenario/main_scenario/common_0135_/common_0135_post_end_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/common_0140_legend_action_02_/common_0140_legend_action_02_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/common_0170_/common_0170_always_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/common_0170_/common_0170_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/common_0185_/common_0185_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/common_0270_/common_0270_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/common_0310_/common_0310_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/common_1050_/common_1050_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/common_1150_/common_1150_always_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/common_1190_/common_1190_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/common_1190_/common_1190_pre_start_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/common_1290_/common_1290_always_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/legend_0020_/legend_0020_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/sdc01_side01_010_/sdc01_side01_010_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/sdc02_0250_/sdc02_0250_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/sdc02_4kings_b_02_/sdc02_4kings_b_02_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/team_0050_/team_0050_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/team_0070_/team_0070_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/main_scenario/team_0090_/team_0090_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/sub_scenario/s2_sub_039_/s2_sub_039_main_{0}.trsog",
        "world/scene/parts/event/event_scenario/sub_scenario/sub_018_/sub_018_pre_start_{0}.trsog",
    ];

    foreach (var t in IguanaScenes)
        UpdateOverworldSceneSingle(t, IguanaDev, IguanaForm, IguanaSex, IguanaRare);

    List<DevID> InletGrottoSpecies = new();
    List<short> InletGrottoForm = new();
    List<SexType> InletGrottoSex = new();
    List<RareType> InletGrottoRare = new();

    for (int i = 0; i < 8; i++)
    {
        if (RANDOMIZE_GENERIC_OVERWORLD_POKEMON)
        {
            var species = GetRandomSpecies(0);
            var form = GetRandomForm(species);
            var sex = GetRandomGender(species, form);
            var rare = RollForRandomShiny();
            InletGrottoSpecies.Add(species);
            InletGrottoForm.Add(form);
            InletGrottoSex.Add(sex);
            InletGrottoRare.Add(rare);
        }

        else
        {
            InletGrottoSpecies.Add(DevID.DEV_DERUBIRU);
            InletGrottoForm.Add(0);
            InletGrottoSex.Add(0);
            InletGrottoRare.Add(0);
        }
    }

    InletGrottoSpecies.Insert(0, IguanaDev);
    InletGrottoForm.Insert(0, IguanaForm);
    InletGrottoSex.Insert(0, IguanaSex);
    InletGrottoRare.Insert(0, IguanaRare);
    UpdateOverworldSceneMulti("world/scene/parts/demo/ev/d050_/d050_{0}.trscn", InletGrottoSpecies, InletGrottoForm, InletGrottoSex, InletGrottoRare, false);

    var MabosstiffSpecies = RANDOMIZE_GENERIC_OVERWORLD_POKEMON ? GetRandomSpecies(0) : DevID.DEV_MASUTHIHU2;
    var MabosstiffForm = RANDOMIZE_GENERIC_OVERWORLD_POKEMON ? GetRandomForm(MabosstiffSpecies) : (short)0;
    var MabosstiffSex = RANDOMIZE_GENERIC_OVERWORLD_POKEMON ? GetRandomGender(MabosstiffSpecies, MabosstiffForm) : 0;
    var MabosstiffRare = RANDOMIZE_GENERIC_OVERWORLD_POKEMON ? RollForRandomShiny() : 0;

    List<DevID> PathOfLegendsSpecies = [ IguanaDev,  MabosstiffSpecies ];
    List<short> PathOfLegendsForm    = [ IguanaForm, MabosstiffForm    ];
    List<SexType> PathOfLegendsSex   = [ IguanaSex,  MabosstiffSex     ];
    List<RareType> PathOfLegendsRare = [ IguanaRare, MabosstiffRare    ];
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/legend_0030_/legend_0030_main_{0}.trsog",   PathOfLegendsSpecies, PathOfLegendsForm, PathOfLegendsSex, PathOfLegendsRare, false);
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/legend_0040_/legend_0040_main_{0}.trsog",   PathOfLegendsSpecies, PathOfLegendsForm, PathOfLegendsSex, PathOfLegendsRare, false);
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/legend_0050_/legend_0050_main_{0}.trsog",   PathOfLegendsSpecies, PathOfLegendsForm, PathOfLegendsSex, PathOfLegendsRare, false);
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/legend_0060_/legend_0060_always_{0}.trsog", PathOfLegendsSpecies, PathOfLegendsForm, PathOfLegendsSex, PathOfLegendsRare, false);

    List<DevID> CrystalPoolSpecies = [ IguanaDev,  DevID.DEV_KODAIGAME ];
    List<short> CrystalPoolForm    = [ IguanaForm, 0 ];
    List<SexType> CrystalPoolSex   = [ IguanaSex,  0 ];
    List<RareType> CrystalPoolRare = [ IguanaRare, 0 ];
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/sub_scenario/s2_sub_042_/s2_sub_042_always_{0}.trsog", CrystalPoolSpecies, CrystalPoolForm, CrystalPoolSex, CrystalPoolRare, false);

    // crashes and softlocks
    // world/scene/parts/event/event_scenario/main_scenario/common_0150_/common_0150_pre_start_{0}.trsog -- randomized Pokémon does not walk inside Inlet Grotto, softlocking the game
}

private static void UpdateAllKitakamiScenes(EventBattlePokemonArray obj)
{
    var table = obj.Table;

    var OkidogiDev  = table[069].PokeData.DevId;
    var OkidogiForm = table[069].PokeData.FormId;
    var OkidogiSex  = table[069].PokeData.Sex;
    var OkidogiRare = table[069].PokeData.RareType;

    var MunkidoriDev  = table[073].PokeData.DevId;
    var MunkidoriForm = table[073].PokeData.FormId;
    var MunkidoriSex  = table[073].PokeData.Sex;
    var MunkidoriRare = table[073].PokeData.RareType;

    var FezandipitiDev  = table[071].PokeData.DevId;
    var FezandipitiForm = table[071].PokeData.FormId;
    var FezandipitiSex  = table[071].PokeData.Sex;
    var FezandipitiRare = table[071].PokeData.RareType;

    var OgerponDev  = table[065].PokeData.DevId;
    var OgerponForm = table[065].PokeData.FormId;
    var OgerponSex  = table[065].PokeData.Sex;
    var OgerponRare = table[065].PokeData.RareType;

    List<DevID> KitakamiSpecies0 = [ OkidogiDev, MunkidoriDev,   FezandipitiDev                ];
    List<DevID> KitakamiSpecies1 = [ OgerponDev, OkidogiDev,     MunkidoriDev, FezandipitiDev  ];
    List<DevID> KitakamiSpecies2 = [ OgerponDev, FezandipitiDev, MunkidoriDev, OkidogiDev      ];

    List<short> KitakamiForm0 = [ OkidogiForm, MunkidoriForm,   FezandipitiForm                ];
    List<short> KitakamiForm1 = [ OgerponForm, OkidogiForm,     MunkidoriForm, FezandipitiForm ];
    List<short> KitakamiForm2 = [ OgerponForm, FezandipitiForm, MunkidoriForm, OkidogiForm     ];

    List<SexType> KitakamiSex0 = [ OkidogiSex, MunkidoriSex,   FezandipitiSex                  ];
    List<SexType> KitakamiSex1 = [ OgerponSex, OkidogiSex,     MunkidoriSex, FezandipitiSex    ];
    List<SexType> KitakamiSex2 = [ OgerponSex, FezandipitiSex, MunkidoriSex, OkidogiSex        ];

    List<RareType> KitakamiRare0 = [ OkidogiRare, MunkidoriRare,   FezandipitiRare                ];
    List<RareType> KitakamiRare1 = [ OgerponRare, OkidogiRare,     MunkidoriRare, FezandipitiRare ];
    List<RareType> KitakamiRare2 = [ OgerponRare, FezandipitiRare, MunkidoriRare, OkidogiRare     ];

    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/sdc01_0330_/sdc01_0330_always_{0}.trsog",    KitakamiSpecies0, KitakamiForm0, KitakamiSex0, KitakamiRare0, false);
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/sdc01_0360_/sdc01_0360_pre_start_{0}.trsog", KitakamiSpecies1, KitakamiForm1, KitakamiSex1, KitakamiRare1, false);
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/sdc01_0400_/sdc01_0400_main_{0}.trsog",      KitakamiSpecies2, KitakamiForm2, KitakamiSex2, KitakamiRare2, false);
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/sdc01_0410_/sdc01_0410_main_{0}.trsog",      KitakamiSpecies2, KitakamiForm2, KitakamiSex2, KitakamiRare2, false);
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/sdc01_0420_/sdc01_0420_main_{0}.trsog",      KitakamiSpecies2, KitakamiForm2, KitakamiSex2, KitakamiRare2, false);

    // Loyal Three
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_3gods_a_/sdc01_3gods_a_pre_start_{0}.trsog",               OkidogiDev,     OkidogiForm,     OkidogiSex,     OkidogiRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s1_sub_011_/s1_sub_011_pre_start_{0}.trsog",                      OkidogiDev,     OkidogiForm,     OkidogiSex,     OkidogiRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_3gods_b_/sdc01_3gods_b_pre_start_{0}.trsog",               MunkidoriDev,   MunkidoriForm,   MunkidoriSex,   MunkidoriRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s1_sub_012_/s1_sub_012_pre_start_{0}.trsog",                      MunkidoriDev,   MunkidoriForm,   MunkidoriSex,   MunkidoriRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_3gods_c_/sdc01_3gods_c_pre_start_{0}.trsog",               FezandipitiDev, FezandipitiForm, FezandipitiSex, FezandipitiRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_3gods_c_before_/sdc01_3gods_c_before_pre_start_{0}.trsog", FezandipitiDev, FezandipitiForm, FezandipitiSex, FezandipitiRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s1_sub_016_/s1_sub_016_pre_start_{0}.trsog",                      FezandipitiDev, FezandipitiForm, FezandipitiSex, FezandipitiRare);

    // Ogerpon
    UpdateOverworldSceneSingle("world/scene/parts/demo/ev/d610_/d610_{0}.trscn",                                                    OgerponDev, OgerponForm, OgerponSex, OgerponRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0105_/sdc01_0105_main_{0}.trsog",        OgerponDev, OgerponForm, OgerponSex, OgerponRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0130_/sdc01_0130_always_{0}.trsog",      OgerponDev, OgerponForm, OgerponSex, OgerponRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0160_/sdc01_0160_always_{0}.trsog",      OgerponDev, OgerponForm, OgerponSex, OgerponRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0370_/sdc01_0370_post_end_{0}.trsog",    OgerponDev, OgerponForm, OgerponSex, OgerponRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0390_/sdc01_0390_main_{0}.trsog",        OgerponDev, OgerponForm, OgerponSex, OgerponRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0430_/sdc01_0430_main_{0}.trsog",        OgerponDev, OgerponForm, OgerponSex, OgerponRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0440_/sdc01_0440_always_{0}.trsog",      OgerponDev, OgerponForm, OgerponSex, OgerponRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0460_/sdc01_0460_main_{0}.trsog",        OgerponDev, OgerponForm, OgerponSex, OgerponRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0480_/sdc01_0480_main_{0}.trsog",        OgerponDev, OgerponForm, OgerponSex, OgerponRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_3gods_a_/sdc01_3gods_a_main_{0}.trsog",  OgerponDev, OgerponForm, OgerponSex, OgerponRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_3gods_b_/sdc01_3gods_b_main_{0}.trsog",  OgerponDev, OgerponForm, OgerponSex, OgerponRare);
    UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_3gods_c_/sdc01_3gods_c_main_{0}.trsog",  OgerponDev, OgerponForm, OgerponSex, OgerponRare);
}

private static void UpdateAllTrainerScenes(TrDataMainArray obj)
{
    var table = obj.Table;
    var Shellder   = table.FirstOrDefault(z => z.TrId is "pepper_nusi_01").Poke1;
    var Toedscool  = table.FirstOrDefault(z => z.TrId is "pepper_nusi_02").Poke1;
    var Nacli      = table.FirstOrDefault(z => z.TrId is "pepper_nusi_03").Poke1;
    var Scovillain = table.FirstOrDefault(z => z.TrId is "pepper_nusi_04").Poke1;
    var Greedent   = table.FirstOrDefault(z => z.TrId is "pepper_nusi_05").Poke1;

    List<DevID> TitanRockSpecies = [ 0, 0, Shellder.DevId,    Shellder.DevId    ];
    List<short> TitanRockForm    = [ 0, 0, Shellder.FormId,   Shellder.FormId   ];
    List<SexType> TitanRockSex   = [ 0, 0, Shellder.Sex,      Shellder.Sex      ];
    List<RareType> TitanRockRare = [ 0, 0, Shellder.RareType, Shellder.RareType ];

    List<DevID> TitanDragonSpecies = [ 0, 0, 0, 0, Greedent.DevId,    Greedent.DevId    ];
    List<short> TitanDragonForm    = [ 0, 0, 0, 0, Greedent.FormId,   Greedent.FormId   ];
    List<SexType> TitanDragonSex   = [ 0, 0, 0, 0, Greedent.Sex,      Greedent.Sex      ];
    List<RareType> TitanDragonRare = [ 0, 0, 0, 0, Greedent.RareType, Greedent.RareType ];

    UpdateOverworldSceneSingle("world/scene/parts/field/field_contents/nushi/hagane/HaganeRockClashEvent_/HaganeRockClashEvent_{0}.trscn", Toedscool.DevId, Toedscool.FormId, Toedscool.Sex, Toedscool.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/field/field_contents/nushi/hiko/HikoRockClashEvent_/HikoRockClashEvent_{0}.trscn", Nacli.DevId, Nacli.FormId, Nacli.Sex, Nacli.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/field/field_contents/nushi/jimen/JimenRockClashEvent_/JimenRockClashEvent_{0}.trscn", Scovillain.DevId, Scovillain.FormId, Scovillain.Sex, Scovillain.RareType);
    UpdateOverworldSceneSingle("world/scene/parts/field/field_contents/nushi/dragon/DragonRockClashEvent_/DragonRockClashEvent_{0}.trscn", Greedent.DevId,   Greedent.FormId, Greedent.Sex, Greedent.RareType);

    UpdateOverworldSceneMulti("world/scene/parts/field/field_contents/nushi/common/RockClashEvent_/RockClashEvent_{0}.trscn", TitanRockSpecies, TitanRockForm, TitanRockSex, TitanRockRare, true);
    UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/nushi_dragon_020_/nushi_dragon_020_pre_start_{0}.trsog", TitanDragonSpecies, TitanDragonForm, TitanDragonSex, TitanDragonRare, true);
}

private static void PrepareInitialChanges()
{
    "Preparing randomizer changes...".Dump();

    // always output to a romfs folder
    if (!DEST_MASTER.EndsWith("romfs") && !DEST_MASTER.EndsWith("romfs/") && !DEST_MASTER.EndsWith("romfs\\"))
        DEST_MASTER += "/romfs/";

    // apply seed
    pkNX.Randomization.Util.ReseedRand(RANDOMIZER_SEED_VALUE);

    // only English is supported right now; translations are welcomed!
    List<string> ItemStringFiles = [ "iteminfo", "itemname", "itemname_acc", "itemname_acc_classified", "itemname_classified", "itemname_plural", "itemname_plural_classified" ];

    const ushort BLACK_AUGURITE_INDEX_OLD = 1691;
    const ushort BLACK_AUGURITE_INDEX_NEW = 2351;
    const ushort PEAT_BLOCK_INDEX_OLD = 1692;
    const ushort PEAT_BLOCK_INDEX_NEW = 2352;
    const ushort FOREIGN_TREAT_INDEX = 2353;
    const ushort LINKING_CORD_INDEX_OLD = 1611;
    const ushort LINKING_CORD_INDEX_NEW = 2354;

    // update item strings
    for (int i = 0; i < Languages.Count; i++)
    {
        var lang = Languages[i];
        foreach (var t in ItemStringFiles)
        {
            var destStrings = Path.Combine(DEST_MASTER, $"message/dat/{lang}/common/");
            var lines = GetStringsCommon(lang, t);
            var flags = GetStringFlagsCommon(lang, t);

            // copy strings
            lines[BLACK_AUGURITE_INDEX_NEW] = lines[BLACK_AUGURITE_INDEX_OLD];
            lines[PEAT_BLOCK_INDEX_NEW] = lines[PEAT_BLOCK_INDEX_OLD];
            lines[LINKING_CORD_INDEX_NEW] = lines[LINKING_CORD_INDEX_OLD];

            // copy flags
            flags[BLACK_AUGURITE_INDEX_NEW] = flags[BLACK_AUGURITE_INDEX_OLD];
            flags[PEAT_BLOCK_INDEX_NEW] = flags[PEAT_BLOCK_INDEX_OLD];
            flags[LINKING_CORD_INDEX_NEW] = flags[LINKING_CORD_INDEX_OLD];

            if (t is "iteminfo")
                lines[FOREIGN_TREAT_INDEX] = ForeignTreatDescriptions[i];

            else if (t.Contains("plural"))
                lines[FOREIGN_TREAT_INDEX] = ForeignTreatPlurals[i];

            else
                lines[FOREIGN_TREAT_INDEX] = ForeignTreatSingles[i];

            if (!Directory.Exists(destStrings))
                Directory.CreateDirectory(destStrings);
            var write = TextFile.GetBytes(lines, flags, TextConfig);
            var destName = t + ".dat";
            File.WriteAllBytes(Path.Combine(destStrings, destName), write);
        }
    }

    // update item icons
    for (int i = 2351; i <= 2354; i++)
    {
        var destIcon = Path.Combine(DEST_MASTER, $"appli/tex/icon_item/item_{i}/");
        var original = i is 2353 or 2354 ? 2522 : i - 660;
        if (!Directory.Exists(destIcon))
            Directory.CreateDirectory(destIcon);
        File.Copy(Path.Combine(PATH_MASTER, $"appli/tex/icon_item/item_{original}/item_{original}.bntx"), Path.Combine(destIcon, $"item_{i}.bntx"), true);
    }

    if (TYPE_THEMED_IMPORTANT_TRAINERS)
    {
        ThemedTypes.AddRange(Enumerable.Range(0, MAX_TYPE_ID + 1).Select(z => (byte)z));
        pkNX.Randomization.Util.Shuffle(ThemedTypes);
    }

    // refresh strings
    StringsNamesItem = GetStringsCommon(LANGUAGE, "itemname");
}

private static int GetRandomizerSeedValue()
{
    if (string.IsNullOrWhiteSpace(RANDOMIZER_SEED))
        return pkNX.Randomization.Util.Random.Next();

    var hasher = MD5.Create();
    var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(RANDOMIZER_SEED));
    return BitConverter.ToInt32(hash, 0);
}

private static bool GetArePathsValid()
{
    if (string.IsNullOrWhiteSpace(DEST_MASTER))
        return false;
    if (string.IsNullOrWhiteSpace(PATH_MASTER))
        return false;
    if (!Languages.Contains(LANGUAGE))
        return false;
    if (GENERATE_RANDOMIZER_LOG_FILE && string.IsNullOrWhiteSpace(DEST_LOG))
        return false;
    if (AUTOMATICALLY_PATCH_TRPFD && (string.IsNullOrWhiteSpace(PATH_TRPFD)))
        return false;
    return true;
}

private static void RandomizeRandomizerSettings()
{
    ALLOW_SPECIES_GEN_1 = GetRandom(100) < 90; UpdateRandomizerSettingBoolean(ALLOW_SPECIES_GEN_1, nameof(ALLOW_SPECIES_GEN_1));
    ALLOW_SPECIES_GEN_2 = GetRandom(100) < 90; UpdateRandomizerSettingBoolean(ALLOW_SPECIES_GEN_2, nameof(ALLOW_SPECIES_GEN_2));
    ALLOW_SPECIES_GEN_3 = GetRandom(100) < 90; UpdateRandomizerSettingBoolean(ALLOW_SPECIES_GEN_3, nameof(ALLOW_SPECIES_GEN_3));
    ALLOW_SPECIES_GEN_4 = GetRandom(100) < 90; UpdateRandomizerSettingBoolean(ALLOW_SPECIES_GEN_4, nameof(ALLOW_SPECIES_GEN_4));
    ALLOW_SPECIES_GEN_5 = GetRandom(100) < 90; UpdateRandomizerSettingBoolean(ALLOW_SPECIES_GEN_5, nameof(ALLOW_SPECIES_GEN_5));
    ALLOW_SPECIES_GEN_6 = GetRandom(100) < 90; UpdateRandomizerSettingBoolean(ALLOW_SPECIES_GEN_6, nameof(ALLOW_SPECIES_GEN_6));
    ALLOW_SPECIES_GEN_7 = GetRandom(100) < 90; UpdateRandomizerSettingBoolean(ALLOW_SPECIES_GEN_7, nameof(ALLOW_SPECIES_GEN_7));
    ALLOW_SPECIES_GEN_8 = GetRandom(100) < 90; UpdateRandomizerSettingBoolean(ALLOW_SPECIES_GEN_8, nameof(ALLOW_SPECIES_GEN_8));
    ALLOW_SPECIES_GEN_9 = GetRandom(100) < 90; UpdateRandomizerSettingBoolean(ALLOW_SPECIES_GEN_9, nameof(ALLOW_SPECIES_GEN_9));

    ALLOW_SPECIES_LEGENDARY = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(ALLOW_SPECIES_LEGENDARY, nameof(ALLOW_SPECIES_LEGENDARY));
    ALLOW_SPECIES_MYTHICAL = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(ALLOW_SPECIES_MYTHICAL, nameof(ALLOW_SPECIES_MYTHICAL));
    ALLOW_SPECIES_PARADOX = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(ALLOW_SPECIES_PARADOX, nameof(ALLOW_SPECIES_PARADOX));

    FORCE_SWAP_SPECIES_LEGENDARY = !ALLOW_SPECIES_LEGENDARY ? false : GetRandom(100) < 50; UpdateRandomizerSettingBoolean(FORCE_SWAP_SPECIES_LEGENDARY, nameof(FORCE_SWAP_SPECIES_LEGENDARY));
    FORCE_SWAP_SPECIES_MYTHICAL = !ALLOW_SPECIES_MYTHICAL ? false : GetRandom(100) < 50; UpdateRandomizerSettingBoolean(FORCE_SWAP_SPECIES_MYTHICAL, nameof(FORCE_SWAP_SPECIES_MYTHICAL));
    FORCE_SWAP_SPECIES_PARADOX = !ALLOW_SPECIES_PARADOX ? false : GetRandom(100) < 50; UpdateRandomizerSettingBoolean(FORCE_SWAP_SPECIES_PARADOX, nameof(FORCE_SWAP_SPECIES_PARADOX));
    FORCE_SWAP_SPECIES_ALL_SPECIAL_POKEMON = !ALLOW_SPECIES_LEGENDARY && !ALLOW_SPECIES_MYTHICAL && !ALLOW_SPECIES_PARADOX ? false : GetRandom(100) < 20; UpdateRandomizerSettingBoolean(FORCE_SWAP_SPECIES_ALL_SPECIAL_POKEMON, nameof(FORCE_SWAP_SPECIES_ALL_SPECIAL_POKEMON));

    RANDOMIZE_PERSONAL_ABILITY = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_PERSONAL_ABILITY, nameof(RANDOMIZE_PERSONAL_ABILITY));
    RANDOMIZE_PERSONAL_TYPE = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_PERSONAL_TYPE, nameof(RANDOMIZE_PERSONAL_TYPE));

    // only pick one or the other
    var evo = GetRandom(2);
    if (evo is 0) RANDOMIZE_PERSONAL_EVOLUTION = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_PERSONAL_EVOLUTION, nameof(RANDOMIZE_PERSONAL_EVOLUTION));
    if (evo is 1) MAKE_POKEMON_EVOLVE_EVERY_LEVEL = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(MAKE_POKEMON_EVOLVE_EVERY_LEVEL, nameof(MAKE_POKEMON_EVOLVE_EVERY_LEVEL));

    // only pick one or the other
    var learn = GetRandom(2);
    if (learn is 0) RANDOMIZE_PERSONAL_LEARNSET = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_PERSONAL_LEARNSET, nameof(RANDOMIZE_PERSONAL_LEARNSET));
    if (learn is 1) ALL_POKEMON_ONLY_KNOW_METRONOME = GetRandom(100) < 30; UpdateRandomizerSettingBoolean(ALL_POKEMON_ONLY_KNOW_METRONOME, nameof(ALL_POKEMON_ONLY_KNOW_METRONOME));

    // only pick one of the three
    var stats = GetRandom(3);
    if (stats is 0) RANDOMIZE_PERSONAL_BASE_STATS_WITHIN_BST = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_PERSONAL_BASE_STATS_WITHIN_BST, nameof(RANDOMIZE_PERSONAL_BASE_STATS_WITHIN_BST));
    if (stats is 1) RANDOMIZE_PERSONAL_BASE_STATS_COMPLETELY_RANDOM = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_PERSONAL_BASE_STATS_COMPLETELY_RANDOM, nameof(RANDOMIZE_PERSONAL_BASE_STATS_COMPLETELY_RANDOM));
    if (stats is 2) RANDOMIZE_PERSONAL_BASE_STATS_SHUFFLED = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_PERSONAL_BASE_STATS_SHUFFLED, nameof(RANDOMIZE_PERSONAL_BASE_STATS_SHUFFLED));

    ABILITIES_TYPES_FOLLOW_EVOLUTIONS = !(RANDOMIZE_PERSONAL_ABILITY && RANDOMIZE_PERSONAL_TYPE) ? false : GetRandom(100) < 50; UpdateRandomizerSettingBoolean(ABILITIES_TYPES_FOLLOW_EVOLUTIONS, nameof(ABILITIES_TYPES_FOLLOW_EVOLUTIONS));
    GAIN_RANDOM_TYPE_EVO_1_CHANCE_PERCENT = (byte)GetRandom(101);
    GAIN_RANDOM_TYPE_EVO_2_CHANCE_PERCENT = (byte)GetRandom(101);
    RANDOM_DUAL_TYPE_CHANCE_PERCENT = (byte)GetRandom(101);

    ALLOW_STARMOBILE_TORQUE_MOVES = !RANDOMIZE_PERSONAL_LEARNSET ? false : GetRandom(100) < 50; UpdateRandomizerSettingBoolean(ALLOW_STARMOBILE_TORQUE_MOVES, nameof(ALLOW_STARMOBILE_TORQUE_MOVES));
    GUARANTEE_FOUR_MOVES_AT_LEVEL_ONE = !RANDOMIZE_PERSONAL_LEARNSET ? false : GetRandom(100) < 50; UpdateRandomizerSettingBoolean(GUARANTEE_FOUR_MOVES_AT_LEVEL_ONE, nameof(GUARANTEE_FOUR_MOVES_AT_LEVEL_ONE));
    RANDOMIZE_LEARNSETS_WITH_TYPE_BIAS = !RANDOMIZE_PERSONAL_LEARNSET ? false : GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_LEARNSETS_WITH_TYPE_BIAS, nameof(RANDOMIZE_LEARNSETS_WITH_TYPE_BIAS));
    STANDARDIZE_LEVELS_FOR_LEARNING_MOVES = !RANDOMIZE_PERSONAL_LEARNSET ? false : GetRandom(100) < 50; UpdateRandomizerSettingBoolean(STANDARDIZE_LEVELS_FOR_LEARNING_MOVES, nameof(STANDARDIZE_LEVELS_FOR_LEARNING_MOVES));
    TECHNICAL_MACHINE_LEARNSET_SANITY = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(TECHNICAL_MACHINE_LEARNSET_SANITY, nameof(TECHNICAL_MACHINE_LEARNSET_SANITY));
    RANDOM_LEARNSET_TYPE_BIAS_PERCENT = (byte)GetRandom(101);

    // be generous, this setting can be silly
    if (GetRandom(100) < 20)
    {
        RANDOMIZE_MOVE_PROPERTIES_TYPE = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_MOVE_PROPERTIES_TYPE, nameof(RANDOMIZE_MOVE_PROPERTIES_TYPE));
        RANDOMIZE_MOVE_PROPERTIES_CATEGORY = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_MOVE_PROPERTIES_CATEGORY, nameof(RANDOMIZE_MOVE_PROPERTIES_CATEGORY));
        RANDOMIZE_MOVE_PROPERTIES_POWER = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_MOVE_PROPERTIES_POWER, nameof(RANDOMIZE_MOVE_PROPERTIES_POWER));
        RANDOMIZE_MOVE_PROPERTIES_ACCURACY = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_MOVE_PROPERTIES_ACCURACY, nameof(RANDOMIZE_MOVE_PROPERTIES_ACCURACY));
        RANDOMIZE_MOVE_PROPERTIES_PP = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_MOVE_PROPERTIES_PP, nameof(RANDOMIZE_MOVE_PROPERTIES_PP));
    }

    FORCE_FULLY_EVOLVE = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(FORCE_FULLY_EVOLVE, nameof(FORCE_FULLY_EVOLVE));
    FORCE_FULLY_EVOLVE_LEVEL = (byte)GetRandom(25, 71);

    MODIFY_POKEMON_LEVELS = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(MODIFY_POKEMON_LEVELS, nameof(MODIFY_POKEMON_LEVELS));
    LEVEL_MODIFIER_PERCENT = (sbyte)GetRandom(5, 41);

    ALLOW_FULLY_RANDOM_TERA_TYPES = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(ALLOW_FULLY_RANDOM_TERA_TYPES, nameof(ALLOW_FULLY_RANDOM_TERA_TYPES));
    STELLAR_TERA_TYPE_CHANCE_PERCENT = (byte)GetRandom(1, 101);

    ALLOW_BATTLE_ONLY_FORMS = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(ALLOW_BATTLE_ONLY_FORMS, nameof(ALLOW_BATTLE_ONLY_FORMS));
    ALLOW_RANDOM_HIDDEN_ABILITIES = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(ALLOW_RANDOM_HIDDEN_ABILITIES, nameof(ALLOW_RANDOM_HIDDEN_ABILITIES));
    ALLOW_RANDOM_PICNIC_ITEMS = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(ALLOW_RANDOM_PICNIC_ITEMS, nameof(ALLOW_RANDOM_PICNIC_ITEMS));
    BAN_ABILITY_WONDER_GUARD = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(BAN_ABILITY_WONDER_GUARD, nameof(BAN_ABILITY_WONDER_GUARD));
    DO_NOT_RANDOMIZE_TERA_BLAST_TM = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(DO_NOT_RANDOMIZE_TERA_BLAST_TM, nameof(DO_NOT_RANDOMIZE_TERA_BLAST_TM));
    ENSURE_STARTERS_HAVE_TWO_EVOLUTIONS = GetRandom(100) < 10; UpdateRandomizerSettingBoolean(ENSURE_STARTERS_HAVE_TWO_EVOLUTIONS, nameof(ENSURE_STARTERS_HAVE_TWO_EVOLUTIONS));
    FORCE_FULLY_EVOLVE_TITAN_POKEMON = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(FORCE_FULLY_EVOLVE_TITAN_POKEMON, nameof(FORCE_FULLY_EVOLVE_TITAN_POKEMON));
    GIVE_IMPORTANT_TRAINERS_SIX_POKEMON = GetRandom(100) < 30; UpdateRandomizerSettingBoolean(GIVE_IMPORTANT_TRAINERS_SIX_POKEMON, nameof(GIVE_IMPORTANT_TRAINERS_SIX_POKEMON));
    GIVE_POKEMON_HELD_ITEMS = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(GIVE_POKEMON_HELD_ITEMS, nameof(GIVE_POKEMON_HELD_ITEMS));
    GIVE_POKEMON_RANDOM_POKE_BALLS = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(GIVE_POKEMON_RANDOM_POKE_BALLS, nameof(GIVE_POKEMON_RANDOM_POKE_BALLS));
    GIVE_POKEMON_RANDOM_RIBBONS = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(GIVE_POKEMON_RANDOM_RIBBONS, nameof(GIVE_POKEMON_RANDOM_RIBBONS));
    GIVE_TRAINERS_MAXIMUM_AI = GetRandom(100) < 30; UpdateRandomizerSettingBoolean(GIVE_TRAINERS_MAXIMUM_AI, nameof(GIVE_TRAINERS_MAXIMUM_AI));
    GIVE_TRAINERS_MAX_INDIVIDUAL_VALUES = GetRandom(100) < 30; UpdateRandomizerSettingBoolean(GIVE_TRAINERS_MAX_INDIVIDUAL_VALUES, nameof(GIVE_TRAINERS_MAX_INDIVIDUAL_VALUES));
    GIVE_TRAINERS_SMART_EFFORT_VALUES = GetRandom(100) < 30; UpdateRandomizerSettingBoolean(GIVE_TRAINERS_SMART_EFFORT_VALUES, nameof(GIVE_TRAINERS_SMART_EFFORT_VALUES));
    MAKE_ALL_TMS_CRAFTABLE_FROM_START = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(MAKE_ALL_TMS_CRAFTABLE_FROM_START, nameof(MAKE_ALL_TMS_CRAFTABLE_FROM_START));
    MAKE_ALL_TRAINER_BATTLES_DOUBLE_BATTLES = GetRandom(100) < 10; UpdateRandomizerSettingBoolean(MAKE_ALL_TRAINER_BATTLES_DOUBLE_BATTLES, nameof(MAKE_ALL_TRAINER_BATTLES_DOUBLE_BATTLES));
    MAKE_WILD_POKEMON_TIME_OF_DAY_DEPENDENT = GetRandom(100) < 30; UpdateRandomizerSettingBoolean(MAKE_WILD_POKEMON_TIME_OF_DAY_DEPENDENT, nameof(MAKE_WILD_POKEMON_TIME_OF_DAY_DEPENDENT));
    PRESERVE_FORM_CHANGE_ABILITIES = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(PRESERVE_FORM_CHANGE_ABILITIES, nameof(PRESERVE_FORM_CHANGE_ABILITIES));
    PRESERVE_OGERPON_EMBODY_ASPECT_ABILITIES = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(PRESERVE_OGERPON_EMBODY_ASPECT_ABILITIES, nameof(PRESERVE_OGERPON_EMBODY_ASPECT_ABILITIES));
    RANDOMIZE_POKEMON_TO_HAVE_SIMILAR_STRENGTH = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_POKEMON_TO_HAVE_SIMILAR_STRENGTH, nameof(RANDOMIZE_POKEMON_TO_HAVE_SIMILAR_STRENGTH));
    RANDOMIZE_ROAMING_FORM_GIMMIGHOUL = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_ROAMING_FORM_GIMMIGHOUL, nameof(RANDOMIZE_ROAMING_FORM_GIMMIGHOUL));
    RANDOMIZE_TERA_RAID_ITEM_DROPS_BONUS = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_TERA_RAID_ITEM_DROPS_BONUS, nameof(RANDOMIZE_TERA_RAID_ITEM_DROPS_BONUS));
    RANDOMIZE_TERA_RAID_ITEM_DROPS_FIXED = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_TERA_RAID_ITEM_DROPS_FIXED, nameof(RANDOMIZE_TERA_RAID_ITEM_DROPS_FIXED));
    REDUCE_EGG_HATCH_CYCLES = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(REDUCE_EGG_HATCH_CYCLES, nameof(REDUCE_EGG_HATCH_CYCLES));
    REMOVE_EFFORT_VALUE_YIELDS = GetRandom(100) < 30; UpdateRandomizerSettingBoolean(REMOVE_EFFORT_VALUE_YIELDS, nameof(REMOVE_EFFORT_VALUE_YIELDS));
    RIVAL_CARRIES_STARTER_THROUGHOUT_GAME = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RIVAL_CARRIES_STARTER_THROUGHOUT_GAME, nameof(RIVAL_CARRIES_STARTER_THROUGHOUT_GAME));
    TARGET_STAR_BARRAGE_KO_COUNT_INITIAL = (byte)GetRandom(1, 100);
    TARGET_STAR_BARRAGE_KO_COUNT_REMATCH = (byte)GetRandom(1, 100);
    TRAINER_POKEMON_SHINY_CHANCE_PERCENT = (byte)GetRandom(101);
    TYPE_THEMED_IMPORTANT_TRAINERS = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(TYPE_THEMED_IMPORTANT_TRAINERS, nameof(TYPE_THEMED_IMPORTANT_TRAINERS));

    ALLOW_ALL_TRAINERS_TO_TERASTALLIZE = GetRandom(100) < 10; UpdateRandomizerSettingBoolean(ALLOW_ALL_TRAINERS_TO_TERASTALLIZE, nameof(ALLOW_ALL_TRAINERS_TO_TERASTALLIZE));
    ALLOW_ALL_UNUSABLE_MOVES_TO_BE_USED = GetRandom(100) < 10; UpdateRandomizerSettingBoolean(ALLOW_ALL_UNUSABLE_MOVES_TO_BE_USED, nameof(ALLOW_ALL_UNUSABLE_MOVES_TO_BE_USED));
    RANDOMIZE_KORAIDON_MIRAIDON = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_KORAIDON_MIRAIDON, nameof(RANDOMIZE_KORAIDON_MIRAIDON));
    RANDOMIZE_GENERIC_OVERWORLD_POKEMON = GetRandom(100) < 50; UpdateRandomizerSettingBoolean(RANDOMIZE_GENERIC_OVERWORLD_POKEMON, nameof(RANDOMIZE_GENERIC_OVERWORLD_POKEMON));
    GENERIC_OVERWORLD_POKEMON_SHINY_CHANCE_PERCENT = (byte)GetRandom(101);

    // refresh
    SpeciesBanlist = GetSpeciesBanlist();
    SpeciesList = GetSpeciesList();
}

private static void UpdateRandomizerSettingBoolean(bool setting, string name)
{
    var index = Array.IndexOf(RandomizerSettingsDetails, RandomizerSettingsDetails.FirstOrDefault(z => z.Name == name));
    RandomizerSettingsDetails[index].Setting = setting;
}

private static void LogRandomizerSettings()
{
    var ctr = 6;
    RandomizerLog.Insert(ctr, "== RANDOMIZER SETTINGS BREAKDOWN ==");
    ctr++;

    foreach (var t in RandomizerSettingsDetails)
    {
        var state = t.Setting ? 'x' : ' ';
        var desc = t.Description;

        // refresh
        if (t.Name is "FORCE_FULLY_EVOLVE") desc = $"Force fully evolved Pokémon starting from Lv. {FORCE_FULLY_EVOLVE_LEVEL}";
        if (t.Name is "MODIFY_POKEMON_LEVELS") desc = $"Modify Pokémon levels by {LEVEL_MODIFIER_PERCENT}%";
        if (t.Name is "ALLOW_FULLY_RANDOM_TERA_TYPES") desc = $"Completely random Tera Types (Stellar: {STELLAR_TERA_TYPE_CHANCE_PERCENT}%)";
        if (t.Name is "RANDOMIZE_LEARNSETS_WITH_TYPE_BIAS") desc = $"Pokémon learnsets are type biased by {RANDOM_LEARNSET_TYPE_BIAS_PERCENT}%";

        RandomizerLog.Insert(ctr, $"[{state}] {desc}");
        ctr++;
    }

    RandomizerLog.Insert(ctr, $"[x] Star Barrage KO count (initial): {TARGET_STAR_BARRAGE_KO_COUNT_INITIAL}"); ctr++;
    RandomizerLog.Insert(ctr, $"[x] Star Barrage KO count (rematch): {TARGET_STAR_BARRAGE_KO_COUNT_REMATCH}"); ctr++;
    RandomizerLog.Insert(ctr, $"[x] Additional Pokémon for important trainers: {ADD_NUMBER_OF_POKEMON_TO_IMPORTANT_TRAINERS}"); ctr++;
    RandomizerLog.Insert(ctr, $"[x] Minimum trainer Pokémon: {TRAINER_MINIMUM_POKEMON_COUNT}"); ctr++;
    RandomizerLog.Insert(ctr, $"[x] Maximum trainer Pokémon: {TRAINER_MAXIMUM_POKEMON_COUNT}"); ctr++;
    RandomizerLog.Insert(ctr, $"[x] Trainer Pokémon Shiny rate: {TRAINER_POKEMON_SHINY_CHANCE_PERCENT}%"); ctr++;
    RandomizerLog.Insert(ctr, "");

    var seed = $"0x{RANDOMIZER_SEED_VALUE:X8}";
    if (!string.IsNullOrWhiteSpace(RANDOMIZER_SEED))
        seed += $" ({RANDOMIZER_SEED})";
    RandomizerLog.Insert(1, $"Randomizer Seed: {seed}");
}

private static void GenerateRandomizerLogFile()
{
    "Generating randomizer log file...".Dump();

    var dt = DateTime.Now;
    var date = dt.ToString("dddd, MMMM d, yyyy");
    var time = dt.ToString("HH:mm:ss");

    RandomizerLog.Insert(0, "POKÉMON SCARLET / POKÉMON VIOLET - RANDOMIZER");
    RandomizerLog.Insert(1, $"Randomized on: {date}");
    RandomizerLog.Insert(2, $"Randomized at: {time}");
    RandomizerLog.Insert(3, "The randomizer can be found at: https://gist.github.com/sora10pls/07d69600241f3a47959b802560ab8900");
    RandomizerLog.Insert(4, "If you experience any issues or have any questions or suggestions, please reach out with a comment at the above URL.");
    RandomizerLog.Insert(5, "");
    LogRandomizerSettings();

    if (!Directory.Exists(DEST_LOG))
        Directory.CreateDirectory(DEST_LOG);
    File.WriteAllLines(Path.Combine(DEST_LOG, "Randomizer Log.txt"), RandomizerLog);
}

private static void FinalizeRandomizerData()
{
    "Finalizing randomizer data...".Dump();
    var dest = Path.Combine(DEST_LOG, "ScarletVioletRandomizer.zip");
    var result = "In order to play your randomizer, run TrinityModLoader and apply a mod using the generated .zip file.";

    if (AUTOMATICALLY_PATCH_TRPFD)
    {
        try
        {
            PatchFileDescriptor();
            Process.Start("explorer.exe", DEST_MASTER.Replace("/", "\\"));
            result = "In order to play your randomizer, move all of the generated contents to your CFW console's SD Card, or to your emulator of choice's mods directory.";
        }

        catch (Exception e)
        {
            "Failed to patch file descriptor! Generating a .zip file instead...".Dump();
            GenerateRandomizerZipFile(dest);
        }
    }

    else
    {
        GenerateRandomizerZipFile(dest);
    }

    "".Dump();
    "Randomization is now complete!".Dump();
    result.Dump();
}

private static void PatchFileDescriptor()
{
    var dest = Path.Combine(DEST_MASTER, "arc");
    var obj = FlatBufferConverter.DeserializeFrom<TrinityFileDescriptors>(PATH_TRPFD);
    var files = Directory.EnumerateFiles(DEST_MASTER, "*.*", SearchOption.AllDirectories);

    foreach (var t in files)
    {
        var path = t.Replace(DEST_MASTER, "").Replace("\\", "/");
        var hash = FnvHash.HashFnv1a_64(path);
        var index = obj.SubFileHashes.IndexOf(hash);

        if (index < 0)
            continue;

        obj.SubFileHashes.RemoveAt(index);
        obj.SubFileInfos.RemoveAt(index);
    }

    var data = FlatBufferConverter.SerializeFrom(obj);
    if (!Directory.Exists(dest))
        Directory.CreateDirectory(dest);
    File.WriteAllBytes(Path.Combine(dest, "data.trpfd"), data);
}

private static void GenerateRandomizerZipFile(string dest)
{
    if (File.Exists(dest))
        File.Delete(dest);
    ZipFile.CreateFromDirectory(DEST_MASTER, dest);
    Process.Start("explorer.exe", DEST_LOG.Replace("/", "\\"));
}

private static List<ushort> BattleOnlyForms =
[
    0648, // Meloetta
    0774, // Minior
    0778, // Mimikyu
    0845, // Cramorant
    0875, // Eiscue
    0877, // Morpeko
    0888, // Zacian
    0889, // Zamazenta
    0964, // Palafin
    1024, // Terapagos
];

private static List<int> FormChangeAbilities =
[
    059, // Forecast
    121, // Multitype
    161, // Zen Mode
    176, // Stance Change
    197, // Shields Down
    208, // Schooling
    209, // Disguise
    211, // Power Construct
    225, // RKS System
    241, // Gulp Missile
    248, // Ice Face
    258, // Hunger Switch
    278, // Zero to Hero
    307, // Tera Shift
];

private static List<ushort> VagueAbilities =
[
    266, // As One (Glastrier)
    267, // As One (Spectrier)
    301, // Embody Aspect (Teal Mask)
    302, // Embody Aspect (Hearthflame Mask)
    303, // Embody Aspect (Wellspring Mask)
    304, // Embody Aspect (Cornerstone Mask)
];

private static List<ushort> UnavailableForms =
[
    // Mega Evolutions
    0003, 0006, 0009, 0015, 0018, 0065, 0080, 0094, 0115, 0127, 0130, 0142, 0150,
    0181, 0208, 0212, 0214, 0229, 0248,
    0254, 0257, 0260, 0282, 0302, 0303, 0306, 0308, 0310, 0319, 0323, 0334, 0354, 0359, 0362, 0373, 0376, 0380, 0381, 0384,
    0428, 0445, 0448, 0460, 0475,
    0531,
    0719,

    0133, // Partner Eevee
    0382, // Primal Kyogre
    0383, // Primal Groudon
];

private static bool IsGenderVariant(ushort species, short form) => species switch
{
    0003 or 0012 or 0041 or 0042 or 0044 or 0045 or 0064 or 0065 or 0084 or 0085 or 0097 or 0111 or 0112 or 0118 or 0119 or 0123 or 0129 or 0130 or 0133 => true,
    0154 or 0165 or 0166 or 0178 or 0185 or 0186 or 0190 or 0195 or 0198 or 0202 or 0203 or 0207 or 0208 or 0212 or 0214 or 0215 or 0217 or 0221 or 0224 or 0229 or 0232 => true,
    0255 or 0256 or 0257 or 0269 or 0272 or 0274 or 0275 or 0307 or 0308 or 0315 or 0316 or 0317 or 0322 or 0323 or 0332 or 0350 or 0369 => true,
    0396 or 0397 or 0398 or 0399 or 0400 or 0401 or 0402 or 0403 or 0404 or 0405 or 0407 or 0415 or 0417 or 0418 or 0419 or 0424 or 0443 or 0444 or 0445 or 0449 or 0450 or 0453 or 0454 or 0456 or 0457 or 0459 or 0460 or 0461 or 0464 or 0465 or 0473 => true,
    0521 or 0592 or 0593 => true,
    0668 => true,

    0019 or 0020 when form is 0 => true, // Rattata, Raticate
    0025 or 0026 when form is 0 => true, // Pikachu, Raichu
    0194 when form is 0 => true, // Wooper

    _ => false,
};

private static bool GetCanFlyOrLevitate(ushort species, short form) => species switch
{
    0006 or 0039 or 0040 or 0049 or 0070 or 0071 or 0074 or 0081 or 0082 or 0092 or 0093 or 0094 or 0109 or 0110 or 0123 or 0137 or 0144 or 0146 or 0149 or 0150 or 0151 => true,
    0163 or 0164 or 0174 or 0182 or 0187 or 0188 or 0189 or 0193 or 0198 or 0200 or 0205 or 0207 or 0214 or 0225 or 0227 or 0233 or 0250 => true,
    0278 or 0279 or 0284 or 0313 or 0314 or 0329 or 0330 or 0333 or 0334 or 0353 or 0355 or 0357 or 0358 or 0362 or 0373 or 0374 or 0375 or 0376 or 0380 or 0381 or 0384 or 0385 or 0386 => true,
    0396 or 0397 or 0398 or 0402 or 0415 or 0416 or 0425 or 0426 or 0429 or 0430 or 0433 or 0436 or 0437 or 0462 or 0469 or 0472 or 0474 or 0476 or 0477 or 0478 or 0479 or 0480 or 0481 or 0482 or 0483 or 0484 or 0487 or 0488 or 0491 or 0493 => true,
    0546 or 0547 or 0577 or 0578 or 0579 or 0608 or 0609 or 0615 or 0627 or 0628 or 0630 or 0635 or 0637 or 0641 or 0642 or 0643 or 0644 or 0645 or 0648 => true,
    0661 or 0662 or 0663 or 0666 or 0669 or 0670 or 0671 or 0686 or 0687 or 0701 or 0703 or 0707 or 0708 or 0714 or 0715 or 0719 or 0720 => true,
    0722 or 0723 or 0724 or 0731 or 0732 or 0733 or 0738 or 0741 or 0742 or 0743 or 0764 or 0774 or 0789 or 0790 or 0791 or 0792 or 0800 => true,
    0821 or 0822 or 0823 or 0841 or 0854 or 0855 or 0868 or 0873 or 0885 or 0886 or 0887 or 0890 or 0895 or 0905 => true,
    0931 or 0938 or 0940 or 0941 or 0954 or 0955 or 0962 or 0965 or 0966 or 0969 or 0970 or 0973 or 0985 or 0987 or 0993 or 0994 or 1004 or 1005 or 1008 or 1012 or 1013 or 1016 or 1025 => true,

    0026 when form is 1 => true, // Alolan Raichu
    0145 when form is 0 => true, // Zapdos
    0492 when form is 1 => true, // Sky Forme Shaymin
    0646 when form is not 0 => true, // White Kyurem, Black Kyurem
    0898 when form is 0 => true, // Calyrex

    _ => false,
};

private static bool GetCanSwimOrBeAboveWater(ushort species, short form) => species switch
{
    0001 or 0002 or 0003 or 0004 or 0005 or 0006 or 0023 or 0024 or 0027 or 0028 or 0035 or 0036 or 0037 or 0038 or 0043 or 0044 or 0045 or 0048 or 0050 or 0051 or 0052 or 0053 or 0056 or 0057 or 0058 or 0059 or 0069 or 0075 or 0076 or 0084 or 0085 or 0088 or 0089 or 0096 or 0097 or 0100 or 0101 or 0102 or 0103 or 0106 or 0107 or 0111 or 0112 or 0113 or 0125 or 0126 or 0132 or 0143 => false,
    0152 or 0153 or 0154 or 0155 or 0156 or 0157 or 0161 or 0162 or 0167 or 0168 or 0173 or 0179 or 0180 or 0181 or 0185 or 0190 or 0191 or 0192 or 0203 or 0204 or 0206 or 0209 or 0210 or 0212 or 0215 or 0216 or 0217 or 0218 or 0219 or 0220 or 0221 or 0228 or 0229 or 0231 or 0232 or 0234 or 0235 or 0236 or 0237 or 0239 or 0240 or 0242 or 0243 or 0244 or 0246 or 0247 or 0248 => false,
    0252 or 0253 or 0254 or 0255 or 0256 or 0257 or 0261 or 0262 or 0273 or 0274 or 0275 or 0280 or 0281 or 0282 or 0285 or 0286 or 0287 or 0288 or 0289 or 0296 or 0297 or 0299 or 0302 or 0307 or 0308 or 0311 or 0312 or 0316 or 0317 or 0322 or 0323 or 0324 or 0325 or 0326 or 0328 or 0331 or 0332 or 0335 or 0336 or 0354 or 0356 or 0361 or 0371 or 0372 or 0377 or 0378 or 0379 or 0383 => false,
    0387 or 0388 or 0389 or 0390 or 0391 or 0392 or 0401 or 0403 or 0404 or 0405 or 0408 or 0409 or 0410 or 0411 or 0417 or 0422 or 0423 or 0424 or 0434 or 0435 or 0438 or 0440 or 0442 or 0443 or 0444 or 0445 or 0446 or 0447 or 0448 or 0449 or 0450 or 0453 or 0454 or 0459 or 0460 or 0461 or 0464 or 0466 or 0467 or 0473 or 0475 or 0485 or 0486 => false,
    0495 or 0496 or 0497 or 0498 or 0499 or 0500 or 0522 or 0523 or 0529 or 0530 or 0532 or 0533 or 0534 or 0540 or 0541 or 0542 or 0548 or 0549 or 0551 or 0552 or 0553 or 0559 or 0560 or 0570 or 0571 or 0572 or 0573 or 0574 or 0575 or 0576 or 0585 or 0586 or 0590 or 0591 or 0595 or 0596 or 0607 or 0610 or 0611 or 0612 or 0613 or 0614 or 0619 or 0620 or 0622 or 0623 or 0624 or 0625 or 0629 or 0633 or 0634 or 0636 or 0638 or 0639 or 0640 => false,
    0650 or 0651 or 0652 or 0653 or 0654 or 0655 or 0664 or 0665 or 0667 or 0668 or 0672 or 0673 or 0677 or 0678 or 0702 or 0704 or 0705 or 0706 or 0709 or 0721 => false,
    0725 or 0726 or 0727 or 0734 or 0735 or 0736 or 0737 or 0739 or 0740 or 0744 or 0745 or 0749 or 0750 or 0753 or 0754 or 0757 or 0758 or 0761 or 0762 or 0763 or 0765 or 0766 or 0769 or 0770 or 0775 or 0778 or 0782 or 0783 or 0784 or 0801 => false,
    0810 or 0811 or 0812 or 0813 or 0814 or 0815 or 0819 or 0820 or 0837 or 0838 or 0839 or 0840 or 0842 or 0843 or 0844 or 0848 or 0849 or 0856 or 0857 or 0858 or 0859 or 0860 or 0861 or 0863 or 0869 or 0870 or 0871 or 0872 or 0874 or 0876 or 0877 or 0878 or 0879 or 0884 or 0888 or 0889 or 0891 or 0892 or 0893 or 0894 or 0896 or 0897 or 0899 or 0900 or 0901 or 0903 => false,
    0906 or 0907 or 0908 or 0909 or 0910 or 0911 or 0915 or 0916 or 0917 or 0918 or 0919 or 0920 or 0924 or 0925 or 0926 or 0927 or 0928 or 0929 or 0930 or 0932 or 0933 or 0934 or 0935 or 0936 or 0937 or 0939 or 0942 or 0943 or 0944 or 0945 or 0946 or 0947 or 0948 or 0949 or 0950 or 0951 or 0952 or 0953 or 0956 or 0957 or 0958 or 0959 or 0960 or 0961 or 0967 or 0968 or 0971 or 0972 or 0974 or 0975 or 0979 or 0981 or 0982 or 0983 or 0984 or 0986 or 0988 or 0989 or 0990 or 0991 or 0992 or 0995 or 0996 or 0997 or 0998 or 0999 or 1001 or 1002 or 1003 or 1006 or 1010 or 1011 or 1014 or 1015 or 1017 or 1018 or 1019 or 1020 or 1021 or 1022 or 1023 or 1024 => false,

    0128 when form is not 3 => false, // Tauros
    0145 when form is 1 => false, // Galarian Zapdos
    0492 when form is 0 => false, // Land Forme Shaymin
    0646 when form is 0 => false, // Kyurem
    0713 when form is 1 => false, // Hisuian Avalugg
    0898 when form is not 0 => false, // Ice Rider Calyrex, Shadow Rider Calyrex

    _ => true,
};

private static bool GetIsThreeStageFamily(ushort species) => species switch
{
    0001 or 0004 or 0007 or 0043 or 0056 or 0060 or 0069 or 0074 or 0074 or 0081 or 0092 or 0111 or 0116 or 0137 or 0147 => true,
    0152 or 0155 or 0158 or 0172 or 0173 or 0174 or 0179 or 0187 or 0216 or 0220 or 0239 or 0240 or 0246 => true,
    0252 or 0255 or 0258 or 0270 or 0273 or 0280 or 0287 or 0298 or 0328 or 0355 or 0371 or 0374 => true,
    0387 or 0390 or 0393 or 0396 or 0403 or 0440 or 0443 => true,
    0495 or 0498 or 0501 or 0532 or 0540 or 0551 or 0574 or 0577 or 0602 or 0607 or 0610 or 0624 or 0633 => true,
    0650 or 0653 or 0656 or 0661 or 0664 or 0669 or 0704 => true,
    0722 or 0725 or 0728 or 0731 or 0736 or 0761 or 0782 or 0789 => true,
    0810 or 0813 or 0816 or 0821 or 0837 or 0840 or 0856 or 0859 or 0885 => true,
    0906 or 0909 or 0912 or 0921 or 0928 or 0932 or 0957 or 0996 => true,

    _ => false,
};

private static bool IsGenderedEvolution(ushort species, byte form) => species switch
{
    0677 => true, // Espurr
    0915 => true, // Lechonk

    0550 when form is 2 => true, // Basculin (White-Striped Form)
    _ => false,
};

private static string GetFormString(List<AHTBEntry> FormStringEntries, string[] lines, ushort species, ushort form)
{
    // Tauros
    if (species is 0128 && form is not 0)
    {
        string[] forms = [ "", "Combat Breed", "Blaze Breed", "Aqua Breed" ];
        var paldea = GetStringFromAHTBCommon("zkn_form", FnvHash.HashFnv1a_64("ZKN_FORM_128_001"), LANGUAGE);
        return $"{paldea} / {forms[form]}";
    }

    // Kyogre, Groudon, Rotom, Kyurem
    if (species is 0382 or 0383 or 0479 or 0646 && form is 0)
        return string.Empty;

    // Arceus
    if (species is 0493)
        return StringsNamesType[form];

    // Greninja
    if (species is 0658 && form is 1)
        return StringsNamesAbility[210];

    // source Scatterbug and Spewpa from Vivillon
    if (species is 0664 or 0665)
        species = 0666;

    // Rockruff
    if (species is 0744 && form is 1)
        return StringsNamesAbility[020];

    // Minior
    if (species is 0774 && form is <= 6)
    {
        string[] forms = [ "Red", "Orange", "Yellow", "Green", "Blue", "Indigo", "Violet" ];
        var meteor = GetStringFromAHTBCommon("zkn_form", FnvHash.HashFnv1a_64("ZKN_FORM_774_000"), LANGUAGE);
        return $"{forms[form]} {meteor}";
    }

    // Ogerpon
    if (GetNationalDexID(species) is 1017 && form >= 4)
        form -= 4;

    var target = FormStringEntries.FirstOrDefault(z => z.Name == $"ZKN_FORM_{species:000}_{form:000}");
    var index = FormStringEntries.IndexOf(target);
    if (target is null)
        return string.Empty;
    return lines[index];
}

private static bool GetIsInvalidScenePokemon(ushort species) => species is 0778 or 0845 or 0875 or 0877;

private static List<ushort> ValuableItems =
[
    0001, // Master Ball
    0050, // Rare Candy
    0051, // PP Up
    0053, // PP Max
    0089, // Big Pearl
    0092, // Nugget
    0231, // Lucky Egg
    0492, // Fast Ball
    0493, // Level Ball
    0494, // Lure Ball
    0495, // Heavy Ball
    0496, // Love Ball
    0497, // Friend Ball
    0498, // Moon Ball
    0499, // Sport Ball
    0576, // Dream Ball
    0580, // Balm Mushroom
    0581, // Big Nugget
    0582, // Pearl String
    0583, // Comet Shard
    0645, // Ability Capsule
    0795, // Bottle Cap
    0796, // Gold Bottle Cap
    0851, // Beast Ball
    1127, // Exp. Candy L
    1128, // Exp. Candy XL
    1606, // Ability Patch
    1904, // Sweet Herba Mystica
    1905, // Salty Herba Mystica
    1906, // Sour Herba Mystica
    1907, // Bitter Herba Mystica
    1908, // Spicy Herba Mystica
    2137, // Gimmighoul Coin
];

private static int GetRandom(int max) => pkNX.Randomization.Util.Random.Next(max);
private static int GetRandom(int min, int max) => pkNX.Randomization.Util.Random.Next(min, max);

private static List<string> Languages = [ "English", "French", "German", "Italian", "JPN", "JPN_KANJI", "Korean", "Simp_Chinese", "Spanish", "Trad_Chinese" ];
private static List<string> BiomeNames = [ string.Empty, "Grass", "Forest", "Town", "Desert", "Mountain", "Snow", "Swamp", "Lake", "River", "Ocean", "Underground", "Rocky", "Cave", "Beach", "Flower", "Bamboo", "Wasteland", "Volcano", "Mine", "Olive", "Ruins", "CaveWater", "Chargestone" ];

private static List<byte> ToxtricityNaturesAmped = [ 0, 2, 3, 4, 6, 8, 9, 11, 13, 14, 19, 22, 24 ];
private static List<byte> ToxtricityNaturesLowKey = [ 1, 5, 7, 10, 12, 15, 16, 17, 18, 20, 21, 23 ];

private static List<string> ForeignTreatDescriptions =
[
    /*    ENG    */ "A snack made from ingredients favored by\nPokémon who evolve differently in other regions.",
    /*    FRE    */ "A snack made from ingredients favored by\nPokémon who evolve differently in other regions.",
    /*    GER    */ "A snack made from ingredients favored by\nPokémon who evolve differently in other regions.",
    /*    ITA    */ "A snack made from ingredients favored by\nPokémon who evolve differently in other regions.",
    /*    JPN    */ "A snack made from ingredients favored by\nPokémon who evolve differently in other regions.",
    /* JPN_KANJI */ "A snack made from ingredients favored by\nPokémon who evolve differently in other regions.",
    /*    KOR    */ "A snack made from ingredients favored by\nPokémon who evolve differently in other regions.",
    /*    CHS    */ "A snack made from ingredients favored by\nPokémon who evolve differently in other regions.",
    /*   SP-EU   */ "A snack made from ingredients favored by\nPokémon who evolve differently in other regions.",
    /*    CHT    */ "A snack made from ingredients favored by\nPokémon who evolve differently in other regions.",
];

private static List<string> ForeignTreatPlurals =
[
    /*    ENG    */ "Foreign Treats",
    /*    FRE    */ "Foreign Treats",
    /*    GER    */ "Foreign Treats",
    /*    ITA    */ "Foreign Treats",
    /*    JPN    */ "Foreign Treats",
    /* JPN_KANJI */ "Foreign Treats",
    /*    KOR    */ "Foreign Treats",
    /*    CHS    */ "Foreign Treats",
    /*   SP-EU   */ "Foreign Treats",
    /*    CHT    */ "Foreign Treats",
];

private static List<string> ForeignTreatSingles =
[
    /*    ENG    */ "Foreign Treat",
    /*    FRE    */ "Foreign Treat",
    /*    GER    */ "Foreign Treat",
    /*    ITA    */ "Foreign Treat",
    /*    JPN    */ "Foreign Treat",
    /* JPN_KANJI */ "Foreign Treat",
    /*    KOR    */ "Foreign Treat",
    /*    CHS    */ "Foreign Treat",
    /*   SP-EU   */ "Foreign Treat",
    /*    CHT    */ "Foreign Treat",
];

private static List<string> PlaceholderPokedexEntries =
[
    /*    ENG    */ "Ecology under research.",
    /*    FRE    */ "Recherches sur cette espèce en cours.",
    /*    GER    */ "Lebensweise wird untersucht.",
    /*    ITA    */ "Le ricerche su questa specie non\nsono ancora terminate.",
    /*    JPN    */ "せいたい ちょうさちゅう です。",
    /* JPN_KANJI */ "生態調査中です。",
    /*    KOR    */ "현재 생태 조사 중입니다.",
    /*    CHS    */ "生态调查中。",
    /*   SP-EU   */ "En proceso de investigación.",
    /*    CHT    */ "生態調查中。",
];

private static (string Name, bool Setting, string Description)[] RandomizerSettingsDetails =
{
    (nameof(ALLOW_SPECIES_GEN_1), ALLOW_SPECIES_GEN_1, "Allow Pokémon from Generation 1" ),
    (nameof(ALLOW_SPECIES_GEN_2), ALLOW_SPECIES_GEN_2, "Allow Pokémon from Generation 2" ),
    (nameof(ALLOW_SPECIES_GEN_3), ALLOW_SPECIES_GEN_3, "Allow Pokémon from Generation 3" ),
    (nameof(ALLOW_SPECIES_GEN_4), ALLOW_SPECIES_GEN_4, "Allow Pokémon from Generation 4" ),
    (nameof(ALLOW_SPECIES_GEN_5), ALLOW_SPECIES_GEN_5, "Allow Pokémon from Generation 5" ),
    (nameof(ALLOW_SPECIES_GEN_6), ALLOW_SPECIES_GEN_6, "Allow Pokémon from Generation 6" ),
    (nameof(ALLOW_SPECIES_GEN_7), ALLOW_SPECIES_GEN_7, "Allow Pokémon from Generation 7" ),
    (nameof(ALLOW_SPECIES_GEN_8), ALLOW_SPECIES_GEN_8, "Allow Pokémon from Generation 8" ),
    (nameof(ALLOW_SPECIES_GEN_9), ALLOW_SPECIES_GEN_9, "Allow Pokémon from Generation 9" ),

    (nameof(ALLOW_SPECIES_LEGENDARY), ALLOW_SPECIES_LEGENDARY, "Allow Legendary Pokémon" ),
    (nameof(ALLOW_SPECIES_MYTHICAL), ALLOW_SPECIES_MYTHICAL, "Allow Mythical Pokémon" ),
    (nameof(ALLOW_SPECIES_PARADOX), ALLOW_SPECIES_PARADOX, "Allow Paradox Pokémon" ),

    (nameof(FORCE_SWAP_SPECIES_LEGENDARY), FORCE_SWAP_SPECIES_LEGENDARY, "Swap Legendary Pokémon with other Legendary Pokémon" ),
    (nameof(FORCE_SWAP_SPECIES_MYTHICAL), FORCE_SWAP_SPECIES_MYTHICAL, "Swap Mythical Pokémon with other Mythical Pokémon" ),
    (nameof(FORCE_SWAP_SPECIES_PARADOX), FORCE_SWAP_SPECIES_PARADOX, "Swap Paradox Pokémon with other Paradox Pokémon" ),
    (nameof(FORCE_SWAP_SPECIES_ALL_SPECIAL_POKEMON), FORCE_SWAP_SPECIES_ALL_SPECIAL_POKEMON, "Swap Legendary, Mythical, and Paradox Pokémon with other Pokémon from any of the other groups" ),

    (nameof(RANDOMIZE_PERSONAL_ABILITY), RANDOMIZE_PERSONAL_ABILITY, "Randomize Pokémon abilities" ),
    (nameof(RANDOMIZE_PERSONAL_TYPE), RANDOMIZE_PERSONAL_TYPE, "Randomize Pokémon types" ),
    (nameof(RANDOMIZE_PERSONAL_EVOLUTION), RANDOMIZE_PERSONAL_EVOLUTION, "Randomize Pokémon evolutions" ),
    (nameof(RANDOMIZE_PERSONAL_LEARNSET), RANDOMIZE_PERSONAL_LEARNSET, "Randomize Pokémon learnsets" ),
    (nameof(RANDOMIZE_PERSONAL_BASE_STATS_WITHIN_BST), RANDOMIZE_PERSONAL_BASE_STATS_WITHIN_BST, "Randomize Pokémon base stats (within the original BST)" ),
    (nameof(RANDOMIZE_PERSONAL_BASE_STATS_COMPLETELY_RANDOM), RANDOMIZE_PERSONAL_BASE_STATS_COMPLETELY_RANDOM, "Randomize Pokémon base stats (completely random)" ),
    (nameof(RANDOMIZE_PERSONAL_BASE_STATS_SHUFFLED), RANDOMIZE_PERSONAL_BASE_STATS_SHUFFLED, "Shuffle Pokémon base stats" ),

    (nameof(ABILITIES_TYPES_FOLLOW_EVOLUTIONS), ABILITIES_TYPES_FOLLOW_EVOLUTIONS, "Random abilities and types persist upon evolution" ),

    (nameof(ALLOW_STARMOBILE_TORQUE_MOVES), ALLOW_STARMOBILE_TORQUE_MOVES, "Allow Starmobile Torque moves" ),
    (nameof(GUARANTEE_FOUR_MOVES_AT_LEVEL_ONE), GUARANTEE_FOUR_MOVES_AT_LEVEL_ONE, "Pokémon are guaranteed to have four moves at Lv. 1" ),
    (nameof(RANDOMIZE_LEARNSETS_WITH_TYPE_BIAS), RANDOMIZE_LEARNSETS_WITH_TYPE_BIAS, $"Pokémon learnsets are type biased by {RANDOM_LEARNSET_TYPE_BIAS_PERCENT}%" ),
    (nameof(STANDARDIZE_LEVELS_FOR_LEARNING_MOVES), STANDARDIZE_LEVELS_FOR_LEARNING_MOVES, "All Pokémon learn moves every 10 levels" ),
    (nameof(TECHNICAL_MACHINE_LEARNSET_SANITY), TECHNICAL_MACHINE_LEARNSET_SANITY, "Pokémon are guaranteed to learn TMs if they learn that move leveling up" ),

    (nameof(RANDOMIZE_MOVE_PROPERTIES_TYPE), RANDOMIZE_MOVE_PROPERTIES_TYPE, "Randomize move types" ),
    (nameof(RANDOMIZE_MOVE_PROPERTIES_CATEGORY), RANDOMIZE_MOVE_PROPERTIES_CATEGORY, "Randomize move categories" ),
    (nameof(RANDOMIZE_MOVE_PROPERTIES_POWER), RANDOMIZE_MOVE_PROPERTIES_POWER, "Randomize move power" ),
    (nameof(RANDOMIZE_MOVE_PROPERTIES_ACCURACY), RANDOMIZE_MOVE_PROPERTIES_ACCURACY, "Randomize move accuracy" ),
    (nameof(RANDOMIZE_MOVE_PROPERTIES_PP), RANDOMIZE_MOVE_PROPERTIES_PP, "Randomize move PP" ),

    (nameof(FORCE_FULLY_EVOLVE), FORCE_FULLY_EVOLVE, $"Force fully evolved Pokémon starting from Lv. {FORCE_FULLY_EVOLVE_LEVEL}" ),

    (nameof(MODIFY_POKEMON_LEVELS), MODIFY_POKEMON_LEVELS, $"Modify Pokémon levels by {LEVEL_MODIFIER_PERCENT}%" ),

    (nameof(ALLOW_FULLY_RANDOM_TERA_TYPES), ALLOW_FULLY_RANDOM_TERA_TYPES, $"Completely random Tera Types (Stellar: {STELLAR_TERA_TYPE_CHANCE_PERCENT}%)" ),

    (nameof(ALLOW_BATTLE_ONLY_FORMS), ALLOW_BATTLE_ONLY_FORMS, "Allow battle only forms outside of battle" ),
    (nameof(ALLOW_RANDOM_HIDDEN_ABILITIES), ALLOW_RANDOM_HIDDEN_ABILITIES, "Pokémon can have their Hidden Abilities" ),
    (nameof(ALLOW_RANDOM_PICNIC_ITEMS), ALLOW_RANDOM_PICNIC_ITEMS, "Add picnic items to the random item pool" ),
    (nameof(ALL_POKEMON_ONLY_KNOW_METRONOME), ALL_POKEMON_ONLY_KNOW_METRONOME, "All Pokémon only know Metronome" ),
    (nameof(BAN_ABILITY_WONDER_GUARD), BAN_ABILITY_WONDER_GUARD, "Ban Wonder Guard" ),
    (nameof(DO_NOT_RANDOMIZE_TERA_BLAST_TM), DO_NOT_RANDOMIZE_TERA_BLAST_TM, "TM171 Tera Blast is unchanged" ),
    (nameof(ENSURE_STARTERS_HAVE_TWO_EVOLUTIONS), ENSURE_STARTERS_HAVE_TWO_EVOLUTIONS, "Starter Pokémon are the base species of a three-stage evolutionary line" ),
    (nameof(FORCE_FULLY_EVOLVE_TITAN_POKEMON), FORCE_FULLY_EVOLVE_TITAN_POKEMON, "Force fully evolved Titan Pokémon" ),
    (nameof(GIVE_IMPORTANT_TRAINERS_SIX_POKEMON), GIVE_IMPORTANT_TRAINERS_SIX_POKEMON, "Important trainers have 6 Pokémon" ),
    (nameof(GIVE_POKEMON_HELD_ITEMS), GIVE_POKEMON_HELD_ITEMS, "Random held items" ),
    (nameof(GIVE_POKEMON_RANDOM_POKE_BALLS), GIVE_POKEMON_RANDOM_POKE_BALLS, "Random Poké Balls for certain Pokémon" ),
    (nameof(GIVE_POKEMON_RANDOM_RIBBONS), GIVE_POKEMON_RANDOM_RIBBONS, "Random Ribbon/Marks for certain Pokémon" ),
    (nameof(GIVE_TRAINERS_MAXIMUM_AI), GIVE_TRAINERS_MAXIMUM_AI, "Trainers have maximum AI" ),
    (nameof(GIVE_TRAINERS_MAX_INDIVIDUAL_VALUES), GIVE_TRAINERS_MAX_INDIVIDUAL_VALUES, "Trainer Pokémon have perfect IVs" ),
    (nameof(GIVE_TRAINERS_SMART_EFFORT_VALUES), GIVE_TRAINERS_SMART_EFFORT_VALUES, "Trainer Pokémon have optimal EV spreads" ),
    (nameof(MAKE_ALL_TMS_CRAFTABLE_FROM_START), MAKE_ALL_TMS_CRAFTABLE_FROM_START, "All TMs are craftable from the start" ),
    (nameof(MAKE_ALL_TRAINER_BATTLES_DOUBLE_BATTLES), MAKE_ALL_TRAINER_BATTLES_DOUBLE_BATTLES, "All trainer battles are now Double Battles" ),
    (nameof(MAKE_POKEMON_EVOLVE_EVERY_LEVEL), MAKE_POKEMON_EVOLVE_EVERY_LEVEL, "Pokémon evolve every level" ),
    (nameof(MAKE_WILD_POKEMON_TIME_OF_DAY_DEPENDENT), MAKE_WILD_POKEMON_TIME_OF_DAY_DEPENDENT, "Time of day dependent wild Pokémon" ),
    (nameof(PRESERVE_FORM_CHANGE_ABILITIES), PRESERVE_FORM_CHANGE_ABILITIES, "Preserve form change abilities" ),
    (nameof(PRESERVE_OGERPON_EMBODY_ASPECT_ABILITIES), PRESERVE_OGERPON_EMBODY_ASPECT_ABILITIES, "Preserve Ogerpon's Embody Aspect abilities" ),
    (nameof(RANDOMIZE_POKEMON_TO_HAVE_SIMILAR_STRENGTH), RANDOMIZE_POKEMON_TO_HAVE_SIMILAR_STRENGTH, "Randomize Pokémon to have similar strength" ),
    (nameof(RANDOMIZE_ROAMING_FORM_GIMMIGHOUL), RANDOMIZE_ROAMING_FORM_GIMMIGHOUL, "Randomize Roaming Form Gimmighoul" ),
    (nameof(RANDOMIZE_TERA_RAID_ITEM_DROPS_BONUS), RANDOMIZE_TERA_RAID_ITEM_DROPS_BONUS, "Randomize Tera Raid Battle bonus item drops" ),
    (nameof(RANDOMIZE_TERA_RAID_ITEM_DROPS_FIXED), RANDOMIZE_TERA_RAID_ITEM_DROPS_FIXED, "Randomize Tera Raid Battle fixed item drops" ),
    (nameof(REDUCE_EGG_HATCH_CYCLES), REDUCE_EGG_HATCH_CYCLES, "Reduce Egg hatch cycles" ),
    (nameof(REMOVE_EFFORT_VALUE_YIELDS), REMOVE_EFFORT_VALUE_YIELDS, "Remove EV yields" ),
    (nameof(RIVAL_CARRIES_STARTER_THROUGHOUT_GAME), RIVAL_CARRIES_STARTER_THROUGHOUT_GAME, "Nemona and Clavell use the other two starter Pokémon you did not pick" ),
    (nameof(TYPE_THEMED_IMPORTANT_TRAINERS), TYPE_THEMED_IMPORTANT_TRAINERS, "Type themed important trainers" ),

    (nameof(ALLOW_ALL_TRAINERS_TO_TERASTALLIZE), ALLOW_ALL_TRAINERS_TO_TERASTALLIZE, "All trainers can Terastallize" ),
    (nameof(ALLOW_ALL_UNUSABLE_MOVES_TO_BE_USED), ALLOW_ALL_UNUSABLE_MOVES_TO_BE_USED, "All unusable moves can now be used" ),
    (nameof(RANDOMIZE_KORAIDON_MIRAIDON), RANDOMIZE_KORAIDON_MIRAIDON, "Randomize Koraidon/Miraidon" ),
    (nameof(RANDOMIZE_GENERIC_OVERWORLD_POKEMON), RANDOMIZE_GENERIC_OVERWORLD_POKEMON, $"Randomize generic overworld Pokémon" ),
};

private static List<string> ImportantTrainers =
[
    "botan_01",
    "botan_02",
    "botan_02_01",
    "botan_multi",
    "botan_schoolwars",
    "brother_01_01",
    "brother_01_01_strong",
    "brother_01_02",
    "brother_01_02_strong",
    "brother_01_03",
    "brother_01_03_strong",
    "brother_01_04",
    "brother_01_04_strong",
    "brother_01_05",
    "brother_01_05_strong",
    "brother_02_01",
    "brother_kodaigame",
    "camera_01_01",
    "chairperson_01",
    "chairperson_02",
    "chairperson_03",
    "clavel_01",
    "clavel_01_hono",
    "clavel_01_kusa",
    "clavel_01_mizu",
    "clavel_02_hono",
    "clavel_02_kusa",
    "clavel_02_mizu",
    "dan_aku_boss_01",
    "dan_aku_boss_02",
    "dan_doku_boss_01",
    "dan_doku_boss_02",
    "dan_fairy_boss_01",
    "dan_fairy_boss_02",
    "dan_hono_boss_01",
    "dan_hono_boss_02",
    "dan_kakutou_boss_01",
    "dan_kakutou_boss_02",
    "dragon4_02_01",
    "e4_dragon_01",
    "e4_dragon_02",
    "e4_hagane_01",
    "e4_hikou_01",
    "e4_jimen_01",
    "fairy4_02_01",
    "fairy4_02_02",
    "gym_denki_leader_01",
    "gym_denki_leader_02",
    "gym_esper_leader_01",
    "gym_esper_leader_02",
    "gym_ghost_leader_01",
    "gym_ghost_leader_02",
    "gym_koori_leader_01",
    "gym_koori_leader_02",
    "gym_kusa_leader_01",
    "gym_kusa_leader_02",
    "gym_mizu_leader_01",
    "gym_mizu_leader_02",
    "gym_mushi_leader_01",
    "gym_mushi_leader_02",
    "gym_normal_leader_01",
    "gym_normal_leader_02",
    "hagane4_02_01",
    "hono4_02_01",
    "kihada_01",
    "kihada_02",
    "mimoza_01",
    "pepper_01",
    "pepper_02",
    "pepper_02_01",
    "pepper_03",
    "pepper_multi",
    "pepper_schoolwars",
    "professor_A_01",
    "professor_B_01",
    "rehoru_01",
    "rival_02",
    "rival_02_01hono",
    "rival_02_01kusa",
    "rival_02_01mizu",
    "rival_02_hono",
    "rival_02_kusa",
    "rival_02_mizu",
    "rival_03",
    "rival_03_hono",
    "rival_03_kusa",
    "rival_03_mizu",
    "rival_04_hono",
    "rival_04_kusa",
    "rival_04_mizu",
    "rival_05",
    "rival_05_hono",
    "rival_05_kusa",
    "rival_05_mizu",
    "rival_06",
    "rival_06_hono",
    "rival_06_kusa",
    "rival_06_mizu",
    "rival_07_hono",
    "rival_07_kusa",
    "rival_07_mizu",
    "rival_08_hono",
    "rival_08_kusa",
    "rival_08_mizu",
    "rival_multi_hono",
    "rival_multi_kusa",
    "rival_multi_mizu",
    "rival_schoolwars_hono",
    "rival_schoolwars_kusa",
    "rival_schoolwars_mizu",
    "s2_side_brother",
    "sawaro_01",
    "seizi_01",
    "shiano",
    "sister_01_01",
    "sister_01_01_strong",
    "sister_01_02",
    "sister_01_02_strong",
    "sister_01_03",
    "sister_01_03_strong",
    "sister_02_01",
    "sister_muruchi_01",
    "sister_muruchi_01_strong",
    "sister_onitaizi",
    "sister_onitaizi_strong",
    "su2_bukatu_Koori",
    "su2_bukatu_akamatu",
    "su2_bukatu_botan",
    "su2_bukatu_claver_honoo",
    "su2_bukatu_claver_kusa",
    "su2_bukatu_claver_mizu",
    "su2_bukatu_denki",
    "su2_bukatu_doragon",
    "su2_bukatu_esper",
    "su2_bukatu_ghost",
    "su2_bukatu_hagane",
    "su2_bukatu_hikou",
    "su2_bukatu_kakitubata",
    "su2_bukatu_kihada",
    "su2_bukatu_kusa",
    "su2_bukatu_mimoza",
    "su2_bukatu_mizu",
    "su2_bukatu_mushi",
    "su2_bukatu_nemo_honoo",
    "su2_bukatu_nemo_kusa",
    "su2_bukatu_nemo_mizu",
    "su2_bukatu_nerine",
    "su2_bukatu_omodaka",
    "su2_bukatu_pepa",
    "su2_bukatu_rehool",
    "su2_bukatu_sawaro",
    "su2_bukatu_seizi",
    "su2_bukatu_suguri",
    "su2_bukatu_taro",
    "su2_bukatu_time",
    "su2_bukatu_zeiyu",
    "su2_bukatu_zimen",
    "su2_bukatu_zinia",
    "taimu_01",
    "zinia_01",
    "zinia_02",
];

private static string[][] TypeThemedTrainers =
[
    /* Katy     */ [ "gym_mushi_01", "gym_mushi_02", "gym_mushi_03", "gym_mushi_leader_01", "gym_mushi_leader_02", "su2_bukatu_mushi" ],
    /* Brassius */ [ "gym_kusa_leader_01", "gym_kusa_leader_02", "su2_bukatu_kusa" ],
    /* Iono     */ [ "gym_denki_01", "gym_denki_02", "gym_denki_03", "gym_denki_leader_01", "gym_denki_leader_02", "su2_bukatu_denki" ],
    /* Kofu     */ [ "gym_mizu_01", "gym_mizu_leader_01", "gym_mizu_leader_02", "su2_bukatu_mizu" ],
    /* Larry    */ [ "gym_normal_01", "gym_normal_02", "gym_normal_03", "gym_normal_leader_01", "gym_normal_leader_02", "su2_bukatu_hikou" ],
    /* Ryme     */ [ "gym_ghost_01", "gym_ghost_02", "gym_ghost_03", "gym_ghost_leader_01", "gym_ghost_leader_02", "su2_bukatu_ghost" ],
    /* Tulip    */ [ "gym_esper_01", "gym_esper_02", "gym_esper_leader_01", "gym_esper_leader_02", "su2_bukatu_esper" ],
    /* Grusha   */ [ "gym_koori_leader_01", "gym_koori_leader_02", "su2_bukatu_Koori" ],
    /* Giacomo  */ [ "dan_aku_01", "dan_aku_boss_01", "dan_aku_boss_02" ],
    /* Mela     */ [ "dan_hono_01", "dan_hono_boss_01", "dan_hono_boss_02" ],
    /* Atticus  */ [ "dan_doku_01", "dan_doku_boss_01", "dan_doku_boss_02" ],
    /* Ortega   */ [ "dan_fairy_butler_01", "dan_fairy_boss_01", "dan_fairy_boss_02" ],
    /* Eri      */ [ "dan_kakutou_01", "dan_kakutou_boss_01", "dan_kakutou_boss_02" ],
    /* Rika     */ [ "e4_jimen_01", "su2_bukatu_zimen" ],
    /* Poppy    */ [ "e4_hagane_01", "su2_bukatu_hagane" ],
    /* Larry    */ [ "e4_hikou_01" ],
    /* Hassel   */ [ "e4_dragon_01", "e4_dragon_02", "su2_bukatu_doragon" ],
    /* Tyme     */ [ "taimu_01", "su2_bukatu_time" ],
    /* Dendra   */ [ "kihada_01", "kihada_02", "su2_bukatu_kihada" ],
    /* Crispin  */ [ "honochallenge_01", "honochallenge_02", "honochallenge_03", "hono4_02_01", "su2_bukatu_akamatu" ],
    /* Amarys   */ [ "hagane4_02_01", "su2_bukatu_nerine" ],
    /* Lacey    */ [ "fairychallenge_01", "fairychallenge_02", "fairychallenge_03", "fairychallenge_04", "fairychallenge_05", "fairy4_02_02", "su2_bukatu_taro" ],
    /* Drayton  */ [ "dragonchallenge_01", "dragonchallenge_02", "dragonchallenge_03", "dragon4_02_01", "su2_bukatu_kakitubata" ],
];

private static List<string> GenericOverworldPokemonScenes =
[
    "world/obj_template/parts/demo/sd/sd8040_fu_result_/sd8040_fu_result_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_025_/fp_025_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_055_/fp_055_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_082_/fp_082_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_094_/fp_094_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_129_/fp_129_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_129_group_/fp_129_group_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_130_/fp_130_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_156_/fp_156_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_198_/fp_198_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_340_/fp_340_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_396_/fp_396_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_396_group_/fp_396_group_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_397_/fp_397_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_398_/fp_398_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_403_/fp_403_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_403_group_/fp_403_group_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_405_/fp_405_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_415_/fp_415_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_415_group_/fp_415_group_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_417_/fp_417_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_418_/fp_418_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_418_group_/fp_418_group_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_425_/fp_425_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_425_group_/fp_425_group_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_426_/fp_426_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_430_/fp_430_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_445_/fp_445_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_448_/fp_448_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_448_jw_/fp_448_jw_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_459_/fp_459_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_470_/fp_470_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_501_/fp_501_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_548_/fp_548_{0}.trsot",
    "world/obj_template/parts/field_pokemon/fp_571_/fp_571_{0}.trsot",
    "world/obj_template/parts/field_pokemon/group_1/fp_396_group1_/fp_396_group1_{0}.trsot",
    "world/obj_template/parts/field_pokemon/group_1/fp_398_group1_/fp_398_group1_{0}.trsot",
    "world/obj_template/parts/field_pokemon/group_2/fp_396_group2_/fp_396_group2_{0}.trsot",
    "world/obj_template/parts/field_pokemon/group_2/fp_398_group2_/fp_398_group2_{0}.trsot",
    "world/obj_template/parts/field_pokemon/group_3/fp_396_group3_/fp_396_group3_{0}.trsot",
    "world/obj_template/parts/field_pokemon/group_3/fp_398_group3_/fp_398_group3_{0}.trsot",
    "world/obj_template/parts/field_pokemon/group_4/fp_396_group4_/fp_396_group4_{0}.trsot",
    "world/obj_template/parts/field_pokemon/group_4/fp_398_group4_/fp_398_group4_{0}.trsot",
    "world/obj_template/parts/field_pokemon/group_5/fp_396_group5_/fp_396_group5_{0}.trsot",
    "world/obj_template/parts/field_pokemon/group_5/fp_398_group5_/fp_398_group5_{0}.trsot",
    "world/obj_template/parts/field_pokemon/group_6/fp_396_group6_/fp_396_group6_{0}.trsot",
    "world/obj_template/parts/field_pokemon/group_6/fp_398_group6_/fp_398_group6_{0}.trsot",
    "world/obj_template/parts/gym/esper/event_npc_gym_esper_020_lively_/event_npc_gym_esper_020_lively_{0}.trsot",
    "world/obj_template/parts/gym/esper/event_npc_gym_esper_020_trainer_poke_/event_npc_gym_esper_020_trainer_poke_{0}.trsot",
    "world/obj_template/parts/gym/esper/gym_esper_livelys_/gym_esper_livelys_{0}.trsot",
    "world/obj_template/parts/gym/esper/gym_esper_trainer_/gym_esper_trainer_{0}.trsot",
    "world/obj_template/parts/gym/koori/gym_koori_course_pokemon_/gym_koori_course_pokemon_{0}.trsot",
    "world/obj_template/parts/gym/mushi/event_npc_gym_mushi_040_pm708_/event_npc_gym_mushi_040_pm708_{0}.trsot",
    "world/obj_template/parts/npc_pokemon/npc_gym_denki_poke_/npc_gym_denki_poke_{0}.trsot",
    "world/obj_template/parts/npc_pokemon/npc_gym_ghost_poke_/npc_gym_ghost_poke_{0}.trsot",
    "world/obj_template/parts/npc_pokemon/npc_gym_kusa_poke_/npc_gym_kusa_poke_{0}.trsot",
    "world/obj_template/parts/npc_pokemon/npc_gym_mushi_poke_olive_/npc_gym_mushi_poke_olive_{0}.trsot",
    "world/obj_template/parts/npc_pokemon/npc_gym_mushi_poke_spider_/npc_gym_mushi_poke_spider_{0}.trsot",
    "world/obj_template/parts/npc_pokemon/npc_kuma_fieldwork_poke_/npc_kuma_fieldwork_poke_{0}.trsot",
    "world/obj_template/parts/npc_pokemon/npc_oyatsu_legend_poke_base_/npc_oyatsu_legend_poke_base_{0}.trsot",
    "world/obj_template/parts/npc_pokemon/npc_poke_base_/npc_poke_base_{0}.trsot",
    "world/obj_template/parts/npc_pokemon/npc_poke_base_dynamic_exclusion_/npc_poke_base_dynamic_exclusion_{0}.trsot",
    "world/obj_template/parts/nushi/dragon/nushi_dragon_fp_1056_015_/nushi_dragon_fp_1056_015_{0}.trsot",
    "world/obj_template/parts/nushi/hiko/HikoNushiActor_/HikoNushiActor_{0}.trsot",
    "world/obj_template/parts/nushi/jimen/JimenNushiActor_/JimenNushiActor_{0}.trsot",
    "world/scene/parts/demo/ev/d010/d010_a_sch_office01_/d010_a_sch_office01_{0}.trscn",
    "world/scene/parts/demo/ev/d010/d010_chara_c06_/d010_chara_c06_{0}.trscn",
    "world/scene/parts/demo/ev/d010/d010_chara_c18_/d010_chara_c18_{0}.trscn",
    "world/scene/parts/demo/ev/d010/d010_chara_c20_/d010_chara_c20_{0}.trscn",
    "world/scene/parts/demo/ev/d060_/d060_{0}.trscn",
    "world/scene/parts/demo/ev/d200_/d200_{0}.trscn",
    "world/scene/parts/demo/ev/d210_/d210_{0}.trscn",
    "world/scene/parts/demo/ev/d710_/d710_{0}.trscn",
    "world/scene/parts/demo/ev/d720_/d720_{0}.trscn",
    "world/scene/parts/demo/sd/sd8015_eat_bbfire_/sd8015_eat_bbfire_{0}.trscn",
    "world/scene/parts/demo/sd/sd8022_clear_2_/sd8022_clear_2_{0}.trscn",
    "world/scene/parts/event/event_scenario/main_scenario/ajito_honoo_010_/ajito_honoo_010_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/ajito_honoo_040_/ajito_honoo_040_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/common_0025_/common_0025_always_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/common_0110_/common_0110_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/common_0110_/common_0110_pre_start_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/common_0135_/common_0135_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/common_0140_legend_action_02_/common_0140_legend_action_02_pre_start_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/common_0300_/common_0300_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/common_1180_/common_1180_always_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/common_1330_/common_1330_pre_start_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_denki_020_/first_training_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_denki_020_/second_training_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_denki_020_/third_training_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_esper_020_/gym_esper_020_always_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_esper_040_/gym_esper_040_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_ghost_020_/npc_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_ghost_020_/step1_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_ghost_020_/step2_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_ghost_020_/step3_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_ghost_020_/step4_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_ghost_040_/gym_ghost_040_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_koori_040_/gym_koori_040_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_mushi_020_/gym_mushi_020_npc_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_mushi_040_/gym_mushi_040_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/gym_normal_020_/gym_normal_020_pre_start_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/legend_0120_/legend_0120_pre_start_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/nushi_dragon_015_/nushi_dragon_015_always_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/sdc01_0040_/sdc01_0040_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/sdc02_4kings_a_01_/sdc02_4kings_a_01_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/sdc02_4kings_a_02_/sdc02_4kings_a_02_pre_start_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/sdc02_4kings_a_04_/sdc02_4kings_a_04_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/sdc02_4kings_c_02_/question1_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/sdc02_4kings_c_02_/question2_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/sdc02_4kings_c_02_/question3_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/sdc02_4kings_c_02_/question4_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/sdc02_4kings_c_02_/question5_{0}.trsog",
    "world/scene/parts/event/event_scenario/main_scenario/sdc02_4kings_c_04_/sdc02_4kings_c_04_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/class_d_05_/class_d_05_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/class_d_06_/class_d_06_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/class_d_07_/class_d_07_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/class_d_08_/class_d_08_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/class_e_01_/class_e_01_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/class_e_02_/class_e_02_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/class_e_03_/class_e_03_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/class_e_04_/class_e_04_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/class_e_05_/class_e_05_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/class_e_06_/class_e_06_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/class_e_07_/class_e_07_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/class_e_08_/class_e_08_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/class_f_02_/class_f_02_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/class_f_03_/class_f_03_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/kizuna_friend_d_/kizuna_friend_d_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/kizuna_language_b_/kizuna_language_b_pre_start_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/kizuna_language_c_/kizuna_language_c_always_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/s1_side02_0030_/s1_side02_0030_pre_start_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/s1_sub_001_/s1_sub_001_main_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/s1_sub_013_/s1_sub_013_pre_start_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/sub_002_/sub_002_always_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/sub_007_/sub_007_pre_start_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/sub_008_/sub_008_pre_start_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/sub_035_/sub_035_pre_start_{0}.trsog",
    "world/scene/parts/event/event_scenario/sub_scenario/sub_046_/sub_046_pre_start_{0}.trsog",
    "world/scene/parts/field/resident_event/resident_gym_koori_course/gym_koori_course_hard_/gym_koori_course_hard_{0}.trscn",
    "world/scene/parts/field/resident_event/resident_gym_koori_course/gym_koori_course_very_hard_/gym_koori_course_very_hard_{0}.trscn",
    "world/scene/parts/field/room/a_c01_hairsalon/a_c01_hairsalon_event/a_c01_hairsalon_npc_/a_c01_hairsalon_npc_{0}.trscn",
    "world/scene/parts/field/room/a_c01_sandwich01/a_c01_sandwich01_event/a_c01_sandwich01_npc_/a_c01_sandwich01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_c01_sandwich03/a_c01_sandwich03_event/a_c01_sandwich03_npc_/a_c01_sandwich03_npc_{0}.trscn",
    "world/scene/parts/field/room/a_c01_sandwich04/a_c01_sandwich04_event/a_c01_sandwich04_npc_/a_c01_sandwich04_npc_{0}.trscn",
    "world/scene/parts/field/room/a_c01_sandwich05/a_c01_sandwich05_event/a_c01_sandwich05_npc_/a_c01_sandwich05_npc_{0}.trscn",
    "world/scene/parts/field/room/a_c01_sandwich06/a_c01_sandwich06_event/a_c01_sandwich06_npc_/a_c01_sandwich06_npc_{0}.trscn",
    "world/scene/parts/field/room/a_c02_g01/a_c02_g01_event/a_c02_g01_npc_/a_c02_g01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_c02_hairsalon01/a_c02_hairsalon01_event/a_c02_hairsalon01_npc_/a_c02_hairsalon01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_c02_sandwich01/a_c02_sandwich01_event/a_c02_sandwich01_npc_/a_c02_sandwich01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_c03_g01/a_c03_g01_event/a_c03_g01_npc_/a_c03_g01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_c03_sandwich01/a_c03_sandwich01_event/a_c03_sandwich01_npc_/a_c03_sandwich01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_c03_sandwich02/a_c03_sandwich02_event/a_c03_sandwich02_npc_/a_c03_sandwich02_npc_{0}.trscn",
    "world/scene/parts/field/room/a_sch_2_cafe01/a_sch_2_cafe01_npc_daytime_/a_sch_2_cafe01_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_2_cafe01/a_sch_2_cafe01_npc_nighttime_/a_sch_2_cafe01_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_2_class1/a_sch_2_class1_npc_daytime_/a_sch_2_class1_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_2_class1/a_sch_2_class1_npc_nighttime_/a_sch_2_class1_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_2_class2/a_sch_2_class2_npc_daytime_/a_sch_2_class2_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_2_class2/a_sch_2_class2_npc_nighttime_/a_sch_2_class2_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_2_clubroom/a_sch_2_clubroom_mob_npc_/room01_npc_{0}.trsog",
    "world/scene/parts/field/room/a_sch_2_clubroom/a_sch_2_clubroom_mob_npc_/room02_npc_{0}.trsog",
    "world/scene/parts/field/room/a_sch_2_clubroom/a_sch_2_clubroom_mob_npc_/room03_npc_{0}.trsog",
    "world/scene/parts/field/room/a_sch_2_clubroom/a_sch_2_clubroom_mob_npc_/room04_npc_{0}.trsog",
    "world/scene/parts/field/room/a_sch_2_clubroom/a_sch_2_clubroom_mob_npc_/room05_npc_{0}.trsog",
    "world/scene/parts/field/room/a_sch_2_clubroom/a_sch_2_clubroom_mob_npc_/room06_npc_{0}.trsog",
    "world/scene/parts/field/room/a_sch_2_clubroom/a_sch_2_clubroom_mob_npc_/room07_npc_{0}.trsog",
    "world/scene/parts/field/room/a_sch_2_clubroom/a_sch_2_clubroom_mob_npc_/room08_npc_{0}.trsog",
    "world/scene/parts/field/room/a_sch_2_entrance01/a_sch_2_entrance01_npc_/a_sch_2_entrance01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_sch_2_shop01/a_sch_2_shop01_npc_daytime_/a_sch_2_shop01_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_2_shop01/a_sch_2_shop01_npc_nighttime_/a_sch_2_shop01_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_cafe01/a_sch_cafe01_npc_daytime_/a_sch_cafe01_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_cafe01/a_sch_cafe01_npc_nighttime_/a_sch_cafe01_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_class1a/a_sch_class1a_npc_daytime_/a_sch_class1a_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_class1a/a_sch_class1a_npc_nighttime_/a_sch_class1a_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_class1d/a_sch_class1d_npc_daytime_/a_sch_class1d_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_class1d/a_sch_class1d_npc_nighttime_/a_sch_class1d_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_class2g/a_sch_class2g_npc_daytime_/a_sch_class2g_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_class2g/a_sch_class2g_npc_nighttime_/a_sch_class2g_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_dorm02/a_sch_dorm02_event_/a_sch_dorm02_event_{0}.trscn",
    "world/scene/parts/field/room/a_sch_dorm03/a_sch_dorm03_event_/a_sch_dorm03_event_{0}.trscn",
    "world/scene/parts/field/room/a_sch_dorm04/a_sch_dorm04_event_/a_sch_dorm04_event_{0}.trscn",
    "world/scene/parts/field/room/a_sch_entrance01/a_sch_entrance01_npc_daytime_/a_sch_entrance01_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_entrance01/a_sch_entrance01_npc_nighttime_/a_sch_entrance01_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_ground01/a_sch_ground01_npc_daytime_/a_sch_ground01_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_ground01/a_sch_ground01_npc_nighttime_/a_sch_ground01_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_ground01/a_sch_ground01_npc_studytime_/a_sch_ground01_npc_studytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_office02/a_sch_office02_kizuna_language_teacher_/a_sch_office02_kizuna_language_teacher_{0}.trscn",
    "world/scene/parts/field/room/a_sch_office02/a_sch_office02_npc_daytime_/a_sch_office02_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_office02/a_sch_office02_npc_nighttime_/a_sch_office02_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_office03/a_sch_office03_npc_daytime_/a_sch_office03_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_office03/a_sch_office03_npc_nighttime_/a_sch_office03_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_room01/a_sch_room01_npc_daytime_/a_sch_room01_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_room01/a_sch_room01_npc_nighttime_/a_sch_room01_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_room02/a_sch_room02_npc_daytime_/a_sch_room02_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_room02/a_sch_room02_npc_nighttime_/a_sch_room02_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_room03/a_sch_room03_npc_daytime_/a_sch_room03_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_room03/a_sch_room03_npc_nighttime_/a_sch_room03_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_shop01/a_sch_shop01_npc_daytime_/a_sch_shop01_npc_daytime_{0}.trscn",
    "world/scene/parts/field/room/a_sch_shop01/a_sch_shop01_npc_nighttime_/a_sch_shop01_npc_nighttime_{0}.trscn",
    "world/scene/parts/field/room/a_t01_i01/a_t01_i01_event/a_t01_i01_npc_/a_t01_i01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_t01_i02/a_t01_i02_event/a_t01_i02_npc_/a_t01_i02_npc_{0}.trscn",
    "world/scene/parts/field/room/a_t03_g01/a_t03_g01_event/a_t03_g01_npc_/a_t03_g01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_t03_sandwich01/a_t03_sandwich01_event/a_t03_sandwich01_npc_/a_t03_sandwich01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_t04_g01/a_t04_g01_event/a_t04_g01_npc_/a_t04_g01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_t04_sandwich01/a_t04_sandwich01_event/a_t04_sandwich01_npc_/a_t04_sandwich01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_t06_g01/a_t06_g01_event/a_t06_g01_npc_/a_t06_g01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_t06_g10/a_t06_g10_event_npc_floor_/a_t06_g10_event_npc_floor_{0}.trscn",
    "world/scene/parts/field/room/a_t06_g10/a_t06_g10_event_npc_outside_/a_t06_g10_event_npc_outside_{0}.trscn",
    "world/scene/parts/field/room/a_t06_sandwich01/a_t06_sandwich01_event/a_t06_sandwich01_npc_/a_t06_sandwich01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_t07_sandwich01/a_t07_sandwich01_event/a_t07_sandwich01_npc_/a_t07_sandwich01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_t08_g01/a_t08_g01_event/a_t08_g01_npc_/a_t08_g01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_t08_sandwich01/a_t08_sandwich01_event/a_t08_sandwich01_npc_/a_t08_sandwich01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_t09_g01/a_t09_g01_event/a_t09_g01_npc_/a_t09_g01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_t09_sandwich01/a_t09_sandwich01_event/a_t09_sandwich01_npc_/a_t09_sandwich01_npc_{0}.trscn",
    "world/scene/parts/field/room/a_t10_i01/a_t10_i01_npc_00_/a_t10_i01_npc_00_{0}.trscn",
    "world/scene/parts/field/room/a_w17_g01/a_w17_g01_event/a_w17_g01_npc_/a_w17_g01_npc_{0}.trscn",
    "world/scene/parts/field/room/clubroom/a_sch_2_/a_sch_2_{0}.trscn",
    "world/scene/parts/field/streaming_event/area01_encount_/area01_encount_{0}.trscn",
    "world/scene/parts/field/streaming_event/c01_npc_/c01_npc_{0}.trscn",
    "world/scene/parts/field/streaming_event/su1_world_npc_/su1_world_npc_{0}.trscn",
    "world/scene/parts/field/streaming_event/su2_world_npc_/su2_world_npc_{0}.trscn",
    "world/scene/parts/field/streaming_event/world_npc_/world_npc_{0}.trscn",
];