Skip to content

Instantly share code, notes, and snippets.

@sora10pls
Last active March 11, 2025 15:47
Show Gist options
  • Save sora10pls/07d69600241f3a47959b802560ab8900 to your computer and use it in GitHub Desktop.
Save sora10pls/07d69600241f3a47959b802560ab8900 to your computer and use it in GitHub Desktop.
A Randomizer for Pokémon Scarlet and Pokémon Violet
// 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",
];
@Pfeiffer54
Copy link

I've gotten to the point where everything is set and it runs...and then it tells me the VTable wasn't long enough. I've searched around and even started from zero again just in case I missed something...same error. Apologies if I'm missing something obvious, but can anyone tell me where I'm making a mistake?

@yvelandre
Copy link

Hi, I have tried and succeeded using the randomizer and it's awesome. One question is it possible to just use your QoL (like natdex, hisuian evo, etc) without randomizing anything? I have edited wild encounters and trainers manually and I really want to use your QoL when I play my edited ROM. Thank you 😊

@sora10pls
Copy link
Author

@yvelandre While I don't think I'll be adding only applying QoL changes as a feature, it still is possible!

Just overwrite your entire Main() method with the following:

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
    {
        PrepareInitialChanges();
        RandomizePokemonStats();
        EnhanceShopLineups();
        EnhanceItemData();
        UpdatePlibConversionTable();
        EnhanceEvolutionParameters();
        EnhanceBlueberryQuests();
        EnhanceBoutiqueAndSalonLineups();
        ExpandPaldeaPokedex();
        UpdateItemSortTables();
        FinalizeRandomizerData();
    }
}

And make sure that the following settings are set to false:

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

This will skip all randomizer functions and only apply QoL changes. Don't worry about it running the RandomizePokemonStats() method; with the above settings disabled, all this does is give Stantler access to Psyshield Bash, Dipplin access to Dragon Cheer, and add in regional form branched evolutions.

@Pfeiffer54
Copy link

I've gotten to the point where everything is set and it runs...and then it tells me the VTable wasn't long enough. I've searched around and even started from zero again just in case I missed something...same error. Apologies if I'm missing something obvious, but can anyone tell me where I'm making a mistake?

Okay, I've run through the steps a few more times, and I keep hitting that error "InvalidDataException: FlatBuffer was in an invalid format: VTable was not long enough to be valid."

On this line:

var obj = FlatBufferConverter.DeserializeFrom<TrinityFileDescriptors>(PATH_TRPFD);

Any guidance appreciated. Thank you.

@sora10pls
Copy link
Author

@Pfeiffer54 Unable to replicate with latest changes, as well as data.trpfd from 3.0.1. Make sure that your PATH_TRPFD string points to a valid, unedited file descriptor. Otherwise, you can try disabling the setting to automatically patch the file descriptor, and have TrinityModLoader do it for you (although I’m not sure if that’ll make a difference).

@yvelandre
Copy link

@sora10pls thanks a lot for explaining the method. I combined your QoL changes and my own mod and it works like a charm!

@FrostedGeulleisia
Copy link

Got a softlock on phase 2 of the Klawf (Wugtrio in my seed) fight, thinking it could be related to Arven trying to Terastalize which doesn't work in titan battles.
Then, loading a save from right after phase 1 causes an infinite load loop. Were these the "potential crashes" you were referring to under the "randomize overworld pokemon models" setting? By moving elsewhere and saving, then trying to approach the area, everything fails to load.
vid here: https://streamable.com/m0wbfn (since it's too big to upload directly)
After the video ends, i get a blank textbox (~a minute later) followed by infinite loading.
Tried approaching Klawf (Wugtrio) from both the Artazon side and the Mesagoza side to no avail, the latter at least lets me get to where it's supposed to be, only to get stuck due to unloaded collision.
I'm using a few other mods, which I have thoroughly tested and have had no issues with them ever.

@sora10pls
Copy link
Author

@FrostedGeulleisia The "potential crashes/softlocks" warning, to be honest, is simply there because I can't investigate all cases myself, and it's a pretty hefty edit. I've run several seeds with the generic overworld Pokémon setting enabled, and haven't experienced anything game breaking, but keep it there as a precaution.

Arven softlock: able to replicate, and is because of the setting to allow all trainers to Terastallize. I have plans to compile a list of blacklisted trainers to address this, but it'll take some time.

Missing overworld objects/collision: I just tested on both console and emulator with the remaining experimental settings enabled, and it works fine. I would advise running without other mods, since I'm not entirely sure what would even cause it. This randomizer has had all of its testing done without any additional mods applied, so there could be some compatibility issues.

@FrostedGeulleisia
Copy link

FrostedGeulleisia commented Nov 26, 2024

okay yeah the latter problem was due to my quickie mod to remove Tandemaus's LoD models (replacing them with the full detail models). I kinda forgot I had that on, even. (Tandemaus's LoD models have no eyes, which makes the Shiny Indicator mod ineffective on them unless close).
For the softlock, Arven's titan ally rosters are "pepper_nusi_01" through "pepper_nusi_05", I'll continue playing through (having manually set "changeGem" on those rosters to false) and note down any other fights that cause a softlock as a result.

Those five rosters are the only highly problematic ones, due to Arven only having one team member in them.

@Snowpoints1
Copy link

really not sure what's wrong. i managed to get the mod working after randomizing, but now it crashes when i try to fight nemona after getting the starter

@sora10pls
Copy link
Author

@FrostedGeulleisia I've gone ahead and pushed some updates, feel free to read the first comment on the gist to see what's been added/changed.

