Skip to content

Instantly share code, notes, and snippets.

@sora10pls
Last active July 15, 2024 12:23
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
public const string DEST_MASTER = @"";
public const string PATH_MASTER = @"";
public const string DEST_LOG = @"";
public const string LANGUAGE = "English";
public static string DEST_ZIP_ARCHIVE = Path.Combine(DEST_LOG, "ScarletVioletRandomizer.zip");
// MAXIMUMS
public const int MAX_SPECIES_ID = 1025;
public const int MAX_MOVE_ID = 919;
public const int MAX_ABILITY_ID = 310;
public const int MAX_TM_COUNT = 229;
public const int MAX_TYPE_ID = 17;
public const int MAX_BALL_ID = 26;
public const int MAX_RIBBON_ID = 111;
// GLOBAL SETTINGS
public static bool ALLOW_SPECIES_GEN_1 = true;
public static bool ALLOW_SPECIES_GEN_2 = true;
public static bool ALLOW_SPECIES_GEN_3 = true;
public static bool ALLOW_SPECIES_GEN_4 = true;
public static bool ALLOW_SPECIES_GEN_5 = true;
public static bool ALLOW_SPECIES_GEN_6 = true;
public static bool ALLOW_SPECIES_GEN_7 = true;
public static bool ALLOW_SPECIES_GEN_8 = true;
public static bool ALLOW_SPECIES_GEN_9 = true;
public static bool ALLOW_SPECIES_LEGENDARY = true;
public static bool ALLOW_SPECIES_MYTHICAL = true;
public static bool ALLOW_SPECIES_PARADOX = true;
public static bool FORCE_SWAP_SPECIES_LEGENDARY = true;
public static bool FORCE_SWAP_SPECIES_MYTHICAL = true;
public static bool FORCE_SWAP_SPECIES_PARADOX = true;
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 SHUFFLE_PERSONAL_BASE_STATS = false;
public static bool ABILITIES_TYPES_FOLLOW_EVOLUTIONS = false;
public static bool GUARANTEE_FOUR_MOVES_AT_LEVEL_ONE = false;
public static bool RANDOMIZE_MOVE_PROPERTIES_TYPE = false;
public static bool RANDOMIZE_MOVE_PROPERTIES_CATEGORY = false;
public static bool RANDOMIZE_MOVE_PROPERTIES_POWER = false;
public static bool RANDOMIZE_MOVE_PROPERTIES_ACCURACY = false;
public static bool RANDOMIZE_MOVE_PROPERTIES_PP = false;
public static bool FORCE_FULLY_EVOLVE = false;
public const byte FORCE_FULLY_EVOLVE_LEVEL = 40;
public static bool MODIFY_POKEMON_LEVELS = false;
public const sbyte LEVEL_MODIFIER_PERCENT = 10;
public static bool ALLOW_FULLY_RANDOM_TERA_TYPES = false;
public const byte STELLAR_TERA_TYPE_CHANCE_PERCENT = 2;
// ADDITIONAL SETTINGS
public static bool ALL_POKEMON_ONLY_KNOW_METRONOME = false;
public static bool ALLOW_RANDOM_PICNIC_ITEMS = false;
public static bool BAN_ABILITY_WONDER_GUARD = false;
public static bool DO_NOT_RANDOMIZE_TERA_BLAST_TM = false;
public static bool ENSURE_SANE_FORM_HELD_ITEMS = false;
public static bool ENSURE_STARTERS_HAVE_TWO_EVOLUTIONS = false;
public static bool GIVE_IMPORTANT_TRAINERS_SIX_POKEMON = false;
public static bool GIVE_TRAINERS_MAXIMUM_AI = false;
public static bool GIVE_TRAINERS_MAX_INDIVIDUAL_VALUES = false;
public static bool GIVE_TRAINERS_SMART_EFFORT_VALUES = false;
public static bool GIVE_POKEMON_HELD_ITEMS = false;
public static bool GIVE_POKEMON_RANDOM_POKE_BALLS = false;
public static bool GIVE_POKEMON_RANDOM_RIBBONS = false;
public static bool MAKE_ALL_TMS_CRAFTABLE_FROM_START = false;
public static bool MAKE_ALL_TRAINER_BATTLES_DOUBLE_BATTLES = false;
public static bool MAKE_POKEMON_EVOLVE_EVERY_LEVEL = false;
public static bool MAKE_WILD_POKEMON_TIME_OF_DAY_DEPENDENT = false;
public static bool RANDOMIZE_ROAMING_FORM_GIMMIGHOUL = false;
public static bool RANDOMIZE_TERA_RAID_ITEM_DROPS_BONUS = false;
public static bool RANDOMIZE_TERA_RAID_ITEM_DROPS_FIXED = false;
public static bool REMOVE_EFFORT_VALUE_YIELDS = false;
public static bool RIVAL_CARRIES_STARTER_THROUGHOUT_GAME = false;
public const byte TRAINER_MINIMUM_POKEMON_COUNT = 1;
public const byte TRAINER_MAXIMUM_POKEMON_COUNT = 6;
public const byte TRAINER_POKEMON_SHINY_CHANCE_PERCENT = 2;
// POTENTIAL CRASHES AND SOFTLOCKS -- PROCEED WITH CAUTION
public static bool RANDOMIZE_KORAIDON_MIRAIDON = false;
public static bool RANDOMIZE_GENERIC_OVERWORLD_POKEMON = false;
public const byte GENERIC_OVERWORLD_POKEMON_SHINY_CHANCE_PERCENT = 2;
// INITIALIZE LISTS
public static List<DevID> SpeciesBanlist = GetSpeciesBanlist();
public static List<DevID> SpeciesList = GetSpeciesList();
public static List<int> PermittedMoves = GetPermittedMoves();
public static List<int> PermittedAbilities = GetPermittedAbilities();
public static List<int> TechnicalMachineList = GetTMList();
public static List<ushort> ItemList = GetItemList();
public static List<string> RandomizerLog = new();
void Main()
{
if (String.IsNullOrWhiteSpace(DEST_MASTER) || String.IsNullOrWhiteSpace(PATH_MASTER) || String.IsNullOrWhiteSpace(DEST_LOG) || !Languages.Contains(LANGUAGE))
{
"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
AddCustomItemData();
RandomizePokemonStats();
RandomizeMoveProperties();
"".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();
ExtraChanges();
"".Dump();
if (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",
];
foreach (string t in PokeData)
{
int index = t.LastIndexOf("/") + 1;
string f = t[index..];
string d = t[..index];
string location = f switch
{
"pokedata_array.bin" => "Paldea",
"pokedata_su1_array.bin" => "Kitakami",
"pokedata_su2_array.bin" => "Blueberry Academy",
_ => throw new ArgumentException($"Invalid file: {f}"),
};
$"Randomizing wild Pokémon ({location})...".Dump();
string file = Path.Combine(PATH_MASTER, d, f);
var obj = FlatBufferConverter.DeserializeFrom<EncountPokeDataArray>(file);
obj.Table.Clear(); // now we reconstruct PokeData from scratch!
int maxBiome = location is "Blueberry Academy" ? 24 : 23; // DENKI_ISHI does not exist in Paldea/Kitakami
for (int i = 0001; i <= 1025; i++)
{
if (SpeciesBanlist.Contains((DevID)i))
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;
byte forms = GetFormCount((ushort)i);
for (int c = 0; c < forms; c++)
{
if (!GetIsPresentInGame((ushort)i, (byte)c))
continue;
if (!IsValidForm((ushort)i, (byte)c))
continue;
BandType band = IsBisharp ? BandType.BOSS : (BandType)GetRandom(1, 3);
EncountPokeData entry = new()
{
DevId = (DevID)i,
Sex = SexType.DEFAULT,
Form = (sbyte)c,
MinLevel = 1,
MaxLevel = 100,
LotValue = (short)GetRandom(30, 81),
Biome1 = (Biome)GetRandom(1, maxBiome),
LotValue1 = (short)GetRandom(30, 81),
Biome2 = (Biome)GetRandom(1, maxBiome),
LotValue2 = (short)GetRandom(30, 81),
Biome3 = (Biome)GetRandom(1, maxBiome),
LotValue3 = (short)GetRandom(30, 81),
Biome4 = (Biome)GetRandom(1, maxBiome),
LotValue4 = (short)GetRandom(30, 81),
Area = GenerateAreaList(),
LocationName = string.Empty,
MinHeight = 0,
MaxHeight = 0,
Enable = GetEnableTable((ushort)i, (byte)c),
Time = GetTimeTable(),
FlagName = string.Empty,
BandRate = IsBisharp ? (short)100 : (short)GetRandom(30, 81),
Band = band,
BandPoke = IsBisharp ? DevID.DEV_KOMATANA : (DevID)i,
BandSex = SexType.DEFAULT,
BandForm = IsBisharp ? (sbyte)0 : (sbyte)c,
OutbreakLotValue = (sbyte)GetRandom(30, 81),
PokeVoiceClassification = "ANIMAL",
Version = new VersionTable() { A = true, B = true },
Item = GetBringItem((DevID)i, (sbyte)c),
};
obj.Table.Add(entry);
}
}
// 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")
{
"Randomizing wild encounters (Area Zero)...".Dump();
for (int i = 0; i < 200; i++)
{
List<Biome> Biomes = GetAreaZeroBiomes();
DevID dev = GetRandomSpecies(0);
// prevent wild Terapagos due to the game generating an invalid Tera Type (Normal, not Stellar)
if (dev is DevID.DEV_KODAIGAME)
{
while (dev is DevID.DEV_KODAIGAME)
dev = GetRandomSpecies(0);
}
sbyte form = (sbyte)GetRandomForm(dev);
bool IsBisharp = dev is DevID.DEV_KIRIKIZAN;
EncountPokeData entry = new()
{
DevId = dev,
Sex = SexType.DEFAULT,
Form = form,
MinLevel = 1,
MaxLevel = 100,
LotValue = (short)GetRandom(30, 81),
Biome1 = Biomes[0],
LotValue1 = (short)GetRandom(30, 81),
Biome2 = Biomes[1],
LotValue2 = (short)GetRandom(30, 81),
Biome3 = Biomes[2],
LotValue3 = (short)GetRandom(30, 81),
Biome4 = Biomes[3],
LotValue4 = (short)GetRandom(30, 81),
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 = (short)GetRandom(30, 81),
Band = (BandType)GetRandom(1, 3),
BandPoke = dev,
BandSex = SexType.DEFAULT,
BandForm = form,
OutbreakLotValue = 0,
PokeVoiceClassification = "ANIMAL",
Version = new VersionTable() { A = true, B = true },
Item = GetBringItem(dev, form),
};
obj.Table.Add(entry);
}
}
byte[] data = FlatBufferConverter.SerializeFrom(obj);
string dest = Path.Combine(DEST_MASTER, d);
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
File.WriteAllBytes(Path.Combine(dest, f), data);
}
}
private static string GenerateAreaList()
{
int[] group = new int[10];
for (int i = 0; i < group.Length; i++)
group[i] = GetRandom(1, 23);
List<int> Areas = group.ToList();
Areas.Sort();
Areas = Areas.Distinct().ToList();
return String.Join(",", Areas);
}
private static EnableTable GetEnableTable(ushort dev, byte form)
{
ushort species = GetNationalDexID(dev);
EnableTable Enable = new() { Land = true };
if (GetCanSwimOrBeAboveWater(species, form))
Enable.UpWater = Enable.Underwater = 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 };
int rand = GetRandom(4);
TimeTable Time = new();
if (rand is 0) Time.Morning = true;
if (rand is 1) Time.Noon = true;
if (rand is 2) Time.Evening = true;
if (rand is 3) Time.Night = true;
return Time;
}
private static BringItem GetBringItem(DevID species, sbyte form)
{
bool IsBisharp = species is DevID.DEV_KIRIKIZAN;
if (IsBisharp)
return new BringItem() { ItemID = ItemID.ITEMID_KASIRANOAKASI, BringRate = 100 }; // always give Leader’s Crest
if (ENSURE_SANE_FORM_HELD_ITEMS && IsFormHeldItemDependent((DevID)species))
return new BringItem() { ItemID = GetFormSpecificItem((DevID)species, (short)form), BringRate = 100 };
if (GIVE_POKEMON_HELD_ITEMS)
return new BringItem() { ItemID = GetRandomItem(), BringRate = (sbyte)GetRandom(101) };
return new BringItem();
}
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
int exclude = GetRandom(5);
foreach (Biome b in EligibleBiomes)
{
if (b == (Biome)exclude)
continue;
Biomes.Add(b);
}
return Biomes;
}
private static void RandomizeFixedSymbolEncounters()
{
RandomizerLog.Add("== FIXED SYMBOL ENCOUNTERS ==");
"Randomizing fixed symbol encounters...".Dump();
const string file = "fixed_symbol_table_array.bin";
string dest = Path.Combine(DEST_MASTER, "world/data/field/fixed_symbol/fixed_symbol_table/");
string path = Path.Combine(PATH_MASTER, "world/data/field/fixed_symbol/fixed_symbol_table/");
var obj = FlatBufferConverter.DeserializeFrom<FixedSymbolTableArray>(Path.Combine(path, file));
string[] names = GetStringsCommon(LANGUAGE, "monsname");
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;
t.DevId = GetRandomSpecies(t.DevId);
t.FormId = GetRandomForm(t.DevId);
// keep randomizing water fixed symbols until we get an eligible species/form pair
if (IsWaterFixedSymbol(enc.TableKey))
{
while (!GetCanSwimOrBeAboveWater((ushort)t.DevId, t.FormId))
{
t.DevId = GetRandomSpecies(t.DevId);
t.FormId = GetRandomForm(t.DevId);
}
}
t.Sex = GetRandomGender(t.DevId, t.FormId);
t.RareType = RareType.DEFAULT;
t.WazaType = WazaType.DEFAULT;
t.TokuseiIndex = (TokuseiType)GetRandom(2, 5);
// random if wild Tera, preserve Stellar, otherwise randomize according to setting
t.GemType = t.GemType switch
{
not (GemType.DEFAULT or GemType.RANDOM or GemType.NIJI) => (GemType)GetRandom(2, 20),
GemType.NIJI => GemType.NIJI,
_ => GetTeraType(t.DevId, (byte)t.FormId, t.GemType, false)
};
if (MODIFY_POKEMON_LEVELS)
t.Level = Math.Clamp((int)(t.Level * (1.0f + ((float)LEVEL_MODIFIER_PERCENT / 100.0f))), 1, 100);
if (t.DevId is DevID.DEV_MAHOIPPU)
t.MahoippuViewId = (MahoippuViewID)GetRandom(7);
string oForm = oldForm is 0 ? string.Empty : $"-{oldForm}";
string nForm = t.FormId is 0 ? string.Empty : $"-{t.FormId}";
RandomizerLog.Add($"{names[(int)oldDev]}{oForm} -> {names[(int)t.DevId]}{nForm}");
}
RandomizerLog.Add("");
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, file), data);
}
private static void RandomizeStaticEncounters()
{
RandomizerLog.Add("== STATIC ENCOUNTERS ==");
"Randomizing static encounters...".Dump();
List<PokeDataEventBattle> OldSpecies = GetOldStaticEncounters();
List<PokeDataEventBattle> NewSpecies = new();
List<string> Blacklist = [ "nusi_952_dummy_pink", "nusi_952_dummy_red", "nusi_952_dummy_yellow" ];
const string file = "eventBattlePokemon_array.bin";
string dest = Path.Combine(DEST_MASTER, "world/data/battle/eventBattlePokemon/");
string path = Path.Combine(PATH_MASTER, "world/data/battle/eventBattlePokemon/");
var obj = FlatBufferConverter.DeserializeFrom<EventBattlePokemonArray>(Path.Combine(path, file));
string[] names = GetStringsCommon(LANGUAGE, "monsname");
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);
}
foreach (var enc in table.Where(z => !Blacklist.Contains(z.Label)))
{
var t = enc.PokeData;
t.DevId = GetRandomSpecies(t.DevId);
t.FormId = GetRandomForm(t.DevId);
t.Sex = GetRandomGender(t.DevId, t.FormId);
t.Seikaku = t.SeikakuHosei = SeikakuType.DEFAULT;
t.RareType = RareType.DEFAULT;
t.WazaType = WazaType.DEFAULT;
t.Tokusei = (TokuseiType)GetRandom(2, 5);
t.DropItem = GetRandomItem();
// 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),
};
if (GIVE_POKEMON_RANDOM_RIBBONS)
t.SetRibbon = (RibbonType)GetRandom(1, MAX_RIBBON_ID + 1);
if (MODIFY_POKEMON_LEVELS)
t.Level = Math.Clamp((int)(t.Level * (1.0f + ((float)LEVEL_MODIFIER_PERCENT / 100.0f))), 1, 100);
if (GIVE_POKEMON_HELD_ITEMS)
t.Item = GetRandomItem();
if (FORCE_FULLY_EVOLVE && t.Level >= FORCE_FULLY_EVOLVE_LEVEL)
{
t.DevId = (DevID)ForceEvolveSpeciesFully((ushort)t.DevId, (byte)t.FormId);
t.FormId = GetRandomForm(t.DevId); // refresh
t.Sex = GetRandomGender(t.DevId, t.FormId); // refresh
}
if (enc.Label is "sub018_BIGUANA")
t = ApplyParadiseSoftlockPrevention(t);
if (enc.Label is "SDC02_0330_kodaikame")
t = ApplyTerapagosSoftlockPrevention(t);
if (ENSURE_SANE_FORM_HELD_ITEMS && IsFormHeldItemDependent(t.DevId))
t.Item = GetFormSpecificItem(t.DevId, t.FormId);
NewSpecies.Add(t);
}
obj = StandardizeStaticEncounters(obj);
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, file), data);
List<DevID> BossBattles = [ table[043].PokeData.DevId, table[035].PokeData.DevId, table[041].PokeData.DevId, table[033].PokeData.DevId, table[037].PokeData.DevId, table[045].PokeData.DevId, table[031].PokeData.DevId, table[032].PokeData.DevId ];
UpdateBossText(BossBattles);
UpdateAllStaticScenes(obj);
UpdateAllKitakamiScenes(obj);
int count = table.Count - Blacklist.Count;
for (int i = 0; i < count; i++)
{
var o = OldSpecies[i];
var n = NewSpecies[i];
string oForm = o.FormId is 0 ? string.Empty : $"-{o.FormId}";
string nForm = n.FormId is 0 ? string.Empty : $"-{n.FormId}";
RandomizerLog.Add($"{names[(int)o.DevId]}{oForm} -> {names[(int)n.DevId]}{nForm}");
}
RandomizerLog.Add("");
}
private static List<PokeDataEventBattle> GetOldStaticEncounters()
{
List<PokeDataEventBattle> StaticEncounters = new();
List<string> Blacklist = [ "nusi_952_dummy_pink", "nusi_952_dummy_red", "nusi_952_dummy_yellow" ];
const string file = "eventBattlePokemon_array.bin";
string 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.PokeData);
return StaticEncounters;
}
private static void RandomizeGiftEncounters()
{
RandomizerLog.Add("== GIFT ENCOUNTERS ==");
"Randomizing gift encounters...".Dump();
const string file = "eventAddPokemon_array.bin";
string dest = Path.Combine(DEST_MASTER, "world/data/event/event_add_pokemon/eventAddPokemon/");
string path = Path.Combine(PATH_MASTER, "world/data/event/event_add_pokemon/eventAddPokemon/");
var obj = FlatBufferConverter.DeserializeFrom<EventAddPokemonArray>(Path.Combine(path, file));
string[] names = GetStringsCommon(LANGUAGE, "monsname");
List<PokeDataFull> OldSpecies = GetOldGiftEncounters();
List<PokeDataFull> 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";
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;
t.DevId = GetRandomSpecies(t.DevId);
if (ENSURE_STARTERS_HAVE_TWO_EVOLUTIONS && IsStarter(enc.Label))
{
while (!GetIsThreeStageFamily(GetNationalDexID((ushort)t.DevId)))
t.DevId = GetRandomSpecies(0);
}
t.FormId = GetRandomForm(t.DevId);
t.Sex = GetRandomGender(t.DevId, t.FormId);
t.Seikaku = t.SeikakuHosei = SeikakuType.DEFAULT;
t.RareType = RareType.DEFAULT;
t.WazaType = WazaType.DEFAULT;
t.Tokusei = (TokuseiType)GetRandom(2, 5);
t.GemType = GetTeraType(t.DevId, (byte)t.FormId, t.GemType, false);
if (GIVE_POKEMON_RANDOM_POKE_BALLS)
t.BallId = (BallType)GetRandom(1, MAX_BALL_ID + 1);
if (GIVE_POKEMON_RANDOM_RIBBONS)
t.SetRibbon = (RibbonType)GetRandom(1, MAX_RIBBON_ID + 1);
if (MODIFY_POKEMON_LEVELS)
t.Level = Math.Clamp((int)(t.Level * (1.0f + ((float)LEVEL_MODIFIER_PERCENT / 100.0f))), 1, 100);
if (GIVE_POKEMON_HELD_ITEMS)
t.Item = GetRandomItem();
if (ENSURE_SANE_FORM_HELD_ITEMS && IsFormHeldItemDependent(t.DevId))
t.Item = GetFormSpecificItem(t.DevId, t.FormId);
NewSpecies.Add(t);
}
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, file), data);
UpdateAllGiftScenes(obj);
if (RANDOMIZE_KORAIDON_MIRAIDON)
{
string statics = Path.Combine(DEST_MASTER, "world/data/battle/eventBattlePokemon/eventBattlePokemon_array.bin");
var objS = FlatBufferConverter.DeserializeFrom<EventBattlePokemonArray>(statics);
obj = StandardizeGiftEncounters(obj, objS);
UpdateAllIguanaScenes(obj);
}
int count = obj.Table.Count - Blacklist.Count;
for (int i = 0; i < count; i++)
{
var o = OldSpecies[i];
var n = NewSpecies[i];
string oForm = o.FormId is 0 ? string.Empty : $"-{o.FormId}";
string nForm = n.FormId is 0 ? string.Empty : $"-{n.FormId}";
RandomizerLog.Add($"{names[(int)o.DevId]}{oForm} -> {names[(int)n.DevId]}{nForm}");
}
RandomizerLog.Add("");
}
private static List<PokeDataFull> GetOldGiftEncounters()
{
List<PokeDataFull> GiftEncounters = new();
List<string> Blacklist = [ "add_0195", "add_0193" ];
const string file = "eventAddPokemon_array.bin";
string 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.PokeData);
return GiftEncounters;
}
private static void RandomizeTrainerPokemon()
{
RandomizerLog.Add("== TRAINER BATTLES ==");
"Randomizing trainer Pokémon...".Dump();
const string fileTrData = "trdata_array.bin";
string dest = Path.Combine(DEST_MASTER, "world/data/trainer/trdata/");
string path = Path.Combine(PATH_MASTER, "world/data/trainer/trdata/");
const string fileTrType = "trtype_array.bin";
string pathType = Path.Combine(PATH_MASTER, "world/data/trainer/trtype/");
var obj = FlatBufferConverter.DeserializeFrom<TrDataMainArray>(Path.Combine(path, fileTrData));
var typ = FlatBufferConverter.DeserializeFrom<TrainerTypeArray>(Path.Combine(pathType, fileTrType));
string[] species = GetStringsCommon(LANGUAGE, "monsname");
string[] trNames = GetStringsCommon(LANGUAGE, "trname");
string[] trTypes = GetStringsCommon(LANGUAGE, "trtype");
string gifts = Path.Combine(DEST_MASTER, "world/data/event/event_add_pokemon/eventAddPokemon/eventAddPokemon_array.bin");
var objG = FlatBufferConverter.DeserializeFrom<EventAddPokemonArray>(gifts);
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",
];
bool IsNemona(string id) => id.Contains("rival_") || id.Contains("nemo_");
bool IsClavell(string id) => id.Contains("clavel_") || id.Contains("claver_");
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_");
bool IsValidDoubleBattle(string id) => !id.EndsWith("_boss_01") && !id.Contains("_multi") && !id.StartsWith("pepper_nusi_") && !id.StartsWith("professor_") && !id.Contains("raid_assist") && id is not "brother_02_02";
PokeDataFull StarterG1 = objG.Table[001].PokeData;
PokeDataFull StarterF1 = objG.Table[000].PokeData;
PokeDataFull StarterW1 = objG.Table[002].PokeData;
PokeDataFull StarterG2 = ForceEvolveSpeciesOnce(new PokeDataFull { DevId = StarterG1.DevId, FormId = StarterG1.FormId });
PokeDataFull StarterF2 = ForceEvolveSpeciesOnce(new PokeDataFull { DevId = StarterF1.DevId, FormId = StarterF1.FormId });
PokeDataFull StarterW2 = ForceEvolveSpeciesOnce(new PokeDataFull { DevId = StarterW1.DevId, FormId = StarterW1.FormId });
PokeDataFull StarterG3 = ForceEvolveSpeciesOnce(new PokeDataFull { DevId = StarterG2.DevId, FormId = StarterG2.FormId });
PokeDataFull StarterF3 = ForceEvolveSpeciesOnce(new PokeDataFull { DevId = StarterF2.DevId, FormId = StarterF2.FormId });
PokeDataFull StarterW3 = ForceEvolveSpeciesOnce(new PokeDataFull { DevId = StarterW2.DevId, FormId = StarterW2.FormId });
foreach (var t in obj.Table)
{
List<PokeDataBattle> Team = [ t.Poke1, t.Poke2, t.Poke3, t.Poke4, t.Poke5, t.Poke6 ];
int count = Team.Count(z => z.DevId is not DevID.DEV_NULL);
// add Pokémon to party to meet required minimum
if (count < TRAINER_MINIMUM_POKEMON_COUNT && !IncreasedCountBlacklist.Contains(t.TrId))
{
int 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].TalentValue, Team[0].EffortValue);
}
// remove Pokémon from party to meet required maximum
if (count > TRAINER_MAXIMUM_POKEMON_COUNT)
{
int 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].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
int 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].TalentValue, Team[0].EffortValue);
}
}
// 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;
RandomizeTrainerPokemonSingle(pk);
}
// special handling to give AI Sada/Turo a full party of unique Paradoxes
if (FORCE_SWAP_SPECIES_PARADOX && ALLOW_SPECIES_GEN_9 && t.TrId is "professor_A_01" or "professor_B_01")
{
int max = Math.Clamp((int)TRAINER_MAXIMUM_POKEMON_COUNT, 1, 6);
pkNX.Randomization.Util.Shuffle(Paradox);
Team[max - 1].Item = ItemID.ITEMID_BUUSUTOENAJII; // give last Pokémon Booster Energy, true to the vanilla game
for (int i = 0; i < max; i++)
{
Team[i].DevId = (DevID)GetDevID((ushort)Paradox[i]);
Team[i].FormId = 0;
Team[i].Sex = SexType.DEFAULT;
if (GIVE_TRAINERS_SMART_EFFORT_VALUES)
Team[i].EffortValue = SetSmartEVs((ushort)Team[i].DevId, Team[i].FormId);
}
}
if (RIVAL_CARRIES_STARTER_THROUGHOUT_GAME && (IsNemona(t.TrId) || IsClavell(t.TrId)))
{
// unused rival battles
if (!t.TrId.Contains("kusa") && !t.TrId.Contains("hono") && !t.TrId.Contains("mizu"))
continue;
int ace = Team.IndexOf(Team.OrderBy(z => z.Level).Last());
int 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
PokeDataFull Starter = new();
if (IsClavell(t.TrId))
{
if (t.TrId.Contains("kusa")) Starter = StarterF3;
if (t.TrId.Contains("hono")) Starter = StarterW3;
if (t.TrId.Contains("mizu")) Starter = StarterG3;
}
if (IsNemonaStage1(t.TrId))
{
if (t.TrId.Contains("kusa")) Starter = StarterW1;
if (t.TrId.Contains("hono")) Starter = StarterG1;
if (t.TrId.Contains("mizu")) Starter = StarterF1;
}
else if (IsNemonaStage2(t.TrId))
{
if (t.TrId.Contains("kusa")) Starter = StarterW2;
if (t.TrId.Contains("hono")) Starter = StarterG2;
if (t.TrId.Contains("mizu")) Starter = StarterF2;
}
else
{
if (t.TrId.Contains("kusa")) Starter = StarterW3;
if (t.TrId.Contains("hono")) Starter = StarterG3;
if (t.TrId.Contains("mizu")) Starter = StarterF3;
}
Team[index].DevId = Starter.DevId;
Team[index].FormId = Starter.FormId;
Team[index].Sex = Starter.Sex;
Team[index].RareType = RareType.NO_RARE;
if (GIVE_TRAINERS_SMART_EFFORT_VALUES)
Team[index].EffortValue = SetSmartEVs((ushort)Team[index].DevId, Team[index].FormId);
}
if (GIVE_TRAINERS_MAXIMUM_AI)
t.AiBasic = t.AiHigh = t.AiExpert = t.AiChange = true;
}
string statics = Path.Combine(DEST_MASTER, "world/data/battle/eventBattlePokemon/eventBattlePokemon_array.bin");
var objS = FlatBufferConverter.DeserializeFrom<EventBattlePokemonArray>(statics);
obj = StandardizeTrainerData(obj, objS);
foreach (var t in obj.Table)
{
string type = GetTrainerClass(t.TrainerType);
string name = GetStringFromAHTBCommon("trname", FnvHash.HashFnv1a_64(t.TrNameLabel), LANGUAGE);
string 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;
display = display.Replace("INVALID", " ").Trim();
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))
{
string form = pk.FormId is 0 ? string.Empty : $"-{pk.FormId}";
string rare = pk.RareType is RareType.RARE ? " ★" : string.Empty;
string line = $"{species[(int)pk.DevId]}{form}{rare} (Lv. {pk.Level})";
Members.Add(line);
}
RandomizerLog.Add($"{display} - {String.Join(" / ", Members)}");
}
PokeDataBattle GetBlankPokemon(int level, ParamSet talent, ParamSet effort)
{
PokeDataBattle pk = new()
{
DevId = DevID.DEV_HUSIGIDANE,
Level = level,
Waza1 = new WazaSet { WazaId = WazaID.WAZA_NULL, PointUp = 0 },
Waza2 = new WazaSet { WazaId = WazaID.WAZA_NULL, PointUp = 0 },
Waza3 = new WazaSet { WazaId = WazaID.WAZA_NULL, PointUp = 0 },
Waza4 = new WazaSet { WazaId = WazaID.WAZA_NULL, PointUp = 0 },
TalentValue = talent,
EffortValue = effort,
};
return pk;
}
string GetTrainerClass(string trClass)
{
var trainer = typ.Table.FirstOrDefault(z => z.NameLabel == trClass);
if (trainer is null)
return string.Empty;
return GetStringFromAHTBCommon("trtype", FnvHash.HashFnv1a_64(trainer.MsgLabel), LANGUAGE);
}
RandomizerLog.Add("");
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, fileTrData), data);
UpdateAllTrainerScenes(obj);
}
private static PokeDataBattle RandomizeTrainerPokemonSingle(PokeDataBattle pk)
{
pk.DevId = GetRandomSpecies(pk.DevId);
pk.FormId = GetRandomForm(pk.DevId);
pk.Sex = GetRandomGender(pk.DevId, pk.FormId);
pk.WazaType = WazaType.DEFAULT;
pk.GemType = GetTeraType(pk.DevId, (byte)pk.FormId, pk.GemType, false);
pk.Seikaku = SeikakuType.DEFAULT;
pk.Tokusei = (TokuseiType)GetRandom(2, 5);
pk.RareType = GetRandom(100) < TRAINER_POKEMON_SHINY_CHANCE_PERCENT ? RareType.RARE : RareType.NO_RARE;
if (GIVE_POKEMON_RANDOM_POKE_BALLS)
pk.BallId = (BallType)GetRandom(1, MAX_BALL_ID + 1);
if (MODIFY_POKEMON_LEVELS)
pk.Level = Math.Clamp((int)(pk.Level * (1.0f + ((float)LEVEL_MODIFIER_PERCENT / 100.0f))), 1, 100);
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)
{
pk.DevId = (DevID)ForceEvolveSpeciesFully((ushort)pk.DevId, (byte)pk.FormId);
pk.FormId = GetRandomForm(pk.DevId); // refresh
pk.Sex = GetRandomGender(pk.DevId, pk.FormId); // refresh
}
if (GIVE_TRAINERS_SMART_EFFORT_VALUES)
pk.EffortValue = SetSmartEVs((ushort)pk.DevId, pk.FormId);
if (ENSURE_SANE_FORM_HELD_ITEMS && IsFormHeldItemDependent(pk.DevId))
pk.Item = GetFormSpecificItem(pk.DevId, pk.FormId);
return pk;
}
private static ParamSet SetSmartEVs(ushort species, short form)
{
string source = Path.Combine(PATH_MASTER, "avalon/data/personal_array.bin");
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(source);
var sel = obj.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 ];
var highest = BaseStats
.Select((stat, index) => new Tuple<int, byte>(index, stat))
.OrderByDescending(t => t.Item2)
.Take(3)
.ToArray();
ParamSet Spread = new();
if (highest[0].Item1 is 0) Spread.HP = 252;
if (highest[0].Item1 is 1) Spread.ATK = 252;
if (highest[0].Item1 is 2) Spread.DEF = 252;
if (highest[0].Item1 is 3) Spread.SPA = 252;
if (highest[0].Item1 is 4) Spread.SPD = 252;
if (highest[0].Item1 is 5) Spread.SPE = 252;
if (highest[1].Item1 is 0) Spread.HP = 252;
if (highest[1].Item1 is 1) Spread.ATK = 252;
if (highest[1].Item1 is 2) Spread.DEF = 252;
if (highest[1].Item1 is 3) Spread.SPA = 252;
if (highest[1].Item1 is 4) Spread.SPD = 252;
if (highest[1].Item1 is 5) Spread.SPE = 252;
if (highest[2].Item1 is 0) Spread.HP = 6;
if (highest[2].Item1 is 1) Spread.ATK = 6;
if (highest[2].Item1 is 2) Spread.DEF = 6;
if (highest[2].Item1 is 3) Spread.SPA = 6;
if (highest[2].Item1 is 4) Spread.SPD = 6;
if (highest[2].Item1 is 5) Spread.SPE = 6;
return Spread;
}
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 IsTeraShard(ulong item) => item is (>= 1862 and <= 1879) or 2549;
foreach (string t in FieldItems)
{
int index = t.LastIndexOf("/") + 1;
string f = t[index..];
string d = t[..index];
string 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}) ==");
string file = Path.Combine(PATH_MASTER, d, f);
var obj = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(file);
string[] names = GetStringsCommon(LANGUAGE, "itemname");
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 (TechnicalMachineList.Contains((int)item.Value))
continue;
if (IsBlueberryTeraShard) // do not randomize due to excessive quantity
continue;
string oItem = names[item.Value];
item.Value = (ulong)GetRandomItem();
string nItem = names[item.Value];
IsModifyQuantity = IsTeraShard(item.Value);
string oQuantity = $"×{quantity.Value}";
if (IsModifyQuantity)
quantity.Value = GetRandomWeightedQuantity(); // give Tera Shards a special, weighted quantity
string nQuantity = $"×{quantity.Value}";
RandomizerLog.Add($"- {oItem} ({oQuantity}) -> {nItem} ({nQuantity})");
}
}
s.Data = FlatBufferConverter.SerializeFrom(tps);
}
}
RandomizerLog.Add("");
byte[] data = FlatBufferConverter.SerializeFrom(obj);
string 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); // Violet
}
}
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();
string[] names = GetStringsCommon(LANGUAGE, "itemname");
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 (string h in HiddenItems)
{
int index = h.LastIndexOf("/") + 1;
string f = h[index..];
string d = h[..index];
string 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}) ==");
string 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;
string oldItem = $"{names[(int)item.ItemId]} (×{item.DropCount})";
item.ItemId = GetRandomItem();
string newItem = $"{names[(int)item.ItemId]} (×{item.DropCount})";
string line = $"- {rate:00.00}% {oldItem} -> {newItem}";
RandomizerLog.Add(line);
}
RandomizerLog.Add("");
}
byte[] data = FlatBufferConverter.SerializeFrom(obj);
string 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";
string dest = Path.Combine(DEST_MASTER, "world/data/item/monohiroiItemData/");
string path = Path.Combine(PATH_MASTER, "world/data/item/monohiroiItemData/");
var obj = FlatBufferConverter.DeserializeFrom<MonohiroiItemArray>(Path.Combine(path, file));
string[] names = GetStringsCommon(LANGUAGE, "itemname");
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];
string 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}% {names[(int)item.ItemId]}");
}
RandomizerLog.Add("");
}
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] 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";
string dest = Path.Combine(DEST_MASTER, "world/data/item/rummagingItemDataTable/");
string 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);
byte[] 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 (string r in RaidBosses)
{
int index = r.LastIndexOf("/") + 1;
string f = r[index..];
string d = r[..index];;
string 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;
t.DevId = GetRandomSpecies(t.DevId);
// Terapagos can only be Stellar Tera Type, and the game does not load Stellar raid crystals
if (t.DevId is DevID.DEV_KODAIGAME)
{
while (t.DevId is DevID.DEV_KODAIGAME)
t.DevId = GetRandomSpecies(0);
}
t.FormId = GetRandomForm(t.DevId);
t.Sex = GetRandomGender(t.DevId, t.FormId);
t.WazaType = WazaType.DEFAULT;
t.GemType = GetTeraType(t.DevId, (byte)t.FormId, t.GemType, true);
if (GIVE_POKEMON_HELD_ITEMS)
t.Item = GetRandomItem();
if (MODIFY_POKEMON_LEVELS)
{
t.Level = Math.Clamp((int)(t.Level * (1.0f + ((float)LEVEL_MODIFIER_PERCENT / 100.0f))), 1, 100);
boss.Info.CaptureLv = (sbyte)Math.Clamp((int)(boss.Info.CaptureLv * (1.0f + ((float)LEVEL_MODIFIER_PERCENT / 100.0f))), 1, 100);
}
if (FORCE_FULLY_EVOLVE && IsFiveOrSixStar)
{
t.DevId = (DevID)ForceEvolveSpeciesFully((ushort)t.DevId, (byte)t.FormId);
t.FormId = GetRandomForm(t.DevId); // refresh
t.Sex = GetRandomGender(t.DevId, t.FormId); // refresh
}
if (ENSURE_SANE_FORM_HELD_ITEMS && IsFormHeldItemDependent(t.DevId))
t.Item = GetFormSpecificItem(t.DevId, t.FormId);
// replace move extra actions with more sane actions
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))
{
RaidBossExtraActType action = (RaidBossExtraActType)GetRandom(1, 3);
RaidBossExtraTimingType type = (RaidBossExtraTimingType)GetRandom(1, 3);
a.Action = action;
a.Timing = type;
a.Value = (short)GetRandom(1, 100);
}
}
}
byte[] data = FlatBufferConverter.SerializeFrom(obj);
string 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";
string dest = Path.Combine(DEST_MASTER, "world/data/item/itemdata/");
string path = Path.Combine(PATH_MASTER, "world/data/item/itemdata/");
string[] items = GetStringsCommon(LANGUAGE, "itemname");
string[] moves = GetStringsCommon(LANGUAGE, "wazaname");
List<int> Moves = GetRandomMoveList();
List<int> TMs = new();
string machine = items[0328].Remove(items[0328].Length - 3);
byte ctr = 0;
byte 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)
{
byte[] 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} {moves[(ushort)t.MachineWaza]}");
idx++;
}
}
RandomizerLog.Add("");
FixTMCompatibility(TMs);
FixTMMachine(TMs);
FixTMIcons();
}
private static void FixTMCompatibility(List<int> TMs)
{
const string file = "personal_array.bin";
string dest = Path.Combine(DEST_MASTER, "avalon/data/");
string source = Directory.Exists(dest) ? Path.Combine(dest, file) : Path.Combine(PATH_MASTER, "avalon/data/", file);
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(source);
foreach (var t in obj.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;
int index = i > 100 ? i : i - 1;
Revised.Add((ushort)TMs[index]);
}
t.TechnicalMachine = Revised.ToList();
}
}
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, file), data);
}
private static void FixTMMachine(List<int> TMs)
{
const string file = "shop_wazamachine_data_array.bin";
string dest = Path.Combine(DEST_MASTER, "world/data/ui/shop/shop_wazamachine/shop_wazamachine_data/");
string 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!
int 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);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, file), data);
}
private static void FixTMIcons()
{
const string file = "itemdata_array.bin";
string dest = Path.Combine(DEST_MASTER, "world/data/item/itemdata/");
string destIcon = Path.Combine(DEST_MASTER, "appli/tex/icon_item/");
string pathIcon = Path.Combine(PATH_MASTER, "appli/tex/icon_item/");
var obj = FlatBufferConverter.DeserializeFrom<ItemDataArray>(Path.Combine(dest, file));
foreach (var t in obj.Table)
{
if (t.MachineWaza is WazaID.WAZA_NULL || t.Id is 1230)
continue;
string current = $"item_{t.Id:0000}";
string 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()
{
const string file = "personal_array.bin";
string dest = Path.Combine(DEST_MASTER, "avalon/data/");
string path = Path.Combine(PATH_MASTER, "avalon/data/");
string[] species = GetStringsCommon(LANGUAGE, "monsname");
string[] ability = GetStringsCommon(LANGUAGE, "tokusei");
string[] types = GetStringsCommon(LANGUAGE, "typename");
string[] moves = GetStringsCommon(LANGUAGE, "wazaname");
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(Path.Combine(path, file));
obj = AddRegionalFormEvolutions(obj);
var ordered = obj.Table.OrderBy(z => z.Info.SpeciesNational);
// give evolution moves to Stantler and Dipplin, regardless of randomizer settings
var stan = ordered.FirstOrDefault(z => z.Info.SpeciesNational is 0234);
var dipp = ordered.FirstOrDefault(z => z.Info.SpeciesNational is 1011);
stan.Learnset.Add(new PersonalInfoMove { Move = 828, Level = 30 });
dipp.ReminderMoves.Add(913);
if (RANDOMIZE_PERSONAL_ABILITY)
{
RandomizerLog.Add("== POKÉMON ABILITIES ==");
"Randomizing Pokémon abilities...".Dump();
if (ABILITIES_TYPES_FOLLOW_EVOLUTIONS)
{
foreach (var t in ordered.Where(z => z.IsPresentInGame))
{
t.Ability1 = (ushort)GetRandomAbility();
t.Ability2 = (ushort)GetRandomAbility();
t.AbilityH = (ushort)GetRandomAbility();
}
foreach (var t in ordered.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 = obj.Table.FirstOrDefault(z => z.Info.SpeciesInternal == evoS1 && z.Info.Form == evoF1);
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 = obj.Table.FirstOrDefault(z => z.Info.SpeciesInternal == evoS2 && z.Info.Form == evoF2);
evo2.Ability1 = t.Ability1;
evo2.Ability2 = t.Ability2;
evo2.AbilityH = t.AbilityH;
}
}
}
// Gimmighoul edge case
var gimmi = ordered.FirstOrDefault(z => z.Info.SpeciesNational is 0999 && z.Info.Form is 0);
var ghold = ordered.FirstOrDefault(z => z.Info.SpeciesNational is 1000);
gimmi.Ability1 = ghold.Ability1;
gimmi.Ability2 = ghold.Ability2;
gimmi.AbilityH = ghold.AbilityH;
}
else
{
foreach (var t in ordered.Where(z => z.IsPresentInGame))
{
t.Ability1 = (ushort)GetRandomAbility();
t.Ability2 = (ushort)GetRandomAbility();
t.AbilityH = (ushort)GetRandomAbility();
}
}
foreach (var t in ordered.Where(z => z.IsPresentInGame))
{
if (t.Info.SpeciesNational is 1007 or 1008 && t.Info.Form is not 0)
continue;
string form = t.Info.Form is 0 ? string.Empty : $"-{t.Info.Form}";
string name = $"{t.Info.SpeciesNational:0000} {species[t.Info.SpeciesInternal]}{form}";
string abil = $"{ability[t.Ability1]} (1) / {ability[t.Ability2]} (2) / {ability[t.AbilityH]} (H)";
RandomizerLog.Add($"{name} - {abil}");
}
RandomizerLog.Add("");
}
if (RANDOMIZE_PERSONAL_TYPE)
{
RandomizerLog.Add("== POKÉMON TYPES ==");
"Randomizing Pokémon types...".Dump();
if (ABILITIES_TYPES_FOLLOW_EVOLUTIONS)
{
foreach (var t in ordered.Where(z => z.IsPresentInGame))
{
byte type1 = (byte)GetRandom(MAX_TYPE_ID + 1);
byte type2 = type1;
// give a 40% chance at dual-type
if (GetRandom(100) < 40)
{
while (GetRandom(MAX_TYPE_ID + 1) != type1)
type2 = (byte)GetRandom(MAX_TYPE_ID + 1);
}
t.Type1 = type1;
t.Type2 = type2;
}
bool GainNewTypeFirst = GetRandom(100) < 30 ? true : false;
foreach (var t in ordered.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 = obj.Table.FirstOrDefault(z => z.Info.SpeciesInternal == evoS1 && z.Info.Form == evoF1);
evo1.Type1 = t.Type1;
evo1.Type2 = t.Type2;
// give a 30% chance at gaining a second type upon first evolution
if (evo1.Type1 == evo1.Type2 && GainNewTypeFirst)
{
while (GetRandom(MAX_TYPE_ID + 1) != t.Type1)
evo1.Type2 = (byte)GetRandom(MAX_TYPE_ID + 1);
}
// second evo
bool GainNewTypeSecond = GetRandom(100) < 40 ? true : false;
foreach (var f in evo1.Evolutions.Where(z => z.SpeciesInternal is not 0))
{
var evoS2 = f.SpeciesInternal;
var evoF2 = f.Form;
var evo2 = obj.Table.FirstOrDefault(z => z.Info.SpeciesInternal == evoS2 && z.Info.Form == evoF2);
evo2.Type1 = evo1.Type1;
evo2.Type2 = evo1.Type2;
// give a 40% chance at gaining a second type upon second evolution
if (evo2.Type1 == evo2.Type2 && GainNewTypeSecond)
{
while (GetRandom(MAX_TYPE_ID + 1) != evo1.Type1)
evo2.Type2 = (byte)GetRandom(MAX_TYPE_ID + 1);
}
}
}
}
// Gimmighoul edge case
var gimmi = ordered.FirstOrDefault(z => z.Info.SpeciesNational is 0999 && z.Info.Form is 0);
var ghold = ordered.FirstOrDefault(z => z.Info.SpeciesNational is 1000);
gimmi.Type1 = ghold.Type1;
gimmi.Type2 = ghold.Type2;
}
else
{
foreach (var t in ordered.Where(z => z.IsPresentInGame))
{
byte type1 = (byte)GetRandom(MAX_TYPE_ID + 1);
byte type2 = type1;
// give a 40% chance at dual-type
if (GetRandom(100) < 40)
{
while (GetRandom(MAX_TYPE_ID + 1) != type1)
type2 = (byte)GetRandom(MAX_TYPE_ID + 1);
}
t.Type1 = type1;
t.Type2 = type2;
}
}
foreach (var t in ordered.Where(z => z.IsPresentInGame))
{
if (t.Info.SpeciesNational is 1007 or 1008 && t.Info.Form is not 0 || t.Info.SpeciesNational is 1017 && t.Info.Form >= 4)
continue;
string form = t.Info.Form is 0 ? string.Empty : $"-{t.Info.Form}";
string name = $"{t.Info.SpeciesNational:0000} {species[t.Info.SpeciesInternal]}{form}";
string type = t.Type1 == t.Type2 ? $"{types[t.Type1]}" : $"{types[t.Type1]} / {types[t.Type2]}";
RandomizerLog.Add($"{name} ({type})");
}
RandomizerLog.Add("");
}
if (RANDOMIZE_PERSONAL_EVOLUTION)
{
"Randomizing Pokémon evolutions...".Dump();
foreach (var t in ordered.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 ordered.Where(z => z.IsPresentInGame))
{
t.EXPGrowth = 5;
var evos = t.Evolutions;
DevID s = GetRandomSpecies(0);
byte 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_EVOLUTION || MAKE_POKEMON_EVOLVE_EVERY_LEVEL)
{
RandomizerLog.Add("== POKÉMON EVOLUTIONS ==");
foreach (var t in ordered.Where(z => z.IsPresentInGame))
{
var evos = t.Evolutions;
foreach (var e in evos)
{
if (t.Info.SpeciesNational is 1007 or 1008 && t.Info.Form is not 0 || t.Info.SpeciesNational is 1017 && t.Info.Form >= 4)
continue;
string form = t.Info.Form is 0 ? string.Empty : $"-{t.Info.Form}";
string formE = e.Form is 0 ? string.Empty : $"-{e.Form}";
string name = $"{t.Info.SpeciesNational:0000} [{evos.IndexOf(e)}] {species[t.Info.SpeciesInternal]}{form}";
string evol = $"Evolves into {species[e.SpeciesInternal]}{formE}";
RandomizerLog.Add($"{name} - {evol}");
}
}
RandomizerLog.Add("");
}
if (RANDOMIZE_PERSONAL_LEARNSET && !ALL_POKEMON_ONLY_KNOW_METRONOME)
{
RandomizerLog.Add("== POKÉMON LEARNSETS ==");
"Randomizing Pokémon learnsets...".Dump();
List<int> PokemonWithImportantMoves =
[
0057, // Primeape
0190, // Aipom
0193, // Yanma
0203, // Girafarig
0206, // Dunsparce
0211, // Qwilfish
0221, // Piloswine
0438, // Bonsly
0647, // Keldeo
0648, // Meloetta
0762, // Steenee
];
foreach (var t in ordered.Where(z => z.IsPresentInGame))
{
IList<PersonalInfoMove> Learnset = t.Learnset;
IList<ushort> Reminder = t.ReminderMoves;
List<int> Moves = GetRandomMoveList();
int ctr = 0;
// level up
for (int i = 0; i < Learnset.Count; i++)
{
if (t.Info.SpeciesNational is 0234 && Learnset[i].Move is 828) // keep Psyshield Bash for Stantler
continue;
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;
ushort move = t.Info.SpeciesNational switch
{
0057 => 889, // Rage Fist
0193 or 0221 => 246, // Ancient Power
0190 or 0438 => 102, // Mimic
0203 => 888, // Twin Beam
0206 => 887, // Hyper Drill
0211 => 839, // Barb Barrage
0647 => 548, // Secret Sword
0648 => 547, // Relic Song
0762 => 023, // Stomp
_ => throw new ArgumentException($"Invalid species: {t.Info.SpeciesNational}"),
};
Reminder.Add(move);
}
t.Learnset = t.Learnset.OrderBy(z => z.Level).ToList();
string form = t.Info.Form is 0 ? string.Empty : $"-{t.Info.Form}";
string name = $"{t.Info.SpeciesNational:0000} {species[t.Info.SpeciesInternal]}{form}";
if (t.Info.SpeciesNational is 1007 or 1008 && t.Info.Form is not 0 || t.Info.SpeciesNational is 1017 && t.Info.Form >= 4)
continue;
RandomizerLog.Add(name);
foreach (var r in t.ReminderMoves)
RandomizerLog.Add($"- [000] {moves[r]}");
foreach (var m in t.Learnset)
{
int level = m.Level is -3 ? 0 : m.Level;
RandomizerLog.Add($"- [{level:000}] {moves[m.Move]}");
}
RandomizerLog.Add("");
}
}
if (ALL_POKEMON_ONLY_KNOW_METRONOME)
{
"Modifying all learnsets to only include Metronome...".Dump();
foreach (var t in ordered.Where(z => z.IsPresentInGame))
{
t.TechnicalMachine.Clear();
t.EggMoves.Clear();
t.ReminderMoves.Clear();
t.Learnset.Clear();
PersonalInfoMove Metronome = new() { 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 ordered.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();
int 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)
{
int diff = BaseStatTotal - Final.Sum();
for (int i = 0; i < diff; i++)
{
int 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 ordered.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 (SHUFFLE_PERSONAL_BASE_STATS)
{
"Shuffling Pokémon base stats...".Dump();
foreach (var t in ordered.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 (RANDOMIZE_PERSONAL_BASE_STATS_WITHIN_BST || RANDOMIZE_PERSONAL_BASE_STATS_COMPLETELY_RANDOM || SHUFFLE_PERSONAL_BASE_STATS)
{
RandomizerLog.Add("== POKÉMON BASE STATS ==");
foreach (var t in ordered.Where(z => z.IsPresentInGame))
{
if (t.Info.SpeciesNational is 1007 or 1008 && t.Info.Form is not 0 || t.Info.SpeciesNational is 1017 && t.Info.Form >= 4)
continue;
string form = t.Info.Form is 0 ? string.Empty : $"-{t.Info.Form}";
string name = $"{t.Info.SpeciesNational:0000} {species[t.Info.SpeciesInternal]}{form}";
int total = t.Base.HP + t.Base.ATK + t.Base.DEF + t.Base.SPA + t.Base.SPD + t.Base.SPE;
string stats = $"{t.Base.HP}/{t.Base.ATK}/{t.Base.DEF}/{t.Base.SPA}/{t.Base.SPD}/{t.Base.SPE} (BST: {total})";
RandomizerLog.Add($"{name} - {stats}");
}
RandomizerLog.Add("");
}
if (REMOVE_EFFORT_VALUE_YIELDS)
{
"Removing EV yields...".Dump();
foreach (var t in ordered.Where(z => z.IsPresentInGame))
t.EVYield = new PersonalInfoStats { HP = 0, ATK = 0, DEF = 0, SPA = 0, SPD = 0, SPE = 0 };
}
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, file), data);
}
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 (int 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 void RandomizeMoveProperties()
{
const string file = "waza_array.bin";
string dest = Path.Combine(DEST_MASTER, "avalon/data/");
string path = Path.Combine(PATH_MASTER, "avalon/data/");
var obj = FlatBufferConverter.DeserializeFrom<WazaTable>(Path.Combine(path, file));
List<byte> PP = [ 5, 10, 15, 20, 25, 30, 35, 40 ];
string[] moves = GetStringsCommon(LANGUAGE, "wazaname");
string[] types = GetStringsCommon(LANGUAGE, "typename");
bool IsNotStatusMove(byte category) => category is not 0;
string localePP = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_skill_01"), LANGUAGE);
string localeCategory = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_skill_category_00"), LANGUAGE);
string localePower = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_skill_power_00"), LANGUAGE);
string localeAccuracy = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_skill_hit_00"), LANGUAGE);
string 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" ],
[ "變化", "物理", "特殊" ],
];
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 obj.Table.Where(z => z.CanUseMove && z.MoveID is not 0))
{
if (RANDOMIZE_MOVE_PROPERTIES_TYPE)
t.Type = (byte)GetRandom(MAX_TYPE_ID + 1);
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 = PP[GetRandom(PP.Count)];
}
foreach (var t in obj.Table.Where(z => z.CanUseMove && z.MoveID is not 0))
{
string type = $"- {localeType}: {types[(int)t.Type]}";
string category = $"- {localeCategory}: {Categories[Languages.IndexOf(LANGUAGE)][t.Category]}";
string power = $"- {localePower}: {t.Power}";
string accuracy = $"- {localeAccuracy}: {t.Accuracy}";
string pp = $"- {localePP}: {t.PP}";
RandomizerLog.Add($"{moves[t.MoveID]}");
RandomizerLog.Add(type);
RandomizerLog.Add(category);
RandomizerLog.Add(power);
RandomizerLog.Add(accuracy);
RandomizerLog.Add(pp);
RandomizerLog.Add("");
}
RandomizerLog.Add("");
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, file), data);
}
}
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";
string dest = Path.Combine(DEST_MASTER, "world/data/event/eventTradePokemon/");
string path = Path.Combine(PATH_MASTER, "world/data/event/eventTradePokemon/");
var obj = FlatBufferConverter.DeserializeFrom<EventTradePokemonArray>(Path.Combine(path, file));
string[] names = GetStringsCommon(LANGUAGE, "monsname");
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;
DevID oldSpecies = t.DevId;
short oldForm = t.FormId;
t.DevId = GetRandomSpecies(t.DevId);
t.FormId = GetRandomForm(t.DevId);
t.Sex = GetRandomGender(t.DevId, t.FormId);
t.Tokusei = (TokuseiType)GetRandom(2, 5);
t.GemType = GetTeraType(t.DevId, (byte)t.FormId, t.GemType, false);
t.RareType = RareType.DEFAULT;
t.Seikaku = t.SeikakuHosei = SeikakuType.DEFAULT;
t.WazaType = WazaType.DEFAULT;
DevID newSpecies = t.DevId;
if (GIVE_POKEMON_RANDOM_POKE_BALLS)
t.BallId = (BallType)GetRandom(1, MAX_BALL_ID + 1);
if (GIVE_POKEMON_RANDOM_RIBBONS)
t.SetRibbon = (RibbonType)GetRandom(1, MAX_RIBBON_ID + 1);
if (MODIFY_POKEMON_LEVELS)
t.Level = Math.Clamp((int)(t.Level * (1.0f + ((float)LEVEL_MODIFIER_PERCENT / 100.0f))), 1, 100);
if (GIVE_POKEMON_HELD_ITEMS)
t.Item = GetRandomItem();
if (ENSURE_SANE_FORM_HELD_ITEMS && 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)newSpecies);
}
string oForm = oldForm is 0 ? string.Empty : $"-{oldForm}";
string nForm = t.FormId is 0 ? string.Empty : $"-{t.FormId}";
RandomizerLog.Add($"{names[(int)oldSpecies]}{oForm} -> {names[(int)t.DevId]}{nForm}");
}
RandomizerLog.Add("");
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] 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";
string dest = Path.Combine(DEST_MASTER, "world/data/event/eventTradeList/");
string path = Path.Combine(PATH_MASTER, "world/data/event/eventTradeList/");
var obj = FlatBufferConverter.DeserializeFrom<EventTradeListArray>(Path.Combine(path, file));
string[] names = GetStringsCommon(LANGUAGE, "monsname");
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
];
RandomizerLog.Add($"- {Trainers[0]}: Trade {names[(int)take1.SendPokeDevId]} for {names[(int)give1.DevId]}{GetForm((byte)give1.FormId)}");
RandomizerLog.Add($"- {Trainers[1]}: Trade {names[(int)take2.SendPokeDevId]} for {names[(int)give2.DevId]}{GetForm((byte)give2.FormId)}");
RandomizerLog.Add($"- {Trainers[2]}: Trade {names[(int)take3.SendPokeDevId]} for {names[(int)give3.DevId]}{GetForm((byte)give3.FormId)}");
RandomizerLog.Add("");
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] 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;
foreach (string lang in Languages)
{
string dest = Path.Combine(DEST_MASTER, $"message/dat/{lang}/script/");
string[] lines = GetStringsScript(lang, "field_trade");
string[] names = GetStringsCommon(lang, "monsname");
TextConfig cfg = new(GameVersion.SV);
string TRADE_A = names[0194]; // Wooper
string TRADE_B_1 = names[0093]; // Haunter
string TRADE_B_2 = names[0871]; // Pincurchin
string TRADE_C_1 = names[0872]; // Snom
string TRADE_C_2 = names[0669]; // Flabébé
// special handling for Wooper -> Wooper
List<int> WooperStrings = new();
int 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[give1]);
if (lang is not "Italian")
lines[09] = lines[09].Remove(WooperStrings[1], TRADE_A.Length).Insert(WooperStrings[1], names[take1]);
lines[09] = lines[09].Remove(WooperStrings[0], TRADE_A.Length).Insert(WooperStrings[0], names[give1]);
lines[10] = lines[10].Replace(TRADE_A, names[give1]);
lines[11] = lines[11].Replace(TRADE_A, names[give1]);
lines[12] = lines[12].Replace(TRADE_A, names[take1]);
lines[13] = lines[13].Replace(TRADE_A, names[give1]);
for (int t = 0; t < lines.Length; t++)
{
lines[t] = lines[t].Replace(TRADE_B_1, names[take2]).Replace(TRADE_B_2, names[give2]);
lines[t] = lines[t].Replace(TRADE_C_1, names[take3]).Replace(TRADE_C_2, names[give3]);
}
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] write = TextFile.GetBytes(lines, cfg);
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++)
{
string lang = Languages[i];
string dest = Path.Combine(DEST_MASTER, $"message/dat/{lang}/script/");
string text = coach is "teacher_history" or "bbteacher_head" ? "trade_01_02" : "trade_01_03";
ulong hash = FnvHash.HashFnv1a_64($"coach_{coach}_{text}");
string[] names = GetStringsCommon(lang, "monsname");
string[] lines = GetStringsScript(lang, $"coach_{coach}");
byte[] data = File.ReadAllBytes(Path.Combine(PATH_MASTER, $"message/dat/{lang}/script/coach_{coach}.tbl"));
List<AHTBEntry> Entries = new AHTB(data).Entries.ToList();
AHTBEntry target = Entries.FirstOrDefault(z => z.Hash == hash);
int index = Entries.IndexOf(target);
string species = coach switch
{
"paldea_botan" => Botan[i], // Veevee
"gym_ghost" => Ghost[i], // DJ G-Rave
_ => names[oldSpecies],
};
string line = lines[index].Replace(species, names[newSpecies]);
lines[index] = line;
}
}
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";
string dest = Path.Combine(DEST_MASTER, "world/data/event/sub_012/sub_012_reward/");
string path = Path.Combine(PATH_MASTER, "world/data/event/sub_012/sub_012_reward/");
var obj = FlatBufferConverter.DeserializeFrom<RewardTableArray>(Path.Combine(path, file));
string[] items = GetStringsCommon(LANGUAGE, "itemname");
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}% {items[(int)t.ItemId]} (×{t.ItemNum})");
}
RandomizerLog.Add("");
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] 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",
];
string[] items = GetStringsCommon(LANGUAGE, "itemname");
foreach (string r in RewardData)
{
int index = r.LastIndexOf("/") + 1;
string f = r[index..];
string d = r[..index];
string 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}"),
};
string 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
// unfortunately, the game cuts off milestones in sets of 40, so each page can display 4 milestones
// because of this, we need to stop at 720 total captured, which is only 13 away from the total, so it's not a big deal
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 <= 710 => 15,
_ => 1,
};
obj.Table.Clear();
for (int i = 10; i <= 710; i += 10)
{
RewardData Reward = new()
{
PokedexType = PokedexType.TITAN,
CaptureNum = i,
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
ItemID item = (ItemID)ValuableItems[GetRandom(ValuableItems.Count)];
int num = item is ItemID.ITEMID_SOZAI182 ? 999 : 100;
RewardData FinalReward = new()
{
PokedexType = PokedexType.TITAN,
CaptureNum = 720,
ItemId = item,
ItemNum = num,
};
obj.Table.Add(FinalReward);
}
else
{
foreach (var t in obj.Table)
{
t.ItemId = GetRandomItem();
t.ItemNum = 1;
}
}
RandomizerLog.Add($"== POKÉDEX MILESTONE REWARDS ({dex}) ==");
foreach (var t in obj.Table)
RandomizerLog.Add($"- {t.CaptureNum} Captured: {items[(int)t.ItemId]} (×{t.ItemNum})");
RandomizerLog.Add("");
byte[] data = FlatBufferConverter.SerializeFrom(obj);
string 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";
string dest = Path.Combine(DEST_MASTER, "world/data/ajito/AjitoPokemon/");
string path = Path.Combine(PATH_MASTER, "world/data/ajito/AjitoPokemon/");
var obj = FlatBufferConverter.DeserializeFrom<AjitoPokemonArray>(Path.Combine(path, file));
string[] names = GetStringsCommon(LANGUAGE, "monsname");
foreach (var enc in obj.Table)
{
var t = enc.Table;
var oldDev = t.DevId;
var oldForm = t.FormNo;
t.DevId = GetRandomSpecies(t.DevId);
t.FormNo = GetRandomForm(t.DevId);
string oForm = oldForm is 0 ? string.Empty : $"-{oldForm}";
string nForm = t.FormNo is 0 ? string.Empty : $"-{t.FormNo}";
RandomizerLog.Add($"{names[(int)oldDev]}{oForm} -> {names[(int)t.DevId]}{nForm}");
}
RandomizerLog.Add("");
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] 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";
string dest = Path.Combine(DEST_MASTER, "world/data/danbattle/boss/dan_car_battle_data/");
string path = Path.Combine(PATH_MASTER, "world/data/danbattle/boss/dan_car_battle_data/");
var obj = FlatBufferConverter.DeserializeFrom<DanCarBattleDataArray>(Path.Combine(path, file));
string[] cars = GetStringsScript(LANGUAGE, "futatsuna");
string[] types = GetStringsCommon(LANGUAGE, "typename");
string[] abils = GetStringsCommon(LANGUAGE, "tokusei");
string[] moves = GetStringsCommon(LANGUAGE, "wazaname");
string localeType = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_pokemon_type_00"), LANGUAGE);
string localeAbility = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_pokemon_chara_00"), LANGUAGE);
string 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 = GetRandomMoveList();
var t = obj.Table[i].DanBossCarBattleStruct;
t.Waza1 = (WazaID)Moves[0];
t.Waza2 = (WazaID)Moves[1];
t.Waza3 = (WazaID)Moves[2];
t.Waza4 = (WazaID)Moves[3];
t.TokuseiId = (TokuseiID)GetRandomAbility();
t.Type1 = t.Type2 = (MoveType)GetRandom(MAX_TYPE_ID + 1);
if (MODIFY_POKEMON_LEVELS)
t.Level = Math.Clamp((int)(t.Level * (1.0f + ((float)LEVEL_MODIFIER_PERCENT / 100.0f))), 1, 100);
RandomizerLog.Add(StarmobileNames[i]);
RandomizerLog.Add($"{Level[Languages.IndexOf(LANGUAGE)]}: {t.Level}");
RandomizerLog.Add($"{localeType}: {types[(int)t.Type1]}");
RandomizerLog.Add($"{localeAbility}: {abils[(int)t.TokuseiId]}");
RandomizerLog.Add($"{localeMove}:");
RandomizerLog.Add($"- {moves[(int)t.Waza1]}");
RandomizerLog.Add($"- {moves[(int)t.Waza2]}");
RandomizerLog.Add($"- {moves[(int)t.Waza3]}");
RandomizerLog.Add($"- {moves[(int)t.Waza4]}");
RandomizerLog.Add("");
}
FixStarmobileText(obj);
RandomizerLog.Add("");
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, file), data);
}
private static void FixStarmobileText(DanCarBattleDataArray obj)
{
TextConfig cfg = new(GameVersion.SV);
foreach (string lang in Languages)
{
byte[] source = File.ReadAllBytes(Path.Combine(DEST_MASTER, $"message/dat/{lang}/script/futatsuna.dat"));
string[] bosses = new TextFile(source, cfg).Lines;
string[] types = GetStringsCommon(lang, "typename");
string[] cars = bosses[18..23];
string localeType = GetStringFromAHTBCommon("status", FnvHash.HashFnv1a_64("msg_ui_status_pokemon_type_00"), LANGUAGE);
for (int i = 0; i < obj.Table.Count; i++)
{
var t = obj.Table[i].DanBossCarBattleStruct;
string car = cars[i];
string type = types[(int)t.Type1];
string line = $"{car} ({localeType}: {type})";
bosses[18 + i] = line;
}
string dest = Path.Combine(DEST_MASTER, $"message/dat/{lang}/script/");
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] write = TextFile.GetBytes(bosses, cfg);
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 file = "reward_level_param_array.bin";
string dest = Path.Combine(DEST_MASTER, "world/data/oniballoon/reward_level_param/");
string path = Path.Combine(PATH_MASTER, "world/data/oniballoon/reward_level_param/");
var obj = FlatBufferConverter.DeserializeFrom<pkNX.Structures.FlatBuffers.SV.Balloon.RewardTableArray>(Path.Combine(path, file));
string[] items = GetStringsCommon(LANGUAGE, "itemname");
foreach (var t in obj.Table)
{
foreach (var item in t.Param.RewardItems.Where(z => z.ItemId is not 0))
{
item.ItemId = GetRandomItem();
RandomizerLog.Add($"- {items[(int)item.ItemId]} (×{item.Num})");
}
}
RandomizerLog.Add("");
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, file), data);
}
private static void RandomizeSpecialCoachRewards()
{
RandomizerLog.Add("== SPECIAL COACH ITEM REWARDS ==");
"Randomizing Special Coach item rewards...".Dump();
const string file = "ClubNpcRewardList_array.bin";
string dest = Path.Combine(DEST_MASTER, "world/data/club/ClubNpcRewardList/");
string 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 ];
string[] names = GetStringsCommon(LANGUAGE, "trname");
string[] items = GetStringsCommon(LANGUAGE, "itemname");
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;
string line = "- " + names[Coaches[(int)t.Coach]];
if (reward1.RewardType is ClubRewardType.ITEM) { reward1.RewardId = (int)GetRandomItem(); line += $": {items[(int)reward1.RewardId]} (×{reward1.Count})"; }
if (reward2.RewardType is ClubRewardType.ITEM) { reward2.RewardId = (int)GetRandomItem(); line += $": {items[(int)reward2.RewardId]} (×{reward2.Count})"; }
if (reward3.RewardType is ClubRewardType.ITEM) { reward3.RewardId = (int)GetRandomItem(); line += $": {items[(int)reward3.RewardId]} (×{reward3.Count})"; }
RandomizerLog.Add(line);
}
RandomizerLog.Add("");
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] 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";
string dest = Path.Combine(DEST_MASTER, "world/data/gym/gym_mizu_seri_item/");
string path = Path.Combine(PATH_MASTER, "world/data/gym/gym_mizu_seri_item/");
var obj = FlatBufferConverter.DeserializeFrom<SeriItemTableArray>(Path.Combine(path, file));
string[] items = GetStringsCommon(LANGUAGE, "itemname");
foreach (var t in obj.Table.Where(z => z.ItemId is not 0 && z.DevId is 0))
{
t.ItemId = GetRandomItem();
string line = $"- {items[(int)t.ItemId]} (Min: {t.MinItemNum} / Max: {t.MaxItemNum})";
RandomizerLog.Add(line);
}
RandomizerLog.Add("");
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] 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";
string dest = Path.Combine(DEST_MASTER, "world/data/ui/item_machine/item_table/");
string path = Path.Combine(PATH_MASTER, "world/data/ui/item_machine/item_table/");
var obj = FlatBufferConverter.DeserializeFrom<ItemMachineItemTableArray>(Path.Combine(path, file));
string[] items = GetStringsCommon(LANGUAGE, "itemname");
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();
string item = $"{items[(int)t.Param.Value.ItemId]}";
float rate = t.Param.Value.EmergePercent / 100f;
string num = $"Min: {t.Param.Value.LotteryItemNumMin} / Max: {t.Param.Value.LotteryItemNumMax}";
string line = $"- {rate:0.00}% {item} ({num})";
RandomizerLog.Add(line);
}
RandomizerLog.Add("");
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] 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";
string destB = Path.Combine(DEST_MASTER, "world/data/raid/raid_lottery_reward_item/");
string 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";
string destF = Path.Combine(DEST_MASTER, "world/data/raid/raid_fixed_reward_item/");
string pathF = Path.Combine(PATH_MASTER, "world/data/raid/raid_fixed_reward_item/");
var objF = FlatBufferConverter.DeserializeFrom<RaidFixedRewardItemArray>(Path.Combine(pathF, fileF));
string[] items = GetStringsCommon(LANGUAGE, "itemname");
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)
{
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));
string oldItem = $"{items[(int)drop.ItemID]} (×{drop.Num})";
drop.ItemID = GetRandomItem();
string newItem = $"{items[(int)drop.ItemID]} (×{drop.Num})";
RandomizerLog.Add($"- {rate:00.00}% {oldItem} -> {newItem}");
}
RandomizerLog.Add("");
}
RandomizerLog.Add("");
if (!Directory.Exists(destB))
Directory.CreateDirectory(destB);
byte[] 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)
{
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;
string oldItem = $"{items[(int)drop.ItemID]} (×{drop.Num})";
drop.ItemID = GetRandomItem();
string newItem = $"{items[(int)drop.ItemID]} (×{drop.Num})";
RandomizerLog.Add($"- {oldItem} -> {newItem}");
}
RandomizerLog.Add("");
}
RandomizerLog.Add("");
if (!Directory.Exists(destF))
Directory.CreateDirectory(destF);
byte[] 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";
string 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";
string dest = Path.Combine(DEST_MASTER, "world/data/ui/shop/friendlyshop/friendlyshop_lineup_data/");
string 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();
int 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++)
{
int 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 (ushort 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);
byte[] 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";
string dest = Path.Combine(DEST_MASTER, "world/data/item/itemdata/");
string source = Directory.Exists(dest) ? Path.Combine(dest, file) : Path.Combine(PATH_MASTER, "world/data/item/itemdata/", file);
var obj = FlatBufferConverter.DeserializeFrom<ItemDataArray>(source);
List<int> TradeEvolutions = [ 0221, 0226, 0227, 0233, 0235, 0252, 0321, 0322, 0323, 0324, 0325, 0537, 0646, 0647 ];
// adjust Mythical Pecha Berry price, make trade evolution items usable
foreach (var t in obj.Table.Where(z => TradeEvolutions.Contains(z.Id)))
{
if (t.Id is 2550)
t.Price = 100000;
t.FieldFunctionType = FieldFunctionType.FIELDFUNC_EVOLUTION;
t.WorkType = WorkType.WORKTYPE_EffectPokemon;
t.WorkEvolutional = 1;
t.EquipEffect = EquipEffect.SOUBI_NONE;
t.EquipPower = 0;
}
// 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);
byte[] 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";
string dest = Path.Combine(DEST_MASTER, "world/data/battle/plib_item_conversion/");
string 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);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, file), data);
}
private static void EnhanceEvolutionParameters()
{
"Enhancing evolution methods...".Dump();
const string file = "personal_array.bin";
string dest = Path.Combine(DEST_MASTER, "avalon/data/");
string source = Directory.Exists(dest) ? Path.Combine(dest, file) : Path.Combine(PATH_MASTER, "avalon/data/", file);
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(source);
foreach (var t in obj.Table)
{
var evos = t.Evolutions;
if (evos.Any(z => z.Method is (int)EvolutionType.Trade)) // add a Linking Cord alternative
{
PersonalInfoEvolution newEvo = new()
{
Level = 0,
Method = (int)EvolutionType.UseItem,
Argument = 58,
SpeciesInternal = (ushort)(t.Info.SpeciesNational + 1),
Form = t.Info.Form,
};
evos.Add(newEvo);
}
for (int i = 0; i < evos.Count; i++)
{
var evo = evos[i];
if (evo.Method is (int)EvolutionType.TradeHeldItem or (int)EvolutionType.Hisui)
{
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++)
{
// Hisui
if (evo.Argument is 0)
{
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
else if (evo.Argument == lib[p].ItemID)
{
evo.Method = (int)EvolutionType.UseItem;
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;
}
}
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, file), data);
}
private static void EnhanceBlueberryQuests()
{
"Updating Snacksworth BBQ requirements...".Dump();
const string file = "release_poke_array.bin";
string dest = Path.Combine(DEST_MASTER, "world/data/event/s2_sub_012_legend_poke/release_poke/");
string 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);
byte[] 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";
string dest = Path.Combine(DEST_MASTER, "world/data/ui/shop/dressup_shop/dressup_shop_data/");
string 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);
byte[] 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/";
string dirP = "appli/tex/pokedex_capture_thum";
const string dirK = "_dlc1";
const string dirB = "_dlc2";
const string file = "personal_array.bin";
string dest = Path.Combine(DEST_MASTER, "avalon/data/");
string source = Directory.Exists(dest) ? Path.Combine(dest, file) : Path.Combine(PATH_MASTER, "avalon/data/", file);
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(source);
byte GetForm(ushort species) => species is 0128 or 0194 or 0901 ? (byte)1 : (byte)0;
// Pokédex images (1024x1024)
foreach (var t in obj.Table.Where(z => z.IsPresentInGame && z.Info.Form == GetForm(z.Info.SpeciesInternal) && z.Dex is null))
{
int dev = t.Info.SpeciesInternal;
string thumb = $"pokedex_capture_thum_{dev:000}";
string target = $"{DEST_MASTER}{dir}{thumb}.bntx";
string original = Path.Combine(PATH_MASTER, dir) + t switch
{
_ when t.KitakamiDex is not 0 => $"{thumb}_01.bntx",
_ when t.BlueberryDex is not 0 => $"{thumb}_02.bntx",
_ => "pokedex_capture_thum_001.bntx",
};
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 obj.Table.Where(z => z.IsPresentInGame && z.Info.Form == GetForm(z.Info.SpeciesInternal) && z.Dex is null))
{
int dev = t.Info.SpeciesInternal;
string thumb = $"pokedex_capture_thum_{dev:000}";
string targetDir = $"{DEST_MASTER}{dirP}/{thumb}/";
string target = $"{targetDir}{thumb}.bntx";
string original = t switch
{
_ when t.KitakamiDex is not 0 => $"{PATH_MASTER}{dirP}{dirK}/{thumb}/{thumb}.bntx",
_ when t.BlueberryDex is not 0 => $"{PATH_MASTER}{dirP}{dirB}/{thumb}/{thumb}.bntx",
_ => Path.Combine(PATH_MASTER, dir, "pokedex_capture_thum_001.bntx"),
};
if (!Directory.Exists(targetDir))
Directory.CreateDirectory(targetDir);
File.Copy(original, target, true);
}
// Pokédex indexes
foreach (var t in obj.Table.Where(z => z.IsPresentInGame))
t.Dex = new PersonalInfoDex { Index = t.Info.SpeciesNational, Group = 0 };
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, file), data);
WipePokedexBlacklistData();
FillEmptyPokedexEntries();
}
private static void WipePokedexBlacklistData()
{
"Wiping Pokédex blacklist data...".Dump();
const string file = "blacklist_array.bin";
string dest = Path.Combine(DEST_MASTER, "world/data/ui/pokedex/blacklist/");
string 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);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, file), data);
}
private static void FillEmptyPokedexEntries()
{
"Filling in empty Pokédex entries...".Dump();
TextConfig cfg = new(GameVersion.SV);
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++)
{
char ver = i is 0 ? 'A' : 'B';
foreach (string lang in Languages)
{
byte[] 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 (string t in MissingEntries)
{
string name = t;
if (i is 1)
name = name.Replace("_A_", "_B_");
AHTBEntry Entry = new(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
string 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 (AHTBEntry 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++)
{
string lang = Languages[i];
string dest = Path.Combine(DEST_MASTER, $"message/dat/{lang}/common/");
for (int j = 0; j <= 1; j++)
{
char ver = j is 0 ? 'A' : 'B';
string zkn = $"zukan_comment_{ver}";
string[] original = GetStringsCommon(lang, $"zukan_comment_{ver}");
string[] lines = new string[original.Length + MissingEntries.Count];
// 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);
byte[] write = TextFile.GetBytes(lines, cfg);
File.WriteAllBytes(Path.Combine(dest, zkn + ".dat"), write);
}
}
}
private static void UpdateItemSortTables()
{
"Updating item sort tables...".Dump();
foreach (string lang in Languages)
{
Dictionary<ushort, string> ItemDictionary = new();
string[] items = GetStringsCommon(lang, "itemname");
for (ushort j = 0; j < items.Length; j++)
ItemDictionary.Add(j, items[j]);
ItemDictionary = ItemDictionary.OrderBy(z => z.Value).ToDictionary();
string dir = Path.Combine(DEST_MASTER, $"message/sort/{lang}/");
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
string 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 ExtraChanges()
{
if (RANDOMIZE_ROAMING_FORM_GIMMIGHOUL)
{
"Randomizing Roaming Form Gimmighoul...".Dump();
DevID species = GetRandomSpecies(0);
byte form = GetRandomForm(species);
SexType sex = GetRandomGender(species, form);
UpdateOverworldSceneSingle("world/obj_template/parts/coin_symbol/coin_symbol_walk_/coin_symbol_walk_0.trsot", species, form, sex);
}
if (ALL_POKEMON_ONLY_KNOW_METRONOME)
{
"Increasing Metronome base PP...".Dump();
const string file = "waza_array.bin";
string dest = Path.Combine(DEST_MASTER, "avalon/data/");
string source = Directory.Exists(dest) ? Path.Combine(dest, file) : Path.Combine(PATH_MASTER, "avalon/data/", file);
var obj = FlatBufferConverter.DeserializeFrom<WazaTable>(source);
var mov = obj.Table.FirstOrDefault(z => z.MoveID is 118);
mov.PP = 40;
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] data = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, file), data);
}
if (RANDOMIZE_GENERIC_OVERWORLD_POKEMON)
{
"Randomizing all generic overworld Pokémon (this may take a while)...".Dump();
foreach (string t in GenericOverworldPokemonScenes)
UpdateOverworldSceneGeneric(t);
}
}
private static List<DevID> GetSpeciesBanlist()
{
string file = Path.Combine(PATH_MASTER, "avalon/data/personal_array.bin");
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(file);
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();
if (!ALLOW_SPECIES_LEGENDARY) foreach (ushort species in Legendary) Banned.Add((DevID)GetDevID(species));
if (!ALLOW_SPECIES_MYTHICAL) foreach (ushort species in Mythical) Banned.Add((DevID)GetDevID(species));
if (!ALLOW_SPECIES_PARADOX) foreach (ushort species in Paradox) Banned.Add((DevID)GetDevID(species));
foreach (var t in obj.Table.Where(z => z.Info.Form is 0))
{
if (!t.IsPresentInGame)
Banned.Add((DevID)t.Info.SpeciesInternal);
}
Banned = Banned.Distinct().ToList();
return Banned;
}
private static List<DevID> GetSpeciesList() => Enumerable.Range(1, MAX_SPECIES_ID).Select(z => (DevID)z).Except(SpeciesBanlist).ToList();
private static List<int> GetPermittedAbilities()
{
List<int> Banned =
[
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
];
if (BAN_ABILITY_WONDER_GUARD)
Banned.Add(025);
List<int> Abilities = Enumerable.Range(1, MAX_ABILITY_ID).Except(Banned).ToList();
return Abilities;
}
private static List<ushort> GetItemList()
{
string 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 ];
List<int> DisallowedItems = [ 0016, 0111, 0485, 0486, 0487, 0488, 0489, 0490, 0491, 0500, 0708, 0709, 1785, 2351, 2352, 2353, 2354 ];
if (!ALLOW_RANDOM_PICNIC_ITEMS)
DisallowedPockets.Add(FieldPocket.FPOCKET_PICNIC);
// we only want picks and ingredients in the pool; accessories like cups and tablecloths should be capped at 1
else
{
for (int i = 1947; i <= 1955; i++) DisallowedItems.Add(i); // unused, but have item data
for (int i = 2311; i <= 2400; i++) DisallowedItems.Add(i);
for (int i = 2417; i <= 2437; i++) DisallowedItems.Add(i);
DisallowedItems.Add(2551); // Blueberry Tablecloth
DisallowedItems.Add(2552); // 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()
{
string 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()
{
string file = Path.Combine(PATH_MASTER, "avalon/data/waza_array.bin");
var obj = FlatBufferConverter.DeserializeFrom<WazaTable>(file);
List<int> Moves = obj.Table.Where(z => z.CanUseMove).Select(z => (int)z.MoveID).ToList();
return Moves;
}
private static DevID GetRandomSpecies(DevID dev)
{
ushort nat = GetNationalDexID((ushort)dev);
DevID species;
if (FORCE_SWAP_SPECIES_LEGENDARY && Legendary.Contains(nat))
{
List<DevID> Legendaries = Legendary.Select(z => (DevID)z).Except(SpeciesBanlist).ToList();
species = Legendaries[GetRandom(Legendaries.Count)];
return (DevID)GetDevID((ushort)species);
}
if (FORCE_SWAP_SPECIES_MYTHICAL && Mythical.Contains(nat))
{
List<DevID> Mythicals = Mythical.Select(z => (DevID)z).Except(SpeciesBanlist).ToList();
species = Mythicals[GetRandom(Mythicals.Count)];
return (DevID)GetDevID((ushort)species);
}
if (FORCE_SWAP_SPECIES_PARADOX && Paradox.Contains(nat))
{
List<DevID> Paradoxes = Paradox.Select(z => (DevID)z).Except(SpeciesBanlist).ToList();
species = Paradoxes[GetRandom(Paradoxes.Count)];
return (DevID)GetDevID((ushort)species);
}
species = SpeciesList[GetRandom(SpeciesList.Count)];
return species;
}
private static PokeDataFull ForceEvolveSpeciesOnce(PokeDataFull pk)
{
string file = Path.Combine(PATH_MASTER, "avalon/data/personal_array.bin");
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(file);
// does not evolve
var sel = obj.Table.FirstOrDefault(z => z.Info.SpeciesInternal == (ushort)pk.DevId && z.Info.Form == pk.FormId);
if (sel.Evolutions.Count is 0)
return pk;
int evo = GetRandom(sel.Evolutions.Count);
pk.DevId = (DevID)sel.Evolutions[evo].SpeciesInternal;
pk.FormId = (short)sel.Evolutions[evo].Form;
return pk;
}
private static ushort ForceEvolveSpeciesFully(ushort species, byte form)
{
string file = Path.Combine(PATH_MASTER, "avalon/data/personal_array.bin");
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(file);
// does not evolve
var stage1 = obj.Table.FirstOrDefault(z => z.Info.SpeciesInternal == species && z.Info.Form == form);
if (stage1.Evolutions.Count is 0)
return species;
int select1 = GetRandom(stage1.Evolutions.Count);
ushort evoS = stage1.Evolutions[select1].SpeciesInternal;
ushort evoF = stage1.Evolutions[select1].Form;
// two-stage family
var stage2 = obj.Table.FirstOrDefault(z => z.Info.SpeciesInternal == evoS && z.Info.Form == evoF);
if (stage2.Evolutions.Count is 0)
return evoS;
// three-stage family
int select2 = GetRandom(stage2.Evolutions.Count);
return stage2.Evolutions[select2].SpeciesInternal;
}
private static byte GetRandomForm(DevID dev)
{
ushort species = GetNationalDexID((ushort)dev);
byte count = GetFormCount((ushort)dev);
return species switch
{
0025 or 0080 => GetValidForm(species), // Partner Pikachu, Mega Slowbro
0493 when !RANDOMIZE_PERSONAL_ABILITY || !ENSURE_SANE_FORM_HELD_ITEMS => 0, // Arceus
0658 => (byte)GetRandom(2), // Ash-Greninja
0670 => (byte)GetRandom(5), // Eternal Flower Floette
0774 => (byte)GetRandom(7), // Core Form Minior
0800 => (byte)GetRandom(3), // Ultra Necrozma
0890 or 1007 or 1008 => 0, // Eternatus, Koraidon, Miraidon
1017 => (byte)GetRandom(4), // Terastallized Ogerpon
_ when BattleOnlyForms.Contains(species) && !RANDOMIZE_PERSONAL_ABILITY => 0,
_ when UnavailableForms.Contains(species) => 0,
_ => (byte)GetRandom(count),
};
}
private static SexType GetRandomGender(DevID species, short form)
{
string file = Path.Combine(PATH_MASTER, "avalon/data/personal_array.bin");
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(file);
var sel = obj.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)
{
string file = Path.Combine(PATH_MASTER, "avalon/data/personal_array.bin");
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(file);
var sel = obj.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 int GetRandomAbility() => PermittedAbilities[GetRandom(PermittedAbilities.Count)];
private static ItemID GetRandomItem() => (ItemID)ItemList[GetRandom(ItemList.Count)];
private static bool IsFormHeldItemDependent(DevID species) => (ushort)species is 0483 or 0484 or 0487 or 0493 or 0888 or 0889 or 1011;
private static ItemID GetFormSpecificItem(DevID species, short form) => (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),
1011 => 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 ];
return (ItemID)Masks[form];
}
private static List<int> GetRandomMoveList()
{
List<int> Banned =
[
000, // ———
165, // Struggle
464, // Dark Void
621, // Hyperspace Fury
783, // Aura Wheel
896, // Blazing Torque
897, // Wicked Torque
898, // Noxious Torque
899, // Combat Torque
900, // Magical Torque
];
List<int> Moves = PermittedMoves.Except(Banned).ToList();
pkNX.Randomization.Util.Shuffle(Moves);
return Moves;
}
private static byte GetValidForm(ushort species)
{
byte count = GetFormCount(species);
byte banned = species is 0025 ? (byte)8 : (byte)1;
byte form = (byte)GetRandom(count - 1);
if (form == banned)
form++;
return form;
}
private static GemType GetTeraType(DevID species, byte form, GemType gem, bool raid)
{
// Ogerpon Tera Type must match form
if (species is DevID.DEV_KAMENONI)
{
return form switch
{
1 => GemType.MIZU, // Wellspring Mask
2 => GemType.HONOO, // Hearthflame Mask
3 => GemType.IWA, // Cornerstone Mask
_ => GemType.KUSA, // Teal Mask
};
}
// Terapagos can only have Stellar
if (species is DevID.DEV_KODAIGAME)
return GemType.NIJI;
// raids must be random
if (raid)
return GemType.RANDOM;
// abide by standard type
if (!ALLOW_FULLY_RANDOM_TERA_TYPES)
return 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 (GemType)GetRandom(2, 20);
}
private static byte GetFormCount(ushort species)
{
string file = Path.Combine(PATH_MASTER, "avalon/data/personal_array.bin");
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(file);
byte count = (byte)obj.Table.Where(z => z.Info.SpeciesInternal == species).ToArray().Length;
return count;
}
private static bool GetIsPresentInGame(ushort species, byte form)
{
string file = Path.Combine(PATH_MASTER, "avalon/data/personal_array.bin");
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(file);
var sel = obj.Table.FirstOrDefault(z => z.Info.SpeciesInternal == species && z.Info.Form == form);
return sel.IsPresentInGame;
}
private static bool IsValidForm(ushort species, byte form) => GetNationalDexID(species) switch
{
// can only be a different type if it does not have Multitype, or it is holding the correct plate
0493 when form is not 0 => RANDOMIZE_PERSONAL_ABILITY || ENSURE_SANE_FORM_HELD_ITEMS,
// handled by BibiyonFormNoSave_formNo
0664 or 0665 or 0666 when form is not 18 => false, // Scatterbug, Spewpa, Vivillon
// form change is dependent on a signature ability
0774 when form is >= 7 => RANDOMIZE_PERSONAL_ABILITY, // Minior
0778 when form is not 0 => RANDOMIZE_PERSONAL_ABILITY, // Mimikyu
0845 when form is not 0 => RANDOMIZE_PERSONAL_ABILITY, // Cramorant
0875 when form is not 0 => RANDOMIZE_PERSONAL_ABILITY, // Eiscue
0877 when form is not 0 => RANDOMIZE_PERSONAL_ABILITY, // Morpeko
0964 when form is not 0 => RANDOMIZE_PERSONAL_ABILITY, // Palafin
// no proper animations
1007 or 1008 when form is not 0 => false, // Koraidon, Miraidon
// safeguards for invalid Tera Types
1017 when form is not 0 => false, // Ogerpon
_ => true,
};
private static ushort GetDevID(ushort species)
{
string file = Path.Combine(PATH_MASTER, "avalon/data/personal_array.bin");
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(file);
var sel = obj.Table.FirstOrDefault(z => z.Info.SpeciesNational == species);
return sel.Info.SpeciesInternal;
}
private static ushort GetNationalDexID(ushort species)
{
string file = Path.Combine(PATH_MASTER, "avalon/data/personal_array.bin");
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(file);
var sel = obj.Table.FirstOrDefault(z => z.Info.SpeciesInternal == species);
return sel.Info.SpeciesNational;
}
private static byte GetMoveType(ushort move)
{
const string file = "waza_array.bin";
string dest = Path.Combine(DEST_MASTER, "avalon/data/");
string source = File.Exists(Path.Combine(dest, file)) ? Path.Combine(dest, file) : Path.Combine(PATH_MASTER, "avalon/data/", file);
var obj = FlatBufferConverter.DeserializeFrom<WazaTable>(source);
var sel = obj.Table.FirstOrDefault(z => z.MoveID == move);
return sel.Type;
}
private static List<ushort> GetCurrentMoves(ushort species, byte form, byte level)
{
const string file = "personal_array.bin";
string dest = Path.Combine(DEST_MASTER, "avalon/data/");
string source = Directory.Exists(dest) ? Path.Combine(dest, file) : Path.Combine(PATH_MASTER, "avalon/data/", file);
var obj = FlatBufferConverter.DeserializeFrom<PersonalTable>(source);
var sel = obj.Table.FirstOrDefault(z => z.Info.SpeciesNational == species && z.Info.Form == form);
List<ushort> SelfFaintMoves = [ 120, 153, 262, 361, 461, 515, 802 ];
List<ushort> Moves = sel.Learnset.Where(z => !SelfFaintMoves.Contains(z.Move) && z.Move is not 906 && z.Level <= level).Distinct().TakeLast(4).Select(z => 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"));
string source = file.StartsWith("item") && exists ? DEST_MASTER : PATH_MASTER;
string path = Path.Combine(source, $"message/dat/{lang}/common/{file}.dat");
byte[] data = File.ReadAllBytes(path);
TextConfig cfg = new(GameVersion.SV);
return new TextFile(data, cfg).Lines;
}
private static string[] GetStringsScript(string lang, string file)
{
string path = Path.Combine(PATH_MASTER, $"message/dat/{lang}/script/{file}.dat");
byte[] data = File.ReadAllBytes(path);
TextConfig cfg = new(GameVersion.SV);
return new TextFile(data, cfg).Lines;
}
private static string GetStringFromAHTBCommon(string file, ulong hash, string lang)
{
string path = Path.Combine(PATH_MASTER, $"message/dat/{lang}/common/{file}.dat");
string ahtb = Path.Combine(PATH_MASTER, $"message/dat/{lang}/common/{file}.tbl");
byte[] data = File.ReadAllBytes(path);
TextConfig cfg = new(GameVersion.SV);
string[] text = new TextFile(data, cfg).Lines;
byte[] dataAHTB = File.ReadAllBytes(ahtb);
List<AHTBEntry> Entries = new AHTB(dataAHTB).Entries.ToList();
AHTBEntry target = Entries.FirstOrDefault(z => z.Hash == hash);
int index = Entries.IndexOf(target);
if (target is null)
return "INVALID";
return text[index];
}
private static string GetStringFromAHTBScript(string file, ulong hash, string lang)
{
string path = Path.Combine(PATH_MASTER, $"message/dat/{lang}/script/{file}.dat");
string ahtb = Path.Combine(PATH_MASTER, $"message/dat/{lang}/script/{file}.tbl");
byte[] data = File.ReadAllBytes(path);
TextConfig cfg = new(GameVersion.SV);
string[] text = new TextFile(data, cfg).Lines;
byte[] dataAHTB = File.ReadAllBytes(ahtb);
List<AHTBEntry> Entries = new AHTB(dataAHTB).Entries.ToList();
AHTBEntry target = Entries.FirstOrDefault(z => z.Hash == hash);
int index = Entries.IndexOf(target);
if (target is null)
return "INVALID";
return text[index];
}
private static void UpdateOverworldSceneSingle(string scene, DevID species, short form, SexType sex)
{
int index = scene.LastIndexOf("/") + 1;
string f = scene[index..];
string d = scene[..index];
string dest = Path.Combine(DEST_MASTER, d);
string path = Path.Combine(PATH_MASTER, d);
byte[] 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"))
{
var pmc = FlatBufferConverter.DeserializeFrom<TIPokemonModelComponent>(s.Data);
pmc.DevId = (ushort)species;
pmc.FormId = (byte)form;
pmc.Sex = (sbyte)GetSceneGender((ushort)species, form, (byte)sex);
s.Data = FlatBufferConverter.SerializeFrom(pmc);
}
foreach (var s in o.SubObjects.Where(z => z.Type is "ti_FieldPokemonComponent"))
{
var fpc = FlatBufferConverter.DeserializeFrom<TIFieldPokemonComponent>(s.Data);
fpc.DevId = (ushort)species;
fpc.FormId = (byte)form;
fpc.Sex = (sbyte)GetSceneGender((ushort)species, form, (byte)sex);
s.Data = FlatBufferConverter.SerializeFrom(fpc);
}
}
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] write = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, f), write);
File.WriteAllBytes(Path.Combine(dest, f.Replace("_0.", "_1.")), write);
}
private static void UpdateOverworldSceneMulti(string scene, List<DevID> OldSpecies, List<DevID> NewSpecies, List<short> Forms, List<SexType> Sex, bool IsNushiDragon)
{
int index = scene.LastIndexOf("/") + 1;
string f = scene[index..];
string d = scene[..index];
string dest = Path.Combine(DEST_MASTER, d);
string path = IsNushiDragon ? dest : Path.Combine(PATH_MASTER, d);
byte[] 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"))
{
var pmc = FlatBufferConverter.DeserializeFrom<TIPokemonModelComponent>(s.Data);
for (int i = 0; i < OldSpecies.Count; i++)
{
if (pmc.DevId == (ushort)OldSpecies[i])
{
pmc.DevId = (ushort)NewSpecies[i];
pmc.FormId = (byte)Forms[i];
pmc.Sex = (sbyte)GetSceneGender((ushort)NewSpecies[i], Forms[i], (byte)Sex[i]);
}
}
s.Data = FlatBufferConverter.SerializeFrom(pmc);
}
foreach (var s in o.SubObjects.Where(z => z.Type is "ti_FieldPokemonComponent"))
{
var fpc = FlatBufferConverter.DeserializeFrom<TIFieldPokemonComponent>(s.Data);
for (int i = 0; i < OldSpecies.Count; i++)
{
if (fpc.DevId == (ushort)OldSpecies[i])
{
fpc.DevId = (ushort)NewSpecies[i];
fpc.FormId = (byte)Forms[i];
fpc.Sex = (sbyte)GetSceneGender((ushort)NewSpecies[i], Forms[i], (byte)Sex[i]);
}
}
s.Data = FlatBufferConverter.SerializeFrom(fpc);
}
}
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] write = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, f), write);
File.WriteAllBytes(Path.Combine(dest, f.Replace("_0.", "_1.")), write);
}
private static void UpdateOverworldSceneGeneric(string scene)
{
int index = scene.LastIndexOf("/") + 1;
string f = scene[index..];
string d = scene[..index];
string dest = Path.Combine(DEST_MASTER, d);
string path = Path.Combine(PATH_MASTER, d);
byte[] data = File.ReadAllBytes(Path.Combine(path, f));
var obj = FlatBufferConverter.DeserializeFrom<TrinitySceneObjectTemplate>(data);
int count = 0;
int 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++;
}
List<DevID> Species = new();
List<short> Forms = new();
List<SexType> Sex = new();
for (int i = 0; i <= count; i++)
{
DevID rSpecies = GetRandomSpecies(0);
short rForm = GetRandomForm(rSpecies);
SexType rGender = GetRandomGender(rSpecies, rForm);
Species.Add(rSpecies);
Forms.Add(rForm);
Sex.Add(rGender);
}
foreach (var o in obj.Objects)
{
foreach (var s in o.SubObjects.Where(z => z.Type is "ti_PokemonModelComponent" or "ti_FieldPokemonComponent"))
{
var pmc = FlatBufferConverter.DeserializeFrom<TIPokemonModelComponent>(s.Data);
for (int i = 0; i < Species.Count; i++)
{
pmc.DevId = (ushort)Species[ctr];
pmc.FormId = (byte)Forms[ctr];
pmc.Sex = (sbyte)GetSceneGender((ushort)Species[ctr], Forms[ctr], (byte)Sex[ctr]);
pmc.Rare = GetRandom(100) < GENERIC_OVERWORLD_POKEMON_SHINY_CHANCE_PERCENT ? (sbyte)1 : (sbyte)0;
}
ctr++;
s.Data = FlatBufferConverter.SerializeFrom(pmc);
}
}
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] write = FlatBufferConverter.SerializeFrom(obj);
File.WriteAllBytes(Path.Combine(dest, f), write);
File.WriteAllBytes(Path.Combine(dest, f.Replace("_0.", "_1.")), write);
}
private static void UpdateBossText(List<DevID> Bosses)
{
TextConfig cfg = new(GameVersion.SV);
foreach (string lang in Languages)
{
string dest = Path.Combine(DEST_MASTER, $"message/dat/{lang}/script/");
string[] names = GetStringsCommon(lang, "monsname");
string[] lines = GetStringsScript(lang, "futatsuna");
lines[08] = names[(int)Bosses[0]];
lines[09] = names[(int)Bosses[1]];
lines[10] = names[(int)Bosses[2]];
lines[11] = names[(int)Bosses[3]];
lines[12] = names[(int)Bosses[4]];
lines[13] = names[(int)Bosses[5]];
lines[14] = names[(int)Bosses[5]];
lines[15] = names[(int)Bosses[6]];
lines[16] = names[(int)Bosses[7]];
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
byte[] write = TextFile.GetBytes(lines, cfg);
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;
if (ENSURE_SANE_FORM_HELD_ITEMS)
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<ushort> Moves = GetCurrentMoves((ushort)t.DevId, (byte)t.FormId, (byte)t.Level);
t.WazaType = WazaType.MANUAL;
t.Waza1.WazaId = (WazaID)Moves[0];
t.Waza2.WazaId = (WazaID)Moves[1];
t.Waza3.WazaId = (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
int 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
return t;
}
private static PokeDataEventBattle ApplyTerapagosSoftlockPrevention(PokeDataEventBattle t)
{
// Tera Starstorm for Terapagos, Tera Blast for all others
WazaID tera = t.DevId is DevID.DEV_KODAIGAME ? WazaID.WAZA_TERAKURASUTAA : WazaID.WAZA_TERABAASUTO;
List<ushort> Moves = GetCurrentMoves((ushort)t.DevId, (byte)t.FormId, (byte)t.Level);
// always put Tera Blast/Starstorm in final move slot
int numEmptyMoves = Moves.Count(z => z is 0);
if (numEmptyMoves is 3) Moves[1] = (ushort)tera; Moves[2] = Moves[3] = 0;
if (numEmptyMoves is 2) Moves[2] = (ushort)tera; Moves[3] = 0;
if (numEmptyMoves is 1) Moves[3] = (ushort)tera;
t.WazaType = WazaType.MANUAL;
t.Waza1.WazaId = (WazaID)Moves[0];
t.Waza2.WazaId = (WazaID)Moves[1];
t.Waza3.WazaId = (WazaID)Moves[2];
t.Waza4.WazaId = (WazaID)Moves[3];
return t;
}
private static EventBattlePokemonArray StandardizeStaticEncounters(EventBattlePokemonArray obj)
{
var table = obj.Table;
// Scream Tail / Iron Bundle
table[001].PokeData.DevId = table[002].PokeData.DevId;
table[001].PokeData.FormId = table[002].PokeData.FormId;
table[001].PokeData.Sex = table[002].PokeData.Sex;
table[001].PokeData.Item = table[002].PokeData.Item;
table[001].PokeData.GemType = table[002].PokeData.GemType;
// Great Tusk / Iron Treads (Area Zero)
table[003].PokeData.DevId = table[004].PokeData.DevId;
table[003].PokeData.FormId = table[004].PokeData.FormId;
table[003].PokeData.Sex = table[004].PokeData.Sex;
table[003].PokeData.Item = table[004].PokeData.Item;
table[003].PokeData.GemType = table[004].PokeData.GemType;
// Great Tusk, Brute Bonnet, Flutter Mane / Iron Treads, Iron Hands, Iron Jugulis
table[005].PokeData.DevId = table[008].PokeData.DevId;
table[005].PokeData.FormId = table[008].PokeData.FormId;
table[005].PokeData.Sex = table[008].PokeData.Sex;
table[005].PokeData.Item = table[008].PokeData.Item;
table[005].PokeData.GemType = table[008].PokeData.GemType;
table[006].PokeData.DevId = table[009].PokeData.DevId;
table[006].PokeData.FormId = table[009].PokeData.FormId;
table[006].PokeData.Sex = table[009].PokeData.Sex;
table[006].PokeData.Item = table[009].PokeData.Item;
table[006].PokeData.GemType = table[009].PokeData.GemType;
table[007].PokeData.DevId = table[010].PokeData.DevId;
table[007].PokeData.FormId = table[010].PokeData.FormId;
table[007].PokeData.Sex = table[010].PokeData.Sex;
table[007].PokeData.Item = table[010].PokeData.Item;
table[007].PokeData.GemType = table[010].PokeData.GemType;
// Gimmighoul
for (int i = 012; i <= 023; i++)
{
table[i].PokeData.DevId = table[011].PokeData.DevId;
table[i].PokeData.FormId = table[011].PokeData.FormId;
table[i].PokeData.Sex = table[011].PokeData.Sex;
table[i].PokeData.Item = table[011].PokeData.Item;
table[i].PokeData.GemType = table[011].PokeData.GemType;
}
// Sunflora
for (int i = 027; i <= 030; i++)
{
table[i].PokeData.DevId = table[026].PokeData.DevId;
table[i].PokeData.FormId = table[026].PokeData.FormId;
table[i].PokeData.Sex = table[026].PokeData.Sex;
table[i].PokeData.Item = table[026].PokeData.Item;
table[i].PokeData.GemType = table[026].PokeData.GemType;
}
// Dondozo
table[033].PokeData.DevId = table[034].PokeData.DevId;
table[033].PokeData.FormId = table[034].PokeData.FormId;
table[033].PokeData.Sex = table[034].PokeData.Sex;
table[033].PokeData.Item = table[034].PokeData.Item;
table[033].PokeData.GemType = table[034].PokeData.GemType;
// Orthworm
table[035].PokeData.DevId = table[036].PokeData.DevId;
table[035].PokeData.FormId = table[036].PokeData.FormId;
table[035].PokeData.Sex = table[036].PokeData.Sex;
table[035].PokeData.Item = table[036].PokeData.Item;
table[035].PokeData.GemType = table[036].PokeData.GemType;
// Bombirdier
table[041].PokeData.DevId = table[042].PokeData.DevId;
table[041].PokeData.FormId = table[042].PokeData.FormId;
table[041].PokeData.Sex = table[042].PokeData.Sex;
table[041].PokeData.Item = table[042].PokeData.Item;
table[041].PokeData.GemType = table[042].PokeData.GemType;
// Klawf
table[043].PokeData.DevId = table[044].PokeData.DevId;
table[043].PokeData.FormId = table[044].PokeData.FormId;
table[043].PokeData.Sex = table[044].PokeData.Sex;
table[043].PokeData.Item = table[044].PokeData.Item;
table[043].PokeData.GemType = table[044].PokeData.GemType;
// Great Tusk / Iron Treads (Titan)
table[045].PokeData.DevId = table[046].PokeData.DevId = table[047].PokeData.DevId = table[048].PokeData.DevId;
table[045].PokeData.FormId = table[046].PokeData.FormId = table[047].PokeData.FormId = table[048].PokeData.FormId;
table[045].PokeData.Sex = table[046].PokeData.Sex = table[047].PokeData.Sex = table[048].PokeData.Sex;
table[045].PokeData.Item = table[046].PokeData.Item = table[047].PokeData.Item = table[048].PokeData.Item;
table[045].PokeData.GemType = table[046].PokeData.GemType = table[047].PokeData.GemType = table[048].PokeData.GemType;
// Munkidori
table[053].PokeData.DevId = table[054].PokeData.DevId = table[057].PokeData.DevId = table[072].PokeData.DevId = table[073].PokeData.DevId;
table[053].PokeData.FormId = table[054].PokeData.FormId = table[057].PokeData.FormId = table[072].PokeData.FormId = table[073].PokeData.FormId;
table[053].PokeData.Sex = table[054].PokeData.Sex = table[057].PokeData.Sex = table[072].PokeData.Sex = table[073].PokeData.Sex;
table[053].PokeData.Item = table[054].PokeData.Item = table[057].PokeData.Item = table[072].PokeData.Item = table[073].PokeData.Item;
table[053].PokeData.GemType = table[054].PokeData.GemType = table[057].PokeData.GemType = table[072].PokeData.GemType = table[073].PokeData.GemType;
// Okidogi
table[055].PokeData.DevId = table[068].PokeData.DevId = table[069].PokeData.DevId;
table[055].PokeData.FormId = table[068].PokeData.FormId = table[069].PokeData.FormId;
table[055].PokeData.Sex = table[068].PokeData.Sex = table[069].PokeData.Sex;
table[055].PokeData.Item = table[068].PokeData.Item = table[069].PokeData.Item;
table[055].PokeData.GemType = table[068].PokeData.GemType = table[069].PokeData.GemType;
// Fezandipiti
table[056].PokeData.DevId = table[070].PokeData.DevId = table[071].PokeData.DevId;
table[056].PokeData.FormId = table[070].PokeData.FormId = table[071].PokeData.FormId;
table[056].PokeData.Sex = table[070].PokeData.Sex = table[071].PokeData.Sex;
table[056].PokeData.Item = table[070].PokeData.Item = table[071].PokeData.Item;
table[056].PokeData.GemType = table[070].PokeData.GemType = table[071].PokeData.GemType;
// Ogerpon
for (int i = 059; i <= 065; i++)
{
table[i].PokeData.DevId = table[058].PokeData.DevId;
table[i].PokeData.FormId = table[058].PokeData.FormId;
table[i].PokeData.Sex = table[058].PokeData.Sex;
table[i].PokeData.Item = table[058].PokeData.Item;
table[i].PokeData.GemType = table[058].PokeData.GemType;
}
// Milotic
table[066].PokeData.DevId = table[067].PokeData.DevId;
table[066].PokeData.FormId = table[067].PokeData.FormId;
table[066].PokeData.Sex = table[067].PokeData.Sex;
table[066].PokeData.Item = table[067].PokeData.Item;
table[066].PokeData.GemType = table[067].PokeData.GemType;
// Sandy Shocks / Iron Thorns
table[082].PokeData.DevId = table[083].PokeData.DevId;
table[082].PokeData.FormId = table[083].PokeData.FormId;
table[082].PokeData.Sex = table[083].PokeData.Sex;
table[082].PokeData.Item = table[083].PokeData.Item;
table[082].PokeData.GemType = table[083].PokeData.GemType;
// Terapagos
table[087].PokeData.DevId = table[088].PokeData.DevId;
table[087].PokeData.FormId = table[088].PokeData.FormId;
table[087].PokeData.Sex = table[088].PokeData.Sex;
table[087].PokeData.Item = table[088].PokeData.Item;
table[087].PokeData.GemType = table[088].PokeData.GemType;
if (RANDOMIZE_KORAIDON_MIRAIDON)
{
table[031].PokeData.DevId = table[032].PokeData.DevId = table[115].PokeData.DevId = table[116].PokeData.DevId;
table[031].PokeData.FormId = table[032].PokeData.FormId = table[115].PokeData.FormId = table[116].PokeData.FormId;
table[031].PokeData.Sex = table[032].PokeData.Sex = table[115].PokeData.Sex = table[116].PokeData.Sex;
table[031].PokeData.Tokusei = table[032].PokeData.Tokusei = table[115].PokeData.Tokusei = table[116].PokeData.Tokusei;
table[031].PokeData.Item = table[032].PokeData.Item = table[115].PokeData.Item = table[116].PokeData.Item;
table[031].PokeData.GemType = table[032].PokeData.GemType = table[115].PokeData.GemType = table[116].PokeData.GemType;
table[031].PokeData.WazaType = table[032].PokeData.WazaType = table[115].PokeData.WazaType = table[116].PokeData.WazaType;
table[031].PokeData.Waza1 = table[032].PokeData.Waza1 = table[115].PokeData.Waza1 = table[116].PokeData.Waza1;
table[031].PokeData.Waza2 = table[032].PokeData.Waza2 = table[115].PokeData.Waza2 = table[116].PokeData.Waza2;
table[031].PokeData.Waza3 = table[032].PokeData.Waza3 = table[115].PokeData.Waza3 = table[116].PokeData.Waza3;
table[031].PokeData.Waza4 = table[032].PokeData.Waza4 = table[115].PokeData.Waza4 = table[116].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.Tokusei = tableG[006].PokeData.Tokusei = tableS[116].PokeData.Tokusei;
tableG[005].PokeData.Item = tableG[006].PokeData.Item = tableS[116].PokeData.Item;
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;
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.Tokusei = turo.Tokusei = tableS[116].PokeData.Tokusei;
sada.Item = turo.Item = tableS[116].PokeData.Item;
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 = RareType.NO_RARE;
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.Tokusei = tableS[088].PokeData.Tokusei;
tera.Item = tableS[088].PokeData.Item;
tera.RareType = RareType.NO_RARE;
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); // 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); // 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); // 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); // 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); // 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); // Sunflora
// Orthworm
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);
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);
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);
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);
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);
// 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);
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);
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);
// 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);
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);
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);
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);
// 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);
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);
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);
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);
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);
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);
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);
// 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);
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);
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);
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);
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);
// 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);
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);
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);
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);
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);
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);
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);
// 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);
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);
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);
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);
// 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); // 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); // 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); // 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); // 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); // 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);
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);
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);
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);
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);
// 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);
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);
// Terapagos
UpdateOverworldSceneSingle("world/scene/parts/demo/ev/d730_/d730_0.trscn", table[088].PokeData.DevId, table[088].PokeData.FormId, table[088].PokeData.Sex);
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);
// 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);
// 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++)
{
DevID dev = table[Snacksworth[k]].PokeData.DevId;
short form = table[Snacksworth[k]].PokeData.FormId;
SexType sex = table[Snacksworth[k]].PokeData.Sex;
UpdateOverworldSceneSingle($"world/scene/parts/event/event_scenario/sub_scenario/s2_sub_{i:000}_/s2_sub_{i:000}_pre_start_0.trsog", dev, form, sex);
}
// Area Zero Paradox Pokémon Gauntlet (Great Tusk/Iron Treads ×2, Brute Bonnet/Iron Hands ×5, Flutter Mane/Iron Jugulis ×1)
List<DevID> ParadoxGauntletOld = [ DevID.DEV_ADONFAN, DevID.DEV_AMOROBARERU, DevID.DEV_AMUUMA, DevID.DEV_AMOROBARERU, DevID.DEV_AMOROBARERU, DevID.DEV_ADONFAN, DevID.DEV_AMOROBARERU, DevID.DEV_AMOROBARERU ];
List<DevID> ParadoxGauntletNew = [ table[008].PokeData.DevId, table[009].PokeData.DevId, table[010].PokeData.DevId, table[009].PokeData.DevId, table[009].PokeData.DevId, table[008].PokeData.DevId, table[009].PokeData.DevId, table[009].PokeData.DevId ];
List<short> ParadoxGauntletForm = [ table[008].PokeData.FormId, table[009].PokeData.FormId, table[010].PokeData.FormId, table[009].PokeData.FormId, table[009].PokeData.FormId, table[008].PokeData.FormId, table[009].PokeData.FormId, table[009].PokeData.FormId ];
List<SexType> ParadoxGauntletSex = [ table[008].PokeData.Sex, table[009].PokeData.Sex, table[010].PokeData.Sex, table[009].PokeData.Sex, table[009].PokeData.Sex, table[008].PokeData.Sex, table[009].PokeData.Sex, table[009].PokeData.Sex ];
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/common_1170_/common_1170_always_0.trsog", ParadoxGauntletOld, ParadoxGauntletNew, ParadoxGauntletForm, ParadoxGauntletSex, false);
// Poco Path Catching Tutorial (Lechonk ×5, Tarountula ×4, Pawmi ×1, Fletchling ×2)
DevID Tarountula = GetRandomSpecies(0);
DevID Pawmi = GetRandomSpecies(0);
DevID Fletchling = GetRandomSpecies(0);
List<DevID> PocoPathOld = [ DevID.DEV_BUTA1, DevID.DEV_BUTA1, DevID.DEV_BUTA1, DevID.DEV_BUTA1, DevID.DEV_BUTA1, DevID.DEV_KUMO1, DevID.DEV_KUMO1, DevID.DEV_KUMO1, DevID.DEV_KUMO1, DevID.DEV_MAAMOTTO1, DevID.DEV_YAYAKOMA, DevID.DEV_YAYAKOMA ];
List<DevID> PocoPathNew = [ table[024].PokeData.DevId, table[024].PokeData.DevId, table[024].PokeData.DevId, table[024].PokeData.DevId, table[024].PokeData.DevId, (DevID)Tarountula, (DevID)Tarountula, (DevID)Tarountula, (DevID)Tarountula, (DevID)Pawmi, (DevID)Fletchling, (DevID)Fletchling ];
List<short> PocoPathForm = [ table[024].PokeData.FormId, table[024].PokeData.FormId, table[024].PokeData.FormId, table[024].PokeData.FormId, table[024].PokeData.FormId, 0, 0, 0, 0, 0, 0, 0 ];
List<SexType> PocoPathSex = [ table[024].PokeData.Sex, table[024].PokeData.Sex, table[024].PokeData.Sex, table[024].PokeData.Sex, table[024].PokeData.Sex, 0, 0, 0, 0, 0, 0, 0 ];
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/common_0100_/common_0100_main_0.trsog", PocoPathOld, PocoPathNew, PocoPathForm, PocoPathSex, false);
// Casseroya Lake
List<DevID> CasseroyaOld = [ DevID.DEV_OYAKATA, DevID.DEV_SUSIDORA, DevID.DEV_YOKUBARISU ];
List<DevID> CasseroyaNew = [ table[034].PokeData.DevId, table[037].PokeData.DevId, DevID.DEV_YOKUBARISU ];
List<short> CasseroyaForm = [ table[034].PokeData.FormId, table[037].PokeData.FormId, 0 ];
List<SexType> CasseroyaSex = [ table[034].PokeData.Sex, table[037].PokeData.Sex, 0 ];
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/nushi_dragon_020_/nushi_dragon_020_pre_start_0.trsog", CasseroyaOld, CasseroyaNew, CasseroyaForm, CasseroyaSex, false);
}
private static void UpdateAllGiftScenes(EventAddPokemonArray obj)
{
var table = obj.Table;
List<DevID> StarterOld = [ DevID.DEV_NEKO1, DevID.DEV_WANI1, DevID.DEV_KAMO1 ];
List<DevID> StarterNew = [ table[001].PokeData.DevId, table[000].PokeData.DevId, table[002].PokeData.DevId ];
List<short> StarterForm = [ table[001].PokeData.FormId, table[000].PokeData.FormId, table[002].PokeData.FormId ];
List<SexType> StarterSex = [ table[001].PokeData.Sex, table[000].PokeData.Sex, table[002].PokeData.Sex ];
// Starter Pokémon
UpdateOverworldSceneMulti("world/scene/parts/demo/ev/d030_/d030_0.trscn", StarterOld, StarterNew, StarterForm, StarterSex, false);
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/common_0060_/common_0060_always_0.trsog", StarterOld, StarterNew, StarterForm, StarterSex, false);
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/common_0060_/common_0060_main_0.trsog", StarterOld, StarterNew, StarterForm, StarterSex, false);
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/common_0070_/common_0070_always_0.trsog", StarterOld, StarterNew, StarterForm, StarterSex, false);
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/common_0088_/common_0088_always_0.trsog", StarterOld, StarterNew, StarterForm, StarterSex, false);
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/common_0090_/common_0090_main_0.trsog", StarterOld, StarterNew, StarterForm, StarterSex, 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);
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);
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);
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);
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);
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);
// 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;
DevID iguanaDev = table[005].PokeData.DevId;
short iguanaForm = table[005].PokeData.FormId;
SexType iguanaSex = table[005].PokeData.Sex;
List<string> IguanaScenes =
[
"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 (string t in IguanaScenes)
UpdateOverworldSceneSingle(t, iguanaDev, iguanaForm, iguanaSex);
List<DevID> InletGrottoOld = [ DevID.DEV_AIGUANA, DevID.DEV_DERUBIRU, DevID.DEV_DERUBIRU, DevID.DEV_DERUBIRU, DevID.DEV_DERUBIRU, DevID.DEV_DERUBIRU, DevID.DEV_DERUBIRU, DevID.DEV_DERUBIRU, DevID.DEV_DERUBIRU ];
List<DevID> InletGrottoNew = [ iguanaDev ];
List<short> InletGrottoForm = [ iguanaForm ];
List<SexType> InletGrottoSex = [ iguanaSex ];
for (int i = 0; i < 8; i++)
{
if (RANDOMIZE_GENERIC_OVERWORLD_POKEMON)
{
DevID species = GetRandomSpecies(0);
short form = GetRandomForm(species);
SexType sex = GetRandomGender(species, form);
InletGrottoNew.Add(species);
InletGrottoForm.Add(form);
InletGrottoSex.Add(sex);
}
else
{
InletGrottoNew.Add(DevID.DEV_DERUBIRU);
InletGrottoForm.Add(0);
InletGrottoSex.Add(0);
}
}
UpdateOverworldSceneMulti("world/scene/parts/demo/ev/d050_/d050_0.trscn", InletGrottoOld, InletGrottoNew, InletGrottoForm, InletGrottoSex, false);
DevID MabosstiffSpecies = RANDOMIZE_GENERIC_OVERWORLD_POKEMON ? GetRandomSpecies(0) : DevID.DEV_MASUTHIHU2;
short MabosstiffForm = RANDOMIZE_GENERIC_OVERWORLD_POKEMON ? GetRandomForm(MabosstiffSpecies) : (short)0;
SexType MabosstiffSex = RANDOMIZE_GENERIC_OVERWORLD_POKEMON ? GetRandomGender(MabosstiffSpecies, MabosstiffForm) : 0;
List<DevID> PathOfLegendsOld = [ DevID.DEV_AIGUANA, DevID.DEV_MASUTHIHU2 ];
List<DevID> PathOfLegendsNew = [ iguanaDev, MabosstiffSpecies ];
List<short> PathOfLegendsForm = [ iguanaForm, MabosstiffForm ];
List<SexType> PathOfLegendsSex = [ iguanaSex, MabosstiffSex ];
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/legend_0030_/legend_0030_main_0.trsog", PathOfLegendsOld, PathOfLegendsNew, PathOfLegendsForm, PathOfLegendsSex, false);
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/legend_0040_/legend_0040_main_0.trsog", PathOfLegendsOld, PathOfLegendsNew, PathOfLegendsForm, PathOfLegendsSex, false);
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/legend_0050_/legend_0050_main_0.trsog", PathOfLegendsOld, PathOfLegendsNew, PathOfLegendsForm, PathOfLegendsSex, false);
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/legend_0060_/legend_0060_always_0.trsog", PathOfLegendsOld, PathOfLegendsNew, PathOfLegendsForm, PathOfLegendsSex, false);
List<DevID> CrystalPoolOld = [ DevID.DEV_AIGUANA, DevID.DEV_KODAIGAME ];
List<DevID> CrystalPoolNew = [ iguanaDev, DevID.DEV_KODAIGAME ];
List<short> CrystalPoolForm = [ iguanaForm, 0 ];
List<SexType> CrystalPoolSex = [ iguanaSex, 0 ];
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/sub_scenario/s2_sub_042_/s2_sub_042_always_0.trsog", CrystalPoolOld, CrystalPoolNew, CrystalPoolForm, CrystalPoolSex, 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;
DevID OkidogiDev = table[069].PokeData.DevId;
short OkidogiForm = table[069].PokeData.FormId;
SexType OkidogiSex = table[069].PokeData.Sex;
DevID MunkidoriDev = table[073].PokeData.DevId;
short MunkidoriForm = table[073].PokeData.FormId;
SexType MunkidoriSex = table[073].PokeData.Sex;
DevID FezandipitiDev = table[071].PokeData.DevId;
short FezandipitiForm = table[071].PokeData.FormId;
SexType FezandipitiSex = table[071].PokeData.Sex;
DevID OgerponDev = table[065].PokeData.DevId;
short OgerponForm = table[065].PokeData.FormId;
SexType OgerponSex = table[065].PokeData.Sex;
List<DevID> KitakamiLegendsOld = [ DevID.DEV_KAMENONI, DevID.DEV_DOKUINU, DevID.DEV_DOKUZARU, DevID.DEV_DOKUKIZI ];
List<DevID> KitakamiLegendsNew = [ OgerponDev, OkidogiDev, MunkidoriDev, FezandipitiDev ];
List<short> KitakamiLegendsForm = [ OgerponForm, OkidogiForm, MunkidoriForm, FezandipitiForm ];
List<SexType> KitakamiLegendsSex = [ OgerponSex, OkidogiSex, MunkidoriSex, FezandipitiSex ];
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/sdc01_0330_/sdc01_0330_always_0.trsog", KitakamiLegendsOld, KitakamiLegendsNew, KitakamiLegendsForm, KitakamiLegendsSex, false);
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/sdc01_0360_/sdc01_0360_pre_start_0.trsog", KitakamiLegendsOld, KitakamiLegendsNew, KitakamiLegendsForm, KitakamiLegendsSex, false);
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/sdc01_0400_/sdc01_0400_main_0.trsog", KitakamiLegendsOld, KitakamiLegendsNew, KitakamiLegendsForm, KitakamiLegendsSex, false);
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/sdc01_0410_/sdc01_0410_main_0.trsog", KitakamiLegendsOld, KitakamiLegendsNew, KitakamiLegendsForm, KitakamiLegendsSex, false);
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/sdc01_0420_/sdc01_0420_main_0.trsog", KitakamiLegendsOld, KitakamiLegendsNew, KitakamiLegendsForm, KitakamiLegendsSex, 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);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s1_sub_011_/s1_sub_011_pre_start_0.trsog", OkidogiDev, OkidogiForm, OkidogiSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_3gods_b_/sdc01_3gods_b_pre_start_0.trsog", MunkidoriDev, MunkidoriForm, MunkidoriSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s1_sub_012_/s1_sub_012_pre_start_0.trsog", MunkidoriDev, MunkidoriForm, MunkidoriSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_3gods_c_/sdc01_3gods_c_pre_start_0.trsog", FezandipitiDev, FezandipitiForm, FezandipitiSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_3gods_c_before_/sdc01_3gods_c_before_pre_start_0.trsog", FezandipitiDev, FezandipitiForm, FezandipitiSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/sub_scenario/s1_sub_016_/s1_sub_016_pre_start_0.trsog", FezandipitiDev, FezandipitiForm, FezandipitiSex);
// Ogerpon
UpdateOverworldSceneSingle("world/scene/parts/demo/ev/d610_/d610_0.trscn", OgerponDev, OgerponForm, OgerponSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0105_/sdc01_0105_main_0.trsog", OgerponDev, OgerponForm, OgerponSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0130_/sdc01_0130_always_0.trsog", OgerponDev, OgerponForm, OgerponSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0160_/sdc01_0160_always_0.trsog", OgerponDev, OgerponForm, OgerponSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0370_/sdc01_0370_post_end_0.trsog", OgerponDev, OgerponForm, OgerponSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0390_/sdc01_0390_main_0.trsog", OgerponDev, OgerponForm, OgerponSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0430_/sdc01_0430_main_0.trsog", OgerponDev, OgerponForm, OgerponSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0440_/sdc01_0440_always_0.trsog", OgerponDev, OgerponForm, OgerponSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0460_/sdc01_0460_main_0.trsog", OgerponDev, OgerponForm, OgerponSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_0480_/sdc01_0480_main_0.trsog", OgerponDev, OgerponForm, OgerponSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_3gods_a_/sdc01_3gods_a_main_0.trsog", OgerponDev, OgerponForm, OgerponSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_3gods_b_/sdc01_3gods_b_main_0.trsog", OgerponDev, OgerponForm, OgerponSex);
UpdateOverworldSceneSingle("world/scene/parts/event/event_scenario/main_scenario/sdc01_3gods_c_/sdc01_3gods_c_main_0.trsog", OgerponDev, OgerponForm, OgerponSex);
}
private static void UpdateAllTrainerScenes(TrDataMainArray obj)
{
var table = obj.Table;
PokeDataBattle Shellder = obj.Table.FirstOrDefault(z => z.TrId is "pepper_nusi_01").Poke1;
PokeDataBattle Toedscool = obj.Table.FirstOrDefault(z => z.TrId is "pepper_nusi_02").Poke1;
PokeDataBattle Nacli = obj.Table.FirstOrDefault(z => z.TrId is "pepper_nusi_03").Poke1;
PokeDataBattle Scovillain = obj.Table.FirstOrDefault(z => z.TrId is "pepper_nusi_04").Poke1;
PokeDataBattle Greedent = obj.Table.FirstOrDefault(z => z.TrId is "pepper_nusi_05").Poke1;
List<DevID> TitanRockOld = [ 0, 0, DevID.DEV_SHERUDAA ];
List<DevID> TitanRockNew = [ 0, 0, Shellder.DevId ];
List<short> TitanRockForm = [ 0, 0, Shellder.FormId ];
List<SexType> TitanRockSex = [ 0, 0, Shellder.Sex ];
List<DevID> TitanDragonOld = [ 0, 0, DevID.DEV_YOKUBARISU ];
List<DevID> TitanDragonNew = [ 0, 0, Greedent.DevId ];
List<short> TitanDragonForm = [ 0, 0, Greedent.FormId ];
List<SexType> TitanDragonSex = [ 0, 0, Greedent.Sex ];
UpdateOverworldSceneSingle("world/scene/parts/field/field_contents/nushi/hagane/HaganeRockClashEvent_/HaganeRockClashEvent_0.trscn", Toedscool.DevId, Toedscool.FormId, Toedscool.Sex);
UpdateOverworldSceneSingle("world/scene/parts/field/field_contents/nushi/hiko/HikoRockClashEvent_/HikoRockClashEvent_0.trscn", Nacli.DevId, Nacli.FormId, Nacli.Sex);
UpdateOverworldSceneSingle("world/scene/parts/field/field_contents/nushi/jimen/JimenRockClashEvent_/JimenRockClashEvent_0.trscn", Scovillain.DevId, Scovillain.FormId, Scovillain.Sex);
UpdateOverworldSceneSingle("world/scene/parts/field/field_contents/nushi/dragon/DragonRockClashEvent_/DragonRockClashEvent_0.trscn", Greedent.DevId, Greedent.FormId, Greedent.Sex);
UpdateOverworldSceneMulti("world/scene/parts/field/field_contents/nushi/common/RockClashEvent_/RockClashEvent_0.trscn", TitanRockOld, TitanRockNew, TitanRockForm, TitanRockSex, false);
UpdateOverworldSceneMulti("world/scene/parts/event/event_scenario/main_scenario/nushi_dragon_020_/nushi_dragon_020_pre_start_0.trsog", TitanDragonOld, TitanDragonNew, TitanDragonForm, TitanDragonSex, true);
}
private static void AddCustomItemData()
{
"Preparing randomizer changes...".Dump();
// 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" ];
TextConfig cfg = new(GameVersion.SV);
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++)
{
string lang = Languages[i];
foreach (string t in ItemStringFiles)
{
string destStrings = Path.Combine(DEST_MASTER, $"message/dat/{lang}/common/");
string[] lines = GetStringsCommon(lang, t);
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];
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);
byte[] write = TextFile.GetBytes(lines, cfg);
string destName = t + ".dat";
File.WriteAllBytes(Path.Combine(destStrings, destName), write);
}
}
// update item icons
for (int i = 2351; i <= 2354; i++)
{
string destIcon = Path.Combine(DEST_MASTER, $"appli/tex/icon_item/item_{i}/");
int 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);
}
}
private static void GenerateRandomizerLogFile()
{
"Generating randomizer log file...".Dump();
DateTime dt = DateTime.Now;
string date = dt.ToString("dddd, MMMM d, yyyy");
string 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, "");
File.WriteAllLines(Path.Combine(DEST_LOG, "Randomizer Log.txt"), RandomizerLog);
}
private static void FinalizeRandomizerData()
{
"Compressing randomizer data...".Dump();
if (File.Exists(DEST_ZIP_ARCHIVE))
File.Delete(DEST_ZIP_ARCHIVE);
ZipFile.CreateFromDirectory(DEST_MASTER, DEST_ZIP_ARCHIVE);
Process.Start("explorer.exe", DEST_LOG.Replace("/", "\\"));
"".Dump();
"Randomization is now complete!".Dump();
"In order to play your randomizer, run TrinityModLoader and apply a mod using the generated .zip file.".Dump();
}
private static List<ushort> Legendary =
[
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> Mythical =
[
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> UltraBeast = Enumerable.Range(0793, 7).Concat(Enumerable.Range(0803, 4)).ToList();
private static List<int> Paradox = Enumerable.Range(0984, 12).Concat(Enumerable.Range(1005, 2)).Concat(Enumerable.Range(1009, 2)).Concat(Enumerable.Range(1020, 4)).ToList();
private static List<ushort> BattleOnlyForms =
[
0778, // Mimikyu
0845, // Cramorant
0875, // Eiscue
0877, // Morpeko
0964, // Palafin
1024, // Terapagos
];
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 => 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
0648 when form is 0 => true, // Aria Forme Meloetta
0898 when form is not 0 => true, // Ice Rider Calyrex, Shadow Rider 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, // Aqua Breed 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 0 => false, // Avalugg
0898 when form is 0 => false, // 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 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> 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 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_02_02",
"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 List<string> GenericOverworldPokemonScenes =
[
"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/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_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_side02_0040_/targets_fieldwork_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/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",
];
@sora10pls
Copy link
Author

sora10pls commented Jun 4, 2024

The Foreign Treat code has now been pushed, and allows for evolving Pokémon into their respective regional forms. This includes:
Pikachu, Exeggcute, Koffing, Quilava, Dewott, Petilil, Rufflet, Goomy, Bergmite, and Dartrix.

You'll need to update your local LINQ query with the code that was just pushed, and re-randomize your game, as this also fixes a few issues regarding the old Black Augurite and Peat Block implementation. Have fun!

Edit: I've also just pushed code that includes a reimplemented Linking Cord for standard trade evolutions.

@chernobylzambie
Copy link

Thank you so much! ;o; It looks like a wonderful method and easy on the players as well! Thank you for your hard work and dedication. 🙏 I'm super excited to get on it!

@SHRetro
Copy link

SHRetro commented Jun 6, 2024

That shouldn't be there, looks like when you did all Materials in the shop, you didn't exclude the None Items (Paldea Starters & Base Game Paradox Pokemon)
Pokémon Scarlet_2024-06-05_20-14-20

@sora10pls
Copy link
Author

@SHRetro Thanks, fixed.

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