using System; using System.Diagnostics; namespace PKHeX.Core { /// /// table (array). /// /// /// Serves as the main object that is accessed for stat data in a particular generation/game format. /// [DebuggerDisplay($"{{{nameof(Game)},nq}}[{{{nameof(TableLength)},nq}}]")] public sealed class PersonalTable { /// /// Personal Table used in . /// public static readonly PersonalTable LA = GetTable("la", GameVersion.PLA); /// /// Personal Table used in . /// public static readonly PersonalTable BDSP = GetTable("bdsp", GameVersion.BDSP); /// /// Personal Table used in . /// public static readonly PersonalTable SWSH = GetTable("swsh", GameVersion.SWSH); /// /// 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) => new(Util.GetBinaryResource($"personal_{game}"), format); private static Func GetConstructor(GameVersion format) => format switch { GameVersion.RB or GameVersion.YW => z => new PersonalInfoG1(z), GameVersion.GS or GameVersion.C => z => new PersonalInfoG2(z), GameVersion.RS or GameVersion.E or GameVersion.FR or GameVersion.LG => z => new PersonalInfoG3(z), GameVersion.DP or GameVersion.Pt or GameVersion.HGSS => z => new PersonalInfoG4(z), GameVersion.BW => z => new PersonalInfoBW(z), GameVersion.B2W2 => z => new PersonalInfoB2W2(z), GameVersion.XY => z => new PersonalInfoXY(z), GameVersion.ORAS => z => new PersonalInfoORAS(z), GameVersion.SM or GameVersion.USUM => z => new PersonalInfoSM(z), GameVersion.GG => z => new PersonalInfoGG(z), GameVersion.SWSH => z => new PersonalInfoSWSH(z), GameVersion.PLA => z => new PersonalInfoLA(z), _ => z => new PersonalInfoBDSP(z), }; private static int GetEntrySize(GameVersion format) => format switch { GameVersion.RB or GameVersion.YW => PersonalInfoG1.SIZE, GameVersion.GS or GameVersion.C => PersonalInfoG2.SIZE, GameVersion.RS or GameVersion.E or GameVersion.FR or GameVersion.LG => PersonalInfoG3.SIZE, GameVersion.DP or GameVersion.Pt or GameVersion.HGSS => PersonalInfoG4.SIZE, GameVersion.BW => PersonalInfoBW.SIZE, GameVersion.B2W2 => PersonalInfoB2W2.SIZE, GameVersion.XY => PersonalInfoXY.SIZE, GameVersion.ORAS => PersonalInfoORAS.SIZE, GameVersion.SM or GameVersion.USUM or GameVersion.GG => PersonalInfoSM.SIZE, GameVersion.SWSH => PersonalInfoSWSH.SIZE, GameVersion.BDSP => PersonalInfoBDSP.SIZE, GameVersion.PLA => PersonalInfoLA.SIZE, _ => -1, }; static PersonalTable() // Finish Setup { FixPersonalTableG1(); PopulateGen3Tutors(); PopulateGen4Tutors(); CopyDexitGenders(); } private static void FixPersonalTableG1() { // Load Gen2 Gender Ratios into Gen1 PersonalInfo[] rb = RB.Table, y = Y.Table, gs = GS.Table; for (int i = Legal.MaxSpeciesID_1; i >= 0; 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 machine = BinLinkerAccessor.Get(Util.GetBinaryResource("hmtm_g3.pkl"), "g3"); var tutors = BinLinkerAccessor.Get(Util.GetBinaryResource("tutors_g3.pkl"), "g3"); var table = E.Table; for (int i = Legal.MaxSpeciesID_3; i >= 0; i--) { var entry = table[i]; entry.AddTMHM(machine[i]); entry.AddTypeTutors(tutors[i]); // Copy to other tables RS.Table[i].TMHM = FR.Table[i].TMHM = LG.Table[i].TMHM = entry.TMHM; RS.Table[i].TypeTutors = FR.Table[i].TypeTutors = LG.Table[i].TypeTutors = entry.TypeTutors; } } private static void PopulateGen4Tutors() { var tutors = BinLinkerAccessor.Get(Util.GetBinaryResource("tutors_g4.pkl"), "g4"); var table = HGSS.Table; var count = tutors.Length; for (int i = 0; i < count; i++) table[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() { var swsh = SWSH.Table; var usum = USUM.Table; for (int i = 1; i <= Legal.MaxSpeciesID_7_USUM; i++) { var ss = swsh[i]; if (ss.HP == 0) ss.Gender = usum[i].Gender; } var la = LA; for (int i = 1; i <= Legal.MaxSpeciesID_8_R2; i++) { var e = la.Table[i]; var fc = e.FormCount; for (int f = 0; f < fc; f++) { var l = (PersonalInfoLA)la.GetFormEntry(i, f); if (l.HP != 0) continue; var s = (PersonalInfoSWSH)SWSH.GetFormEntry(i, f); l.Ability1 = s.Ability1; l.Ability2 = s.Ability2; l.AbilityH = s.AbilityH; l.Gender = s.Gender; l.EXPGrowth = s.EXPGrowth; } } } public PersonalTable(ReadOnlySpan data, GameVersion format) { var get = GetConstructor(format); int size = GetEntrySize(format); var count = data.Length / size; var table = new PersonalInfo[count]; for (int i = 0; i < table.Length; i++) table[i] = get(data.Slice(size * i, size).ToArray()); Table = table; 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 { var table = Table; if ((uint)index >= table.Length) return table[0]; return table[index]; } set { var table = Table; if ((uint)index >= table.Length) return; table[index] = value; } } /// /// Gets the entry index for a given and . /// /// /// /// Entry index for the input criteria public int GetFormIndex(int species, int form) { if ((uint)species <= MaxSpeciesID) return Table[species].FormIndex(species, form); Debug.WriteLine($"Requested out of bounds {nameof(species)}: {species} (max={MaxSpeciesID})"); return 0; } /// /// Gets the entry for a given and . /// /// /// /// Entry for the input criteria public PersonalInfo GetFormEntry(int species, int form) { return this[GetFormIndex(species, form)]; } /// /// Count of entries in the table, which includes default species entries and their separate entries. /// 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].FormCount; 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[][] forms, 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 (forms[i].Length == 0) continue; int basePtr = this[i].FormStatsIndex; if (basePtr <= 0) continue; for (int j = 1; j < forms[i].Length; j++) { int ptr = basePtr + j - 1; baseForm[ptr] = i; formVal[ptr] = j; result[ptr] = forms[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 Array.Find(Table, p => p.IsValidTypeCombination(type1, type2)) != null; } public bool IsSpeciesInGame(int species) { if ((uint)species > MaxSpeciesID) return false; var form0 = Table[species]; if (form0.IsPresentInGame) return true; var fc = form0.FormCount; for (int i = 1; i < fc; i++) { if (GetFormEntry(species, i).IsPresentInGame) return true; } return false; } public bool IsPresentInGame(int species, int form) { if ((uint)species > MaxSpeciesID) return false; var entry = GetFormEntry(species, form); return entry.IsPresentInGame; } } }