2022-06-18 18:04:24 +00:00
|
|
|
using System;
|
2022-01-03 05:35:59 +00:00
|
|
|
using static System.Buffers.Binary.BinaryPrimitives;
|
2018-08-10 04:53:39 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
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
|
2018-08-10 04:53:39 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
public const int Size = 0x358; // 856
|
|
|
|
public override int Generation => 4;
|
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
|
|
|
public override EntityContext Context => EntityContext.Gen4;
|
2023-01-22 04:02:33 +00:00
|
|
|
public override bool FatefulEncounter => Gift.PK.FatefulEncounter;
|
|
|
|
public override GameVersion Version { get=> Gift.Version; set => Gift.Version = value; }
|
2022-06-18 18:04:24 +00:00
|
|
|
|
|
|
|
public override byte Level
|
2018-08-10 04:53:39 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
get => Gift.Level;
|
|
|
|
set => Gift.Level = value;
|
|
|
|
}
|
2018-08-10 04:53:39 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public override int Ball
|
|
|
|
{
|
|
|
|
get => Gift.Ball;
|
|
|
|
set => Gift.Ball = value;
|
|
|
|
}
|
2018-08-10 04:53:39 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public PCD() : this(new byte[Size]) { }
|
|
|
|
public PCD(byte[] data) : base(data) { }
|
2018-08-10 04:53:39 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
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;
|
|
|
|
}
|
2018-08-10 04:53:39 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public PGT Gift
|
|
|
|
{
|
|
|
|
get => _gift ??= new PGT(Data.Slice(0, PGT.Size));
|
|
|
|
set => (_gift = value).Data.CopyTo(Data, 0);
|
|
|
|
}
|
2019-12-25 07:24:28 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private PGT? _gift;
|
2018-08-10 04:53:39 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public Span<byte> GetMetadata() => Data.AsSpan(PGT.Size);
|
|
|
|
public void SetMetadata(ReadOnlySpan<byte> data) => data.CopyTo(Data.AsSpan(PGT.Size));
|
2018-08-10 04:53:39 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public override bool GiftUsed { get => Gift.GiftUsed; set => Gift.GiftUsed = value; }
|
|
|
|
public override bool IsEntity { get => Gift.IsEntity; set => Gift.IsEntity = 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;
|
2018-08-10 04:53:39 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public override int CardID
|
|
|
|
{
|
|
|
|
get => ReadUInt16LittleEndian(Data.AsSpan(0x150));
|
|
|
|
set => WriteUInt16LittleEndian(Data.AsSpan(0x150), (ushort)value);
|
|
|
|
}
|
2018-08-10 04:53:39 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private const int TitleLength = 0x48;
|
2018-08-10 04:53:39 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private Span<byte> CardTitleSpan => Data.AsSpan(0x104, TitleLength);
|
2018-08-10 04:53:39 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public override string CardTitle
|
|
|
|
{
|
|
|
|
get => StringConverter4.GetString(CardTitleSpan);
|
2023-01-22 04:02:33 +00:00
|
|
|
set => StringConverter4.SetString(CardTitleSpan, value, TitleLength / 2, StringConverterOption.ClearFF);
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2022-01-03 05:35:59 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public ushort CardCompatibility => ReadUInt16LittleEndian(Data.AsSpan(0x14C)); // rest of bytes we don't really care about
|
|
|
|
|
2022-08-27 06:43:36 +00:00
|
|
|
public override ushort Species { get => Gift.IsManaphyEgg ? (ushort)490 : Gift.Species; set => Gift.Species = value; }
|
2022-08-22 00:34:32 +00:00
|
|
|
public override Moveset Moves { get => Gift.Moves; set => Gift.Moves = value; }
|
2022-06-18 18:04:24 +00:00
|
|
|
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; }
|
2022-08-27 06:43:36 +00:00
|
|
|
public override byte Form { get => Gift.Form; set => Gift.Form = value; }
|
2023-01-22 04:02:33 +00:00
|
|
|
public override uint ID32 { get => Gift.ID32; set => Gift.ID32 = value; }
|
|
|
|
public override ushort TID16 { get => Gift.TID16; set => Gift.TID16 = value; }
|
|
|
|
public override ushort SID16 { get => Gift.SID16; set => Gift.SID16 = value; }
|
2022-06-18 18:04:24 +00:00
|
|
|
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++)
|
2018-08-10 04:53:39 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
if (g[i] != c[i])
|
|
|
|
return false;
|
2018-08-10 04:53:39 +00:00
|
|
|
}
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
for (int i = 3; i < g.Length; i++)
|
2018-08-10 04:53:39 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
if (g[i] != c[i])
|
2018-08-10 04:53:39 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-01-22 04:02:33 +00:00
|
|
|
public override PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
|
|
|
return Gift.ConvertToPKM(tr, criteria);
|
|
|
|
}
|
2018-08-10 04:53:39 +00:00
|
|
|
|
2023-01-31 06:57:18 +00:00
|
|
|
public bool CanBeReceivedByVersion(int pkmVersion) => ((CardCompatibility >> pkmVersion) & 1) == 1;
|
2018-12-27 09:00:08 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public override bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|
|
|
{
|
|
|
|
var wc = Gift.PK;
|
|
|
|
if (!wc.IsEgg)
|
2018-12-27 09:00:08 +00:00
|
|
|
{
|
2023-01-22 04:02:33 +00:00
|
|
|
if (wc.TID16 != pk.TID16) return false;
|
|
|
|
if (wc.SID16 != pk.SID16) return false;
|
2022-06-18 18:04:24 +00:00
|
|
|
if (wc.OT_Name != pk.OT_Name) return false;
|
|
|
|
if (wc.OT_Gender != pk.OT_Gender) return false;
|
|
|
|
if (wc.Language != 0 && wc.Language != pk.Language) return false;
|
|
|
|
|
|
|
|
if (pk.Format != 4) // transferred
|
2018-12-27 09:00:08 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
// met location: deferred to general transfer check
|
|
|
|
if (wc.CurrentLevel > pk.Met_Level) return false;
|
|
|
|
if (!IsMatchEggLocation(pk))
|
|
|
|
return false;
|
2018-12-27 09:00:08 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
else
|
2018-12-27 09:00:08 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
if (wc.Egg_Location + 3000 != pk.Met_Location) return false;
|
|
|
|
if (wc.CurrentLevel != pk.Met_Level) return false;
|
2018-12-27 09:00:08 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
|
|
|
else // Egg
|
|
|
|
{
|
|
|
|
if (wc.Egg_Location + 3000 != pk.Egg_Location && pk.Egg_Location != Locations.LinkTrade4) // traded
|
|
|
|
return false;
|
|
|
|
if (wc.CurrentLevel != pk.Met_Level)
|
2018-12-27 09:00:08 +00:00
|
|
|
return false;
|
2023-01-22 04:02:33 +00:00
|
|
|
if (pk is { IsEgg: true, IsNative: false })
|
2022-06-18 18:04:24 +00:00
|
|
|
return false;
|
|
|
|
}
|
2018-12-27 09:00:08 +00:00
|
|
|
|
2022-12-08 07:28:47 +00:00
|
|
|
if (wc.Form != evo.Form && !FormInfo.IsFormChangeable(wc.Species, wc.Form, pk.Form, Context, pk.Context))
|
2022-06-18 18:04:24 +00:00
|
|
|
return false;
|
2018-12-27 09:00:08 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (wc.Ball != pk.Ball) return false;
|
|
|
|
if (wc.OT_Gender < 3 && wc.OT_Gender != pk.OT_Gender) return false;
|
2020-09-03 21:28:51 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
// Milotic is the only gift to come with Contest stats.
|
2022-11-25 01:42:17 +00:00
|
|
|
if (wc.Species == (int)Core.Species.Milotic && pk is IContestStatsReadOnly s && s.IsContestBelow(wc))
|
2022-06-18 18:04:24 +00:00
|
|
|
return false;
|
2020-08-29 21:33:51 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (IsRandomPID())
|
|
|
|
{
|
|
|
|
// Random PID, never shiny
|
|
|
|
// PID=0 was never used (pure random).
|
|
|
|
if (pk.IsShiny)
|
|
|
|
return false;
|
2020-08-29 21:33:51 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
// 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 != pk.Gender)
|
|
|
|
return false;
|
2018-12-27 09:00:08 +00:00
|
|
|
}
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
return true;
|
2018-08-10 04:53:39 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
2023-01-31 06:57:18 +00:00
|
|
|
protected override bool IsMatchPartial(PKM pk) => !CanBeReceivedByVersion(pk.Version);
|
2022-06-18 18:04:24 +00:00
|
|
|
protected override bool IsMatchDeferred(PKM pk) => Species != pk.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
|
2018-08-10 04:53:39 +00:00
|
|
|
}
|