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 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.RBY);
///
/// Personal Table used in .
///
public static readonly PersonalTable Y = GetTable("y", GameVersion.RBY);
private static PersonalTable GetTable(string game, GameVersion format)
{
return new PersonalTable(Util.GetBinaryResource($"personal_{game}"), format);
}
private static byte[][] SplitBytes(byte[] data, int size)
{
byte[][] r = new byte[data.Length / size][];
for (int i = 0; i < data.Length; i += size)
{
r[i / size] = new byte[size];
Array.Copy(data, i, r[i / size], 0, size);
}
return r;
}
private static Func GetConstructor(GameVersion format)
{
switch (format)
{
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);
default:
return z => new PersonalInfoSM(z);
}
}
private static int GetEntrySize(GameVersion format)
{
switch (format)
{
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: return PersonalInfoSM.SIZE;
default: return -1;
}
}
static PersonalTable() // Finish Setup
{
FixPersonalTableG1();
PopulateGen3Tutors();
PopulateGen4Tutors();
}
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 = Data.UnpackMini(Util.GetBinaryResource("hmtm_g3.pkl"), "g3");
var tutors = Data.UnpackMini(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 = Data.UnpackMini(Util.GetBinaryResource("tutors_g4.pkl"), "g4");
for (int i = 0; i < tutors.Length; i++)
HGSS[i].AddTypeTutors(tutors[i]);
}
public PersonalTable(byte[] data, GameVersion format)
{
var get = GetConstructor(format);
int size = GetEntrySize(format);
byte[][] entries = SplitBytes(data, size);
Table = new PersonalInfo[data.Length / size];
for (int i = 0; i < Table.Length; i++)
Table[i] = get(entries[i]);
MaxSpeciesID = format.GetMaxSpeciesID();
}
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;
///
/// 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));
}
}
}