using System; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; public sealed class SuperTrainBlock : SaveBlock { 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; /// /// Checks if a Regimen is unlocked. /// /// Index of regimen. /// Is Unlocked public bool GetIsRegimenUnlocked(int index) { if ((uint)index >= MAX) throw new ArgumentOutOfRangeException(nameof(index)); return SAV.GetFlag(Offset, index); } /// /// Sets a Regimen to the desired unlocked state. /// /// Index of regimen. /// Is Unlocked public void SetIsRegimenUnlocked(int index, bool value) { if ((uint)index >= MAX) throw new ArgumentOutOfRangeException(nameof(index)); SAV.SetFlag(Offset, index, value); } /// /// Checks if a Distribution Regimen is unlocked. /// /// Index of regimen. /// Is Unlocked public bool GetIsDistributionUnlocked(int index) { if ((uint)index >= MAX_DIST) throw new ArgumentOutOfRangeException(nameof(index)); return SAV.GetFlag(Offset + 6, index); } /// /// Sets a Distribution Regimen to the desired unlocked state. /// /// Index of regimen. /// Is Unlocked public void SetIsDistributionUnlocked(int index, bool value) { if ((uint)index >= MAX_DIST) throw new ArgumentOutOfRangeException(nameof(index)); SAV.SetFlag(Offset + 6, index, value); } /// /// Counts something up to 10 (overall stages unlocked?) /// public byte Counter { get => Data[Offset + 7]; set => Data[Offset + 7] = Math.Min((byte)10, value); } /// /// Gets the record time. /// /// Index of the record. public float GetTime1(int index) { if ((uint) index >= MAX) throw new ArgumentOutOfRangeException(nameof(index)); return ReadSingleLittleEndian(Data.AsSpan(Offset + 0x08 + (4 * index))); } /// /// Sets the record time. /// /// Index of the record. /// Value to set. 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); } /// /// Gets the record time. /// /// Index of the record. public float GetTime2(int index) { if ((uint)index >= MAX) throw new ArgumentOutOfRangeException(nameof(index)); return ReadSingleLittleEndian(Data.AsSpan(Offset + 0xC8 + (4 * index))); } /// /// Sets the record time. /// /// Index of the record. /// Value to set. 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); } /// /// Returns an object which will edit the record directly from data. /// /// Index of the record. /// Object that will edit the record data if modified. public SuperTrainingSpeciesRecord GetHolder1(int index) { if ((uint)index >= MAX) throw new ArgumentOutOfRangeException(nameof(index)); return new SuperTrainingSpeciesRecord(Data, Offset + 0x188 + (4 * index)); } /// /// Returns an object which will edit the record directly from data. /// /// Index of the record. /// Object that will edit the record data if modified. public SuperTrainingSpeciesRecord GetHolder2(int index) { if ((uint)index >= MAX) throw new ArgumentOutOfRangeException(nameof(index)); return new SuperTrainingSpeciesRecord(Data, Offset + 0x248 + (4 * index)); } /// /// Gets the bag from the desired . /// /// Bag index public byte GetBag(int index) { if ((uint)index >= MAX_BAG) throw new ArgumentOutOfRangeException(nameof(index)); return Data[Offset + 0x308 + index]; } /// /// Sets a bag to the desired . /// /// Bag index /// Bag ID public void SetBag(int index, byte value) { if ((uint)index >= MAX_BAG) throw new ArgumentOutOfRangeException(nameof(index)); Data[Offset + 0x308 + index] = value; } /// /// Gets the next open bag index. /// /// Bag index that is empty public int GetOpenBagIndex() { for (int i = 0; i < MAX_BAG; i++) { if (GetBag(i) != 0) return i; } return -1; } /// /// Adds a bag to the list of bags. /// /// Bag type /// Bag was added or not public bool AddBag(byte bag) { int index = GetOpenBagIndex(); if (index < 0) return false; SetBag(index, bag); return true; } /// /// Removes a bag from the list of bags. /// /// Bag index 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); } /// /// Clears all data for the record. /// /// Index of the record. public void ClearRecord1(int index) { if ((uint)index >= MAX) throw new ArgumentOutOfRangeException(nameof(index)); SetTime1(index, 0f); GetHolder1(index).Clear(); } /// /// Clears all data for the record. /// /// Index of the record. public void ClearRecord2(int index) { if ((uint)index >= MAX) throw new ArgumentOutOfRangeException(nameof(index)); SetTime2(index, 0f); GetHolder2(index).Clear(); } /// /// Unlocks all stages. /// /// Unlock all Distribution stages too. 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); } /// /// Clears all data in the block. /// 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; } /// /// of the Record Holder. /// public int Species { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0), (ushort)value); } /// /// of the Record Holder. /// public int Form { get => Data[Offset + 2]; set => Data[Offset + 2] = (byte)value; } /// /// of the Record Holder. /// /// public int Gender { get => Data[Offset + 3]; set => Data[Offset + 3] = (byte)value; } /// /// Wipes the record holder's pk-related data. /// public void Clear() => Species = Form = Gender = 0; /// /// Sets the data to match what is in the provided reference. /// /// Reference to load from. public void LoadFrom(PKM pk) { Species = pk.Species; Form = pk.Form; Gender = pk.Gender; } }