mirror of
https://github.com/kwsch/PKHeX
synced 2025-01-05 09:08:45 +00:00
ccf87242c1
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.
302 lines
13 KiB
C#
302 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using static System.Buffers.Binary.BinaryPrimitives;
|
|
|
|
namespace PKHeX.Core
|
|
{
|
|
/// <summary>
|
|
/// Generation 4 Mystery Gift Template File (Inner Gift Data, no card data)
|
|
/// </summary>
|
|
public sealed class PGT : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4
|
|
{
|
|
public const int Size = 0x104; // 260
|
|
public override int Generation => 4;
|
|
|
|
public override byte Level
|
|
{
|
|
get => IsManaphyEgg ? (byte)1 : IsPokémon ? (byte)PK.Met_Level : (byte)0;
|
|
set { if (IsPokémon) PK.Met_Level = value; }
|
|
}
|
|
|
|
public override int Ball
|
|
{
|
|
get => IsPokémon ? PK.Ball : 0;
|
|
set { if (IsPokémon) PK.Ball = value; }
|
|
}
|
|
|
|
public override AbilityPermission Ability => IsManaphyEgg ? AbilityPermission.Any12 : (int)(PK.PID & 1) == 1 ? AbilityPermission.OnlySecond : AbilityPermission.OnlyFirst;
|
|
|
|
private enum GiftType
|
|
{
|
|
Pokémon = 1,
|
|
PokémonEgg = 2,
|
|
Item = 3,
|
|
Rule = 4,
|
|
Seal = 5,
|
|
Accessory = 6,
|
|
ManaphyEgg = 7,
|
|
MemberCard = 8,
|
|
OaksLetter = 9,
|
|
AzureFlute = 10,
|
|
PokétchApp = 11,
|
|
Ribbon = 12,
|
|
PokéWalkerArea = 14,
|
|
}
|
|
|
|
public override string CardTitle { get => "Raw Gift (PGT)"; set { } }
|
|
public override int CardID { get => -1; set { } }
|
|
public override bool GiftUsed { get => false; set { } }
|
|
public override Shiny Shiny => IsEgg ? Shiny.Random : PK.PID == 1 ? Shiny.Never : IsShiny ? Shiny.Always : Shiny.Never;
|
|
|
|
public PGT() : this(new byte[Size]) { }
|
|
public PGT(byte[] data) : base(data) { }
|
|
|
|
public byte CardType { get => Data[0]; set => Data[0] = value; }
|
|
// Unused 0x01
|
|
public byte Slot { get => Data[2]; set => Data[2] = value; }
|
|
public byte Detail { get => Data[3]; set => Data[3] = value; }
|
|
public override int ItemID { get => ReadUInt16LittleEndian(Data.AsSpan(0x4)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4), (ushort)value); }
|
|
|
|
public PK4 PK
|
|
{
|
|
get => _pk ??= new PK4(Data.Slice(8, PokeCrypto.SIZE_4PARTY));
|
|
set
|
|
{
|
|
_pk = value;
|
|
var data = Array.FindIndex(value.Data, z => z != 0) == -1 // all zero
|
|
? value.Data
|
|
: PokeCrypto.EncryptArray45(value.Data);
|
|
data.CopyTo(Data, 8);
|
|
}
|
|
}
|
|
|
|
public override byte[] Write()
|
|
{
|
|
// Ensure PGT content is encrypted
|
|
var clone = new PGT((byte[])Data.Clone());
|
|
clone.VerifyPKEncryption();
|
|
return clone.Data;
|
|
}
|
|
|
|
private PK4? _pk;
|
|
|
|
/// <summary>
|
|
/// Double checks the encryption of the gift data for Pokemon data.
|
|
/// </summary>
|
|
/// <returns>True if data was encrypted, false if the data was not modified.</returns>
|
|
public bool VerifyPKEncryption()
|
|
{
|
|
if (PGTGiftType is not (GiftType.Pokémon or GiftType.PokémonEgg))
|
|
return false; // not encrypted
|
|
if (ReadUInt32LittleEndian(Data.AsSpan(0x64 + 8)) != 0)
|
|
return false; // already encrypted (unused PK4 field, zero)
|
|
EncryptPK();
|
|
return true;
|
|
}
|
|
|
|
private void EncryptPK()
|
|
{
|
|
var span = Data.AsSpan(8, PokeCrypto.SIZE_4PARTY);
|
|
var ekdata = PokeCrypto.EncryptArray45(span);
|
|
ekdata.CopyTo(span);
|
|
}
|
|
|
|
private GiftType PGTGiftType { get => (GiftType)Data[0]; set => Data[0] = (byte)value; }
|
|
public bool IsHatched => PGTGiftType == GiftType.Pokémon;
|
|
public override bool IsEgg { get => PGTGiftType == GiftType.PokémonEgg || IsManaphyEgg; set { if (value) { PGTGiftType = GiftType.PokémonEgg; PK.IsEgg = true; } } }
|
|
public bool IsManaphyEgg { get => PGTGiftType == GiftType.ManaphyEgg; set { if (value) PGTGiftType = GiftType.ManaphyEgg; } }
|
|
public override bool EggEncounter => IsEgg;
|
|
public override bool IsItem { get => PGTGiftType == GiftType.Item; set { if (value) PGTGiftType = GiftType.Item; } }
|
|
public override bool IsPokémon { get => PGTGiftType is GiftType.Pokémon or GiftType.PokémonEgg or GiftType.ManaphyEgg; set { } }
|
|
|
|
public override int Species { get => IsManaphyEgg ? 490 : PK.Species; set => PK.Species = value; }
|
|
public override IReadOnlyList<int> Moves { get => PK.Moves; set => PK.SetMoves(value.ToArray()); }
|
|
public override int HeldItem { get => PK.HeldItem; set => PK.HeldItem = value; }
|
|
public override bool IsShiny => PK.IsShiny;
|
|
public override int Gender { get => PK.Gender; set => PK.Gender = value; }
|
|
public override int Form { get => PK.Form; set => PK.Form = value; }
|
|
public override int TID { get => (ushort)PK.TID; set => PK.TID = value; }
|
|
public override int SID { get => (ushort)PK.SID; set => PK.SID = value; }
|
|
public override string OT_Name { get => PK.OT_Name; set => PK.OT_Name = value; }
|
|
public override int Location { get => PK.Met_Location; set => PK.Met_Location = value; }
|
|
public override int EggLocation { get => PK.Egg_Location; set => PK.Egg_Location = value; }
|
|
public override bool HasFixedIVs => PK.IV32 != 0;
|
|
public override void GetIVs(Span<int> value)
|
|
{
|
|
if (HasFixedIVs)
|
|
PK.GetIVs(value);
|
|
}
|
|
|
|
public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
|
|
{
|
|
if (!IsPokémon)
|
|
throw new ArgumentException(nameof(IsPokémon));
|
|
|
|
// template is already filled out, only minor mutations required
|
|
PK4 pk4 = new((byte[])PK.Data.Clone()) { Sanity = 0 };
|
|
if (!IsHatched && Detail == 0)
|
|
{
|
|
pk4.OT_Name = sav.OT;
|
|
pk4.TID = sav.TID;
|
|
pk4.SID = sav.SID;
|
|
pk4.OT_Gender = sav.Gender;
|
|
pk4.Language = sav.Language;
|
|
}
|
|
|
|
if (IsManaphyEgg)
|
|
SetDefaultManaphyEggDetails(pk4, sav);
|
|
|
|
SetPINGA(pk4, criteria);
|
|
SetMetData(pk4, sav);
|
|
|
|
var pi = pk4.PersonalInfo;
|
|
pk4.CurrentFriendship = pk4.IsEgg ? pi.HatchCycles : pi.BaseFriendship;
|
|
|
|
pk4.RefreshChecksum();
|
|
return pk4;
|
|
}
|
|
|
|
private void SetMetData(PK4 pk4, ITrainerInfo trainer)
|
|
{
|
|
if (!EggEncounter)
|
|
{
|
|
pk4.Met_Location = pk4.Egg_Location + 3000;
|
|
pk4.Egg_Location = 0;
|
|
pk4.MetDate = DateTime.Now;
|
|
pk4.IsEgg = false;
|
|
}
|
|
else
|
|
{
|
|
pk4.Egg_Location += 3000;
|
|
if (trainer.Generation == 4)
|
|
SetUnhatchedEggDetails(pk4);
|
|
else
|
|
SetHatchedEggDetails(pk4);
|
|
}
|
|
}
|
|
|
|
private static void SetDefaultManaphyEggDetails(PK4 pk4, ITrainerInfo trainer)
|
|
{
|
|
// Since none of this data is populated, fill in default info.
|
|
pk4.Species = (int)Core.Species.Manaphy;
|
|
pk4.Gender = 2;
|
|
// Level 1 Moves
|
|
pk4.Move1 = 294; pk4.Move1_PP = 20;
|
|
pk4.Move2 = 145; pk4.Move2_PP = 30;
|
|
pk4.Move3 = 346; pk4.Move3_PP = 15;
|
|
pk4.Ability = (int)Core.Ability.Hydration;
|
|
pk4.FatefulEncounter = true;
|
|
pk4.Ball = (int)Core.Ball.Poke;
|
|
pk4.Version = GameVersion.Gen4.Contains(trainer.Game) ? trainer.Game : (int)GameVersion.D;
|
|
var lang = trainer.Language < (int)LanguageID.Korean ? trainer.Language : (int)LanguageID.English;
|
|
pk4.Language = lang;
|
|
pk4.Egg_Location = 1; // Ranger (will be +3000 later)
|
|
pk4.Nickname = SpeciesName.GetSpeciesNameGeneration((int)Core.Species.Manaphy, lang, 4);
|
|
}
|
|
|
|
private void SetPINGA(PK4 pk4, EncounterCriteria criteria)
|
|
{
|
|
// Ability is forced already, can't force anything
|
|
|
|
// Generate PID
|
|
var seed = SetPID(pk4, criteria);
|
|
|
|
if (!IsManaphyEgg)
|
|
seed = Util.Rand32(); // reseed, do not have method 1 correlation
|
|
|
|
// Generate IVs
|
|
if (pk4.IV32 == 0) // Ignore Nickname/Egg flag bits; none are set for varied-IV gifts.
|
|
{
|
|
uint iv1 = ((seed = RNG.LCRNG.Next(seed)) >> 16) & 0x7FFF;
|
|
uint iv2 = ((RNG.LCRNG.Next(seed)) >> 16) & 0x7FFF;
|
|
pk4.IV32 = iv1 | iv2 << 15;
|
|
}
|
|
}
|
|
|
|
private uint SetPID(PK4 pk4, EncounterCriteria criteria)
|
|
{
|
|
uint seed = Util.Rand32();
|
|
if (pk4.PID != 1 && !IsManaphyEgg)
|
|
return seed; // PID is already set.
|
|
|
|
// The games don't decide the Nature/Gender up-front, but we can try to honor requests.
|
|
// Pre-determine the result values, and generate something.
|
|
var n = (int)criteria.GetNature(Nature.Random);
|
|
// Gender is already pre-determined in the template.
|
|
while (true)
|
|
{
|
|
seed = GeneratePID(seed, pk4);
|
|
if (pk4.Nature != n)
|
|
continue;
|
|
return seed;
|
|
}
|
|
}
|
|
|
|
private static void SetHatchedEggDetails(PK4 pk4)
|
|
{
|
|
pk4.IsEgg = false;
|
|
// Met Location & Date is modified when transferred to pk5; don't worry about it.
|
|
pk4.EggMetDate = DateTime.Now;
|
|
}
|
|
|
|
private void SetUnhatchedEggDetails(PK4 pk4)
|
|
{
|
|
pk4.IsEgg = true;
|
|
pk4.IsNicknamed = false;
|
|
pk4.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk4.Language, Generation);
|
|
pk4.EggMetDate = DateTime.Now;
|
|
}
|
|
|
|
private static uint GeneratePID(uint seed, PK4 pk4)
|
|
{
|
|
do
|
|
{
|
|
uint pid1 = (seed = RNG.LCRNG.Next(seed)) >> 16; // low
|
|
uint pid2 = (seed = RNG.LCRNG.Next(seed)) & 0xFFFF0000; // hi
|
|
pk4.PID = pid2 | pid1;
|
|
// sanity check gender for non-genderless PID cases
|
|
} while (!pk4.IsGenderValid());
|
|
|
|
while (pk4.IsShiny) // Call the ARNG to change the PID
|
|
pk4.PID = RNG.ARNG.Next(pk4.PID);
|
|
return seed;
|
|
}
|
|
|
|
public static bool IsRangerManaphy(PKM pkm)
|
|
{
|
|
var egg = pkm.Egg_Location;
|
|
if (!pkm.IsEgg) // Link Trade Egg or Ranger
|
|
return egg is Locations.LinkTrade4 or Locations.Ranger4;
|
|
if (egg != Locations.Ranger4)
|
|
return false;
|
|
|
|
if (pkm.Language == (int)LanguageID.Korean) // never korean
|
|
return false;
|
|
|
|
var met = pkm.Met_Location;
|
|
return met is Locations.LinkTrade4 or 0;
|
|
}
|
|
|
|
// Nothing is stored as a PGT besides Ranger Manaphy. Nothing should trigger these.
|
|
public override bool IsMatchExact(PKM pkm, EvoCriteria evo) => false;
|
|
protected override bool IsMatchDeferred(PKM pkm) => false;
|
|
protected override bool IsMatchPartial(PKM pkm) => false;
|
|
|
|
public bool RibbonEarth { get => PK.RibbonEarth; set => PK.RibbonEarth = value; }
|
|
public bool RibbonNational { get => PK.RibbonNational; set => PK.RibbonNational = value; }
|
|
public bool RibbonCountry { get => PK.RibbonCountry; set => PK.RibbonCountry = value; }
|
|
public bool RibbonChampionBattle { get => PK.RibbonChampionBattle; set => PK.RibbonChampionBattle = value; }
|
|
public bool RibbonChampionRegional { get => PK.RibbonChampionRegional; set => PK.RibbonChampionRegional = value; }
|
|
public bool RibbonChampionNational { get => PK.RibbonChampionNational; set => PK.RibbonChampionNational = value; }
|
|
public bool RibbonClassic { get => PK.RibbonClassic; set => PK.RibbonClassic = value; }
|
|
public bool RibbonWishing { get => PK.RibbonWishing; set => PK.RibbonWishing = value; }
|
|
public bool RibbonPremier { get => PK.RibbonPremier; set => PK.RibbonPremier = value; }
|
|
public bool RibbonEvent { get => PK.RibbonEvent; set => PK.RibbonEvent = value; }
|
|
public bool RibbonBirthday { get => PK.RibbonBirthday; set => PK.RibbonBirthday = value; }
|
|
public bool RibbonSpecial { get => PK.RibbonSpecial; set => PK.RibbonSpecial = value; }
|
|
public bool RibbonWorld { get => PK.RibbonWorld; set => PK.RibbonWorld = value; }
|
|
public bool RibbonChampionWorld { get => PK.RibbonChampionWorld; set => PK.RibbonChampionWorld = value; }
|
|
public bool RibbonSouvenir { get => PK.RibbonSouvenir; set => PK.RibbonSouvenir = value; }
|
|
}
|
|
}
|