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.HP != 0)
return true;
var fc = form0.FormCount;
for (int i = 1; i < fc; i++)
{
if (GetFormEntry(species, i).HP != 0)
return true;
}
return false;
}
}
}