using System; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; /// /// Side game data for data transferred into HOME. /// public sealed class GameDataPB7 : HomeOptional1, IGameDataSide, IScaledSizeAbsolute, IMemoryOT, IGameDataSplitAbility { private const HomeGameDataFormat ExpectFormat = HomeGameDataFormat.PB7; private const int SIZE = HomeCrypto.SIZE_2GAME_PB7; protected override HomeGameDataFormat Format => ExpectFormat; public GameDataPB7() : base(SIZE) { } public GameDataPB7(Memory data) : base(data) => EnsureSize(SIZE); public GameDataPB7 Clone() => new(ToArray()); public int WriteTo(Span result) => WriteWithHeader(result); #region Structure public byte AV_HP { get => Data[0x00]; set => Data[0x00] = value; } public byte AV_ATK { get => Data[0x01]; set => Data[0x01] = value; } public byte AV_DEF { get => Data[0x02]; set => Data[0x02] = value; } public byte AV_SPE { get => Data[0x03]; set => Data[0x03] = value; } public byte AV_SPA { get => Data[0x04]; set => Data[0x04] = value; } public byte AV_SPD { get => Data[0x05]; set => Data[0x05] = value; } public byte ResortEventState { get => Data[0x06]; set => Data[0x06] = value; } public ushort Move1 { get => ReadUInt16LittleEndian(Data[0x07..]); set => WriteUInt16LittleEndian(Data[0x07..], value); } public ushort Move2 { get => ReadUInt16LittleEndian(Data[0x09..]); set => WriteUInt16LittleEndian(Data[0x09..], value); } public ushort Move3 { get => ReadUInt16LittleEndian(Data[0x0B..]); set => WriteUInt16LittleEndian(Data[0x0B..], value); } public ushort Move4 { get => ReadUInt16LittleEndian(Data[0x0D..]); set => WriteUInt16LittleEndian(Data[0x0D..], value); } public int Move1_PP { get => Data[0x0F]; set => Data[0x0F] = (byte)value; } public int Move2_PP { get => Data[0x10]; set => Data[0x10] = (byte)value; } public int Move3_PP { get => Data[0x11]; set => Data[0x11] = (byte)value; } public int Move4_PP { get => Data[0x12]; set => Data[0x12] = (byte)value; } public int Move1_PPUps { get => Data[0x13]; set => Data[0x13] = (byte)value; } public int Move2_PPUps { get => Data[0x14]; set => Data[0x14] = (byte)value; } public int Move3_PPUps { get => Data[0x15]; set => Data[0x15] = (byte)value; } public int Move4_PPUps { get => Data[0x16]; set => Data[0x16] = (byte)value; } public ushort RelearnMove1 { get => ReadUInt16LittleEndian(Data[0x17..]); set => WriteUInt16LittleEndian(Data[0x17..], value); } public ushort RelearnMove2 { get => ReadUInt16LittleEndian(Data[0x19..]); set => WriteUInt16LittleEndian(Data[0x19..], value); } public ushort RelearnMove3 { get => ReadUInt16LittleEndian(Data[0x1B..]); set => WriteUInt16LittleEndian(Data[0x1B..], value); } public ushort RelearnMove4 { get => ReadUInt16LittleEndian(Data[0x1D..]); set => WriteUInt16LittleEndian(Data[0x1D..], value); } public float HeightAbsolute { get => ReadSingleLittleEndian(Data[0x1F..]); set => WriteSingleLittleEndian(Data[0x1F..], value); } public float WeightAbsolute { get => ReadSingleLittleEndian(Data[0x23..]); set => WriteSingleLittleEndian(Data[0x23..], value); } public byte FieldEventFatigue1 { get => Data[0x27]; set => Data[0x27] = value; } public byte FieldEventFatigue2 { get => Data[0x28]; set => Data[0x28] = value; } public byte Fullness { get => Data[0x29]; set => Data[0x29] = value; } public byte Rank { get => Data[0x2A]; set => Data[0x2A] = value; } public int OT_Affection { get => Data[0x2B]; set => Data[0x2B] = (byte)value; } public byte OT_Intensity { get => Data[0x2C]; set => Data[0x2C] = value; } public byte OT_Memory { get => Data[0x2D]; set => Data[0x2D] = value; } public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data[0x2E..]); set => WriteUInt16LittleEndian(Data[0x2E..], value); } public byte OT_Feeling { get => Data[0x30]; set => Data[0x30] = value; } public byte Enjoyment { get => Data[0x31]; set => Data[0x31] = value; } public uint GeoPadding { get => ReadUInt32LittleEndian(Data[0x32..]); set => WriteUInt32LittleEndian(Data[0x32..], value); } public int Ball { get => Data[0x36]; set => Data[0x36] = (byte)value; } public int Egg_Location { get => ReadUInt16LittleEndian(Data[0x37..]); set => WriteUInt16LittleEndian(Data[0x37..], (ushort)value); } public int Met_Location { get => ReadUInt16LittleEndian(Data[0x39..]); set => WriteUInt16LittleEndian(Data[0x39..], (ushort)value); } public byte PKRS { get => Data[0x3B]; set => Data[0x3B] = value; } public ushort Ability { get => ReadUInt16LittleEndian(Data[0x3C..]); set => WriteUInt16LittleEndian(Data[0x3C..], value); } public byte AbilityNumber { get => Data[0x3E]; set => Data[0x3E] = value; } #endregion #region Conversion public PersonalInfo GetPersonalInfo(ushort species, byte form) => PersonalTable.GG.GetFormEntry(species, form); public void CopyTo(PB7 pk, PKH pkh) { this.CopyTo(pk); pk.AV_HP = AV_HP; pk.AV_ATK = AV_ATK; pk.AV_DEF = AV_DEF; pk.AV_SPE = AV_SPE; pk.AV_SPA = AV_SPA; pk.AV_SPD = AV_SPD; pk.ResortEventStatus = (ResortEventState)ResortEventState; pk.HeightAbsolute = pk.CalcHeightAbsolute; // Ignore the stored value, be nice and recalculate for the user. pk.WeightAbsolute = pk.CalcWeightAbsolute; // Ignore the stored value, be nice and recalculate for the user. // Some fields are unused as PB7, don't bother copying. pk.FieldEventFatigue1 = FieldEventFatigue1; pk.FieldEventFatigue2 = FieldEventFatigue2; pk.Fullness = Fullness; // pk.Rank = Rank; // pk.OT_Affection // pk.OT_Intensity // pk.OT_Memory // pk.OT_TextVar // pk.OT_Feeling pk.Enjoyment = Enjoyment; // pk.GeoPadding = GeoPadding; pk.AbilityNumber = AbilityNumber; pk.Ability = Ability; } public void CopyFrom(PB7 pk, PKH pkh) { this.CopyFrom(pk); AV_HP = pk.AV_HP; AV_ATK = pk.AV_ATK; AV_DEF = pk.AV_DEF; AV_SPE = pk.AV_SPE; AV_SPA = pk.AV_SPA; AV_SPD = pk.AV_SPD; ResortEventState = (byte)pk.ResortEventStatus; HeightAbsolute = pk.CalcHeightAbsolute; // Ignore the stored value, be nice and recalculate for the user. WeightAbsolute = pk.CalcWeightAbsolute; // Ignore the stored value, be nice and recalculate for the user. // Some fields are unused as PB7, don't bother copying. FieldEventFatigue1 = pk.FieldEventFatigue1; FieldEventFatigue2 = pk.FieldEventFatigue2; Fullness = pk.Fullness; // Rank = pk.Rank; // OT_Affection // OT_Intensity // OT_Memory // OT_TextVar // OT_Feeling Enjoyment = pk.Enjoyment; // GeoPadding = pk.GeoPadding; AbilityNumber = (byte)pk.AbilityNumber; Ability = (ushort)pk.Ability; // All other side formats have HT Language. Just fake a value. if (pkh is { HT_Language: 0, IsUntraded: false }) pkh.HT_Language = (byte)pk.Language; } public PB7 ConvertToPKM(PKH pkh) { var pk = new PB7(); pkh.CopyTo(pk); CopyTo(pk, pkh); pk.ResetCalculatedValues(); pk.ResetPartyStats(); pk.RefreshChecksum(); return pk; } #endregion /// Reconstructive logic to best apply suggested values. public static GameDataPB7? TryCreate(PKH pkh) { if (!PersonalTable.GG.IsPresentInGame(pkh.Species, pkh.Form)) return null; // There isn't an actual preference since this format cannot naturally backwards transfer. // Just pick out the first one. var result = CreateInternal(pkh); if (result == null) return null; result.PopulateFromCore(pkh); return result; } private static GameDataPB7? CreateInternal(PKH pkh) { var side = GetNearestNeighbor(pkh); if (side == null) return null; var ball = side.Ball; if (pkh.Version is (int)GameVersion.GO) return new GameDataPB7 { Ball = ball, Met_Location = Locations.GO7 }; if (pkh.Version is (int)GameVersion.GP or (int)GameVersion.GE) return new GameDataPB7 { Ball = ball, Met_Location = side.Met_Location }; var result = new GameDataPB7(); result.InitializeFrom(side, pkh); return result; } public void InitializeFrom(IGameDataSide side, PKH pkh) { Met_Location = side.Met_Location == Locations.Default8bNone ? 0 : side.Met_Location; Egg_Location = side.Egg_Location == Locations.Default8bNone ? 0 : side.Egg_Location; if (side is IGameDataSplitAbility a) AbilityNumber = a.AbilityNumber; else AbilityNumber = 1; } private void PopulateFromCore(PKH pkh) { var pi = PersonalTable.GG.GetFormEntry(pkh.Species, pkh.Form); HeightAbsolute = PB7.GetHeightAbsolute(pi, pkh.HeightScalar); WeightAbsolute = PB7.GetWeightAbsolute(pi, pkh.HeightScalar, pkh.WeightScalar); Ability = (ushort)pi.GetAbilityAtIndex(AbilityNumber >> 1); } private static IGameDataSide? GetNearestNeighbor(PKH pkh) => pkh.DataPK9 as IGameDataSide ?? pkh.DataPB8 as IGameDataSide ?? pkh.DataPK8 as IGameDataSide ?? pkh.DataPB7; public static T Create(GameDataPB7 data) where T : IGameDataSide, new() => new() { Ball = data.Ball, Met_Location = data.Met_Location, Egg_Location = data.Egg_Location, }; }