using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
///
/// Generation 4 Mystery Gift Template File (Inner Gift Data, no card data)
///
public sealed class PGT : DataMysteryGift
{
public const int Size = 0x104; // 260
public override int Format => 4;
public override int Level
{
get => IsPokémon ? PK.Met_Level : 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; }
}
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 object Content => PK;
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 => BitConverter.ToUInt16(Data, 0x4); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x4); }
public PK4 PK
{
get
{
if (_pk != null)
return _pk;
byte[] ekdata = new byte[PokeCrypto.SIZE_4PARTY];
Array.Copy(Data, 8, ekdata, 0, ekdata.Length);
return _pk = new PK4(ekdata);
}
set
{
if ((_pk = value) == null)
return;
var pkdata = value.Data.All(z => z == 0)
? value.Data
: PokeCrypto.EncryptArray45(value.Data);
pkdata.CopyTo(Data, 8);
}
}
public override byte[] Write()
{
// Ensure PGT content is encrypted
var clone = (PGT)Clone();
clone.VerifyPKEncryption();
return clone.Data;
}
private PK4? _pk;
///
/// Double checks the encryption of the gift data for Pokemon data.
///
/// True if data was encrypted, false if the data was not modified.
public bool VerifyPKEncryption()
{
if (!IsPokémon || BitConverter.ToUInt32(Data, 0x64 + 8) != 0)
return false;
EncryptPK();
return true;
}
private void EncryptPK()
{
byte[] ekdata = new byte[PokeCrypto.SIZE_4PARTY];
Array.Copy(Data, 8, ekdata, 0, ekdata.Length);
ekdata = PokeCrypto.EncryptArray45(ekdata);
ekdata.CopyTo(Data, 8);
}
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 == GiftType.Pokémon || PGTGiftType == GiftType.PokémonEgg || PGTGiftType == GiftType.ManaphyEgg; set { } }
public override int Species { get => IsManaphyEgg ? 490 : PK.Species; set => PK.Species = value; }
public override IReadOnlyList Moves { get => PK.Moves; set => PK.SetMoves(value); }
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.AltForm; set => PK.AltForm = 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 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 PK4((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);
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)
{
// Since none of this data is populated, fill in default info.
pk4.Species = 490;
pk4.Gender = 2;
// Level 1 Moves
pk4.Move1 = 294;
pk4.Move2 = 145;
pk4.Move3 = 346;
pk4.Ability = pk4.PersonalInfo.Abilities[0];
pk4.FatefulEncounter = true;
pk4.Ball = 4;
pk4.Version = 10; // Diamond
pk4.Language = (int)LanguageID.English; // English
pk4.Nickname = "MANAPHY";
pk4.Egg_Location = 1; // Ranger (will be +3000 later)
pk4.Move1_PP = pk4.GetMovePP(pk4.Move1, 0);
pk4.Move2_PP = pk4.GetMovePP(pk4.Move2, 0);
pk4.Move3_PP = pk4.GetMovePP(pk4.Move3, 0);
}
private void SetPINGA(PK4 pk4, EncounterCriteria criteria)
{
// Ability is forced already, can't force anything
// todo: loop force the Nature/Gender
// Generate IV
uint seed = Util.Rand32();
if (pk4.PID == 1 || IsManaphyEgg) // Create Nonshiny
seed = GeneratePID(seed, pk4);
if (!IsManaphyEgg)
seed = Util.Rand32(); // reseed, do not have method 1 correlation
// Generate IVs
if (pk4.IV32 == 0)
{
uint iv1 = ((seed = RNG.LCRNG.Next(seed)) >> 16) & 0x7FFF;
uint iv2 = ((RNG.LCRNG.Next(seed)) >> 16) & 0x7FFF;
pk4.IV32 = iv1 | iv2 << 15;
}
}
private static void SetHatchedEggDetails(PK4 pk4)
{
pk4.IsEgg = false;
// Met Location 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, Format);
pk4.MetDate = 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 == Locations.LinkTrade4 || egg == 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 == Locations.LinkTrade4 || met == 0;
}
protected override bool IsMatchExact(PKM pkm) => false;
protected override bool IsMatchDeferred(PKM pkm) => false;
}
}