using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PKHeX.Core
{
///
/// Generation 8 Mystery Gift Template File
///
public sealed class WC8 : DataMysteryGift, ILangNick, INature, IGigantamax, IDynamaxLevel
{
public const int Size = 0x2D0;
public const int CardStart = 0x0;
public override int Format => 8;
public enum GiftType : byte
{
None = 0,
Pokemon = 1,
Item = 2,
BP = 3,
Clothing = 4,
}
public WC8() : this(new byte[Size]) { }
public WC8(byte[] data) : base(data) { }
// TODO: public byte RestrictVersion?
public bool CanBeReceivedByVersion(int v)
{
if (v < (int)GameVersion.SW || v > (int)GameVersion.SH)
return false;
return true;
}
// General Card Properties
public override int CardID
{
get => BitConverter.ToUInt16(Data, CardStart + 0x8);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x8);
}
public byte CardFlags { get => Data[CardStart + 0x10]; set => Data[CardStart + 0x10] = value; }
public GiftType CardType { get => (GiftType)Data[CardStart + 0x11]; set => Data[CardStart + 0x11] = (byte)value; }
public bool GiftRepeatable { get => (CardFlags & 1) == 0; set => CardFlags = (byte)((CardFlags & ~1) | (value ? 0 : 1)); }
public override bool GiftUsed { get => false; set => throw new Exception(); }
public int CardTitleIndex
{
get => Data[CardStart + 0x15];
set => Data[CardStart + 0x15] = (byte) value;
}
public override string CardTitle
{
get => "Mystery Gift"; // TODO: Use text string from CardTitleIndex
set => throw new Exception();
}
// Item Properties
public override bool IsItem { get => CardType == GiftType.Item; set { if (value) CardType = GiftType.Item; } }
public override int ItemID
{
get => GetItem(0);
set => SetItem(0, (ushort)value);
}
public override int Quantity
{
get => GetQuantity(0);
set => SetQuantity(0, (ushort)value);
}
public int GetItem(int index) => BitConverter.ToUInt16(Data, CardStart + 0x20 + (0x4 * index));
public void SetItem(int index, ushort item) => BitConverter.GetBytes(item).CopyTo(Data, CardStart + 0x20 + (4 * index));
public int GetQuantity(int index) => BitConverter.ToUInt16(Data, CardStart + 0x22 + (0x4 * index));
public void SetQuantity(int index, ushort quantity) => BitConverter.GetBytes(quantity).CopyTo(Data, CardStart + 0x22 + (4 * index));
// Pokémon Properties
public override bool IsPokémon { get => CardType == GiftType.Pokemon; set { if (value) CardType = GiftType.Pokemon; } }
public override bool IsShiny => PIDType == Shiny.Always;
public override int TID
{
get => BitConverter.ToUInt16(Data, CardStart + 0x20);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x20);
}
public override int SID {
get => BitConverter.ToUInt16(Data, CardStart + 0x22);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x22);
}
public int OriginGame
{
get => BitConverter.ToInt32(Data, CardStart + 0x24);
set => BitConverter.GetBytes(value).CopyTo(Data, CardStart + 0x24);
}
public uint EncryptionConstant
{
get => BitConverter.ToUInt32(Data, CardStart + 0x28);
set => BitConverter.GetBytes(value).CopyTo(Data, CardStart + 0x28);
}
public uint PID
{
get => BitConverter.ToUInt32(Data, CardStart + 0x2C);
set => BitConverter.GetBytes(value).CopyTo(Data, CardStart + 0x2C);
}
// Nicknames, OT Names 0x30 - 0x228
public override int EggLocation { get => BitConverter.ToUInt16(Data, CardStart + 0x228); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x228); }
public int MetLocation { get => BitConverter.ToUInt16(Data, CardStart + 0x22A); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x22A); }
public override int Ball
{
get => BitConverter.ToUInt16(Data, CardStart + 0x22C);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x22C);
}
public override int HeldItem
{
get => BitConverter.ToUInt16(Data, CardStart + 0x22E);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x22E);
}
public int Move1 { get => BitConverter.ToUInt16(Data, CardStart + 0x230); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x230); }
public int Move2 { get => BitConverter.ToUInt16(Data, CardStart + 0x232); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x232); }
public int Move3 { get => BitConverter.ToUInt16(Data, CardStart + 0x234); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x234); }
public int Move4 { get => BitConverter.ToUInt16(Data, CardStart + 0x236); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x236); }
public int RelearnMove1 { get => BitConverter.ToUInt16(Data, CardStart + 0x238); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x238); }
public int RelearnMove2 { get => BitConverter.ToUInt16(Data, CardStart + 0x23A); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x23A); }
public int RelearnMove3 { get => BitConverter.ToUInt16(Data, CardStart + 0x23C); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x23C); }
public int RelearnMove4 { get => BitConverter.ToUInt16(Data, CardStart + 0x23E); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x23E); }
public override int Species { get => BitConverter.ToUInt16(Data, CardStart + 0x240); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x240); }
public override int Form { get => Data[CardStart + 0x242]; set => Data[CardStart + 0x242] = (byte)value; }
public override int Gender { get => Data[CardStart + 0x243]; set => Data[CardStart + 0x243] = (byte)value; }
public override int Level { get => Data[CardStart + 0x244]; set => Data[CardStart + 0x244] = (byte)value; }
public override bool IsEgg { get => Data[CardStart + 0x245] == 1; set => Data[CardStart + 0x245] = (byte)(value ? 1 : 0); }
public int Nature { get => (sbyte)Data[CardStart + 0x246]; set => Data[CardStart + 0x246] = (byte)value; }
public override int AbilityType { get => Data[CardStart + 0x247]; set => Data[CardStart + 0x247] = (byte)value; }
public Shiny PIDType
{
get
{
switch (Data[CardStart + 0x248])
{
case 0:
return Shiny.Never;
case 1:
return Shiny.Random;
case 2: // Fixed never shiny
case 3: // Fixed always shiny
case 4:
return Shiny.FixedValue;
default:
throw new ArgumentException();
}
}
}
public int MetLevel { get => Data[CardStart + 0x249]; set => Data[CardStart + 0x249] = (byte)value; }
public byte DynamaxLevel { get => Data[CardStart + 0x24A]; set => Data[CardStart + 0x24A] = value; }
public bool CanGigantamax { get => Data[CardStart + 0x24B] != 0; set => Data[CardStart + 0x24B] = (byte)(value ? 1 : 0); }
// Ribbons 0x24C-0x26C
public byte GetRibbon(int index)
{
if (index >= 0x20) throw new IndexOutOfRangeException();
return Data[0x24C + index];
}
public void SetRibbon(int index, byte value)
{
if (index >= 0x20) throw new IndexOutOfRangeException();
Data[0x24C + index] = value;
}
public int IV_HP { get => Data[CardStart + 0x26C]; set => Data[CardStart + 0x26C] = (byte)value; }
public int IV_ATK { get => Data[CardStart + 0x26D]; set => Data[CardStart + 0x26D] = (byte)value; }
public int IV_DEF { get => Data[CardStart + 0x26E]; set => Data[CardStart + 0x26E] = (byte)value; }
public int IV_SPE { get => Data[CardStart + 0x26F]; set => Data[CardStart + 0x26F] = (byte)value; }
public int IV_SPA { get => Data[CardStart + 0x270]; set => Data[CardStart + 0x270] = (byte)value; }
public int IV_SPD { get => Data[CardStart + 0x271]; set => Data[CardStart + 0x271] = (byte)value; }
public int OTGender { get => Data[CardStart + 0x272]; set => Data[CardStart + 0x272] = (byte)value; }
public int EV_HP { get => Data[CardStart + 0x273]; set => Data[CardStart + 0x273] = (byte)value; }
public int EV_ATK { get => Data[CardStart + 0x274]; set => Data[CardStart + 0x274] = (byte)value; }
public int EV_DEF { get => Data[CardStart + 0x275]; set => Data[CardStart + 0x275] = (byte)value; }
public int EV_SPE { get => Data[CardStart + 0x276]; set => Data[CardStart + 0x276] = (byte)value; }
public int EV_SPA { get => Data[CardStart + 0x277]; set => Data[CardStart + 0x277] = (byte)value; }
public int EV_SPD { get => Data[CardStart + 0x278]; set => Data[CardStart + 0x278] = (byte)value; }
public int OT_Intensity { get => Data[CardStart + 0x279]; set => Data[CardStart + 0x279] = (byte)value; }
public int OT_Memory { get => Data[CardStart + 0x27A]; set => Data[CardStart + 0x27A] = (byte)value; }
public int OT_Feeling { get => Data[CardStart + 0x27B]; set => Data[CardStart + 0x27B] = (byte)value; }
public int OT_TextVar { get => BitConverter.ToUInt16(Data, CardStart + 0x27C); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, CardStart + 0x27C); }
// Meta Accessible Properties
public override int[] IVs
{
get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD };
set
{
if (value.Length != 6) return;
IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2];
IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5];
}
}
public int[] EVs
{
get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD };
set
{
if (value.Length != 6) return;
EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2];
EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5];
}
}
public bool GetIsNicknamed(int language) => BitConverter.ToUInt16(Data, GetNicknameOffset(language)) != 0;
public int GetNicknameLanguage(int language) => Data[GetNicknameOffset(language) + 0x1A];
public bool GetHasOT(int language) => BitConverter.ToUInt16(Data, GetOTOffset(language)) != 0;
private int GetLanguageIndex(int language)
{
var lang = (LanguageID) language;
if (lang < LanguageID.Japanese || lang == LanguageID.UNUSED_6)
return (int) LanguageID.English; // fallback
return lang < LanguageID.UNUSED_6 ? language - 1 : language - 2;
}
public override int Location { get => MetLocation; set => MetLocation = (ushort)value; }
public override int[] Moves
{
get => new[] { Move1, Move2, Move3, Move4 };
set
{
if (value.Length > 0) Move1 = value[0];
if (value.Length > 1) Move2 = value[1];
if (value.Length > 2) Move3 = value[2];
if (value.Length > 3) Move4 = value[3];
}
}
public override int[] RelearnMoves
{
get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 };
set
{
if (value.Length > 0) RelearnMove1 = value[0];
if (value.Length > 1) RelearnMove2 = value[1];
if (value.Length > 2) RelearnMove3 = value[2];
if (value.Length > 3) RelearnMove4 = value[3];
}
}
public override string OT_Name { get; set; } = string.Empty;
public string Nickname => string.Empty;
public bool IsNicknamed => false;
public int Language => 2;
public string GetNickname(int language) => Util.TrimFromZero(Encoding.Unicode.GetString(Data, GetNicknameOffset(language), 0x1A));
public void SetNickname(int language, string value) => Encoding.Unicode.GetBytes(value.PadRight(0x1A / 2, '\0')).CopyTo(Data, GetNicknameOffset(language));
public string GetOT(int language) => Util.TrimFromZero(Encoding.Unicode.GetString(Data, GetOTOffset(language), 0x1A));
public void SetOT(int language, string value) => Encoding.Unicode.GetBytes(value.PadRight(0x1A / 2, '\0')).CopyTo(Data, GetOTOffset(language));
private int GetNicknameOffset(int language)
{
int index = GetLanguageIndex(language);
return 0x30 + (index * 0x1C);
}
private int GetOTOffset(int language)
{
int index = GetLanguageIndex(language);
return 0x12C + (index * 0x1C);
}
public override PKM ConvertToPKM(ITrainerInfo SAV, EncounterCriteria criteria)
{
if (!IsPokémon)
throw new ArgumentException(nameof(IsPokémon));
int currentLevel = Level > 0 ? Level : Util.Rand.Next(100) + 1;
int metLevel = MetLevel > 0 ? MetLevel : currentLevel;
var pi = PersonalTable.SWSH.GetFormeEntry(Species, Form);
var OT = GetOT(SAV.Language);
var pk = new PK8
{
EncryptionConstant = EncryptionConstant != 0 ? EncryptionConstant : Util.Rand32(),
TID = TID,
SID = SID,
Species = Species,
AltForm = Form,
CurrentLevel = currentLevel,
Ball = Ball != 0 ? Ball : 4, // Default is Pokeball
Met_Level = metLevel,
HeldItem = HeldItem,
EXP = Experience.GetEXP(currentLevel, pi.EXPGrowth),
Move1 = Move1,
Move2 = Move2,
Move3 = Move3,
Move4 = Move4,
RelearnMove1 = RelearnMove1,
RelearnMove2 = RelearnMove2,
RelearnMove3 = RelearnMove3,
RelearnMove4 = RelearnMove4,
Version = OriginGame != 0 ? OriginGame : SAV.Game,
OT_Name = OT.Length > 0 ? OT : SAV.OT,
OT_Gender = OTGender < 2 ? OTGender : SAV.Gender,
HT_Name = GetHasOT(Language) ? SAV.OT : string.Empty,
HT_Gender = GetHasOT(Language) ? SAV.Gender : 0,
HT_Language = GetHasOT(Language) ? SAV.Language : 0,
CurrentHandler = GetHasOT(Language) ? 1 : 0,
OT_Friendship = pi.BaseFriendship,
OT_Intensity = OT_Intensity,
OT_Memory = OT_Memory,
OT_TextVar = OT_TextVar,
OT_Feeling = OT_Feeling,
FatefulEncounter = true,
EVs = EVs,
CanGigantamax = CanGigantamax,
DynamaxLevel = DynamaxLevel,
Met_Location = MetLocation,
Egg_Location = EggLocation,
};
pk.SetMaximumPPCurrent();
if ((SAV.Generation > Format && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version))
{
// give random valid game
do { pk.Version = (int)GameVersion.SW + Util.Rand.Next(2); }
while (!CanBeReceivedByVersion(pk.Version));
}
if (OTGender >= 2)
{
pk.TID = SAV.TID;
pk.SID = SAV.SID;
}
// Official code explicitly corrects for meowstic
if (pk.Species == 678)
pk.AltForm = pk.Gender;
pk.MetDate = DateTime.Now;
var nickname_language = GetNicknameLanguage(SAV.Language);
pk.Language = nickname_language != 0 ? nickname_language : SAV.Language;
pk.IsNicknamed = GetIsNicknamed(SAV.Language);
pk.Nickname = pk.IsNicknamed ? Nickname : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Format);
for (var i = 0; i < 0x20; i++)
{
var ribbon = GetRibbon(i);
if (ribbon != 0xFF)
pk.SetRibbon(ribbon);
}
SetPINGA(pk, SAV, criteria);
if (IsEgg)
SetEggMetData(pk);
pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship;
pk.HeightScalar = PokeSizeExtensions.GetRandomPokeSize();
pk.WeightScalar = PokeSizeExtensions.GetRandomPokeSize();
pk.RefreshChecksum();
return pk;
}
private void SetEggMetData(PKM pk)
{
pk.IsEgg = true;
pk.EggMetDate = DateTime.Now;
pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Format);
pk.IsNicknamed = true;
}
private void SetPINGA(PKM pk, ITrainerInfo SAV, EncounterCriteria criteria)
{
var pi = PersonalTable.USUM.GetFormeEntry(Species, Form);
pk.Nature = (int)criteria.GetNature(Nature == -1 ? Core.Nature.Random : (Nature)Nature);
pk.StatNature = pk.Nature;
pk.Gender = criteria.GetGender(Gender, pi);
var av = GetAbilityIndex(criteria, pi);
pk.RefreshAbility(av);
SetPID(pk, SAV);
SetIVs(pk);
}
private int GetAbilityIndex(EncounterCriteria criteria, PersonalInfo pi)
{
switch (AbilityType)
{
case 00: // 0 - 0
case 01: // 1 - 1
case 02: // 2 - H
return AbilityType;
case 03: // 0/1
case 04: // 0/1/H
return criteria.GetAbility(AbilityType, pi); // 3 or 2
default:
throw new ArgumentException(nameof(AbilityType));
}
}
private uint GetFixedPID(ITrainerInfo SAV)
{
uint pid = PID;
var val = Data[CardStart + 0x248];
if (val == 4)
return pid;
return (uint)((pid & 0xFFFF) | ((SAV.SID ^ SAV.TID ^ (pid & 0xFFFF) ^ (val == 2 ? 1 : 0)) << 16));
}
private void SetPID(PKM pk, ITrainerInfo SAV)
{
switch (PIDType)
{
case Shiny.FixedValue: // Specified
pk.PID = GetFixedPID(SAV);
break;
case Shiny.Random: // Random
pk.PID = Util.Rand32();
break;
case Shiny.Always: // Random Shiny
pk.PID = Util.Rand32();
pk.PID = (uint)(((pk.TID ^ pk.SID ^ (pk.PID & 0xFFFF)) << 16) | (pk.PID & 0xFFFF));
break;
case Shiny.Never: // Random Nonshiny
pk.PID = Util.Rand32();
if (pk.IsShiny) pk.PID ^= 0x10000000;
break;
}
}
private void SetIVs(PKM pk)
{
int[] finalIVs = new int[6];
var ivflag = Array.Find(IVs, iv => (byte)(iv - 0xFC) < 3);
if (ivflag == 0) // Random IVs
{
for (int i = 0; i < 6; i++)
finalIVs[i] = IVs[i] > 31 ? Util.Rand.Next(pk.MaxIV + 1) : IVs[i];
}
else // 1/2/3 perfect IVs
{
int IVCount = ivflag - 0xFB;
do { finalIVs[Util.Rand.Next(6)] = 31; }
while (finalIVs.Count(iv => iv == 31) < IVCount);
for (int i = 0; i < 6; i++)
finalIVs[i] = finalIVs[i] == 31 ? pk.MaxIV : Util.Rand.Next(pk.MaxIV + 1);
}
pk.IVs = finalIVs;
}
protected override bool IsMatchExact(PKM pkm, IEnumerable vs)
{
if (pkm.Egg_Location == 0) // Not Egg
{
if (OTGender < 2)
{
if (SID != pkm.SID) return false;
if (TID != pkm.TID) return false;
if (OTGender != pkm.OT_Gender) return false;
}
var OT = GetOT(pkm.Language); // May not be guaranteed to work.
if (!string.IsNullOrEmpty(OT) && OT != pkm.OT_Name) return false;
if (OriginGame != 0 && OriginGame != pkm.Version) return false;
if (EncryptionConstant != 0 && EncryptionConstant != pkm.EncryptionConstant) return false;
}
if (Form != pkm.AltForm && vs.All(z => !Legal.IsFormChangeable(pkm, z.Species)))
return false;
if (IsEgg)
{
if (EggLocation != pkm.Egg_Location) // traded
{
if (pkm.Egg_Location != Locations.LinkTrade6)
return false;
}
else if (PIDType == 0 && pkm.IsShiny)
{
return false; // can't be traded away for unshiny
}
if (pkm.IsEgg && !pkm.IsNative)
return false;
}
else
{
if (!PIDType.IsValid(pkm)) return false;
if (EggLocation != pkm.Egg_Location) return false;
if (MetLocation != pkm.Met_Location) return false;
}
if (MetLevel != 0 && MetLevel != pkm.Met_Level) return false;
if (Ball != 0 && Ball != pkm.Ball) return false;
if (Ball == 0 && pkm.Ball != 4) return false;
if (OTGender < 2 && OTGender != pkm.OT_Gender) return false;
if (Nature != -1 && pkm.Nature != Nature) return false;
if (Gender != 3 && Gender != pkm.Gender) return false;
if (!(pkm is IGigantamax g && g.CanGigantamax == CanGigantamax))
return false;
if (!(pkm is IDynamaxLevel dl && dl.DynamaxLevel >= DynamaxLevel))
return false;
return PIDType != 0 || pkm.PID == PID;
}
protected override bool IsMatchDeferred(PKM pkm)
{
return pkm.Species == Species;
}
}
}