PKHeX/PKHeX.Core/MysteryGifts/PGT.cs

295 lines
12 KiB
C#
Raw Normal View History

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 int Level
{
get => IsManaphyEgg ? 1 : 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; }
}
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,
2021-08-20 20:49:20 +00:00
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;
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
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
{
2020-04-16 20:11:27 +00:00
get => _pk ??= new PK4(Data.Slice(8, PokeCrypto.SIZE_4PARTY));
set
{
_pk = value;
var data = value.Data.All(z => z == 0)
? 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;
}
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
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 (!IsPokémon || ReadUInt32LittleEndian(Data.AsSpan(0x64 + 8)) != 0)
return false;
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;
2020-01-13 07:00:32 +00:00
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; } }
2020-01-13 07:00:32 +00:00
public override bool EggEncounter => IsEgg;
public override bool IsItem { get => PGTGiftType == GiftType.Item; set { if (value) PGTGiftType = GiftType.Item; } }
2021-08-22 08:26:28 +00:00
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); }
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 PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
{
if (!IsPokémon)
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
throw new ArgumentException(nameof(IsPokémon));
// template is already filled out, only minor mutations required
2020-12-22 01:17:56 +00:00
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;
2021-08-01 02:50:27 +00:00
var lang = trainer.Language < (int)LanguageID.Korean ? trainer.Language : (int)LanguageID.English;
pk4.Language = lang;
pk4.Egg_Location = 1; // Ranger (will be +3000 later)
2021-08-01 02:50:27 +00:00
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
2020-12-25 20:30:26 +00:00
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;
2020-12-25 20:30:26 +00:00
return met is Locations.LinkTrade4 or 0;
}
Fracture the encounter matching checks to allow progressive validation (#3137) ## Issue We want to discard-but-remember any slots that aren't a perfect fit, on the off chance that a better one exists later in the search space. If there's no better match, then we gotta go with what we got. ## Example: Wurmple exists in area `X`, and also has a more rare slot for Silcoon, with the same level for both slots. * We have a Silcoon that we've leveled up a few times. Was our Silcoon originally a Wurmple, or was it caught as a Silcoon? * To be sure, we have to check the EC/PID if the Wurmple wouldn't evolve into Cascoon instead. * We don't want to wholly reject that Wurmple slot, as maybe the Met Level isn't within Silcoon's slot range. --- Existing implementation would store "deferred" matches in a list; we only need to keep 1 of these matches around (less allocation!). We also want to differentiate between a "good" deferral and a "bad" deferral; I don't think this is necessary but it's currently used by Mystery Gift matching (implemented for the Eeveelution mystery gifts which matter for evolution moves). The existing logic didn't use inheritance, and instead had static methods being reused across generations. Quite kludgy. Also, the existing logic was a pain to modify the master encounter yield methods, as one generation's quirks had to not impact all other generations that used the method. --- The new implementation splits out the encounter yielding methods to be separate for each generation / subset. Now, things don't have to check `WasLink` for Gen7 origin, because Pokémon Link wasn't a thing in Gen7. --- ## Future Maybe refactoring yielders into "GameCores" that expose yielding behaviors / properties, rather than the static logic. As more generations and side-gamegroups get added (thanks LGPE/GO/GameCube), all this switch stuff gets annoying to maintain instead of just overriding/inheritance. ## Conclusion This shouldn't impact any legality results negatively; if you notice any regressions, report them! This should reduce false flags where we didn't defer-discard an encounter when we should have (wild area mons being confused with raids).
2021-01-30 01:55:27 +00:00
// Nothing is stored as a PGT besides Ranger Manaphy. Nothing should trigger these.
public override bool IsMatchExact(PKM pkm, DexLevel evo) => false;
protected override bool IsMatchDeferred(PKM pkm) => false;
Fracture the encounter matching checks to allow progressive validation (#3137) ## Issue We want to discard-but-remember any slots that aren't a perfect fit, on the off chance that a better one exists later in the search space. If there's no better match, then we gotta go with what we got. ## Example: Wurmple exists in area `X`, and also has a more rare slot for Silcoon, with the same level for both slots. * We have a Silcoon that we've leveled up a few times. Was our Silcoon originally a Wurmple, or was it caught as a Silcoon? * To be sure, we have to check the EC/PID if the Wurmple wouldn't evolve into Cascoon instead. * We don't want to wholly reject that Wurmple slot, as maybe the Met Level isn't within Silcoon's slot range. --- Existing implementation would store "deferred" matches in a list; we only need to keep 1 of these matches around (less allocation!). We also want to differentiate between a "good" deferral and a "bad" deferral; I don't think this is necessary but it's currently used by Mystery Gift matching (implemented for the Eeveelution mystery gifts which matter for evolution moves). The existing logic didn't use inheritance, and instead had static methods being reused across generations. Quite kludgy. Also, the existing logic was a pain to modify the master encounter yield methods, as one generation's quirks had to not impact all other generations that used the method. --- The new implementation splits out the encounter yielding methods to be separate for each generation / subset. Now, things don't have to check `WasLink` for Gen7 origin, because Pokémon Link wasn't a thing in Gen7. --- ## Future Maybe refactoring yielders into "GameCores" that expose yielding behaviors / properties, rather than the static logic. As more generations and side-gamegroups get added (thanks LGPE/GO/GameCube), all this switch stuff gets annoying to maintain instead of just overriding/inheritance. ## Conclusion This shouldn't impact any legality results negatively; if you notice any regressions, report them! This should reduce false flags where we didn't defer-discard an encounter when we should have (wild area mons being confused with raids).
2021-01-30 01:55:27 +00:00
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; }
}
}