PKHeX/PKHeX.Core/Saves/Substructures/PokeDex/Zukan7b.cs
Kurt 47071b41f3
Refactoring: Span-based value writes and method signatures (#3361)
Existing `get`/`set` logic is flawed in that it doesn't work on Big Endian operating systems, and it allocates heap objects when it doesn't need to.

`System.Buffers.Binary.BinaryPrimitives` in the `System.Memory` NuGet package provides both Little Endian and Big Endian methods to read and write data; all the `get`/`set` operations have been reworked to use this new API. This removes the need for PKHeX's manual `BigEndian` class, as all functions are already covered by the BinaryPrimitives API.

The `StringConverter` has now been rewritten to accept a Span to read from & write to, no longer requiring a temporary StringBuilder.

Other Fixes included:
- The Super Training UI for Gen6 has been reworked according to the latest block structure additions.
- Cloning a Stadium2 Save File now works correctly (opening from the Folder browser list).
- Checksum & Sanity properties removed from parent PKM class, and is now implemented via interface.
2022-01-02 21:35:59 -08:00

222 lines
8.3 KiB
C#

using System;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core
{
/// <summary>
/// Pokédex structure used for <see cref="GameVersion.GG"/> games, slightly modified from <see cref="Zukan7"/>.
/// </summary>>
public sealed class Zukan7b : Zukan7
{
public Zukan7b(SAV7b sav, int dex, int langflag) : base(sav, dex, langflag)
{
}
public override void SetDex(PKM pkm)
{
if (!TryGetSizeEntryIndex(pkm.Species, pkm.Form, out _))
return;
SetSizeData((PB7)pkm);
base.SetDex(pkm);
}
protected override void SetDex(int species, int bit, int form, int gender, bool shiny, int lang)
{
if (IsBuddy(species, form))
form = 0;
base.SetDex(species, bit, form, gender, shiny, lang);
}
private static bool IsBuddy(int species, int form) => (species == (int)Species.Pikachu && form == 8) || (species == (int)Species.Eevee && form == 1);
public const int DefaultEntryValue = 0x7F;
public bool GetSizeData(DexSizeType group, int species, int form, out int height, out int weight)
{
height = weight = DefaultEntryValue;
if (TryGetSizeEntryIndex(species, form, out var index))
return GetSizeData(group, index, out height, out weight);
return false;
}
public bool GetSizeData(DexSizeType group, int index, out int height, out int weight)
{
var ofs = GetDexSizeOffset(group, index);
var entry = SAV.Data.AsSpan(ofs);
height = ReadUInt16LittleEndian(entry) >> 1;
weight = ReadUInt16LittleEndian(entry[2..]);
return !IsEntryUnset(height, weight);
}
private static bool IsEntryUnset(int height, int weight) => height == DefaultEntryValue && weight == DefaultEntryValue;
private void SetSizeData(PB7 pkm)
{
int species = pkm.Species;
int form = pkm.Form;
if (!TryGetSizeEntryIndex(species, form, out int index))
return;
if (Math.Round(pkm.HeightAbsolute) < pkm.PersonalInfo.Height) // possible minimum height
{
int ofs = GetDexSizeOffset(DexSizeType.MinHeight, index);
var entry = SAV.Data.AsSpan(ofs);
var minHeight = ReadUInt16LittleEndian(entry) >> 1;
var calcHeight = PB7.GetHeightAbsolute(pkm.PersonalInfo, minHeight);
if (Math.Round(pkm.HeightAbsolute) < Math.Round(calcHeight) || ReadUInt32LittleEndian(entry) == 0x007F00FE) // unset
SetSizeData(pkm, DexSizeType.MinHeight);
}
else if (Math.Round(pkm.HeightAbsolute) > pkm.PersonalInfo.Height) // possible maximum height
{
int ofs = GetDexSizeOffset(DexSizeType.MaxHeight, index);
var entry = SAV.Data.AsSpan(ofs);
var maxHeight = ReadUInt16LittleEndian(entry) >> 1;
var calcHeight = PB7.GetHeightAbsolute(pkm.PersonalInfo, maxHeight);
if (Math.Round(pkm.HeightAbsolute) > Math.Round(calcHeight) || ReadUInt32LittleEndian(entry) == 0x007F00FE) // unset
SetSizeData(pkm, DexSizeType.MaxHeight);
}
if (Math.Round(pkm.WeightAbsolute) < pkm.PersonalInfo.Weight) // possible minimum weight
{
int ofs = GetDexSizeOffset(DexSizeType.MinWeight, index);
var entry = SAV.Data.AsSpan(ofs);
var minHeight = ReadUInt16LittleEndian(entry) >> 1;
var minWeight = ReadUInt16LittleEndian(entry[2..]);
var calcWeight = PB7.GetWeightAbsolute(pkm.PersonalInfo, minHeight, minWeight);
if (Math.Round(pkm.WeightAbsolute) < Math.Round(calcWeight) || ReadUInt32LittleEndian(entry) == 0x007F00FE) // unset
SetSizeData(pkm, DexSizeType.MinWeight);
}
else if (Math.Round(pkm.WeightAbsolute) > pkm.PersonalInfo.Weight) // possible maximum weight
{
int ofs = GetDexSizeOffset(DexSizeType.MaxWeight, index);
var entry = SAV.Data.AsSpan(ofs);
var maxHeight = ReadUInt16LittleEndian(entry) >> 1;
var maxWeight = ReadUInt16LittleEndian(entry[2..]);
var calcWeight = PB7.GetWeightAbsolute(pkm.PersonalInfo, maxHeight, maxWeight);
if (Math.Round(pkm.WeightAbsolute) > Math.Round(calcWeight) || ReadUInt32LittleEndian(entry) == 0x007F00FE) // unset
SetSizeData(pkm, DexSizeType.MaxWeight);
}
}
private static int GetDexSizeOffset(DexSizeType group, int index) => 0x3978 + (index * 6) + ((int)group * 0x45C); // blockofs + 0xF78 + ([186*6]*n) + x*6
private void SetSizeData(PB7 pkm, DexSizeType group)
{
var tree = EvolutionTree.GetEvolutionTree(pkm, 7);
int species = pkm.Species;
int form = pkm.Form;
int height = pkm.HeightScalar;
int weight = pkm.WeightScalar;
// update for all species in potential lineage
var allspec = tree.GetEvolutionsAndPreEvolutions(species, form);
foreach (var sf in allspec)
{
var s = sf & 0x7FF;
var f = sf >> 11;
SetSizeData(group, s, f, height, weight);
}
}
public void SetSizeData(DexSizeType group, int species, int form, int height, int weight)
{
if (TryGetSizeEntryIndex(species, form, out var index))
SetSizeData(group, index, height, weight);
}
public void SetSizeData(DexSizeType group, int index, int height, int weight)
{
var ofs = GetDexSizeOffset(group, index);
var span = SAV.Data.AsSpan(ofs);
WriteUInt16LittleEndian(span, (ushort)(height << 1));
WriteUInt16LittleEndian(span[2..], (ushort)weight);
}
public static bool TryGetSizeEntryIndex(int species, int form, out int index)
{
index = -1;
if (form == 0 && species <= 151)
{
index = species - 1;
return true;
}
for (int i = 0; i < SizeDexInfoTable.Length; i += 3)
{
if (SizeDexInfoTable[i] != species)
continue;
if (SizeDexInfoTable[i + 1] != form)
continue;
index = SizeDexInfoTable[i + 2];
return true;
}
return false;
}
private static readonly ushort[] SizeDexInfoTable =
{
// species, form, index
808, 0, 151,
809, 0, 152,
003, 1, 153,
006, 1, 154,
006, 2, 155,
009, 1, 156,
015, 1, 157,
018, 1, 158,
019, 1, 159,
020, 1, 160,
026, 1, 161,
027, 1, 162,
028, 1, 163,
037, 1, 164,
038, 1, 165,
050, 1, 166,
051, 1, 167,
052, 1, 168,
053, 1, 169,
065, 1, 170,
074, 1, 171,
075, 1, 172,
076, 1, 173,
080, 1, 174,
088, 1, 175,
089, 1, 176,
094, 1, 177,
103, 1, 178,
105, 1, 179,
115, 1, 180,
127, 1, 181,
130, 1, 182,
142, 1, 183,
150, 1, 184,
150, 2, 185,
};
protected override bool GetSaneFormsToIterate(int species, out int formStart, out int formEnd, int formIn)
{
switch (species)
{
// Totems with Alolan Forms
case 020 or 105: // Raticate or Marowak
formStart = 0;
formEnd = 1;
return true;
default:
int count = DexFormUtil.GetDexFormCountGG(species);
formStart = formEnd = 0;
return count < formIn;
}
}
}
public enum DexSizeType
{
MinHeight,
MaxHeight,
MinWeight,
MaxWeight,
}
}