PKHeX/PKHeX.Core/Saves/Substructures/Gen6/SuperTrainBlock.cs
Kurt 3c232505e5
Refactoring: Narrow some value types (Species, Move, Form) (#3575)
In this pull request I've changed a ton of method signatures to reflect the more-narrow types of Species, Move# and Form; additionally, I've narrowed other large collections that stored lists of species / permitted values, and reworked them to be more performant with the latest API spaghetti that PKHeX provides. Roamer met locations, usually in a range of [max-min]<64, can be quickly checked using a bitflag operation on a UInt64. Other collections (like "Is this from Colosseum or XD") were eliminated -- shadow state is not transferred COLO<->XD, so having a Shadow ID or matching the met location from a gift/wild encounter is a sufficient check for "originated in XD".
2022-08-26 23:43:36 -07:00

343 lines
9.8 KiB
C#

using System;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
public sealed class SuperTrainBlock : SaveBlock<SAV6>
{
public SuperTrainBlock(SAV6XY sav, int offset) : base(sav) => Offset = offset;
public SuperTrainBlock(SAV6AO sav, int offset) : base(sav) => Offset = offset;
// Structure:
// 6 bytes stage unlock flags
// 1 byte distribution stage unlock flags
// 1 byte counter (max value = 10)
// float[48] recordScore1; // 0x08, 4byte/entry
// float[48] recordScore2; // 0xC8, 4byte/entry
// SS-F-G[48] recordHolder1; // 0x188, 4byte/entry
// SS-F-G[48] recordHolder2; // 0x248, 4byte/entry
// byte[12] bags // 0x308
// u32 tutorial tracker (max value = 6) // 0x314
// 0x318 total size
// SS-F-G = u16 species, u8 form, u8 gender
private const int MAX = 48;
private const int MAX_RELEASED = 32;
private const int MAX_DIST = 6;
private const int MAX_BAG = 12;
/// <summary>
/// Checks if a Regimen is unlocked.
/// </summary>
/// <param name="index">Index of regimen.</param>
/// <returns>Is Unlocked</returns>
public bool GetIsRegimenUnlocked(int index)
{
if ((uint)index >= MAX)
throw new ArgumentOutOfRangeException(nameof(index));
return SAV.GetFlag(Offset, index);
}
/// <summary>
/// Sets a Regimen to the desired unlocked state.
/// </summary>
/// <param name="index">Index of regimen.</param>
/// <param name="value">Is Unlocked</param>
public void SetIsRegimenUnlocked(int index, bool value)
{
if ((uint)index >= MAX)
throw new ArgumentOutOfRangeException(nameof(index));
SAV.SetFlag(Offset, index, value);
}
/// <summary>
/// Checks if a Distribution Regimen is unlocked.
/// </summary>
/// <param name="index">Index of regimen.</param>
/// <returns>Is Unlocked</returns>
public bool GetIsDistributionUnlocked(int index)
{
if ((uint)index >= MAX_DIST)
throw new ArgumentOutOfRangeException(nameof(index));
return SAV.GetFlag(Offset + 6, index);
}
/// <summary>
/// Sets a Distribution Regimen to the desired unlocked state.
/// </summary>
/// <param name="index">Index of regimen.</param>
/// <param name="value">Is Unlocked</param>
public void SetIsDistributionUnlocked(int index, bool value)
{
if ((uint)index >= MAX_DIST)
throw new ArgumentOutOfRangeException(nameof(index));
SAV.SetFlag(Offset + 6, index, value);
}
/// <summary>
/// Counts something up to 10 (overall stages unlocked?)
/// </summary>
public byte Counter
{
get => Data[Offset + 7];
set => Data[Offset + 7] = Math.Min((byte)10, value);
}
/// <summary>
/// Gets the record time.
/// </summary>
/// <param name="index">Index of the record.</param>
public float GetTime1(int index)
{
if ((uint) index >= MAX)
throw new ArgumentOutOfRangeException(nameof(index));
return ReadSingleLittleEndian(Data.AsSpan(Offset + 0x08 + (4 * index)));
}
/// <summary>
/// Sets the record time.
/// </summary>
/// <param name="index">Index of the record.</param>
/// <param name="value">Value to set.</param>
public void SetTime1(int index, float value = 0)
{
if ((uint)index >= MAX)
throw new ArgumentOutOfRangeException(nameof(index));
WriteSingleLittleEndian(Data.AsSpan(Offset + 0x08 + (4 * index)), value);
}
/// <summary>
/// Gets the record time.
/// </summary>
/// <param name="index">Index of the record.</param>
public float GetTime2(int index)
{
if ((uint)index >= MAX)
throw new ArgumentOutOfRangeException(nameof(index));
return ReadSingleLittleEndian(Data.AsSpan(Offset + 0xC8 + (4 * index)));
}
/// <summary>
/// Sets the record time.
/// </summary>
/// <param name="index">Index of the record.</param>
/// <param name="value">Value to set.</param>
public void SetTime2(int index, float value = 0)
{
if ((uint)index >= MAX)
throw new ArgumentOutOfRangeException(nameof(index));
WriteSingleLittleEndian(Data.AsSpan(Offset + 0xC8 + (4 * index)), value);
}
/// <summary>
/// Returns an object which will edit the record directly from data.
/// </summary>
/// <param name="index">Index of the record.</param>
/// <returns>Object that will edit the record data if modified.</returns>
public SuperTrainingSpeciesRecord GetHolder1(int index)
{
if ((uint)index >= MAX)
throw new ArgumentOutOfRangeException(nameof(index));
return new SuperTrainingSpeciesRecord(Data, Offset + 0x188 + (4 * index));
}
/// <summary>
/// Returns an object which will edit the record directly from data.
/// </summary>
/// <param name="index">Index of the record.</param>
/// <returns>Object that will edit the record data if modified.</returns>
public SuperTrainingSpeciesRecord GetHolder2(int index)
{
if ((uint)index >= MAX)
throw new ArgumentOutOfRangeException(nameof(index));
return new SuperTrainingSpeciesRecord(Data, Offset + 0x248 + (4 * index));
}
/// <summary>
/// Gets the bag from the desired <see cref="index"/>.
/// </summary>
/// <param name="index">Bag index</param>
public byte GetBag(int index)
{
if ((uint)index >= MAX_BAG)
throw new ArgumentOutOfRangeException(nameof(index));
return Data[Offset + 0x308 + index];
}
/// <summary>
/// Sets a bag to the desired <see cref="index"/>.
/// </summary>
/// <param name="index">Bag index</param>
/// <param name="value">Bag ID</param>
public void SetBag(int index, byte value)
{
if ((uint)index >= MAX_BAG)
throw new ArgumentOutOfRangeException(nameof(index));
Data[Offset + 0x308 + index] = value;
}
/// <summary>
/// Gets the next open bag index.
/// </summary>
/// <returns>Bag index that is empty</returns>
public int GetOpenBagIndex()
{
for (int i = 0; i < MAX_BAG; i++)
{
if (GetBag(i) != 0)
return i;
}
return -1;
}
/// <summary>
/// Adds a bag to the list of bags.
/// </summary>
/// <param name="bag">Bag type</param>
/// <returns>Bag was added or not</returns>
public bool AddBag(byte bag)
{
int index = GetOpenBagIndex();
if (index < 0)
return false;
SetBag(index, bag);
return true;
}
/// <summary>
/// Removes a bag from the list of bags.
/// </summary>
/// <param name="index">Bag index</param>
public void RemoveBag(int index)
{
if ((uint)index >= MAX_BAG)
throw new ArgumentOutOfRangeException(nameof(index));
for (int i = index; i < MAX_BAG - 1; i++)
{
var next = GetBag(i + 1);
SetBag(i, next);
}
SetBag(MAX_BAG - 1, 0);
}
public uint TutorialIndex
{
get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x314));
set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x314), value);
}
/// <summary>
/// Clears all data for the record.
/// </summary>
/// <param name="index">Index of the record.</param>
public void ClearRecord1(int index)
{
if ((uint)index >= MAX)
throw new ArgumentOutOfRangeException(nameof(index));
SetTime1(index, 0f);
GetHolder1(index).Clear();
}
/// <summary>
/// Clears all data for the record.
/// </summary>
/// <param name="index">Index of the record.</param>
public void ClearRecord2(int index)
{
if ((uint)index >= MAX)
throw new ArgumentOutOfRangeException(nameof(index));
SetTime2(index, 0f);
GetHolder2(index).Clear();
}
/// <summary>
/// Unlocks all stages.
/// </summary>
/// <param name="dist">Unlock all Distribution stages too.</param>
public void UnlockAllStages(bool dist)
{
for (int i = 0; i < MAX_RELEASED; i++)
SetIsRegimenUnlocked(i, true);
if (!dist)
return;
for (int i = 0; i < MAX_DIST; i++)
SetIsDistributionUnlocked(i, true);
}
/// <summary>
/// Clears all data in the block.
/// </summary>
public void ClearBlock() => Array.Clear(Data, Offset, 0x318);
}
public sealed class SuperTrainingSpeciesRecord : ISpeciesForm
{
private readonly byte[] Data;
private readonly int Offset;
public SuperTrainingSpeciesRecord(byte[] data, int offset)
{
Data = data;
Offset = offset;
}
/// <summary>
/// <see cref="PKM.Species"/> of the Record Holder.
/// </summary>
public ushort Species
{
get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0));
set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0), value);
}
/// <summary>
/// <see cref="PKM.Form"/> of the Record Holder.
/// </summary>
public byte Form
{
get => Data[Offset + 2];
set => Data[Offset + 2] = value;
}
/// <summary>
/// <see cref="PKM.Gender"/> of the Record Holder.
/// </summary>
/// <seealso cref="Core.Gender"/>
public byte Gender
{
get => Data[Offset + 3];
set => Data[Offset + 3] = value;
}
/// <summary>
/// Wipes the record holder's pk-related data.
/// </summary>
public void Clear()
{
Species = 0; Form = Gender = 0;
}
/// <summary>
/// Sets the data to match what is in the provided reference.
/// </summary>
/// <param name="pk">Reference to load from.</param>
public void LoadFrom(PKM pk)
{
Species = pk.Species;
Form = pk.Form;
Gender = (byte)pk.Gender;
}
}