diff --git a/PKHeX.Core/Editing/Bulk/BatchMods.cs b/PKHeX.Core/Editing/Bulk/BatchMods.cs
index ee36e99e6..d4992ba5e 100644
--- a/PKHeX.Core/Editing/Bulk/BatchMods.cs
+++ b/PKHeX.Core/Editing/Bulk/BatchMods.cs
@@ -43,7 +43,7 @@ public static class BatchMods
new ComplexSuggestion(PROP_RIBBONS, (_, value, info) => BatchModifications.SetSuggestedRibbons(info, value)),
new ComplexSuggestion(nameof(PKM.Met_Location), (_, _, info) => BatchModifications.SetSuggestedMetData(info)),
new ComplexSuggestion(nameof(PKM.CurrentLevel), (_, _, info) => BatchModifications.SetMinimumCurrentLevel(info)),
- new ComplexSuggestion(PROP_CONTESTSTATS, p => p is IContestStatsMutable, (_, value, info) => BatchModifications.SetContestStats(info.Entity, info.Legality.EncounterMatch, value)),
+ new ComplexSuggestion(PROP_CONTESTSTATS, p => p is IContestStatsMutable, (_, value, info) => BatchModifications.SetContestStats(info.Entity, info.Legality, value)),
new ComplexSuggestion(PROP_MOVEMASTERY, (_, value, info) => BatchModifications.SetSuggestedMasteryData(info, value)),
};
diff --git a/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs b/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs
index f806ef1df..608bd259d 100644
--- a/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs
+++ b/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs
@@ -105,14 +105,14 @@ public static ModifyResult SetEVs(PKM pk)
/// Sets the contests stats as requested.
///
/// Pokémon to modify.
- /// Encounter matched to.
+ /// Legality Information matched to.
/// Option to apply with
- public static ModifyResult SetContestStats(PKM pk, IEncounterTemplate enc, string option)
+ public static ModifyResult SetContestStats(PKM pk, LegalityAnalysis la, string option)
{
if (option.Length != 0 && option[BatchEditing.CONST_SUGGEST.Length..] is not "0")
- pk.SetMaxContestStats(enc);
+ pk.SetMaxContestStats(la.EncounterMatch, la.Info.EvoChainsAllGens);
else
- pk.SetSuggestedContestStats(enc);
+ pk.SetSuggestedContestStats(la.EncounterMatch, la.Info.EvoChainsAllGens);
return ModifyResult.Modified;
}
}
diff --git a/PKHeX.Core/Editing/LocationEdits.cs b/PKHeX.Core/Editing/LocationEdits.cs
new file mode 100644
index 000000000..0ad9bf3fe
--- /dev/null
+++ b/PKHeX.Core/Editing/LocationEdits.cs
@@ -0,0 +1,10 @@
+namespace PKHeX.Core;
+
+public static class LocationEdits
+{
+ public static int GetNoneLocation(PKM pk) => pk switch
+ {
+ PB8 => Locations.Default8bNone,
+ _ => 0,
+ };
+}
diff --git a/PKHeX.Core/Editing/Pokerus.cs b/PKHeX.Core/Editing/Pokerus.cs
index f408a61b1..657ae70d1 100644
--- a/PKHeX.Core/Editing/Pokerus.cs
+++ b/PKHeX.Core/Editing/Pokerus.cs
@@ -17,7 +17,22 @@ public static class Pokerus
///
/// Entity to check
/// True if Pokérus exists in the game format, or can be transmitted to the entity via another game.
- public static bool IsObtainable(PKM pk) => pk is not PA8; // don't care about PK1, not stored in the data.
+ public static bool IsObtainable(PKM pk) => pk switch
+ {
+ PA8 pa8 => HasVisitedBDSPorSWSH(pa8),
+ _ => true,
+ };
+
+ private static bool HasVisitedBDSPorSWSH(PA8 pk)
+ {
+ if (pk.IsUntraded)
+ return false;
+ if (PersonalTable.BDSP.IsPresentInGame(pk.Species, pk.Form))
+ return true;
+ if (PersonalTable.SWSH.IsPresentInGame(pk.Species, pk.Form))
+ return true;
+ return false;
+ }
///
/// Checks if the Pokérus value for Strain is possible to have on the input entity.
diff --git a/PKHeX.Core/Editing/Program/StartupArguments.cs b/PKHeX.Core/Editing/Program/StartupArguments.cs
index ca4bea303..df1bb6b1e 100644
--- a/PKHeX.Core/Editing/Program/StartupArguments.cs
+++ b/PKHeX.Core/Editing/Program/StartupArguments.cs
@@ -91,9 +91,10 @@ public void ReadTemplateIfNoEntity(string path)
#region Utility
private static SaveFile GetBlank(PKM pk)
{
- var ver = (GameVersion)pk.Version;
- if (ver.GetGeneration() != pk.Format)
- ver = GameUtil.GetVersion(pk.Format);
+ var ctx = pk.Context;
+ var ver = ctx.GetSingleGameVersion();
+ if (pk is { Format: 1, Japanese: true })
+ ver = GameVersion.BU;
return SaveUtil.GetBlankSAV(ver, pk.OT_Name, (LanguageID)pk.Language);
}
diff --git a/PKHeX.Core/Editing/Saves/BoxManip/BoxManipBase.cs b/PKHeX.Core/Editing/Saves/BoxManip/BoxManipBase.cs
index bd9e2351c..4db37a528 100644
--- a/PKHeX.Core/Editing/Saves/BoxManip/BoxManipBase.cs
+++ b/PKHeX.Core/Editing/Saves/BoxManip/BoxManipBase.cs
@@ -31,7 +31,7 @@ protected BoxManipBase(BoxManipType type, Func usable)
new BoxManipSort(BoxManipType.SortLevelReverse, EntitySorting.OrderByDescendingLevel),
new BoxManipSort(BoxManipType.SortDate, EntitySorting.OrderByDateObtained, s => s.Generation >= 4),
new BoxManipSort(BoxManipType.SortName, list => list.OrderBySpeciesName(GameInfo.Strings.Species)),
- new BoxManipSort(BoxManipType.SortFavorite, list => list.OrderByCustom(pk => pk is PB7 {Favorite: true}), s => s is SAV7b),
+ new BoxManipSort(BoxManipType.SortFavorite, list => list.OrderByCustom(pk => pk is PB7 {Favorite: true}), s => s.BlankPKM is IFavorite),
new BoxManipSortComplex(BoxManipType.SortParty, (list, sav, start) => list.BubbleUp(sav, i => ((SAV7b)sav).Blocks.Storage.IsParty(i), start), s => s is SAV7b),
new BoxManipSort(BoxManipType.SortShiny, list => list.OrderByCustom(pk => !pk.IsShiny)),
new BoxManipSort(BoxManipType.SortRandom, list => list.OrderByCustom(_ => Util.Rand32())),
diff --git a/PKHeX.Core/Game/Enums/GameVersion.cs b/PKHeX.Core/Game/Enums/GameVersion.cs
index 2d4bbd000..a5c54d1ca 100644
--- a/PKHeX.Core/Game/Enums/GameVersion.cs
+++ b/PKHeX.Core/Game/Enums/GameVersion.cs
@@ -62,12 +62,12 @@ public enum GameVersion
Pt = 12,
///
- /// Pokémon Heart Gold (NDS)
+ /// Pokémon HeartGold (NDS)
///
HG = 7,
///
- /// Pokémon Soul Silver (NDS)
+ /// Pokémon SoulSilver (NDS)
///
SS = 8,
#endregion
@@ -139,7 +139,7 @@ public enum GameVersion
#endregion
///
- /// Pokémon GO (GO -> Lets Go transfers)
+ /// Pokémon GO (GO -> Let's Go/HOME transfers)
///
GO = 34,
@@ -184,12 +184,12 @@ public enum GameVersion
#region Nintendo Switch
///
- /// Pokémon Let's Go Pikachu (NX)
+ /// Pokémon: Let's Go, Pikachu! (NX)
///
GP = 42,
///
- /// Pokémon Let's Go Eevee (NX)
+ /// Pokémon: Let's Go, Eevee! (NX)
///
GE = 43,
@@ -204,6 +204,10 @@ public enum GameVersion
SH = 45,
// HOME = 46,
+
+ ///
+ /// Pokémon Legends: Arceus (NX)
+ ///
PLA = 47,
///
@@ -311,7 +315,7 @@ public enum GameVersion
DPPt,
///
- /// Pokémon Heart Gold & Soul Silver [] identifier.
+ /// Pokémon HeartGold & SoulSilver [] identifier.
///
///
///
diff --git a/PKHeX.Core/Game/GameStrings/MetDataSource.cs b/PKHeX.Core/Game/GameStrings/MetDataSource.cs
index b37486a15..cc97ed21a 100644
--- a/PKHeX.Core/Game/GameStrings/MetDataSource.cs
+++ b/PKHeX.Core/Game/GameStrings/MetDataSource.cs
@@ -151,6 +151,13 @@ private static List CreateGen8(GameStrings s)
Util.AddCBWithOffset(locations, s.metSWSH_30000, 30000, Locations8.Met3);
Util.AddCBWithOffset(locations, s.metSWSH_40000, 40000, Locations8.Met4);
Util.AddCBWithOffset(locations, s.metSWSH_60000, 60000, Locations8.Met6);
+
+ // Add in the BDSP+PLA magic met locations.
+ locations.Add(new ComboItem($"{s.EggName} (BD/SP)", Locations.HOME_SWSHBDSPEgg));
+ locations.Add(new ComboItem(s.gamelist[(int)BD], Locations.HOME_SWBD));
+ locations.Add(new ComboItem(s.gamelist[(int)SP], Locations.HOME_SHSP));
+ locations.Add(new ComboItem(s.gamelist[(int)PLA], Locations.HOME_SWLA));
+
return locations;
}
@@ -162,13 +169,20 @@ private static List CreateGen8a(GameStrings s)
Util.AddCBWithOffset(locations, s.metLA_30000, 30000, Locations8a.Met3);
Util.AddCBWithOffset(locations, s.metLA_40000, 40000, Locations8a.Met4);
Util.AddCBWithOffset(locations, s.metLA_60000, 60000, Locations8a.Met6);
+
+ // Add in the BDSP+PLA magic met locations.
+ locations.Add(new ComboItem($"{s.EggName} (BD/SP)", Locations.HOME_SWSHBDSPEgg));
+ locations.Add(new ComboItem(s.gamelist[(int)BD], Locations.HOME_SWBD));
+ locations.Add(new ComboItem(s.gamelist[(int)SP], Locations.HOME_SHSP));
+ locations.Add(new ComboItem(s.gamelist[(int)PLA], Locations.HOME_SWLA));
+
return locations;
}
private static List CreateGen8b(GameStrings s)
{
// Manually add invalid (-1) location from SWSH as ID 65535
- var locations = new List { new(s.metSWSH_00000[0], unchecked((ushort)Locations.Default8bNone)) };
+ var locations = new List { new(s.metSWSH_00000[0], Locations.Default8bNone) };
Util.AddCBWithOffset(locations, s.metBDSP_60000, 60000, Locations.Daycare5);
Util.AddCBWithOffset(locations, s.metBDSP_30000, 30000, Locations.LinkTrade6);
Util.AddCBWithOffset(locations, s.metBDSP_00000, 00000, Locations8b.Met0);
@@ -193,29 +207,45 @@ public IReadOnlyList GetLocationList(GameVersion version, int current
if (egg && version < W && currentGen >= 5)
return MetGen4;
+ var result = GetLocationListInternal(version, currentGen);
+
+ // Insert the BDSP none location if the format requires it.
+ if (currentGen == 8 && !BDSP.Contains(version))
+ {
+ var list = new List(result.Count + 1);
+ list.AddRange(result);
+ list.Insert(1, new ComboItem($"{list[0].Text} (BD/SP)", Locations.Default8bNone));
+ result = list;
+ }
+
+ return result;
+ }
+
+ private IReadOnlyList GetLocationListInternal(GameVersion version, int currentGen)
+ {
return version switch
{
- CXD when currentGen == 3 => MetGen3CXD,
- R or S when currentGen == 3 => Partition1(MetGen3, z => z <= 87), // Ferry
- E when currentGen == 3 => Partition1(MetGen3, z => z is <= 87 or >= 197 and <= 212), // Trainer Hill
+ CXD when currentGen == 3 => MetGen3CXD,
+ R or S when currentGen == 3 => Partition1(MetGen3, z => z <= 87), // Ferry
+ E when currentGen == 3 => Partition1(MetGen3, z => z is <= 87 or >= 197 and <= 212), // Trainer Hill
FR or LG when currentGen == 3 => Partition1(MetGen3, z => z is > 87 and < 197), // Celadon Dept.
- D or P when currentGen == 4 => Partition2(MetGen4, z => z <= 111, 4), // Battle Park
- Pt when currentGen == 4 => Partition2(MetGen4, z => z <= 125, 4), // Rock Peak Ruins
+ D or P when currentGen == 4 => Partition2(MetGen4, z => z <= 111, 4), // Battle Park
+ Pt when currentGen == 4 => Partition2(MetGen4, z => z <= 125, 4), // Rock Peak Ruins
HG or SS when currentGen == 4 => Partition2(MetGen4, z => z is > 125 and < 234, 4), // Celadon Dept.
- B or W => MetGen5,
- B2 or W2 => Partition2(MetGen5, z => z <= 116), // Abyssal Ruins
- X or Y => Partition2(MetGen6, z => z <= 168), // Unknown Dungeon
+ B or W => MetGen5,
+ B2 or W2 => Partition2(MetGen5, z => z <= 116), // Abyssal Ruins
+ X or Y => Partition2(MetGen6, z => z <= 168), // Unknown Dungeon
OR or AS => Partition2(MetGen6, z => z is > 168 and <= 354), // Secret Base
- SN or MN => Partition2(MetGen7, z => z < 200), // Outer Cape
+ SN or MN => Partition2(MetGen7, z => z < 200), // Outer Cape
US or UM
- or RD or BU or GN or YW
- or GD or SI or C => Partition2(MetGen7, z => z < 234), // Dividing Peak Tunnel
+ or RD or BU or GN or YW
+ or GD or SI or C => Partition2(MetGen7, z => z < 234), // Dividing Peak Tunnel
GP or GE or GO => Partition2(MetGen7GG, z => z <= 54), // Pokémon League
SW or SH => Partition2(MetGen8, z => z < 400),
BD or SP => Partition2(MetGen8b, z => z < 628),
PLA => Partition2(MetGen8a, z => z < 512),
- _ => GetLocationListModified(version, currentGen),
+ _ => new List(GetLocationListModified(version, currentGen)),
};
static IReadOnlyList Partition1(IReadOnlyList list, Func criteria)
@@ -224,7 +254,8 @@ static IReadOnlyList Partition1(IReadOnlyList list, Func GetOrderedList(IReadOnlyList list, ComboItem[] result, Func criteria, int start = 0)
+ static IReadOnlyList GetOrderedList(IReadOnlyList list, ComboItem[] result,
+ Func criteria, int start = 0)
{
// store values that match criteria at the next available position of the array
// store non-matches starting at the end. reverse before returning
@@ -237,12 +268,14 @@ static IReadOnlyList GetOrderedList(IReadOnlyList list, Co
else
result[end--] = item;
}
+
// since the non-matches are reversed in order, we swap them back since we know where they end up at.
Array.Reverse(result, start, list.Count - start);
return result;
}
- static IReadOnlyList Partition2(IReadOnlyList list, Func criteria, int keepFirst = 3)
+ static IReadOnlyList Partition2(IReadOnlyList list, Func criteria,
+ int keepFirst = 3)
{
var result = new ComboItem[list.Count];
for (int i = 0; i < keepFirst; i++)
diff --git a/PKHeX.Core/Game/Locations/Locations.cs b/PKHeX.Core/Game/Locations/Locations.cs
index 09e8ea678..b3d153eb1 100644
--- a/PKHeX.Core/Game/Locations/Locations.cs
+++ b/PKHeX.Core/Game/Locations/Locations.cs
@@ -100,10 +100,34 @@ public static class Locations
public const int BugCatchingContest4 = 207;
- ///
- /// -1
- ///
- public const int Default8bNone = -1;
+ public const int HOME_SHSP = 59998; // SP traded to (SW)SH
+ public const int HOME_SWBD = 59999; // BD traded to SW(SH)
+ public const int HOME_SWLA = 60000; // PLA traded to SW(SH)
+ public const int HOME_SWSHBDSPEgg = 65534; // -2 = 8bNone-1..
+ public const int Default8bNone = 65535;
+
+ public static int GetVersionSWSH(int ver) => (GameVersion)ver switch
+ {
+ GameVersion.PLA => (int)GameVersion.SW,
+ GameVersion.BD => (int)GameVersion.SW,
+ GameVersion.SP => (int)GameVersion.SH,
+ _ => ver,
+ };
+
+ public static int GetMetSWSH(int loc, int ver) => (GameVersion)ver switch
+ {
+ GameVersion.PLA => HOME_SWLA,
+ GameVersion.BD => HOME_SWBD,
+ GameVersion.SP => HOME_SHSP,
+ _ => loc,
+ };
+
+ public static bool IsValidMetBDSP(int loc, int ver) => loc switch
+ {
+ HOME_SHSP when ver == (int)GameVersion.SH => true,
+ HOME_SWBD when ver == (int)GameVersion.SW => true,
+ _ => false,
+ };
public static int TradedEggLocationNPC(int generation) => generation switch
{
@@ -125,6 +149,7 @@ public static class Locations
public static bool IsPtHGSSLocation(int location) => location is > 111 and < 2000;
public static bool IsPtHGSSLocationEgg(int location) => location is > 2010 and < 3000;
+ public static bool IsEventLocation3(int location) => location is 255;
public static bool IsEventLocation4(int location) => location is >= 3000 and <= 3076;
public static bool IsEventLocation5(int location) => location is > 40000 and < 50000;
@@ -146,14 +171,6 @@ public static bool IsEggLocationBred4(int loc, GameVersion ver)
public static bool IsEggLocationBred6(int loc) => loc is Daycare5 or LinkTrade6;
public static bool IsEggLocationBred8b(int loc) => loc is Daycare8b or LinkTrade6NPC;
- public static bool IsNoneLocation(GameVersion ver, int location) => GetNoneLocation(ver) == (short)location;
-
- public static int GetNoneLocation(GameVersion ver) => ver switch
- {
- GameVersion.BD or GameVersion.SP => Default8bNone,
- _ => 0,
- };
-
public static int GetDaycareLocation(int generation, GameVersion version) => generation switch
{
1 or 2 or 3 => 0,
diff --git a/PKHeX.Core/Legality/Areas/EncounterArea7g.cs b/PKHeX.Core/Legality/Areas/EncounterArea7g.cs
index dd5631323..f89ebbbea 100644
--- a/PKHeX.Core/Legality/Areas/EncounterArea7g.cs
+++ b/PKHeX.Core/Legality/Areas/EncounterArea7g.cs
@@ -35,19 +35,21 @@ internal static EncounterArea7g[] GetArea(BinLinkerAccessor data)
return areas;
}
+ private const int meta = 4;
private const int entrySize = (2 * sizeof(int)) + 2;
private static EncounterArea7g GetArea(ReadOnlySpan data)
{
var species = ReadUInt16LittleEndian(data);
- byte form = (byte)(species >> 11);
- species &= 0x3FF;
+ var form = data[2];
+ //var import = (EntityFormatDetected)data[3];
- var result = new EncounterSlot7GO[(data.Length - 2) / entrySize];
+ data = data[meta..];
+ var result = new EncounterSlot7GO[data.Length / entrySize];
var area = new EncounterArea7g(species, form, result);
for (int i = 0; i < result.Length; i++)
{
- var offset = (i * entrySize) + 2;
+ var offset = i * entrySize;
var entry = data.Slice(offset, entrySize);
result[i] = ReadSlot(entry, area, species, form);
}
diff --git a/PKHeX.Core/Legality/Areas/EncounterArea8g.cs b/PKHeX.Core/Legality/Areas/EncounterArea8g.cs
index b41f2d7f7..fbdb9df7e 100644
--- a/PKHeX.Core/Legality/Areas/EncounterArea8g.cs
+++ b/PKHeX.Core/Legality/Areas/EncounterArea8g.cs
@@ -35,29 +35,29 @@ internal static EncounterArea8g[] GetArea(BinLinkerAccessor data)
return areas;
}
+ private const int meta = 4;
private const int entrySize = (2 * sizeof(int)) + 2;
private static EncounterArea8g GetArea(ReadOnlySpan data)
{
var species = ReadUInt16LittleEndian(data);
- byte form = (byte)(species >> 11);
- species &= 0x3FF;
+ var form = data[2];
+ var import = (PogoImportFormat)data[3];
- var group = GetGroup(species, form);
-
- var result = new EncounterSlot8GO[(data.Length - 2) / entrySize];
+ data = data[meta..];
+ var result = new EncounterSlot8GO[data.Length / entrySize];
var area = new EncounterArea8g(species, form, result);
for (int i = 0; i < result.Length; i++)
{
- var offset = (i * entrySize) + 2;
+ var offset = i * entrySize;
var entry = data.Slice(offset, entrySize);
- result[i] = ReadSlot(entry, area, species, form, group);
+ result[i] = ReadSlot(entry, area, species, form, import);
}
return area;
}
- private static EncounterSlot8GO ReadSlot(ReadOnlySpan entry, EncounterArea8g area, ushort species, byte form, GameVersion group)
+ private static EncounterSlot8GO ReadSlot(ReadOnlySpan entry, EncounterArea8g area, ushort species, byte form, PogoImportFormat format)
{
int start = ReadInt32LittleEndian(entry);
int end = ReadInt32LittleEndian(entry[4..]);
@@ -65,32 +65,7 @@ private static EncounterSlot8GO ReadSlot(ReadOnlySpan entry, EncounterArea
var shiny = (Shiny)(sg & 0x3F);
var gender = (Gender)(sg >> 6);
var type = (PogoType)entry[9];
- return new EncounterSlot8GO(area, species, form, start, end, shiny, gender, type, group);
- }
-
- private static GameVersion GetGroup(int species, int form)
- {
- // Transfer Rules:
- // If it can exist in LGP/E, it uses LGP/E's move data for the initial moves.
- // Else, if it can exist in SW/SH, it uses SW/SH's move data for the initial moves.
- // Else, it must exist in US/UM, thus it uses US/UM's moves.
-
- var pt8 = PersonalTable.SWSH;
- var ptGG = PersonalTable.GG;
-
- var pi8 = (PersonalInfoSWSH)pt8[species];
- if (pi8.IsPresentInGame)
- {
- bool lgpe = (species is (<= 151 or 808 or 809)) && (form == 0 || ptGG[species].HasForm(form));
- return lgpe ? GameVersion.GG : GameVersion.SWSH;
- }
- if (species <= Legal.MaxSpeciesID_7_USUM)
- {
- bool lgpe = species <= 151 && (form == 0 || ptGG[species].HasForm(form));
- return lgpe ? GameVersion.GG : GameVersion.USUM;
- }
-
- throw new ArgumentOutOfRangeException(nameof(species));
+ return new EncounterSlot8GO(area, species, form, start, end, shiny, gender, type, format);
}
public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain)
@@ -101,7 +76,7 @@ public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria
// Find the first chain that has slots defined.
// Since it is possible to evolve before transferring, we only need the highest evolution species possible.
// PoGoEncTool has already extrapolated the evolutions to separate encounters!
- var sf = Array.Find(chain, z => z.Species == Species && (z.Form == Form || FormInfo.IsFormChangeable(Species, Form, z.Form, pkm.Format)));
+ var sf = FindCriteriaToIterate(pkm, chain);
if (sf == default)
yield break;
@@ -133,5 +108,25 @@ public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria
if (deferredIV != null)
yield return deferredIV;
}
+
+ private EvoCriteria FindCriteriaToIterate(PKM pkm, EvoCriteria[] chain)
+ {
+ foreach (var evo in chain)
+ {
+ if (evo.Species != Species)
+ continue;
+
+ if (evo.Form == Form)
+ return evo;
+
+ // Check for form mismatches
+ if (FormInfo.IsFormChangeable(Species, Form, evo.Form, pkm.Format))
+ return evo;
+ if (Species == (int)Core.Species.Burmy)
+ return evo;
+ break;
+ }
+ return default;
+ }
}
}
diff --git a/PKHeX.Core/Legality/Breeding.cs b/PKHeX.Core/Legality/Breeding.cs
index 748e1d0c8..0c50e8141 100644
--- a/PKHeX.Core/Legality/Breeding.cs
+++ b/PKHeX.Core/Legality/Breeding.cs
@@ -141,8 +141,11 @@ public static bool CanHatchAsEgg(int species, int form, GameVersion game)
{
// Sanity check form for origin
var pt = GameData.GetPersonal(game);
+ if ((uint)species > pt.MaxSpeciesID)
+ return false;
+
var entry = pt.GetFormEntry(species, form);
- if (entry is PersonalInfoSWSH { IsPresentInGame: false })
+ if (!entry.IsPresentInGame)
return false;
return form < entry.FormCount || (species == (int)Rotom && form <= 5);
}
diff --git a/PKHeX.Core/Legality/Core.cs b/PKHeX.Core/Legality/Core.cs
index 651bc75a1..17f72aa00 100644
--- a/PKHeX.Core/Legality/Core.cs
+++ b/PKHeX.Core/Legality/Core.cs
@@ -110,6 +110,23 @@ internal static int GetMaxSpeciesOrigin(PKM pkm)
_ => -1,
};
+ internal static int GetMaxSpeciesOrigin(EntityContext context) => context switch
+ {
+ EntityContext.Gen1 => MaxSpeciesID_1,
+ EntityContext.Gen2 => MaxSpeciesID_2,
+ EntityContext.Gen3 => MaxSpeciesID_3,
+ EntityContext.Gen4 => MaxSpeciesID_4,
+ EntityContext.Gen5 => MaxSpeciesID_5,
+ EntityContext.Gen6 => MaxSpeciesID_6,
+ EntityContext.Gen7 => MaxSpeciesID_7_USUM,
+ EntityContext.Gen8 => MaxSpeciesID_8_R2,
+
+ EntityContext.Gen7b => MaxSpeciesID_7b,
+ EntityContext.Gen8a => MaxSpeciesID_8a,
+ EntityContext.Gen8b => MaxSpeciesID_8b,
+ _ => -1,
+ };
+
internal static int GetMaxSpeciesOrigin(int generation) => generation switch
{
1 => MaxSpeciesID_1,
@@ -169,43 +186,93 @@ internal static int GetMaxSpeciesOrigin(PKM pkm)
internal static bool HasVisitedORAS(this PKM pkm, int species) => pkm.InhabitedGeneration(6, species) && (pkm.AO || !pkm.IsUntraded);
internal static bool HasVisitedUSUM(this PKM pkm, int species) => pkm.InhabitedGeneration(7, species) && (pkm.USUM || !pkm.IsUntraded);
- internal static bool HasVisitedBDSP(this PKM pkm, int species)
+ internal static bool HasVisitedSWSH(this PKM pkm, EvoCriteria[] evos)
{
- if (!pkm.InhabitedGeneration(8, species))
+ if (pkm.SWSH)
+ return true;
+ if (pkm.IsUntraded)
return false;
+ if (pkm.BDSP && pkm.Species is (int)Species.Spinda or (int)Species.Nincada)
+ return false;
+
+ var pt = PersonalTable.SWSH;
+ foreach (var evo in evos)
+ {
+ if (pt.IsPresentInGame(evo.Species, evo.Form))
+ return true;
+ }
+ return false;
+ }
+
+ internal static bool HasVisitedBDSP(this PKM pkm, EvoCriteria[] evos)
+ {
if (pkm.BDSP)
return true;
if (pkm.IsUntraded)
return false;
- var pi = (PersonalInfoBDSP)PersonalTable.BDSP[species];
- return pi.IsPresentInGame;
+ if (pkm.Species is (int)Species.Spinda or (int)Species.Nincada)
+ return false;
+
+ var pt = PersonalTable.BDSP;
+ foreach (var evo in evos)
+ {
+ if (pt.IsPresentInGame(evo.Species, evo.Form))
+ return true;
+ }
+ return false;
}
- internal static bool HasVisitedLA(this PKM pkm, int species)
+ internal static bool HasVisitedLA(this PKM pkm, EvoCriteria[] evos)
{
- if (!pkm.InhabitedGeneration(8, species))
- return false;
if (pkm.LA)
return true;
if (pkm.IsUntraded)
return false;
- var pi = (PersonalInfoLA)PersonalTable.LA[species];
- return pi.IsPresentInGame;
+
+ var pt = PersonalTable.LA;
+ foreach (var evo in evos)
+ {
+ if (pt.IsPresentInGame(evo.Species, evo.Form))
+ return true;
+ }
+ return false;
}
///
- /// Indicates if the moveset is restricted to only the original version.
+ /// Checks if the moveset is restricted to only a specific version.
///
/// Entity to check
- ///
- internal static bool IsMovesetRestricted(this PKM pkm)
+ internal static (bool IsRestricted, GameVersion Game) IsMovesetRestricted(this PKM pkm) => pkm switch
{
- if (pkm.IsUntraded)
+ PB7 => (true, GameVersion.GP),
+ PA8 => (true, GameVersion.PLA),
+ PB8 => (true, GameVersion.BD),
+ PK8 when pkm.Version > (int)GameVersion.SH => (true, GameVersion.SH), // Permit past generation moves.
+
+ IBattleVersion { BattleVersion: not 0 } bv => (true, (GameVersion)bv.BattleVersion),
+ _ when pkm.IsUntraded => (true, (GameVersion)pkm.Version),
+ _ => (false, GameVersion.Any),
+ };
+
+ ///
+ /// Checks if the relearn moves should be wiped.
+ ///
+ /// Already checked for generations < 8.
+ /// Entity to check
+ internal static bool IsOriginalMovesetDeleted(this PKM pkm)
+ {
+ if (pkm is PA8 {LA: false} or PB8 {BDSP: false})
return true;
- if (pkm.BDSP)
- return true;
- if (pkm.LA)
+ if (pkm.IsNative)
+ {
+ if (pkm is PK8 {LA: true} or PK8 {BDSP: true})
+ return true;
+ return false;
+ }
+
+ if (pkm is IBattleVersion { BattleVersion: not 0 })
return true;
+
return false;
}
@@ -218,23 +285,6 @@ public static bool IsPPUpAvailable(PKM pkm)
return pkm is not PA8;
}
- ///
- /// Indicates if the moveset is restricted to only the original version.
- ///
- /// Entity to check
- /// Generation the move check is for
- ///
- internal static bool IsMovesetRestricted(this PKM pkm, int gen)
- {
- if (pkm.IsMovesetRestricted())
- return true;
- return gen switch
- {
- 7 when pkm.Version is (int)GameVersion.GO or (int)GameVersion.GP or (int)GameVersion.GE => true,
- _ => false,
- };
- }
-
public static int GetMaxLengthOT(int generation, LanguageID language) => language switch
{
LanguageID.ChineseS or LanguageID.ChineseT => 6,
@@ -270,5 +320,12 @@ public static bool GetIsFixedIVSequenceValidNoRand(ReadOnlySpan IVs, PKM pk
}
return true;
}
+
+ public static bool IsMetAsEgg(PKM pkm) => pkm switch
+ {
+ PA8 or PK8 => pkm.Egg_Location is not 0 || (pkm.BDSP && pkm.Egg_Day is not 0),
+ PB8 pb8 => pb8.Egg_Location is not Locations.Default8bNone,
+ _ => pkm.Egg_Location is not 0,
+ };
}
}
diff --git a/PKHeX.Core/Legality/Encounters/Data/EncounterServerDate.cs b/PKHeX.Core/Legality/Encounters/Data/EncounterServerDate.cs
index 49c66e74f..450765be1 100644
--- a/PKHeX.Core/Legality/Encounters/Data/EncounterServerDate.cs
+++ b/PKHeX.Core/Legality/Encounters/Data/EncounterServerDate.cs
@@ -41,11 +41,13 @@ private static bool IsValidDate(DateTime obtained, (DateTime Start, DateTime End
{
WC8 wc8 => Result(IsValidDateWC8(wc8.CardID, obtained)),
WA8 wa8 => Result(IsValidDateWA8(wa8.CardID, obtained)),
+ WB8 wb8 => Result(IsValidDateWB8(wb8.CardID, obtained)),
_ => throw new ArgumentOutOfRangeException(nameof(enc)),
};
public static bool IsValidDateWC8(int cardID, DateTime obtained) => WC8Gifts.TryGetValue(cardID, out var time) && IsValidDate(obtained, time);
public static bool IsValidDateWA8(int cardID, DateTime obtained) => WA8Gifts.TryGetValue(cardID, out var time) && IsValidDate(obtained, time);
+ public static bool IsValidDateWB8(int cardID, DateTime obtained) => WB8Gifts.TryGetValue(cardID, out var time) && IsValidDate(obtained, time);
///
/// Minimum date the gift can be received.
@@ -69,13 +71,31 @@ private static bool IsValidDate(DateTime obtained, (DateTime Start, DateTime End
{9014, new DateTime(2021, 06, 17)}, // Gigantamax Squirtle
};
+ private static readonly DateTime Never = DateTime.MaxValue;
+
///
/// Minimum date the gift can be received.
///
public static readonly Dictionary WA8Gifts = new()
{
- {0138, (new(2022, 01, 27), new(2022, 11, 01))}, // Poké Center Happiny
+ {0138, (new(2022, 01, 27), new(2023, 02, 01))}, // Poké Center Happiny
{0301, (new(2022, 02, 04), new(2022, 02, 24))}, // プロポチャ Piplup
{0801, (new(2022, 02, 25), new(2022, 06, 01))}, // Teresa Roca Hisuian Growlithe
+ {1201, (new(2022, 05, 31), new(2022, 08, 01))}, // 전이마을 Regigigas
+ {1202, (new(2022, 05, 31), new(2022, 08, 01))}, // 빛나's Piplup
+
+ {9018, (new(2022, 05, 18), Never)}, // Hidden Ability Rowlet
+ {9019, (new(2022, 05, 18), Never)}, // Hidden Ability Cyndaquil
+ {9020, (new(2022, 05, 18), Never)}, // Hidden Ability Oshawott
+ };
+
+ ///
+ /// Minimum date the gift can be received.
+ ///
+ public static readonly Dictionary WB8Gifts = new()
+ {
+ {9015, (new(2022, 05, 18), Never)}, // Hidden Ability Turtwig
+ {9016, (new(2022, 05, 18), Never)}, // Hidden Ability Chimchar
+ {9017, (new(2022, 05, 18), Never)}, // Hidden Ability Piplup
};
}
diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs
index e5c65cd18..a95560bb8 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs
@@ -74,12 +74,14 @@ public virtual string LongName
public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
{
- var pk = EntityBlank.GetBlank(Generation, Version);
+ var pk = GetBlank();
sav.ApplyTo(pk);
ApplyDetails(sav, criteria, pk);
return pk;
}
+ protected virtual PKM GetBlank() => EntityBlank.GetBlank(Generation, Version);
+
protected virtual void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk)
{
var version = this.GetCompatibleVersion((GameVersion) sav.Game);
diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6AO.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6AO.cs
index e48604162..60459232a 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6AO.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6AO.cs
@@ -49,7 +49,7 @@ public override string GetConditionString(out bool valid)
private int[] GetDexNavMoves()
{
- var et = EvolutionTree.GetEvolutionTree(6);
+ var et = EvolutionTree.Evolves6;
var sf = et.GetBaseSpeciesForm(Species, Form);
return MoveEgg.GetEggMoves(6, sf & 0x7FF, sf >> 11, Version);
}
diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot7GO.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot7GO.cs
index 719d75c58..f7a739a67 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot7GO.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot7GO.cs
@@ -14,6 +14,8 @@ public EncounterSlot7GO(EncounterArea7g area, int species, int form, int start,
{
}
+ protected override PKM GetBlank() => new PB7();
+
protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk)
{
base.ApplyDetails(sav, criteria, pk);
@@ -27,6 +29,21 @@ protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteri
pb.ResetCP();
}
+ protected override void SetPINGA(PKM pk, EncounterCriteria criteria)
+ {
+ var pi = pk.PersonalInfo;
+ int gender = criteria.GetGender(-1, pi);
+ int nature = (int)criteria.GetNature(Nature.Random);
+ var ability = criteria.GetAbilityFromNumber(Ability);
+
+ pk.PID = Util.Rand32();
+ pk.Nature = pk.StatNature = nature;
+ pk.Gender = gender;
+ pk.RefreshAbility(ability);
+ pk.SetRandomIVsGO();
+ base.SetPINGA(pk, criteria);
+ }
+
protected override void SetEncounterMoves(PKM pk, GameVersion version, int level)
{
var moves = MoveLevelUp.GetEncounterMoves(pk, level, GameVersion.GG);
diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot8GO.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot8GO.cs
index 69cfc6a4b..856d88cfd 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot8GO.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot8GO.cs
@@ -1,10 +1,12 @@
+using System;
+
namespace PKHeX.Core
{
///
/// Encounter Slot representing data transferred to (HOME).
///
///
- public sealed record EncounterSlot8GO : EncounterSlotGO
+ public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship
{
public override int Generation => 8;
@@ -13,14 +15,14 @@ public sealed record EncounterSlot8GO : EncounterSlotGO
///
///
/// Future game releases might change this value.
- /// With respect to date legality, new dates might be incompatible with initial values.
+ /// With respect to date legality, new dates might be incompatible with initial values.
///
- public GameVersion OriginGroup { get; }
+ public PogoImportFormat OriginFormat { get; }
- public EncounterSlot8GO(EncounterArea8g area, int species, int form, int start, int end, Shiny shiny, Gender gender, PogoType type, GameVersion originGroup)
+ public EncounterSlot8GO(EncounterArea8g area, int species, int form, int start, int end, Shiny shiny, Gender gender, PogoType type, PogoImportFormat originFormat)
: base(area, species, form, start, end, shiny, gender, type)
{
- OriginGroup = originGroup;
+ OriginFormat = originFormat;
}
///
@@ -34,21 +36,84 @@ public bool IsBallValid(Ball ball, int currentSpecies)
return Type.IsBallValid(ball);
}
+ protected override PKM GetBlank() => OriginFormat switch
+ {
+ PogoImportFormat.PK7 => new PK8(),
+ PogoImportFormat.PB7 => new PB7(),
+ PogoImportFormat.PK8 => new PK8(),
+ PogoImportFormat.PA8 => new PA8(),
+ _ => throw new ArgumentOutOfRangeException(nameof(OriginFormat)),
+ };
+
+ private PersonalInfo GetPersonal() => OriginFormat switch
+ {
+ PogoImportFormat.PK7 => PersonalTable.USUM.GetFormEntry(Species, Form),
+ PogoImportFormat.PB7 => PersonalTable.GG.GetFormEntry(Species, Form),
+ PogoImportFormat.PK8 => PersonalTable.SWSH.GetFormEntry(Species, Form),
+ PogoImportFormat.PA8 => PersonalTable.LA.GetFormEntry(Species, Form),
+ _ => throw new ArgumentOutOfRangeException(nameof(OriginFormat)),
+ };
+
+ internal GameVersion OriginGroup => OriginFormat switch
+ {
+ PogoImportFormat.PK7 => GameVersion.USUM,
+ PogoImportFormat.PB7 => GameVersion.GG,
+ PogoImportFormat.PK8 => GameVersion.SWSH,
+ PogoImportFormat.PA8 => GameVersion.PLA,
+ _ => throw new ArgumentOutOfRangeException(nameof(OriginFormat)),
+ };
+
protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk)
{
- var pk8 = (PK8)pk;
- pk8.HT_Name = "PKHeX";
- pk8.HT_Language = 2;
- pk8.CurrentHandler = 1;
+ pk.HT_Name = "PKHeX";
+ pk.CurrentHandler = 1;
+ if (pk is IHandlerLanguage l)
+ l.HT_Language = 2;
base.ApplyDetails(sav, criteria, pk);
var ball = Type.GetValidBall();
if (ball != Ball.None)
pk.Ball = (int)ball;
- pk8.SetRandomEC();
- pk8.HeightScalar = PokeSizeUtil.GetRandomScalar();
- pk8.WeightScalar = PokeSizeUtil.GetRandomScalar();
+ if (pk is IScaledSize s)
+ {
+ s.HeightScalar = PokeSizeUtil.GetRandomScalar();
+ s.WeightScalar = PokeSizeUtil.GetRandomScalar();
+ }
+
+ if (OriginFormat is PogoImportFormat.PA8)
+ {
+ var pa8 = (PA8)pk;
+ pa8.ResetHeight();
+ pa8.ResetWeight();
+ pa8.HeightScalarCopy = pa8.HeightScalar;
+ }
+
+ pk.OT_Friendship = OT_Friendship;
+
+ pk.SetRandomEC();
+ }
+
+ protected override void SetPINGA(PKM pk, EncounterCriteria criteria)
+ {
+ var pi = GetPersonal();
+ if (OriginFormat is PogoImportFormat.PK7)
+ pk.EXP = Experience.GetEXP(LevelMin, pi.EXPGrowth);
+ int gender = criteria.GetGender(-1, pi);
+ int nature = (int)criteria.GetNature(Nature.Random);
+ var ability = criteria.GetAbilityFromNumber(Ability);
+
+ pk.PID = Util.Rand32();
+ pk.Nature = pk.StatNature = nature;
+ pk.Gender = gender;
+
+ pk.AbilityNumber = 1 << ability;
+ var abilities = pi.Abilities;
+ if ((uint)ability < abilities.Count)
+ pk.Ability = abilities[ability];
+
+ pk.SetRandomIVsGO();
+ base.SetPINGA(pk, criteria);
}
protected override void SetEncounterMoves(PKM pk, GameVersion version, int level)
@@ -65,6 +130,22 @@ public override EncounterMatchRating GetMatchRating(PKM pkm)
return base.GetMatchRating(pkm) == EncounterMatchRating.PartialMatch ? EncounterMatchRating.PartialMatch : EncounterMatchRating.Match;
}
+ public byte OT_Friendship => Species switch
+ {
+ (int)Core.Species.Timburr when Form == 0 => 70,
+ (int)Core.Species.Stunfisk when Form == 0 => 70,
+ (int)Core.Species.Hoopa when Form == 1 => 50,
+ _ => GetHOMEFriendship(),
+ };
+
+ private byte GetHOMEFriendship()
+ {
+ var fs = (byte)GetPersonal().BaseFriendship;
+ if (fs == 70)
+ return 50;
+ return fs;
+ }
+
private bool IsMatchPartial(PKM pk)
{
var stamp = GetTimeStamp(pk.Met_Year + 2000, pk.Met_Month, pk.Met_Day);
@@ -76,19 +157,33 @@ private bool IsMatchPartial(PKM pk)
return true;
// Eevee & Glaceon have different base friendships. Make sure if it is invalid that we yield the other encounter before.
- if (PersonalTable.SWSH.GetFormEntry(Species, Form).BaseFriendship != pk.OT_Friendship)
+ if (pk.OT_Friendship != OT_Friendship)
return true;
return Species switch
{
(int)Core.Species.Yamask when pk.Species != Species && Form == 1 => pk is IFormArgument { FormArgument: 0 },
(int)Core.Species.Milcery when pk.Species != Species => pk is IFormArgument { FormArgument: 0 },
+ (int)Core.Species.Qwilfish when pk.Species != Species && Form == 1 => pk is IFormArgument { FormArgument: 0 },
+ (int)Core.Species.Basculin when pk.Species != Species && Form == 2 => pk is IFormArgument { FormArgument: 0 },
+ (int)Core.Species.Stantler when pk.Species != Species => pk is IFormArgument { FormArgument: 0 },
(int)Core.Species.Runerigus => pk is IFormArgument { FormArgument: not 0 },
(int)Core.Species.Alcremie => pk is IFormArgument { FormArgument: not 0 },
+ (int)Core.Species.Wyrdeer => pk is IFormArgument { FormArgument: not 0 },
+ (int)Core.Species.Basculegion => pk is IFormArgument { FormArgument: not 0 },
+ (int)Core.Species.Overqwil => pk is IFormArgument { FormArgument: not 0 },
_ => false,
};
}
}
+
+ public enum PogoImportFormat : byte
+ {
+ PK7 = 0,
+ PB7 = 1,
+ PK8 = 2,
+ PA8 = 3,
+ }
}
diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs
index 34309aed7..cd1274d70 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs
@@ -153,7 +153,6 @@ public bool GetIVsValid(PKM pkm)
protected override void SetPINGA(PKM pk, EncounterCriteria criteria)
{
- base.SetPINGA(pk, criteria);
switch (Shiny)
{
case Shiny.Random when !pk.IsShiny && criteria.Shiny.IsShiny():
diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs
index f6d2ae6fd..30f46ecf3 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs
@@ -39,7 +39,7 @@ public abstract record EncounterStatic(GameVersion Version) : IEncounterable, IM
public IReadOnlyList Moves { get; init; } = Array.Empty();
public IReadOnlyList IVs { get; init; } = Array.Empty();
- public bool EggEncounter => EggLocation > 0;
+ public virtual bool EggEncounter => EggLocation != 0;
private const string _name = "Static Encounter";
public string Name => _name;
@@ -107,9 +107,9 @@ protected virtual void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria
s.HeightScalar = PokeSizeUtil.GetRandomScalar();
s.WeightScalar = PokeSizeUtil.GetRandomScalar();
}
- if (this is IGigantamax g && pk is IGigantamax pg)
+ if (this is IGigantamax g && pk is PK8 pg)
pg.CanGigantamax = g.CanGigantamax;
- if (this is IDynamaxLevel d && pk is IDynamaxLevel pd)
+ if (this is IDynamaxLevel d && pk is PK8 pd)
pd.DynamaxLevel = d.DynamaxLevel;
}
@@ -264,7 +264,11 @@ protected virtual bool IsMatchForm(PKM pkm, EvoCriteria evo)
}
// override me if the encounter type has any eggs
- protected virtual bool IsMatchEggLocation(PKM pkm) => pkm.Egg_Location == 0;
+ protected virtual bool IsMatchEggLocation(PKM pkm)
+ {
+ var expect = pkm is PB8 ? Locations.Default8bNone : 0;
+ return pkm.Egg_Location == expect;
+ }
private bool IsMatchGender(PKM pkm)
{
diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic3.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic3.cs
index 420671ddd..0b4a01948 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic3.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic3.cs
@@ -22,7 +22,7 @@ protected override bool IsMatchEggLocation(PKM pkm)
{
if (pkm.Format == 3)
return !pkm.IsEgg || EggLocation == 0 || EggLocation == pkm.Met_Location;
- return pkm.Egg_Location == 0;
+ return base.IsMatchEggLocation(pkm);
}
protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo)
diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4.cs
index 4d9d79644..e1895c8d4 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4.cs
@@ -33,10 +33,10 @@ protected override bool IsMatchLocation(PKM pkm)
protected override bool IsMatchEggLocation(PKM pkm)
{
- var eggloc = pkm.Egg_Location;
if (!EggEncounter)
- return eggloc == 0;
+ return base.IsMatchEggLocation(pkm);
+ var eggloc = pkm.Egg_Location;
// Transferring 4->5 clears Pt/HG/SS location value and keeps Faraway Place
if (pkm is not G4PKM pk4)
{
diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5.cs
index f42c28efe..5358789d8 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5.cs
@@ -29,10 +29,10 @@ protected sealed override bool IsMatchLocation(PKM pk)
protected override bool IsMatchEggLocation(PKM pkm)
{
- var eggloc = pkm.Egg_Location;
if (!EggEncounter)
- return eggloc == EggLocation;
+ return base.IsMatchEggLocation(pkm);
+ var eggloc = pkm.Egg_Location;
if (!pkm.IsEgg) // hatched
return eggloc == EggLocation || eggloc == Locations.LinkTrade5;
diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic6.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic6.cs
index e8df11799..8419de0b7 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic6.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic6.cs
@@ -30,10 +30,10 @@ protected override bool IsMatchLocation(PKM pkm)
protected override bool IsMatchEggLocation(PKM pkm)
{
- var eggloc = pkm.Egg_Location;
if (!EggEncounter)
- return eggloc == EggLocation;
+ return base.IsMatchEggLocation(pkm);
+ var eggloc = pkm.Egg_Location;
if (!pkm.IsEgg) // hatched
return eggloc == EggLocation || eggloc == Locations.LinkTrade6;
diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7.cs
index 4669cae05..b5289fe17 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7.cs
@@ -23,10 +23,10 @@ protected override bool IsMatchLocation(PKM pkm)
protected override bool IsMatchEggLocation(PKM pkm)
{
- var eggloc = pkm.Egg_Location;
if (!EggEncounter)
- return eggloc == EggLocation;
+ return base.IsMatchEggLocation(pkm);
+ var eggloc = pkm.Egg_Location;
if (!pkm.IsEgg) // hatched
return eggloc == EggLocation || eggloc == Locations.LinkTrade6;
diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8.cs
index 2472d739a..03a855723 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8.cs
@@ -31,7 +31,7 @@ protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo)
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
- if (pkm is IDynamaxLevel d && d.DynamaxLevel < DynamaxLevel)
+ if (pkm is PK8 d && d.DynamaxLevel < DynamaxLevel)
return false;
if (pkm.Met_Level < EncounterArea8.BoostLevel && Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location))
return false;
diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8Nest.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8Nest.cs
index d378aba65..012ddfaa6 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8Nest.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8Nest.cs
@@ -20,11 +20,11 @@ public abstract record EncounterStatic8Nest(GameVersion Version) : EncounterS
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
- if (pkm is IDynamaxLevel d && d.DynamaxLevel < DynamaxLevel)
+ if (pkm is PK8 d && d.DynamaxLevel < DynamaxLevel)
return false;
// Required Ability
- if (Ability == AbilityPermission.OnlyHidden && pkm.AbilityNumber != 4)
+ if (Ability == OnlyHidden && pkm.AbilityNumber != 4)
return false; // H
if (Version != GameVersion.SWSH && pkm.Version != (int)Version && pkm.Met_Location != SharedNest)
@@ -64,7 +64,7 @@ protected sealed override EncounterMatchRating IsMatchDeferred(PKM pkm)
protected override bool IsMatchPartial(PKM pkm)
{
- if (pkm is IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pkm.Species, pkm.Form, Species, Form))
+ if (pkm is PK8 and IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pkm.Species, pkm.Form, Species, Form))
return true;
if (Species == (int)Core.Species.Alcremie && pkm is IFormArgument { FormArgument: not 0 })
return true;
diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs
index 204599ef9..2aa1f2c92 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs
@@ -83,6 +83,16 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
return true;
}
+ protected override bool IsMatchLocation(PKM pkm)
+ {
+ if (pkm is PK8)
+ return pkm.Met_Location == Locations.HOME_SWLA;
+ if (pkm is PB8 { Version: (int)GameVersion.PLA, Met_Location: Locations.HOME_SWLA })
+ return true;
+
+ return base.IsMatchLocation(pkm);
+ }
+
public override EncounterMatchRating GetMatchRating(PKM pkm)
{
var result = GetMatchRatingInternal(pkm);
diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8b.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8b.cs
index 3243ef2ab..ba637d31b 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8b.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8b.cs
@@ -12,14 +12,14 @@ public sealed record EncounterStatic8b : EncounterStatic, IStaticCorrelation8b
public override int Generation => 8;
public bool Roaming { get; init; }
+ public override bool EggEncounter => EggLocation != Locations.Default8bNone;
- public EncounterStatic8b(GameVersion game) : base(game)
- {
- EggLocation = Locations.Default8bNone;
- }
+ public EncounterStatic8b(GameVersion game) : base(game) => EggLocation = Locations.Default8bNone;
protected override bool IsMatchLocation(PKM pkm)
{
+ if (pkm is PK8)
+ return Locations.IsValidMetBDSP(pkm.Met_Location, pkm.Version);
if (!Roaming)
return base.IsMatchLocation(pkm);
return IsRoamingLocation(pkm);
@@ -47,17 +47,34 @@ public bool IsStaticCorrelationCorrect(PKM pk)
protected override bool IsMatchEggLocation(PKM pkm)
{
- var eggloc = (short)pkm.Egg_Location;
+ if (pkm is not PB8)
+ {
+ if (!EggEncounter)
+ return pkm.Egg_Location == 0;
+
+ if (pkm is PK8)
+ {
+ if (EggLocation > 60000 && pkm.Egg_Location == Locations.HOME_SWSHBDSPEgg)
+ return true;
+ // >60000 can be reset to Link Trade (30001), then altered differently.
+ return Locations.IsValidMetBDSP(pkm.Egg_Location, pkm.Version);
+ }
+
+ // Hatched
+ return pkm.Egg_Location == EggLocation || pkm.Egg_Location == Locations.LinkTrade6NPC;
+ }
+
+ var eggloc = pkm.Egg_Location;
if (!EggEncounter)
- return eggloc == (short)EggLocation;
+ return eggloc == EggLocation;
if (!pkm.IsEgg) // hatched
- return eggloc == (short)EggLocation || eggloc == Locations.LinkTrade6NPC;
+ return eggloc == EggLocation || eggloc == Locations.LinkTrade6NPC;
// Unhatched:
- if (eggloc != (short)EggLocation)
+ if (eggloc != EggLocation)
return false;
- if ((short)pkm.Met_Location is not (Locations.Default8bNone or Locations.LinkTrade6NPC))
+ if (pkm.Met_Location is not (Locations.Default8bNone or Locations.LinkTrade6NPC))
return false;
return true;
}
diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs
index 617b90652..8f4227846 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs
@@ -203,7 +203,7 @@ public virtual bool IsMatchExact(PKM pkm, EvoCriteria evo)
return false;
if (OTGender != -1 && OTGender != pkm.OT_Gender)
return false;
- if (EggLocation != pkm.Egg_Location)
+ if (!IsMatchEggLocation(pkm))
return false;
// if (z.Ability == 4 ^ pkm.AbilityNumber == 4) // defer to Ability
// continue;
@@ -213,6 +213,14 @@ public virtual bool IsMatchExact(PKM pkm, EvoCriteria evo)
return true;
}
+ protected virtual bool IsMatchEggLocation(PKM pkm)
+ {
+ var expect = EggLocation;
+ if (pkm is PB8 && expect is 0)
+ expect = Locations.Default8bNone;
+ return pkm.Egg_Location == expect;
+ }
+
private bool IsMatchLevel(PKM pkm, EvoCriteria evo)
{
if (!pkm.HasOriginalMetLocation)
diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8.cs
index 0ec0d24d6..759352700 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8.cs
@@ -35,7 +35,7 @@ public EncounterTrade8(GameVersion game, int species, byte level, byte memory, u
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
- if (pkm is IDynamaxLevel d && d.DynamaxLevel < DynamaxLevel)
+ if (pkm is PK8 d && d.DynamaxLevel < DynamaxLevel)
return false;
if (pkm.FlawlessIVCount < FlawlessIVCount)
return false;
diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8b.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8b.cs
index 7b3c3e691..a8e9a1a0e 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8b.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8b.cs
@@ -9,7 +9,7 @@ public sealed record EncounterTrade8b : EncounterTrade, IContestStats, IScaledSi
public override int Generation => 8;
public override int Location => Locations.LinkTrade6NPC;
- public EncounterTrade8b(GameVersion game) : base(game) => EggLocation = unchecked((ushort)Locations.Default8bNone);
+ public EncounterTrade8b(GameVersion game) : base(game) => EggLocation = Locations.Default8bNone;
public byte CNT_Cool => BaseContest;
public byte CNT_Beauty => BaseContest;
public byte CNT_Cute => BaseContest;
@@ -38,6 +38,14 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
return base.IsMatchExact(pkm, evo);
}
+ protected override bool IsMatchEggLocation(PKM pkm)
+ {
+ var expect = EggLocation;
+ if (pkm is not PB8 && expect == Locations.Default8bNone)
+ expect = 0;
+ return pkm.Egg_Location == expect;
+ }
+
protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk)
{
base.ApplyDetails(sav, criteria, pk);
diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs
index f874a55d2..ea490a82c 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs
@@ -79,8 +79,9 @@ private static IEnumerable GenerateRawEncounters3CXD(PKM pkm)
{
var chain = EncounterOrigin.GetOriginChain(pkm);
+ var game = (GameVersion)pkm.Version;
// Mystery Gifts
- foreach (var z in GetValidGifts(pkm, chain))
+ foreach (var z in GetValidGifts(pkm, chain, game))
{
// Don't bother deferring matches.
var match = z.GetMatchRating(pkm);
@@ -89,7 +90,7 @@ private static IEnumerable GenerateRawEncounters3CXD(PKM pkm)
}
// Trades
- foreach (var z in GetValidEncounterTrades(pkm, chain))
+ foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
// Don't bother deferring matches.
var match = z.GetMatchRating(pkm);
@@ -100,7 +101,7 @@ private static IEnumerable GenerateRawEncounters3CXD(PKM pkm)
IEncounterable? partial = null;
// Static Encounter
- foreach (var z in GetValidStaticEncounter(pkm, chain))
+ foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == PartialMatch)
@@ -110,7 +111,7 @@ private static IEnumerable GenerateRawEncounters3CXD(PKM pkm)
}
// Encounter Slots
- foreach (var z in GetValidWildEncounters(pkm, chain))
+ foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == PartialMatch)
@@ -128,9 +129,10 @@ private static IEnumerable GenerateRawEncounters3CXD(PKM pkm)
private static IEnumerable GenerateRawEncounters3(PKM pkm, LegalInfo info)
{
var chain = EncounterOrigin.GetOriginChain(pkm);
+ var game = (GameVersion)pkm.Version;
// Mystery Gifts
- foreach (var z in GetValidGifts(pkm, chain))
+ foreach (var z in GetValidGifts(pkm, chain, game))
{
// Don't bother deferring matches.
var match = z.GetMatchRating(pkm);
@@ -139,7 +141,7 @@ private static IEnumerable GenerateRawEncounters3(PKM pkm, Legal
}
// Trades
- foreach (var z in GetValidEncounterTrades(pkm, chain))
+ foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
// Don't bother deferring matches.
var match = z.GetMatchRating(pkm);
@@ -155,7 +157,7 @@ private static IEnumerable GenerateRawEncounters3(PKM pkm, Legal
bool safari = pkm.Ball == 0x05; // never static encounters
if (!safari)
{
- foreach (var z in GetValidStaticEncounter(pkm, chain))
+ foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == PartialMatch)
@@ -167,7 +169,7 @@ private static IEnumerable GenerateRawEncounters3(PKM pkm, Legal
// Encounter Slots
var slots = FrameFinder.GetFrames(info.PIDIV, pkm).ToList();
- foreach (var z in GetValidWildEncounters(pkm, chain))
+ foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == PartialMatch)
@@ -208,7 +210,7 @@ private static IEnumerable GenerateRawEncounters3(PKM pkm, Legal
partial = null;
- foreach (var z in GetValidStaticEncounter(pkm, chain))
+ foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == PartialMatch)
diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator4.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator4.cs
index 86319160b..294670aa3 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator4.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator4.cs
@@ -42,19 +42,20 @@ public static IEnumerable GetEncounters(PKM pkm, LegalInfo info)
private static IEnumerable GenerateRawEncounters4(PKM pkm, LegalInfo info)
{
var chain = EncounterOrigin.GetOriginChain(pkm);
+ var game = (GameVersion)pkm.Version;
if (pkm.FatefulEncounter)
{
int ctr = 0;
- foreach (var z in GetValidGifts(pkm, chain))
+ foreach (var z in GetValidGifts(pkm, chain, game))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
- if (Locations.IsEggLocationBred4(pkm.Egg_Location, (GameVersion)pkm.Version))
+ if (Locations.IsEggLocationBred4(pkm.Egg_Location, game))
{
foreach (var z in GenerateEggs(pkm, 4))
yield return z;
}
- foreach (var z in GetValidEncounterTrades(pkm, chain))
+ foreach (var z in GetValidEncounterTrades(pkm, chain, game))
yield return z;
IEncounterable? deferred = null;
@@ -63,7 +64,7 @@ private static IEnumerable GenerateRawEncounters4(PKM pkm, Legal
bool safariSport = pkm.Ball is (int)Ball.Sport or (int)Ball.Safari; // never static encounters
if (!safariSport)
{
- foreach (var z in GetValidStaticEncounter(pkm, chain))
+ foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == PartialMatch)
@@ -74,7 +75,7 @@ private static IEnumerable GenerateRawEncounters4(PKM pkm, Legal
}
var slots = FrameFinder.GetFrames(info.PIDIV, pkm).ToList();
- foreach (var slot in GetValidWildEncounters(pkm, chain))
+ foreach (var slot in GetValidWildEncounters(pkm, chain, game))
{
var z = (EncounterSlot4)slot;
var match = z.GetMatchRating(pkm);
@@ -115,7 +116,7 @@ private static IEnumerable GenerateRawEncounters4(PKM pkm, Legal
if (!safariSport)
yield break;
- foreach (var z in GetValidStaticEncounter(pkm, chain))
+ foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == PartialMatch)
diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator5.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator5.cs
index f5d0564ae..6abdeafbb 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator5.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator5.cs
@@ -16,9 +16,11 @@ public static IEnumerable GetEncounters(PKM pkm)
int ctr = 0;
var chain = EncounterOrigin.GetOriginChain(pkm);
+ var game = (GameVersion)pkm.Version;
+
if (pkm.FatefulEncounter)
{
- foreach (var z in GetValidGifts(pkm, chain))
+ foreach (var z in GetValidGifts(pkm, chain, game))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
@@ -33,7 +35,7 @@ public static IEnumerable GetEncounters(PKM pkm)
IEncounterable? deferred = null;
IEncounterable? partial = null;
- foreach (var z in GetValidStaticEncounter(pkm, chain))
+ foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@@ -45,7 +47,7 @@ public static IEnumerable GetEncounters(PKM pkm)
}
if (ctr != 0) yield break;
- foreach (var z in GetValidWildEncounters(pkm, chain))
+ foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@@ -57,7 +59,7 @@ public static IEnumerable GetEncounters(PKM pkm)
}
if (ctr != 0) yield break;
- foreach (var z in GetValidEncounterTrades(pkm, chain))
+ foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator6.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator6.cs
index ce4cc567c..36badf7df 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator6.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator6.cs
@@ -16,13 +16,14 @@ public static IEnumerable GetEncounters(PKM pkm)
int ctr = 0;
var chain = EncounterOrigin.GetOriginChain(pkm);
+ var game = (GameVersion)pkm.Version;
IEncounterable? deferred = null;
IEncounterable? partial = null;
if (pkm.FatefulEncounter || pkm.Met_Location == Locations.LinkGift6)
{
- foreach (var z in GetValidGifts(pkm, chain))
+ foreach (var z in GetValidGifts(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@@ -53,7 +54,7 @@ public static IEnumerable GetEncounters(PKM pkm)
if (ctr == 0) yield break;
}
- foreach (var z in GetValidStaticEncounter(pkm, chain))
+ foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@@ -65,7 +66,7 @@ public static IEnumerable GetEncounters(PKM pkm)
}
if (ctr != 0) yield break;
- foreach (var z in GetValidWildEncounters(pkm, chain))
+ foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@@ -77,7 +78,7 @@ public static IEnumerable GetEncounters(PKM pkm)
}
if (ctr != 0) yield break;
- foreach (var z in GetValidEncounterTrades(pkm, chain))
+ foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7.cs
index ea21a8f13..de82c7da8 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7.cs
@@ -28,7 +28,7 @@ internal static IEnumerable GetEncountersGO(PKM pkm, EvoCriteria
IEncounterable? partial = null;
int ctr = 0;
- foreach (var z in GetValidWildEncounters(pkm, chain))
+ foreach (var z in GetValidWildEncounters(pkm, chain, GameVersion.GO))
{
var match = z.GetMatchRating(pkm);
switch (match)
@@ -50,9 +50,11 @@ internal static IEnumerable GetEncountersGO(PKM pkm, EvoCriteria
private static IEnumerable GetEncountersGG(PKM pkm, EvoCriteria[] chain)
{
int ctr = 0;
+ var game = (GameVersion)pkm.Version;
+
if (pkm.FatefulEncounter)
{
- foreach (var z in GetValidGifts(pkm, chain))
+ foreach (var z in GetValidGifts(pkm, chain, game))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
@@ -60,7 +62,7 @@ private static IEnumerable GetEncountersGG(PKM pkm, EvoCriteria[
IEncounterable? deferred = null;
IEncounterable? partial = null;
- foreach (var z in GetValidStaticEncounter(pkm, chain))
+ foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@@ -72,7 +74,7 @@ private static IEnumerable GetEncountersGG(PKM pkm, EvoCriteria[
}
if (ctr != 0) yield break;
- foreach (var z in GetValidWildEncounters(pkm, chain))
+ foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@@ -84,7 +86,7 @@ private static IEnumerable GetEncountersGG(PKM pkm, EvoCriteria[
}
if (ctr != 0) yield break;
- foreach (var z in GetValidEncounterTrades(pkm, chain))
+ foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@@ -105,9 +107,11 @@ private static IEnumerable GetEncountersGG(PKM pkm, EvoCriteria[
private static IEnumerable GetEncountersMainline(PKM pkm, EvoCriteria[] chain)
{
int ctr = 0;
+ var game = (GameVersion)pkm.Version;
+
if (pkm.FatefulEncounter)
{
- foreach (var z in GetValidGifts(pkm, chain))
+ foreach (var z in GetValidGifts(pkm, chain, game))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
@@ -122,7 +126,7 @@ private static IEnumerable GetEncountersMainline(PKM pkm, EvoCri
IEncounterable? deferred = null;
IEncounterable? partial = null;
- foreach (var z in GetValidStaticEncounter(pkm, chain))
+ foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@@ -134,7 +138,7 @@ private static IEnumerable GetEncountersMainline(PKM pkm, EvoCri
}
if (ctr != 0) yield break;
- foreach (var z in GetValidWildEncounters(pkm, chain))
+ foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@@ -146,7 +150,7 @@ private static IEnumerable GetEncountersMainline(PKM pkm, EvoCri
}
if (ctr != 0) yield break;
- foreach (var z in GetValidEncounterTrades(pkm, chain))
+ foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8.cs
index 7a9c4b9a3..50a8b69de 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8.cs
@@ -19,6 +19,9 @@ public static IEnumerable GetEncounters(PKM pkm)
(int)GameVersion.GO => EncounterGenerator7.GetEncountersGO(pkm, chain),
(int)GameVersion.PLA => EncounterGenerator8a.GetEncounters(pkm, chain),
(int)GameVersion.BD or (int)GameVersion.SP => EncounterGenerator8b.GetEncounters(pkm, chain),
+ (int)GameVersion.SW when pkm.Met_Location == Locations.HOME_SWLA => EncounterGenerator8a.GetEncounters(pkm, chain),
+ (int)GameVersion.SW when pkm.Met_Location == Locations.HOME_SWBD => EncounterGenerator8b.GetEncountersFuzzy(pkm, chain, GameVersion.BD),
+ (int)GameVersion.SH when pkm.Met_Location == Locations.HOME_SHSP => EncounterGenerator8b.GetEncountersFuzzy(pkm, chain, GameVersion.SP),
_ => GetEncountersMainline(pkm, chain),
};
}
@@ -26,9 +29,11 @@ public static IEnumerable GetEncounters(PKM pkm)
private static IEnumerable GetEncountersMainline(PKM pkm, EvoCriteria[] chain)
{
int ctr = 0;
+ var game = (GameVersion)pkm.Version;
+
if (pkm.FatefulEncounter)
{
- foreach (var z in GetValidGifts(pkm, chain))
+ foreach (var z in GetValidGifts(pkm, chain, game))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
@@ -46,7 +51,7 @@ private static IEnumerable GetEncountersMainline(PKM pkm, EvoCri
// Trades
if (pkm.Met_Location == Locations.LinkTrade6NPC)
{
- foreach (var z in GetValidEncounterTrades(pkm, chain))
+ foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == Match)
@@ -66,7 +71,7 @@ private static IEnumerable GetEncountersMainline(PKM pkm, EvoCri
}
// Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded.
- var encs = GetValidStaticEncounter(pkm, chain);
+ var encs = GetValidStaticEncounter(pkm, chain, game);
foreach (var z in encs)
{
var match = z.GetMatchRating(pkm);
@@ -81,7 +86,7 @@ private static IEnumerable GetEncountersMainline(PKM pkm, EvoCri
}
}
- foreach (var z in GetValidWildEncounters(pkm, chain))
+ foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == Match)
diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8a.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8a.cs
index 2830be3cc..e61ef7514 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8a.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8a.cs
@@ -11,13 +11,15 @@ internal static class EncounterGenerator8a
{
public static IEnumerable GetEncounters(PKM pkm, EvoCriteria[] chain)
{
+ if (pkm is PK8 { SWSH: false })
+ yield break;
if (pkm.IsEgg)
yield break;
int ctr = 0;
if (pkm.FatefulEncounter)
{
- foreach (var z in GetValidGifts(pkm, chain))
+ foreach (var z in GetValidGifts(pkm, chain, GameVersion.PLA))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
@@ -26,7 +28,7 @@ public static IEnumerable GetEncounters(PKM pkm, EvoCriteria[] c
EncounterMatchRating rating = None;
// Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded.
- var encs = GetValidStaticEncounter(pkm, chain);
+ var encs = GetValidStaticEncounter(pkm, chain, GameVersion.PLA);
foreach (var z in encs)
{
var match = z.GetMatchRating(pkm);
@@ -41,7 +43,7 @@ public static IEnumerable GetEncounters(PKM pkm, EvoCriteria[] c
}
}
- foreach (var z in GetValidWildEncounters(pkm, chain))
+ foreach (var z in GetValidWildEncounters(pkm, chain, GameVersion.PLA))
{
var match = z.GetMatchRating(pkm);
if (match == Match)
diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8b.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8b.cs
index f5224799c..035dad326 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8b.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8b.cs
@@ -13,10 +13,14 @@ internal static class EncounterGenerator8b
{
public static IEnumerable GetEncounters(PKM pkm, EvoCriteria[] chain)
{
+ if (pkm is PK8)
+ yield break;
int ctr = 0;
+ var game = (GameVersion)pkm.Version;
+
if (pkm.FatefulEncounter)
{
- foreach (var z in GetValidGifts(pkm, chain))
+ foreach (var z in GetValidGifts(pkm, chain, game))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
@@ -34,7 +38,7 @@ public static IEnumerable GetEncounters(PKM pkm, EvoCriteria[] c
// Trades
if (!pkm.IsEgg && pkm.Met_Location == Locations.LinkTrade6NPC)
{
- foreach (var z in GetValidEncounterTrades(pkm, chain))
+ foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == Match)
@@ -54,7 +58,7 @@ public static IEnumerable GetEncounters(PKM pkm, EvoCriteria[] c
}
// Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded.
- var encs = GetValidStaticEncounter(pkm, chain);
+ var encs = GetValidStaticEncounter(pkm, chain, game);
foreach (var z in encs)
{
var match = z.GetMatchRating(pkm);
@@ -69,7 +73,83 @@ public static IEnumerable GetEncounters(PKM pkm, EvoCriteria[] c
}
}
- foreach (var z in GetValidWildEncounters(pkm, chain))
+ foreach (var z in GetValidWildEncounters(pkm, chain, game))
+ {
+ var match = z.GetMatchRating(pkm);
+ if (match == Match)
+ {
+ yield return z;
+ }
+ else if (match < rating)
+ {
+ cache = z;
+ rating = match;
+ }
+ }
+
+ if (cache != null)
+ yield return cache;
+ }
+
+ public static IEnumerable GetEncountersFuzzy(PKM pkm, EvoCriteria[] chain, GameVersion game)
+ {
+ int ctr = 0;
+
+ if (pkm.FatefulEncounter)
+ {
+ foreach (var z in GetValidGifts(pkm, chain, game))
+ { yield return z; ++ctr; }
+ if (ctr != 0) yield break;
+ }
+
+ if (pkm.Egg_Location == Locations.HOME_SWSHBDSPEgg && pkm.Met_Level == 1)
+ {
+ foreach (var z in GenerateEggs(pkm, 8))
+ { yield return z; ++ctr; }
+ if (ctr == 0) yield break;
+ }
+
+ IEncounterable? cache = null;
+ EncounterMatchRating rating = None;
+
+ // Trades
+ if (!pkm.IsEgg)
+ {
+ foreach (var z in GetValidEncounterTrades(pkm, chain, game))
+ {
+ var match = z.GetMatchRating(pkm);
+ if (match == Match)
+ {
+ yield return z;
+ }
+ else if (match < rating)
+ {
+ cache = z;
+ rating = match;
+ }
+ }
+
+ if (cache != null)
+ yield return cache;
+ }
+
+ // Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded.
+ var encs = GetValidStaticEncounter(pkm, chain, game);
+ foreach (var z in encs)
+ {
+ var match = z.GetMatchRating(pkm);
+ if (match == Match)
+ {
+ yield return z;
+ }
+ else if (match < rating)
+ {
+ cache = z;
+ rating = match;
+ }
+ }
+
+ foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == Match)
diff --git a/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs b/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs
index e6d2a3670..421740c48 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs
@@ -173,18 +173,18 @@ private static string GetHintWhyNotFound(PKM pkm, int gen)
private static bool WasEventEgg(PKM pkm, int gen) => gen switch
{
- // Event Egg, indistinguible from normal eggs after hatch
+ // Event Egg, indistinguishable from normal eggs after hatch
// can't tell after transfer
- 3 => pkm.Format == 3 && pkm.IsEgg && pkm.Met_Location == 255,
+ 3 => pkm.Format == 3 && pkm.IsEgg && Locations.IsEventLocation3(pkm.Met_Location),
// Manaphy was the only generation 4 released event egg
- _ => pkm.Egg_Location is not 0 && pkm.FatefulEncounter,
+ _ => pkm.FatefulEncounter && pkm.Egg_Day != 0,
};
private static bool WasEvent(PKM pkm, int gen) => pkm.FatefulEncounter || gen switch
{
- 3 => (pkm.Met_Location == 255 && pkm.Format == 3),
- 4 => (Locations.IsEventLocation4(pkm.Met_Location) && pkm.Format == 4),
+ 3 => Locations.IsEventLocation3(pkm.Met_Location) && pkm.Format == 3,
+ 4 => Locations.IsEventLocation4(pkm.Met_Location) && pkm.Format == 4,
>=5 => Locations.IsEventLocation5(pkm.Met_Location),
_ => false,
};
diff --git a/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterMovesetGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterMovesetGenerator.cs
index c8b4d8de8..91d1babec 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterMovesetGenerator.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterMovesetGenerator.cs
@@ -142,10 +142,10 @@ public static IEnumerable GenerateVersionEncounters(PKM pk, IEnu
if (pk.Species == 0) // can enter this method after failing to set a species ID that cannot exist in the format
return Array.Empty();
pk.Version = (int)version;
- var format = pk.Format;
- if (format is 2 && version is GameVersion.RD or GameVersion.GN or GameVersion.BU or GameVersion.YW)
- format = 1; // try excluding baby pokemon from our evolution chain, for move learning purposes.
- var et = EvolutionTree.GetEvolutionTree(pk, format);
+ var context = pk.Context;
+ if (context is EntityContext.Gen2 && version is GameVersion.RD or GameVersion.GN or GameVersion.BU or GameVersion.YW)
+ context = EntityContext.Gen1; // try excluding baby pokemon from our evolution chain, for move learning purposes.
+ var et = EvolutionTree.GetEvolutionTree(context);
var chain = et.GetValidPreEvolutions(pk, maxLevel: 100, skipChecks: true);
int[] needs = GetNeededMoves(pk, moves, chain);
@@ -225,7 +225,7 @@ private static IEnumerable GetPossibleOfType(PKM pk, IReadOnlyLi
return type switch
{
EncounterOrder.Egg => GetEggs(pk, needs, chain, version),
- EncounterOrder.Mystery => GetGifts(pk, needs, chain),
+ EncounterOrder.Mystery => GetGifts(pk, needs, chain, version),
EncounterOrder.Static => GetStatic(pk, needs, chain, version),
EncounterOrder.Trade => GetTrades(pk, needs, chain, version),
EncounterOrder.Slot => GetSlots(pk, needs, chain, version),
@@ -274,11 +274,12 @@ private static IEnumerable GetEggs(PKM pk, IReadOnlyCollectionRough Pokémon data which contains the requested species, gender, and form.
/// Moves which cannot be taught by the player.
/// Origin possible evolution chain
+ /// Specific version to iterate for.
/// A consumable list of possible encounters.
- private static IEnumerable GetGifts(PKM pk, IReadOnlyCollection needs, EvoCriteria[] chain)
+ private static IEnumerable GetGifts(PKM pk, IReadOnlyCollection needs, EvoCriteria[] chain, GameVersion version)
{
var format = pk.Format;
- var gifts = MysteryGiftGenerator.GetPossible(pk, chain);
+ var gifts = MysteryGiftGenerator.GetPossible(pk, chain, version);
foreach (var gift in gifts)
{
if (gift is WC3 {NotDistributed: true})
diff --git a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator.cs
index 612ba903e..92a640aec 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator.cs
@@ -8,7 +8,7 @@ public static class EncounterEggGenerator
{
public static IEnumerable GenerateEggs(PKM pkm, int generation, bool all = false)
{
- var table = EvolutionTree.GetEvolutionTree(pkm, pkm.Format);
+ var table = EvolutionTree.GetEvolutionTree(pkm.Context);
int maxSpeciesOrigin = GetMaxSpeciesOrigin(generation);
var evos = table.GetValidPreEvolutions(pkm, maxLevel: 100, maxSpeciesOrigin: maxSpeciesOrigin, skipChecks: true);
return GenerateEggs(pkm, evos, generation, all);
diff --git a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator2.cs b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator2.cs
index 17f12b71e..91d78f059 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator2.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator2.cs
@@ -9,7 +9,7 @@ internal static class EncounterEggGenerator2
{
public static IEnumerable GenerateEggs(PKM pkm, bool all = false)
{
- var table = EvolutionTree.GetEvolutionTree(pkm, 2);
+ var table = EvolutionTree.Evolves2;
int maxSpeciesOrigin = Legal.GetMaxSpeciesOrigin(2);
var evos = table.GetValidPreEvolutions(pkm, maxLevel: 100, maxSpeciesOrigin: maxSpeciesOrigin, skipChecks: true);
return GenerateEggs(pkm, evos, all);
diff --git a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterSlotGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterSlotGenerator.cs
index 4689df8a7..aa89343f8 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterSlotGenerator.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterSlotGenerator.cs
@@ -45,7 +45,9 @@ public static IEnumerable GetPossible(PKM pkm, EvoCriteria[] chai
private static IEnumerable GetRawEncounterSlots(PKM pkm, EvoCriteria[] chain, GameVersion gameSource)
{
- if (!Locations.IsNoneLocation(gameSource, pkm.Egg_Location) || pkm.IsEgg)
+ if (pkm.IsEgg)
+ yield break;
+ if (IsMetAsEgg(pkm))
yield break;
var possibleAreas = GetEncounterAreas(pkm, gameSource);
@@ -62,6 +64,11 @@ public static IEnumerable GetValidWildEncounters12(PKM pkm, EvoCr
return GetRawEncounterSlots(pkm, chain, gameSource);
}
+ public static IEnumerable GetValidWildEncounters(PKM pkm, EvoCriteria[] chain, GameVersion gameSource)
+ {
+ return GetRawEncounterSlots(pkm, chain, gameSource);
+ }
+
public static IEnumerable GetValidWildEncounters(PKM pkm, EvoCriteria[] chain)
{
var gameSource = (GameVersion)pkm.Version;
diff --git a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterStaticGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterStaticGenerator.cs
index 7fbd29f96..9b8c5d35f 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterStaticGenerator.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterStaticGenerator.cs
@@ -48,11 +48,6 @@ static IEnumerable GetEvents(GameVersion g)
return table.Where(e => chain.Any(d => d.Species == e.Species));
}
- public static IEnumerable GetValidStaticEncounter(PKM pkm, EvoCriteria[] chain)
- {
- return GetValidStaticEncounter(pkm, chain, (GameVersion)pkm.Version);
- }
-
public static IEnumerable GetValidStaticEncounter(PKM pkm, EvoCriteria[] chain, GameVersion gameSource)
{
var table = GetEncounterStaticTable(pkm, gameSource);
diff --git a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterTradeGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterTradeGenerator.cs
index e962577f7..d0f0f802e 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterTradeGenerator.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterTradeGenerator.cs
@@ -42,7 +42,7 @@ public static IEnumerable GetValidEncounterTradesVC(PKM pkm, E
}
}
- public static IEnumerable GetValidEncounterTrades(PKM pkm, EvoCriteria[] chain)
+ public static IEnumerable GetValidEncounterTrades(PKM pkm, EvoCriteria[] chain, GameVersion game)
{
// Pre-filter for some language scenarios
int lang = pkm.Language;
@@ -51,7 +51,6 @@ public static IEnumerable GetValidEncounterTrades(PKM pkm, EvoCr
if (lang == (int)LanguageID.Hacked && !EncounterTrade5PID.IsValidMissingLanguage(pkm)) // Japanese trades in BW have no language ID
return Array.Empty();
- var game = (GameVersion)pkm.Version;
var table = GetTable(game);
return GetValidEncounterTrades(pkm, chain, table);
}
diff --git a/PKHeX.Core/Legality/Encounters/Generator/Specific/MysteryGiftGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/Specific/MysteryGiftGenerator.cs
index bac13d4bc..d69902d71 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/Specific/MysteryGiftGenerator.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/Specific/MysteryGiftGenerator.cs
@@ -7,20 +7,20 @@ namespace PKHeX.Core
{
public static class MysteryGiftGenerator
{
- public static IEnumerable GetPossible(PKM pkm, EvoCriteria[] chain)
+ public static IEnumerable GetPossible(PKM pkm, EvoCriteria[] chain, GameVersion game)
{
// Ranger Manaphy is a PGT and is not in the PCD[] for gen4. Check manually.
int gen = pkm.Generation;
if (gen == 4 && pkm.Species == (int) Species.Manaphy)
yield return RangerManaphy;
- var table = GetTable(gen, pkm);
+ var table = GetTable(gen, game);
var possible = table.Where(wc => chain.Any(evo => evo.Species == wc.Species));
foreach (var enc in possible)
yield return enc;
}
- public static IEnumerable GetValidGifts(PKM pkm, EvoCriteria[] chain)
+ public static IEnumerable GetValidGifts(PKM pkm, EvoCriteria[] chain, GameVersion game)
{
int gen = pkm.Generation;
if (pkm.IsEgg && pkm.Format != gen) // transferred
@@ -28,18 +28,23 @@ public static IEnumerable GetValidGifts(PKM pkm, EvoCriteria[] chai
if (gen == 4) // check for Manaphy gift
return GetMatchingPCD(pkm, MGDB_G4, chain);
- var table = GetTable(gen, pkm);
+ var table = GetTable(gen, game);
return GetMatchingGifts(pkm, table, chain);
}
- private static IReadOnlyCollection GetTable(int generation, PKM pkm) => generation switch
+ private static IReadOnlyCollection GetTable(int generation, GameVersion game) => generation switch
{
3 => MGDB_G3,
4 => MGDB_G4,
5 => MGDB_G5,
6 => MGDB_G6,
- 7 => pkm.LGPE ? MGDB_G7GG : MGDB_G7,
- 8 => pkm.BDSP ? MGDB_G8B : pkm.LA ? MGDB_G8A : MGDB_G8,
+ 7 => game is GameVersion.GP or GameVersion.GE ? MGDB_G7GG : MGDB_G7,
+ 8 => game switch
+ {
+ GameVersion.BD or GameVersion.SP => MGDB_G8B,
+ GameVersion.PLA => MGDB_G8A,
+ _ => MGDB_G8,
+ },
_ => Array.Empty(),
};
diff --git a/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestion.cs b/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestion.cs
index 7e4eae512..33e59cade 100644
--- a/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestion.cs
+++ b/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestion.cs
@@ -103,7 +103,7 @@ public static int GetLowestLevel(PKM pkm, byte startLevel)
if (startLevel >= 100)
startLevel = 100;
- var table = EvolutionTree.GetEvolutionTree(pkm, pkm.Format);
+ var table = EvolutionTree.GetEvolutionTree(pkm.Context);
int count = 1;
for (byte i = 100; i >= startLevel; i--)
{
diff --git a/PKHeX.Core/Legality/Encounters/Information/ValidEncounterMoves.cs b/PKHeX.Core/Legality/Encounters/Information/ValidEncounterMoves.cs
index 00869782a..f8a68180d 100644
--- a/PKHeX.Core/Legality/Encounters/Information/ValidEncounterMoves.cs
+++ b/PKHeX.Core/Legality/Encounters/Information/ValidEncounterMoves.cs
@@ -17,7 +17,7 @@ public sealed class ValidEncounterMoves
private const int EmptyCount = PKX.Generation + 1; // one for each generation index (and 0th)
private static readonly IReadOnlyList[] Empty = Enumerable.Repeat((IReadOnlyList)new List(), EmptyCount).ToArray();
- public ValidEncounterMoves(PKM pkm, IEncounterTemplate encounter, EvoCriteria[][] chains)
+ public ValidEncounterMoves(PKM pkm, IEncounterTemplate encounter, EvolutionHistory chains)
{
var level = MoveList.GetValidMovesAllGens(pkm, chains, types: MoveSourceType.Encounter, RemoveTransferHM: false);
diff --git a/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs b/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs
index 3c6f9eca9..63e64be8d 100644
--- a/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs
+++ b/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs
@@ -124,7 +124,9 @@ private static CheckResult VerifyEncounterEgg3Transfer(PKM pkm)
return new CheckResult(Severity.Invalid, LTransferEgg, CheckIdentifier.Encounter);
if (pkm.Met_Level < 5)
return new CheckResult(Severity.Invalid, LTransferEggMetLevel, CheckIdentifier.Encounter);
- if (pkm.Egg_Location != 0)
+
+ var expectEgg = pkm is PB8 ? Locations.Default8bNone : 0;
+ if (pkm.Egg_Location != expectEgg)
return new CheckResult(Severity.Invalid, LEggLocationNone, CheckIdentifier.Encounter);
if (pkm.Format != 4)
@@ -173,7 +175,7 @@ private static CheckResult VerifyEncounterEgg6(PKM pkm)
if (pkm.AO)
return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_AO);
- if (pkm.Egg_Location == 318)
+ if (pkm.Egg_Location == Locations.HatchLocation6AO) // Battle Resort Daycare is only OR/AS.
return new CheckResult(Severity.Invalid, LEggMetLocationFail, CheckIdentifier.Encounter);
return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_XY);
@@ -193,7 +195,11 @@ private static CheckResult VerifyEncounterEgg7(PKM pkm)
private static CheckResult VerifyEncounterEgg8(PKM pkm)
{
if (pkm.SWSH)
+ {
+ if (pkm.BDSP)
+ return VerifyEncounterEggLevelLoc(pkm, 1, (location, game) => location == (game == GameVersion.SW ? Locations.HOME_SWBD : Locations.HOME_SHSP));
return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_SWSH);
+ }
// no other games
return new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter);
@@ -223,7 +229,7 @@ private static CheckResult VerifyEncounterEggLevelLoc(PKM pkm, int eggLevel, Fun
: new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter);
}
- private static CheckResult VerifyUnhatchedEgg(PKM pkm, int tradeLoc, short noneLoc = 0)
+ private static CheckResult VerifyUnhatchedEgg(PKM pkm, int tradeLoc, int noneLoc = 0)
{
var eggLevel = pkm.Format < 5 ? 0 : 1;
if (pkm.Met_Level != eggLevel)
@@ -234,7 +240,7 @@ private static CheckResult VerifyUnhatchedEgg(PKM pkm, int tradeLoc, short noneL
var met = pkm.Met_Location;
if (met == tradeLoc)
return new CheckResult(Severity.Valid, LEggLocationTrade, CheckIdentifier.Encounter);
- return (short)met == noneLoc
+ return met == noneLoc
? new CheckResult(Severity.Valid, LEggUnhatched, CheckIdentifier.Encounter)
: new CheckResult(Severity.Invalid, LEggLocationNone, CheckIdentifier.Encounter);
}
diff --git a/PKHeX.Core/Legality/Encounters/Verifiers/VerifyCurrentMoves.cs b/PKHeX.Core/Legality/Encounters/Verifiers/VerifyCurrentMoves.cs
index 3d003e406..d1dfde8d3 100644
--- a/PKHeX.Core/Legality/Encounters/Verifiers/VerifyCurrentMoves.cs
+++ b/PKHeX.Core/Legality/Encounters/Verifiers/VerifyCurrentMoves.cs
@@ -78,7 +78,7 @@ private static void ParseMovesWasEggPreRelearn(PKM pkm, CheckMoveResult[] parse,
var TradebackPreevo = pkm.Format == 2 && e.Species > 151;
var NonTradebackLvlMoves = TradebackPreevo
- ? MoveList.GetExclusivePreEvolutionMoves(pkm, e.Species, info.EvoChainsAllGens[2], 2, e.Version).Where(m => m > Legal.MaxMoveID_1).ToArray()
+ ? MoveList.GetExclusivePreEvolutionMoves(pkm, e.Species, info.EvoChainsAllGens.Gen2, 2, e.Version).Where(m => m > Legal.MaxMoveID_1).ToArray()
: Array.Empty();
var Egg = MoveEgg.GetEggMoves(pkm.PersonalInfo, e.Species, e.Form, e.Version, e.Generation);
@@ -238,9 +238,10 @@ private static void ParseMoves(PKM pkm, MoveParseSource source, LegalInfo info,
// Special considerations!
const int NoMinGeneration = 0;
int minGeneration = NoMinGeneration;
- if (pkm is IBattleVersion {BattleVersion: not 0} v)
+ if (pkm.IsOriginalMovesetDeleted())
{
- minGeneration = ((GameVersion) v.BattleVersion).GetGeneration();
+ var (_, resetGame) = pkm.IsMovesetRestricted();
+ minGeneration = resetGame.GetGeneration();
source.ResetSources();
}
@@ -578,7 +579,7 @@ private static void ParseEvolutionsIncompatibleMoves(PKM pkm, CheckMoveResult[]
}
}
- private static void ParseShedinjaEvolveMoves(PKM pkm, CheckMoveResult[] parse, IReadOnlyList currentMoves, EvoCriteria[][] evos)
+ private static void ParseShedinjaEvolveMoves(PKM pkm, CheckMoveResult[] parse, IReadOnlyList currentMoves, EvolutionHistory evos)
{
int shedinjaEvoMoveIndex = 0;
var format = pkm.Format;
diff --git a/PKHeX.Core/Legality/Encounters/Verifiers/VerifyRelearnMoves.cs b/PKHeX.Core/Legality/Encounters/Verifiers/VerifyRelearnMoves.cs
index 76a440e51..fc90ec0c9 100644
--- a/PKHeX.Core/Legality/Encounters/Verifiers/VerifyRelearnMoves.cs
+++ b/PKHeX.Core/Legality/Encounters/Verifiers/VerifyRelearnMoves.cs
@@ -27,7 +27,7 @@ public static CheckMoveResult[] VerifyRelearn(PKM pkm, IEncounterTemplate enc, C
};
}
- public static bool ShouldNotHaveRelearnMoves(IGeneration enc, PKM pkm) => enc.Generation < 6 || pkm is IBattleVersion {BattleVersion: not 0};
+ public static bool ShouldNotHaveRelearnMoves(IGeneration enc, PKM pkm) => enc.Generation < 6 || pkm.IsOriginalMovesetDeleted();
private static CheckMoveResult[] VerifyRelearnSpecifiedMoveset(PKM pkm, IReadOnlyList required, CheckMoveResult[] result)
{
diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionChain.cs b/PKHeX.Core/Legality/Evolutions/EvolutionChain.cs
index a31cd4b80..07908f360 100644
--- a/PKHeX.Core/Legality/Evolutions/EvolutionChain.cs
+++ b/PKHeX.Core/Legality/Evolutions/EvolutionChain.cs
@@ -6,9 +6,7 @@ namespace PKHeX.Core
{
public static class EvolutionChain
{
- private static readonly EvoCriteria[] NONE = Array.Empty();
-
- internal static EvoCriteria[][] GetEvolutionChainsAllGens(PKM pkm, IEncounterTemplate enc)
+ internal static EvolutionHistory GetEvolutionChainsAllGens(PKM pkm, IEncounterTemplate enc)
{
var chain = GetEvolutionChain(pkm, enc, pkm.Species, (byte)pkm.CurrentLevel);
if (chain.Length == 0 || pkm.IsEgg || enc is EncounterInvalid)
@@ -17,40 +15,36 @@ internal static EvoCriteria[][] GetEvolutionChainsAllGens(PKM pkm, IEncounterTem
return GetChainAll(pkm, enc, chain);
}
- private static EvoCriteria[][] GetAllEmpty(int count)
+ private static EvolutionHistory GetChainSingle(PKM pkm, EvoCriteria[] fullChain)
{
- var result = new EvoCriteria[count][];
- for (int i = 0; i < result.Length; i++)
- result[i] = NONE; // default no-evolutions
- return result;
+ var count = Math.Max(2, pkm.Format) + 1;
+ return new EvolutionHistory(fullChain, count)
+ {
+ [pkm.Format] = fullChain,
+ };
}
- private static EvoCriteria[][] GetChainSingle(PKM pkm, EvoCriteria[] fullChain)
+ private static EvolutionHistory GetChainAll(PKM pkm, IEncounterTemplate enc, EvoCriteria[] fullChain)
{
- var chain = GetAllEmpty(Math.Max(2, pkm.Format) + 1);
- chain[pkm.Format] = fullChain;
- return chain;
- }
-
- private static EvoCriteria[][] GetChainAll(PKM pkm, IEncounterTemplate enc, EvoCriteria[] fullChain)
- {
- int maxgen = ParseSettings.AllowGen1Tradeback && pkm is PK1 ? 2 : pkm.Format;
- var GensEvoChains = GetAllEmpty(maxgen + 1);
+ int maxgen = ParseSettings.AllowGen1Tradeback && pkm.Context == EntityContext.Gen1 ? 2 : pkm.Format;
+ var GensEvoChains = new EvolutionHistory(fullChain, maxgen + 1);
var head = 0; // inlined FIFO queue indexing
var mostEvolved = fullChain[head++];
var lvl = (byte)pkm.CurrentLevel;
var maxLevel = lvl;
- int pkGen = enc.Generation;
// Iterate generations backwards
// Maximum level of an earlier generation (GenX) will never be greater than a later generation (GenX+Y).
- int mingen = pkGen >= 3 ? pkGen : GBRestrictions.GetTradebackStatusInitial(pkm) == PotentialGBOrigin.Gen2Only ? 2 : 1;
+ int mingen = enc.Generation;
+ if (mingen is 1 or 2)
+ mingen = GBRestrictions.GetTradebackStatusInitial(pkm) == PotentialGBOrigin.Gen2Only ? 2 : 1;
+
bool noxfrDecremented = true;
for (int g = GensEvoChains.Length - 1; g >= mingen; g--)
{
- if (pkGen <= 2 && g == 6)
+ if (g == 6 && enc.Generation < 3)
g = 2; // skip over 6543 as it never existed in these.
if (g <= 4 && pkm.Format > 2 && pkm.Format > g && !pkm.HasOriginalMetLocation)
@@ -70,99 +64,124 @@ private static EvoCriteria[][] GetChainAll(PKM pkm, IEncounterTemplate enc, EvoC
if (head >= fullChain.Length)
{
if (g <= 2 && pkm.VC1)
- GensEvoChains[pkm.Format] = NONE; // invalidate here since we haven't reached the regular invalidation
+ GensEvoChains.Invalidate(); // invalidate here since we haven't reached the regular invalidation
return GensEvoChains;
}
if (mostEvolved.RequiresLvlUp)
- {
- // This is a Gen3 pokemon in a Gen4 phase evolution that requires level up and then transferred to Gen5+
- // We can deduce that it existed in Gen4 until met level,
- // but if current level is met level we can also deduce it existed in Gen3 until maximum met level -1
- if (g == 3 && pkm.Format > 4 && lvl == maxLevel)
- lvl--;
+ ReviseMaxLevel(ref lvl, pkm, g, maxLevel);
- // The same condition for Gen2 evolution of Gen1 pokemon, level of the pokemon in Gen1 games would be CurrentLevel -1 one level below Gen2 level
- else if (g == 1 && pkm.Format == 2 && lvl == maxLevel)
- lvl--;
- }
mostEvolved = fullChain[head++];
}
// Alolan form evolutions, remove from gens 1-6 chains
- if (HasAlolanForm(mostEvolved.Species))
+ if (g < 7 && HasAlolanForm(mostEvolved.Species) && pkm.Format >= 7 && mostEvolved.Form > 0)
{
- if (g < 7 && pkm.Format >= 7 && mostEvolved.Form > 0)
- {
- if (head >= fullChain.Length)
- break;
- mostEvolved = fullChain[head++];
- }
+ if (head >= fullChain.Length)
+ return GensEvoChains;
+ mostEvolved = fullChain[head++];
}
- GensEvoChains[g] = GetEvolutionChain(pkm, enc, mostEvolved.Species, lvl);
- ref var genChain = ref GensEvoChains[g];
- if (genChain.Length == 0)
+ var tmp = GetEvolutionChain(pkm, enc, mostEvolved.Species, lvl);
+ if (tmp.Length == 0)
continue;
- if (g > 2 && !pkm.HasOriginalMetLocation && g >= pkGen && noxfrDecremented)
+ GensEvoChains[g] = tmp;
+ if (g == 1)
{
- bool isTransferred = HasMetLocationUpdatedTransfer(pkGen, g);
+ CleanGen1(pkm, enc, GensEvoChains);
+ continue;
+ }
+
+ if (g >= 3 && !pkm.HasOriginalMetLocation && g >= enc.Generation && noxfrDecremented)
+ {
+ bool isTransferred = HasMetLocationUpdatedTransfer(enc.Generation, g);
if (!isTransferred)
continue;
- noxfrDecremented = g > (pkGen != 3 ? 4 : 5);
+ noxfrDecremented = g > (enc.Generation != 3 ? 4 : 5);
// Remove previous evolutions below transfer level
// For example a gen3 Charizard in format 7 with current level 36 and met level 36, thus could never be Charmander / Charmeleon in Gen5+.
// chain level for Charmander is 35, is below met level.
int minlvl = GetMinLevelGeneration(pkm, g);
+
+ ref var genChain = ref GensEvoChains[g];
int minIndex = Array.FindIndex(genChain, e => e.LevelMax >= minlvl);
if (minIndex != -1)
genChain = genChain.AsSpan(minIndex).ToArray();
}
- else if (g == 1)
- {
- // Remove Gen7 pre-evolutions and chain break scenarios
- if (pkm.VC1)
- TrimVC1Transfer(pkm, GensEvoChains);
-
- ref var lastGen = ref GensEvoChains[1];
- var g1 = lastGen.AsSpan();
- // Remove Gen2 post-evolutions (Scizor, Blissey...)
- if (g1[0].Species > MaxSpeciesID_1)
- {
- if (g1.Length == 1)
- {
- lastGen = Array.Empty();
- continue; // done
- }
- g1 = g1[1..];
- }
-
- // Remove Gen2 pre-evolutions (Pichu, Cleffa...)
- if (g1[^1].Species > MaxSpeciesID_1)
- {
- if (g1.Length == 1)
- {
- lastGen = Array.Empty();
- continue; // done
- }
- g1 = g1[..^1];
- }
-
- if (g1.Length != lastGen.Length)
- lastGen = g1.ToArray();
- // Update min level for the encounter to prevent certain level up moves.
- if (g1.Length != 0)
- {
- ref var last = ref g1[^1];
- last = last with { LevelMin = enc.LevelMin };
- }
- }
}
return GensEvoChains;
}
+ private static void ReviseMaxLevel(ref byte lvl, PKM pkm, int g, byte maxLevel)
+ {
+ // This is a Gen3 pokemon in a Gen4 phase evolution that requires level up and then transferred to Gen5+
+ // We can deduce that it existed in Gen4 until met level,
+ // but if current level is met level we can also deduce it existed in Gen3 until maximum met level -1
+ if (g == 3 && pkm.Format > 4 && lvl == maxLevel)
+ lvl--;
+
+ // The same condition for Gen2 evolution of Gen1 pokemon, level of the pokemon in Gen1 games would be CurrentLevel -1 one level below Gen2 level
+ else if (g == 1 && pkm.Format == 2 && lvl == maxLevel)
+ lvl--;
+ }
+
+ private static void CleanGen1(PKM pkm, IEncounterTemplate enc, EvolutionHistory chains)
+ {
+ // Remove Gen7 pre-evolutions and chain break scenarios
+ if (pkm.VC1)
+ {
+ var index = Array.FindLastIndex(chains.Gen7, z => z.Species <= MaxSpeciesID_1);
+ if (index == -1)
+ {
+ chains.Invalidate(); // needed a Gen1 species present; invalidate the chain.
+ return;
+ }
+ }
+
+ TrimSpeciesAbove(enc, MaxSpeciesID_1, ref chains.Gen1);
+ }
+
+ private static void TrimSpeciesAbove(IEncounterTemplate enc, int species, ref EvoCriteria[] chain)
+ {
+ var span = chain.AsSpan();
+
+ // Remove post-evolutions
+ if (span[0].Species > species)
+ {
+ if (span.Length == 1)
+ {
+ chain = Array.Empty();
+ return;
+ }
+
+ span = span[1..];
+ }
+
+ // Remove pre-evolutions
+ if (span[^1].Species > species)
+ {
+ if (span.Length == 1)
+ {
+ chain = Array.Empty();
+ return;
+ }
+
+ span = span[..^1];
+ }
+
+ if (span.Length != chain.Length)
+ chain = span.ToArray();
+
+ // Update min level for the encounter to prevent certain level up moves.
+ if (span.Length != 0)
+ {
+ ref var last = ref span[^1];
+ last = last with { LevelMin = enc.LevelMin };
+ }
+ }
+
private static bool HasMetLocationUpdatedTransfer(int originalGeneration, int currentGeneration) => originalGeneration switch
{
< 3 => currentGeneration >= 3,
@@ -170,29 +189,20 @@ private static EvoCriteria[][] GetChainAll(PKM pkm, IEncounterTemplate enc, EvoC
_ => false,
};
- private static void TrimVC1Transfer(PKM pkm, EvoCriteria[][] allChains)
- {
- var vc7 = allChains[7];
- var gen1Index = Array.FindLastIndex(vc7, z => z.Species <= MaxSpeciesID_1);
- if (gen1Index == -1)
- allChains[pkm.Format] = NONE; // needed a Gen1 species present; invalidate the chain.
- }
-
private static EvoCriteria[] GetEvolutionChain(PKM pkm, IEncounterTemplate enc, int mostEvolvedSpecies, byte maxlevel)
{
int min = enc.LevelMin;
if (pkm.HasOriginalMetLocation && pkm.Met_Level != 0)
min = pkm.Met_Level;
+
var chain = GetValidPreEvolutions(pkm, minLevel: min);
+ return TrimChain(chain, enc, mostEvolvedSpecies, maxlevel);
+ }
+
+ private static EvoCriteria[] TrimChain(EvoCriteria[] chain, IEncounterTemplate enc, int mostEvolvedSpecies, byte maxlevel)
+ {
if (enc.Species == mostEvolvedSpecies)
- {
- if (chain.Length == 1)
- return chain;
- var index = Array.FindLastIndex(chain, z => z.Species == enc.Species);
- if (index == -1)
- return Array.Empty();
- return new[] { chain[index] };
- }
+ return TrimChainSingle(chain, enc);
// Evolution chain is in reverse order (devolution)
// Find the index of the minimum species to determine the end of the chain
@@ -210,6 +220,11 @@ private static EvoCriteria[] GetEvolutionChain(PKM pkm, IEncounterTemplate enc,
CheckLastEncounterRemoval(enc, chain);
}
+ return TrimChainMore(chain, mostEvolvedSpecies, maxlevel);
+ }
+
+ private static EvoCriteria[] TrimChainMore(EvoCriteria[] chain, int mostEvolvedSpecies, byte maxlevel)
+ {
// maxspec is used to remove future geneneration evolutions, to gather evolution chain of a pokemon in previous generations
var maxSpeciesIndex = Array.FindIndex(chain, z => z.Species == mostEvolvedSpecies);
if (maxSpeciesIndex > 0)
@@ -223,13 +238,28 @@ private static EvoCriteria[] GetEvolutionChain(PKM pkm, IEncounterTemplate enc,
chain = Array.FindAll(chain, z => z.LevelMin <= maxlevel);
// Reduce the evolution chain levels to max level to limit any later analysis/results.
+ SanitizeMaxLevel(chain, maxlevel);
+
+ return chain;
+ }
+
+ private static void SanitizeMaxLevel(EvoCriteria[] chain, byte maxlevel)
+ {
for (var i = 0; i < chain.Length; i++)
{
ref var c = ref chain[i];
c = c with { LevelMax = Math.Min(c.LevelMax, maxlevel) };
}
+ }
- return chain;
+ private static EvoCriteria[] TrimChainSingle(EvoCriteria[] chain, IEncounterTemplate enc)
+ {
+ if (chain.Length == 1)
+ return chain;
+ var index = Array.FindLastIndex(chain, z => z.Species == enc.Species);
+ if (index == -1)
+ return Array.Empty();
+ return new[] { chain[index] };
}
private static void CheckLastEncounterRemoval(IEncounterTemplate enc, EvoCriteria[] chain)
@@ -263,8 +293,10 @@ internal static EvoCriteria[] GetValidPreEvolutions(PKM pkm, int maxspeciesorigi
if (maxspeciesorigin == -1 && pkm.InhabitedGeneration(2) && pkm.Format <= 2 && pkm.Generation == 1)
maxspeciesorigin = MaxSpeciesID_2;
- int tree = Math.Max(2, pkm.Format);
- var et = EvolutionTree.GetEvolutionTree(pkm, tree);
+ var context = pkm.Context;
+ if (context < EntityContext.Gen2)
+ context = EntityContext.Gen2;
+ var et = EvolutionTree.GetEvolutionTree(context);
return et.GetValidPreEvolutions(pkm, maxLevel: (byte)maxLevel, maxSpeciesOrigin: maxspeciesorigin, skipChecks: skipChecks, minLevel: (byte)minLevel);
}
diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionHistory.cs b/PKHeX.Core/Legality/Evolutions/EvolutionHistory.cs
new file mode 100644
index 000000000..21334cfaf
--- /dev/null
+++ b/PKHeX.Core/Legality/Evolutions/EvolutionHistory.cs
@@ -0,0 +1,55 @@
+using System;
+
+namespace PKHeX.Core;
+
+///
+/// Stores the possible evolution bounds for a parsed entity with respect to its origins and game traversal.
+///
+public class EvolutionHistory
+{
+ private static readonly EvoCriteria[] NONE = Array.Empty();
+
+ public EvoCriteria[] Gen1 = NONE;
+ public EvoCriteria[] Gen2 = NONE;
+ public EvoCriteria[] Gen3 = NONE;
+ public EvoCriteria[] Gen4 = NONE;
+ public EvoCriteria[] Gen5 = NONE;
+ public EvoCriteria[] Gen6 = NONE;
+ public EvoCriteria[] Gen7 = NONE;
+ public EvoCriteria[] Gen7b = NONE;
+ public EvoCriteria[] Gen8 = NONE;
+
+ public EvoCriteria[] Gen8a => Gen8; // future: separate field instead of copy
+ public EvoCriteria[] Gen8b => Gen8; // future: separate field instead of copy
+
+ public readonly int Length;
+ public readonly EvoCriteria[] FullChain;
+
+ public EvolutionHistory(EvoCriteria[] fullChain, int count)
+ {
+ FullChain = fullChain;
+ Length = count;
+ }
+
+ public ref EvoCriteria[] this[int index]
+ {
+ get
+ {
+ if (index == 1) return ref Gen1;
+ if (index == 2) return ref Gen2;
+ if (index == 3) return ref Gen3;
+ if (index == 4) return ref Gen4;
+ if (index == 5) return ref Gen5;
+ if (index == 6) return ref Gen6;
+ if (index == 7) return ref Gen7;
+ if (index == 8) return ref Gen8;
+ throw new IndexOutOfRangeException(nameof(index));
+ }
+ }
+
+ internal void Invalidate() => this[Length - 1] = NONE;
+
+ public bool HasVisitedSWSH => Gen8.Length != 0;
+ public bool HasVisitedPLA => Gen8a.Length != 0;
+ public bool HasVisitedBDSP => Gen8b.Length != 0;
+}
diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionLegality.cs b/PKHeX.Core/Legality/Evolutions/EvolutionLegality.cs
index 2bbac1d44..207043e26 100644
--- a/PKHeX.Core/Legality/Evolutions/EvolutionLegality.cs
+++ b/PKHeX.Core/Legality/Evolutions/EvolutionLegality.cs
@@ -30,6 +30,8 @@ internal static class EvolutionLegality
(int)Species.PorygonZ,
(int)Species.Sylveon,
+
+ (int)Species.Kleavor,
};
private static readonly HashSet FutureEvolutionsGen2 = new()
@@ -53,6 +55,9 @@ internal static class EvolutionLegality
(int)Species.PorygonZ,
(int)Species.Sylveon,
+
+ (int)Species.Wyrdeer,
+ (int)Species.Ursaluna,
};
private static readonly HashSet FutureEvolutionsGen3 = new()
diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionTree.cs b/PKHeX.Core/Legality/Evolutions/EvolutionTree.cs
index 3e61c978d..eaa6addd2 100644
--- a/PKHeX.Core/Legality/Evolutions/EvolutionTree.cs
+++ b/PKHeX.Core/Legality/Evolutions/EvolutionTree.cs
@@ -14,17 +14,17 @@ namespace PKHeX.Core
///
public sealed class EvolutionTree
{
- private static readonly EvolutionTree Evolves1 = new(GetResource("rby"), Gen1, PersonalTable.Y, MaxSpeciesID_1);
- private static readonly EvolutionTree Evolves2 = new(GetResource("gsc"), Gen2, PersonalTable.C, MaxSpeciesID_2);
- private static readonly EvolutionTree Evolves3 = new(GetResource("g3"), Gen3, PersonalTable.RS, MaxSpeciesID_3);
- private static readonly EvolutionTree Evolves4 = new(GetResource("g4"), Gen4, PersonalTable.DP, MaxSpeciesID_4);
- private static readonly EvolutionTree Evolves5 = new(GetResource("g5"), Gen5, PersonalTable.BW, MaxSpeciesID_5);
- private static readonly EvolutionTree Evolves6 = new(GetReader("ao"), Gen6, PersonalTable.AO, MaxSpeciesID_6);
- private static readonly EvolutionTree Evolves7 = new(GetReader("uu"), Gen7, PersonalTable.USUM, MaxSpeciesID_7_USUM);
- private static readonly EvolutionTree Evolves7b = new(GetReader("gg"), Gen7, PersonalTable.GG, MaxSpeciesID_7b);
- private static readonly EvolutionTree Evolves8 = new(GetReader("ss"), Gen8, PersonalTable.SWSH, MaxSpeciesID_8);
- private static readonly EvolutionTree Evolves8a = new(GetReader("la"), Gen8, PersonalTable.LA, MaxSpeciesID_8a);
- private static readonly EvolutionTree Evolves8b = new(GetReader("bs"), Gen8, PersonalTable.BDSP, MaxSpeciesID_8b);
+ public static readonly EvolutionTree Evolves1 = new(GetResource("rby"), Gen1, PersonalTable.Y, MaxSpeciesID_1);
+ public static readonly EvolutionTree Evolves2 = new(GetResource("gsc"), Gen2, PersonalTable.C, MaxSpeciesID_2);
+ public static readonly EvolutionTree Evolves3 = new(GetResource("g3"), Gen3, PersonalTable.RS, MaxSpeciesID_3);
+ public static readonly EvolutionTree Evolves4 = new(GetResource("g4"), Gen4, PersonalTable.DP, MaxSpeciesID_4);
+ public static readonly EvolutionTree Evolves5 = new(GetResource("g5"), Gen5, PersonalTable.BW, MaxSpeciesID_5);
+ public static readonly EvolutionTree Evolves6 = new(GetReader("ao"), Gen6, PersonalTable.AO, MaxSpeciesID_6);
+ public static readonly EvolutionTree Evolves7 = new(GetReader("uu"), Gen7, PersonalTable.USUM, MaxSpeciesID_7_USUM);
+ public static readonly EvolutionTree Evolves7b = new(GetReader("gg"), Gen7, PersonalTable.GG, MaxSpeciesID_7b);
+ public static readonly EvolutionTree Evolves8 = new(GetReader("ss"), Gen8, PersonalTable.SWSH, MaxSpeciesID_8);
+ public static readonly EvolutionTree Evolves8a = new(GetReader("la"), Gen8, PersonalTable.LA, MaxSpeciesID_8a);
+ public static readonly EvolutionTree Evolves8b = new(GetReader("bs"), Gen8, PersonalTable.BDSP, MaxSpeciesID_8b);
private static ReadOnlySpan GetResource(string resource) => Util.GetBinaryResource($"evos_{resource}.pkl");
private static BinLinkerAccessor GetReader(string resource) => BinLinkerAccessor.Get(GetResource(resource), resource);
@@ -38,33 +38,20 @@ static EvolutionTree()
Evolves8b.FixEvoTreeBS();
}
- public static EvolutionTree GetEvolutionTree(int generation) => generation switch
+ public static EvolutionTree GetEvolutionTree(EntityContext context) => context switch
{
- 1 => Evolves1,
- 2 => Evolves2,
- 3 => Evolves3,
- 4 => Evolves4,
- 5 => Evolves5,
- 6 => Evolves6,
- 7 => Evolves7,
- _ => Evolves8,
- };
-
- public static EvolutionTree GetEvolutionTree(PKM pkm, int generation) => generation switch
- {
- 1 => Evolves1,
- 2 => Evolves2,
- 3 => Evolves3,
- 4 => Evolves4,
- 5 => Evolves5,
- 6 => Evolves6,
- 7 => pkm.Version is (int)GO or (int)GP or (int)GE ? Evolves7b : Evolves7,
- _ => pkm.Version switch
- {
- (int)PLA => Evolves8a,
- (int)BD or (int)SP => Evolves8b,
- _ => Evolves8,
- },
+ EntityContext.Gen1 => Evolves1,
+ EntityContext.Gen2 => Evolves2,
+ EntityContext.Gen3 => Evolves3,
+ EntityContext.Gen4 => Evolves4,
+ EntityContext.Gen5 => Evolves5,
+ EntityContext.Gen6 => Evolves6,
+ EntityContext.Gen7 => Evolves7,
+ EntityContext.Gen8 => Evolves8,
+ EntityContext.Gen7b => Evolves7b,
+ EntityContext.Gen8a => Evolves8a,
+ EntityContext.Gen8b => Evolves8b,
+ _ => throw new ArgumentOutOfRangeException(nameof(context), context, null)
};
private readonly IReadOnlyList Entries;
@@ -233,26 +220,11 @@ public EvoCriteria[] GetValidPreEvolutions(PKM pkm, byte maxLevel, int maxSpecie
{
if (maxSpeciesOrigin <= 0)
maxSpeciesOrigin = GetMaxSpeciesOrigin(pkm);
- if (pkm.IsEgg && !skipChecks)
- {
- return new[]
- {
- new EvoCriteria{ Species = (ushort)pkm.Species, Form = (byte)pkm.Form, LevelMax = maxLevel, LevelMin = maxLevel },
- };
- }
- // Shedinja's evolution case can be a little tricky; hard-code handling.
- if (pkm.Species == (int)Species.Shedinja && maxLevel >= 20 && (!pkm.HasOriginalMetLocation || minLevel < maxLevel))
- {
- var min = Math.Max(minLevel, (byte)20);
- return new[]
- {
- new EvoCriteria { Species = (ushort)Species.Shedinja, LevelMax = maxLevel, LevelMin = min, Method = EvolutionType.LevelUp },
- new EvoCriteria { Species = (ushort)Species.Nincada, LevelMax = maxLevel, LevelMin = minLevel },
- };
- }
+ ushort species = (ushort)pkm.Species;
+ byte form = (byte)pkm.Form;
- return GetExplicitLineage(pkm, maxLevel, skipChecks, maxSpeciesOrigin, minLevel);
+ return GetExplicitLineage(species, form, pkm, minLevel, maxLevel, maxSpeciesOrigin, skipChecks);
}
public bool IsSpeciesDerivedFrom(int species, int form, int otherSpecies, int otherForm, bool ignoreForm = true)
@@ -348,16 +320,39 @@ public IEnumerable GetEvolutions(int species, int form)
///
/// Generates the reverse evolution path for the input .
///
+ /// Entity Species to begin the chain
+ /// Entity Form to begin the chain
/// Entity data
- /// Maximum level
+ /// Minimum level
+ /// Maximum level
+ /// Clamp for maximum species ID
/// Skip the secondary checks that validate the evolution
- /// Clamp for maximum species ID
- /// Minimum level
- private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks, int maxSpeciesOrigin, byte minLevel)
+ private EvoCriteria[] GetExplicitLineage(ushort species, byte form, PKM pkm, byte levelMin, byte levelMax, int maxSpeciesID, bool skipChecks)
{
- var species = pkm.Species;
- var form = pkm.Form;
- var lvl = maxLevel;
+ if (pkm.IsEgg && !skipChecks)
+ {
+ return new[]
+ {
+ new EvoCriteria{ Species = species, Form = form, LevelMax = levelMax, LevelMin = levelMax },
+ };
+ }
+
+ // Shedinja's evolution case can be a little tricky; hard-code handling.
+ if (species == (int)Species.Shedinja && levelMax >= 20 && (!pkm.HasOriginalMetLocation || levelMin < levelMax))
+ {
+ var min = Math.Max(levelMin, (byte)20);
+ return new[]
+ {
+ new EvoCriteria { Species = (ushort)Species.Shedinja, LevelMax = levelMax, LevelMin = min, Method = EvolutionType.LevelUp },
+ new EvoCriteria { Species = (ushort)Species.Nincada, LevelMax = levelMax, LevelMin = levelMin },
+ };
+ }
+ return GetLineage(species, form, pkm, levelMin, levelMax, maxSpeciesID, skipChecks);
+ }
+
+ private EvoCriteria[] GetLineage(int species, int form, PKM pkm, byte levelMin, byte levelMax, int maxSpeciesID, bool skipChecks)
+ {
+ var lvl = levelMax;
var first = new EvoCriteria { Species = (ushort)species, Form = (byte)form, LevelMax = lvl };
const int maxEvolutions = 3;
@@ -366,7 +361,8 @@ private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks
switch (species)
{
- case (int)Species.Silvally: form = 0;
+ case (int)Species.Silvally:
+ form = 0;
break;
}
@@ -376,9 +372,9 @@ private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks
while (true)
{
var key = GetLookupKey(species, form);
+ bool oneValid = false;
var node = Lineage[key];
- bool oneValid = false;
foreach (var link in node)
{
if (link.IsEvolutionBanned(pkm) && !skipChecks)
@@ -388,17 +384,18 @@ private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks
if (!evo.Valid(pkm, lvl, skipChecks))
continue;
- if (evo.RequiresLevelUp && minLevel >= lvl)
+ if (evo.RequiresLevelUp && levelMin >= lvl)
break; // impossible evolution
- oneValid = true;
- UpdateMinValues(evos[..ctr], evo, minLevel);
+ UpdateMinValues(evos[..ctr], evo, levelMin);
species = link.Species;
form = link.Form;
evos[ctr++] = evo.GetEvoCriteria((ushort)species, (byte)form, lvl);
if (evo.RequiresLevelUp)
lvl--;
+
+ oneValid = true;
break;
}
if (!oneValid)
@@ -407,11 +404,11 @@ private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks
// Remove future gen pre-evolutions; no Munchlax from a Gen3 Snorlax, no Pichu from a Gen1-only Raichu, etc
ref var last = ref evos[ctr - 1];
- if (last.Species > maxSpeciesOrigin)
+ if (last.Species > maxSpeciesID)
{
for (int i = 0; i < ctr; i++)
{
- if (evos[i].Species > maxSpeciesOrigin)
+ if (evos[i].Species > maxSpeciesID)
continue;
ctr--;
break;
@@ -421,9 +418,16 @@ private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks
// Last species is the wild/hatched species, the minimum level is because it has not evolved from previous species
var result = evos[..ctr];
last = ref result[^1];
- last = last with { LevelMin = minLevel, LevelUpRequired = 0 };
+ last = last with { LevelMin = levelMin, LevelUpRequired = 0 };
// Rectify minimum levels
+ RectifyMinimumLevels(result);
+
+ return result.ToArray();
+ }
+
+ private static void RectifyMinimumLevels(Span result)
+ {
for (int i = result.Length - 2; i >= 0; i--)
{
ref var evo = ref result[i];
@@ -431,8 +435,6 @@ private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks
var min = (byte)Math.Max(prev.LevelMin + evo.LevelUpRequired, evo.LevelMin);
evo = evo with { LevelMin = min };
}
-
- return result.ToArray();
}
private static void UpdateMinValues(Span evos, EvolutionMethod evo, byte minLevel)
diff --git a/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs b/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs
index 933210ba5..86793d07f 100644
--- a/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs
+++ b/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs
@@ -467,6 +467,7 @@ public static class LegalityCheckStrings
public static string LTransferFlagIllegal { get; set; } = "Flagged as illegal by the game (glitch abuse).";
public static string LTransferHTFlagRequired { get; set; } = "Current handler cannot be past gen OT for transferred specimen.";
public static string LTransferMet { get; set; } = "Invalid Met Location, expected Poké Transfer or Crown.";
+ public static string LTransferNotPossible { get; set; } = "Unable to transfer into current format from origin format.";
public static string LTransferMetLocation { get; set; } = "Invalid Transfer Met Location.";
public static string LTransferMove { get; set; } = "Incompatible transfer move.";
public static string LTransferMoveG4HM { get; set; } = "Defog and Whirlpool. One of the two moves should have been removed before transferred to Generation 5.";
diff --git a/PKHeX.Core/Legality/Learnset/Learnset.cs b/PKHeX.Core/Legality/Learnset/Learnset.cs
index a575369c3..f729be0e7 100644
--- a/PKHeX.Core/Legality/Learnset/Learnset.cs
+++ b/PKHeX.Core/Legality/Learnset/Learnset.cs
@@ -192,7 +192,11 @@ public void SetEvolutionMoves(Span moves, int ctr = 0)
public void SetLevelUpMoves(int startLevel, int endLevel, Span moves, ReadOnlySpan ignore, int ctr = 0)
{
int startIndex = Array.FindIndex(Levels, z => z >= startLevel);
+ if (startIndex == -1)
+ return; // No more remain
int endIndex = Array.FindIndex(Levels, z => z > endLevel);
+ if (endIndex == -1)
+ endIndex = Levels.Length;
for (int i = startIndex; i < endIndex; i++)
{
int move = Moves[i];
diff --git a/PKHeX.Core/Legality/LegalityAnalysis.cs b/PKHeX.Core/Legality/LegalityAnalysis.cs
index de4ca7fab..9bb44d1b8 100644
--- a/PKHeX.Core/Legality/LegalityAnalysis.cs
+++ b/PKHeX.Core/Legality/LegalityAnalysis.cs
@@ -256,7 +256,7 @@ private void UpdateVCTransferInfo()
var enc = (Info.EncounterOriginalGB = EncounterMatch);
if (enc is EncounterInvalid)
return;
- var vc = EncounterStaticGenerator.GetVCStaticTransferEncounter(pkm, enc, Info.EvoChainsAllGens[7]);
+ var vc = EncounterStaticGenerator.GetVCStaticTransferEncounter(pkm, enc, Info.EvoChainsAllGens.Gen7);
Info.EncounterMatch = vc;
foreach (var z in Transfer.VerifyVCEncounter(pkm, enc, vc, Info.Moves))
diff --git a/PKHeX.Core/Legality/MoveList.cs b/PKHeX.Core/Legality/MoveList.cs
index 1f3898dae..013ea32fd 100644
--- a/PKHeX.Core/Legality/MoveList.cs
+++ b/PKHeX.Core/Legality/MoveList.cs
@@ -161,7 +161,7 @@ internal static int[] GetBaseEggMoves(PKM pkm, int species, int form, GameVersio
return Array.Empty();
}
- internal static IReadOnlyList[] GetValidMovesAllGens(PKM pkm, EvoCriteria[][] evoChains, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true)
+ internal static IReadOnlyList[] GetValidMovesAllGens(PKM pkm, EvolutionHistory evoChains, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true)
{
var result = new IReadOnlyList[evoChains.Length];
for (int i = 0; i < result.Length; i++)
@@ -181,9 +181,7 @@ internal static IReadOnlyList[] GetValidMovesAllGens(PKM pkm, EvoCriteria[]
internal static IEnumerable GetValidMoves(PKM pkm, EvoCriteria[] evoChain, int generation, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true)
{
- GameVersion version = (GameVersion)pkm.Version;
- if (!pkm.IsMovesetRestricted(generation))
- version = Any;
+ var (_, version) = pkm.IsMovesetRestricted();
return GetValidMoves(pkm, version, evoChain, generation, types: types, RemoveTransferHM: RemoveTransferHM);
}
diff --git a/PKHeX.Core/Legality/MoveListSuggest.cs b/PKHeX.Core/Legality/MoveListSuggest.cs
index 9e6b7ba01..e26361502 100644
--- a/PKHeX.Core/Legality/MoveListSuggest.cs
+++ b/PKHeX.Core/Legality/MoveListSuggest.cs
@@ -6,7 +6,7 @@ namespace PKHeX.Core
{
public static class MoveListSuggest
{
- private static int[] GetSuggestedMoves(PKM pkm, EvoCriteria[][] evoChains, MoveSourceType types, IEncounterTemplate enc)
+ private static int[] GetSuggestedMoves(PKM pkm, EvolutionHistory evoChains, MoveSourceType types, IEncounterTemplate enc)
{
if (pkm.IsEgg && pkm.Format <= 5) // pre relearn
return MoveList.GetBaseEggMoves(pkm, pkm.Species, 0, (GameVersion)pkm.Version, pkm.CurrentLevel);
@@ -30,15 +30,13 @@ private static int[] GetSuggestedMoves(PKM pkm, EvoCriteria[][] evoChains, MoveS
return GetValidMoves(pkm, evoChains, types).Skip(1).ToArray(); // skip move 0
}
- private static IEnumerable GetValidMoves(PKM pkm, EvoCriteria[][] evoChains, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true)
+ private static IEnumerable GetValidMoves(PKM pkm, EvolutionHistory evoChains, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true)
{
- GameVersion version = (GameVersion)pkm.Version;
- if (!pkm.IsMovesetRestricted())
- version = GameVersion.Any;
+ var (_, version) = pkm.IsMovesetRestricted();
return GetValidMoves(pkm, version, evoChains, types: types, RemoveTransferHM: RemoveTransferHM);
}
- private static IEnumerable GetValidMoves(PKM pkm, GameVersion version, EvoCriteria[][] evoChains, MoveSourceType types = MoveSourceType.Reminder, bool RemoveTransferHM = true)
+ private static IEnumerable GetValidMoves(PKM pkm, GameVersion version, EvolutionHistory evoChains, MoveSourceType types = MoveSourceType.Reminder, bool RemoveTransferHM = true)
{
var r = new List { 0 };
if (types.HasFlagFast(MoveSourceType.RelearnMoves) && pkm.Format >= 6)
diff --git a/PKHeX.Core/Legality/Moves/MoveLevelUp.cs b/PKHeX.Core/Legality/Moves/MoveLevelUp.cs
index ae9854f13..1d885eb4f 100644
--- a/PKHeX.Core/Legality/Moves/MoveLevelUp.cs
+++ b/PKHeX.Core/Legality/Moves/MoveLevelUp.cs
@@ -31,8 +31,9 @@ private static readonly LearnLookup
public static LearnVersion GetIsLevelUpMove(PKM pkm, int species, int form, int maxLevel, int generation, int move, int minlvlG1, int minlvlG2, GameVersion version = Any)
{
- if (pkm.IsMovesetRestricted(generation))
- version = (GameVersion)pkm.Version;
+ var restrict = pkm.IsMovesetRestricted();
+ if (restrict.IsRestricted)
+ version = restrict.Game;
return generation switch
{
@@ -43,7 +44,7 @@ public static LearnVersion GetIsLevelUpMove(PKM pkm, int species, int form, int
4 => GetIsLevelUp4(species, form, move, maxLevel, version),
5 => GetIsLevelUp5(species, form, move, maxLevel, version),
6 => GetIsLevelUp6(species, form, move, maxLevel, version),
- 7 => GetIsLevelUp7(species, form, move, version), // move reminder can give any move 1-100
+ 7 => GetIsLevelUp7(species, form, move, pkm.GG ? (GameVersion)pkm.Version : version), // move reminder can give any move 1-100
8 => GetIsLevelUp8(species, form, move, maxLevel, version),
_ => LearnNONE,
};
@@ -267,8 +268,10 @@ private static LearnVersion GetIsLevelUp3Deoxys(int form, int move, int lvl, Gam
public static IEnumerable GetMovesLevelUp(PKM pkm, int species, int form, int maxLevel, int minlvlG1, int minlvlG2, GameVersion version, bool MoveReminder, int generation)
{
- if (pkm.IsMovesetRestricted(generation))
- version = (GameVersion)pkm.Version;
+ var restrict = pkm.IsMovesetRestricted();
+ if (restrict.IsRestricted)
+ version = restrict.Game;
+
return generation switch
{
1 => GetMovesLevelUp1(species, form, maxLevel, minlvlG1, version),
@@ -277,7 +280,7 @@ public static IEnumerable GetMovesLevelUp(PKM pkm, int species, int form, i
4 => GetMovesLevelUp4(species, form, maxLevel, version),
5 => GetMovesLevelUp5(species, form, maxLevel, version),
6 => GetMovesLevelUp6(species, form, maxLevel, version),
- 7 => GetMovesLevelUp7(species, form, maxLevel, MoveReminder, version),
+ 7 => GetMovesLevelUp7(species, form, maxLevel, MoveReminder, pkm.GG ? (GameVersion)pkm.Version : version),
8 => GetMovesLevelUp8(species, form, maxLevel, version),
_ => Array.Empty(),
};
diff --git a/PKHeX.Core/Legality/Moves/MoveTechnicalMachine.cs b/PKHeX.Core/Legality/Moves/MoveTechnicalMachine.cs
index 0acdbacc4..87a31ca81 100644
--- a/PKHeX.Core/Legality/Moves/MoveTechnicalMachine.cs
+++ b/PKHeX.Core/Legality/Moves/MoveTechnicalMachine.cs
@@ -8,8 +8,10 @@ public static class MoveTechnicalMachine
{
public static GameVersion GetIsMachineMove(PKM pkm, int species, int form, int generation, int move, GameVersion ver = GameVersion.Any, bool RemoveTransfer = false)
{
- if (pkm.IsMovesetRestricted(generation))
- ver = (GameVersion) pkm.Version;
+ var (isRestricted, game) = pkm.IsMovesetRestricted();
+ if (isRestricted)
+ ver = game;
+
switch (generation)
{
case 1: return GetIsMachine1(species, move);
@@ -30,8 +32,10 @@ public static GameVersion GetIsMachineMove(PKM pkm, int species, int form, int g
public static GameVersion GetIsRecordMove(PKM pkm, int species, int form, int generation, int move, GameVersion ver = GameVersion.Any, bool allowBit = false)
{
- if (pkm.IsMovesetRestricted(generation))
- ver = (GameVersion)pkm.Version;
+ var (isRestricted, game) = pkm.IsMovesetRestricted();
+ if (isRestricted)
+ ver = game;
+
return generation switch
{
8 => GetIsRecord8(pkm, species, move, form, ver, allowBit),
@@ -223,8 +227,9 @@ private static GameVersion GetIsRecord8(PKM pkm, int species, int move, int form
public static IEnumerable GetTMHM(PKM pkm, int species, int form, int generation, GameVersion ver = GameVersion.Any, bool RemoveTransfer = true)
{
var r = new List();
- if (pkm.IsMovesetRestricted(generation))
- ver = (GameVersion)pkm.Version;
+ var (isRestricted, game) = pkm.IsMovesetRestricted();
+ if (isRestricted)
+ ver = game;
switch (generation)
{
@@ -237,7 +242,7 @@ public static IEnumerable GetTMHM(PKM pkm, int species, int form, int gener
case 4: AddMachine4(r, species, pkm.Format, RemoveTransfer, form); break;
case 5: AddMachine5(r, species, form); break;
case 6: AddMachine6(r, species, form, ver); break;
- case 7: AddMachine7(r, species, form, ver); break;
+ case 7: AddMachine7(r, species, form, pkm.GG ? (GameVersion)pkm.Version : ver); break;
case 8: AddMachine8(r, species, form, ver); break;
}
return r.Distinct();
diff --git a/PKHeX.Core/Legality/Moves/MoveTutor.cs b/PKHeX.Core/Legality/Moves/MoveTutor.cs
index ef9655ed8..65153003b 100644
--- a/PKHeX.Core/Legality/Moves/MoveTutor.cs
+++ b/PKHeX.Core/Legality/Moves/MoveTutor.cs
@@ -153,7 +153,7 @@ private static GameVersion GetIsTutor7(PKM pkm, int species, int form, bool spec
private static GameVersion GetIsTutor8(PKM pkm, int species, int form, bool specialTutors, int move)
{
- if (pkm.LA)
+ if (pkm is PA8)
{
var pi = (PersonalInfoLA)PersonalTable.LA.GetFormEntry(species, form);
if (!pi.IsPresentInGame)
@@ -278,7 +278,7 @@ private static void AddMovesTutor7(List moves, int species, int form, PKM p
private static void AddMovesTutor8(List moves, int species, int form, PKM pkm, bool specialTutors)
{
- if (pkm.LA)
+ if (pkm is PA8)
{
var pi = (PersonalInfoLA)PersonalTable.LA.GetFormEntry(species, form);
if (!pi.IsPresentInGame)
@@ -292,7 +292,7 @@ private static void AddMovesTutor8(List moves, int species, int form, PKM p
}
return;
}
- if (pkm.BDSP)
+ if (pkm is PB8)
{
var pi = (PersonalInfoBDSP)PersonalTable.BDSP.GetFormEntry(species, form);
if (!pi.IsPresentInGame)
diff --git a/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs b/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs
index e218f11c7..84b39ad33 100644
--- a/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs
+++ b/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs
@@ -19,16 +19,18 @@ internal static class EvolutionRestrictions
///
private static readonly Dictionary SpeciesEvolutionWithMove = new()
{
- {(int)Eevee, new(0, 0)}, // FairyMoves
- {(int)MimeJr, new(1, (int)Mimic)},
- {(int)Bonsly, new(2, (int)Mimic)},
- {(int)Aipom, new(3, (int)Mimic)},
- {(int)Lickitung, new(4, (int)Rollout)},
- {(int)Tangela, new(5, (int)AncientPower)},
- {(int)Yanma, new(6, (int)AncientPower)},
- {(int)Piloswine, new(7, (int)AncientPower)},
- {(int)Steenee, new(8, (int)Stomp)},
- {(int)Clobbopus, new(9, (int)Taunt)},
+ {(int)Eevee, new(00, 0)}, // FairyMoves
+ {(int)MimeJr, new(01, (int)Mimic)},
+ {(int)Bonsly, new(02, (int)Mimic)},
+ {(int)Aipom, new(03, (int)Mimic)},
+ {(int)Lickitung, new(04, (int)Rollout)},
+ {(int)Tangela, new(05, (int)AncientPower)},
+ {(int)Yanma, new(06, (int)AncientPower)},
+ {(int)Piloswine, new(07, (int)AncientPower)},
+ {(int)Steenee, new(08, (int)Stomp)},
+ {(int)Clobbopus, new(09, (int)Taunt)},
+ {(int)Stantler, new(10, (int)PsyshieldBash)},
+ {(int)Qwilfish, new(11, (int)BarbBarrage)},
};
private readonly record struct MoveEvolution(int ReferenceIndex, int Move);
@@ -65,6 +67,7 @@ internal static class EvolutionRestrictions
(int)SpiritBreak,
(int)StrangeSteam,
(int)MistyExplosion,
+ (int)SpringtideStorm,
};
///
@@ -85,7 +88,6 @@ internal static class EvolutionRestrictions
new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 35 }, // Grapploct (Clobbopus with Taunt)
new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00 }, // Wyrdeer (Stantler with AGILE Psyshield Bash)
new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00 }, // Overqwil (Qwilfish with STRONG Barb Barrage)
- new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00 }, // Basculegion (Basculin-2 with Recoil Move)
};
private static readonly byte[] MinLevelEvolutionWithMove_8LA =
@@ -102,7 +104,6 @@ internal static class EvolutionRestrictions
99, // Grapploct (Clobbopus with Taunt)
31, // Wyrdeer (Stantler with AGILE Psyshield Bash)
25, // Overqwil (Qwilfish with STRONG Barb Barrage)
- 34, // Basculegion (Basculin-2 with Recoil Move)
};
private static readonly bool[][] CanEggHatchWithEvolveMove =
@@ -119,7 +120,6 @@ internal static class EvolutionRestrictions
new [] { false, false, false, false, false, false, false, false, true }, // Grapploct (Clobbopus with Taunt)
new [] { false, false, false, false, false, false, false, false, false }, // Wyrdeer (Stantler with AGILE Psyshield Bash)
new [] { false, false, false, false, false, false, false, false, false }, // Overqwil (Qwilfish with STRONG Barb Barrage)
- new [] { false, false, false, false, false, false, false, false, false }, // Basculegion (Basculin-2 with Recoil Move)
};
///
@@ -170,13 +170,13 @@ public static bool IsValidEvolutionWithMove(PKM pkm, LegalInfo info)
}
// Current level must be at least the minimum post-evolution level.
- var lvl = GetMinLevelKnowRequiredMove(pkm, gen, index);
+ var lvl = GetMinLevelKnowRequiredMove(pkm, gen, index, info.EvoChainsAllGens);
return pkm.CurrentLevel >= lvl;
}
- private static int GetMinLevelKnowRequiredMove(PKM pkm, int gen, int index)
+ private static int GetMinLevelKnowRequiredMove(PKM pkm, int gen, int index, EvolutionHistory evos)
{
- if (gen == 8 && pkm.LA) // No Level Up required, and different levels than mainline SW/SH.
+ if (gen == 8 && pkm.HasVisitedLA(evos.Gen8a)) // No Level Up required, and different levels than mainline SW/SH.
return MinLevelEvolutionWithMove_8LA[index];
var lvl = GetLevelLearnMove(pkm, gen, index);
diff --git a/PKHeX.Core/Legality/Restrictions/GBRestrictions.cs b/PKHeX.Core/Legality/Restrictions/GBRestrictions.cs
index 1fa593855..4434d326b 100644
--- a/PKHeX.Core/Legality/Restrictions/GBRestrictions.cs
+++ b/PKHeX.Core/Legality/Restrictions/GBRestrictions.cs
@@ -396,7 +396,7 @@ internal static IEnumerable GetGen1Versions(IEncounterTemplate enc)
private static bool GetCatchRateMatchesPreEvolution(PK1 pkm, int catch_rate)
{
// For species catch rate, discard any species that has no valid encounters and a different catch rate than their pre-evolutions
- var table = EvolutionTree.GetEvolutionTree(1);
+ var table = EvolutionTree.Evolves1;
var chain = table.GetValidPreEvolutions(pkm, maxLevel: (byte)pkm.CurrentLevel);
foreach (var entry in chain)
{
diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8.cs
index c37c8a548..3ac452137 100644
--- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8.cs
+++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8.cs
@@ -23,7 +23,7 @@ public override IEnumerable GetMemoryItemParams() => Legal.HeldItem_AO.C
public override bool CanObtainMemoryOT(GameVersion pkmVersion, byte memory) => pkmVersion switch
{
GameVersion.SW or GameVersion.SH => CanObtainMemorySWSH(memory),
- _ => false,
+ _ => memory is 0,
};
public override bool CanObtainMemoryHT(GameVersion pkmVersion, byte memory) => CanObtainMemorySWSH(memory);
diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs
index 56abbc8c6..8ca4633ba 100644
--- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs
+++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs
@@ -66,12 +66,6 @@ public static bool CanBuyItem(int generation, int item, GameVersion version = Ga
return context.CanBuyItem(item, version);
}
- public static bool GetCanLearnMachineMove(PKM pkm, int move, int generation, GameVersion version = GameVersion.Any)
- {
- var evos = EvolutionChain.GetValidPreEvolutions(pkm);
- return GetCanLearnMachineMove(pkm, evos, move, generation, version);
- }
-
public static bool GetCanLearnMachineMove(PKM pkm, EvoCriteria[] evos, int move, int generation, GameVersion version = GameVersion.Any)
{
if (IsOtherFormMove(pkm, evos, move, generation, version, types: MoveSourceType.AllMachines))
@@ -145,10 +139,10 @@ public static bool CanKnowMove(PKM pkm, MemoryVariableSet memory, int gen, Legal
return false;
}
- private static bool IsSpecialEncounterMoveEggDeleted(PKM pkm, IEncounterable enc)
+ private static bool IsSpecialEncounterMoveEggDeleted(PKM pkm, IEncounterTemplate enc)
{
- if (pkm is IBattleVersion { BattleVersion: not 0 }) // can hide Relearn moves (Gen6+ Eggs, or DexNav)
- return enc is EncounterEgg { Generation: >= 6 } or EncounterSlot6AO { CanDexNav: true } or EncounterSlot8b { IsUnderground: true };
+ if (pkm.IsOriginalMovesetDeleted())
+ return true;
return enc is EncounterEgg { Generation: < 6 }; // egg moves that are no longer in the movepool
}
@@ -159,7 +153,7 @@ public static bool GetCanRelearnMove(PKM pkm, int move, int generation, EvoCrite
return MoveList.GetValidMoves(pkm, version, evos, generation, types: MoveSourceType.Reminder).Contains(move);
}
- private static bool GetCanKnowMove(PKM pkm, int move, int generation, EvoCriteria[][] evos, GameVersion version = GameVersion.Any)
+ private static bool GetCanKnowMove(PKM pkm, int move, int generation, EvolutionHistory evos, GameVersion version = GameVersion.Any)
{
if (pkm.Species == (int)Smeargle)
return Legal.IsValidSketch(move, generation);
diff --git a/PKHeX.Core/Legality/Structures/LegalInfo.cs b/PKHeX.Core/Legality/Structures/LegalInfo.cs
index 886223ef2..981a236e5 100644
--- a/PKHeX.Core/Legality/Structures/LegalInfo.cs
+++ b/PKHeX.Core/Legality/Structures/LegalInfo.cs
@@ -55,8 +55,8 @@ private static CheckMoveResult[] GetArray()
private static readonly ValidEncounterMoves NONE = new();
public ValidEncounterMoves EncounterMoves { get; internal set; } = NONE;
- public EvoCriteria[][] EvoChainsAllGens => _evochains ??= EvolutionChain.GetEvolutionChainsAllGens(pkm, EncounterMatch);
- private EvoCriteria[][]? _evochains;
+ public EvolutionHistory EvoChainsAllGens => _evochains ??= EvolutionChain.GetEvolutionChainsAllGens(pkm, EncounterMatch);
+ private EvolutionHistory? _evochains;
/// related information that generated the / value(s).
public PIDIV PIDIV
diff --git a/PKHeX.Core/Legality/Tables/Tables.cs b/PKHeX.Core/Legality/Tables/Tables.cs
index 9005111b0..1d30e4be3 100644
--- a/PKHeX.Core/Legality/Tables/Tables.cs
+++ b/PKHeX.Core/Legality/Tables/Tables.cs
@@ -130,7 +130,7 @@ public static bool IsValidSketch(int move, int generation)
return false;
if (generation is 8) // can't Sketch unusable moves in BDSP, no Sketch in PLA
{
- if (SignatureSketch_BDSP.Contains(move) || DummiedMoves_BDSP.Contains(move))
+ if (DummiedMoves_BDSP.Contains(move))
return false;
if (move > MaxMoveID_8)
return false;
diff --git a/PKHeX.Core/Legality/Tables/Tables8bs.cs b/PKHeX.Core/Legality/Tables/Tables8bs.cs
index 6a952f927..b69d5d1ab 100644
--- a/PKHeX.Core/Legality/Tables/Tables8bs.cs
+++ b/PKHeX.Core/Legality/Tables/Tables8bs.cs
@@ -7,7 +7,7 @@ public static partial class Legal
internal const int MaxSpeciesID_8b = MaxSpeciesID_4; // Arceus-493
internal const int MaxMoveID_8b = MaxMoveID_8_R2;
internal const int MaxItemID_8b = 1822; // DS Sounds
- internal const int MaxBallID_8b = (int)Ball.Beast;
+ internal const int MaxBallID_8b = (int)Ball.LAOrigin;
internal const int MaxGameID_8b = (int)GameVersion.SP;
internal const int MaxAbilityID_8b = MaxAbilityID_8_R2;
@@ -119,14 +119,6 @@ public static partial class Legal
(int)Move.DracoMeteor,
};
- ///
- /// Unavailable Sketch Moves that are only learnable once certain species are distributed / made accessible.
- ///
- public static readonly HashSet SignatureSketch_BDSP = new()
- {
- (int)Move.PsychoBoost, // Deoxys
- };
-
///
/// Moves that are kill
///
diff --git a/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs
index 60ff6fb30..adc3b6667 100644
--- a/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs
@@ -58,8 +58,8 @@ private CheckResult VerifyAbility(LegalityAnalysis data)
if (abilities[0] == abilities[1] && num != 1)
{
// Check if any pre-evolution could have it flipped.
- var evos = data.Info.EvoChainsAllGens[6];
- var pt = GameData.GetPersonal(GameUtil.GetVersion(format));
+ var evos = data.Info.EvoChainsAllGens.Gen6;
+ var pt = GameData.GetPersonal(pkm.Context.GetSingleGameVersion());
if (!GetWasDual(evos, pt, pkm))
return INVALID;
}
@@ -68,7 +68,8 @@ private CheckResult VerifyAbility(LegalityAnalysis data)
if (format >= 8) // Ability Patch
{
- if (pkm.AbilityNumber == 4 && !pkm.LA)
+ var evos = data.Info.EvoChainsAllGens;
+ if (pkm.AbilityNumber == 4 && IsAccessibleAbilityPatch(pkm, evos))
{
if (CanAbilityPatch(format, abilities, pkm.Species))
return GetValid(LAbilityPatchUsed);
@@ -163,7 +164,7 @@ private CheckResult VerifyFixedAbility(LegalityAnalysis data, IReadOnlyList
var enc = data.Info.EncounterMatch;
if (enc.Generation >= 6)
{
- if (IsAbilityCapsuleModified(pkm, abilities, encounterAbility))
+ if (IsAbilityCapsuleModified(pkm, abilities, encounterAbility, data.Info.EvoChainsAllGens))
return GetValid(LAbilityCapsuleUsed);
if (pkm.AbilityNumber != 1 << encounterAbility.GetSingleValue())
return INVALID;
@@ -202,7 +203,7 @@ private CheckResult VerifyFixedAbility(LegalityAnalysis data, IReadOnlyList
if (state == AbilityState.CanMismatch || encounterAbility == 0)
return CheckMatch(pkm, abilities, enc.Generation, AbilityState.MustMatch, enc);
- if (IsAbilityCapsuleModified(pkm, abilities, encounterAbility))
+ if (IsAbilityCapsuleModified(pkm, abilities, encounterAbility, data.Info.EvoChainsAllGens))
return GetValid(LAbilityCapsuleUsed);
return INVALID;
@@ -231,7 +232,7 @@ private AbilityState VerifyAbilityPreCapsule(LegalityAnalysis data, IReadOnlyLis
return AbilityState.MustMatch;
// If the species could not exist in Gen3, must match.
- var g3 = info.EvoChainsAllGens[3];
+ var g3 = info.EvoChainsAllGens.Gen3;
if (g3.Length == 0)
return AbilityState.MustMatch;
@@ -251,7 +252,7 @@ private AbilityState VerifyAbilityGen3Transfer(LegalityAnalysis data, IReadOnlyL
return AbilityState.MustMatch;
var chain = data.Info.EvoChainsAllGens;
- bool evolved45 = chain[4].Length > 1 || (pkm.Format == 5 && chain[5].Length > 1);
+ bool evolved45 = chain.Gen4.Length > 1 || (pkm.Format == 5 && chain.Gen5.Length > 1);
if (evolved45)
{
if (pkm.Ability == pers.Ability1) // Could evolve in Gen4/5 and have a Gen3 only ability
@@ -445,15 +446,27 @@ private CheckResult GetPIDAbilityMatch(PKM pkm, IReadOnlyList abilities)
return VALID;
}
- // Ability Capsule can change between 1/2
- private static bool IsAbilityCapsuleModified(PKM pkm, IReadOnlyList abilities, AbilityPermission encounterAbility)
+ private static bool IsAccessibleAbilityPatch(PKM pkm, EvolutionHistory evosAll)
{
+ return pkm.HasVisitedSWSH(evosAll.Gen8) || pkm.HasVisitedBDSP(evosAll.Gen8b);
+ }
+
+ private static bool IsAccessibleAbilityCapsule(PKM pkm, EvolutionHistory evosAll)
+ {
+ if (evosAll.Gen6.Length > 0 || evosAll.Gen7.Length > 0)
+ return true;
+ return pkm.HasVisitedSWSH(evosAll.Gen8) || pkm.HasVisitedBDSP(evosAll.Gen8b);
+ }
+
+ // Ability Capsule can change between 1/2
+ private static bool IsAbilityCapsuleModified(PKM pkm, IReadOnlyList abilities, AbilityPermission encounterAbility, EvolutionHistory evos)
+ {
+ if (!IsAccessibleAbilityCapsule(pkm, evos))
+ return false; // Not available.
if (!CanAbilityCapsule(pkm.Format, abilities))
return false;
if (pkm.AbilityNumber == 4)
return false; // Cannot alter to hidden ability.
- if (pkm.LA)
- return false; // Not available.
if (encounterAbility == AbilityPermission.OnlyHidden)
return false; // Cannot alter from hidden ability.
return true;
diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs
index 77fb86f99..4447a2b9a 100644
--- a/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs
+++ b/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs
@@ -984,60 +984,6 @@ internal static class BallBreedLegality
(int)Flabébé + (3 << 11), // Flabébé-Blue
};
- ///
- /// All egg species that can inherit a Safari Ball when bred in BD/SP.
- ///
- internal static readonly HashSet InheritSafari_BDSP = new()
- {
- (int)Ekans,
- (int)Azurill,
- (int)Barboach,
- (int)Bidoof,
- (int)Budew,
- (int)Carnivine,
- (int)Carvanha,
- (int)Croagunk,
- (int)Exeggcute,
- (int)Gulpin,
- (int)Hoothoot,
- (int)Kangaskhan,
- (int)Magikarp,
- (int)Marill,
- (int)Paras,
- (int)Psyduck,
- (int)Roselia,
- (int)Shroomish,
- (int)Skorupi,
- (int)Starly,
- (int)Wooper,
- (int)Yanma,
- };
-
- internal static readonly HashSet BanInheritedExceptSafari_BDSP = new()
- {
- (int)Exeggcute,
- (int)Kangaskhan,
- (int)Yanma,
- (int)Shroomish,
- (int)Gulpin,
- (int)Carnivine,
- };
-
- internal static readonly HashSet BanInheritedBall_BDSP = new()
- {
- // Gen1 Fossils
- (int)Aerodactyl, (int)Omanyte, (int)Kabuto,
- // Gen3 Fossils
- (int)Lileep, (int)Anorith,
- // Gen4 Fossils
- (int)Cranidos, (int)Shieldon,
-
- // Riolu Egg from Riley
- (int)Riolu,
-
- (int)Phione,
- };
-
///
/// Gets a legal value for a bred egg encounter.
///
@@ -1045,14 +991,9 @@ internal static class BallBreedLegality
/// Species the egg contained.
/// Valid ball to hatch with.
/// Not all things can hatch with a Poké Ball!
+#pragma warning disable RCS1163, IDE0060 // Unused parameter.
public static Ball GetDefaultBall(GameVersion version, int species)
{
- if (version is GameVersion.BD or GameVersion.SP)
- {
- if (BanInheritedExceptSafari_BDSP.Contains(species))
- return Ball.Safari;
- }
-
return Ball.None;
}
}
diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs
index 18d9ee026..d2cd69bf2 100644
--- a/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs
@@ -20,11 +20,21 @@ public override void Verify(LegalityAnalysis data)
data.AddLine(result);
}
+ private static int IsReplacedBall(IVersion enc, PKM pk) => pk switch
+ {
+ PK8 when enc.Version == GameVersion.PLA => (int)Poke,
+ _ => (int)None,
+ };
+
private CheckResult VerifyBall(LegalityAnalysis data)
{
var Info = data.Info;
var enc = Info.EncounterMatch;
+ var ball = IsReplacedBall(enc, data.pkm);
+ if (ball != 0)
+ return VerifyBallEquals(data, ball);
+
// Fixed ball cases -- can be only one ball ever
switch (enc)
{
@@ -49,7 +59,7 @@ private CheckResult VerifyBall(LegalityAnalysis data)
// The special move verifier has a similar check!
if (pkm.HGSS && pkm.Ball == (int)Sport) // Can evolve in DP to retain the HG/SS ball -- not able to be captured in any other ball
return VerifyBallEquals(data, (int)Sport);
- if (Info.Generation != 3 || Info.EvoChainsAllGens[3].Length != 2)
+ if (Info.Generation != 3 || Info.EvoChainsAllGens.Gen3.Length != 2)
return VerifyBallEquals(data, (int)Poke); // Pokeball Only
}
@@ -264,20 +274,81 @@ private CheckResult VerifyBallEggGen7(LegalityAnalysis data)
private CheckResult VerifyBallEggGen8BDSP(LegalityAnalysis data)
{
int species = data.EncounterMatch.Species;
- if (BallBreedLegality.BanInheritedBall_BDSP.Contains(species))
+ if (species == (int)Species.Phione)
return VerifyBallEquals(data, (int)Poke);
- if (BallBreedLegality.BanInheritedExceptSafari_BDSP.Contains(species))
- return VerifyBallEquals(data, (int)Safari);
- if (data.pkm.Ball == (int)Safari)
+ if (species is (int)Species.Cranidos or (int)Species.Shieldon)
+ return VerifyBallEquals(data, BallUseLegality.DreamWorldBalls);
+
+ var pkm = data.pkm;
+ Ball ball = (Ball)pkm.Ball;
+ var balls = BallUseLegality.GetWildBalls(8, GameVersion.BDSP);
+ if (balls.Contains((int)ball))
+ return GetValid(LBallSpeciesPass);
+
+ if (species is (int)Species.Spinda)
+ return GetInvalid(LBallSpecies); // Can't enter or exit, needs to adhere to wild balls.
+
+ // Cross-game inheritance
+ if (IsGalarCatchAndBreed(species))
{
- if (BallBreedLegality.InheritSafari_BDSP.Contains(species))
+ if (BallUseLegality.WildPokeballs8.Contains(pkm.Ball))
+ return GetValid(LBallSpeciesPass);
+ }
+
+ if (ball == Safari)
+ {
+ if (!(BallBreedLegality.Inherit_Safari.Contains(species) || BallBreedLegality.Inherit_SafariMale.Contains(species)))
+ return GetInvalid(LBallSpecies);
+ if (BallBreedLegality.Ban_SafariBallHidden_7.Contains(species) && IsHiddenAndNotPossible(pkm))
+ return GetInvalid(LBallAbility);
+ return GetValid(LBallSpeciesPass);
+ }
+ if (ball.IsApricornBall()) // Apricorn Ball
+ {
+ if (!BallBreedLegality.Inherit_Apricorn7.Contains(species))
+ return GetInvalid(LBallSpecies);
+ if (BallBreedLegality.Ban_NoHidden8Apricorn.Contains(species | pkm.Form << 11) && IsHiddenAndNotPossible(pkm)) // lineage is 3->2->origin
+ return GetInvalid(LBallAbility);
+ return GetValid(LBallSpeciesPass);
+ }
+ if (ball == Sport) // Sport Ball
+ {
+ if (!BallBreedLegality.Inherit_Sport.Contains(species))
+ return GetInvalid(LBallSpecies);
+ if ((species is (int)Species.Volbeat or (int)Species.Illumise) && IsHiddenAndNotPossible(pkm)) // Volbeat/Illumise
+ return GetInvalid(LBallAbility);
+ return GetValid(LBallSpeciesPass);
+ }
+ if (ball == Dream) // Dream Ball
+ {
+ if (BallBreedLegality.Inherit_Dream.Contains(species) || BallBreedLegality.Inherit_DreamMale.Contains(species))
+ return GetValid(LBallSpeciesPass);
+ return GetInvalid(LBallSpecies);
+ }
+ if (ball is >= Dusk and <= Quick) // Dusk Heal Quick
+ {
+ if (!BallBreedLegality.Ban_Gen4Ball_7.Contains(species))
+ return GetValid(LBallSpeciesPass);
+ return GetInvalid(LBallSpecies);
+ }
+ if (ball is >= Ultra and <= Premier) // Don't worry, Safari was already checked.
+ {
+ if (!BallBreedLegality.Ban_Gen3Ball_7.Contains(species))
return GetValid(LBallSpeciesPass);
return GetInvalid(LBallSpecies);
}
- var balls = BallUseLegality.GetWildBalls(8, GameVersion.BDSP);
- return VerifyBallEquals(data, balls);
+ if (ball == Beast)
+ {
+ if (BallBreedLegality.PastGenAlolanScans.Contains(species))
+ return GetValid(LBallSpeciesPass);
+ }
+
+ if (ball > Beast)
+ return GetInvalid(LBallUnavailable);
+
+ return GetInvalid(LBallEncMismatch);
}
private CheckResult VerifyBallEggGen8(LegalityAnalysis data)
diff --git a/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs b/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs
index 87f885788..98ee65d28 100644
--- a/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs
@@ -14,7 +14,7 @@ public override void Verify(LegalityAnalysis data)
var pkm = data.pkm;
if (data.EncounterMatch is EncounterStatic3 s3)
VerifyCXDStarterCorrelation(data, s3);
- else if (pkm.Egg_Location != 0) // can't obtain eggs in CXD
+ else if (pkm.Egg_Location != 0 && pkm is not PB8 {Egg_Location: Locations.Default8bNone}) // can't obtain eggs in CXD
data.AddLine(GetInvalid(LEncInvalid, CheckIdentifier.Encounter)); // invalid encounter
if (pkm.OT_Gender == 1)
diff --git a/PKHeX.Core/Legality/Verifiers/ContestStatVerifier.cs b/PKHeX.Core/Legality/Verifiers/ContestStatVerifier.cs
index b44e86c74..c5dd977cd 100644
--- a/PKHeX.Core/Legality/Verifiers/ContestStatVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/ContestStatVerifier.cs
@@ -25,7 +25,7 @@ public override void Verify(LegalityAnalysis data)
// In generations 3,4 and BDSP, blocks/poffins have a feel(sheen) equal to sheen=sum(stats)/5, with +/- 10% for a favored stat.
// In generation 6 (ORAS), they don't award any sheen, so any value is legal.
- var correlation = GetContestStatRestriction(pkm, data.Info.Generation);
+ var correlation = GetContestStatRestriction(pkm, data.Info.Generation, data.Info.EvoChainsAllGens);
if (correlation == None)
{
// We're only here because we have contest stat values. We aren't permitted to have any, so flag it.
@@ -41,7 +41,7 @@ public override void Verify(LegalityAnalysis data)
else if (correlation == CorrelateSheen)
{
bool gen3 = data.Info.Generation == 3;
- bool bdsp = pkm.HasVisitedBDSP(data.Info.EncounterOriginal.Species);
+ bool bdsp = pkm.HasVisitedBDSP(data.Info.EvoChainsAllGens.Gen8b);
var method = gen3 ? ContestStatGrantingSheen.Gen3 :
bdsp ? ContestStatGrantingSheen.Gen8b : ContestStatGrantingSheen.Gen4;
diff --git a/PKHeX.Core/Legality/Verifiers/FormVerifier.cs b/PKHeX.Core/Legality/Verifiers/FormVerifier.cs
index 92bb34333..06c5bdeb9 100644
--- a/PKHeX.Core/Legality/Verifiers/FormVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/FormVerifier.cs
@@ -66,7 +66,7 @@ private CheckResult VerifyForm(LegalityAnalysis data)
break;
case Unown when Info.Generation == 2 && form >= 26:
return GetInvalid(string.Format(LFormInvalidRange, "Z", form == 26 ? "!" : "?"));
- case Dialga or Palkia or Giratina or Arceus when form > 0 && pkm.LA: // can change forms with key items
+ case Dialga or Palkia or Giratina or Arceus when form > 0 && pkm is PA8: // can change forms with key items
break;
case Giratina when form == 1 ^ pkm.HeldItem == 112: // Giratina, Origin form only with Griseous Orb
return GetInvalid(LFormItemInvalid);
@@ -308,12 +308,14 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f)
Qwilfish when pkm.Form is 1 => arg switch
{
not 0 when pkm.IsEgg => GetInvalid(LFormArgumentNotAllowed),
+ not 0 when pkm.CurrentLevel < 25 => GetInvalid(LFormArgumentHigh),
> 9_999 => GetInvalid(LFormArgumentHigh),
_ => GetValid(LFormArgumentValid),
},
- Stantler when pkm is PA8 => arg switch
+ Stantler when pkm is PA8 || pkm.HasVisitedLA(data.Info.EvoChainsAllGens.Gen8a) => arg switch
{
not 0 when pkm.IsEgg => GetInvalid(LFormArgumentNotAllowed),
+ not 0 when pkm.CurrentLevel < 31 => GetInvalid(LFormArgumentHigh),
> 9_999 => GetInvalid(LFormArgumentHigh),
_ => GetValid(LFormArgumentValid),
},
diff --git a/PKHeX.Core/Legality/Verifiers/HistoryVerifier.cs b/PKHeX.Core/Legality/Verifiers/HistoryVerifier.cs
index 7814e9859..f7e06712d 100644
--- a/PKHeX.Core/Legality/Verifiers/HistoryVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/HistoryVerifier.cs
@@ -1,5 +1,4 @@
using System;
-using System.Linq;
namespace PKHeX.Core
{
@@ -76,18 +75,14 @@ private void VerifyOTFriendship(LegalityAnalysis data, bool neverOT, int origin,
{
if (origin < 0)
return;
+
if (origin <= 2)
{
- // Verify the original friendship value since it cannot change from the value it was assigned in the original generation.
- // Since some evolutions have different base friendship values, check all possible evolutions for a match.
- // If none match, then it is not a valid OT friendship.
- const int vc = 7; // VC transfers use SM personal info
- var evos = data.Info.EvoChainsAllGens[vc];
- var fs = pkm.OT_Friendship;
- if (evos.All(z => GetBaseFriendship(vc, z.Species, z.Form) != fs))
- data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryStatFriendshipOTBaseEvent));
+ VerifyOTFriendshipVC12(data, pkm);
+ return;
}
- else if (neverOT)
+
+ if (neverOT)
{
// Verify the original friendship value since it cannot change from the value it was assigned in the original generation.
// If none match, then it is not a valid OT friendship.
@@ -98,6 +93,30 @@ private void VerifyOTFriendship(LegalityAnalysis data, bool neverOT, int origin,
}
}
+ private void VerifyOTFriendshipVC12(LegalityAnalysis data, PKM pkm)
+ {
+ // Verify the original friendship value since it cannot change from the value it was assigned in the original generation.
+ // Since some evolutions have different base friendship values, check all possible evolutions for a match.
+ // If none match, then it is not a valid OT friendship.
+ // VC transfers use SM personal info
+ var any = IsMatchFriendship(data.Info.EvoChainsAllGens.Gen7, PersonalTable.USUM, pkm.OT_Friendship);
+ if (!any)
+ data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryStatFriendshipOTBaseEvent));
+ }
+
+ private static bool IsMatchFriendship(EvoCriteria[] evos, PersonalTable pt, int fs)
+ {
+ foreach (var z in evos)
+ {
+ if (!pt.IsPresentInGame(z.Species, z.Form))
+ continue;
+ var entry = pt.GetFormEntry(z.Species, z.Form);
+ if (entry.BaseFriendship == fs)
+ return true;
+ }
+ return false;
+ }
+
private void VerifyOTAffection(LegalityAnalysis data, bool neverOT, int origin, PKM pkm)
{
if (pkm is not IAffection a)
@@ -209,13 +228,10 @@ public static bool GetCanOTHandle(IEncounterTemplate enc, PKM pkm, int generatio
private static int GetBaseFriendship(int generation, int species, int form) => generation switch
{
- 1 => PersonalTable.USUM[species].BaseFriendship,
- 2 => PersonalTable.USUM[species].BaseFriendship,
-
6 => PersonalTable.AO[species].BaseFriendship,
7 => PersonalTable.USUM[species].BaseFriendship,
8 => PersonalTable.SWSH.GetFormEntry(species, form).BaseFriendship,
- _ => throw new IndexOutOfRangeException(),
+ _ => throw new ArgumentOutOfRangeException(nameof(generation)),
};
}
}
diff --git a/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs b/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs
index 80dfe683d..7d32c55fb 100644
--- a/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs
@@ -14,12 +14,9 @@ public sealed class LegendsArceusVerifier : Verifier
public override void Verify(LegalityAnalysis data)
{
var pk = data.pkm;
- if (!pk.LA || pk is not PA8 pa)
+ if (pk is not PA8 pa)
return;
- CheckLearnset(data, pa);
- CheckMastery(data, pa);
-
if (pa.IsNoble)
data.AddLine(GetInvalid(LStatNobleInvalid));
if (pa.IsAlpha != data.EncounterMatch is IAlpha { IsAlpha: true })
@@ -27,6 +24,9 @@ public override void Verify(LegalityAnalysis data)
CheckScalars(data, pa);
CheckGanbaru(data, pa);
+
+ CheckLearnset(data, pa);
+ CheckMastery(data, pa);
}
private static void CheckGanbaru(LegalityAnalysis data, PA8 pa)
@@ -67,7 +67,7 @@ private static void CheckLearnset(LegalityAnalysis data, PA8 pa)
// Get the bare minimum moveset.
Span expect = stackalloc int[4];
- var minMoveCount = LoadBareMinimumMoveset(data.EncounterMatch, data.Info.EvoChainsAllGens[8], pa, expect);
+ var minMoveCount = LoadBareMinimumMoveset(data.EncounterMatch, data.Info.EvoChainsAllGens, pa, expect);
// Flag move slots that are empty.
for (int i = moveCount; i < minMoveCount; i++)
@@ -81,7 +81,7 @@ private static void CheckLearnset(LegalityAnalysis data, PA8 pa)
///
/// Gets the expected minimum count of moves, and modifies the input with the bare minimum move IDs.
///
- private static int LoadBareMinimumMoveset(ISpeciesForm enc, EvoCriteria[] evos, PA8 pa, Span moves)
+ private static int LoadBareMinimumMoveset(ISpeciesForm enc, EvolutionHistory h, PA8 pa, Span moves)
{
// Get any encounter moves
var pt = PersonalTable.LA;
@@ -100,6 +100,10 @@ private static int LoadBareMinimumMoveset(ISpeciesForm enc, EvoCriteria[] evos,
Span purchased = stackalloc int[purchasedCount];
LoadPurchasedMoves(pa, purchased);
+ // If it can be leveled up in other games, level it up in other games.
+ if (pa.HasVisitedSWSH(h.Gen8) || pa.HasVisitedBDSP(h.Gen8b))
+ return count;
+
// Level up to current level
var level = pa.CurrentLevel;
moveset.SetLevelUpMoves(pa.Met_Level, level, moves, purchased, count);
@@ -108,6 +112,7 @@ private static int LoadBareMinimumMoveset(ISpeciesForm enc, EvoCriteria[] evos,
return 4;
// Evolve and try
+ var evos = h.Gen8a;
for (int i = 0; i < evos.Length - 1; i++)
{
var evo = evos[i];
@@ -223,7 +228,7 @@ private static bool CanLearnMoveByLevelUp(LegalityAnalysis data, PA8 pa, int i,
// Changing forms do not have separate tutor permissions, so we don't need to bother with form changes.
// Level up movepools can grant moves for mastery at lower levels for earlier evolutions... find the minimum.
int level = 101;
- foreach (var evo in data.Info.EvoChainsAllGens[8])
+ foreach (var evo in data.Info.EvoChainsAllGens.Gen8a)
{
var pt = PersonalTable.LA;
var index = pt.GetFormIndex(evo.Species, evo.Form);
diff --git a/PKHeX.Core/Legality/Verifiers/MarkVerifier.cs b/PKHeX.Core/Legality/Verifiers/MarkVerifier.cs
index e425262b0..244addec9 100644
--- a/PKHeX.Core/Legality/Verifiers/MarkVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/MarkVerifier.cs
@@ -44,14 +44,14 @@ private void VerifyMarksPresent(LegalityAnalysis data, IRibbonIndex m)
if (hasOne)
{
- data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, mark)));
+ data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, GetRibbonNameSafe(mark))));
return;
}
bool result = IsMarkValid(mark, data.pkm, data.EncounterMatch);
if (!result)
{
- data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, mark)));
+ data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, GetRibbonNameSafe(mark))));
return;
}
@@ -59,6 +59,14 @@ private void VerifyMarksPresent(LegalityAnalysis data, IRibbonIndex m)
}
}
+ private static string GetRibbonNameSafe(RibbonIndex index)
+ {
+ if (index >= RibbonIndex.MAX_COUNT)
+ return index.ToString();
+ var expect = $"Ribbon{index}";
+ return RibbonStrings.GetName(expect);
+ }
+
public static bool IsMarkValid(RibbonIndex mark, PKM pk, IEncounterTemplate enc)
{
return IsMarkAllowedAny(enc) && IsMarkAllowedSpecific(mark, pk, enc);
@@ -128,28 +136,31 @@ public static bool IsMarkAllowedFishing(IEncounterTemplate enc)
private void VerifyAffixedRibbonMark(LegalityAnalysis data, IRibbonIndex m)
{
- if (m is not PK8 pk8)
+ if (m is not IRibbonSetAffixed a)
return;
- var affix = pk8.AffixedRibbon;
+ var affix = a.AffixedRibbon;
if (affix == -1) // None
return;
- if ((byte)affix > (int)RibbonIndex.MarkSlump)
+ if ((byte)affix > (int)RibbonIndex.MarkSlump) // SW/SH cannot affix anything higher.
{
- data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, affix)));
+ data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe((RibbonIndex)affix))));
return;
}
- if (pk8.Species == (int)Species.Shedinja && data.EncounterOriginal.Species is not (int)Species.Shedinja)
+ if (m is not PKM pk)
+ return;
+
+ if (pk.Species == (int)Species.Shedinja && data.EncounterOriginal.Species is not (int)Species.Shedinja)
{
- VerifyShedinjaAffixed(data, affix, pk8);
+ VerifyShedinjaAffixed(data, affix, pk, m);
return;
}
- EnsureHasRibbon(data, pk8, affix);
+ EnsureHasRibbon(data, m, affix);
}
- private void VerifyShedinjaAffixed(LegalityAnalysis data, sbyte affix, PK8 pk8)
+ private void VerifyShedinjaAffixed(LegalityAnalysis data, sbyte affix, PKM pk, IRibbonIndex r)
{
// Does not copy ribbons or marks, but retains the Affixed Ribbon value.
// Try re-verifying to see if it could have had the Ribbon/Mark.
@@ -157,48 +168,47 @@ private void VerifyShedinjaAffixed(LegalityAnalysis data, sbyte affix, PK8 pk8)
var enc = data.EncounterOriginal;
if ((byte) affix >= (int) RibbonIndex.MarkLunchtime)
{
- if (!IsMarkValid((RibbonIndex)affix, pk8, enc))
- data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, (RibbonIndex) affix)));
+ if (!IsMarkValid((RibbonIndex)affix, pk, enc))
+ data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe((RibbonIndex)affix))));
return;
}
- if (enc.Generation <= 4 && (pk8.Ball != (int)Ball.Poke || IsMoveSetEvolvedShedinja(pk8)))
+ if (enc.Generation <= 4 && (pk.Ball != (int)Ball.Poke || IsMoveSetEvolvedShedinja(pk)))
{
// Evolved in a prior generation.
- EnsureHasRibbon(data, pk8, affix);
+ EnsureHasRibbon(data, r, affix);
return;
}
- var clone = pk8.Clone();
+ var clone = pk.Clone();
clone.Species = (int) Species.Nincada;
((IRibbonIndex) clone).SetRibbon(affix);
var parse = RibbonVerifier.GetRibbonResults(clone, data.Info.EvoChainsAllGens, enc);
- var expect = $"Ribbon{(RibbonIndex) affix}";
- var name = RibbonStrings.GetName(expect);
+ var name = GetRibbonNameSafe((RibbonIndex)affix);
bool invalid = parse.FirstOrDefault(z => z.Name == name)?.Invalid == true;
var severity = invalid ? Severity.Invalid : Severity.Fishy;
- data.AddLine(Get(string.Format(LRibbonMarkingAffixedF_0, affix), severity));
+ data.AddLine(Get(string.Format(LRibbonMarkingAffixedF_0, name), severity));
}
- private static bool IsMoveSetEvolvedShedinja(PK8 pk8)
+ private static bool IsMoveSetEvolvedShedinja(PKM pk)
{
// Check for gen3/4 exclusive moves that are Ninjask glitch only.
- if (pk8.HasMove((int) Move.Screech))
+ if (pk.HasMove((int) Move.Screech))
return true;
- if (pk8.HasMove((int) Move.SwordsDance))
+ if (pk.HasMove((int) Move.SwordsDance))
return true;
- if (pk8.HasMove((int) Move.Slash))
+ if (pk.HasMove((int) Move.Slash))
return true;
- if (pk8.HasMove((int) Move.BatonPass))
+ if (pk.HasMove((int) Move.BatonPass))
return true;
- return pk8.HasMove((int)Move.Agility) && !pk8.GetMoveRecordFlag(12); // TR12 (Agility)
+ return pk.HasMove((int)Move.Agility) && pk is PK8 pk8 && !pk8.GetMoveRecordFlag(12); // TR12 (Agility)
}
- private void EnsureHasRibbon(LegalityAnalysis data, IRibbonIndex pk8, sbyte affix)
+ private void EnsureHasRibbon(LegalityAnalysis data, IRibbonIndex m, sbyte affix)
{
- var hasRibbon = pk8.GetRibbonIndex((RibbonIndex) affix);
+ var hasRibbon = m.GetRibbonIndex((RibbonIndex) affix);
if (!hasRibbon)
- data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, (RibbonIndex) affix)));
+ data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe((RibbonIndex) affix))));
}
}
}
diff --git a/PKHeX.Core/Legality/Verifiers/MemoryVerifier.cs b/PKHeX.Core/Legality/Verifiers/MemoryVerifier.cs
index 7324052dd..c0fecaa02 100644
--- a/PKHeX.Core/Legality/Verifiers/MemoryVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/MemoryVerifier.cs
@@ -15,7 +15,7 @@ public sealed class MemoryVerifier : Verifier
public override void Verify(LegalityAnalysis data)
{
var pkm = data.pkm;
- if (pkm.BDSP || pkm.LA)
+ if (ShouldHaveNoMemory(data, pkm))
{
VerifyOTMemoryIs(data, 0, 0, 0, 0);
VerifyHTMemoryNone(data, (ITrainerMemories)pkm);
@@ -25,6 +25,13 @@ public override void Verify(LegalityAnalysis data)
VerifyHTMemory(data);
}
+ private static bool ShouldHaveNoMemory(LegalityAnalysis data, PKM pkm)
+ {
+ if (pkm.BDSP || pkm.LA)
+ return !pkm.HasVisitedSWSH(data.Info.EvoChainsAllGens.Gen8);
+ return false;
+ }
+
private CheckResult VerifyCommonMemory(PKM pkm, int handler, int gen, LegalInfo info, MemoryContext context)
{
var memory = MemoryVariableSet.Read((ITrainerMemories)pkm, handler);
@@ -56,7 +63,7 @@ private CheckResult VerifyCommonMemory(PKM pkm, int handler, int gen, LegalInfo
return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler));
// {0} saw {2} carrying {1} on its back. {4} that {3}.
- case 21 when gen != 6 || !GetCanLearnMachineMove(new PK6 {Species = memory.Variable, EXP = Experience.GetEXP(100, PersonalTable.XY.GetFormIndex(memory.Variable, 0))}, (int)Move.Fly, 6):
+ case 21 when gen != 6 || !PersonalTable.AO.GetFormEntry(memory.Variable, 0).TMHM[101]: // Fly
return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler));
// {0} used {2} at {1}’s instruction, but it had no effect. {4} that {3}.
@@ -71,7 +78,7 @@ private CheckResult VerifyCommonMemory(PKM pkm, int handler, int gen, LegalInfo
// {0} battled at {1}’s side against {2} that Dynamaxed. {4} that {3}.
case 71 when !GetCanDynamaxTrainer(memory.Variable, 8, handler == 0 ? (GameVersion)pkm.Version : GameVersion.Any):
// {0} battled {2} and Dynamaxed upon {1}’s instruction. {4} that {3}.
- case 72 when !((PersonalInfoSWSH)PersonalTable.SWSH[memory.Variable]).IsPresentInGame:
+ case 72 when !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable):
return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler));
// Move
@@ -88,13 +95,13 @@ private CheckResult VerifyCommonMemory(PKM pkm, int handler, int gen, LegalInfo
// {0} saw {1} paying attention to {2}. {4} that {3}.
// {0} fought hard until it had to use Struggle when it battled at {1}’s side against {2}. {4} that {3}.
// {0} was taken to a Pokémon Nursery by {1} and left with {2}. {4} that {3}.
- case 9 or 60 or 75 when gen == 8 && !((PersonalInfoSWSH)PersonalTable.SWSH[memory.Variable]).IsPresentInGame:
+ case 9 or 60 or 75 when gen == 8 && !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable):
return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler));
// {0} had a great chat about {1} with the {2} that it was in a Box with. {4} that {3}.
// {0} became good friends with the {2} in a Box, practiced moves with it, and talked about the day that {0} would be praised by {1}. {4} that {3}.
// {0} got in a fight with the {2} that it was in a Box with about {1}. {4} that {3}.
- case 82 or 83 or 87 when !((PersonalInfoSWSH)PersonalTable.SWSH[memory.Variable]).IsPresentInGame:
+ case 82 or 83 or 87 when !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable):
return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler));
// {0} had a very hard training session with {1}. {4} that {3}.
@@ -201,7 +208,7 @@ private void VerifyOTMemory(LegalityAnalysis data)
return;
}
}
- else if (!CanHaveMemoryForOT(pkm, memoryGen, memory))
+ else if (!CanHaveMemoryForOT(pkm, memoryGen, memory, Info.EvoChainsAllGens))
{
VerifyOTMemoryIs(data, 0, 0, 0, 0); // empty
return;
@@ -248,7 +255,7 @@ private void VerifyOTMemory(LegalityAnalysis data)
data.AddLine(VerifyCommonMemory(pkm, 0, Info.Generation, Info, context));
}
- private static bool CanHaveMemoryForOT(PKM pkm, int origin, int memory)
+ private static bool CanHaveMemoryForOT(PKM pkm, int origin, int memory, EvolutionHistory evos)
{
switch (origin)
{
@@ -259,8 +266,8 @@ private static bool CanHaveMemoryForOT(PKM pkm, int origin, int memory)
case 7 when pkm.GG: // LGPE does not set memories.
case 8 when pkm.GO_HOME: // HOME does not set memories.
case 8 when pkm.Met_Location == Locations.HOME8: // HOME does not set memories.
- case 8 when pkm.BDSP: // BDSP does not set memories.
- case 8 when pkm.LA: // LA does not set memories.
+ case 8 when pkm.BDSP && !pkm.HasVisitedSWSH(evos.Gen8): // BDSP does not set memories.
+ case 8 when pkm.LA && !pkm.HasVisitedSWSH(evos.Gen8): // LA does not set memories.
return false;
// Eggs cannot have memories
diff --git a/PKHeX.Core/Legality/Verifiers/Misc/ContestStatInfo.cs b/PKHeX.Core/Legality/Verifiers/Misc/ContestStatInfo.cs
index 821e76dcd..d41078942 100644
--- a/PKHeX.Core/Legality/Verifiers/Misc/ContestStatInfo.cs
+++ b/PKHeX.Core/Legality/Verifiers/Misc/ContestStatInfo.cs
@@ -29,12 +29,12 @@ public static class ContestStatInfo
///
private const int BestSheenStat8b = 120;
- public static void SetSuggestedContestStats(this PKM pk, IEncounterTemplate enc)
+ public static void SetSuggestedContestStats(this PKM pk, IEncounterTemplate enc, EvolutionHistory h)
{
if (pk is not IContestStatsMutable s)
return;
- var restrict = GetContestStatRestriction(pk, pk.Generation);
+ var restrict = GetContestStatRestriction(pk, pk.Generation, h);
var baseStat = GetReferenceTemplate(enc);
if (restrict == None || pk.Species is not (int)Species.Milotic)
baseStat.CopyContestStatsTo(s); // reset
@@ -42,25 +42,25 @@ public static void SetSuggestedContestStats(this PKM pk, IEncounterTemplate enc)
s.SetAllContestStatsTo(MaxContestStat, restrict == NoSheen ? baseStat.CNT_Sheen : MaxContestStat);
}
- public static void SetMaxContestStats(this PKM pk, IEncounterTemplate enc)
+ public static void SetMaxContestStats(this PKM pk, IEncounterTemplate enc, EvolutionHistory h)
{
if (pk is not IContestStatsMutable s)
return;
- var restrict = GetContestStatRestriction(pk, enc.Generation);
+ var restrict = GetContestStatRestriction(pk, enc.Generation, h);
var baseStat = GetReferenceTemplate(enc);
if (restrict == None)
return;
s.SetAllContestStatsTo(MaxContestStat, restrict == NoSheen ? baseStat.CNT_Sheen : MaxContestStat);
}
- public static ContestStatGranting GetContestStatRestriction(PKM pk, int origin) => origin switch
+ public static ContestStatGranting GetContestStatRestriction(PKM pk, int origin, EvolutionHistory h) => origin switch
{
3 => pk.Format < 6 ? CorrelateSheen : Mixed,
4 => pk.Format < 6 ? CorrelateSheen : Mixed,
5 => pk.Format >= 6 ? NoSheen : None, // ORAS Contests
6 => pk.AO || !pk.IsUntraded ? NoSheen : None,
- 8 => pk.BDSP ? CorrelateSheen : None, // BDSP Contests
+ 8 => pk.HasVisitedBDSP(h.Gen8b) ? CorrelateSheen : None, // BDSP Contests
_ => None,
};
diff --git a/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs b/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs
index b5cce04e7..feefbae95 100644
--- a/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs
@@ -73,6 +73,14 @@ public override void Verify(LegalityAnalysis data)
if (enc is IEncounterServerDate { IsDateRestricted: true } serverGift)
{
var date = new DateTime(pkm.Met_Year + 2000, pkm.Met_Month, pkm.Met_Day);
+
+ // HOME Gifts for Sinnoh/Hisui starters were forced JPN until May 20, 2022 (UTC).
+ if (enc is WB8 { CardID: 9015 or 9016 or 9017 } or WA8 { CardID: 9018 or 9019 or 9020 })
+ {
+ if (date < new DateTime(2022, 5, 20) && pkm.Language != (int)LanguageID.Japanese)
+ data.AddLine(GetInvalid(LDateOutsideDistributionWindow));
+ }
+
var result = serverGift.IsValidDate(date);
if (result == EncounterServerDateCheck.Invalid)
data.AddLine(GetInvalid(LDateOutsideDistributionWindow));
@@ -369,8 +377,8 @@ private static void VerifyReceivability(LegalityAnalysis data, MysteryGift g)
case WC6 wc6 when !wc6.CanBeReceivedByVersion(pkm.Version) && !pkm.WasTradedEgg:
case WC7 wc7 when !wc7.CanBeReceivedByVersion(pkm.Version) && !pkm.WasTradedEgg:
case WC8 wc8 when !wc8.CanBeReceivedByVersion(pkm.Version):
- case WB8 wb8 when !wb8.CanBeReceivedByVersion(pkm.Version):
- case WA8 wa8 when !wa8.CanBeReceivedByVersion(pkm.Version):
+ case WB8 wb8 when !wb8.CanBeReceivedByVersion(pkm.Version, pkm):
+ case WA8 wa8 when !wa8.CanBeReceivedByVersion(pkm.Version, pkm):
data.AddLine(GetInvalid(LEncGiftVersionNotDistributed, GameOrigin));
return;
case WC6 wc6 when wc6.RestrictLanguage != 0 && pkm.Language != wc6.RestrictLanguage:
@@ -423,9 +431,9 @@ private static void VerifyFullness(LegalityAnalysis data, PKM pkm)
if (pkm.IsEgg)
{
if (pkm.Fullness != 0)
- data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, 0), Encounter));
+ data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter));
if (pkm.Enjoyment != 0)
- data.AddLine(GetInvalid(string.Format(LMemoryStatEnjoyment, 0), Encounter));
+ data.AddLine(GetInvalid(string.Format(LMemoryStatEnjoyment, "0"), Encounter));
return;
}
@@ -433,8 +441,11 @@ private static void VerifyFullness(LegalityAnalysis data, PKM pkm)
{
if (pkm.Fullness > 245) // Exiting camp is -10
data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "<=245"), Encounter));
+ else if (pkm.Fullness is not 0 && pkm is not PK8)
+ data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter));
+
if (pkm.Enjoyment != 0)
- data.AddLine(GetInvalid(string.Format(LMemoryStatEnjoyment, 0), Encounter));
+ data.AddLine(GetInvalid(string.Format(LMemoryStatEnjoyment, "0"), Encounter));
return;
}
@@ -448,7 +459,7 @@ private static void VerifyFullness(LegalityAnalysis data, PKM pkm)
return; // evolved
if (Unfeedable.Contains(pkm.Species))
- data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, 0), Encounter));
+ data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter));
}
private static readonly HashSet Unfeedable = new()
@@ -488,7 +499,7 @@ private static void VerifyAbsoluteSizes(LegalityAnalysis data, IScaledSizeValue
private void VerifySWSHStats(LegalityAnalysis data, PK8 pk8)
{
- if (pk8.Favorite)
+ if (pk8.Favorite && !pk8.GG)
data.AddLine(GetInvalid(LFavoriteMarkingUnavailable, Encounter));
var social = pk8.Sociability;
@@ -504,12 +515,8 @@ private void VerifySWSHStats(LegalityAnalysis data, PK8 pk8)
VerifyStatNature(data, pk8);
- var bv = pk8.BattleVersion;
- if (bv != 0)
- {
- if ((bv != (int)GameVersion.SW && bv != (int)GameVersion.SH) || pk8.SWSH)
- data.AddLine(GetInvalid(LStatBattleVersionInvalid));
- }
+ if (!pk8.IsBattleVersionValid(data.Info.EvoChainsAllGens))
+ data.AddLine(GetInvalid(LStatBattleVersionInvalid));
var enc = data.EncounterMatch;
bool originGMax = enc is IGigantamax {CanGigantamax: true};
@@ -556,12 +563,15 @@ private void VerifyPLAStats(LegalityAnalysis data, PA8 pa8)
{
VerifyAbsoluteSizes(data, pa8);
- if (pa8.Favorite)
+ if (pa8.Favorite && !pa8.GG)
data.AddLine(GetInvalid(LFavoriteMarkingUnavailable, Encounter));
- var affix = pa8.AffixedRibbon;
- if (affix != -1) // None
- data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, affix)));
+ if (!pa8.HasVisitedSWSH(data.Info.EvoChainsAllGens.Gen8))
+ {
+ var affix = pa8.AffixedRibbon;
+ if (affix != -1) // None
+ data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, affix)));
+ }
var social = pa8.Sociability;
if (social != 0)
@@ -569,8 +579,7 @@ private void VerifyPLAStats(LegalityAnalysis data, PA8 pa8)
VerifyStatNature(data, pa8);
- var bv = pa8.BattleVersion;
- if (bv != 0)
+ if (!pa8.IsBattleVersionValid(data.Info.EvoChainsAllGens))
data.AddLine(GetInvalid(LStatBattleVersionInvalid));
if (pa8.CanGigantamax)
@@ -588,12 +597,15 @@ private void VerifyPLAStats(LegalityAnalysis data, PA8 pa8)
private void VerifyBDSPStats(LegalityAnalysis data, PB8 pb8)
{
- if (pb8.Favorite)
+ if (pb8.Favorite && !pb8.GG)
data.AddLine(GetInvalid(LFavoriteMarkingUnavailable, Encounter));
- var affix = pb8.AffixedRibbon;
- if (affix != -1) // None
- data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, affix)));
+ if (!pb8.HasVisitedSWSH(data.Info.EvoChainsAllGens.Gen8))
+ {
+ var affix = pb8.AffixedRibbon;
+ if (affix != -1) // None
+ data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, affix)));
+ }
var social = pb8.Sociability;
if (social != 0)
@@ -601,11 +613,14 @@ private void VerifyBDSPStats(LegalityAnalysis data, PB8 pb8)
if (pb8.IsDprIllegal)
data.AddLine(GetInvalid(LTransferFlagIllegal));
+ if (pb8.Species is (int)Species.Spinda or (int)Species.Nincada && !pb8.BDSP)
+ data.AddLine(GetInvalid(LTransferNotPossible));
+ if (pb8.Species is (int)Species.Spinda && pb8.Tracker != 0)
+ data.AddLine(GetInvalid(LTransferTrackerShouldBeZero));
VerifyStatNature(data, pb8);
- var bv = pb8.BattleVersion;
- if (bv != 0)
+ if (!pb8.IsBattleVersionValid(data.Info.EvoChainsAllGens))
data.AddLine(GetInvalid(LStatBattleVersionInvalid));
if (pb8.CanGigantamax)
diff --git a/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs b/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs
index 10a1f8541..1e93c65da 100644
--- a/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs
@@ -91,7 +91,7 @@ private void VerifyFixedNicknameEncounter(LegalityAnalysis data, ILangNicknamedT
if (n is WC3 && pkm.Format != 3)
{
// Gen3 gifts transferred to Generation 4 from another language can set the nickname flag.
- var evos = data.Info.EvoChainsAllGens[3];
+ var evos = data.Info.EvoChainsAllGens.Gen3;
bool matchAny = evos.Any(evo => !SpeciesName.IsNicknamedAnyLanguage(evo.Species, nickname, 3));
if (matchAny)
return;
diff --git a/PKHeX.Core/Legality/Verifiers/PIDVerifier.cs b/PKHeX.Core/Legality/Verifiers/PIDVerifier.cs
index e6ea91c17..59c5683eb 100644
--- a/PKHeX.Core/Legality/Verifiers/PIDVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/PIDVerifier.cs
@@ -133,6 +133,13 @@ private static void VerifyEC(LegalityAnalysis data)
// Gen1-2, Gen6+ should have PID != EC
if (pkm.PID == pkm.EncryptionConstant)
{
+ // Check for edge cases
+ var enc = Info.EncounterMatch;
+ if (enc is WA8 {IsEquivalentFixedECPID: true})
+ return;
+ if (enc is WB8 {IsEquivalentFixedECPID: true})
+ return;
+
data.AddLine(GetInvalid(LPIDEqualsEC, CheckIdentifier.EC)); // better to flag than 1:2^32 odds since RNG is not feasible to yield match
return;
}
diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifier.cs
index 5da5a52d6..7f704be85 100644
--- a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifier.cs
@@ -38,7 +38,7 @@ public override void Verify(LegalityAnalysis data)
}
}
- private static List GetIncorrectRibbons(PKM pkm, EvoCriteria[][] evos, IEncounterTemplate enc)
+ private static List GetIncorrectRibbons(PKM pkm, EvolutionHistory evos, IEncounterTemplate enc)
{
List missingRibbons = new();
List invalidRibbons = new();
@@ -75,14 +75,14 @@ private static bool GetIncorrectRibbonsEgg(PKM pkm, IEncounterTemplate enc)
return false;
}
- internal static IEnumerable GetRibbonResults(PKM pkm, EvoCriteria[][] evos, IEncounterTemplate enc)
+ internal static IEnumerable GetRibbonResults(PKM pkm, EvolutionHistory evos, IEncounterTemplate enc)
{
return GetInvalidRibbons(pkm, evos, enc)
.Concat(GetInvalidRibbonsEvent1(pkm, enc))
.Concat(GetInvalidRibbonsEvent2(pkm, enc));
}
- private static IEnumerable GetInvalidRibbons(PKM pkm, EvoCriteria[][] evos, IEncounterTemplate enc)
+ private static IEnumerable GetInvalidRibbons(PKM pkm, EvolutionHistory evos, IEncounterTemplate enc)
{
// is a part of Event4, but O3 doesn't have the others
if (pkm is IRibbonSetOnly3 {RibbonWorld: true})
@@ -133,7 +133,7 @@ private static IEnumerable GetInvalidRibbons(PKM pkm, EvoCriteria[
var iterate = GetInvalidRibbons4Any(pkm, evos, s4, gen);
if (!inhabited4)
{
- if (pkm.BDSP) // Allow Sinnoh Champion. ILCA reused the Gen4 ribbon for the remake.
+ if (pkm.HasVisitedBDSP(evos.Gen8b)) // Allow Sinnoh Champion. ILCA reused the Gen4 ribbon for the remake.
iterate = iterate.Concat(GetInvalidRibbonsNoneSkipIndex(s4.RibbonBitsOnly(), s4.RibbonNamesOnly(), 1));
else
iterate = iterate.Concat(GetInvalidRibbonsNone(s4.RibbonBitsOnly(), s4.RibbonNamesOnly()));
@@ -148,7 +148,7 @@ private static IEnumerable GetInvalidRibbons(PKM pkm, EvoCriteria[
var iterate = inhabited6
? GetInvalidRibbons6Any(pkm, s6, gen, enc)
: pkm.Format >= 8
- ? GetInvalidRibbons6AnyG8(pkm, s6)
+ ? GetInvalidRibbons6AnyG8(pkm, s6, evos)
: GetInvalidRibbonsNone(s6.RibbonBits(), s6.RibbonNamesBool());
foreach (var z in iterate)
yield return z;
@@ -187,7 +187,7 @@ private static IEnumerable GetInvalidRibbons(PKM pkm, EvoCriteria[
if (pkm is IRibbonSetCommon8 s8)
{
bool inhabited8 = gen <= 8;
- var iterate = inhabited8 ? GetInvalidRibbons8Any(pkm, s8, enc) : GetInvalidRibbonsNone(s8.RibbonBits(), s8.RibbonNames());
+ var iterate = inhabited8 ? GetInvalidRibbons8Any(pkm, s8, enc, evos) : GetInvalidRibbonsNone(s8.RibbonBits(), s8.RibbonNames());
foreach (var z in iterate)
yield return z;
}
@@ -207,14 +207,14 @@ private static IEnumerable GetMissingContestRibbons(IReadOnlyList<
}
}
- private static IEnumerable GetInvalidRibbons4Any(PKM pkm, EvoCriteria[][] evos, IRibbonSetCommon4 s4, int gen)
+ private static IEnumerable GetInvalidRibbons4Any(PKM pkm, EvolutionHistory evos, IRibbonSetCommon4 s4, int gen)
{
if (s4.RibbonRecord)
yield return new RibbonResult(nameof(s4.RibbonRecord)); // Unobtainable
if (s4.RibbonFootprint && !CanHaveFootprintRibbon(pkm, evos, gen))
yield return new RibbonResult(nameof(s4.RibbonFootprint));
- bool visitBDSP = pkm.BDSP;
+ bool visitBDSP = pkm.HasVisitedBDSP(evos.Gen8b);
bool gen34 = gen is 3 or 4;
bool not6 = pkm.Format < 6 || gen is > 6 or < 3;
bool noDaily = !gen34 && not6 && !visitBDSP;
@@ -287,9 +287,9 @@ private static IEnumerable GetInvalidRibbons6Any(PKM pkm, IRibbonS
yield return result;
}
- private static IEnumerable GetInvalidRibbons6AnyG8(PKM pkm, IRibbonSetCommon6 s6)
+ private static IEnumerable GetInvalidRibbons6AnyG8(PKM pkm, IRibbonSetCommon6 s6, EvolutionHistory evos)
{
- if (!pkm.BDSP)
+ if (!pkm.HasVisitedBDSP(evos.Gen8b))
{
var none = GetInvalidRibbonsNone(s6.RibbonBits(), s6.RibbonNamesBool());
foreach (var x in none)
@@ -417,14 +417,21 @@ private static IEnumerable GetInvalidRibbons7Any(PKM pkm, IRibbonS
}
}
- private static IEnumerable GetInvalidRibbons8Any(PKM pkm, IRibbonSetCommon8 s8, IEncounterTemplate enc)
+ private static IEnumerable GetInvalidRibbons8Any(PKM pkm, IRibbonSetCommon8 s8, IEncounterTemplate enc, EvolutionHistory evos)
{
- if (!pkm.InhabitedGeneration(8) || !((PersonalInfoSWSH)PersonalTable.SWSH[pkm.Species]).IsPresentInGame || pkm.BDSP)
+ bool swsh = pkm.HasVisitedSWSH(evos.Gen8);
+ bool bdsp = pkm.HasVisitedBDSP(evos.Gen8b);
+ bool pla = pkm.HasVisitedLA(evos.Gen8a);
+
+ if (!swsh && !bdsp)
+ {
+ if (s8.RibbonTowerMaster)
+ yield return new RibbonResult(nameof(s8.RibbonTowerMaster));
+ }
+ if (!swsh)
{
if (s8.RibbonChampionGalar)
yield return new RibbonResult(nameof(s8.RibbonChampionGalar));
- if (s8.RibbonTowerMaster && !(pkm.SWSH || pkm.BDSP) && pkm.IsUntraded)
- yield return new RibbonResult(nameof(s8.RibbonTowerMaster));
if (s8.RibbonMasterRank)
yield return new RibbonResult(nameof(s8.RibbonMasterRank));
}
@@ -440,15 +447,10 @@ private static IEnumerable GetInvalidRibbons8Any(PKM pkm, IRibbonS
// Legends cannot compete in Ranked, thus cannot reach Master Rank and obtain the ribbon.
// Past gen Pokemon can get the ribbon only if they've been reset.
- if (s8.RibbonMasterRank && !CanParticipateInRankedSWSH(pkm, enc))
+ if (s8.RibbonMasterRank && !CanParticipateInRankedSWSH(pkm, enc, evos))
yield return new RibbonResult(nameof(s8.RibbonMasterRank));
- if (s8.RibbonTowerMaster)
- {
- if (!(pkm.SWSH || pkm.BDSP) && pkm.IsUntraded)
- yield return new RibbonResult(nameof(s8.RibbonTowerMaster));
- }
- else
+ if (!s8.RibbonTowerMaster)
{
// If the Tower Master ribbon is not present but a memory hint implies it should...
// This memory can also be applied in Gen6/7 via defeating the Chatelaines, where legends are disallowed.
@@ -461,22 +463,26 @@ private static IEnumerable GetInvalidRibbons8Any(PKM pkm, IRibbonS
}
}
- // can be expanded upon if SWSH gets updated with the new ribbon when HOME has BDSP support
- if (s8.RibbonTwinklingStar && (pkm is IRibbonSetCommon6 {RibbonContestStar:false} || !pkm.BDSP))
+ if (s8.RibbonTwinklingStar && (!bdsp || pkm is IRibbonSetCommon6 {RibbonContestStar:false}))
{
yield return new RibbonResult(nameof(s8.RibbonTwinklingStar));
}
// received when capturing photos with Pokémon in the Photography Studio
- if (s8.RibbonPioneer && !pkm.LA)
+ if (s8.RibbonPioneer && !pla)
{
yield return new RibbonResult(nameof(s8.RibbonPioneer));
}
}
- private static bool CanParticipateInRankedSWSH(PKM pkm, IEncounterTemplate enc)
+ private static bool CanParticipateInRankedSWSH(PKM pkm, IEncounterTemplate enc, EvolutionHistory evos)
{
- if (!pkm.SWSH && pkm is IBattleVersion {BattleVersion: 0})
+ bool exist = enc.Generation switch
+ {
+ < 8 => pkm is IBattleVersion { BattleVersion: (int)GameVersion.SW or (int)GameVersion.SH },
+ _ => pkm.HasVisitedSWSH(evos.Gen8),
+ };
+ if (!exist)
return false;
// Clamp to permitted species
@@ -490,14 +496,14 @@ private static bool CanParticipateInRankedSWSH(PKM pkm, IEncounterTemplate enc)
if (Legal.Mythicals.Contains(species))
return false;
- if (enc.Version == GameVersion.GO) // Capture date is global time, and not console changeable.
+ if (enc.Version == GameVersion.GO || enc is IEncounterServerDate { IsDateRestricted: true }) // Capture date is global time, and not console changeable.
{
if (pkm.MetDate > new DateTime(2022, 9, 1)) // Series 12 end date
return false;
}
}
- var pi = (PersonalInfoSWSH)PersonalTable.SWSH[species];
- return pi.IsPresentInGame;
+
+ return PersonalTable.SWSH.IsPresentInGame(species, pkm.Form);
}
private static IEnumerable GetInvalidRibbonsEvent1(PKM pkm, IEncounterTemplate enc)
@@ -582,7 +588,7 @@ private static bool IsAllowedBattleFrontier(int species, int form, int gen)
return IsAllowedBattleFrontier(species);
}
- private static bool CanHaveFootprintRibbon(PKM pkm, EvoCriteria[][] evos, int gen)
+ private static bool CanHaveFootprintRibbon(PKM pkm, EvolutionHistory evos, int gen)
{
if (gen <= 4) // Friendship Check unnecessary - can decrease after obtaining ribbon.
return true;
@@ -595,15 +601,16 @@ private static bool CanHaveFootprintRibbon(PKM pkm, EvoCriteria[][] evos, int ge
return true;
// Gen8-BDSP: Variable by species Footprint
- if (pkm.BDSP)
+ var bdspEvos = evos.Gen8b;
+ if (pkm.HasVisitedBDSP(bdspEvos))
{
- if (evos[8].Any(z => z.Species <= Legal.MaxSpeciesID_4 && !HasFootprintBDSP[z.Species]))
+ if (bdspEvos.Any(z => PersonalTable.BDSP.IsPresentInGame(z.Species, z.Form) && !HasFootprintBDSP[z.Species]))
return true; // no footprint
if (pkm.CurrentLevel - pkm.Met_Level >= 30)
return true; // traveled well
}
- // Gen8: Can't obtain
+ // Otherwise: Can't obtain
return false;
}
diff --git a/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs b/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs
index 662709727..384155b60 100644
--- a/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs
@@ -138,8 +138,7 @@ public void VerifyTransferLegalityG8(LegalityAnalysis data)
// PK8
int species = pkm.Species;
- var pi = (PersonalInfoSWSH)PersonalTable.SWSH.GetFormEntry(species, pkm.Form);
- if (!pi.IsPresentInGame || pkm.BDSP || pkm.LA) // Can't transfer
+ if (!PersonalTable.SWSH.IsPresentInGame(species, pkm.Form)) // Can't transfer
{
data.AddLine(GetInvalid(LTransferBad));
return;
@@ -150,7 +149,7 @@ public void VerifyTransferLegalityG8(LegalityAnalysis data)
{
VerifyHOMETracker(data, pkm);
}
- else if (enc.Generation < 8 && pkm.Format >= 8)
+ else if (pkm.Format >= 8 && !pkm.IsNative)
{
if (enc is EncounterStatic7 {IsTotem: true} s)
{
@@ -160,19 +159,18 @@ public void VerifyTransferLegalityG8(LegalityAnalysis data)
data.AddLine(GetInvalid(LTransferBad));
}
- VerifyHOMETransfer(data, pkm);
+ if (enc.Generation < 8)
+ VerifyHOMETransfer(data, pkm);
VerifyHOMETracker(data, pkm);
}
}
private void VerifyTransferLegalityG8a(LegalityAnalysis data, PA8 pk)
{
// Tracker value is set via Transfer across HOME.
- // No HOME access yet.
- if (pk is IHomeTrack { Tracker: not 0 })
- data.AddLine(GetInvalid(LTransferTrackerShouldBeZero));
+ if (!pk.IsNative)
+ VerifyHOMETracker(data, pk);
- var pi = (PersonalInfoLA)PersonalTable.LA.GetFormEntry(pk.Species, pk.Form);
- if (!pi.IsPresentInGame || !pk.LA) // Can't transfer
+ if (!PersonalTable.LA.IsPresentInGame(pk.Species, pk.Form)) // Can't transfer
data.AddLine(GetInvalid(LTransferBad));
}
@@ -180,12 +178,10 @@ private void VerifyTransferLegalityG8a(LegalityAnalysis data, PA8 pk)
private void VerifyTransferLegalityG8b(LegalityAnalysis data, PB8 pk)
{
// Tracker value is set via Transfer across HOME.
- // No HOME access yet.
- if (pk is IHomeTrack { Tracker: not 0 })
- data.AddLine(GetInvalid(LTransferTrackerShouldBeZero));
+ if (!pk.IsNative)
+ VerifyHOMETracker(data, pk);
- var pi = (PersonalInfoBDSP)PersonalTable.BDSP.GetFormEntry(pk.Species, pk.Form);
- if (!pi.IsPresentInGame || !pk.BDSP) // Can't transfer
+ if (!PersonalTable.BDSP.IsPresentInGame(pk.Species, pk.Form)) // Can't transfer
data.AddLine(GetInvalid(LTransferBad));
}
@@ -219,7 +215,9 @@ public IEnumerable VerifyVCEncounter(PKM pkm, IEncounterTemplate en
{
if (pkm.Met_Location != transfer.Location)
yield return GetInvalid(LTransferMetLocation);
- if (pkm.Egg_Location != transfer.EggLocation)
+
+ var expecteEgg = pkm is PB8 ? Locations.Default8bNone : transfer.EggLocation;
+ if (pkm.Egg_Location != expecteEgg)
yield return GetInvalid(LEggLocationNone);
// Flag Moves that cannot be transferred
diff --git a/PKHeX.Core/MysteryGifts/MysteryGift.cs b/PKHeX.Core/MysteryGifts/MysteryGift.cs
index 00255448f..e033afc12 100644
--- a/PKHeX.Core/MysteryGifts/MysteryGift.cs
+++ b/PKHeX.Core/MysteryGifts/MysteryGift.cs
@@ -161,6 +161,12 @@ public virtual Shiny Shiny
public virtual bool EggEncounter => IsEgg;
public abstract int EggLocation { get; set; }
+ protected virtual bool IsMatchEggLocation(PKM pk)
+ {
+ var expect = EggEncounter ? EggLocation : pk is PB8 ? Locations.Default8bNone : 0;
+ return pk.Egg_Location == expect;
+ }
+
public Ball FixedBall => (Ball)Ball;
public int TrainerID7 => (int)((uint)(TID | (SID << 16)) % 1000000);
diff --git a/PKHeX.Core/MysteryGifts/PCD.cs b/PKHeX.Core/MysteryGifts/PCD.cs
index af1c7e825..fb53dcd16 100644
--- a/PKHeX.Core/MysteryGifts/PCD.cs
+++ b/PKHeX.Core/MysteryGifts/PCD.cs
@@ -141,6 +141,8 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
// met location: deferred to general transfer check
if (wc.CurrentLevel > pkm.Met_Level) return false;
+ if (!IsMatchEggLocation(pkm))
+ return false;
}
else
{
diff --git a/PKHeX.Core/MysteryGifts/PGF.cs b/PKHeX.Core/MysteryGifts/PGF.cs
index 9568a48fe..bc4a5cb35 100644
--- a/PKHeX.Core/MysteryGifts/PGF.cs
+++ b/PKHeX.Core/MysteryGifts/PGF.cs
@@ -372,7 +372,7 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
if (OriginGame != 0 && OriginGame != pkm.Version) return false;
if (Language != 0 && Language != pkm.Language) return false;
- if (EggLocation != pkm.Egg_Location) return false;
+ if (!IsMatchEggLocation(pkm)) return false;
if (MetLocation != pkm.Met_Location) return false;
}
else
diff --git a/PKHeX.Core/MysteryGifts/WA8.cs b/PKHeX.Core/MysteryGifts/WA8.cs
index b3eed9892..85c33731a 100644
--- a/PKHeX.Core/MysteryGifts/WA8.cs
+++ b/PKHeX.Core/MysteryGifts/WA8.cs
@@ -26,8 +26,9 @@ public enum GiftType : byte
public WA8() : this(new byte[Size]) { }
public WA8(byte[] data) : base(data) { }
- public bool CanBeReceivedByVersion(int v) => v is (int) GameVersion.PLA;
+ public bool CanBeReceivedByVersion(int v, PKM pk) => v is (int) GameVersion.PLA || (pk is PK8 && v is (int)GameVersion.SW);
public bool IsDateRestricted => true;
+ public bool IsEquivalentFixedECPID => EncryptionConstant != 0 && PIDType == ShinyType8.FixedValue && PID == EncryptionConstant;
// General Card Properties
public override int CardID
@@ -403,7 +404,7 @@ public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
Species = Species,
Form = Form,
CurrentLevel = currentLevel,
- Ball = Ball != 0 ? Ball : 4, // Default is Pokeball
+ Ball = Ball != 0 ? Ball : (int)Core.Ball.LAPoke, // Default is Pokeball
Met_Level = metLevel,
HeldItem = HeldItem,
@@ -441,15 +442,22 @@ public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
EV_SPA = EV_SPA,
EV_SPD = EV_SPD,
- CanGigantamax = CanGigantamax,
- DynamaxLevel = DynamaxLevel,
+ GV_HP = GV_HP,
+ GV_ATK = GV_ATK,
+ GV_DEF = GV_DEF,
+ GV_SPE = GV_SPE,
+ GV_SPA = GV_SPA,
+ GV_SPD = GV_SPD,
+
+ //CanGigantamax = CanGigantamax,
+ //DynamaxLevel = DynamaxLevel,
Met_Location = MetLocation,
Egg_Location = EggLocation,
};
pk.SetMaximumPPCurrent();
- if ((sav.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version))
+ if ((sav.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version, pk))
pk.Version = (int)GameVersion.PLA;
if (OTGender >= 2)
@@ -578,7 +586,7 @@ private void SetIVs(PKM pk)
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
- if (pkm.Egg_Location == 0) // Not Egg
+ if (!IsEgg)
{
if (OTGender < 2)
{
@@ -623,8 +631,17 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
else
{
if (!Shiny.IsValid(pkm)) return false;
- if (EggLocation != pkm.Egg_Location) return false;
- if (MetLocation != pkm.Met_Location) return false;
+ if (!IsMatchEggLocation(pkm)) return false;
+ if (pkm is PK8)
+ {
+ if (pkm.Met_Location != Locations.HOME_SWLA)
+ return false;
+ }
+ else
+ {
+ if (MetLocation != pkm.Met_Location)
+ return false;
+ }
}
if (MetLevel != 0 && MetLevel != pkm.Met_Level) return false;
@@ -636,13 +653,15 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
var expectedBall = (Ball == 0 ? poke : Ball);
if (expectedBall < poke) // Not even Cherish balls are safe! They get set to the proto-Poké ball.
expectedBall = poke;
+ if (pkm is PK8)
+ expectedBall = (int)Core.Ball.Poke; // Transferred to SWSH -> Regular Poké ball
if (expectedBall != pkm.Ball)
return false;
- if (pkm is IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pkm.Species, pkm.Form, Species, Form))
+ if (pkm is IDynamaxLevel dl && dl.DynamaxLevel < DynamaxLevel)
return false;
- if (pkm is not IDynamaxLevel dl || dl.DynamaxLevel < DynamaxLevel)
+ if (pkm is IGanbaru b && b.IsGanbaruValuesBelow(this))
return false;
// PID Types 0 and 1 do not use the fixed PID value.
diff --git a/PKHeX.Core/MysteryGifts/WB7.cs b/PKHeX.Core/MysteryGifts/WB7.cs
index 54f959d38..aa4ae66bd 100644
--- a/PKHeX.Core/MysteryGifts/WB7.cs
+++ b/PKHeX.Core/MysteryGifts/WB7.cs
@@ -531,7 +531,7 @@ public bool CanBeAnyLanguage()
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
- if (pkm.Egg_Location == 0) // Not Egg
+ if (!IsEgg)
{
if (OTGender != 3)
{
@@ -544,6 +544,7 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
if (OriginGame != 0 && OriginGame != pkm.Version) return false;
if (EncryptionConstant != 0 && EncryptionConstant != pkm.EncryptionConstant) return false;
+ if (!IsMatchEggLocation(pkm)) return false;
if (!CanBeAnyLanguage() && !CanHaveLanguage(pkm.Language))
return false;
}
@@ -569,7 +570,7 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
else
{
if (!Shiny.IsValid(pkm)) return false;
- if (EggLocation != pkm.Egg_Location) return false;
+ if (!IsMatchEggLocation(pkm)) return false;
if (MetLocation != pkm.Met_Location) return false;
}
diff --git a/PKHeX.Core/MysteryGifts/WB8.cs b/PKHeX.Core/MysteryGifts/WB8.cs
index d746f5834..1e5730535 100644
--- a/PKHeX.Core/MysteryGifts/WB8.cs
+++ b/PKHeX.Core/MysteryGifts/WB8.cs
@@ -8,7 +8,7 @@ namespace PKHeX.Core
///
/// Generation 8b Mystery Gift Template File
///
- public sealed class WB8 : DataMysteryGift, ILangNick, INature, IRibbonIndex, IContestStats, ILangNicknamedTemplate,
+ public sealed class WB8 : DataMysteryGift, ILangNick, INature, IRibbonIndex, IContestStats, ILangNicknamedTemplate, IEncounterServerDate,
IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8
{
public const int Size = 0x2DC;
@@ -16,6 +16,9 @@ public sealed class WB8 : DataMysteryGift, ILangNick, INature, IRibbonIndex, ICo
public override int Generation => 8;
+ public bool IsDateRestricted => IsHOMEGift;
+ public bool IsEquivalentFixedECPID => EncryptionConstant != 0 && PIDType == ShinyType8.FixedValue && PID == EncryptionConstant;
+
public enum GiftType : byte
{
None = 0,
@@ -32,7 +35,7 @@ public enum GiftType : byte
// TODO: public byte RestrictVersion?
- public bool CanBeReceivedByVersion(int v) => v is (int) GameVersion.BD or (int) GameVersion.SP;
+ public bool CanBeReceivedByVersion(int v, PKM pk) => v is (int) GameVersion.BD or (int) GameVersion.SP || (pk is PK8 && Locations.IsValidMetBDSP(pk.Met_Location, pk.Version));
// General Card Properties
@@ -182,7 +185,7 @@ public override int HeldItem
public int MetLevel { get => Data[CardStart + 0x291]; set => Data[CardStart + 0x291] = (byte)value; }
- // Ribbons 0x24C-0x26C
+ // Ribbons 0x292-0x2B2
private const int RibbonBytesOffset = 0x292;
private const int RibbonBytesCount = 0x20;
private const int RibbonByteNone = 0xFF; // signed -1
@@ -366,6 +369,8 @@ private static int GetOTOffset(int language)
return 0x150 + (index * 0x20);
}
+ public bool IsHOMEGift => CardID >= 9000;
+
public bool CanHandleOT(int language) => !GetHasOT(language);
public override GameVersion Version
@@ -447,12 +452,12 @@ public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
}
pk.SetMaximumPPCurrent();
- if ((sav.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version))
+ if ((sav.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version, pk))
{
// give random valid game
var rnd = Util.Rand;
do { pk.Version = (int)GameVersion.BD + rnd.Next(2); }
- while (!CanBeReceivedByVersion(pk.Version));
+ while (!CanBeReceivedByVersion(pk.Version, pk));
}
if (pk.TID == 0 && pk.SID == 0)
@@ -581,7 +586,7 @@ private void SetIVs(PKM pk)
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
- if ((short)pkm.Egg_Location == Locations.Default8bNone) // Not Egg
+ if (!IsEgg)
{
if (OTGender < 2)
{
@@ -627,8 +632,17 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
else
{
if (!Shiny.IsValid(pkm)) return false;
- if (EggLocation != pkm.Egg_Location) return false;
- if (MetLocation != pkm.Met_Location) return false;
+ if (!IsMatchEggLocation(pkm)) return false;
+ if (pkm is PK8)
+ {
+ if (!Locations.IsValidMetBDSP(pkm.Met_Location, pkm.Version))
+ return false;
+ }
+ else
+ {
+ if (MetLocation != pkm.Met_Location)
+ return false;
+ }
}
if (MetLevel != 0 && MetLevel != pkm.Met_Level) return false;
@@ -646,6 +660,12 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
return pkm.PID == GetPID(pkm, type);
}
+ protected override bool IsMatchEggLocation(PKM pk)
+ {
+ var expect = pk is PB8 ? Locations.Default8bNone : 0;
+ return pk.Egg_Location == expect;
+ }
+
protected override bool IsMatchDeferred(PKM pkm) => Species != pkm.Species;
protected override bool IsMatchPartial(PKM pkm) => false; // no version compatibility checks yet.
diff --git a/PKHeX.Core/MysteryGifts/WC6.cs b/PKHeX.Core/MysteryGifts/WC6.cs
index c0f78bd31..5fdb7ecc0 100644
--- a/PKHeX.Core/MysteryGifts/WC6.cs
+++ b/PKHeX.Core/MysteryGifts/WC6.cs
@@ -520,7 +520,7 @@ private void SetIVs(PKM pk)
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
- if (pkm.Egg_Location == 0) // Not Egg
+ if (!IsEgg)
{
if (OTGender != 3)
{
@@ -559,7 +559,7 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
}
else
{
- if (EggLocation != pkm.Egg_Location) return false;
+ if (!IsMatchEggLocation(pkm)) return false;
if (MetLocation != pkm.Met_Location) return false;
}
diff --git a/PKHeX.Core/MysteryGifts/WC7.cs b/PKHeX.Core/MysteryGifts/WC7.cs
index e0bc2ce19..24c31cc2a 100644
--- a/PKHeX.Core/MysteryGifts/WC7.cs
+++ b/PKHeX.Core/MysteryGifts/WC7.cs
@@ -556,7 +556,7 @@ public bool IsAshGreninjaWC7(PKM pkm)
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
- if (pkm.Egg_Location == 0) // Not Egg
+ if (!IsEgg)
{
if (OTGender != 3)
{
@@ -591,7 +591,7 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
else
{
if (!Shiny.IsValid(pkm)) return false;
- if (EggLocation != pkm.Egg_Location) return false;
+ if (!IsMatchEggLocation(pkm)) return false;
if (MetLocation != pkm.Met_Location) return false;
}
diff --git a/PKHeX.Core/MysteryGifts/WC8.cs b/PKHeX.Core/MysteryGifts/WC8.cs
index b50295866..1dc91dde3 100644
--- a/PKHeX.Core/MysteryGifts/WC8.cs
+++ b/PKHeX.Core/MysteryGifts/WC8.cs
@@ -581,7 +581,7 @@ private void SetIVs(PKM pk)
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
- if (pkm.Egg_Location == 0) // Not Egg
+ if (!IsEgg)
{
if (OTGender < 2)
{
@@ -646,7 +646,7 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
else
{
if (!Shiny.IsValid(pkm)) return false;
- if (EggLocation != pkm.Egg_Location) return false;
+ if (!IsMatchEggLocation(pkm)) return false;
if (MetLocation != pkm.Met_Location) return false;
}
@@ -656,7 +656,7 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
if (Nature != -1 && pkm.Nature != Nature) return false;
if (Gender != 3 && Gender != pkm.Gender) return false;
- if (pkm is IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pkm.Species, pkm.Form, Species, Form))
+ if (pkm is PK8 and IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pkm.Species, pkm.Form, Species, Form))
return false;
if (pkm is not IDynamaxLevel dl || dl.DynamaxLevel < DynamaxLevel)
diff --git a/PKHeX.Core/PKM/BK4.cs b/PKHeX.Core/PKM/BK4.cs
index e588578ed..c85df3c3f 100644
--- a/PKHeX.Core/PKM/BK4.cs
+++ b/PKHeX.Core/PKM/BK4.cs
@@ -20,7 +20,7 @@ public sealed class BK4 : G4PKM
public override int SIZE_PARTY => PokeCrypto.SIZE_4STORED;
public override int SIZE_STORED => PokeCrypto.SIZE_4STORED;
- public override int Format => 4;
+ public override EntityContext Context => EntityContext.Gen4;
public override PersonalInfo PersonalInfo => PersonalTable.HGSS[Species];
public override byte[] DecryptedBoxData => EncryptedBoxData;
@@ -66,7 +66,7 @@ public override uint EXP
public override int OT_Friendship { get => Data[0x14]; set => Data[0x14] = (byte)value; }
public override int Ability { get => Data[0x15]; set => Data[0x15] = (byte)value; }
- public override int MarkValue { get => Data[0x16]; protected set => Data[0x16] = (byte)value; }
+ public override int MarkValue { get => Data[0x16]; set => Data[0x16] = (byte)value; }
public override int Language { get => Data[0x17]; set => Data[0x17] = (byte)value; }
public override int EV_HP { get => Data[0x18]; set => Data[0x18] = (byte)value; }
public override int EV_ATK { get => Data[0x19]; set => Data[0x19] = (byte)value; }
diff --git a/PKHeX.Core/PKM/CK3.cs b/PKHeX.Core/PKM/CK3.cs
index fbb3d14de..9c781fd94 100644
--- a/PKHeX.Core/PKM/CK3.cs
+++ b/PKHeX.Core/PKM/CK3.cs
@@ -22,7 +22,7 @@ public sealed class CK3 : G3PKM, IShadowPKM
public override int SIZE_PARTY => PokeCrypto.SIZE_3CSTORED;
public override int SIZE_STORED => PokeCrypto.SIZE_3CSTORED;
- public override int Format => 3;
+ public override EntityContext Context => EntityContext.Gen3;
public override PersonalInfo PersonalInfo => PersonalTable.RS[Species];
public CK3(byte[] data) : base(data) { }
public CK3() : this(new byte[PokeCrypto.SIZE_3CSTORED]) { }
@@ -173,7 +173,7 @@ public sealed class CK3 : G3PKM, IShadowPKM
public override bool AbilityBit { get => Data[0xCC] == 1; set => Data[0xCC] = value ? (byte)1 : (byte)0; }
public override bool Valid { get => Data[0xCD] == 0; set => Data[0xCD] = !value ? (byte)1 : (byte)0; }
- public override int MarkValue { get => SwapBits(Data[0xCF], 1, 2); protected set => Data[0xCF] = (byte)SwapBits(value, 1, 2); }
+ public override int MarkValue { get => SwapBits(Data[0xCF], 1, 2); set => Data[0xCF] = (byte)SwapBits(value, 1, 2); }
public override int PKRS_Days { get => Math.Max((sbyte)Data[0xD0], (sbyte)0); set => Data[0xD0] = (byte)(value == 0 ? 0xFF : value & 0xF); }
public int PartySlot { get => Data[0xD7]; set => Data[0xD7] = (byte)value; } // or not; only really used while in party?
diff --git a/PKHeX.Core/PKM/G8PKM.cs b/PKHeX.Core/PKM/G8PKM.cs
index b2bfccc56..47b802fda 100644
--- a/PKHeX.Core/PKM/G8PKM.cs
+++ b/PKHeX.Core/PKM/G8PKM.cs
@@ -4,14 +4,15 @@
namespace PKHeX.Core
{
/// Generation 8 format.
- public abstract class G8PKM : PKM, ISanityChecksum,
+ public abstract class G8PKM : PKM, ISanityChecksum, IMoveReset,
IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8, IRibbonSetAffixed, ITechRecord8, ISociability,
IContestStats, IContestStatsMutable, IHyperTrain, IScaledSize, IGigantamax, IFavorite, IDynamaxLevel, IRibbonIndex, IHandlerLanguage, IFormArgument, IHomeTrack, IBattleVersion, ITrainerMemories
{
- public sealed override int Format => 8;
protected G8PKM() : base(PokeCrypto.SIZE_8PARTY) { }
protected G8PKM(byte[] data) : base(DecryptParty(data)) { }
+ public abstract void ResetMoves();
+
private static byte[] DecryptParty(byte[] data)
{
PokeCrypto.DecryptIfEncrypted8(ref data);
@@ -124,7 +125,7 @@ public void FixRelearn()
public bool Favorite { get => (Data[0x16] & 8) != 0; set => Data[0x16] = (byte)((Data[0x16] & ~8) | ((value ? 1 : 0) << 3)); } // unused, was in LGPE but not in SWSH
public bool CanGigantamax { get => (Data[0x16] & 16) != 0; set => Data[0x16] = (byte)((Data[0x16] & ~16) | (value ? 16 : 0)); }
// 0x17 alignment unused
- public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); protected set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); }
+ public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); }
// 0x1A alignment unused
// 0x1B alignment unused
public override uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x1C), value); }
@@ -411,6 +412,8 @@ public void SetPokeJobFlag(int index, bool value)
public bool GetPokeJobFlagAny() => Array.FindIndex(Data, 0xCE, 14, z => z != 0) >= 0;
+ public void ClearPokeJobFlags() => Data.AsSpan(0xCE, 14).Clear();
+
public override byte Fullness { get => Data[0xDC]; set => Data[0xDC] = value; }
public override byte Enjoyment { get => Data[0xDD]; set => Data[0xDD] = value; }
public override int Version { get => Data[0xDE]; set => Data[0xDE] = (byte)value; }
@@ -478,6 +481,8 @@ public void SetMoveRecordFlag(int index, bool value)
public bool GetMoveRecordFlagAny() => Array.FindIndex(Data, 0x127, 14, z => z != 0) >= 0;
+ public void ClearMoveRecordFlags() => Data.AsSpan(0x127, 14).Clear();
+
// Why did you mis-align this field, GameFreak?
public ulong Tracker
{
@@ -532,5 +537,227 @@ public int GetRibbonByte(int index)
index -= 64;
return 0x40 + (index >> 3);
}
+
+ protected T ConvertTo() where T : G8PKM, new()
+ {
+ var pk = new T();
+ Data.AsSpan().CopyTo(pk.Data);
+ pk.Move1_PPUps = pk.Move2_PPUps = pk.Move3_PPUps = pk.Move4_PPUps = 0;
+ pk.RelearnMove1 = pk.RelearnMove2 = pk.RelearnMove3 = pk.RelearnMove4 = 0;
+ pk.ClearMoveRecordFlags();
+ pk.ClearPokeJobFlags();
+
+ pk.CanGigantamax = false;
+ pk.DynamaxLevel = 0;
+ pk.Sociability = 0;
+ pk.Fullness = 0;
+ pk.Data[0x52] = 0;
+
+ pk.ResetMoves();
+ pk.ResetPartyStats();
+ pk.RefreshChecksum();
+
+ return pk;
+ }
+
+ public virtual PA8 ConvertToPA8()
+ {
+ var pk = new PA8
+ {
+ EncryptionConstant = EncryptionConstant,
+ PID = PID,
+ Species = Species,
+ Form = Form,
+ FormArgument = FormArgument,
+ Gender = Gender,
+ Nature = Nature,
+ StatNature = StatNature,
+
+ TID = TID,
+ SID = SID,
+ EXP = EXP,
+ Ability = Ability,
+ AbilityNumber = AbilityNumber,
+ Language = Language,
+ Version = Version,
+
+ IV_HP = IV_HP,
+ IV_ATK = IV_ATK,
+ IV_DEF = IV_DEF,
+ IV_SPE = IV_SPE,
+ IV_SPA = IV_SPA,
+ IV_SPD = IV_SPD,
+ IsEgg = IsEgg,
+ EV_HP = EV_HP,
+ EV_ATK = EV_ATK,
+ EV_DEF = EV_DEF,
+ EV_SPE = EV_SPE,
+ EV_SPA = EV_SPA,
+ EV_SPD = EV_SPD,
+
+ OT_Gender = OT_Gender,
+ OT_Friendship = OT_Friendship,
+ OT_Intensity = OT_Intensity,
+ OT_Memory = OT_Memory,
+ OT_TextVar = OT_TextVar,
+ OT_Feeling = OT_Feeling,
+ Egg_Year = Egg_Year,
+ Egg_Month = Egg_Month,
+ Egg_Day = Egg_Day,
+ Met_Year = Met_Year,
+ Met_Month = Met_Month,
+ Met_Day = Met_Day,
+ Ball = Ball,
+ Egg_Location = Egg_Location,
+ Met_Location = Met_Location,
+ Met_Level = Met_Level,
+ Tracker = Tracker,
+
+ IsNicknamed = IsNicknamed,
+ CurrentHandler = CurrentHandler,
+ HT_Gender = HT_Gender,
+ HT_Language = HT_Language,
+ HT_Friendship = HT_Friendship,
+ HT_Intensity = HT_Intensity,
+ HT_Memory = HT_Memory,
+ HT_Feeling = HT_Feeling,
+ HT_TextVar = HT_TextVar,
+
+ FatefulEncounter = FatefulEncounter,
+ CNT_Cool = CNT_Cool,
+ CNT_Beauty = CNT_Beauty,
+ CNT_Cute = CNT_Cute,
+ CNT_Smart = CNT_Smart,
+ CNT_Tough = CNT_Tough,
+ CNT_Sheen = CNT_Sheen,
+
+ RibbonChampionKalos = RibbonChampionKalos,
+ RibbonChampionG3 = RibbonChampionG3,
+ RibbonChampionSinnoh = RibbonChampionSinnoh,
+ RibbonBestFriends = RibbonBestFriends,
+ RibbonTraining = RibbonTraining,
+ RibbonBattlerSkillful = RibbonBattlerSkillful,
+ RibbonBattlerExpert = RibbonBattlerExpert,
+ RibbonEffort = RibbonEffort,
+ RibbonAlert = RibbonAlert,
+ RibbonShock = RibbonShock,
+ RibbonDowncast = RibbonDowncast,
+ RibbonCareless = RibbonCareless,
+ RibbonRelax = RibbonRelax,
+ RibbonSnooze = RibbonSnooze,
+ RibbonSmile = RibbonSmile,
+ RibbonGorgeous = RibbonGorgeous,
+ RibbonRoyal = RibbonRoyal,
+ RibbonGorgeousRoyal = RibbonGorgeousRoyal,
+ RibbonArtist = RibbonArtist,
+ RibbonFootprint = RibbonFootprint,
+ RibbonRecord = RibbonRecord,
+ RibbonLegend = RibbonLegend,
+ RibbonCountry = RibbonCountry,
+ RibbonNational = RibbonNational,
+ RibbonEarth = RibbonEarth,
+ RibbonWorld = RibbonWorld,
+ RibbonClassic = RibbonClassic,
+ RibbonPremier = RibbonPremier,
+ RibbonEvent = RibbonEvent,
+ RibbonBirthday = RibbonBirthday,
+ RibbonSpecial = RibbonSpecial,
+ RibbonSouvenir = RibbonSouvenir,
+ RibbonWishing = RibbonWishing,
+ RibbonChampionBattle = RibbonChampionBattle,
+ RibbonChampionRegional = RibbonChampionRegional,
+ RibbonChampionNational = RibbonChampionNational,
+ RibbonChampionWorld = RibbonChampionWorld,
+ HasContestMemoryRibbon = HasContestMemoryRibbon,
+ HasBattleMemoryRibbon = HasBattleMemoryRibbon,
+ RibbonChampionG6Hoenn = RibbonChampionG6Hoenn,
+ RibbonContestStar = RibbonContestStar,
+ RibbonMasterCoolness = RibbonMasterCoolness,
+ RibbonMasterBeauty = RibbonMasterBeauty,
+ RibbonMasterCuteness = RibbonMasterCuteness,
+ RibbonMasterCleverness = RibbonMasterCleverness,
+ RibbonMasterToughness = RibbonMasterToughness,
+ RibbonChampionAlola = RibbonChampionAlola,
+ RibbonBattleRoyale = RibbonBattleRoyale,
+ RibbonBattleTreeGreat = RibbonBattleTreeGreat,
+ RibbonBattleTreeMaster = RibbonBattleTreeMaster,
+ RibbonChampionGalar = RibbonChampionGalar,
+ RibbonTowerMaster = RibbonTowerMaster,
+ RibbonMasterRank = RibbonMasterRank,
+
+ RibbonMarkLunchtime = RibbonMarkLunchtime,
+ RibbonMarkSleepyTime = RibbonMarkSleepyTime,
+ RibbonMarkDusk = RibbonMarkDusk,
+ RibbonMarkDawn = RibbonMarkDawn,
+ RibbonMarkCloudy = RibbonMarkCloudy,
+ RibbonMarkRainy = RibbonMarkRainy,
+ RibbonMarkStormy = RibbonMarkStormy,
+ RibbonMarkSnowy = RibbonMarkSnowy,
+ RibbonMarkBlizzard = RibbonMarkBlizzard,
+ RibbonMarkDry = RibbonMarkDry,
+ RibbonMarkSandstorm = RibbonMarkSandstorm,
+ RibbonCountMemoryContest = RibbonCountMemoryContest,
+ RibbonCountMemoryBattle = RibbonCountMemoryBattle,
+ RibbonMarkMisty = RibbonMarkMisty,
+ RibbonMarkDestiny = RibbonMarkDestiny,
+ RibbonMarkFishing = RibbonMarkFishing,
+ RibbonMarkCurry = RibbonMarkCurry,
+ RibbonMarkUncommon = RibbonMarkUncommon,
+ RibbonMarkRare = RibbonMarkRare,
+ RibbonMarkRowdy = RibbonMarkRowdy,
+ RibbonMarkAbsentMinded = RibbonMarkAbsentMinded,
+ RibbonMarkJittery = RibbonMarkJittery,
+ RibbonMarkExcited = RibbonMarkExcited,
+ RibbonMarkCharismatic = RibbonMarkCharismatic,
+ RibbonMarkCalmness = RibbonMarkCalmness,
+ RibbonMarkIntense = RibbonMarkIntense,
+ RibbonMarkZonedOut = RibbonMarkZonedOut,
+ RibbonMarkJoyful = RibbonMarkJoyful,
+ RibbonMarkAngry = RibbonMarkAngry,
+ RibbonMarkSmiley = RibbonMarkSmiley,
+ RibbonMarkTeary = RibbonMarkTeary,
+ RibbonMarkUpbeat = RibbonMarkUpbeat,
+ RibbonMarkPeeved = RibbonMarkPeeved,
+ RibbonMarkIntellectual = RibbonMarkIntellectual,
+ RibbonMarkFerocious = RibbonMarkFerocious,
+ RibbonMarkCrafty = RibbonMarkCrafty,
+ RibbonMarkScowling = RibbonMarkScowling,
+ RibbonMarkKindly = RibbonMarkKindly,
+ RibbonMarkFlustered = RibbonMarkFlustered,
+ RibbonMarkPumpedUp = RibbonMarkPumpedUp,
+ RibbonMarkZeroEnergy = RibbonMarkZeroEnergy,
+ RibbonMarkPrideful = RibbonMarkPrideful,
+ RibbonMarkUnsure = RibbonMarkUnsure,
+ RibbonMarkHumble = RibbonMarkHumble,
+ RibbonMarkThorny = RibbonMarkThorny,
+ RibbonMarkVigor = RibbonMarkVigor,
+ RibbonMarkSlump = RibbonMarkSlump,
+ RibbonPioneer = RibbonPioneer,
+ RibbonTwinklingStar = RibbonTwinklingStar,
+
+ AffixedRibbon = AffixedRibbon,
+ HyperTrainFlags = HyperTrainFlags,
+
+ BattleVersion = BattleVersion,
+ PKRS_Days = PKRS_Days,
+ PKRS_Strain = PKRS_Strain,
+ HeightScalar = HeightScalar,
+ WeightScalar = WeightScalar,
+
+ Favorite = Favorite,
+ };
+
+ Nickname_Trash.CopyTo(pk.Nickname_Trash);
+ OT_Trash.CopyTo(pk.OT_Trash);
+ HT_Trash.CopyTo(pk.HT_Trash);
+
+ pk.SanitizeImport();
+
+ pk.ResetMoves();
+ pk.ResetPartyStats();
+ pk.RefreshChecksum();
+
+ return pk;
+ }
}
}
diff --git a/PKHeX.Core/PKM/HOME/GameDataCore.cs b/PKHeX.Core/PKM/HOME/GameDataCore.cs
new file mode 100644
index 000000000..225c2c5ce
--- /dev/null
+++ b/PKHeX.Core/PKM/HOME/GameDataCore.cs
@@ -0,0 +1,414 @@
+using System;
+using static System.Buffers.Binary.BinaryPrimitives;
+
+namespace PKHeX.Core;
+
+///
+/// Core game data storage, format 1.
+///
+public sealed class GameDataCore : IHomeTrack, ISpeciesForm, ITrainerID, INature, IContestStats, IContestStatsMutable, IScaledSize, ITrainerMemories, IHandlerLanguage, IBattleVersion, IHyperTrain, IFormArgument, IFavorite,
+ IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8
+{
+ // Internal Attributes set on creation
+ public readonly byte[] Data; // Raw Storage
+ public readonly int Offset;
+
+ public GameDataCore(byte[] data, int offset)
+ {
+ if (ReadUInt16LittleEndian(data.AsSpan(offset)) != HomeCrypto.SIZE_1CORE)
+ throw new ArgumentException("Invalid Format 1 Core Data!");
+
+ Data = data;
+ Offset = offset + 2;
+ }
+
+ public ulong Tracker { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x00), value); }
+ public uint EncryptionConstant { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x08)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x08), value); }
+ public bool IsBadEgg { get => Data[Offset + 0x0C] != 0; set => Data[Offset + 0x0C] = (byte)(value ? 1 : 0); }
+ public int Species { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0D)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0D), (ushort)value); }
+ public int TID { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0F)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0F), (ushort)value); }
+ public int SID { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x11)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x11), (ushort)value); }
+ public uint EXP { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x13)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x13), value); }
+ public int Ability { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x17)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x17), (ushort)value); }
+ public int AbilityNumber { get => Data[Offset + 0x19] & 7; set => Data[Offset + 0x19] = (byte)((Data[Offset + 0x19] & ~7) | (value & 7)); }
+ public bool Favorite { get => Data[Offset + 0x1A] != 0; set => Data[Offset + 0x1A] = (byte)(value ? 1 : 0); }
+ public int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x1B)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x1B), (ushort)value); }
+ public uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x1D)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x1D), value); }
+ public int Nature { get => Data[Offset + 0x21]; set => Data[Offset + 0x21] = (byte)value; }
+ public int StatNature { get => Data[Offset + 0x22]; set => Data[Offset + 0x22] = (byte)value; }
+ public bool FatefulEncounter { get => Data[Offset + 0x23] != 0; set => Data[Offset + 0x23] = (byte)(value ? 1 : 0); }
+ public int Gender { get => Data[Offset + 0x24]; set => Data[Offset + 0x24] = (byte)value; }
+ public int Form { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x25)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x25), (ushort)value); }
+ public int EV_HP { get => Data[Offset + 0x27]; set => Data[Offset + 0x27] = (byte)value; }
+ public int EV_ATK { get => Data[Offset + 0x28]; set => Data[Offset + 0x28] = (byte)value; }
+ public int EV_DEF { get => Data[Offset + 0x29]; set => Data[Offset + 0x29] = (byte)value; }
+ public int EV_SPE { get => Data[Offset + 0x2A]; set => Data[Offset + 0x2A] = (byte)value; }
+ public int EV_SPA { get => Data[Offset + 0x2B]; set => Data[Offset + 0x2B] = (byte)value; }
+ public int EV_SPD { get => Data[Offset + 0x2C]; set => Data[Offset + 0x2C] = (byte)value; }
+ public byte CNT_Cool { get => Data[Offset + 0x2D]; set => Data[Offset + 0x2D] = value; }
+ public byte CNT_Beauty { get => Data[Offset + 0x2E]; set => Data[Offset + 0x2E] = value; }
+ public byte CNT_Cute { get => Data[Offset + 0x2F]; set => Data[Offset + 0x2F] = value; }
+ public byte CNT_Smart { get => Data[Offset + 0x30]; set => Data[Offset + 0x30] = value; }
+ public byte CNT_Tough { get => Data[Offset + 0x31]; set => Data[Offset + 0x31] = value; }
+ public byte CNT_Sheen { get => Data[Offset + 0x32]; set => Data[Offset + 0x32] = value; }
+ private byte PKRS { get => Data[Offset + 0x33]; set => Data[Offset + 0x33] = value; }
+ public int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); }
+ public int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | value << 4); }
+
+ private bool GetFlag(int offset, int bit) => FlagUtil.GetFlag(Data, Offset + offset, bit);
+ private void SetFlag(int offset, int bit, bool value) => FlagUtil.SetFlag(Data, Offset + offset, bit, value);
+
+ public bool RibbonChampionKalos { get => GetFlag(0x34, 0); set => SetFlag(0x34, 0, value); }
+ public bool RibbonChampionG3 { get => GetFlag(0x34, 1); set => SetFlag(0x34, 1, value); }
+ public bool RibbonChampionSinnoh { get => GetFlag(0x34, 2); set => SetFlag(0x34, 2, value); }
+ public bool RibbonBestFriends { get => GetFlag(0x34, 3); set => SetFlag(0x34, 3, value); }
+ public bool RibbonTraining { get => GetFlag(0x34, 4); set => SetFlag(0x34, 4, value); }
+ public bool RibbonBattlerSkillful { get => GetFlag(0x34, 5); set => SetFlag(0x34, 5, value); }
+ public bool RibbonBattlerExpert { get => GetFlag(0x34, 6); set => SetFlag(0x34, 6, value); }
+ public bool RibbonEffort { get => GetFlag(0x34, 7); set => SetFlag(0x34, 7, value); }
+
+ public bool RibbonAlert { get => GetFlag(0x35, 0); set => SetFlag(0x35, 0, value); }
+ public bool RibbonShock { get => GetFlag(0x35, 1); set => SetFlag(0x35, 1, value); }
+ public bool RibbonDowncast { get => GetFlag(0x35, 2); set => SetFlag(0x35, 2, value); }
+ public bool RibbonCareless { get => GetFlag(0x35, 3); set => SetFlag(0x35, 3, value); }
+ public bool RibbonRelax { get => GetFlag(0x35, 4); set => SetFlag(0x35, 4, value); }
+ public bool RibbonSnooze { get => GetFlag(0x35, 5); set => SetFlag(0x35, 5, value); }
+ public bool RibbonSmile { get => GetFlag(0x35, 6); set => SetFlag(0x35, 6, value); }
+ public bool RibbonGorgeous { get => GetFlag(0x35, 7); set => SetFlag(0x35, 7, value); }
+
+ public bool RibbonRoyal { get => GetFlag(0x36, 0); set => SetFlag(0x36, 0, value); }
+ public bool RibbonGorgeousRoyal { get => GetFlag(0x36, 1); set => SetFlag(0x36, 1, value); }
+ public bool RibbonArtist { get => GetFlag(0x36, 2); set => SetFlag(0x36, 2, value); }
+ public bool RibbonFootprint { get => GetFlag(0x36, 3); set => SetFlag(0x36, 3, value); }
+ public bool RibbonRecord { get => GetFlag(0x36, 4); set => SetFlag(0x36, 4, value); }
+ public bool RibbonLegend { get => GetFlag(0x36, 5); set => SetFlag(0x36, 5, value); }
+ public bool RibbonCountry { get => GetFlag(0x36, 6); set => SetFlag(0x36, 6, value); }
+ public bool RibbonNational { get => GetFlag(0x36, 7); set => SetFlag(0x36, 7, value); }
+
+ public bool RibbonEarth { get => GetFlag(0x37, 0); set => SetFlag(0x37, 0, value); }
+ public bool RibbonWorld { get => GetFlag(0x37, 1); set => SetFlag(0x37, 1, value); }
+ public bool RibbonClassic { get => GetFlag(0x37, 2); set => SetFlag(0x37, 2, value); }
+ public bool RibbonPremier { get => GetFlag(0x37, 3); set => SetFlag(0x37, 3, value); }
+ public bool RibbonEvent { get => GetFlag(0x37, 4); set => SetFlag(0x37, 4, value); }
+ public bool RibbonBirthday { get => GetFlag(0x37, 5); set => SetFlag(0x37, 5, value); }
+ public bool RibbonSpecial { get => GetFlag(0x37, 6); set => SetFlag(0x37, 6, value); }
+ public bool RibbonSouvenir { get => GetFlag(0x37, 7); set => SetFlag(0x37, 7, value); }
+
+ // ribbon u32
+ public bool RibbonWishing { get => GetFlag(0x38, 0); set => SetFlag(0x38, 0, value); }
+ public bool RibbonChampionBattle { get => GetFlag(0x38, 1); set => SetFlag(0x38, 1, value); }
+ public bool RibbonChampionRegional { get => GetFlag(0x38, 2); set => SetFlag(0x38, 2, value); }
+ public bool RibbonChampionNational { get => GetFlag(0x38, 3); set => SetFlag(0x38, 3, value); }
+ public bool RibbonChampionWorld { get => GetFlag(0x38, 4); set => SetFlag(0x38, 4, value); }
+ public bool HasContestMemoryRibbon { get => GetFlag(0x38, 5); set => SetFlag(0x38, 5, value); }
+ public bool HasBattleMemoryRibbon { get => GetFlag(0x38, 6); set => SetFlag(0x38, 6, value); }
+ public bool RibbonChampionG6Hoenn { get => GetFlag(0x38, 7); set => SetFlag(0x38, 7, value); }
+
+ public bool RibbonContestStar { get => GetFlag(0x39, 0); set => SetFlag(0x39, 0, value); }
+ public bool RibbonMasterCoolness { get => GetFlag(0x39, 1); set => SetFlag(0x39, 1, value); }
+ public bool RibbonMasterBeauty { get => GetFlag(0x39, 2); set => SetFlag(0x39, 2, value); }
+ public bool RibbonMasterCuteness { get => GetFlag(0x39, 3); set => SetFlag(0x39, 3, value); }
+ public bool RibbonMasterCleverness { get => GetFlag(0x39, 4); set => SetFlag(0x39, 4, value); }
+ public bool RibbonMasterToughness { get => GetFlag(0x39, 5); set => SetFlag(0x39, 5, value); }
+ public bool RibbonChampionAlola { get => GetFlag(0x39, 6); set => SetFlag(0x39, 6, value); }
+ public bool RibbonBattleRoyale { get => GetFlag(0x39, 7); set => SetFlag(0x39, 7, value); }
+
+ public bool RibbonBattleTreeGreat { get => GetFlag(0x3A, 0); set => SetFlag(0x3A, 0, value); }
+ public bool RibbonBattleTreeMaster { get => GetFlag(0x3A, 1); set => SetFlag(0x3A, 1, value); }
+ public bool RibbonChampionGalar { get => GetFlag(0x3A, 2); set => SetFlag(0x3A, 2, value); }
+ public bool RibbonTowerMaster { get => GetFlag(0x3A, 3); set => SetFlag(0x3A, 3, value); }
+ public bool RibbonMasterRank { get => GetFlag(0x3A, 4); set => SetFlag(0x3A, 4, value); }
+ public bool RibbonMarkLunchtime { get => GetFlag(0x3A, 5); set => SetFlag(0x3A, 5, value); }
+ public bool RibbonMarkSleepyTime { get => GetFlag(0x3A, 6); set => SetFlag(0x3A, 6, value); }
+ public bool RibbonMarkDusk { get => GetFlag(0x3A, 7); set => SetFlag(0x3A, 7, value); }
+
+ public bool RibbonMarkDawn { get => GetFlag(0x3B, 0); set => SetFlag(0x3B, 0, value); }
+ public bool RibbonMarkCloudy { get => GetFlag(0x3B, 1); set => SetFlag(0x3B, 1, value); }
+ public bool RibbonMarkRainy { get => GetFlag(0x3B, 2); set => SetFlag(0x3B, 2, value); }
+ public bool RibbonMarkStormy { get => GetFlag(0x3B, 3); set => SetFlag(0x3B, 3, value); }
+ public bool RibbonMarkSnowy { get => GetFlag(0x3B, 4); set => SetFlag(0x3B, 4, value); }
+ public bool RibbonMarkBlizzard { get => GetFlag(0x3B, 5); set => SetFlag(0x3B, 5, value); }
+ public bool RibbonMarkDry { get => GetFlag(0x3B, 6); set => SetFlag(0x3B, 6, value); }
+ public bool RibbonMarkSandstorm { get => GetFlag(0x3B, 7); set => SetFlag(0x3B, 7, value); }
+
+ public int RibbonCountMemoryContest { get => Data[0x3C]; set => HasContestMemoryRibbon = (Data[0x3C] = (byte)value) != 0; }
+ public int RibbonCountMemoryBattle { get => Data[0x3D]; set => HasBattleMemoryRibbon = (Data[0x3D] = (byte)value) != 0; }
+
+ // 0x3E Ribbon 3
+ public bool RibbonMarkMisty { get => GetFlag(0x3E, 0); set => SetFlag(0x3E, 0, value); }
+ public bool RibbonMarkDestiny { get => GetFlag(0x3E, 1); set => SetFlag(0x3E, 1, value); }
+ public bool RibbonMarkFishing { get => GetFlag(0x3E, 2); set => SetFlag(0x3E, 2, value); }
+ public bool RibbonMarkCurry { get => GetFlag(0x3E, 3); set => SetFlag(0x3E, 3, value); }
+ public bool RibbonMarkUncommon { get => GetFlag(0x3E, 4); set => SetFlag(0x3E, 4, value); }
+ public bool RibbonMarkRare { get => GetFlag(0x3E, 5); set => SetFlag(0x3E, 5, value); }
+ public bool RibbonMarkRowdy { get => GetFlag(0x3E, 6); set => SetFlag(0x3E, 6, value); }
+ public bool RibbonMarkAbsentMinded { get => GetFlag(0x3E, 7); set => SetFlag(0x3E, 7, value); }
+
+ public bool RibbonMarkJittery { get => GetFlag(0x3F, 0); set => SetFlag(0x3F, 0, value); }
+ public bool RibbonMarkExcited { get => GetFlag(0x3F, 1); set => SetFlag(0x3F, 1, value); }
+ public bool RibbonMarkCharismatic { get => GetFlag(0x3F, 2); set => SetFlag(0x3F, 2, value); }
+ public bool RibbonMarkCalmness { get => GetFlag(0x3F, 3); set => SetFlag(0x3F, 3, value); }
+ public bool RibbonMarkIntense { get => GetFlag(0x3F, 4); set => SetFlag(0x3F, 4, value); }
+ public bool RibbonMarkZonedOut { get => GetFlag(0x3F, 5); set => SetFlag(0x3F, 5, value); }
+ public bool RibbonMarkJoyful { get => GetFlag(0x3F, 6); set => SetFlag(0x3F, 6, value); }
+ public bool RibbonMarkAngry { get => GetFlag(0x3F, 7); set => SetFlag(0x3F, 7, value); }
+
+ public bool RibbonMarkSmiley { get => GetFlag(0x40, 0); set => SetFlag(0x40, 0, value); }
+ public bool RibbonMarkTeary { get => GetFlag(0x40, 1); set => SetFlag(0x40, 1, value); }
+ public bool RibbonMarkUpbeat { get => GetFlag(0x40, 2); set => SetFlag(0x40, 2, value); }
+ public bool RibbonMarkPeeved { get => GetFlag(0x40, 3); set => SetFlag(0x40, 3, value); }
+ public bool RibbonMarkIntellectual { get => GetFlag(0x40, 4); set => SetFlag(0x40, 4, value); }
+ public bool RibbonMarkFerocious { get => GetFlag(0x40, 5); set => SetFlag(0x40, 5, value); }
+ public bool RibbonMarkCrafty { get => GetFlag(0x40, 6); set => SetFlag(0x40, 6, value); }
+ public bool RibbonMarkScowling { get => GetFlag(0x40, 7); set => SetFlag(0x40, 7, value); }
+
+ public bool RibbonMarkKindly { get => GetFlag(0x41, 0); set => SetFlag(0x41, 0, value); }
+ public bool RibbonMarkFlustered { get => GetFlag(0x41, 1); set => SetFlag(0x41, 1, value); }
+ public bool RibbonMarkPumpedUp { get => GetFlag(0x41, 2); set => SetFlag(0x41, 2, value); }
+ public bool RibbonMarkZeroEnergy { get => GetFlag(0x41, 3); set => SetFlag(0x41, 3, value); }
+ public bool RibbonMarkPrideful { get => GetFlag(0x41, 4); set => SetFlag(0x41, 4, value); }
+ public bool RibbonMarkUnsure { get => GetFlag(0x41, 5); set => SetFlag(0x41, 5, value); }
+ public bool RibbonMarkHumble { get => GetFlag(0x41, 6); set => SetFlag(0x41, 6, value); }
+ public bool RibbonMarkThorny { get => GetFlag(0x41, 7); set => SetFlag(0x41, 7, value); }
+
+ public bool RibbonMarkVigor { get => GetFlag(0x42, 0); set => SetFlag(0x42, 0, value); }
+ public bool RibbonMarkSlump { get => GetFlag(0x42, 1); set => SetFlag(0x42, 1, value); }
+ public bool RibbonPioneer { get => GetFlag(0x42, 2); set => SetFlag(0x42, 2, value); }
+ public bool RibbonTwinklingStar { get => GetFlag(0x42, 3); set => SetFlag(0x42, 3, value); }
+ public bool RIB44_4 { get => GetFlag(0x42, 4); set => SetFlag(0x42, 4, value); }
+ public bool RIB44_5 { get => GetFlag(0x42, 5); set => SetFlag(0x42, 5, value); }
+ public bool RIB44_6 { get => GetFlag(0x42, 6); set => SetFlag(0x42, 6, value); }
+ public bool RIB44_7 { get => GetFlag(0x42, 7); set => SetFlag(0x42, 7, value); }
+
+ public bool RIB45_0 { get => GetFlag(0x43, 0); set => SetFlag(0x43, 0, value); }
+ public bool RIB45_1 { get => GetFlag(0x43, 1); set => SetFlag(0x43, 1, value); }
+ public bool RIB45_2 { get => GetFlag(0x43, 2); set => SetFlag(0x43, 2, value); }
+ public bool RIB45_3 { get => GetFlag(0x43, 3); set => SetFlag(0x43, 3, value); }
+ public bool RIB45_4 { get => GetFlag(0x43, 4); set => SetFlag(0x43, 4, value); }
+ public bool RIB45_5 { get => GetFlag(0x43, 5); set => SetFlag(0x43, 5, value); }
+ public bool RIB45_6 { get => GetFlag(0x43, 6); set => SetFlag(0x43, 6, value); }
+ public bool RIB45_7 { get => GetFlag(0x43, 7); set => SetFlag(0x43, 7, value); }
+
+ public bool RIB46_0 { get => GetFlag(0x44, 0); set => SetFlag(0x44, 0, value); }
+ public bool RIB46_1 { get => GetFlag(0x44, 1); set => SetFlag(0x44, 1, value); }
+ public bool RIB46_2 { get => GetFlag(0x44, 2); set => SetFlag(0x44, 2, value); }
+ public bool RIB46_3 { get => GetFlag(0x44, 3); set => SetFlag(0x44, 3, value); }
+ public bool RIB46_4 { get => GetFlag(0x44, 4); set => SetFlag(0x44, 4, value); }
+ public bool RIB46_5 { get => GetFlag(0x44, 5); set => SetFlag(0x44, 5, value); }
+ public bool RIB46_6 { get => GetFlag(0x44, 6); set => SetFlag(0x44, 6, value); }
+ public bool RIB46_7 { get => GetFlag(0x44, 7); set => SetFlag(0x44, 7, value); }
+
+ public bool RIB47_0 { get => GetFlag(0x45, 0); set => SetFlag(0x45, 0, value); }
+ public bool RIB47_1 { get => GetFlag(0x45, 1); set => SetFlag(0x45, 1, value); }
+ public bool RIB47_2 { get => GetFlag(0x45, 2); set => SetFlag(0x45, 2, value); }
+ public bool RIB47_3 { get => GetFlag(0x45, 3); set => SetFlag(0x45, 3, value); }
+ public bool RIB47_4 { get => GetFlag(0x45, 4); set => SetFlag(0x45, 4, value); }
+ public bool RIB47_5 { get => GetFlag(0x45, 5); set => SetFlag(0x45, 5, value); }
+ public bool RIB47_6 { get => GetFlag(0x45, 6); set => SetFlag(0x45, 6, value); }
+ public bool RIB47_7 { get => GetFlag(0x45, 7); set => SetFlag(0x45, 7, value); }
+
+ public bool HasMark()
+ {
+ var d = Data.AsSpan();
+ if ((ReadUInt16LittleEndian(d[0x3A..]) & 0xFFE0) != 0)
+ return true;
+ if (ReadUInt32LittleEndian(d[0x3E..]) != 0)
+ return true;
+ return (d[0x42] & 3) != 0;
+ }
+
+ public byte HeightScalar { get => Data[Offset + 0x46]; set => Data[Offset + 0x46] = value; }
+ public byte WeightScalar { get => Data[Offset + 0x47]; set => Data[Offset + 0x47] = value; }
+
+ public Span Nickname_Trash => Data.AsSpan(Offset + 0x48, 26);
+
+ public string Nickname
+ {
+ get => StringConverter8.GetString(Nickname_Trash);
+ set => StringConverter8.SetString(Nickname_Trash, value.AsSpan(), 12, StringConverterOption.None);
+ }
+
+ public int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x62)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x62), (ushort)value); }
+ public int IV_HP { get => Data[Offset + 0x64]; set => Data[Offset + 0x64] = (byte)value; }
+ public int IV_ATK { get => Data[Offset + 0x65]; set => Data[Offset + 0x65] = (byte)value; }
+ public int IV_DEF { get => Data[Offset + 0x66]; set => Data[Offset + 0x66] = (byte)value; }
+ public int IV_SPE { get => Data[Offset + 0x67]; set => Data[Offset + 0x67] = (byte)value; }
+ public int IV_SPA { get => Data[Offset + 0x68]; set => Data[Offset + 0x68] = (byte)value; }
+ public int IV_SPD { get => Data[Offset + 0x69]; set => Data[Offset + 0x69] = (byte)value; }
+ public bool IsEgg { get => Data[Offset + 0x6A] != 0; set => Data[Offset + 0x6A] = (byte)(value ? 1 : 0); }
+ public bool IsNicknamed { get => Data[Offset + 0x6B] != 0; set => Data[Offset + 0x6B] = (byte)(value ? 1 : 0); }
+ public int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x6C)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x6C), value); }
+ public Span HT_Trash => Data.AsSpan(Offset + 0x70, 26);
+ public string HT_Name
+ {
+ get => StringConverter8.GetString(HT_Trash);
+ set => StringConverter8.SetString(HT_Trash, value.AsSpan(), 12, StringConverterOption.None);
+ }
+ public int HT_Gender { get => Data[Offset + 0x8A]; set => Data[Offset + 0x8A] = (byte)value; }
+ public byte HT_Language { get => Data[Offset + 0x8B]; set => Data[Offset + 0x8B] = value; }
+ public int CurrentHandler { get => Data[Offset + 0x8C]; set => Data[Offset + 0x8C] = (byte)value; }
+ public int HT_TrainerID { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x8D)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x8D), (ushort)value); } // unused?
+ public int HT_Friendship { get => Data[Offset + 0x8F]; set => Data[Offset + 0x8F] = (byte)value; }
+ public byte HT_Intensity { get => Data[Offset + 0x90]; set => Data[Offset + 0x90] = value; }
+ public byte HT_Memory { get => Data[Offset + 0x91]; set => Data[Offset + 0x91] = value; }
+ public byte HT_Feeling { get => Data[Offset + 0x92]; set => Data[Offset + 0x92] = value; }
+ public ushort HT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x93)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x93), value); }
+ public int Version { get => Data[Offset + 0x95]; set => Data[Offset + 0x95] = (byte)value; }
+ public byte BattleVersion { get => Data[Offset + 0x96]; set => Data[Offset + 0x96] = value; }
+ public int Language { get => Data[Offset + 0x97]; set => Data[Offset + 0x97] = (byte)value; }
+ public uint FormArgument { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x98)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x98), value); }
+ public byte FormArgumentRemain { get => (byte)FormArgument; set => FormArgument = (FormArgument & ~0xFFu) | value; }
+ public byte FormArgumentElapsed { get => (byte)(FormArgument >> 8); set => FormArgument = (FormArgument & ~0xFF00u) | (uint)(value << 8); }
+ public byte FormArgumentMaximum { get => (byte)(FormArgument >> 16); set => FormArgument = (FormArgument & ~0xFF0000u) | (uint)(value << 16); }
+ public sbyte AffixedRibbon { get => (sbyte)Data[Offset + 0x9C]; set => Data[Offset + 0x9C] = (byte)value; } // selected ribbon
+ public Span OT_Trash => Data.AsSpan(Offset + 0x9D, 26);
+ public string OT_Name
+ {
+ get => StringConverter8.GetString(OT_Trash);
+ set => StringConverter8.SetString(OT_Trash, value.AsSpan(), 12, StringConverterOption.None);
+ }
+ public int OT_Friendship { get => Data[Offset + 0xB7]; set => Data[Offset + 0xB7] = (byte)value; }
+ public byte OT_Intensity { get => Data[Offset + 0xB8]; set => Data[Offset + 0xB8] = value; }
+ public byte OT_Memory { get => Data[Offset + 0xB9]; set => Data[Offset + 0xB9] = value; }
+ public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xBA)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xBA), value); }
+ public byte OT_Feeling { get => Data[Offset + 0xBC]; set => Data[Offset + 0xBC] = value; }
+ public int Egg_Year { get => Data[Offset + 0xBD]; set => Data[Offset + 0xBD] = (byte)value; }
+ public int Egg_Month { get => Data[Offset + 0xBE]; set => Data[Offset + 0xBE] = (byte)value; }
+ public int Egg_Day { get => Data[Offset + 0xBF]; set => Data[Offset + 0xBF] = (byte)value; }
+ public int Met_Year { get => Data[Offset + 0xC0]; set => Data[Offset + 0xC0] = (byte)value; }
+ public int Met_Month { get => Data[Offset + 0xC1]; set => Data[Offset + 0xC1] = (byte)value; }
+ public int Met_Day { get => Data[Offset + 0xC2]; set => Data[Offset + 0xC2] = (byte)value; }
+ public int Met_Level { get => Data[Offset + 0xC3]; set => Data[Offset + 0xC3] = (byte)value; }
+ public int OT_Gender { get => Data[Offset + 0xC4]; set => Data[Offset + 0xC4] = (byte)value; }
+ public byte HyperTrainFlags { get => Data[Offset + 0xC5]; set => Data[Offset + 0xC5] = value; }
+ public bool HT_HP { get => ((HyperTrainFlags >> 0) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 0)) | ((value ? 1 : 0) << 0)); }
+ public bool HT_ATK { get => ((HyperTrainFlags >> 1) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 1)) | ((value ? 1 : 0) << 1)); }
+ public bool HT_DEF { get => ((HyperTrainFlags >> 2) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 2)) | ((value ? 1 : 0) << 2)); }
+ public bool HT_SPA { get => ((HyperTrainFlags >> 3) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 3)) | ((value ? 1 : 0) << 3)); }
+ public bool HT_SPD { get => ((HyperTrainFlags >> 4) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 4)) | ((value ? 1 : 0) << 4)); }
+ public bool HT_SPE { get => ((HyperTrainFlags >> 5) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 5)) | ((value ? 1 : 0) << 5)); }
+
+ public int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xC6)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xC6), (ushort)value); }
+
+ public void CopyTo(PKM pk)
+ {
+ pk.EncryptionConstant = EncryptionConstant;
+ pk.PID = PID;
+ pk.Species = Species;
+ pk.Form = Form;
+ pk.Gender = Gender;
+ pk.TID = TID;
+ pk.SID = SID;
+ pk.EXP = EXP;
+ pk.Ability = Ability;
+ pk.AbilityNumber = AbilityNumber;
+ pk.MarkValue = MarkValue;
+ pk.Nature = Nature;
+ pk.StatNature = StatNature;
+ pk.FatefulEncounter = FatefulEncounter;
+ pk.HeldItem = HeldItem;
+ pk.IV_HP = IV_HP;
+ pk.IV_ATK = IV_ATK;
+ pk.IV_DEF = IV_DEF;
+ pk.IV_SPE = IV_SPE;
+ pk.IV_SPA = IV_SPA;
+ pk.IV_SPD = IV_SPD;
+ pk.IsEgg = IsEgg;
+ pk.IsNicknamed = IsNicknamed;
+ pk.EV_HP = EV_HP;
+ pk.EV_ATK = EV_ATK;
+ pk.EV_DEF = EV_DEF;
+ pk.EV_SPE = EV_SPE;
+ pk.EV_SPA = EV_SPA;
+ pk.EV_SPD = EV_SPD;
+ pk.PKRS_Strain = PKRS_Strain;
+ pk.PKRS_Days = PKRS_Days;
+
+ pk.HT_Gender = HT_Gender;
+ pk.CurrentHandler = CurrentHandler;
+ // pk.HT_TrainerID
+ pk.HT_Friendship = HT_Friendship;
+
+ pk.Version = Version;
+ pk.Language = Language;
+
+ pk.OT_Friendship = OT_Friendship;
+ pk.Egg_Year = Egg_Year;
+ pk.Egg_Month = Egg_Month;
+ pk.Egg_Day = Egg_Day;
+ pk.Met_Year = Met_Year;
+ pk.Met_Month = Met_Month;
+ pk.Met_Day = Met_Day;
+ pk.Met_Level = Met_Level;
+ pk.OT_Gender = OT_Gender;
+
+ CopyConditionalInterface(pk);
+
+ OT_Trash.CopyTo(pk.OT_Trash);
+ Nickname_Trash.CopyTo(pk.Nickname_Trash);
+ HT_Trash.CopyTo(pk.HT_Trash);
+
+ CopyConditionalRibbonMark(pk);
+ }
+
+ private void CopyConditionalInterface(PKM pk)
+ {
+ if (pk is IScaledSize ss)
+ {
+ ss.HeightScalar = HeightScalar;
+ ss.WeightScalar = WeightScalar;
+ }
+
+ if (pk is IMemoryOT ot)
+ {
+ ot.OT_Intensity = OT_Intensity;
+ ot.OT_Memory = OT_Memory;
+ ot.OT_TextVar = OT_TextVar;
+ ot.OT_Feeling = OT_Feeling;
+ }
+ if (pk is IMemoryHT hm)
+ {
+ hm.HT_Intensity = HT_Intensity;
+ hm.HT_Memory = HT_Memory;
+ hm.HT_Feeling = HT_Feeling;
+ hm.HT_TextVar = HT_TextVar;
+ }
+ if (pk is IHandlerLanguage hl)
+ hl.HT_Language = HT_Language;
+
+ if (pk is IContestStatsMutable cm)
+ this.CopyContestStatsTo(cm);
+ if (pk is IRibbonSetAffixed affix)
+ affix.AffixedRibbon = AffixedRibbon;
+ if (pk is IHyperTrain ht)
+ ht.HyperTrainFlags = HyperTrainFlags;
+ if (pk is IFormArgument fa)
+ fa.FormArgument = FormArgument;
+ if (pk is IBattleVersion bv)
+ bv.BattleVersion = BattleVersion;
+ if (pk is IFavorite fav)
+ fav.Favorite = Favorite;
+ if (pk is IHomeTrack home)
+ home.Tracker = Tracker;
+ }
+
+ private void CopyConditionalRibbonMark(PKM pk)
+ {
+ if (pk is IRibbonSetEvent3 e3)
+ this.CopyRibbonSetEvent3(e3);
+ if (pk is IRibbonSetEvent4 e4)
+ this.CopyRibbonSetEvent4(e4);
+ if (pk is IRibbonSetCommon3 c3)
+ this.CopyRibbonSetCommon3(c3);
+ if (pk is IRibbonSetCommon4 c4)
+ this.CopyRibbonSetCommon4(c4);
+ if (pk is IRibbonSetCommon6 c6)
+ this.CopyRibbonSetCommon6(c6);
+ if (pk is IRibbonSetCommon7 c7)
+ this.CopyRibbonSetCommon7(c7);
+ if (pk is IRibbonSetCommon8 c8)
+ this.CopyRibbonSetCommon8(c8);
+ if (pk is IRibbonSetMark8 m8)
+ this.CopyRibbonSetMark8(m8);
+ }
+}
diff --git a/PKHeX.Core/PKM/HOME/GameDataPA8.cs b/PKHeX.Core/PKM/HOME/GameDataPA8.cs
new file mode 100644
index 000000000..dc7c79c6e
--- /dev/null
+++ b/PKHeX.Core/PKM/HOME/GameDataPA8.cs
@@ -0,0 +1,166 @@
+using System;
+using static System.Buffers.Binary.BinaryPrimitives;
+
+namespace PKHeX.Core;
+
+public sealed class GameDataPA8 : IGameDataSide, IScaledSizeAbsolute
+{
+ // Internal Attributes set on creation
+ public readonly byte[] Data; // Raw Storage
+ public readonly int Offset;
+
+ private const int SIZE = HomeCrypto.SIZE_1GAME_PA8;
+ private const HomeGameDataFormat Format = HomeGameDataFormat.PA8;
+
+ public GameDataPA8 Clone() => new(Data.AsSpan(Offset, SIZE).ToArray());
+
+ public GameDataPA8()
+ {
+ Data = new byte[SIZE];
+ Data[0] = (byte)Format;
+ WriteUInt16LittleEndian(Data.AsSpan(1, 2), SIZE);
+ }
+
+ public GameDataPA8(byte[] data, int offset = 0)
+ {
+ if ((HomeGameDataFormat)data[offset] != Format)
+ throw new ArgumentException($"Invalid GameDataFormat for {Format}");
+
+ if (ReadUInt16LittleEndian(data.AsSpan(offset + 1)) != SIZE)
+ throw new ArgumentException($"Invalid GameDataSize for {Format}");
+
+ Data = data;
+ Offset = offset + 3;
+ }
+
+ public bool IsAlpha { get => Data[Offset + 0x00] != 0; set => Data[Offset + 0x00] = (byte)(value ? 1 : 0); }
+ public bool IsNoble { get => Data[Offset + 0x01] != 0; set => Data[Offset + 0x01] = (byte)(value ? 1 : 0); }
+ public ushort AlphaMove { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x02)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x02), value); }
+ public byte HeightScalarCopy { get => Data[Offset + 0x04]; set => Data[Offset + 0x04] = value; }
+
+ public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x05)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x05), (ushort)value); }
+ public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x07)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x07), (ushort)value); }
+ public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x09)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x09), (ushort)value); }
+ public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0B)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0B), (ushort)value); }
+
+ public int Move1_PP { get => Data[Offset + 0x0D]; set => Data[Offset + 0x0D] = (byte)value; }
+ public int Move2_PP { get => Data[Offset + 0x0E]; set => Data[Offset + 0x0E] = (byte)value; }
+ public int Move3_PP { get => Data[Offset + 0x0F]; set => Data[Offset + 0x0F] = (byte)value; }
+ public int Move4_PP { get => Data[Offset + 0x10]; set => Data[Offset + 0x10] = (byte)value; }
+ public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x11)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x11), (ushort)value); }
+ public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x13)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x13), (ushort)value); }
+ public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x15)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x15), (ushort)value); }
+ public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x17)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x17), (ushort)value); }
+ public byte GV_HP { get => Data[Offset + 0x19]; set => Data[Offset + 0x19] = value; }
+ public byte GV_ATK { get => Data[Offset + 0x1A]; set => Data[Offset + 0x1A] = value; }
+ public byte GV_DEF { get => Data[Offset + 0x1B]; set => Data[Offset + 0x1B] = value; }
+ public byte GV_SPE { get => Data[Offset + 0x1C]; set => Data[Offset + 0x1C] = value; }
+ public byte GV_SPA { get => Data[Offset + 0x1D]; set => Data[Offset + 0x1D] = value; }
+ public byte GV_SPD { get => Data[Offset + 0x1E]; set => Data[Offset + 0x1E] = value; }
+ public float HeightAbsolute { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x1F)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x1F), value); }
+ public float WeightAbsolute { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x23)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x23), value); }
+ public int Ball { get => Data[Offset + 0x27]; set => Data[Offset + 0x27] = (byte)value; }
+
+ public bool GetPurchasedRecordFlag(int index)
+ {
+ if ((uint)index > 63) // 8 bytes, 8 bits
+ throw new ArgumentOutOfRangeException(nameof(index));
+ int ofs = index >> 3;
+ return FlagUtil.GetFlag(Data, Offset + 0x28 + ofs, index & 7);
+ }
+
+ public void SetPurchasedRecordFlag(int index, bool value)
+ {
+ if ((uint)index > 63) // 8 bytes, 8 bits
+ throw new ArgumentOutOfRangeException(nameof(index));
+ int ofs = index >> 3;
+ FlagUtil.SetFlag(Data, Offset + 0x28 + ofs, index & 7, value);
+ }
+
+ public bool GetPurchasedRecordFlagAny() => Array.FindIndex(Data, Offset + 0x28, 8, z => z != 0) >= 0;
+
+ public int GetPurchasedCount()
+ {
+ var value = ReadUInt64LittleEndian(Data.AsSpan(0x155));
+ ulong result = 0;
+ for (int i = 0; i < 64; i++)
+ result += ((value >> i) & 1);
+ return (int)result;
+ }
+
+ public bool GetMasteredRecordFlag(int index)
+ {
+ if ((uint)index > 63) // 8 bytes, 8 bits
+ throw new ArgumentOutOfRangeException(nameof(index));
+ int ofs = index >> 3;
+ return FlagUtil.GetFlag(Data, Offset + 0x30 + ofs, index & 7);
+ }
+
+ public void SetMasteredRecordFlag(int index, bool value)
+ {
+ if ((uint)index > 63) // 8 bytes, 8 bits
+ throw new ArgumentOutOfRangeException(nameof(index));
+ int ofs = index >> 3;
+ FlagUtil.SetFlag(Data, Offset + 0x30 + ofs, index & 7, value);
+ }
+
+ public bool GetMasteredRecordFlagAny() => Array.FindIndex(Data, Offset + 0x30, 8, z => z != 0) >= 0;
+
+ public int Egg_Location { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x38)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x38), (ushort)value); }
+ public int Met_Location { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x3A)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x3A), (ushort)value); }
+
+ // Not stored.
+ public PersonalInfo GetPersonalInfo(int species, int form) => PersonalTable.LA.GetFormEntry(species, form);
+ public int Move1_PPUps { get => 0; set { } }
+ public int Move2_PPUps { get => 0; set { } }
+ public int Move3_PPUps { get => 0; set { } }
+ public int Move4_PPUps { get => 0; set { } }
+
+ public int CopyTo(Span result)
+ {
+ result[0] = (byte)Format;
+ WriteUInt16LittleEndian(result[1..], SIZE);
+ Data.AsSpan(Offset, SIZE).CopyTo(result[3..]);
+ return 3 + SIZE;
+ }
+
+ public void CopyTo(PA8 pk)
+ {
+ ((IGameDataSide)this).CopyTo(pk);
+ pk.IsAlpha = IsAlpha;
+ pk.IsNoble = IsNoble;
+ pk.AlphaMove = AlphaMove;
+ pk.HeightScalarCopy = HeightScalarCopy;
+ pk.HeightAbsolute = pk.CalcHeightAbsolute; // Ignore the stored value, be nice and recalculate for the user.
+ pk.WeightAbsolute = pk.CalcWeightAbsolute; // Ignore the stored value, be nice and recalculate for the user.
+ pk.GV_HP = GV_HP;
+ pk.GV_ATK = GV_ATK;
+ pk.GV_DEF = GV_DEF;
+ pk.GV_SPE = GV_SPE;
+ pk.GV_SPA = GV_SPA;
+ pk.GV_SPD = GV_SPD;
+ Data.AsSpan(Offset + 0x28, 8 + 8).CopyTo(pk.Data.AsSpan(0x155)); // Copy both bitflag regions
+ }
+
+ public PKM ConvertToPKM(PKH pkh) => ConvertToPA8(pkh);
+
+ public PA8 ConvertToPA8(PKH pkh)
+ {
+ var pk = new PA8();
+ pkh.CopyTo(pk);
+ CopyTo(pk);
+ return pk;
+ }
+
+ /// Reconstructive logic to best apply suggested values.
+ public static GameDataPA8? TryCreate(PKH pkh)
+ {
+ if (pkh.DataPB7 is { } x)
+ return GameDataPB7.Create(x);
+ if (pkh.DataPB8 is { } b)
+ return GameDataPB8.Create(b);
+ if (pkh.DataPK8 is { } c)
+ return new GameDataPA8 { Ball = c.Met_Location == Locations.HOME_SWLA ? (int)Core.Ball.LAPoke : c.Ball, Met_Location = c.Met_Location, Egg_Location = c.Egg_Location };
+ return null;
+ }
+}
diff --git a/PKHeX.Core/PKM/HOME/GameDataPB7.cs b/PKHeX.Core/PKM/HOME/GameDataPB7.cs
new file mode 100644
index 000000000..834e8d8d0
--- /dev/null
+++ b/PKHeX.Core/PKM/HOME/GameDataPB7.cs
@@ -0,0 +1,165 @@
+using System;
+using static System.Buffers.Binary.BinaryPrimitives;
+
+namespace PKHeX.Core;
+
+public sealed class GameDataPB7 : IGameDataSide, IScaledSizeAbsolute, IMemoryOT
+{
+ // Internal Attributes set on creation
+ public readonly byte[] Data; // Raw Storage
+ public readonly int Offset;
+
+ private const int SIZE = HomeCrypto.SIZE_1GAME_PB7;
+ private const HomeGameDataFormat Format = HomeGameDataFormat.PB7;
+
+ public GameDataPB7 Clone() => new(Data.AsSpan(Offset, SIZE).ToArray());
+
+ public GameDataPB7()
+ {
+ Data = new byte[SIZE];
+ Data[0] = (byte)Format;
+ WriteUInt16LittleEndian(Data.AsSpan(1, 2), SIZE);
+ }
+
+ public GameDataPB7(byte[] data, int offset = 0)
+ {
+ if ((HomeGameDataFormat)data[offset] != Format)
+ throw new ArgumentException($"Invalid GameDataFormat for {Format}");
+
+ if (ReadUInt16LittleEndian(data.AsSpan(offset + 1)) != SIZE)
+ throw new ArgumentException($"Invalid GameDataSize for {Format}");
+
+ Data = data;
+ Offset = offset + 3;
+ }
+
+ public byte AV_HP { get => Data[Offset + 0x00]; set => Data[Offset + 0x00] = value; }
+ public byte AV_ATK { get => Data[Offset + 0x01]; set => Data[Offset + 0x01] = value; }
+ public byte AV_DEF { get => Data[Offset + 0x02]; set => Data[Offset + 0x02] = value; }
+ public byte AV_SPE { get => Data[Offset + 0x03]; set => Data[Offset + 0x03] = value; }
+ public byte AV_SPA { get => Data[Offset + 0x04]; set => Data[Offset + 0x04] = value; }
+ public byte AV_SPD { get => Data[Offset + 0x05]; set => Data[Offset + 0x05] = value; }
+ public byte ResortEventState { get => Data[Offset + 0x06]; set => Data[Offset + 0x06] = value; }
+
+ public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x07)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x07), (ushort)value); }
+ public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x09)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x09), (ushort)value); }
+ public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0B)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0B), (ushort)value); }
+ public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0D)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0D), (ushort)value); }
+
+ public int Move1_PP { get => Data[Offset + 0x0F]; set => Data[Offset + 0x0F] = (byte)value; }
+ public int Move2_PP { get => Data[Offset + 0x10]; set => Data[Offset + 0x10] = (byte)value; }
+ public int Move3_PP { get => Data[Offset + 0x11]; set => Data[Offset + 0x11] = (byte)value; }
+ public int Move4_PP { get => Data[Offset + 0x12]; set => Data[Offset + 0x12] = (byte)value; }
+ public int Move1_PPUps { get => Data[Offset + 0x13]; set => Data[Offset + 0x13] = (byte)value; }
+ public int Move2_PPUps { get => Data[Offset + 0x14]; set => Data[Offset + 0x14] = (byte)value; }
+ public int Move3_PPUps { get => Data[Offset + 0x15]; set => Data[Offset + 0x15] = (byte)value; }
+ public int Move4_PPUps { get => Data[Offset + 0x16]; set => Data[Offset + 0x16] = (byte)value; }
+
+ public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x17)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x17), (ushort)value); }
+ public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x19)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x19), (ushort)value); }
+ public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x1B)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x1B), (ushort)value); }
+ public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x1D)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x1D), (ushort)value); }
+ public float HeightAbsolute { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x1F)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x1F), value); }
+ public float WeightAbsolute { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x23)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x23), value); }
+
+ public byte FieldEventFatigue1 { get => Data[Offset + 0x27]; set => Data[Offset + 0x27] = value; }
+ public byte FieldEventFatigue2 { get => Data[Offset + 0x28]; set => Data[Offset + 0x28] = value; }
+ public byte Fullness { get => Data[Offset + 0x29]; set => Data[Offset + 0x29] = value; }
+ public byte Rank { get => Data[Offset + 0x2A]; set => Data[Offset + 0x2A] = value; }
+ public int OT_Affection { get => Data[Offset + 0x2B]; set => Data[Offset + 0x2B] = (byte)value; }
+ public byte OT_Intensity { get => Data[Offset + 0x2C]; set => Data[Offset + 0x2C] = value; }
+ public byte OT_Memory { get => Data[Offset + 0x2D]; set => Data[Offset + 0x2D] = value; }
+ public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x2E)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x2E), value); }
+ public byte OT_Feeling { get => Data[Offset + 0x30]; set => Data[Offset + 0x30] = value; }
+ public byte Enjoyment { get => Data[Offset + 0x31]; set => Data[Offset + 0x31] = value; }
+ public uint GeoPadding { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x32)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x32), value); }
+ public int Ball { get => Data[Offset + 0x36]; set => Data[Offset + 0x36] = (byte)value; }
+ public int Egg_Location { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x37)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x37), (ushort)value); }
+ public int Met_Location { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x39)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x39), (ushort)value); }
+
+ // Not stored.
+ public PersonalInfo GetPersonalInfo(int species, int form) => PersonalTable.GG.GetFormEntry(species, form);
+
+ public int CopyTo(Span result)
+ {
+ result[0] = (byte)Format;
+ WriteUInt16LittleEndian(result[1..], SIZE);
+ Data.AsSpan(Offset, SIZE).CopyTo(result[3..]);
+ return 3 + SIZE;
+ }
+
+ public void CopyTo(PB7 pk)
+ {
+ ((IGameDataSide)this).CopyTo(pk);
+ pk.AV_HP = AV_HP;
+ pk.AV_ATK = AV_ATK;
+ pk.AV_DEF = AV_DEF;
+ pk.AV_SPE = AV_SPE;
+ pk.AV_SPA = AV_SPA;
+ pk.AV_SPD = AV_SPD;
+ pk.ResortEventStatus = (ResortEventState)ResortEventState;
+ pk.HeightAbsolute = pk.CalcHeightAbsolute; // Ignore the stored value, be nice and recalculate for the user.
+ pk.WeightAbsolute = pk.CalcWeightAbsolute; // Ignore the stored value, be nice and recalculate for the user.
+
+ // Some fields are unused as PB7, don't bother copying.
+ pk.FieldEventFatigue1 = FieldEventFatigue1;
+ pk.FieldEventFatigue2 = FieldEventFatigue2;
+ pk.Fullness = Fullness;
+ // pk.Rank = Rank;
+ // pk.OT_Affection
+ // pk.OT_Intensity
+ // pk.OT_Memory
+ // pk.OT_TextVar
+ // pk.OT_Feeling
+ pk.Enjoyment = Enjoyment;
+ // pk.GeoPadding = GeoPadding;
+ }
+
+ public PKM ConvertToPKM(PKH pkh) => ConvertToPB7(pkh);
+
+ public PB7 ConvertToPB7(PKH pkh)
+ {
+ var pk = new PB7();
+ pkh.CopyTo(pk);
+ CopyTo(pk);
+ return pk;
+ }
+
+ /// Reconstructive logic to best apply suggested values.
+ public static GameDataPB7? TryCreate(PKH pkh)
+ {
+ int met = 0;
+ int ball = (int)Core.Ball.Poke;
+ if (pkh.DataPK8 is { } x)
+ {
+ met = x.Met_Location;
+ ball = x.Ball;
+ }
+ else if (pkh.DataPB8 is { } y)
+ {
+ met = y.Met_Location;
+ ball = y.Ball;
+ }
+ else if (pkh.DataPA8 is { } z)
+ {
+ met = z.Met_Location;
+ ball = z.Ball;
+ }
+ if (met == 0)
+ return null;
+
+ if (pkh.Version is (int)GameVersion.GO)
+ return new GameDataPB7 { Ball = ball, Met_Location = Locations.GO7 };
+ if (pkh.Version is (int)GameVersion.GP or (int)GameVersion.GE)
+ return new GameDataPB7 { Ball = ball, Met_Location = met };
+
+ return null;
+ }
+
+ public static T Create(GameDataPB7 data) where T : IGameDataSide, new() => new()
+ {
+ Ball = data.Ball,
+ Met_Location = data.Met_Location,
+ Egg_Location = data.Egg_Location,
+ };
+}
diff --git a/PKHeX.Core/PKM/HOME/GameDataPB8.cs b/PKHeX.Core/PKM/HOME/GameDataPB8.cs
new file mode 100644
index 000000000..36e6cbdb7
--- /dev/null
+++ b/PKHeX.Core/PKM/HOME/GameDataPB8.cs
@@ -0,0 +1,128 @@
+using System;
+using static System.Buffers.Binary.BinaryPrimitives;
+
+namespace PKHeX.Core;
+
+public sealed class GameDataPB8 : IGameDataSide
+{
+ // Internal Attributes set on creation
+ public readonly byte[] Data; // Raw Storage
+ public readonly int Offset;
+
+ private const int SIZE = HomeCrypto.SIZE_1GAME_PB8;
+ private const HomeGameDataFormat Format = HomeGameDataFormat.PB8;
+
+ public GameDataPB8 Clone() => new(Data.AsSpan(Offset, SIZE).ToArray());
+
+ public GameDataPB8()
+ {
+ Data = new byte[SIZE];
+ Data[0] = (byte)Format;
+ WriteUInt16LittleEndian(Data.AsSpan(1, 2), SIZE);
+ }
+
+ public GameDataPB8(byte[] data, int offset = 0)
+ {
+ if ((HomeGameDataFormat)data[offset] != Format)
+ throw new ArgumentException($"Invalid GameDataFormat for {Format}");
+
+ if (ReadUInt16LittleEndian(data.AsSpan(offset + 1)) != SIZE)
+ throw new ArgumentException($"Invalid GameDataSize for {Format}");
+
+ Data = data;
+ Offset = offset + 3;
+ }
+
+ public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x00), (ushort)value); }
+ public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x02)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x02), (ushort)value); }
+ public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x04), (ushort)value); }
+ public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x06)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x06), (ushort)value); }
+
+ public int Move1_PP { get => Data[Offset + 0x08]; set => Data[Offset + 0x08] = (byte)value; }
+ public int Move2_PP { get => Data[Offset + 0x09]; set => Data[Offset + 0x09] = (byte)value; }
+ public int Move3_PP { get => Data[Offset + 0x0A]; set => Data[Offset + 0x0A] = (byte)value; }
+ public int Move4_PP { get => Data[Offset + 0x0B]; set => Data[Offset + 0x0B] = (byte)value; }
+ public int Move1_PPUps { get => Data[Offset + 0x0C]; set => Data[Offset + 0x0C] = (byte)value; }
+ public int Move2_PPUps { get => Data[Offset + 0x0D]; set => Data[Offset + 0x0D] = (byte)value; }
+ public int Move3_PPUps { get => Data[Offset + 0x0E]; set => Data[Offset + 0x0E] = (byte)value; }
+ public int Move4_PPUps { get => Data[Offset + 0x0F]; set => Data[Offset + 0x0F] = (byte)value; }
+
+ public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x10)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x10), (ushort)value); }
+ public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x12)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x12), (ushort)value); }
+ public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x14)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x14), (ushort)value); }
+ public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x16)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x16), (ushort)value); }
+ public bool GetMoveRecordFlag(int index)
+ {
+ if ((uint)index > 112) // 14 bytes, 8 bits
+ throw new ArgumentOutOfRangeException(nameof(index));
+ int ofs = index >> 3;
+ return FlagUtil.GetFlag(Data, Offset + 0x18 + ofs, index & 7);
+ }
+
+ public void SetMoveRecordFlag(int index, bool value)
+ {
+ if ((uint)index > 112) // 14 bytes, 8 bits
+ throw new ArgumentOutOfRangeException(nameof(index));
+ int ofs = index >> 3;
+ FlagUtil.SetFlag(Data, Offset + 0x18 + ofs, index & 7, value);
+ }
+
+ public bool GetMoveRecordFlagAny() => Array.FindIndex(Data, Offset + 0x18, 14, z => z != 0) >= 0;
+
+ public int Ball { get => Data[Offset + 0x26]; set => Data[Offset + 0x26] = (byte)value; }
+ public int Egg_Location { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x27)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x27), (ushort)value); }
+ public int Met_Location { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x29)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x29), (ushort)value); }
+
+ // Not stored.
+ public PersonalInfo GetPersonalInfo(int species, int form) => PersonalTable.BDSP.GetFormEntry(species, form);
+
+ public int CopyTo(Span result)
+ {
+ result[0] = (byte)Format;
+ WriteUInt16LittleEndian(result[1..], SIZE);
+ Data.AsSpan(Offset, SIZE).CopyTo(result[3..]);
+ return 3 + SIZE;
+ }
+
+ public void CopyTo(PB8 pk)
+ {
+ ((IGameDataSide)this).CopyTo(pk);
+ // Move Records are not settable in PB8; do not copy even if nonzero (illegal).
+ }
+
+ public PKM ConvertToPKM(PKH pkh) => ConvertToPB8(pkh);
+
+ public PB8 ConvertToPB8(PKH pkh)
+ {
+ var pk = new PB8();
+ pkh.CopyTo(pk);
+ CopyTo(pk);
+ return pk;
+ }
+
+ /// Reconstructive logic to best apply suggested values.
+ public static GameDataPB8? TryCreate(PKH pkh)
+ {
+ if (pkh.DataPB7 is { } x)
+ return Create(x);
+ if (pkh.DataPK8 is { } b)
+ return Create(b);
+ if (pkh.DataPA8 is { } a)
+ return Create(a);
+ return null;
+ }
+
+ public static T Create(GameDataPB8 data) where T : IGameDataSide, new() => new()
+ {
+ Ball = data.Ball,
+ Met_Location = data.Met_Location == Locations.Default8bNone ? 0 : data.Met_Location,
+ Egg_Location = data.Egg_Location == Locations.Default8bNone ? 0 : data.Egg_Location,
+ };
+
+ public static GameDataPB8 Create(IGameDataSide data) => new()
+ {
+ Ball = data.Ball,
+ Met_Location = data.Met_Location == 0 ? Locations.Default8bNone : data.Met_Location,
+ Egg_Location = data.Egg_Location == 0 ? Locations.Default8bNone : data.Egg_Location,
+ };
+}
diff --git a/PKHeX.Core/PKM/HOME/GameDataPK8.cs b/PKHeX.Core/PKM/HOME/GameDataPK8.cs
new file mode 100644
index 000000000..3a6f0b783
--- /dev/null
+++ b/PKHeX.Core/PKM/HOME/GameDataPK8.cs
@@ -0,0 +1,163 @@
+using System;
+using static System.Buffers.Binary.BinaryPrimitives;
+
+namespace PKHeX.Core;
+
+public sealed class GameDataPK8 : IGameDataSide, IGigantamax, IDynamaxLevel, ISociability
+{
+ // Internal Attributes set on creation
+ public readonly byte[] Data; // Raw Storage
+ public readonly int Offset;
+
+ private const int SIZE = HomeCrypto.SIZE_1GAME_PK8;
+ private const HomeGameDataFormat Format = HomeGameDataFormat.PK8;
+
+ public GameDataPK8 Clone() => new(Data.AsSpan(Offset, SIZE).ToArray());
+
+ public GameDataPK8()
+ {
+ Data = new byte[SIZE];
+ Data[0] = (byte)Format;
+ WriteUInt16LittleEndian(Data.AsSpan(1, 2), SIZE);
+ }
+
+ public GameDataPK8(byte[] data, int offset = 0)
+ {
+ if ((HomeGameDataFormat)data[offset] != Format)
+ throw new ArgumentException($"Invalid GameDataFormat for {Format}");
+
+ if (ReadUInt16LittleEndian(data.AsSpan(offset + 1)) != SIZE)
+ throw new ArgumentException($"Invalid GameDataSize for {Format}");
+
+ Data = data;
+ Offset = offset + 3;
+ }
+
+ public bool CanGigantamax { get => Data[Offset + 0x00] != 0; set => Data[Offset + 0x00] = (byte)(value ? 1 : 0); }
+ public uint Sociability { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x01)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x01), value); }
+
+ public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x05)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x05), (ushort)value); }
+ public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x07)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x07), (ushort)value); }
+ public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x09)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x09), (ushort)value); }
+ public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0B)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0B), (ushort)value); }
+
+ public int Move1_PP { get => Data[Offset + 0x0D]; set => Data[Offset + 0x0D] = (byte)value; }
+ public int Move2_PP { get => Data[Offset + 0x0E]; set => Data[Offset + 0x0E] = (byte)value; }
+ public int Move3_PP { get => Data[Offset + 0x0F]; set => Data[Offset + 0x0F] = (byte)value; }
+ public int Move4_PP { get => Data[Offset + 0x10]; set => Data[Offset + 0x10] = (byte)value; }
+ public int Move1_PPUps { get => Data[Offset + 0x11]; set => Data[Offset + 0x11] = (byte)value; }
+ public int Move2_PPUps { get => Data[Offset + 0x12]; set => Data[Offset + 0x12] = (byte)value; }
+ public int Move3_PPUps { get => Data[Offset + 0x13]; set => Data[Offset + 0x13] = (byte)value; }
+ public int Move4_PPUps { get => Data[Offset + 0x14]; set => Data[Offset + 0x14] = (byte)value; }
+
+ public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x15)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x15), (ushort)value); }
+ public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x17)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x17), (ushort)value); }
+ public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x19)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x19), (ushort)value); }
+ public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x1B)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x1B), (ushort)value); }
+ public byte DynamaxLevel { get => Data[Offset + 0x1D]; set => Data[Offset + 0x1D] = value; }
+
+ public bool GetPokeJobFlag(int index)
+ {
+ if ((uint)index > 112) // 14 bytes, 8 bits
+ throw new ArgumentOutOfRangeException(nameof(index));
+ int ofs = index >> 3;
+ return FlagUtil.GetFlag(Data, Offset + 0x1E + ofs, index & 7);
+ }
+
+ public void SetPokeJobFlag(int index, bool value)
+ {
+ if ((uint)index > 112) // 14 bytes, 8 bits
+ throw new ArgumentOutOfRangeException(nameof(index));
+ int ofs = index >> 3;
+ FlagUtil.SetFlag(Data, Offset + 0x1E + ofs, index & 7, value);
+ }
+
+ public bool GetPokeJobFlagAny() => Array.FindIndex(Data, Offset + 0x1E, 14, z => z != 0) >= 0;
+ public byte Fullness { get => Data[Offset + 0x2C]; set => Data[Offset + 0x2C] = value; }
+
+ public bool GetMoveRecordFlag(int index)
+ {
+ if ((uint)index > 112) // 14 bytes, 8 bits
+ throw new ArgumentOutOfRangeException(nameof(index));
+ int ofs = index >> 3;
+ return FlagUtil.GetFlag(Data, Offset + 0x2D + ofs, index & 7);
+ }
+
+ public void SetMoveRecordFlag(int index, bool value)
+ {
+ if ((uint)index > 112) // 14 bytes, 8 bits
+ throw new ArgumentOutOfRangeException(nameof(index));
+ int ofs = index >> 3;
+ FlagUtil.SetFlag(Data, Offset + 0x2D + ofs, index & 7, value);
+ }
+
+ public bool GetMoveRecordFlagAny() => Array.FindIndex(Data, Offset + 0x2D, 14, z => z != 0) >= 0;
+
+ public int Palma { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x3B)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x3B), value); }
+ public int Ball { get => Data[Offset + 0x3F]; set => Data[Offset + 0x3F] = (byte)value; }
+ public int Egg_Location { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x40)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x40), (ushort)value); }
+ public int Met_Location { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x42)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x42), (ushort)value); }
+
+ // Not stored.
+ public PersonalInfo GetPersonalInfo(int species, int form) => PersonalTable.SWSH.GetFormEntry(species, form);
+
+ public int CopyTo(Span result)
+ {
+ result[0] = (byte)Format;
+ WriteUInt16LittleEndian(result[1..], SIZE);
+ Data.AsSpan(Offset, SIZE).CopyTo(result[3..]);
+ return 3 + SIZE;
+ }
+
+ public void CopyTo(PK8 pk)
+ {
+ ((IGameDataSide)this).CopyTo(pk);
+ pk.CanGigantamax = CanGigantamax;
+ pk.Sociability = Sociability;
+ pk.DynamaxLevel = DynamaxLevel;
+ pk.Fullness = Fullness;
+ pk.Palma = Palma;
+ Data.AsSpan(Offset + 0xE, 14).CopyTo(pk.Data.AsSpan(0xCE)); // PokeJob
+ Data.AsSpan(Offset + 0x2D, 14).CopyTo(pk.Data.AsSpan(0x127)); // Move Record
+ }
+
+ public PKM ConvertToPKM(PKH pkh) => ConvertToPK8(pkh);
+
+ public PK8 ConvertToPK8(PKH pkh)
+ {
+ var pk = new PK8();
+ pkh.CopyTo(pk);
+ CopyTo(pk);
+ return pk;
+ }
+
+ /// Reconstructive logic to best apply suggested values.
+ public static GameDataPK8? TryCreate(PKH pkh)
+ {
+ if (pkh.DataPB7 is { } x)
+ return GameDataPB7.Create(x);
+
+ if (pkh.DataPB8 is { } b)
+ {
+ if (pkh.Version is (int)GameVersion.SW or (int)GameVersion.SH && b.Met_Location is not (Locations.HOME_SWLA or Locations.HOME_SWBD or Locations.HOME_SHSP))
+ return new GameDataPK8 { Ball = b.Ball, Met_Location = b.Met_Location, Egg_Location = b.Egg_Location is Locations.Default8bNone ? 0 : b.Egg_Location };
+
+ var ball = b.Ball > (int)Core.Ball.Beast ? 4 : b.Ball;
+ var ver = pkh.Version;
+ var loc = Locations.GetMetSWSH(b.Met_Location, ver);
+ return new GameDataPK8 { Ball = ball, Met_Location = loc, Egg_Location = loc != b.Met_Location ? Locations.HOME_SWSHBDSPEgg : b.Egg_Location };
+ }
+ if (pkh.DataPA8 is { } a)
+ {
+ if (pkh.Version is (int)GameVersion.SW or (int)GameVersion.SH && a.Met_Location is not (Locations.HOME_SWLA or Locations.HOME_SWBD or Locations.HOME_SHSP))
+ return new GameDataPK8 { Ball = a.Ball > (int)Core.Ball.Beast ? 4 : a.Ball, Met_Location = a.Met_Location, Egg_Location = a.Egg_Location is Locations.Default8bNone ? 0 : a.Egg_Location };
+
+ var ball = a.Ball > (int)Core.Ball.Beast ? 4 : a.Ball;
+ var ver = pkh.Version;
+ var loc = Locations.GetMetSWSH(a.Met_Location, ver);
+ return new GameDataPK8 { Ball = ball, Met_Location = loc, Egg_Location = loc != a.Met_Location ? Locations.HOME_SWSHBDSPEgg : a.Egg_Location };
+ }
+
+ return null;
+ }
+}
diff --git a/PKHeX.Core/PKM/HOME/HomeCrypto.cs b/PKHeX.Core/PKM/HOME/HomeCrypto.cs
new file mode 100644
index 000000000..e484f63e7
--- /dev/null
+++ b/PKHeX.Core/PKM/HOME/HomeCrypto.cs
@@ -0,0 +1,150 @@
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
+using static System.Buffers.Binary.BinaryPrimitives;
+
+namespace PKHeX.Core;
+
+///
+/// Logic related to Encrypting and Decrypting Pokémon Home entity data.
+///
+public static class HomeCrypto
+{
+ internal const int Version1 = 1;
+
+ internal const int SIZE_1HEADER = 0x10; // 16
+
+ internal const int SIZE_1CORE = 0xC8; // 200
+
+ internal const int SIZE_1GAME_PB7 = 0x3B; // 59
+ internal const int SIZE_1GAME_PK8 = 0x44; // 68
+ internal const int SIZE_1GAME_PA8 = 0x3C; // 60
+ internal const int SIZE_1GAME_PB8 = 0x2B; // 43
+ internal const int SIZE_1STORED = 0x1EE; // 494
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void GetFormat1EncryptionKey(Span key, ulong seed)
+ {
+ WriteUInt64BigEndian(key, seed ^ 0x6B7B5966193DB88B);
+ WriteUInt64BigEndian(key.Slice(8, 8), seed & 0x937EC53BF8856E87);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void GetFormat1EncryptionIv(Span iv, ulong seed)
+ {
+ WriteUInt64BigEndian(iv, seed ^ 0x5F4ED4E84975D976);
+ WriteUInt64BigEndian(iv.Slice(8, 8), seed | 0xE3CDA917EA9E489C);
+ }
+
+ ///
+ /// Encryption and Decryption are asymmetrical operations, but we reuse the same method and pivot off the inputs.
+ ///
+ /// Data to crypt, not in place.
+ /// Encryption or Decryption mode
+ /// New array with result data.
+ /// if the format is not supported.
+ public static byte[] Crypt1(ReadOnlySpan data, bool decrypt = true)
+ {
+ var format = ReadUInt16LittleEndian(data);
+ if (format != Version1)
+ throw new ArgumentException($"Unrecognized format: {format}");
+
+ ulong seed = ReadUInt64LittleEndian(data.Slice(2, 8));
+
+ var key = new byte[0x10];
+ GetFormat1EncryptionKey(key, seed);
+ var iv = new byte[0x10];
+ GetFormat1EncryptionIv(iv, seed);
+
+ var dataSize = ReadUInt16LittleEndian(data[0xE..0x10]);
+ var result = new byte[SIZE_1HEADER + dataSize];
+ data[..SIZE_1HEADER].CopyTo(result); // header
+ Crypt1(data, key, iv, result, dataSize, decrypt);
+
+ return result;
+ }
+
+ private static void Crypt1(ReadOnlySpan data, byte[] key, byte[] iv, byte[] result, ushort dataSize, bool decrypt)
+ {
+ using var aes = Aes.Create();
+ aes.Mode = CipherMode.CBC;
+ aes.Padding = PaddingMode.None; // Handle PKCS7 manually.
+
+ var tmp = data[SIZE_1HEADER..].ToArray();
+ using var ms = new MemoryStream(tmp);
+ using var transform = decrypt ? aes.CreateDecryptor(key, iv) : aes.CreateEncryptor(key, iv);
+ using var cs = new CryptoStream(ms, transform, CryptoStreamMode.Read);
+
+ var size = cs.Read(result, SIZE_1HEADER, dataSize);
+ System.Diagnostics.Debug.Assert(SIZE_1HEADER + size == data.Length);
+ }
+
+ ///
+ /// Decrypts the input data into a new array if it is encrypted, and updates the reference.
+ ///
+ /// Format encryption check
+ public static void DecryptIfEncrypted(ref byte[] data)
+ {
+ var span = data.AsSpan();
+ var format = ReadUInt16LittleEndian(span);
+ if (format == Version1)
+ {
+ if (GetIsEncrypted1(span))
+ data = Crypt1(span);
+ }
+ else
+ {
+ throw new ArgumentException($"Unrecognized format: {format}");
+ }
+ }
+
+ public static byte[] Encrypt(ReadOnlySpan pkm)
+ {
+ var result = Crypt1(pkm, false);
+ RefreshChecksum(result, result);
+ return result;
+ }
+
+ private static void RefreshChecksum(ReadOnlySpan encrypted, Span dest)
+ {
+ var chk = GetChecksum1(encrypted);
+ WriteUInt32LittleEndian(dest[0xA..0xE], chk);
+ }
+
+ public static uint GetChecksum1(ReadOnlySpan encrypted) => GetCHK(encrypted[SIZE_1HEADER..]);
+
+ public static bool GetIsEncrypted1(ReadOnlySpan data)
+ {
+ if (ReadUInt16LittleEndian(data[SIZE_1HEADER..]) != SIZE_1CORE)
+ return true; // Core length should be constant if decrypted.
+
+ var core = data.Slice(SIZE_1HEADER + 4, SIZE_1CORE);
+ if (ReadUInt16LittleEndian(core[0x9D..]) != 0)
+ return true; // OT_Name final terminator should be 0 if decrypted.
+ if (ReadUInt16LittleEndian(core[0x60..]) != 0)
+ return true; // Nickname final terminator should be 0 if decrypted.
+ if (ReadUInt16LittleEndian(core[0x70..]) != 0)
+ return true; // HT_Name final terminator should be 0 if decrypted.
+
+ //// Fall back to checksum.
+ //return ReadUInt32LittleEndian(data[0xA..0xE]) == GetChecksum1(data);
+ return false; // 64 bits checked is enough to feel safe about this check.
+ }
+
+ ///
+ /// Gets the checksum of an Pokémon's AES-encrypted data.
+ ///
+ /// AES-Encrypted Pokémon data.
+ public static uint GetCHK(ReadOnlySpan data)
+ {
+ uint chk = 0;
+ for (var i = 0; i < data.Length; i += 100)
+ {
+ var chunkSize = Math.Min(data.Length - i, 100);
+ var span = data.Slice(i, chunkSize);
+ chk ^= Checksums.CRC32Invert(span);
+ }
+ return chk;
+ }
+}
diff --git a/PKHeX.Core/PKM/HOME/HomeGameDataFormat.cs b/PKHeX.Core/PKM/HOME/HomeGameDataFormat.cs
new file mode 100644
index 000000000..dc4e58b37
--- /dev/null
+++ b/PKHeX.Core/PKM/HOME/HomeGameDataFormat.cs
@@ -0,0 +1,10 @@
+namespace PKHeX.Core;
+
+public enum HomeGameDataFormat : byte
+{
+ None = 0,
+ PB7 = 1,
+ PK8 = 2,
+ PA8 = 3,
+ PB8 = 4,
+}
diff --git a/PKHeX.Core/PKM/HOME/IGameDataSide.cs b/PKHeX.Core/PKM/HOME/IGameDataSide.cs
new file mode 100644
index 000000000..59fcc5328
--- /dev/null
+++ b/PKHeX.Core/PKM/HOME/IGameDataSide.cs
@@ -0,0 +1,29 @@
+namespace PKHeX.Core;
+
+public interface IGameDataSide
+{
+ int Move1 { get; set; } int Move1_PP { get; set; } int Move1_PPUps { get; set; } int RelearnMove1 { get; set; }
+ int Move2 { get; set; } int Move2_PP { get; set; } int Move2_PPUps { get; set; } int RelearnMove2 { get; set; }
+ int Move3 { get; set; } int Move3_PP { get; set; } int Move3_PPUps { get; set; } int RelearnMove3 { get; set; }
+ int Move4 { get; set; } int Move4_PP { get; set; } int Move4_PPUps { get; set; } int RelearnMove4 { get; set; }
+ int Ball { get; set; }
+ int Met_Location { get; set; }
+ int Egg_Location { get; set; }
+
+ PersonalInfo GetPersonalInfo(int species, int form);
+ PKM ConvertToPKM(PKH pkh);
+}
+
+public static class GameDataSideExtensions
+{
+ public static void CopyTo(this IGameDataSide data, PKM pk)
+ {
+ pk.Move1 = data.Move1; pk.Move1_PP = data.Move1_PP; pk.Move1_PPUps = data.Move1_PPUps; pk.RelearnMove1 = data.RelearnMove1;
+ pk.Move2 = data.Move2; pk.Move2_PP = data.Move1_PP; pk.Move2_PPUps = data.Move2_PPUps; pk.RelearnMove2 = data.RelearnMove2;
+ pk.Move3 = data.Move3; pk.Move3_PP = data.Move1_PP; pk.Move3_PPUps = data.Move3_PPUps; pk.RelearnMove3 = data.RelearnMove3;
+ pk.Move4 = data.Move4; pk.Move4_PP = data.Move1_PP; pk.Move4_PPUps = data.Move4_PPUps; pk.RelearnMove4 = data.RelearnMove4;
+ pk.Ball = data.Ball;
+ pk.Met_Location = data.Met_Location;
+ pk.Egg_Location = data.Egg_Location;
+ }
+}
diff --git a/PKHeX.Core/PKM/HOME/PKH.cs b/PKHeX.Core/PKM/HOME/PKH.cs
new file mode 100644
index 000000000..1920459b2
--- /dev/null
+++ b/PKHeX.Core/PKM/HOME/PKH.cs
@@ -0,0 +1,319 @@
+using System;
+using static System.Buffers.Binary.BinaryPrimitives;
+using static PKHeX.Core.GameVersion;
+using static PKHeX.Core.Locations;
+
+namespace PKHeX.Core;
+
+/// Generation 8 format.
+public class PKH : PKM, IHandlerLanguage, IFormArgument, IHomeTrack, IBattleVersion, ITrainerMemories, IRibbonSetAffixed, IContestStats, IContestStatsMutable, IScaledSize
+{
+ public readonly GameDataCore _coreData;
+ public GameDataPB7? DataPB7 { get; private set; }
+ public GameDataPK8? DataPK8 { get; private set; }
+ public GameDataPA8? DataPA8 { get; private set; }
+ public GameDataPB8? DataPB8 { get; private set; }
+
+ public override EntityContext Context => EntityContext.Invalid;
+
+ public PKH(byte[] data) : base(DecryptHome(data))
+ {
+ _coreData = new GameDataCore(Data, 0x10);
+ ReadGameData(Data, CoreDataSize, GameDataSize);
+ }
+
+ private void ReadGameData(byte[] data, int coreSize, int gameSize)
+ {
+ var baseOfs = 0x14 + coreSize;
+ var offset = 0;
+ while (offset < gameSize)
+ {
+ var fmt = (HomeGameDataFormat)data[baseOfs + offset];
+ switch (fmt)
+ {
+ case HomeGameDataFormat.PB7: DataPB7 = new GameDataPB7(data, baseOfs + offset); break;
+ case HomeGameDataFormat.PK8: DataPK8 = new GameDataPK8(data, baseOfs + offset); break;
+ case HomeGameDataFormat.PA8: DataPA8 = new GameDataPA8(data, baseOfs + offset); break;
+ case HomeGameDataFormat.PB8: DataPB8 = new GameDataPB8(data, baseOfs + offset); break;
+ default: throw new ArgumentException($"Unknown GameData {fmt}");
+ }
+
+ var len = ReadUInt16LittleEndian(data.AsSpan(baseOfs + offset + 1));
+ offset += 3 + len;
+ }
+ }
+
+ private static byte[] DecryptHome(byte[] data)
+ {
+ HomeCrypto.DecryptIfEncrypted(ref data);
+ //Array.Resize(ref data, HomeCrypto.SIZE_1STORED);
+ return data;
+ }
+
+ public ushort DataVersion { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), value); }
+ public ulong EncryptionSeed { get => ReadUInt64LittleEndian(Data.AsSpan(0x02)); set => WriteUInt64LittleEndian(Data.AsSpan(0x02), value); }
+ public uint Checksum { get => ReadUInt32LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt32LittleEndian(Data.AsSpan(0x0A), value); }
+ public ushort EncodedDataSize { get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), value); }
+ public ushort CoreDataSize { get => ReadUInt16LittleEndian(Data.AsSpan(0x10)); set => WriteUInt16LittleEndian(Data.AsSpan(0x10), value); }
+ public ushort GameDataSize { get => ReadUInt16LittleEndian(Data.AsSpan(0x12 + CoreDataSize)); set => WriteUInt16LittleEndian(Data.AsSpan(0x12 + CoreDataSize), value); }
+
+ public override Span Nickname_Trash => _coreData.Nickname_Trash;
+ public override Span OT_Trash => _coreData.OT_Trash;
+ public override Span HT_Trash => _coreData.HT_Trash;
+
+ #region Core
+
+ public ulong Tracker { get => _coreData.Tracker; set => _coreData.Tracker = value; }
+ public override uint EncryptionConstant { get => _coreData.EncryptionConstant; set => _coreData.EncryptionConstant = value; }
+ public bool IsBadEgg { get => _coreData.IsBadEgg; set => _coreData.IsBadEgg = value; }
+ public override int Species { get => _coreData.Species; set => _coreData.Species = value; }
+ public override int TID { get => _coreData.TID; set => _coreData.TID = value; }
+ public override int SID { get => _coreData.SID; set => _coreData.SID = value; }
+ public override uint EXP { get => _coreData.EXP; set => _coreData.EXP = value; }
+ public override int Ability { get => _coreData.Ability; set => _coreData.Ability = value; }
+ public override int AbilityNumber { get => _coreData.AbilityNumber; set => _coreData.AbilityNumber = value; }
+ public bool Favorite { get => _coreData.Favorite; set => _coreData.Favorite = value; }
+ public override int MarkValue { get => _coreData.MarkValue; set => _coreData.MarkValue = value; }
+ public override uint PID { get => _coreData.PID; set => _coreData.PID = value; }
+ public override int Nature { get => _coreData.Nature; set => _coreData.Nature = value; }
+ public override int StatNature { get => _coreData.StatNature; set => _coreData.StatNature = value; }
+ public override bool FatefulEncounter { get => _coreData.FatefulEncounter; set => _coreData.FatefulEncounter = value; }
+ public override int Gender { get => _coreData.Gender; set => _coreData.Gender = value; }
+ public override int Form { get => _coreData.Form; set => _coreData.Form = value; }
+ public override int EV_HP { get => _coreData.EV_HP; set => _coreData.EV_HP = value; }
+ public override int EV_ATK { get => _coreData.EV_ATK; set => _coreData.EV_ATK = value; }
+ public override int EV_DEF { get => _coreData.EV_DEF; set => _coreData.EV_DEF = value; }
+ public override int EV_SPE { get => _coreData.EV_SPE; set => _coreData.EV_SPE = value; }
+ public override int EV_SPA { get => _coreData.EV_SPA; set => _coreData.EV_SPA = value; }
+ public override int EV_SPD { get => _coreData.EV_SPD; set => _coreData.EV_SPD = value; }
+ public byte CNT_Cool { get => _coreData.CNT_Cool; set => _coreData.CNT_Cool = value; }
+ public byte CNT_Beauty { get => _coreData.CNT_Beauty; set => _coreData.CNT_Beauty = value; }
+ public byte CNT_Cute { get => _coreData.CNT_Cute; set => _coreData.CNT_Cute = value; }
+ public byte CNT_Smart { get => _coreData.CNT_Smart; set => _coreData.CNT_Smart = value; }
+ public byte CNT_Tough { get => _coreData.CNT_Tough; set => _coreData.CNT_Tough = value; }
+ public byte CNT_Sheen { get => _coreData.CNT_Sheen; set => _coreData.CNT_Sheen = value; }
+ public override int PKRS_Days { get => _coreData.PKRS_Days; set => _coreData.PKRS_Days = value; }
+ public override int PKRS_Strain { get => _coreData.PKRS_Strain; set => _coreData.PKRS_Strain = value; }
+ public byte HeightScalar { get => _coreData.HeightScalar; set => _coreData.HeightScalar = value; }
+ public byte WeightScalar { get => _coreData.WeightScalar; set => _coreData.WeightScalar = value; }
+ public override int Stat_HPCurrent { get => _coreData.Stat_HPCurrent; set => _coreData.Stat_HPCurrent = value; }
+ public override int IV_HP { get => _coreData.IV_HP; set => _coreData.IV_HP = value; }
+ public override int IV_ATK { get => _coreData.IV_ATK; set => _coreData.IV_ATK = value; }
+ public override int IV_DEF { get => _coreData.IV_DEF; set => _coreData.IV_DEF = value; }
+ public override int IV_SPE { get => _coreData.IV_SPE; set => _coreData.IV_SPE = value; }
+ public override int IV_SPA { get => _coreData.IV_SPA; set => _coreData.IV_SPA = value; }
+ public override int IV_SPD { get => _coreData.IV_SPD; set => _coreData.IV_SPD = value; }
+ public override bool IsEgg { get => _coreData.IsEgg; set => _coreData.IsEgg = value; }
+ public override bool IsNicknamed { get => _coreData.IsNicknamed; set => _coreData.IsNicknamed = value; }
+ public override int Status_Condition { get => _coreData.Status_Condition; set => _coreData.Status_Condition = value; }
+ public override int HT_Gender { get => _coreData.HT_Gender; set => _coreData.HT_Gender = value; }
+ public byte HT_Language { get => _coreData.HT_Language; set => _coreData.HT_Language = value; }
+ public override int CurrentHandler { get => _coreData.CurrentHandler; set => _coreData.CurrentHandler = value; }
+ public int HT_TrainerID { get => _coreData.HT_TrainerID; set => _coreData.HT_TrainerID = value; }
+ public override int HT_Friendship { get => _coreData.HT_Friendship; set => _coreData.HT_Friendship = value; }
+ public byte HT_Intensity { get => _coreData.HT_Intensity; set => _coreData.HT_Intensity = value; }
+ public byte HT_Memory { get => _coreData.HT_Memory; set => _coreData.HT_Memory = value; }
+ public byte HT_Feeling { get => _coreData.HT_Feeling; set => _coreData.HT_Feeling = value; }
+ public ushort HT_TextVar { get => _coreData.HT_TextVar; set => _coreData.HT_TextVar = value; }
+ public override int Version { get => _coreData.Version; set => _coreData.Version = value; }
+ public byte BattleVersion { get => _coreData.BattleVersion; set => _coreData.BattleVersion = value; }
+ public override int Language { get => _coreData.Language; set => _coreData.Language = value; }
+ public uint FormArgument { get => _coreData.FormArgument; set => _coreData.FormArgument = value; }
+ public byte FormArgumentRemain { get => _coreData.FormArgumentRemain; set => _coreData.FormArgumentRemain = value; }
+ public byte FormArgumentElapsed { get => _coreData.FormArgumentElapsed; set => _coreData.FormArgumentElapsed = value; }
+ public byte FormArgumentMaximum { get => _coreData.FormArgumentMaximum; set => _coreData.FormArgumentMaximum = value; }
+ public sbyte AffixedRibbon { get => _coreData.AffixedRibbon; set => _coreData.AffixedRibbon = value; }
+ public override int OT_Friendship { get => _coreData.OT_Friendship; set => _coreData.OT_Friendship = value; }
+ public byte OT_Intensity { get => _coreData.OT_Intensity; set => _coreData.OT_Intensity = value; }
+ public byte OT_Memory { get => _coreData.OT_Memory; set => _coreData.OT_Memory = value; }
+ public ushort OT_TextVar { get => _coreData.OT_TextVar; set => _coreData.OT_TextVar = value; }
+ public byte OT_Feeling { get => _coreData.OT_Feeling; set => _coreData.OT_Feeling = value; }
+ public override int Egg_Year { get => _coreData.Egg_Year; set => _coreData.Egg_Year = value; }
+ public override int Egg_Month { get => _coreData.Egg_Month; set => _coreData.Egg_Month = value; }
+ public override int Egg_Day { get => _coreData.Egg_Day; set => _coreData.Egg_Day = value; }
+ public override int Met_Year { get => _coreData.Met_Year; set => _coreData.Met_Year = value; }
+ public override int Met_Month { get => _coreData.Met_Month; set => _coreData.Met_Month = value; }
+ public override int Met_Day { get => _coreData.Met_Day; set => _coreData.Met_Day = value; }
+ public override int Met_Level { get => _coreData.Met_Level; set => _coreData.Met_Level = value; }
+ public override int OT_Gender { get => _coreData.OT_Gender; set => _coreData.OT_Gender = value; }
+ public byte HyperTrainFlags { get => _coreData.HyperTrainFlags; set => _coreData.HyperTrainFlags = value; }
+ public bool HT_HP { get => _coreData.HT_HP; set => _coreData.HT_HP = value; }
+ public bool HT_ATK { get => _coreData.HT_ATK; set => _coreData.HT_ATK = value; }
+ public bool HT_DEF { get => _coreData.HT_DEF; set => _coreData.HT_DEF = value; }
+ public bool HT_SPA { get => _coreData.HT_SPA; set => _coreData.HT_SPA = value; }
+ public bool HT_SPD { get => _coreData.HT_SPD; set => _coreData.HT_SPD = value; }
+ public bool HT_SPE { get => _coreData.HT_SPE; set => _coreData.HT_SPE = value; }
+ public override int HeldItem { get => _coreData.HeldItem; set => _coreData.HeldItem = value; }
+
+ public override string Nickname { get => _coreData.Nickname; set => _coreData.Nickname = value; }
+ public override string OT_Name { get => _coreData.OT_Name; set => _coreData.OT_Name = value; }
+ public override string HT_Name { get => _coreData.HT_Name; set => _coreData.HT_Name = value; }
+
+ #endregion
+
+ #region Calculated
+
+ public override int CurrentFriendship { get => CurrentHandler == 0 ? OT_Friendship : HT_Friendship; set { if (CurrentHandler == 0) OT_Friendship = value; else HT_Friendship = value; } }
+
+ public override int PSV => (int)((PID >> 16 ^ (PID & 0xFFFF)) >> 4);
+ public override int TSV => (TID ^ SID) >> 4;
+
+ public override int Characteristic
+ {
+ get
+ {
+ int pm6 = (int)(EncryptionConstant % 6);
+ int maxIV = MaximumIV;
+ int pm6stat = 0;
+ for (int i = 0; i < 6; i++)
+ {
+ pm6stat = (pm6 + i) % 6;
+ if (GetIV(pm6stat) == maxIV)
+ break;
+ }
+ return (pm6stat * 5) + (maxIV % 5);
+ }
+ }
+
+ #endregion
+
+ #region Children
+
+ public override int Move1 { get => LatestGameData.Move1 ; set => LatestGameData.Move1 = value; }
+ public override int Move2 { get => LatestGameData.Move2 ; set => LatestGameData.Move2 = value; }
+ public override int Move3 { get => LatestGameData.Move3 ; set => LatestGameData.Move3 = value; }
+ public override int Move4 { get => LatestGameData.Move4 ; set => LatestGameData.Move4 = value; }
+ public override int Move1_PP { get => LatestGameData.Move1_PP ; set => LatestGameData.Move1_PP = value; }
+ public override int Move2_PP { get => LatestGameData.Move2_PP ; set => LatestGameData.Move2_PP = value; }
+ public override int Move3_PP { get => LatestGameData.Move3_PP ; set => LatestGameData.Move3_PP = value; }
+ public override int Move4_PP { get => LatestGameData.Move4_PP ; set => LatestGameData.Move4_PP = value; }
+ public override int Move1_PPUps { get => LatestGameData.Move1_PPUps; set => LatestGameData.Move1_PPUps = value; }
+ public override int Move2_PPUps { get => LatestGameData.Move2_PPUps; set => LatestGameData.Move2_PPUps = value; }
+ public override int Move3_PPUps { get => LatestGameData.Move3_PPUps; set => LatestGameData.Move3_PPUps = value; }
+ public override int Move4_PPUps { get => LatestGameData.Move4_PPUps; set => LatestGameData.Move4_PPUps = value; }
+
+ public override int Ball { get => LatestGameData.Ball; set => LatestGameData.Ball = value; }
+ public override int Met_Location { get => LatestGameData.Met_Location; set => LatestGameData.Met_Location = value; }
+ public override int Egg_Location { get => LatestGameData.Egg_Location; set => LatestGameData.Egg_Location = value; }
+
+ #endregion
+
+ public override int Stat_Level { get => CurrentLevel; set => CurrentLevel = value; }
+ public override int Stat_HPMax { get => 0; set { } }
+ public override int Stat_ATK { get => 0; set { } }
+ public override int Stat_DEF { get => 0; set { } }
+ public override int Stat_SPE { get => 0; set { } }
+ public override int Stat_SPA { get => 0; set { } }
+ public override int Stat_SPD { get => 0; set { } }
+
+ #region Maximums
+
+ public override int MaxIV => 31;
+ public override int MaxEV => 252;
+ public override int OTLength => 12;
+ public override int NickLength => 12;
+ public override int MaxMoveID => Legal.MaxMoveID_8a;
+ public override int MaxSpeciesID => Legal.MaxSpeciesID_8a;
+ public override int MaxAbilityID => Legal.MaxAbilityID_8a;
+ public override int MaxItemID => Legal.MaxItemID_8a;
+ public override int MaxBallID => Legal.MaxBallID_8a;
+ public override int MaxGameID => Legal.MaxGameID_8a;
+
+ #endregion
+
+ public override int SIZE_PARTY => HomeCrypto.SIZE_1STORED;
+ public override int SIZE_STORED => HomeCrypto.SIZE_1STORED;
+ public override bool Valid { get => true; set { } }
+ public override PersonalInfo PersonalInfo => LatestGameData.GetPersonalInfo(Species, Form);
+ public override void RefreshChecksum() => Checksum = 0;
+ public override bool ChecksumValid => true;
+
+ protected override byte[] Encrypt()
+ {
+ var result = Rebuild();
+ return HomeCrypto.Encrypt(result);
+ }
+
+ private const int GameDataStart = HomeCrypto.SIZE_1HEADER + (2 + HomeCrypto.SIZE_1CORE) + 2;
+
+ public byte[] Rebuild()
+ {
+ var length = WriteLength;
+
+ // Handle PKCS7 manually
+ var remainder = length & 0xF;
+ if (remainder != 0) // pad to nearest 0x10, fill remainder bytes with value.
+ remainder = 0x10 - remainder;
+ var result = new byte[length + remainder];
+ result.AsSpan()[^remainder..].Fill((byte)remainder);
+
+ // Header and Core are already in the current byte array.
+ // Write each part, starting with header and core.
+ int ctr = GameDataStart;
+ Data.AsSpan(0, ctr).CopyTo(result);
+ if (DataPK8 is { } pk8) ctr += pk8.CopyTo(result.AsSpan(ctr));
+ if (DataPB7 is { } pb7) ctr += pb7.CopyTo(result.AsSpan(ctr));
+ if (DataPA8 is { } pa8) ctr += pa8.CopyTo(result.AsSpan(ctr));
+ if (DataPB8 is { } pb8) ctr += pb8.CopyTo(result.AsSpan(ctr));
+
+ // Update metadata to ensure we're a valid object.
+ DataVersion = HomeCrypto.Version1;
+ EncodedDataSize = (ushort)(result.Length - HomeCrypto.SIZE_1HEADER);
+ CoreDataSize = HomeCrypto.SIZE_1CORE;
+ GameDataSize = (ushort)(ctr - GameDataStart);
+
+ return result;
+ }
+
+ private int WriteLength
+ {
+ get
+ {
+ var length = GameDataStart;
+ if (DataPK8 is not null) length += 3 + HomeCrypto.SIZE_1GAME_PK8;
+ if (DataPB7 is not null) length += 3 + HomeCrypto.SIZE_1GAME_PB7;
+ if (DataPA8 is not null) length += 3 + HomeCrypto.SIZE_1GAME_PA8;
+ if (DataPB8 is not null) length += 3 + HomeCrypto.SIZE_1GAME_PB8;
+ return length;
+ }
+ }
+
+ public override PKM Clone() => new PKH((byte[])Data.Clone())
+ {
+ DataPK8 = DataPK8?.Clone(),
+ DataPA8 = DataPA8?.Clone(),
+ DataPB8 = DataPB8?.Clone(),
+ DataPB7 = DataPB7?.Clone(),
+ };
+
+ public IGameDataSide LatestGameData => OriginalGameData() ?? GetFallbackGameData();
+
+ private IGameDataSide GetFallbackGameData() => Version switch
+ {
+ (int)GP or (int)GE => DataPB7 ??= new(),
+ (int)BD or (int)SP => DataPB8 ??= new(),
+ (int)PLA => DataPA8 ??= new(),
+
+ _ => DataPK8 ??= new(),
+ };
+
+ private IGameDataSide? OriginalGameData() => Version switch
+ {
+ (int)GP or (int)GE => DataPB7,
+ (int)BD or (int)SP => DataPB8,
+ (int)PLA => DataPA8,
+
+ (int)SW or (int)SH when DataPK8 is { Met_Location: HOME_SWLA } => DataPA8,
+ (int)SW or (int)SH when DataPK8 is { Met_Location: HOME_SWBD or HOME_SHSP } => DataPB8,
+ (int)SW or (int)SH => DataPK8,
+
+ _ => DataPK8,
+ };
+
+ public PKM? ConvertToPB7() => DataPB7 is { } x ? x.ConvertToPB7(this) : (DataPB7 ??= GameDataPB7.TryCreate(this))?.ConvertToPB7(this);
+ public PK8? ConvertToPK8() => DataPK8 is { } x ? x.ConvertToPK8(this) : (DataPK8 ??= GameDataPK8.TryCreate(this))?.ConvertToPK8(this);
+ public PB8? ConvertToPB8() => DataPB8 is { } x ? x.ConvertToPB8(this) : (DataPB8 ??= GameDataPB8.TryCreate(this))?.ConvertToPB8(this);
+ public PA8? ConvertToPA8() => DataPA8 is { } x ? x.ConvertToPA8(this) : (DataPA8 ??= GameDataPA8.TryCreate(this))?.ConvertToPA8(this);
+
+ public void CopyTo(PKM pk) => _coreData.CopyTo(pk);
+}
diff --git a/PKHeX.Core/PKM/Interfaces/IBattleVersion.cs b/PKHeX.Core/PKM/Interfaces/IBattleVersion.cs
index 463d627e1..64839e1bc 100644
--- a/PKHeX.Core/PKM/Interfaces/IBattleVersion.cs
+++ b/PKHeX.Core/PKM/Interfaces/IBattleVersion.cs
@@ -13,6 +13,13 @@ public interface IBattleVersion
public static class BattleVersionExtensions
{
+ public static bool IsBattleVersionValid(this T pk, EvolutionHistory h) where T : PKM, IBattleVersion => pk.BattleVersion switch
+ {
+ 0 => true,
+ (int)GameVersion.SW or (int)GameVersion.SH => !(pk.SWSH || pk.BDSP || pk.LA) && pk.HasVisitedSWSH(h.Gen8),
+ _ => false,
+ };
+
///
/// Resets the 's moves and sets the requested version.
///
diff --git a/PKHeX.Core/PKM/Interfaces/IDynamaxLevel.cs b/PKHeX.Core/PKM/Interfaces/IDynamaxLevel.cs
index 41ceb54a5..2071b97b4 100644
--- a/PKHeX.Core/PKM/Interfaces/IDynamaxLevel.cs
+++ b/PKHeX.Core/PKM/Interfaces/IDynamaxLevel.cs
@@ -19,9 +19,7 @@ public static bool CanHaveDynamaxLevel(this IDynamaxLevel _, PKM pkm)
{
if (pkm.IsEgg)
return false;
- if (pkm.BDSP || pkm.LA)
- return false;
- return CanHaveDynamaxLevel(pkm.Species);
+ return pkm is PK8 && CanHaveDynamaxLevel(pkm.Species);
}
///
diff --git a/PKHeX.Core/PKM/Interfaces/IFormArgument.cs b/PKHeX.Core/PKM/Interfaces/IFormArgument.cs
index 06089142f..64799a745 100644
--- a/PKHeX.Core/PKM/Interfaces/IFormArgument.cs
+++ b/PKHeX.Core/PKM/Interfaces/IFormArgument.cs
@@ -12,6 +12,9 @@ namespace PKHeX.Core
/// : Topping (Strawberry, Star, etc); [0,7]
/// How much damage the Pokémon has taken as Yamask-1 [0,9999].
/// How much damage the Pokémon has taken as Yamask-1 [0,9999].
+ /// How many times the Pokémon has used Psyshield Bash in the Agile Style [0,9999].
+ /// How many times the Pokémon has used Barb Barrage in the Strong Style as Qwilfish-1 [0,9999].
+ /// How much damage the Pokémon has taken through recoil as Basculin-2 [0,9999].
///
public interface IFormArgument
{
@@ -117,10 +120,11 @@ public static uint GetFormArgumentMax(int species, int form, int generation)
(int)Yamask when form == 1 => 9999,
(int)Runerigus when form == 0 => 9999,
(int)Alcremie => (uint)AlcremieDecoration.Ribbon,
- (int)Qwilfish or (int)Overqwil when generation == 8 => 9999, // 20
+ (int)Qwilfish when form == 1 && generation == 8 => 9999, // 20
+ (int)Overqwil => 9999, // 20
(int)Stantler or (int)Wyrdeer when generation == 8 => 9999, // 20
- (int)Basculin when form == 2 => 9999,
- (int)Basculegion => 9999,
+ (int)Basculin when form == 2 => 9999, // 294
+ (int)Basculegion => 9999, // 294
_ => 0,
};
}
diff --git a/PKHeX.Core/PKM/Interfaces/IGanbaru.cs b/PKHeX.Core/PKM/Interfaces/IGanbaru.cs
index 5271b047c..f88d4d747 100644
--- a/PKHeX.Core/PKM/Interfaces/IGanbaru.cs
+++ b/PKHeX.Core/PKM/Interfaces/IGanbaru.cs
@@ -105,7 +105,7 @@ public static void ClearGanbaruValues(this IGanbaru g)
public static byte GetGanbaruMultiplier(byte gv, int iv) => GanbaruMultiplier[Math.Min(gv + GetBias(iv), TrueMax)];
///
- /// Sets one of the values based on its index within the array.
+ /// Sets one of the values based on its index within the array.
///
/// Pokémon to modify.
/// Index to set to
@@ -122,7 +122,7 @@ public static void ClearGanbaruValues(this IGanbaru g)
};
///
- /// Sets one of the values based on its index within the array.
+ /// Sets one of the values based on its index within the array.
///
/// Pokémon to check.
/// Index to get
@@ -136,4 +136,26 @@ public static void ClearGanbaruValues(this IGanbaru g)
5 => pk.GV_SPD,
_ => throw new ArgumentOutOfRangeException(nameof(index)),
};
+
+ ///
+ /// Checks if any of the values are below a reference's minimum value.
+ ///
+ /// Pokémon to check.
+ /// Reference to check
+ public static bool IsGanbaruValuesBelow(this IGanbaru pk, IGanbaru obj)
+ {
+ if (pk.GV_HP < obj.GV_HP)
+ return true;
+ if (pk.GV_ATK < obj.GV_ATK)
+ return true;
+ if (pk.GV_DEF < obj.GV_DEF)
+ return true;
+ if (pk.GV_SPA < obj.GV_SPA)
+ return true;
+ if (pk.GV_SPD < obj.GV_SPD)
+ return true;
+ if (pk.GV_SPE < obj.GV_SPE)
+ return true;
+ return false;
+ }
}
diff --git a/PKHeX.Core/PKM/Interfaces/IHyperTrain.cs b/PKHeX.Core/PKM/Interfaces/IHyperTrain.cs
index dae0862bb..4199c24ed 100644
--- a/PKHeX.Core/PKM/Interfaces/IHyperTrain.cs
+++ b/PKHeX.Core/PKM/Interfaces/IHyperTrain.cs
@@ -91,10 +91,21 @@ public static void SetSuggestedHyperTrainingData(this PKM pkm, int[]? IVs = null
public static bool IsHyperTrainingAvailable(this IHyperTrain t) => t switch
{
// Check for game formats where training is unavailable:
- PA8 => false,
+ PA8 pa8 => HasVisitedBDSPorSWSH(pa8),
_ => true,
};
+ private static bool HasVisitedBDSPorSWSH(PKM pk)
+ {
+ if (pk.IsUntraded)
+ return false;
+ if (PersonalTable.BDSP.IsPresentInGame(pk.Species, pk.Form))
+ return true;
+ if (PersonalTable.SWSH.IsPresentInGame(pk.Species, pk.Form))
+ return true;
+ return false;
+ }
+
///
/// Entity data
public static bool IsHyperTrainingAvailable(this PKM pk)
diff --git a/PKHeX.Core/PKM/Interfaces/IMoveReset.cs b/PKHeX.Core/PKM/Interfaces/IMoveReset.cs
new file mode 100644
index 000000000..b9c8ded6c
--- /dev/null
+++ b/PKHeX.Core/PKM/Interfaces/IMoveReset.cs
@@ -0,0 +1,6 @@
+namespace PKHeX.Core;
+
+public interface IMoveReset
+{
+ void ResetMoves();
+}
diff --git a/PKHeX.Core/PKM/PA8.cs b/PKHeX.Core/PKM/PA8.cs
index 0f1f5cacd..934e9d4a8 100644
--- a/PKHeX.Core/PKM/PA8.cs
+++ b/PKHeX.Core/PKM/PA8.cs
@@ -6,7 +6,7 @@
namespace PKHeX.Core;
/// Generation 8 format.
-public sealed class PA8 : PKM, ISanityChecksum,
+public sealed class PA8 : PKM, ISanityChecksum, IMoveReset,
IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8, IRibbonSetAffixed, IGanbaru, IAlpha, INoble, ITechRecord8, ISociability, IMoveShop8Mastery,
IContestStats, IContestStatsMutable, IHyperTrain, IScaledSizeValue, IGigantamax, IFavorite, IDynamaxLevel, IRibbonIndex, IHandlerLanguage, IFormArgument, IHomeTrack, IBattleVersion, ITrainerMemories
{
@@ -31,7 +31,8 @@ public sealed class PA8 : PKM, ISanityChecksum,
public override IReadOnlyList ExtraBytes => Unused;
public override PersonalInfo PersonalInfo => PersonalTable.LA.GetFormEntry(Species, Form);
- public override int Format => 8;
+ public override EntityContext Context => EntityContext.Gen8a;
+ public override bool IsNative => LA;
public PA8() : base(PokeCrypto.SIZE_8APARTY) => AffixedRibbon = -1; // 00 would make it show Kalos Champion :)
public PA8(byte[] data) : base(DecryptParty(data)) { }
@@ -153,7 +154,7 @@ public void FixRelearn()
public bool IsAlpha { get => (Data[0x16] & 32) != 0; set => Data[0x16] = (byte)((Data[0x16] & ~32) | ((value ? 1 : 0) << 5)); }
public bool IsNoble { get => (Data[0x16] & 64) != 0; set => Data[0x16] = (byte)((Data[0x16] & ~64) | ((value ? 1 : 0) << 6)); }
// 0x17 alignment unused
- public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); protected set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); }
+ public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); }
// 0x1A alignment unused
// 0x1B alignment unused
public override uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x1C), value); }
@@ -674,16 +675,8 @@ public int GetRibbonByte(int index)
return 0x40 + (index >> 3);
}
- public void Trade(ITrainerInfo tr, int Day = 29, int Month = 1, int Year = 2022)
+ public void Trade(ITrainerInfo tr)
{
- if (IsEgg)
- {
- // Apply link trade data, only if it left the OT (ignore if dumped & imported, or cloned, etc)
- if ((tr.TID != TID) || (tr.SID != SID) || (tr.Gender != OT_Gender) || (tr.OT != OT_Name))
- SetLinkTradeEgg(Day, Month, Year, Locations.LinkTrade6NPC);
- return;
- }
-
// Process to the HT if the OT of the Pokémon does not match the SAV's OT info.
if (!TradeOT(tr))
TradeHT(tr);
@@ -846,4 +839,241 @@ public void SetMasteryFlagMove(int move)
if (MoveShopPermitFlags[flagIndex])
SetMasteredRecordFlag(flagIndex, true);
}
+
+ public PK8 ConvertToPK8()
+ {
+ var pk = ConvertTo();
+ pk.SanitizeImport();
+ return pk;
+ }
+
+ public PB8 ConvertToPB8()
+ {
+ var pk = ConvertTo();
+ if (pk.Egg_Location == 0)
+ pk.Egg_Location = Locations.Default8bNone;
+ return pk;
+ }
+
+ private T ConvertTo() where T : G8PKM, new()
+ {
+ var pk = new T
+ {
+ EncryptionConstant = EncryptionConstant,
+ PID = PID,
+ Species = Species,
+ Form = Form,
+ FormArgument = FormArgument,
+ Gender = Gender,
+ Nature = Nature,
+ StatNature = StatNature,
+
+ TID = TID,
+ SID = SID,
+ EXP = EXP,
+ Ability = Ability,
+ AbilityNumber = AbilityNumber,
+ Language = Language,
+ Version = Version,
+
+ IV_HP = IV_HP,
+ IV_ATK = IV_ATK,
+ IV_DEF = IV_DEF,
+ IV_SPE = IV_SPE,
+ IV_SPA = IV_SPA,
+ IV_SPD = IV_SPD,
+ IsEgg = IsEgg,
+ EV_HP = EV_HP,
+ EV_ATK = EV_ATK,
+ EV_DEF = EV_DEF,
+ EV_SPE = EV_SPE,
+ EV_SPA = EV_SPA,
+ EV_SPD = EV_SPD,
+
+ OT_Gender = OT_Gender,
+ OT_Friendship = OT_Friendship,
+ OT_Intensity = OT_Intensity,
+ OT_Memory = OT_Memory,
+ OT_TextVar = OT_TextVar,
+ OT_Feeling = OT_Feeling,
+ Egg_Year = Egg_Year,
+ Egg_Month = Egg_Month,
+ Egg_Day = Egg_Day,
+ Met_Year = Met_Year,
+ Met_Month = Met_Month,
+ Met_Day = Met_Day,
+ Ball = Ball,
+ Egg_Location = Egg_Location,
+ Met_Location = Met_Location,
+ Met_Level = Met_Level,
+ Tracker = Tracker,
+
+ IsNicknamed = IsNicknamed,
+ CurrentHandler = CurrentHandler,
+ HT_Gender = HT_Gender,
+ HT_Language = HT_Language,
+ HT_Friendship = HT_Friendship,
+ HT_Intensity = HT_Intensity,
+ HT_Memory = HT_Memory,
+ HT_Feeling = HT_Feeling,
+ HT_TextVar = HT_TextVar,
+
+ FatefulEncounter = FatefulEncounter,
+ CNT_Cool = CNT_Cool,
+ CNT_Beauty = CNT_Beauty,
+ CNT_Cute = CNT_Cute,
+ CNT_Smart = CNT_Smart,
+ CNT_Tough = CNT_Tough,
+ CNT_Sheen = CNT_Sheen,
+
+ RibbonChampionKalos = RibbonChampionKalos,
+ RibbonChampionG3 = RibbonChampionG3,
+ RibbonChampionSinnoh = RibbonChampionSinnoh,
+ RibbonBestFriends = RibbonBestFriends,
+ RibbonTraining = RibbonTraining,
+ RibbonBattlerSkillful = RibbonBattlerSkillful,
+ RibbonBattlerExpert = RibbonBattlerExpert,
+ RibbonEffort = RibbonEffort,
+ RibbonAlert = RibbonAlert,
+ RibbonShock = RibbonShock,
+ RibbonDowncast = RibbonDowncast,
+ RibbonCareless = RibbonCareless,
+ RibbonRelax = RibbonRelax,
+ RibbonSnooze = RibbonSnooze,
+ RibbonSmile = RibbonSmile,
+ RibbonGorgeous = RibbonGorgeous,
+ RibbonRoyal = RibbonRoyal,
+ RibbonGorgeousRoyal = RibbonGorgeousRoyal,
+ RibbonArtist = RibbonArtist,
+ RibbonFootprint = RibbonFootprint,
+ RibbonRecord = RibbonRecord,
+ RibbonLegend = RibbonLegend,
+ RibbonCountry = RibbonCountry,
+ RibbonNational = RibbonNational,
+ RibbonEarth = RibbonEarth,
+ RibbonWorld = RibbonWorld,
+ RibbonClassic = RibbonClassic,
+ RibbonPremier = RibbonPremier,
+ RibbonEvent = RibbonEvent,
+ RibbonBirthday = RibbonBirthday,
+ RibbonSpecial = RibbonSpecial,
+ RibbonSouvenir = RibbonSouvenir,
+ RibbonWishing = RibbonWishing,
+ RibbonChampionBattle = RibbonChampionBattle,
+ RibbonChampionRegional = RibbonChampionRegional,
+ RibbonChampionNational = RibbonChampionNational,
+ RibbonChampionWorld = RibbonChampionWorld,
+ HasContestMemoryRibbon = HasContestMemoryRibbon,
+ HasBattleMemoryRibbon = HasBattleMemoryRibbon,
+ RibbonChampionG6Hoenn = RibbonChampionG6Hoenn,
+ RibbonContestStar = RibbonContestStar,
+ RibbonMasterCoolness = RibbonMasterCoolness,
+ RibbonMasterBeauty = RibbonMasterBeauty,
+ RibbonMasterCuteness = RibbonMasterCuteness,
+ RibbonMasterCleverness = RibbonMasterCleverness,
+ RibbonMasterToughness = RibbonMasterToughness,
+ RibbonChampionAlola = RibbonChampionAlola,
+ RibbonBattleRoyale = RibbonBattleRoyale,
+ RibbonBattleTreeGreat = RibbonBattleTreeGreat,
+ RibbonBattleTreeMaster = RibbonBattleTreeMaster,
+ RibbonChampionGalar = RibbonChampionGalar,
+ RibbonTowerMaster = RibbonTowerMaster,
+ RibbonMasterRank = RibbonMasterRank,
+
+ RibbonMarkLunchtime = RibbonMarkLunchtime,
+ RibbonMarkSleepyTime = RibbonMarkSleepyTime,
+ RibbonMarkDusk = RibbonMarkDusk,
+ RibbonMarkDawn = RibbonMarkDawn,
+ RibbonMarkCloudy = RibbonMarkCloudy,
+ RibbonMarkRainy = RibbonMarkRainy,
+ RibbonMarkStormy = RibbonMarkStormy,
+ RibbonMarkSnowy = RibbonMarkSnowy,
+ RibbonMarkBlizzard = RibbonMarkBlizzard,
+ RibbonMarkDry = RibbonMarkDry,
+ RibbonMarkSandstorm = RibbonMarkSandstorm,
+ RibbonCountMemoryContest = RibbonCountMemoryContest,
+ RibbonCountMemoryBattle = RibbonCountMemoryBattle,
+ RibbonMarkMisty = RibbonMarkMisty,
+ RibbonMarkDestiny = RibbonMarkDestiny,
+ RibbonMarkFishing = RibbonMarkFishing,
+ RibbonMarkCurry = RibbonMarkCurry,
+ RibbonMarkUncommon = RibbonMarkUncommon,
+ RibbonMarkRare = RibbonMarkRare,
+ RibbonMarkRowdy = RibbonMarkRowdy,
+ RibbonMarkAbsentMinded = RibbonMarkAbsentMinded,
+ RibbonMarkJittery = RibbonMarkJittery,
+ RibbonMarkExcited = RibbonMarkExcited,
+ RibbonMarkCharismatic = RibbonMarkCharismatic,
+ RibbonMarkCalmness = RibbonMarkCalmness,
+ RibbonMarkIntense = RibbonMarkIntense,
+ RibbonMarkZonedOut = RibbonMarkZonedOut,
+ RibbonMarkJoyful = RibbonMarkJoyful,
+ RibbonMarkAngry = RibbonMarkAngry,
+ RibbonMarkSmiley = RibbonMarkSmiley,
+ RibbonMarkTeary = RibbonMarkTeary,
+ RibbonMarkUpbeat = RibbonMarkUpbeat,
+ RibbonMarkPeeved = RibbonMarkPeeved,
+ RibbonMarkIntellectual = RibbonMarkIntellectual,
+ RibbonMarkFerocious = RibbonMarkFerocious,
+ RibbonMarkCrafty = RibbonMarkCrafty,
+ RibbonMarkScowling = RibbonMarkScowling,
+ RibbonMarkKindly = RibbonMarkKindly,
+ RibbonMarkFlustered = RibbonMarkFlustered,
+ RibbonMarkPumpedUp = RibbonMarkPumpedUp,
+ RibbonMarkZeroEnergy = RibbonMarkZeroEnergy,
+ RibbonMarkPrideful = RibbonMarkPrideful,
+ RibbonMarkUnsure = RibbonMarkUnsure,
+ RibbonMarkHumble = RibbonMarkHumble,
+ RibbonMarkThorny = RibbonMarkThorny,
+ RibbonMarkVigor = RibbonMarkVigor,
+ RibbonMarkSlump = RibbonMarkSlump,
+ RibbonPioneer = RibbonPioneer,
+ RibbonTwinklingStar = RibbonTwinklingStar,
+
+ AffixedRibbon = AffixedRibbon,
+ HyperTrainFlags = HyperTrainFlags,
+
+ Sociability = Sociability,
+ Fullness = Fullness,
+ Enjoyment = Enjoyment,
+ BattleVersion = BattleVersion,
+ PKRS_Days = PKRS_Days,
+ PKRS_Strain = PKRS_Strain,
+ HeightScalar = HeightScalar,
+ WeightScalar = WeightScalar,
+ CanGigantamax = CanGigantamax,
+ DynamaxLevel = DynamaxLevel,
+
+ Favorite = Favorite,
+ };
+
+ Nickname_Trash.CopyTo(pk.Nickname_Trash);
+ OT_Trash.CopyTo(pk.OT_Trash);
+ HT_Trash.CopyTo(pk.HT_Trash);
+ pk.ResetMoves();
+ pk.ResetPartyStats();
+ pk.RefreshChecksum();
+
+ return pk;
+ }
+
+ public void SanitizeImport()
+ {
+ HeightScalarCopy = HeightScalar;
+ ResetHeight();
+ ResetWeight();
+ }
+
+ public void ResetMoves()
+ {
+ var learnsets = Legal.LevelUpLA;
+ var table = PersonalTable.LA;
+
+ var index = table.GetFormIndex(Species, Form);
+ var learn = learnsets[index];
+ Span moves = stackalloc int[4];
+ learn.SetEncounterMoves(CurrentLevel, moves);
+ SetMoves(moves);
+ this.SetMaximumPPCurrent(moves);
+ }
}
diff --git a/PKHeX.Core/PKM/PB7.cs b/PKHeX.Core/PKM/PB7.cs
index 9af3994cd..4d1f3b7d6 100644
--- a/PKHeX.Core/PKM/PB7.cs
+++ b/PKHeX.Core/PKM/PB7.cs
@@ -26,7 +26,7 @@ public sealed class PB7 : G6PKM, IHyperTrain, IAwakened, IScaledSizeValue, IComb
public override int SIZE_PARTY => SIZE;
public override int SIZE_STORED => SIZE;
private const int SIZE = 260;
- public override int Format => 7;
+ public override EntityContext Context => EntityContext.Gen7b;
public override PersonalInfo PersonalInfo => PersonalTable.GG.GetFormEntry(Species, Form);
public PB7() : base(SIZE) { }
@@ -95,7 +95,7 @@ public override uint EXP
public override int Ability { get => Data[0x14]; set => Data[0x14] = (byte)value; }
public override int AbilityNumber { get => Data[0x15] & 7; set => Data[0x15] = (byte)((Data[0x15] & ~7) | (value & 7)); }
public bool Favorite { get => (Data[0x15] & 8) != 0; set => Data[0x15] = (byte)((Data[0x15] & ~8) | ((value ? 1 : 0) << 3)); }
- public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x16)); protected set => WriteUInt16LittleEndian(Data.AsSpan(0x16), (ushort)value); }
+ public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x16)); set => WriteUInt16LittleEndian(Data.AsSpan(0x16), (ushort)value); }
public override uint PID
{
@@ -119,7 +119,7 @@ public override uint PID
public byte AV_SPE { get => Data[0x27]; set => Data[0x27] = value; }
public byte AV_SPA { get => Data[0x28]; set => Data[0x28] = value; }
public byte AV_SPD { get => Data[0x29]; set => Data[0x29] = value; }
- // 0x2A Unused
+ public ResortEventState ResortEventStatus { get => (ResortEventState)Data[0x2A]; set => Data[0x2A] = (byte)value; }
private byte PKRS { get => Data[0x2B]; set => Data[0x2B] = value; }
public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); }
public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | value << 4); }
@@ -653,6 +653,8 @@ public PK8 ConvertToPK8()
StatNature = Nature,
HeightScalar = HeightScalar,
WeightScalar = WeightScalar,
+
+ Favorite = Favorite,
};
// Fix PP and Stats
diff --git a/PKHeX.Core/PKM/PB8.cs b/PKHeX.Core/PKM/PB8.cs
index 008ff0fb0..7f79c2de6 100644
--- a/PKHeX.Core/PKM/PB8.cs
+++ b/PKHeX.Core/PKM/PB8.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
namespace PKHeX.Core;
@@ -27,6 +28,8 @@ public sealed class PB8 : G8PKM
public override IReadOnlyList ExtraBytes => Unused;
public override PersonalInfo PersonalInfo => PersonalTable.BDSP.GetFormEntry(Species, Form);
+ public override bool IsNative => BDSP;
+ public override EntityContext Context => EntityContext.Gen8b;
public PB8()
{
@@ -123,4 +126,40 @@ private void TradeHT(ITrainerInfo tr)
public override int MaxItemID => Legal.MaxItemID_8b;
public override int MaxBallID => Legal.MaxBallID_8b;
public override int MaxGameID => Legal.MaxGameID_8b;
+
+ public override bool WasEgg => IsEgg || Egg_Day != 0;
+
+ public PK8 ConvertToPK8()
+ {
+ var pk = ConvertTo();
+ if (pk.Egg_Location == Locations.Default8bNone)
+ pk.Egg_Location = 0;
+ else
+ pk.Egg_Location = Locations.HOME_SWSHBDSPEgg;
+ pk.SanitizeImport();
+ return pk;
+ }
+
+ public override PA8 ConvertToPA8()
+ {
+ var pk = base.ConvertToPA8();
+ if (pk.Egg_Location == Locations.Default8bNone)
+ pk.Egg_Location = 0;
+ return pk;
+ }
+
+ public override bool HasOriginalMetLocation => base.HasOriginalMetLocation && !(LA && Met_Location == Locations.HOME_SWLA);
+
+ public override void ResetMoves()
+ {
+ var learnsets = Legal.LevelUpBDSP;
+ var table = PersonalTable.BDSP;
+
+ var index = table.GetFormIndex(Species, Form);
+ var learn = learnsets[index];
+ Span moves = stackalloc int[4];
+ learn.SetEncounterMoves(CurrentLevel, moves);
+ SetMoves(moves);
+ this.SetMaximumPPCurrent(moves);
+ }
}
diff --git a/PKHeX.Core/PKM/PK1.cs b/PKHeX.Core/PKM/PK1.cs
index 35f1ec42e..4b4317224 100644
--- a/PKHeX.Core/PKM/PK1.cs
+++ b/PKHeX.Core/PKM/PK1.cs
@@ -15,7 +15,7 @@ public sealed class PK1 : GBPKML
public override int SIZE_STORED => PokeCrypto.SIZE_1STORED;
public override bool Korean => false;
- public override int Format => 1;
+ public override EntityContext Context => EntityContext.Gen1;
public PK1(bool jp = false) : base(PokeCrypto.SIZE_1PARTY, jp) { }
public PK1(byte[] decryptedData, bool jp = false) : base(EnsurePartySize(decryptedData), jp) { }
@@ -104,22 +104,25 @@ private void SetSpeciesValues(int value)
Type_B = PersonalInfo.Type2;
// Before updating catch rate, check if non-standard
- int rate = Catch_Rate;
- if (rate is 0 || IsCatchRateHeldItem(rate))
- return;
- if (value == (int)Core.Species.Pikachu && rate == 0xA3) // Light Ball (starter)
- return;
-
- var table = EvolutionTree.GetEvolutionTree(1);
- var evos = table.GetValidPreEvolutions(this, maxLevel: 100, maxSpeciesOrigin: Legal.MaxSpeciesID_1, skipChecks: true);
- var baseSpecies = evos[^1].Species;
- if (IsCatchRatePreEvolutionRate(baseSpecies, value, rate))
+ if (IsValidCatchRateAnyPreEvo(value, Catch_Rate))
return;
// Matches nothing possible; just reset to current Species' rate.
Catch_Rate = PersonalTable.RB[value].CatchRate;
}
+ private static bool IsValidCatchRateAnyPreEvo(int species, int rate)
+ {
+ if (rate is 0 || IsCatchRateHeldItem(rate))
+ return true;
+ if (species == (int)Core.Species.Pikachu && rate == 0xA3) // Light Ball (starter)
+ return true;
+
+ var table = EvolutionTree.Evolves1;
+ var baby = table.GetBaseSpeciesForm(species, 0);
+ return IsCatchRatePreEvolutionRate(baby, species, rate);
+ }
+
public override int Version { get => (int)GameVersion.RBY; set { } }
public override int PKRS_Strain { get => 0; set { } }
public override int PKRS_Days { get => 0; set { } }
diff --git a/PKHeX.Core/PKM/PK2.cs b/PKHeX.Core/PKM/PK2.cs
index 067f6bc35..1b89e97c9 100644
--- a/PKHeX.Core/PKM/PK2.cs
+++ b/PKHeX.Core/PKM/PK2.cs
@@ -15,7 +15,7 @@ public sealed class PK2 : GBPKML, ICaughtData2
public override int SIZE_STORED => PokeCrypto.SIZE_2STORED;
public override bool Korean => !Japanese && RawOT[0] <= 0xB;
- public override int Format => 2;
+ public override EntityContext Context => EntityContext.Gen2;
public PK2(bool jp = false) : base(PokeCrypto.SIZE_2PARTY, jp) { }
public PK2(byte[] decryptedData, bool jp = false) : base(EnsurePartySize(decryptedData), jp) { }
diff --git a/PKHeX.Core/PKM/PK3.cs b/PKHeX.Core/PKM/PK3.cs
index bb49a4e42..fdbc71982 100644
--- a/PKHeX.Core/PKM/PK3.cs
+++ b/PKHeX.Core/PKM/PK3.cs
@@ -14,7 +14,7 @@ public sealed class PK3 : G3PKM, ISanityChecksum
public override int SIZE_PARTY => PokeCrypto.SIZE_3PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_3STORED;
- public override int Format => 3;
+ public override EntityContext Context => EntityContext.Gen3;
public override PersonalInfo PersonalInfo => PersonalTable.RS[Species];
public override IReadOnlyList ExtraBytes => Unused;
@@ -64,7 +64,7 @@ public override string OT_Name
get => StringConverter3.GetString(OT_Trash, Japanese);
set => StringConverter3.SetString(OT_Trash, value.AsSpan(), 7, Japanese, StringConverterOption.None);
}
- public override int MarkValue { get => SwapBits(Data[0x1B], 1, 2); protected set => Data[0x1B] = (byte)SwapBits(value, 1, 2); }
+ public override int MarkValue { get => SwapBits(Data[0x1B], 1, 2); set => Data[0x1B] = (byte)SwapBits(value, 1, 2); }
public ushort Checksum { get => ReadUInt16LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1C), value); }
public ushort Sanity { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), value); }
diff --git a/PKHeX.Core/PKM/PK4.cs b/PKHeX.Core/PKM/PK4.cs
index 1b6960ffe..a5b62b37c 100644
--- a/PKHeX.Core/PKM/PK4.cs
+++ b/PKHeX.Core/PKM/PK4.cs
@@ -16,7 +16,7 @@ public sealed class PK4 : G4PKM
public override int SIZE_PARTY => PokeCrypto.SIZE_4PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_4STORED;
- public override int Format => 4;
+ public override EntityContext Context => EntityContext.Gen4;
public override PersonalInfo PersonalInfo => PersonalTable.HGSS.GetFormEntry(Species, Form);
public PK4() : base(PokeCrypto.SIZE_4PARTY) { }
@@ -44,7 +44,7 @@ private static byte[] DecryptParty(byte[] data)
public override uint EXP { get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); }
public override int OT_Friendship { get => Data[0x14]; set => Data[0x14] = (byte)value; }
public override int Ability { get => Data[0x15]; set => Data[0x15] = (byte)value; }
- public override int MarkValue { get => Data[0x16]; protected set => Data[0x16] = (byte)value; }
+ public override int MarkValue { get => Data[0x16]; set => Data[0x16] = (byte)value; }
public override int Language { get => Data[0x17]; set => Data[0x17] = (byte)value; }
public override int EV_HP { get => Data[0x18]; set => Data[0x18] = (byte)value; }
public override int EV_ATK { get => Data[0x19]; set => Data[0x19] = (byte)value; }
diff --git a/PKHeX.Core/PKM/PK5.cs b/PKHeX.Core/PKM/PK5.cs
index 8c272e76b..e933ec88c 100644
--- a/PKHeX.Core/PKM/PK5.cs
+++ b/PKHeX.Core/PKM/PK5.cs
@@ -23,7 +23,7 @@ public sealed class PK5 : PKM, ISanityChecksum,
public override int SIZE_PARTY => PokeCrypto.SIZE_5PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_5STORED;
- public override int Format => 5;
+ public override EntityContext Context => EntityContext.Gen5;
public override PersonalInfo PersonalInfo => PersonalTable.B2W2.GetFormEntry(Species, Form);
public PK5() : base(PokeCrypto.SIZE_5PARTY) { }
@@ -65,7 +65,7 @@ private static byte[] DecryptParty(byte[] data)
public override uint EXP { get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); }
public override int OT_Friendship { get => Data[0x14]; set => Data[0x14] = (byte)value; }
public override int Ability { get => Data[0x15]; set => Data[0x15] = (byte)value; }
- public override int MarkValue { get => Data[0x16]; protected set => Data[0x16] = (byte)value; }
+ public override int MarkValue { get => Data[0x16]; set => Data[0x16] = (byte)value; }
public override int Language { get => Data[0x17]; set => Data[0x17] = (byte)value; }
public override int EV_HP { get => Data[0x18]; set => Data[0x18] = (byte)value; }
public override int EV_ATK { get => Data[0x19]; set => Data[0x19] = (byte)value; }
diff --git a/PKHeX.Core/PKM/PK6.cs b/PKHeX.Core/PKM/PK6.cs
index 4db405b05..f875e6d1c 100644
--- a/PKHeX.Core/PKM/PK6.cs
+++ b/PKHeX.Core/PKM/PK6.cs
@@ -15,7 +15,7 @@ public sealed class PK6 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetC
};
public override IReadOnlyList ExtraBytes => Unused;
- public override int Format => 6;
+ public override EntityContext Context => EntityContext.Gen6;
public override PersonalInfo PersonalInfo => PersonalTable.AO.GetFormEntry(Species, Form);
public PK6() : base(PokeCrypto.SIZE_6PARTY) { }
@@ -107,7 +107,7 @@ public override uint PID
public byte CNT_Smart { get => Data[0x27]; set => Data[0x27] = value; }
public byte CNT_Tough { get => Data[0x28]; set => Data[0x28] = value; }
public byte CNT_Sheen { get => Data[0x29]; set => Data[0x29] = value; }
- public override int MarkValue { get => Data[0x2A]; protected set => Data[0x2A] = (byte)value; }
+ public override int MarkValue { get => Data[0x2A]; set => Data[0x2A] = (byte)value; }
private byte PKRS { get => Data[0x2B]; set => Data[0x2B] = value; }
public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); }
public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | value << 4); }
diff --git a/PKHeX.Core/PKM/PK7.cs b/PKHeX.Core/PKM/PK7.cs
index f58e7a20d..df26fd11c 100644
--- a/PKHeX.Core/PKM/PK7.cs
+++ b/PKHeX.Core/PKM/PK7.cs
@@ -16,7 +16,7 @@ public sealed class PK7 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetC
};
public override IReadOnlyList ExtraBytes => Unused;
- public override int Format => 7;
+ public override EntityContext Context => EntityContext.Gen7;
public override PersonalInfo PersonalInfo => PersonalTable.USUM.GetFormEntry(Species, Form);
public PK7() : base(PokeCrypto.SIZE_6PARTY) { }
@@ -83,7 +83,7 @@ public override uint EXP
public override int Ability { get => Data[0x14]; set => Data[0x14] = (byte)value; }
public override int AbilityNumber { get => Data[0x15] & 7; set => Data[0x15] = (byte)((Data[0x15] & ~7) | (value & 7)); }
- public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x16)); protected set => WriteUInt16LittleEndian(Data.AsSpan(0x16), (ushort)value); }
+ public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x16)); set => WriteUInt16LittleEndian(Data.AsSpan(0x16), (ushort)value); }
public override uint PID
{
diff --git a/PKHeX.Core/PKM/PK8.cs b/PKHeX.Core/PKM/PK8.cs
index 76fd5ee44..1cb24a8ae 100644
--- a/PKHeX.Core/PKM/PK8.cs
+++ b/PKHeX.Core/PKM/PK8.cs
@@ -29,6 +29,8 @@ public sealed class PK8 : G8PKM
public override IReadOnlyList ExtraBytes => Unused;
public override PersonalInfo PersonalInfo => PersonalTable.SWSH.GetFormEntry(Species, Form);
+ public override bool IsNative => SWSH;
+ public override EntityContext Context => EntityContext.Gen8;
public PK8() => AffixedRibbon = -1; // 00 would make it show Kalos Champion :)
public PK8(byte[] data) : base(data) { }
@@ -104,5 +106,90 @@ private void TradeHT(ITrainerInfo tr)
public override int MaxItemID => Legal.MaxItemID_8;
public override int MaxBallID => Legal.MaxBallID_8;
public override int MaxGameID => Legal.MaxGameID_8;
+
+ public PB8 ConvertToPB8()
+ {
+ var pk = ConvertTo();
+ if (pk.Egg_Location == 0)
+ pk.Egg_Location = Locations.Default8bNone;
+ UnmapLocation(pk);
+ return pk;
+ }
+
+ public override PA8 ConvertToPA8()
+ {
+ var pk = base.ConvertToPA8();
+ UnmapLocation(pk);
+ return pk;
+ }
+
+ private static void UnmapLocation(PKM pk)
+ {
+ switch (pk.Met_Location)
+ {
+ case Locations.HOME_SWLA:
+ pk.Version = (int)GameVersion.PLA;
+ // Keep location due to bad transfer logic (official) -- server legal.
+ break;
+ case Locations.HOME_SWBD:
+ pk.Version = (int)GameVersion.BD;
+ pk.Met_Location = 0; // Load whatever value from the server. We don't know.
+ break;
+ case Locations.HOME_SHSP:
+ pk.Version = (int)GameVersion.SP;
+ pk.Met_Location = 0; // Load whatever value from the server. We don't know.
+ break;
+ }
+ }
+
+ public override void ResetMoves()
+ {
+ var learnsets = Legal.LevelUpSWSH;
+ var table = PersonalTable.SWSH;
+
+ var index = table.GetFormIndex(Species, Form);
+ var learn = learnsets[index];
+ Span moves = stackalloc int[4];
+ learn.SetEncounterMoves(CurrentLevel, moves);
+ SetMoves(moves);
+ this.SetMaximumPPCurrent(moves);
+ }
+
+ public override bool BDSP => Met_Location is Locations.HOME_SWBD or Locations.HOME_SHSP;
+ public override bool LA => Met_Location is Locations.HOME_SWLA;
+ public override bool HasOriginalMetLocation => base.HasOriginalMetLocation && !(BDSP || LA);
+
+ public void SanitizeImport()
+ {
+ var ver = Version;
+ if (ver is (int)GameVersion.SP)
+ {
+ Met_Location = Locations.HOME_SHSP;
+ Version = (int)GameVersion.SH;
+ if (Egg_Location != 0)
+ Egg_Location = Locations.HOME_SHSP;
+ }
+ else if (ver is (int)GameVersion.BD)
+ {
+ Met_Location = Locations.HOME_SWBD;
+ Version = (int)GameVersion.SW;
+ if (Egg_Location != 0)
+ Egg_Location = Locations.HOME_SWBD;
+ }
+ else if (ver is (int)GameVersion.PLA)
+ {
+ Met_Location = Locations.HOME_SWLA;
+ Version = (int)GameVersion.SW;
+ if (Egg_Location != 0)
+ Egg_Location = Locations.HOME_SWLA;
+ }
+ else if (ver > (int)GameVersion.PLA)
+ {
+ Met_Location = Met_Location <= Locations.HOME_SWLA ? Locations.HOME_SWLA : Locations.HOME_SWSHBDSPEgg;
+ }
+
+ if (Ball > (int)Core.Ball.Beast)
+ Ball = (int)Core.Ball.Poke;
+ }
}
}
diff --git a/PKHeX.Core/PKM/PKM.cs b/PKHeX.Core/PKM/PKM.cs
index 391cc1197..af7b2057d 100644
--- a/PKHeX.Core/PKM/PKM.cs
+++ b/PKHeX.Core/PKM/PKM.cs
@@ -42,7 +42,8 @@ public abstract class PKM : ISpeciesForm, ITrainerID, IGeneration, IShiny, ILang
public virtual Span HT_Trash => Span.Empty;
protected abstract byte[] Encrypt();
- public abstract int Format { get; }
+ public abstract EntityContext Context { get; }
+ public int Format => Context.Generation();
private byte[] Write()
{
@@ -119,7 +120,7 @@ private byte[] Write()
public abstract int TSV { get; }
public abstract int PSV { get; }
public abstract int Characteristic { get; }
- public abstract int MarkValue { get; protected set; }
+ public abstract int MarkValue { get; set; }
public abstract int Met_Location { get; set; }
public abstract int Egg_Location { get; set; }
public abstract int OT_Friendship { get; set; }
@@ -291,8 +292,8 @@ private void SetID7(int sid7, int tid7)
public bool VC2 => Version is >= (int)GD and <= (int)C;
public bool LGPE => Version is (int)GP or (int)GE;
public bool SWSH => Version is (int)SW or (int)SH;
- public bool BDSP => Version is (int)BD or (int)SP;
- public bool LA => Version is (int)PLA;
+ public virtual bool BDSP => Version is (int)BD or (int)SP;
+ public virtual bool LA => Version is (int)PLA;
public bool GO_LGPE => GO && Met_Location == Locations.GO7;
public bool GO_HOME => GO && Met_Location == Locations.GO8;
@@ -555,13 +556,13 @@ public virtual int HPType
}
// Misc Egg Facts
- public bool WasEgg => IsEgg || !Locations.IsNoneLocation((GameVersion)Version, Egg_Location);
+ public virtual bool WasEgg => IsEgg || Egg_Day != 0;
public bool WasTradedEgg => Egg_Location == GetTradedEggLocation();
public bool IsTradedEgg => Met_Location == GetTradedEggLocation();
private int GetTradedEggLocation() => Locations.TradedEggLocation(Generation, (GameVersion)Version);
public virtual bool IsUntraded => false;
- public bool IsNative => Generation == Format;
+ public virtual bool IsNative => Generation == Format;
public bool IsOriginValid => Species <= MaxSpeciesID;
///
@@ -1049,8 +1050,13 @@ public void TransferPropertiesWithReflection(PKM Destination)
// Only transfer declared properties not defined in PKM.cs but in the actual type
var srcType = GetType();
var destType = Destination.GetType();
+
var srcProperties = ReflectUtil.GetPropertiesCanWritePublicDeclared(srcType);
var destProperties = ReflectUtil.GetPropertiesCanWritePublicDeclared(destType);
+ while (srcType.BaseType != typeof(PKM))
+ srcProperties = srcProperties.Concat(ReflectUtil.GetPropertiesCanWritePublicDeclared(srcType = srcType.BaseType));
+ while (destType.BaseType != typeof(PKM))
+ destProperties = destProperties.Concat(ReflectUtil.GetPropertiesCanWritePublicDeclared(destType = destType.BaseType));
// Transfer properties in the order they are defined in the destination PKM format for best conversion
var shared = destProperties.Intersect(srcProperties);
diff --git a/PKHeX.Core/PKM/SK2.cs b/PKHeX.Core/PKM/SK2.cs
index 98d64437c..7da238513 100644
--- a/PKHeX.Core/PKM/SK2.cs
+++ b/PKHeX.Core/PKM/SK2.cs
@@ -17,7 +17,7 @@ public sealed class SK2 : GBPKM, ICaughtData2
public override bool Korean => false;
private const int StringLength = 12;
- public override int Format => 2;
+ public override EntityContext Context => EntityContext.Gen2;
public override int OTLength => StringLength;
public override int NickLength => StringLength;
diff --git a/PKHeX.Core/PKM/Shared/EntityFormat.cs b/PKHeX.Core/PKM/Shared/EntityFormat.cs
new file mode 100644
index 000000000..cbbc23f44
--- /dev/null
+++ b/PKHeX.Core/PKM/Shared/EntityFormat.cs
@@ -0,0 +1,59 @@
+using System;
+using static PKHeX.Core.EntityContext;
+
+namespace PKHeX.Core;
+
+///
+/// "Context" is an existence island; data format restricts the types of changes that can be made (such as evolving).
+///
+///
+/// Starting in the 8th generation games, entities can move between games with wildly different evolution rules.
+/// Previous implementations of a "Format Generation" were unable to differentiate if a class object was present in one of these different-rule contexts.
+/// The "Format Generation" is still a useful generalization to check if certain fields are present in the entity data, or if certain mutations are possible.
+///
+public enum EntityContext
+{
+ Invalid = 0,
+ Gen1 = 1,
+ Gen2 = 2,
+ Gen3 = 3,
+ Gen4 = 4,
+ Gen5 = 5,
+ Gen6 = 6,
+ Gen7 = 7,
+ Gen8 = 8,
+
+ SplitInvalid,
+ Gen7b,
+ Gen8a,
+ Gen8b,
+}
+
+public static class EntityContextExtensions
+{
+ public static int Generation(this EntityContext value) => value < SplitInvalid ? (int)value : value switch
+ {
+ Gen7b => 7,
+ Gen8a => 8,
+ Gen8b => 8,
+ _ => throw new ArgumentOutOfRangeException(nameof(value), value, null),
+ };
+
+ public static GameVersion GetSingleGameVersion(this EntityContext value) => value switch
+ {
+ Gen1 => GameVersion.RD,
+ Gen2 => GameVersion.C,
+ Gen3 => GameVersion.E,
+ Gen4 => GameVersion.SS,
+ Gen5 => GameVersion.W2,
+ Gen6 => GameVersion.AS,
+ Gen7 => GameVersion.UM,
+ Gen8 => GameVersion.SH,
+
+ Gen7b => GameVersion.GP,
+ Gen8a => GameVersion.PLA,
+ Gen8b => GameVersion.BD,
+
+ _ => throw new ArgumentOutOfRangeException(nameof(value), value, null),
+ };
+}
diff --git a/PKHeX.Core/PKM/Shared/GBPKM.cs b/PKHeX.Core/PKM/Shared/GBPKM.cs
index d5405463c..9ac13bd78 100644
--- a/PKHeX.Core/PKM/Shared/GBPKM.cs
+++ b/PKHeX.Core/PKM/Shared/GBPKM.cs
@@ -109,7 +109,7 @@ public sealed override int Gender
public sealed override int TSV => 0x0000;
public sealed override int PSV => 0xFFFF;
public sealed override int Characteristic => -1;
- public sealed override int MarkValue { get => 0; protected set { } }
+ public sealed override int MarkValue { get => 0; set { } }
public sealed override int Ability { get => -1; set { } }
public sealed override int CurrentHandler { get => 0; set { } }
public sealed override int Egg_Location { get => 0; set { } }
diff --git a/PKHeX.Core/PKM/Util/Conversion/EntityCompatibilitySetting.cs b/PKHeX.Core/PKM/Util/Conversion/EntityCompatibilitySetting.cs
new file mode 100644
index 000000000..4fa4d0ebe
--- /dev/null
+++ b/PKHeX.Core/PKM/Util/Conversion/EntityCompatibilitySetting.cs
@@ -0,0 +1,8 @@
+namespace PKHeX.Core;
+
+public enum EntityCompatibilitySetting
+{
+ AllowIncompatibleAll = -1,
+ DisallowIncompatible = 0,
+ AllowIncompatibleSane = 1,
+}
diff --git a/PKHeX.Core/PKM/Util/Conversion/EntityConverter.cs b/PKHeX.Core/PKM/Util/Conversion/EntityConverter.cs
index b089ca9c4..17170f7af 100644
--- a/PKHeX.Core/PKM/Util/Conversion/EntityConverter.cs
+++ b/PKHeX.Core/PKM/Util/Conversion/EntityConverter.cs
@@ -12,7 +12,17 @@ public static class EntityConverter
///
/// If a conversion method does not officially (legally) exist, then the program can try to convert via other means (illegal).
///
- public static bool AllowIncompatibleConversion { get; set; }
+ public static EntityCompatibilitySetting AllowIncompatibleConversion { get; set; }
+
+ ///
+ /// Toggles rejuvenating lost data if direct transfer does not know how to revert fields like Met Location and Ball.
+ ///
+ public static EntityRejuvenationSetting RejuvenateHOME { get; set; } = EntityRejuvenationSetting.MissingDataHOME;
+
+ ///
+ /// Post-conversion rejuvenation worker to restore lost values.
+ ///
+ public static IEntityRejuvenator RejuvenatorHOME { get; set; } = new LegalityRejuvenator();
///
/// Checks if the input file is capable of being converted to the desired format.
@@ -46,19 +56,19 @@ public static bool IsConvertibleToFormat(PKM pk, int format)
}
var pkm = ConvertPKM(pk, destType, fromType, out result);
- if (!AllowIncompatibleConversion || pkm != null)
- return pkm;
-
- if (pk is PK8 && destType == typeof(PB8))
+ if (pkm is not null)
{
- result = SuccessIncompatibleManual;
- return new PB8((byte[])pk.Data.Clone());
+ if (RejuvenateHOME != EntityRejuvenationSetting.None)
+ RejuvenatorHOME.Rejuvenate(pkm, pk);
+ return pkm;
}
- if (pk is PB8 && destType == typeof(PK8))
+ if (AllowIncompatibleConversion != EntityCompatibilitySetting.AllowIncompatibleAll)
{
- result = SuccessIncompatibleManual;
- return new PK8((byte[])pk.Data.Clone());
+ if (result is not NoTransferRoute)
+ return null;
+ if (AllowIncompatibleConversion != EntityCompatibilitySetting.AllowIncompatibleSane)
+ return null;
}
// Try Incompatible Conversion
@@ -72,7 +82,10 @@ public static bool IsConvertibleToFormat(PKM pk, int format)
private static PKM? ConvertPKM(PKM pk, Type destType, Type srcType, out EntityConverterResult result)
{
- result = CheckTransfer(pk);
+ result = CheckTransferOutbound(pk);
+ if (result != Success)
+ return null;
+ result = CheckTransferInbound(pk, destType);
if (result != Success)
return null;
@@ -107,8 +120,11 @@ public static bool IsConvertibleToFormat(PKM pk, int format)
PK3 pk3 when destType == typeof(XK3) => pk3.ConvertToXK3(),
PK4 pk4 when destType == typeof(BK4) => pk4.ConvertToBK4(),
- // Invalid
- PK2 { Species: > Legal.MaxSpeciesID_1 } => InvalidTransfer(out result, IncompatibleSpecies),
+ PB8 pb8 when destType == typeof(PK8) => pb8.ConvertToPK8(),
+ PK8 pk8 when destType == typeof(PB8) => pk8.ConvertToPB8(),
+ G8PKM pk8 when destType == typeof(PA8) => pk8.ConvertToPA8(),
+ PA8 pa8 when destType == typeof(PK8) => pa8.ConvertToPK8(),
+ PA8 pa8 when destType == typeof(PB8) => pa8.ConvertToPB8(),
// Sequential
PK1 pk1 => pk1.ConvertToPK2(),
@@ -136,19 +152,45 @@ public static bool IsConvertibleToFormat(PKM pk, int format)
}
///
- /// Checks to see if a PKM is transferable relative to in-game restrictions and .
+ /// Checks to see if a PKM is transferable out of a specific format.
///
/// PKM to convert
/// Indication if Not Transferable
- private static EntityConverterResult CheckTransfer(PKM pk) => pk switch
+ private static EntityConverterResult CheckTransferOutbound(PKM pk) => pk switch
{
PK4 { Species: (int)Species.Pichu, Form: not 0 } => IncompatibleForm,
PK6 { Species: (int)Species.Pikachu, Form: not 0 } => IncompatibleForm,
PB7 { Species: (int)Species.Pikachu, Form: not 0 } => IncompatibleForm,
PB7 { Species: (int)Species.Eevee, Form: not 0 } => IncompatibleForm,
+ PB8 { Species: (int)Species.Spinda } => IncompatibleSpecies, // Incorrect arrangement of spots (PID endianness)
+ PB8 { Species: (int)Species.Nincada } => IncompatibleSpecies, // Clone paranoia with Shedinja
_ => Success,
};
+ ///
+ /// Checks to see if a PKM is transferable into a specific format.
+ ///
+ /// PKM to convert
+ /// Type to convert to
+ /// Indication if Not Transferable
+ private static EntityConverterResult CheckTransferInbound(PKM pk, Type destType)
+ {
+ if (destType == typeof(PB8))
+ {
+ return pk.Species switch
+ {
+ (int)Species.Nincada => IncompatibleSpecies,
+ (int)Species.Spinda => IncompatibleSpecies,
+ _ => Success,
+ };
+ }
+
+ if (destType.Name[^1] == '1' && pk.Species > Legal.MaxSpeciesID_1)
+ return IncompatibleSpecies;
+
+ return Success;
+ }
+
///
/// Checks if the is compatible with the input , and makes any necessary modifications to force compatibility.
///
@@ -211,7 +253,7 @@ public static bool TryMakePKMCompatible(PKM pk, PKM target, out EntityConverterR
if (!IsConvertibleToFormat(pk, target.Format))
{
converted = target;
- if (!AllowIncompatibleConversion)
+ if (AllowIncompatibleConversion == EntityCompatibilitySetting.DisallowIncompatible)
{
result = NoTransferRoute;
return false;
diff --git a/PKHeX.Core/PKM/Util/Conversion/EntityRejuvenationSetting.cs b/PKHeX.Core/PKM/Util/Conversion/EntityRejuvenationSetting.cs
new file mode 100644
index 000000000..5c205babe
--- /dev/null
+++ b/PKHeX.Core/PKM/Util/Conversion/EntityRejuvenationSetting.cs
@@ -0,0 +1,8 @@
+namespace PKHeX.Core;
+
+public enum EntityRejuvenationSetting
+{
+ Custom = -1,
+ None,
+ MissingDataHOME,
+}
diff --git a/PKHeX.Core/PKM/Util/Conversion/IEntityRejuvenator.cs b/PKHeX.Core/PKM/Util/Conversion/IEntityRejuvenator.cs
new file mode 100644
index 000000000..62a89da95
--- /dev/null
+++ b/PKHeX.Core/PKM/Util/Conversion/IEntityRejuvenator.cs
@@ -0,0 +1,94 @@
+namespace PKHeX.Core;
+
+public interface IEntityRejuvenator
+{
+ public void Rejuvenate(PKM result, PKM original);
+}
+
+public class LegalityRejuvenator : IEntityRejuvenator
+{
+ public void Rejuvenate(PKM result, PKM original)
+ {
+ if (original is not PK8 pk8)
+ return;
+
+ var ver = result.Version;
+ if (ver is (int)GameVersion.BD or (int)GameVersion.SP)
+ RejuvenateBDSP(result, pk8);
+ else if (ver is (int)GameVersion.PLA)
+ RejuvenatePLA(result, pk8);
+ }
+
+ private static void RejuvenatePLA(PKM result, PK8 original)
+ {
+ var la = new LegalityAnalysis(original);
+ var enc = la.EncounterOriginal;
+ if (enc is EncounterInvalid)
+ return; // Won't work for Alphas
+
+ // No egg encounters. Always not-egg.
+ {
+ result.Met_Location = enc.Location;
+ result.Egg_Location = Locations.Default8bNone;
+ }
+
+ // Try again with rectified locations.
+ la = new LegalityAnalysis(result);
+ enc = la.EncounterOriginal;
+ if (result is PA8 pa8)
+ {
+ var relearn = la.GetSuggestedRelearnMoves(enc);
+ if (relearn.Count != 0)
+ {
+ for (int i = 0; i < relearn.Count; i++)
+ pa8.SetRelearnMove(i, relearn[i]);
+ }
+
+ pa8.ClearMoveShopFlags();
+ if (enc is IMasteryInitialMoveShop8 e)
+ e.SetInitialMastery(pa8);
+ pa8.SetMoveShopFlags(pa8);
+ }
+
+ if (result.Ball is >= (int)Ball.LAPoke and <= (int)Ball.LAOrigin)
+ return;
+ if (enc is IFixedBall { FixedBall: not Ball.None } f)
+ result.Ball = (int)f.FixedBall;
+ else
+ result.Ball = result.Species == (int)Species.Unown ? (int)Ball.LAJet : (int)Ball.LAPoke;
+ }
+
+ private static void RejuvenateBDSP(PKM result, PK8 original)
+ {
+ var la = new LegalityAnalysis(original);
+ var enc = la.EncounterOriginal;
+ if (enc is EncounterInvalid)
+ return;
+
+ if (enc is { EggEncounter: true })
+ {
+ result.Met_Location = Locations.HatchLocation8b;
+ result.Egg_Location = Locations.LinkTrade6NPC;
+ }
+ else
+ {
+ result.Met_Location = enc.Location;
+ result.Egg_Location = Locations.Default8bNone;
+ }
+
+ // Try again with rectified locations.
+ la = new LegalityAnalysis(result);
+ enc = la.EncounterOriginal;
+ var relearn = la.GetSuggestedRelearnMoves(enc);
+ if (relearn.Count != 0)
+ {
+ for (int i = 0; i < relearn.Count; i++)
+ result.SetRelearnMove(i, relearn[i]);
+ }
+ }
+}
+
+public sealed class EntityRejuvenatorDummy : IEntityRejuvenator
+{
+ public void Rejuvenate(PKM result, PKM original) { }
+}
diff --git a/PKHeX.Core/PKM/Util/EntityFormat.cs b/PKHeX.Core/PKM/Util/EntityFormat.cs
index 0f00548f4..936150bbc 100644
--- a/PKHeX.Core/PKM/Util/EntityFormat.cs
+++ b/PKHeX.Core/PKM/Util/EntityFormat.cs
@@ -89,7 +89,7 @@ private static EntityFormatDetected GetFormat67(ReadOnlySpan data)
// assumes decrypted state
private static EntityFormatDetected GetFormat8(ReadOnlySpan data)
{
- if (data[0xDE] is (byte)GameVersion.BD or (byte)GameVersion.SP)
+ if (data[0xDE] >= (byte)GameVersion.PLA)
return FormatPB8;
return FormatPK8;
}
@@ -136,7 +136,7 @@ private static EntityFormatDetected IsFormatReally7(PK6 pk)
{
if (pk.Version > Legal.MaxGameID_6)
{
- if (pk.Version is ((int)GameVersion.GP or (int)GameVersion.GE))
+ if (pk.Version is ((int)GameVersion.GP or (int)GameVersion.GE or (int)GameVersion.GO))
return FormatPB7;
return FormatPK7;
}
diff --git a/PKHeX.Core/PKM/XK3.cs b/PKHeX.Core/PKM/XK3.cs
index ccd6bb13f..36533b545 100644
--- a/PKHeX.Core/PKM/XK3.cs
+++ b/PKHeX.Core/PKM/XK3.cs
@@ -19,7 +19,7 @@ public sealed class XK3 : G3PKM, IShadowPKM
public override int SIZE_PARTY => PokeCrypto.SIZE_3XSTORED;
public override int SIZE_STORED => PokeCrypto.SIZE_3XSTORED;
- public override int Format => 3;
+ public override EntityContext Context => EntityContext.Gen3;
public override PersonalInfo PersonalInfo => PersonalTable.RS[Species];
public XK3(byte[] data) : base(data) { }
public XK3() : base(PokeCrypto.SIZE_3XSTORED) { }
@@ -46,7 +46,7 @@ public sealed class XK3 : G3PKM, IShadowPKM
public override int Stat_Level { get => Data[0x11]; set => Data[0x11] = (byte)value; }
public override byte CNT_Sheen { get => Data[0x12]; set => Data[0x12] = value; }
public override int PKRS_Strain { get => Data[0x13] & 0xF; set => Data[0x13] = (byte)(value & 0xF); }
- public override int MarkValue { get => SwapBits(Data[0x14], 1, 2); protected set => Data[0x14] = (byte)SwapBits(value, 1, 2); }
+ public override int MarkValue { get => SwapBits(Data[0x14], 1, 2); set => Data[0x14] = (byte)SwapBits(value, 1, 2); }
public override int PKRS_Days { get => Math.Max((sbyte)Data[0x15], (sbyte)0); set => Data[0x15] = (byte)(value == 0 ? 0xFF : value & 0xF); }
// 0x16-0x1C Battle Related
private int XDPKMFLAGS { get => Data[0x1D]; set => Data[0x1D] = (byte)value; }
diff --git a/PKHeX.Core/PersonalInfo/PersonalInfo.cs b/PKHeX.Core/PersonalInfo/PersonalInfo.cs
index 137670a40..49c16e15d 100644
--- a/PKHeX.Core/PersonalInfo/PersonalInfo.cs
+++ b/PKHeX.Core/PersonalInfo/PersonalInfo.cs
@@ -208,6 +208,8 @@ public abstract class PersonalInfo
///
public bool[][] SpecialTutors = Array.Empty();
+ public virtual bool IsPresentInGame { get => HP != 0; set { } }
+
protected static bool[] GetBits(ReadOnlySpan data)
{
bool[] result = new bool[data.Length << 3];
diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoBDSP.cs b/PKHeX.Core/PersonalInfo/PersonalInfoBDSP.cs
index 638387f94..32d7433f7 100644
--- a/PKHeX.Core/PersonalInfo/PersonalInfoBDSP.cs
+++ b/PKHeX.Core/PersonalInfo/PersonalInfoBDSP.cs
@@ -46,7 +46,7 @@ public sealed class PersonalInfoBDSP : PersonalInfo
public override int FormSprite { get => 0; set { } } // No longer defined in personal
public override int FormCount { get => Data[0x20]; set => Data[0x20] = (byte)value; }
public override int Color { get => Data[0x21] & 0x3F; set => Data[0x21] = (byte)((Data[0x21] & 0xC0) | (value & 0x3F)); }
- public bool IsPresentInGame { get => ((Data[0x21] >> 6) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x40) | (value ? 0x40 : 0)); }
+ public override bool IsPresentInGame { get => ((Data[0x21] >> 6) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x40) | (value ? 0x40 : 0)); }
public bool SpriteForm { get => false; set { } } // Unspecified in table
public override int BaseEXP { get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); }
public override int Height { get => ReadUInt16LittleEndian(Data.AsSpan(0x24)); set => WriteUInt16LittleEndian(Data.AsSpan(0x24), (ushort)value); }
diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoLA.cs b/PKHeX.Core/PersonalInfo/PersonalInfoLA.cs
index 6a564791b..53c1a766b 100644
--- a/PKHeX.Core/PersonalInfo/PersonalInfoLA.cs
+++ b/PKHeX.Core/PersonalInfo/PersonalInfoLA.cs
@@ -66,7 +66,7 @@ public override byte[] Write()
public override int FormSprite { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)value); } // ???
public override int FormCount { get => Data[0x20]; set => Data[0x20] = (byte)value; }
public override int Color { get => Data[0x21] & 0x3F; set => Data[0x21] = (byte)((Data[0x21] & 0xC0) | (value & 0x3F)); }
- public bool IsPresentInGame { get => ((Data[0x21] >> 6) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x40) | (value ? 0x40 : 0)); }
+ public override bool IsPresentInGame { get => ((Data[0x21] >> 6) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x40) | (value ? 0x40 : 0)); }
public bool SpriteForm { get => ((Data[0x21] >> 7) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x80) | (value ? 0x80 : 0)); }
public override int BaseEXP { get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); }
public override int Height { get => ReadUInt16LittleEndian(Data.AsSpan(0x24)); set => WriteUInt16LittleEndian(Data.AsSpan(0x24), (ushort)value); }
diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoSWSH.cs b/PKHeX.Core/PersonalInfo/PersonalInfoSWSH.cs
index b4f374bd0..67cbaa8ed 100644
--- a/PKHeX.Core/PersonalInfo/PersonalInfoSWSH.cs
+++ b/PKHeX.Core/PersonalInfo/PersonalInfoSWSH.cs
@@ -86,7 +86,7 @@ public override byte[] Write()
public override int FormSprite { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)value); } // ???
public override int FormCount { get => Data[0x20]; set => Data[0x20] = (byte)value; }
public override int Color { get => Data[0x21] & 0x3F; set => Data[0x21] = (byte)((Data[0x21] & 0xC0) | (value & 0x3F)); }
- public bool IsPresentInGame { get => ((Data[0x21] >> 6) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x40) | (value ? 0x40 : 0)); }
+ public override bool IsPresentInGame { get => ((Data[0x21] >> 6) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x40) | (value ? 0x40 : 0)); }
public bool SpriteForm { get => ((Data[0x21] >> 7) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x80) | (value ? 0x80 : 0)); }
public override int BaseEXP { get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); }
public override int Height { get => ReadUInt16LittleEndian(Data.AsSpan(0x24)); set => WriteUInt16LittleEndian(Data.AsSpan(0x24), (ushort)value); }
diff --git a/PKHeX.Core/PersonalInfo/PersonalTable.cs b/PKHeX.Core/PersonalInfo/PersonalTable.cs
index a041d088f..5b7a3a2f6 100644
--- a/PKHeX.Core/PersonalInfo/PersonalTable.cs
+++ b/PKHeX.Core/PersonalInfo/PersonalTable.cs
@@ -386,15 +386,23 @@ public bool IsSpeciesInGame(int species)
if ((uint)species > MaxSpeciesID)
return false;
var form0 = Table[species];
- if (form0.HP != 0)
+ if (form0.IsPresentInGame)
return true;
var fc = form0.FormCount;
for (int i = 1; i < fc; i++)
{
- if (GetFormEntry(species, i).HP != 0)
+ if (GetFormEntry(species, i).IsPresentInGame)
return true;
}
return false;
}
+
+ public bool IsPresentInGame(int species, int form)
+ {
+ if ((uint)species > MaxSpeciesID)
+ return false;
+ var entry = GetFormEntry(species, form);
+ return entry.IsPresentInGame;
+ }
}
}
diff --git a/PKHeX.Core/Resources/legality/wild/encounter_go_home.pkl b/PKHeX.Core/Resources/legality/wild/encounter_go_home.pkl
index 3793fedec..0c255a81f 100644
Binary files a/PKHeX.Core/Resources/legality/wild/encounter_go_home.pkl and b/PKHeX.Core/Resources/legality/wild/encounter_go_home.pkl differ
diff --git a/PKHeX.Core/Resources/legality/wild/encounter_go_lgpe.pkl b/PKHeX.Core/Resources/legality/wild/encounter_go_lgpe.pkl
index 1bf018711..99f953a6d 100644
Binary files a/PKHeX.Core/Resources/legality/wild/encounter_go_lgpe.pkl and b/PKHeX.Core/Resources/legality/wild/encounter_go_lgpe.pkl differ
diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon3.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon3.cs
index 785ed3302..4caae8b9f 100644
--- a/PKHeX.Core/Ribbons/IRibbonSetCommon3.cs
+++ b/PKHeX.Core/Ribbons/IRibbonSetCommon3.cs
@@ -26,5 +26,12 @@ internal static bool[] RibbonBits(this IRibbonSetCommon3 set)
}
internal static string[] RibbonNames(this IRibbonSetCommon3 _) => RibbonSetNamesCommon3;
+
+ internal static void CopyRibbonSetCommon3(this IRibbonSetCommon3 set, IRibbonSetCommon3 dest)
+ {
+ dest.RibbonChampionG3 = set.RibbonChampionG3;
+ dest.RibbonArtist = set.RibbonArtist;
+ dest.RibbonEffort = set.RibbonEffort;
+ }
}
}
diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon4.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon4.cs
index 192f611fe..4d45fc195 100644
--- a/PKHeX.Core/Ribbons/IRibbonSetCommon4.cs
+++ b/PKHeX.Core/Ribbons/IRibbonSetCommon4.cs
@@ -77,5 +77,23 @@ internal static bool[] RibbonBitsDaily(this IRibbonSetCommon4 set)
}
internal static string[] RibbonNamesDaily(this IRibbonSetCommon4 _) => RibbonSetNamesCommon4Daily;
+
+ internal static void CopyRibbonSetCommon4(this IRibbonSetCommon4 set, IRibbonSetCommon4 dest)
+ {
+ dest.RibbonChampionSinnoh = set.RibbonChampionSinnoh;
+ dest.RibbonAlert = set.RibbonAlert;
+ dest.RibbonShock = set.RibbonShock;
+ dest.RibbonDowncast = set.RibbonDowncast;
+ dest.RibbonCareless = set.RibbonCareless;
+ dest.RibbonRelax = set.RibbonRelax;
+ dest.RibbonSnooze = set.RibbonSnooze;
+ dest.RibbonSmile = set.RibbonSmile;
+ dest.RibbonGorgeous = set.RibbonGorgeous;
+ dest.RibbonRoyal = set.RibbonRoyal;
+ dest.RibbonGorgeousRoyal = set.RibbonGorgeousRoyal;
+ dest.RibbonFootprint = set.RibbonFootprint;
+ dest.RibbonRecord = set.RibbonRecord;
+ dest.RibbonLegend = set.RibbonLegend;
+ }
}
}
diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon6.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon6.cs
index 0ecc4d96a..47eae3089 100644
--- a/PKHeX.Core/Ribbons/IRibbonSetCommon6.cs
+++ b/PKHeX.Core/Ribbons/IRibbonSetCommon6.cs
@@ -72,5 +72,23 @@ internal static bool[] RibbonBitsContest(this IRibbonSetCommon6 set)
internal static string[] RibbonNamesBool(this IRibbonSetCommon6 _) => RibbonSetNamesCommon6Bool;
internal static string[] RibbonNamesContest(this IRibbonSetCommon6 _) => RibbonSetNamesCommon6Contest;
+
+ internal static void CopyRibbonSetCommon6(this IRibbonSetCommon6 set, IRibbonSetCommon6 dest)
+ {
+ dest.RibbonChampionKalos = set.RibbonChampionKalos;
+ dest.RibbonChampionG6Hoenn = set.RibbonChampionG6Hoenn;
+ dest.RibbonBestFriends = set.RibbonBestFriends;
+ dest.RibbonTraining = set.RibbonTraining;
+ dest.RibbonBattlerSkillful = set.RibbonBattlerSkillful;
+ dest.RibbonBattlerExpert = set.RibbonBattlerExpert;
+ dest.RibbonContestStar = set.RibbonContestStar;
+ dest.RibbonMasterCoolness = set.RibbonMasterCoolness;
+ dest.RibbonMasterBeauty = set.RibbonMasterBeauty;
+ dest.RibbonMasterCuteness = set.RibbonMasterCuteness;
+ dest.RibbonMasterCleverness = set.RibbonMasterCleverness;
+ dest.RibbonMasterToughness = set.RibbonMasterToughness;
+ dest.RibbonCountMemoryContest = set.RibbonCountMemoryContest;
+ dest.RibbonCountMemoryBattle = set.RibbonCountMemoryBattle;
+ }
}
}
diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon7.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon7.cs
index d825b4463..96a5782a3 100644
--- a/PKHeX.Core/Ribbons/IRibbonSetCommon7.cs
+++ b/PKHeX.Core/Ribbons/IRibbonSetCommon7.cs
@@ -29,5 +29,13 @@ internal static bool[] RibbonBits(this IRibbonSetCommon7 set)
}
internal static string[] RibbonNames(this IRibbonSetCommon7 _) => RibbonSetNamesCommon7;
+
+ internal static void CopyRibbonSetCommon7(this IRibbonSetCommon7 set, IRibbonSetCommon7 dest)
+ {
+ dest.RibbonChampionAlola = set.RibbonChampionAlola;
+ dest.RibbonBattleRoyale = set.RibbonBattleRoyale;
+ dest.RibbonBattleTreeGreat = set.RibbonBattleTreeGreat;
+ dest.RibbonBattleTreeMaster = set.RibbonBattleTreeMaster;
+ }
}
}
diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon8.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon8.cs
index 0fd972697..8098e2035 100644
--- a/PKHeX.Core/Ribbons/IRibbonSetCommon8.cs
+++ b/PKHeX.Core/Ribbons/IRibbonSetCommon8.cs
@@ -32,5 +32,14 @@ internal static bool[] RibbonBits(this IRibbonSetCommon8 set)
}
internal static string[] RibbonNames(this IRibbonSetCommon8 _) => RibbonSetNamesCommon8;
+
+ internal static void CopyRibbonSetCommon8(this IRibbonSetCommon8 set, IRibbonSetCommon8 dest)
+ {
+ dest.RibbonChampionGalar = set.RibbonChampionGalar;
+ dest.RibbonTowerMaster = set.RibbonTowerMaster;
+ dest.RibbonMasterRank = set.RibbonMasterRank;
+ dest.RibbonTwinklingStar = set.RibbonTwinklingStar;
+ dest.RibbonPioneer = set.RibbonPioneer;
+ }
}
}
\ No newline at end of file
diff --git a/PKHeX.Core/Ribbons/IRibbonSetEvent3.cs b/PKHeX.Core/Ribbons/IRibbonSetEvent3.cs
index b4dcbb11e..cd08c81cf 100644
--- a/PKHeX.Core/Ribbons/IRibbonSetEvent3.cs
+++ b/PKHeX.Core/Ribbons/IRibbonSetEvent3.cs
@@ -33,5 +33,15 @@ internal static bool[] RibbonBits(this IRibbonSetEvent3 set)
}
internal static string[] RibbonNames(this IRibbonSetEvent3 _) => RibbonSetNamesEvent3;
+
+ internal static void CopyRibbonSetEvent3(this IRibbonSetEvent3 set, IRibbonSetEvent3 dest)
+ {
+ dest.RibbonEarth = set.RibbonEarth;
+ dest.RibbonNational = set.RibbonNational;
+ dest.RibbonCountry = set.RibbonCountry;
+ dest.RibbonChampionBattle = set.RibbonChampionBattle;
+ dest.RibbonChampionRegional = set.RibbonChampionRegional;
+ dest.RibbonChampionNational = set.RibbonChampionNational;
+ }
}
}
diff --git a/PKHeX.Core/Ribbons/IRibbonSetEvent4.cs b/PKHeX.Core/Ribbons/IRibbonSetEvent4.cs
index 08101b604..6d25b7e43 100644
--- a/PKHeX.Core/Ribbons/IRibbonSetEvent4.cs
+++ b/PKHeX.Core/Ribbons/IRibbonSetEvent4.cs
@@ -40,5 +40,18 @@ internal static bool[] RibbonBits(this IRibbonSetEvent4 set)
}
internal static string[] RibbonNames(this IRibbonSetEvent4 _) => RibbonSetNamesEvent4;
+
+ internal static void CopyRibbonSetEvent4(this IRibbonSetEvent4 set, IRibbonSetEvent4 dest)
+ {
+ dest.RibbonClassic = set.RibbonClassic;
+ dest.RibbonWishing = set.RibbonWishing;
+ dest.RibbonPremier = set.RibbonPremier;
+ dest.RibbonEvent = set.RibbonEvent;
+ dest.RibbonBirthday = set.RibbonBirthday;
+ dest.RibbonSpecial = set.RibbonSpecial;
+ dest.RibbonWorld = set.RibbonWorld;
+ dest.RibbonChampionWorld = set.RibbonChampionWorld;
+ dest.RibbonSouvenir = set.RibbonSouvenir;
+ }
}
}
diff --git a/PKHeX.Core/Ribbons/IRibbonSetMark8.cs b/PKHeX.Core/Ribbons/IRibbonSetMark8.cs
index e64c31bb2..c567a9b67 100644
--- a/PKHeX.Core/Ribbons/IRibbonSetMark8.cs
+++ b/PKHeX.Core/Ribbons/IRibbonSetMark8.cs
@@ -162,5 +162,54 @@ internal static bool[] RibbonBits(this IRibbonSetMark8 set)
}
internal static string[] RibbonNames(this IRibbonSetMark8 _) => RibbonSetNamesMark8;
+
+ internal static void CopyRibbonSetMark8(this IRibbonSetMark8 set, IRibbonSetMark8 dest)
+ {
+ dest.RibbonMarkLunchtime = set.RibbonMarkLunchtime;
+ dest.RibbonMarkSleepyTime = set.RibbonMarkSleepyTime;
+ dest.RibbonMarkDusk = set.RibbonMarkDusk;
+ dest.RibbonMarkDawn = set.RibbonMarkDawn;
+ dest.RibbonMarkCloudy = set.RibbonMarkCloudy;
+ dest.RibbonMarkRainy = set.RibbonMarkRainy;
+ dest.RibbonMarkStormy = set.RibbonMarkStormy;
+ dest.RibbonMarkSnowy = set.RibbonMarkSnowy;
+ dest.RibbonMarkBlizzard = set.RibbonMarkBlizzard;
+ dest.RibbonMarkDry = set.RibbonMarkDry;
+ dest.RibbonMarkSandstorm = set.RibbonMarkSandstorm;
+ dest.RibbonMarkMisty = set.RibbonMarkMisty;
+ dest.RibbonMarkDestiny = set.RibbonMarkDestiny;
+ dest.RibbonMarkFishing = set.RibbonMarkFishing;
+ dest.RibbonMarkCurry = set.RibbonMarkCurry;
+ dest.RibbonMarkUncommon = set.RibbonMarkUncommon;
+ dest.RibbonMarkRare = set.RibbonMarkRare;
+ dest.RibbonMarkRowdy = set.RibbonMarkRowdy;
+ dest.RibbonMarkAbsentMinded = set.RibbonMarkAbsentMinded;
+ dest.RibbonMarkJittery = set.RibbonMarkJittery;
+ dest.RibbonMarkExcited = set.RibbonMarkExcited;
+ dest.RibbonMarkCharismatic = set.RibbonMarkCharismatic;
+ dest.RibbonMarkCalmness = set.RibbonMarkCalmness;
+ dest.RibbonMarkIntense = set.RibbonMarkIntense;
+ dest.RibbonMarkZonedOut = set.RibbonMarkZonedOut;
+ dest.RibbonMarkJoyful = set.RibbonMarkJoyful;
+ dest.RibbonMarkAngry = set.RibbonMarkAngry;
+ dest.RibbonMarkSmiley = set.RibbonMarkSmiley;
+ dest.RibbonMarkTeary = set.RibbonMarkTeary;
+ dest.RibbonMarkUpbeat = set.RibbonMarkUpbeat;
+ dest.RibbonMarkPeeved = set.RibbonMarkPeeved;
+ dest.RibbonMarkIntellectual = set.RibbonMarkIntellectual;
+ dest.RibbonMarkFerocious = set.RibbonMarkFerocious;
+ dest.RibbonMarkCrafty = set.RibbonMarkCrafty;
+ dest.RibbonMarkScowling = set.RibbonMarkScowling;
+ dest.RibbonMarkKindly = set.RibbonMarkKindly;
+ dest.RibbonMarkFlustered = set.RibbonMarkFlustered;
+ dest.RibbonMarkPumpedUp = set.RibbonMarkPumpedUp;
+ dest.RibbonMarkZeroEnergy = set.RibbonMarkZeroEnergy;
+ dest.RibbonMarkPrideful = set.RibbonMarkPrideful;
+ dest.RibbonMarkUnsure = set.RibbonMarkUnsure;
+ dest.RibbonMarkHumble = set.RibbonMarkHumble;
+ dest.RibbonMarkThorny = set.RibbonMarkThorny;
+ dest.RibbonMarkVigor = set.RibbonMarkVigor;
+ dest.RibbonMarkSlump = set.RibbonMarkSlump;
+ }
}
}
\ No newline at end of file
diff --git a/PKHeX.Core/Ribbons/RibbonIndex.cs b/PKHeX.Core/Ribbons/RibbonIndex.cs
index 633fefa0e..4d4f93146 100644
--- a/PKHeX.Core/Ribbons/RibbonIndex.cs
+++ b/PKHeX.Core/Ribbons/RibbonIndex.cs
@@ -107,6 +107,8 @@ public enum RibbonIndex
Pioneer,
TwinklingStar,
+
+ MAX_COUNT,
}
public static class RibbonIndexExtensions
diff --git a/PKHeX.Core/Saves/SAV8BS.cs b/PKHeX.Core/Saves/SAV8BS.cs
index acf8cba81..e3e6f7c25 100644
--- a/PKHeX.Core/Saves/SAV8BS.cs
+++ b/PKHeX.Core/Saves/SAV8BS.cs
@@ -116,7 +116,7 @@ private void Initialize()
public override int MaxSpeciesID => Legal.MaxSpeciesID_8b;
public override int MaxItemID => Legal.MaxItemID_8b;
public override int MaxBallID => Legal.MaxBallID_8b;
- public override int MaxGameID => Legal.MaxGameID_8b;
+ public override int MaxGameID => Legal.MaxGameID_8a;
public override int MaxAbilityID => Legal.MaxAbilityID_8b;
public bool HasFirstSaveFileExpansion => (Gem8Version)SaveRevision >= Gem8Version.V1_1;
diff --git a/PKHeX.Core/Saves/SAV8LA.cs b/PKHeX.Core/Saves/SAV8LA.cs
index d830c0e0e..dac236164 100644
--- a/PKHeX.Core/Saves/SAV8LA.cs
+++ b/PKHeX.Core/Saves/SAV8LA.cs
@@ -145,6 +145,14 @@ public override int PartyCount
protected set => PartyInfo.PartyCount = value;
}
+ protected override void SetPKM(PKM pkm, bool isParty = false)
+ {
+ var pk = (PA8)pkm;
+ // Apply to this Save File
+ pk.Trade(this);
+ pkm.RefreshChecksum();
+ }
+
// Zukan
protected override void SetDex(PKM pkm)
{
diff --git a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan7b.cs b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan7b.cs
index 4e205e39c..1a1376ac6 100644
--- a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan7b.cs
+++ b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan7b.cs
@@ -102,7 +102,7 @@ private void SetSizeData(PB7 pkm)
private void SetSizeData(PB7 pkm, DexSizeType group)
{
- var tree = EvolutionTree.GetEvolutionTree(pkm, 7);
+ var tree = EvolutionTree.Evolves7b;
int species = pkm.Species;
int form = pkm.Form;
diff --git a/PKHeX.Core/Saves/Util/Checksums.cs b/PKHeX.Core/Saves/Util/Checksums.cs
index f404b7f29..a8054a71a 100644
--- a/PKHeX.Core/Saves/Util/Checksums.cs
+++ b/PKHeX.Core/Saves/Util/Checksums.cs
@@ -61,6 +61,42 @@ public static ushort CRC16_CCITT(ReadOnlySpan data)
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040,
};
+ private static readonly uint[] crc32 =
+ {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
+ };
+
/// Calculates the 16bit checksum over an input byte array.
/// Input byte array
/// Initial value for checksum
@@ -93,6 +129,26 @@ public static ushort CheckSum32(ReadOnlySpan data, uint initial = 0)
return (ushort)(val + (val >> 16));
}
+ /// Calculates the 32bit checksum over an input byte array.
+ /// Input byte array
+ /// Initial value for checksum
+ /// Checksum
+ private static uint CRC32(ReadOnlySpan data, uint initial)
+ {
+ uint chk = initial;
+ foreach (var b in data)
+ chk = (crc32[(b ^ chk) & 0xFF] ^ chk >> 8);
+ return chk;
+ }
+
+ /// Calculates the 16bit checksum over an input byte array.
+ /// Input byte array
+ public static uint CRC32Invert(ReadOnlySpan data) => ~CRC32(data, unchecked((uint)~0));
+
+ /// Calculates the 16bit checksum over an input byte array.
+ /// Input byte array
+ public static uint CRC32NoInvert(ReadOnlySpan data) => CRC32(data, 0);
+
/// Calculates the 16bit checksum over an input byte array. Used in N64 Stadium save files.
/// Input byte array
/// Initial value for checksum
diff --git a/PKHeX.Core/Saves/Util/SaveUtil.cs b/PKHeX.Core/Saves/Util/SaveUtil.cs
index dad2b1ba7..ccd8b3c46 100644
--- a/PKHeX.Core/Saves/Util/SaveUtil.cs
+++ b/PKHeX.Core/Saves/Util/SaveUtil.cs
@@ -780,13 +780,13 @@ public static SaveFile GetBlankSAV(GameVersion game, string trainerName, Languag
///
/// Creates an instance of a SaveFile with a blank base.
///
- /// Generation of the Save File.
+ /// Context of the Save File.
/// Trainer Name
/// Save file language to initialize for
/// Save File for that generation.
- public static SaveFile GetBlankSAV(int generation, string trainerName, LanguageID language = LanguageID.English)
+ public static SaveFile GetBlankSAV(EntityContext context, string trainerName, LanguageID language = LanguageID.English)
{
- var ver = GameUtil.GetVersion(generation);
+ var ver = context.GetSingleGameVersion();
return GetBlankSAV(ver, trainerName, language);
}
diff --git a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball27.png b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball27.png
index 5d554ffa6..6ce191a3f 100644
Binary files a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball27.png and b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball27.png differ
diff --git a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball28.png b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball28.png
index 9ab1f1872..84820bb3c 100644
Binary files a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball28.png and b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball28.png differ
diff --git a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball29.png b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball29.png
index 374f4f1ca..968c3d772 100644
Binary files a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball29.png and b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball29.png differ
diff --git a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball30.png b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball30.png
index 9d33919f0..03b9698b0 100644
Binary files a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball30.png and b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball30.png differ
diff --git a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball31.png b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball31.png
index 3f0a015fe..f76fd1123 100644
Binary files a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball31.png and b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball31.png differ
diff --git a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball32.png b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball32.png
index f8fbb05d8..8579c4347 100644
Binary files a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball32.png and b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball32.png differ
diff --git a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball33.png b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball33.png
index 9c55abba5..997828803 100644
Binary files a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball33.png and b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball33.png differ
diff --git a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball34.png b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball34.png
index 91f030558..6dbf83263 100644
Binary files a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball34.png and b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball34.png differ
diff --git a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball35.png b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball35.png
index 41445939a..fdbe7be47 100644
Binary files a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball35.png and b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball35.png differ
diff --git a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball36.png b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball36.png
index 2e6a75f71..d30708f1d 100644
Binary files a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball36.png and b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball36.png differ
diff --git a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball37.png b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball37.png
index 69702d0df..48f5c942e 100644
Binary files a/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball37.png and b/PKHeX.Drawing.PokeSprite/Resources/img/ball/_ball37.png differ
diff --git a/PKHeX.WinForms/Controls/PKM Editor/LoadSave.cs b/PKHeX.WinForms/Controls/PKM Editor/LoadSave.cs
index 72a9bdcc9..368b54259 100644
--- a/PKHeX.WinForms/Controls/PKM Editor/LoadSave.cs
+++ b/PKHeX.WinForms/Controls/PKM Editor/LoadSave.cs
@@ -255,7 +255,7 @@ private void SaveMisc3(PKM pk)
private void LoadMisc4(PKM pk)
{
CAL_MetDate.Value = pk.MetDate ?? new DateTime(2000, 1, 1);
- if (Locations.IsNoneLocation((GameVersion)pk.Version, pk.Egg_Location))
+ if (!Legal.IsMetAsEgg(pk))
{
CHK_AsEgg.Checked = GB_EggConditions.Enabled = false;
CAL_EggDate.Value = new DateTime(2000, 01, 01);
@@ -271,21 +271,22 @@ private void LoadMisc4(PKM pk)
private void SaveMisc4(PKM pk)
{
- pk.MetDate = CAL_MetDate.Value;
-
- // Default Dates
- DateTime? egg_date = null;
- int egg_location = Locations.GetNoneLocation((GameVersion)pk.Version);
if (CHK_AsEgg.Checked) // If encountered as an egg, load the Egg Met data from fields.
{
- egg_date = CAL_EggDate.Value;
- egg_location = WinFormsUtil.GetIndex(CB_EggLocation);
+ pk.EggMetDate = CAL_EggDate.Value;
+ pk.Egg_Location = WinFormsUtil.GetIndex(CB_EggLocation);
}
- // Egg Met Data
- pk.EggMetDate = egg_date;
- pk.Egg_Location = egg_location;
- if (pk.IsEgg && Locations.IsNoneLocation((GameVersion)pk.Version, pk.Met_Location)) // If still an egg, it has no hatch location/date. Zero it!
- pk.MetDate = null;
+ else // Default Dates
+ {
+ pk.EggMetDate = null; // clear
+ pk.Egg_Location = LocationEdits.GetNoneLocation(pk);
+ }
+
+ // Met Data
+ if (pk.IsEgg && pk.Met_Location == LocationEdits.GetNoneLocation(pk)) // If still an egg, it has no hatch location/date. Zero it!
+ pk.MetDate = null; // clear
+ else
+ pk.MetDate = CAL_MetDate.Value;
pk.Ability = WinFormsUtil.GetIndex(HaX ? DEV_Ability : CB_Ability);
}
diff --git a/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs b/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs
index 15b895db8..c053f74dc 100644
--- a/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs
+++ b/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs
@@ -1198,7 +1198,7 @@ private void ReloadMetLocations(GameVersion version, int format)
int metLoc = EncounterSuggestion.GetSuggestedTransferLocation(Entity);
int eggLoc = CHK_AsEgg.Checked
? EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(format, version)
- : Locations.GetNoneLocation(version);
+ : LocationEdits.GetNoneLocation(Entity);
CB_MetLocation.SelectedValue = Math.Max(0, metLoc);
CB_EggLocation.SelectedValue = eggLoc;
@@ -1384,7 +1384,7 @@ private void UpdateIsEgg(object sender, EventArgs e)
{
var sav = SaveFileRequested.Invoke(this, e);
bool isTraded = sav.OT != TB_OT.Text || sav.TID != Entity.TID || sav.SID != Entity.SID;
- var loc = isTraded ? Locations.TradedEggLocation(sav.Generation, sav.Version) : Locations.GetNoneLocation(sav.Version);
+ var loc = isTraded ? Locations.TradedEggLocation(sav.Generation, sav.Version) : LocationEdits.GetNoneLocation(Entity);
CB_MetLocation.SelectedValue = loc;
}
else if (Entity.Format == 3)
@@ -1445,7 +1445,7 @@ private void UpdateMetAsEgg(object sender, EventArgs e)
// Remove egg met data
CHK_IsEgg.Checked = false;
CAL_EggDate.Value = new DateTime(2000, 01, 01);
- CB_EggLocation.SelectedValue = Locations.GetNoneLocation((GameVersion)Entity.Version);
+ CB_EggLocation.SelectedValue = LocationEdits.GetNoneLocation(Entity);
UpdateLegality();
}
@@ -2014,7 +2014,7 @@ private void PopulateFilteredDataSources(ITrainerInfo sav, bool force = false)
{
var game = (GameVersion) sav.Game;
if (game <= 0)
- game = GameUtil.GetVersion(sav.Generation);
+ game = Entity.Context.GetSingleGameVersion();
CheckMetLocationChange(game, sav.Generation);
SetIfDifferentCount(source.Items, CB_HeldItem, force);
}
diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs
index a5ca46aa8..140d83ad0 100644
--- a/PKHeX.WinForms/MainWindow/Main.cs
+++ b/PKHeX.WinForms/MainWindow/Main.cs
@@ -50,7 +50,7 @@ public Main()
if (HaX)
{
- EntityConverter.AllowIncompatibleConversion = true;
+ EntityConverter.AllowIncompatibleConversion = EntityCompatibilitySetting.AllowIncompatibleAll;
WinFormsUtil.Alert(MsgProgramIllegalModeActive, MsgProgramIllegalModeBehave);
}
else if (showChangelog)
@@ -172,7 +172,10 @@ private void LoadBlankSaveFile(GameVersion ver)
var tr = SaveUtil.GetSafeTrainerName(current, lang);
var sav = SaveUtil.GetBlankSAV(ver, tr, lang);
if (sav.Version == GameVersion.Invalid) // will fail to load
- sav = SaveUtil.GetBlankSAV((GameVersion)GameInfo.VersionDataSource.Max(z => z.Value), tr, lang);
+ {
+ ver = (GameVersion)GameInfo.VersionDataSource.Max(z => z.Value);
+ sav = SaveUtil.GetBlankSAV(ver, tr, lang);
+ }
OpenSAV(sav, string.Empty);
C_SAV!.SAV.State.Edited = false; // Prevents form close warning from showing until changes are made
}
@@ -402,6 +405,7 @@ private void ReloadProgramSettings(PKHeXSettings settings)
ParseSettings.InitFromSettings(settings.Legality);
PKME_Tabs.HideSecretValues = C_SAV.HideSecretDetails = settings.Privacy.HideSecretDetails;
EntityConverter.AllowIncompatibleConversion = settings.Advanced.AllowIncompatibleConversion;
+ EntityConverter.RejuvenateHOME = settings.Advanced.AllowGuessRejuvenateHOME;
WinFormsUtil.DetectSaveFileOnFileOpen = settings.Startup.TryDetectRecentSave;
SpriteBuilder.LoadSettings(settings.Sprite);
diff --git a/PKHeX.WinForms/Properties/PKHeXSettings.cs b/PKHeX.WinForms/Properties/PKHeXSettings.cs
index c1b906591..71452fced 100644
--- a/PKHeX.WinForms/Properties/PKHeXSettings.cs
+++ b/PKHeX.WinForms/Properties/PKHeXSettings.cs
@@ -214,7 +214,10 @@ public sealed class LegalitySettings : IParseSettings
public sealed class AdvancedSettings
{
[LocalizedDescription("Allow PKM file conversion paths that are not possible via official methods. Individual properties will be copied sequentially.")]
- public bool AllowIncompatibleConversion { get; set; }
+ public EntityCompatibilitySetting AllowIncompatibleConversion { get; set; } = EntityCompatibilitySetting.DisallowIncompatible;
+
+ [LocalizedDescription("Allow PKM file conversion paths to guess the legal original encounter data that is not stored in the format that it was converted from.")]
+ public EntityRejuvenationSetting AllowGuessRejuvenateHOME { get; set; } = EntityRejuvenationSetting.MissingDataHOME;
[LocalizedDescription("Folder path that contains dump(s) of block hash-names. If a specific dump file does not exist, only names defined within the program's code will be loaded.")]
public string PathBlockKeyList { get; set; } = string.Empty;
diff --git a/PKHeX.WinForms/Subforms/PKM Editors/RibbonEditor.cs b/PKHeX.WinForms/Subforms/PKM Editors/RibbonEditor.cs
index 400649411..8ebd4a957 100644
--- a/PKHeX.WinForms/Subforms/PKM Editors/RibbonEditor.cs
+++ b/PKHeX.WinForms/Subforms/PKM Editors/RibbonEditor.cs
@@ -28,11 +28,12 @@ public RibbonEditor(PKM pk)
if (pk is IRibbonSetAffixed affixed)
{
- var names = Enum.GetNames(typeof(RibbonIndex));
- var values = (RibbonIndex[])Enum.GetValues(typeof(RibbonIndex));
- var items = names.Select((z, i) => new ComboItem(RibbonStrings.GetName("Ribbon"+z), (int) values[i])).OrderBy(z => z.Text);
var ds = new List {new(GameInfo.GetStrings(Main.CurrentLanguage).Move[0], -1)};
- ds.AddRange(items.ToArray());
+ var list = Enumerable.Range(0, (int)RibbonIndex.MAX_COUNT)
+ .Select(z => new ComboItem(RibbonStrings.GetName($"Ribbon{(RibbonIndex)z}"), z))
+ .OrderBy(z => z.Text);
+ ds.AddRange(list);
+
CB_Affixed.InitializeBinding();
CB_Affixed.DataSource = ds;
CB_Affixed.SelectedValue = (int)affixed.AffixedRibbon;
diff --git a/PKHeX.WinForms/Subforms/SAV_Database.Designer.cs b/PKHeX.WinForms/Subforms/SAV_Database.Designer.cs
index f4afde1f1..262bf0e01 100644
--- a/PKHeX.WinForms/Subforms/SAV_Database.Designer.cs
+++ b/PKHeX.WinForms/Subforms/SAV_Database.Designer.cs
@@ -699,7 +699,7 @@ private void InitializeComponent()
"Gen 5 (BW/B2W2)",
"Gen 6 (XY/ORAS)",
"Gen 7 (SM/USUM/LGPE)",
- "Gen 8 (SWSH)"});
+ "Gen 8 (SWSH/BDSP/LA)"});
this.CB_Generation.Location = new System.Drawing.Point(83, 293);
this.CB_Generation.Margin = new System.Windows.Forms.Padding(0);
this.CB_Generation.Name = "CB_Generation";
diff --git a/PKHeX.WinForms/Subforms/SAV_Database.cs b/PKHeX.WinForms/Subforms/SAV_Database.cs
index d1396bc46..2a4b4ff62 100644
--- a/PKHeX.WinForms/Subforms/SAV_Database.cs
+++ b/PKHeX.WinForms/Subforms/SAV_Database.cs
@@ -372,9 +372,9 @@ private static List LoadPKMSaves(string pkmdb, SaveFile SAV, List pk is PK8 || ((PersonalInfoSWSH)PersonalTable.SWSH.GetFormEntry(pk.Species, pk.Form)).IsPresentInGame;
- static bool IsPresentInGameBDSP(ISpeciesForm pk) => pk is PB8;//|| ((PersonalInfoBDSP)PersonalTable.BDSP.GetFormEntry(pk.Species, pk.Form)).IsPresentInGame;
- static bool IsPresentInGamePLA (ISpeciesForm pk) => pk is PA8;//|| ((PersonalInfoLA)PersonalTable.LA.GetFormEntry(pk.Species, pk.Form)).IsPresentInGame;
+ static bool IsPresentInGameSWSH(ISpeciesForm pk) => pk is PK8 || PersonalTable.SWSH.IsPresentInGame(pk.Species, pk.Form);
+ static bool IsPresentInGameBDSP(ISpeciesForm pk) => pk is PB8 || PersonalTable.BDSP.IsPresentInGame(pk.Species, pk.Form);
+ static bool IsPresentInGamePLA (ISpeciesForm pk) => pk is PA8 || PersonalTable.LA .IsPresentInGame(pk.Species, pk.Form);
if (SAV is SAV8SWSH)
result.RemoveAll(z => !IsPresentInGameSWSH(z.Entity));
else if (SAV is SAV8BS)
diff --git a/PKHeX.WinForms/Subforms/SAV_Encounters.cs b/PKHeX.WinForms/Subforms/SAV_Encounters.cs
index aa1227e1e..041ae15e1 100644
--- a/PKHeX.WinForms/Subforms/SAV_Encounters.cs
+++ b/PKHeX.WinForms/Subforms/SAV_Encounters.cs
@@ -151,7 +151,7 @@ private EncounterCriteria GetCriteria(ISpeciesForm enc, EncounterDatabaseSetting
return EncounterCriteria.Unrestricted;
var editor = PKME_Tabs.Data;
- var tree = EvolutionTree.GetEvolutionTree(editor, editor.Format);
+ var tree = EvolutionTree.GetEvolutionTree(editor.Context);
bool isInChain = tree.IsSpeciesDerivedFrom(editor.Species, editor.Form, enc.Species, enc.Form);
if (!settings.UseTabsAsCriteriaAnySpecies)
@@ -236,9 +236,9 @@ private IEnumerable SearchDatabase(CancellationToken token)
if (Main.Settings.EncounterDb.FilterUnavailableSpecies)
{
- static bool IsPresentInGameSWSH(ISpeciesForm pk) => ((PersonalInfoSWSH)PersonalTable.SWSH.GetFormEntry(pk.Species, pk.Form)).IsPresentInGame;
- static bool IsPresentInGameBDSP(ISpeciesForm pk) => ((PersonalInfoBDSP)PersonalTable.BDSP.GetFormEntry(pk.Species, pk.Form)).IsPresentInGame;
- static bool IsPresentInGameLA (ISpeciesForm pk) => ((PersonalInfoLA) PersonalTable.LA .GetFormEntry(pk.Species, pk.Form)).IsPresentInGame;
+ static bool IsPresentInGameSWSH(ISpeciesForm pk) => PersonalTable.SWSH.IsPresentInGame(pk.Species, pk.Form);
+ static bool IsPresentInGameBDSP(ISpeciesForm pk) => PersonalTable.BDSP.IsPresentInGame(pk.Species, pk.Form);
+ static bool IsPresentInGameLA (ISpeciesForm pk) => PersonalTable.LA .IsPresentInGame(pk.Species, pk.Form);
results = SAV switch
{
SAV8SWSH => results.Where(IsPresentInGameSWSH),
diff --git a/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs b/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs
index f4beb208b..6632b56e0 100644
--- a/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs
+++ b/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs
@@ -216,9 +216,9 @@ private void LoadDatabase()
{
db = SAV switch
{
- SAV8LA => db.Where(z => z is WA8),
- SAV8BS => db.Where(z => z is WB8),
- SAV8SWSH => db.Where(z => ((PersonalInfoSWSH)PersonalTable.SWSH.GetFormEntry(z.Species, z.Form)).IsPresentInGame),
+ SAV8LA => db.Where(z => PersonalTable.SWSH.IsPresentInGame(z.Species, z.Form)),
+ SAV8BS => db.Where(z => PersonalTable.BDSP.IsPresentInGame(z.Species, z.Form)),
+ SAV8SWSH => db.Where(z => PersonalTable.LA.IsPresentInGame(z.Species, z.Form)),
SAV7b => db.Where(z => z is WB7),
SAV7 => db.Where(z => z.Generation < 7 || z is WC7),
_ => db.Where(z => z.Generation <= SAV.Generation),
diff --git a/Tests/PKHeX.Core.Tests/Saves/HomeTests.cs b/Tests/PKHeX.Core.Tests/Saves/HomeTests.cs
new file mode 100644
index 000000000..5049195a5
--- /dev/null
+++ b/Tests/PKHeX.Core.Tests/Saves/HomeTests.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using FluentAssertions;
+using PKHeX.Core;
+using Xunit;
+using static System.Buffers.Binary.BinaryPrimitives;
+
+namespace PKHeX.Tests.Saves;
+
+public static class HomeTests
+{
+ private static IEnumerable GetHomeEncrypted()
+ {
+ var folder = TestUtil.GetRepoPath();
+ var path = Path.Combine(folder, "TestData");
+ return Directory.EnumerateFiles(path, "*.eh1", SearchOption.TopDirectoryOnly);
+ }
+
+ [Fact]
+ public static void CheckCrypto1()
+ {
+ var paths = GetHomeEncrypted();
+ foreach (var f in paths)
+ {
+ var data = File.ReadAllBytes(f);
+
+ var oldCHK = ReadUInt32LittleEndian(data.AsSpan(0xA, 4));
+ var chk = HomeCrypto.GetChecksum1(data);
+ oldCHK.Should().Be(chk);
+
+ bool encrypted = HomeCrypto.GetIsEncrypted1(data);
+ encrypted.Should().BeTrue();
+
+ var ph1 = new PKH(data);
+ ph1.DataVersion.Should().Be(1);
+
+ var decrypted = HomeCrypto.Crypt1(data);
+ decrypted.Length.Should().Be(data.Length);
+ decrypted.Length.Should().Be(ph1.Data.Length);
+ for (int i = 0; i < decrypted.Length; i++)
+ decrypted[i].Should().Be(ph1.Data[i]);
+
+ var write = ph1.Rebuild();
+ write.Length.Should().Be(decrypted.Length);
+ for (int i = 0; i < decrypted.Length; i++)
+ decrypted[i].Should().Be(write[i]);
+
+ var encrypt = HomeCrypto.Encrypt(write);
+ encrypt.Length.Should().Be(data.Length);
+ for (int i = 0; i < data.Length; i++)
+ encrypt[i].Should().Be(data[i]);
+ }
+ }
+}
diff --git a/Tests/PKHeX.Core.Tests/TestData/001 - Bulbasaur - Impish - Anubis.eh1 b/Tests/PKHeX.Core.Tests/TestData/001 - Bulbasaur - Impish - Anubis.eh1
new file mode 100644
index 000000000..cbf854910
Binary files /dev/null and b/Tests/PKHeX.Core.Tests/TestData/001 - Bulbasaur - Impish - Anubis.eh1 differ
diff --git a/Tests/PKHeX.Core.Tests/TestData/001 - Bulbasaur - Quirky - Anubis.eh1 b/Tests/PKHeX.Core.Tests/TestData/001 - Bulbasaur - Quirky - Anubis.eh1
new file mode 100644
index 000000000..4c958025e
Binary files /dev/null and b/Tests/PKHeX.Core.Tests/TestData/001 - Bulbasaur - Quirky - Anubis.eh1 differ
diff --git a/Tests/PKHeX.Core.Tests/TestData/001 ■ - Bulbasaur - Calm - Anubis.eh1 b/Tests/PKHeX.Core.Tests/TestData/001 ■ - Bulbasaur - Calm - Anubis.eh1
new file mode 100644
index 000000000..3a6d7c115
Binary files /dev/null and b/Tests/PKHeX.Core.Tests/TestData/001 ■ - Bulbasaur - Calm - Anubis.eh1 differ
diff --git a/Tests/PKHeX.Core.Tests/TestData/003 - Venusaur - Calm - Anubis.eh1 b/Tests/PKHeX.Core.Tests/TestData/003 - Venusaur - Calm - Anubis.eh1
new file mode 100644
index 000000000..c1f3e11ac
Binary files /dev/null and b/Tests/PKHeX.Core.Tests/TestData/003 - Venusaur - Calm - Anubis.eh1 differ
diff --git a/Tests/PKHeX.Core.Tests/TestData/003 - Venusaur - Rash - Anubis.eh1 b/Tests/PKHeX.Core.Tests/TestData/003 - Venusaur - Rash - Anubis.eh1
new file mode 100644
index 000000000..b5305ae70
Binary files /dev/null and b/Tests/PKHeX.Core.Tests/TestData/003 - Venusaur - Rash - Anubis.eh1 differ