// 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", ];