After a full playthrough of the base game and both DLCs (Mochi Mayhem is currently untested), the only trainer battles I've found to be problematic with Terastallization are the Titan battles with Arven, as you've mentioned. Multi battles as a whole are fine, as I found no issues with the story battles in Area Zero with Nemona, Penny, and even Arven again. Should I ever figure out why those battles don't work, I'll push a fix, but this workaround is fine enough and isn't too big a loss.

@Mognogle
Copy link

Mognogle commented Jan 3, 2025

I was wondering if anyone could give me a hand. These are the additional references and I am getting this CS1705 Assembly error that I don't know how to fix.
Additional References
Error1

@Mognogle
Copy link

Mognogle commented Jan 3, 2025

I managed to sort the reference error out but now I am getting a different error...
Error 2

@Mognogle
Copy link

Mognogle commented Jan 3, 2025

It looks like I am having the similar issue to Thorin previously where even though my game is version 3.0.1 and has the DLC installed it isn't dumping any of the items from 2500 onwards. Have tried both an NSP version and an XCI version.

@sora10pls
Copy link
Author

@Mognogle You need to make sure that you grabbed latest hashes, as is mentioned in the setup instructions. If you were not prompted at the start, Help -> Get latest hashes, and restart TrinityModLoader.

@Mognogle
Copy link

Mognogle commented Jan 4, 2025

@sora10pls I see what you mean now. I was using an outdated version of the Trinity Mod Loader without realizing it so I didn't have the option to get the latest hashes. I have however got a new Exception and it may just be me being dumb as I am not code savvy. I seem to get the type initializer for UserQuery exception and I don't understand why.
The other issue I am having is when I select the folder of where to save the romFS after going to tree view the trinity mod loader becomes unresponsive. Any idea why that might be??? Thanks in advance!
Error 3

@sora10pls
Copy link
Author

sora10pls commented Jan 4, 2025

@Mognogle Can you click on those 3 dots next to TypeInitializationException, click on View in grid, then click on the System.TypeInitializationException tab that opens up, and send me a screenshot of the output? You have randomized randomizer settings on, so it could be a number of things that may have caused it.

Edit: If you are experiencing an unresponsive TrinityModLoader, that is to be expected. Your computer naturally isn't expecting thousands of files to be saved quickly, so you must wait for the process to finalize and it'll eventually give you a popup saying it was completed. Make sure it's complete before attempting to run the randomizer, as it probably failed to load a file it needs but can't find on initialization.

@Mognogle
Copy link

Mognogle commented Jan 4, 2025

Hey @sora10pls here is the screenshot.
Error 4

@Mognogle
Copy link

Mognogle commented Jan 4, 2025

I will also try and redo the save portion of the trinity mod loader and see if that can complete

@Mognogle
Copy link

Mognogle commented Jan 4, 2025

I allowed the Trinity Mod Loader to complete(It took about 20-30 minutes) and then ran the compiler and it worked. Took me to the folder but it wasn't zipped so I will see if I can make it work from there onwards. Thank you for the help @sora10pls

@Mognogle
Copy link

Mognogle commented Jan 4, 2025

Managed to zip it and apply mod but when i loaded up the game there were no changes so I still have some work to do but there were no more errors at least

@sora10pls
Copy link
Author

@Mognogle If you have automatically patch trpfd enabled then you do not need to use TrinityModLoader to apply the mod, as the instructions say.

@Mognogle
Copy link

Mognogle commented Jan 4, 2025

Oh I see. I do have it enabled so I will have a look

@Mognogle
Copy link

Mognogle commented Jan 4, 2025

Hmmmm. I set Randomize_personal_ability to true but it hasn't randomized the pokemon abilities. Do i need to enable something somewhere else to do this?

@sora10pls
Copy link
Author

@Mognogle If you are using the same settings as your first screenshot showed, then you have randomizer settings randomized… meaning that your other settings are ignored. If you want your settings to be relevant in the randomization process, disable the “randomize randomizer settings” setting near the top. Working as intended.

@Mognogle
Copy link

Mognogle commented Jan 4, 2025

I see. I misunderstood what that line of code meant. Thank you for explaining though. I really appreciate all the assistance!

@Guyshuvus
Copy link

Won't Evolve

I'm having a lot of fun with the randomizer, but for some reason, this happens. Leveling it up while holding an Electrizer doesn't work either, and neither does using a Linking Cord. Is there a fix for this?

@sora10pls
Copy link
Author

@Guyshuvus Make sure that you don’t have any overlapping mods that edit avalon/data/personal_array.bin or world/data/item/itemdata/itemdata_array.bin, otherwise the modified evolution parameters won’t be applied. I won’t be able to test this myself in-game until tomorrow, but none of the evolution handling has been modified in a while, so I’m unsure how this could happen without another mod interfering.

@Guyshuvus
Copy link

@Guyshuvus Make sure that you don’t have any overlapping mods that edit avalon/data/personal_array.bin or world/data/item/itemdata/itemdata_array.bin, otherwise the modified evolution parameters won’t be applied. I won’t be able to test this myself in-game until tomorrow, but none of the evolution handling has been modified in a while, so I’m unsure how this could happen without another mod interfering.

The randomizer is my only mod, but I used PKHex to hack in Rare Candies. Could that be the issue?

@sora10pls
Copy link
Author

@Guyshuvus I took a look at my code and found a bug causing the issue, specifically with evolutions that were originally "Trade with held item". It has now been fixed. Thank you for bringing this to my attention, and I apologize for the inconvenience!

If you want to evolve your Electabuzz (or any future captures that would normally evolve by trading with a held item), you can modify the species in PKHeX to evolve it that way, or re-randomize your game entirely. There is unfortunately no handling for the randomizer to modify an already outputted randomizer at this time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment