PKHeX/PKHeX.Core/MysteryGifts/PCD.cs
Kurt ccf87242c1 Eliminate boxing on encounter search (criteria)
struct implementing interface is boxed when passed to method that accepts interface (not generic method).
Removes IDexLevel (no other inheritors but EvoCriteria) and uses the primitive the data is stored (array, not IReadOnlyList) for slightly better perf.
2022-05-07 18:29:36 -07:00

213 lines
9.3 KiB
C#

using System;
using System.Collections.Generic;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core
{
/// <summary>
/// Generation 4 Mystery Gift Template File
/// </summary>
/// <remarks>
/// Big thanks to Grovyle91's Pokémon Mystery Gift Editor, from which the structure was referenced.
/// https://projectpokemon.org/home/profile/859-grovyle91/
/// https://projectpokemon.org/home/forums/topic/5870-pok%C3%A9mon-mystery-gift-editor-v143-now-with-bw-support/
/// See also: http://tccphreak.shiny-clique.net/debugger/pcdfiles.htm
/// </remarks>
public sealed class PCD : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4
{
public const int Size = 0x358; // 856
public override int Generation => 4;
public override byte Level
{
get => Gift.Level;
set => Gift.Level = value;
}
public override int Ball
{
get => Gift.Ball;
set => Gift.Ball = value;
}
public PCD() : this(new byte[Size]) { }
public PCD(byte[] data) : base(data) { }
public override byte[] Write()
{
// Ensure PGT content is encrypted
var clone = new PCD((byte[])Data.Clone());
if (clone.Gift.VerifyPKEncryption())
clone.Gift = clone.Gift;
return clone.Data;
}
public PGT Gift
{
get => _gift ??= new PGT(Data.Slice(0, PGT.Size));
set => (_gift = value).Data.CopyTo(Data, 0);
}
private PGT? _gift;
public Span<byte> GetMetadata() => Data.AsSpan(PGT.Size);
public void SetMetadata(ReadOnlySpan<byte> data) => data.CopyTo(Data.AsSpan(PGT.Size));
public override bool GiftUsed { get => Gift.GiftUsed; set => Gift.GiftUsed = value; }
public override bool IsPokémon { get => Gift.IsPokémon; set => Gift.IsPokémon = value; }
public override bool IsItem { get => Gift.IsItem; set => Gift.IsItem = value; }
public override int ItemID { get => Gift.ItemID; set => Gift.ItemID = value; }
public bool IsLockCapsule => IsItem && ItemID == 533; // 0x215
public bool CanConvertToPGT => !IsLockCapsule;
public override int CardID
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x150));
set => WriteUInt16LittleEndian(Data.AsSpan(0x150), (ushort)value);
}
private const int TitleLength = 0x48;
private Span<byte> CardTitleSpan => Data.AsSpan(0x104, TitleLength);
public override string CardTitle
{
get => StringConverter4.GetString(CardTitleSpan);
set => StringConverter4.SetString(CardTitleSpan, value.AsSpan(), TitleLength / 2, StringConverterOption.ClearFF);
}
public ushort CardCompatibility => ReadUInt16LittleEndian(Data.AsSpan(0x14C)); // rest of bytes we don't really care about
public override int Species { get => Gift.IsManaphyEgg ? 490 : Gift.Species; set => Gift.Species = value; }
public override IReadOnlyList<int> Moves { get => Gift.Moves; set => Gift.Moves = value; }
public override int HeldItem { get => Gift.HeldItem; set => Gift.HeldItem = value; }
public override bool IsShiny => Gift.IsShiny;
public override Shiny Shiny => Gift.Shiny;
public override bool IsEgg { get => Gift.IsEgg; set => Gift.IsEgg = value; }
public override int Gender { get => Gift.Gender; set => Gift.Gender = value; }
public override int Form { get => Gift.Form; set => Gift.Form = value; }
public override int TID { get => Gift.TID; set => Gift.TID = value; }
public override int SID { get => Gift.SID; set => Gift.SID = value; }
public override string OT_Name { get => Gift.OT_Name; set => Gift.OT_Name = value; }
public override AbilityPermission Ability => Gift.Ability;
public override bool HasFixedIVs => Gift.HasFixedIVs;
public override void GetIVs(Span<int> value) => Gift.GetIVs(value);
// ILocation overrides
public override int Location { get => IsEgg ? 0 : Gift.EggLocation + 3000; set { } }
public override int EggLocation { get => IsEgg ? Gift.EggLocation + 3000 : 0; set { } }
public bool GiftEquals(PGT pgt)
{
// Skip over the PGT's "Corresponding PCD Slot" @ 0x02
byte[] g = pgt.Data;
byte[] c = Gift.Data;
if (g.Length != c.Length || g.Length < 3)
return false;
for (int i = 0; i < 2; i++)
{
if (g[i] != c[i])
return false;
}
for (int i = 3; i < g.Length; i++)
{
if (g[i] != c[i])
return false;
}
return true;
}
public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
{
return Gift.ConvertToPKM(sav, criteria);
}
public bool CanBeReceivedBy(int pkmVersion) => (CardCompatibility >> pkmVersion & 1) == 1;
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
var wc = Gift.PK;
if (!wc.IsEgg)
{
if (wc.TID != pkm.TID) return false;
if (wc.SID != pkm.SID) return false;
if (wc.OT_Name != pkm.OT_Name) return false;
if (wc.OT_Gender != pkm.OT_Gender) return false;
if (wc.Language != 0 && wc.Language != pkm.Language) return false;
if (pkm.Format != 4) // transferred
{
// met location: deferred to general transfer check
if (wc.CurrentLevel > pkm.Met_Level) return false;
}
else
{
if (wc.Egg_Location + 3000 != pkm.Met_Location) return false;
if (wc.CurrentLevel != pkm.Met_Level) return false;
}
}
else // Egg
{
if (wc.Egg_Location + 3000 != pkm.Egg_Location && pkm.Egg_Location != Locations.LinkTrade4) // traded
return false;
if (wc.CurrentLevel != pkm.Met_Level)
return false;
if (pkm.IsEgg && !pkm.IsNative)
return false;
}
if (wc.Form != evo.Form && !FormInfo.IsFormChangeable(wc.Species, wc.Form, pkm.Form, pkm.Format))
return false;
if (wc.Ball != pkm.Ball) return false;
if (wc.OT_Gender < 3 && wc.OT_Gender != pkm.OT_Gender) return false;
// Milotic is the only gift to come with Contest stats.
if (wc.Species == (int)Core.Species.Milotic && pkm is IContestStats s && s.IsContestBelow(wc))
return false;
if (IsRandomPID())
{
// Random PID, never shiny
// PID=0 was never used (pure random).
if (pkm.IsShiny)
return false;
// Don't check gender. All gifts that have PID=1 are genderless, with one exception.
// The KOR Arcanine can end up with either gender, even though the template may have a specified gender.
}
else
{
// Fixed PID
if (wc.Gender != pkm.Gender)
return false;
}
return true;
}
protected override bool IsMatchPartial(PKM pkm) => CanBeReceivedBy(pkm.Version);
protected override bool IsMatchDeferred(PKM pkm) => Species != pkm.Species;
public bool RibbonEarth { get => Gift.RibbonEarth; set => Gift.RibbonEarth = value; }
public bool RibbonNational { get => Gift.RibbonNational; set => Gift.RibbonNational = value; }
public bool RibbonCountry { get => Gift.RibbonCountry; set => Gift.RibbonCountry = value; }
public bool RibbonChampionBattle { get => Gift.RibbonChampionBattle; set => Gift.RibbonChampionBattle = value; }
public bool RibbonChampionRegional { get => Gift.RibbonChampionRegional; set => Gift.RibbonChampionRegional = value; }
public bool RibbonChampionNational { get => Gift.RibbonChampionNational; set => Gift.RibbonChampionNational = value; }
public bool RibbonClassic { get => Gift.RibbonClassic; set => Gift.RibbonClassic = value; }
public bool RibbonWishing { get => Gift.RibbonWishing; set => Gift.RibbonWishing = value; }
public bool RibbonPremier { get => Gift.RibbonPremier; set => Gift.RibbonPremier = value; }
public bool RibbonEvent { get => Gift.RibbonEvent; set => Gift.RibbonEvent = value; }
public bool RibbonBirthday { get => Gift.RibbonBirthday; set => Gift.RibbonBirthday = value; }
public bool RibbonSpecial { get => Gift.RibbonSpecial; set => Gift.RibbonSpecial = value; }
public bool RibbonWorld { get => Gift.RibbonWorld; set => Gift.RibbonWorld = value; }
public bool RibbonChampionWorld { get => Gift.RibbonChampionWorld; set => Gift.RibbonChampionWorld = value; }
public bool RibbonSouvenir { get => Gift.RibbonSouvenir; set => Gift.RibbonSouvenir = value; }
public bool IsFixedPID() => Gift.PK.PID != 1;
public bool IsRandomPID() => Gift.PK.PID == 1; // nothing used 0 (full random), always anti-shiny
}
}