using System; using System.Diagnostics; using System.Linq; namespace PKHeX.Core { /// /// table (array). /// /// /// Serves as the main object that is accessed for stat data in a particular generation/game format. /// public class PersonalTable { /// /// Personal Table used in . /// public static readonly PersonalTable SWSH = GetTable("swsh", GameVersion.SWSH); // todo /// /// Personal Table used in . /// public static readonly PersonalTable GG = GetTable("gg", GameVersion.GG); /// /// Personal Table used in . /// public static readonly PersonalTable USUM = GetTable("uu", GameVersion.USUM); /// /// Personal Table used in . /// public static readonly PersonalTable SM = GetTable("sm", GameVersion.SM); /// /// Personal Table used in . /// public static readonly PersonalTable AO = GetTable("ao", GameVersion.ORAS); /// /// Personal Table used in . /// public static readonly PersonalTable XY = GetTable("xy", GameVersion.XY); /// /// Personal Table used in . /// public static readonly PersonalTable B2W2 = GetTable("b2w2", GameVersion.B2W2); /// /// Personal Table used in . /// public static readonly PersonalTable BW = GetTable("bw", GameVersion.BW); /// /// Personal Table used in . /// public static readonly PersonalTable HGSS = GetTable("hgss", GameVersion.HGSS); /// /// Personal Table used in . /// public static readonly PersonalTable Pt = GetTable("pt", GameVersion.Pt); /// /// Personal Table used in . /// public static readonly PersonalTable DP = GetTable("dp", GameVersion.DP); /// /// Personal Table used in . /// public static readonly PersonalTable LG = GetTable("lg", GameVersion.LG); /// /// Personal Table used in . /// public static readonly PersonalTable FR = GetTable("fr", GameVersion.FR); /// /// Personal Table used in . /// public static readonly PersonalTable E = GetTable("e", GameVersion.E); /// /// Personal Table used in . /// public static readonly PersonalTable RS = GetTable("rs", GameVersion.RS); /// /// Personal Table used in . /// public static readonly PersonalTable C = GetTable("c", GameVersion.C); /// /// Personal Table used in . /// public static readonly PersonalTable GS = GetTable("c", GameVersion.GS); /// /// Personal Table used in . /// public static readonly PersonalTable RB = GetTable("rb", GameVersion.RB); /// /// Personal Table used in . /// public static readonly PersonalTable Y = GetTable("y", GameVersion.YW); private static PersonalTable GetTable(string game, GameVersion format) { return new PersonalTable(Util.GetBinaryResource($"personal_{game}"), format); } private static Func GetConstructor(GameVersion format) { switch (format) { case GameVersion.RB: case GameVersion.YW: case GameVersion.RBY: return z => new PersonalInfoG1(z); case GameVersion.GS: case GameVersion.C: return z => new PersonalInfoG2(z); case GameVersion.RS: case GameVersion.E: case GameVersion.FR: case GameVersion.LG: return z => new PersonalInfoG3(z); case GameVersion.DP: case GameVersion.Pt: case GameVersion.HGSS: return z => new PersonalInfoG4(z); case GameVersion.BW: return z => new PersonalInfoBW(z); case GameVersion.B2W2: return z => new PersonalInfoB2W2(z); case GameVersion.XY: return z => new PersonalInfoXY(z); case GameVersion.ORAS: return z => new PersonalInfoORAS(z); case GameVersion.SM: case GameVersion.USUM: return z => new PersonalInfoSM(z); case GameVersion.GG: return z => new PersonalInfoGG(z); default: return z => new PersonalInfoSWSH(z); } } private static int GetEntrySize(GameVersion format) { switch (format) { case GameVersion.RB: case GameVersion.YW: case GameVersion.RBY: return PersonalInfoG1.SIZE; case GameVersion.GS: case GameVersion.C: return PersonalInfoG2.SIZE; case GameVersion.RS: case GameVersion.E: case GameVersion.FR: case GameVersion.LG: return PersonalInfoG3.SIZE; case GameVersion.DP: case GameVersion.Pt: case GameVersion.HGSS: return PersonalInfoG4.SIZE; case GameVersion.BW: return PersonalInfoBW.SIZE; case GameVersion.B2W2: return PersonalInfoB2W2.SIZE; case GameVersion.XY: return PersonalInfoXY.SIZE; case GameVersion.ORAS: return PersonalInfoORAS.SIZE; case GameVersion.SM: case GameVersion.USUM: case GameVersion.GG: return PersonalInfoSM.SIZE; case GameVersion.SWSH: return PersonalInfoSWSH.SIZE; default: return -1; } } static PersonalTable() // Finish Setup { FixPersonalTableG1(); PopulateGen3Tutors(); PopulateGen4Tutors(); CopyDexitGenders(); } private static void FixPersonalTableG1() { // Update Yellow's catch rates; currently matches Red/Blue's values. Y[25].CatchRate = 163; // Pikachu Y[64].CatchRate = 96; // Kadabra // Load Gen2 Gender Ratios into Gen1 for (int i = 0; i <= Legal.MaxSpeciesID_1; i++) RB[i].Gender = Y[i].Gender = GS[i].Gender; } private static void PopulateGen3Tutors() { // Update Gen3 data with Emerald's data, FR/LG is a subset of Emerald's compatibility. var TMHM = BinLinker.Unpack(Util.GetBinaryResource("hmtm_g3.pkl"), "g3"); var tutors = BinLinker.Unpack(Util.GetBinaryResource("tutors_g3.pkl"), "g3"); for (int i = 0; i <= Legal.MaxSpeciesID_3; i++) { E[i].AddTMHM(TMHM[i]); E[i].AddTypeTutors(tutors[i]); } } private static void PopulateGen4Tutors() { var tutors = BinLinker.Unpack(Util.GetBinaryResource("tutors_g4.pkl"), "g4"); for (int i = 0; i < tutors.Length; i++) HGSS[i].AddTypeTutors(tutors[i]); } /// /// Sword/Shield do not contain personal data (stubbed) for all species that are not allowed to visit the game. /// Copy all the genders from 's table for all past species, since we need it for gender lookups for all generations. /// private static void CopyDexitGenders() { for (int i = 1; i <= 807; i++) { var ss = SWSH[i]; if (ss.HP == 0) ss.Gender = USUM[i].Gender; } } public PersonalTable(byte[] data, GameVersion format) { var get = GetConstructor(format); int size = GetEntrySize(format); byte[][] entries = data.Split(size); Table = new PersonalInfo[entries.Length]; for (int i = 0; i < Table.Length; i++) Table[i] = get(entries[i]); MaxSpeciesID = format.GetMaxSpeciesID(); Game = format; } private readonly PersonalInfo[] Table; /// /// Gets an index from the inner array. /// /// Has built in length checks; returns empty (0) entry if out of range. /// Index to retrieve /// Requested index entry public PersonalInfo this[int index] { get { if (0 <= index && index < Table.Length) return Table[index]; return Table[0]; } set { if (index < 0 || index >= Table.Length) return; Table[index] = value; } } /// /// Gets the abilities possible for a given and . /// /// /// /// Array of possible abilities public int[] GetAbilities(int species, int forme) { return GetFormeEntry(species, forme).Abilities; } /// /// Gets the entry index for a given and . /// /// /// /// Entry index for the input criteria public int GetFormeIndex(int species, int forme) { if (species > MaxSpeciesID) { Debug.WriteLine($"Requested out of bounds {nameof(species)}: {species} (max={MaxSpeciesID})"); species = 0; } return this[species].FormeIndex(species, forme); } /// /// Gets the entry for a given and . /// /// /// /// Entry for the input criteria public PersonalInfo GetFormeEntry(int species, int forme) { return this[GetFormeIndex(species, forme)]; } /// /// Count of entries in the table, which includes default species entries and their separate entreis. /// public int TableLength => Table.Length; /// /// Maximum Species ID for the Table. /// public readonly int MaxSpeciesID; /// /// Game(s) the originated from. /// public readonly GameVersion Game; /// /// Gets form names for every species. /// /// Raw string resource (Species) for the corresponding table. /// Max Species ID () /// Array of species containing an array of form names for that species. public string[][] GetFormList(string[] species, int MaxSpecies) { string[][] FormList = new string[MaxSpecies+1][]; for (int i = 0; i < FormList.Length; i++) { int FormCount = this[i].FormeCount; FormList[i] = new string[FormCount]; if (FormCount <= 0) continue; FormList[i][0] = species[i]; for (int j = 1; j < FormCount; j++) FormList[i][j] = $"{species[i]} {j}"; } return FormList; } /// /// Gets an arranged list of Form names and indexes for use with the individual values. /// /// Raw string resource (Forms) for the corresponding table. /// Raw string resource (Species) for the corresponding table. /// Max Species ID () /// Pointers for base form IDs /// Pointers for table indexes for each form /// Sanitized list of species names, and outputs indexes for various lookup purposes. public string[] GetPersonalEntryList(string[][] AltForms, string[] species, int MaxSpecies, out int[] baseForm, out int[] formVal) { string[] result = new string[Table.Length]; baseForm = new int[result.Length]; formVal = new int[result.Length]; for (int i = 0; i <= MaxSpecies; i++) { result[i] = species[i]; if (AltForms[i].Length == 0) continue; int altformpointer = this[i].FormStatsIndex; if (altformpointer <= 0) continue; for (int j = 1; j < AltForms[i].Length; j++) { int ptr = altformpointer + j - 1; baseForm[ptr] = i; formVal[ptr] = j; result[ptr] = AltForms[i][j]; } } return result; } /// /// Checks to see if either of the input type combinations exist in the table. /// /// Only useful for checking Generation 1 and properties. /// First type /// Second type /// Indication that the combination exists in the table. public bool IsValidTypeCombination(int Type1, int Type2) { return Table.Any(p => p.IsValidTypeCombination(Type1, Type2)); } } }