diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4.cs
index 1fab93ab7..fefd39c78 100644
--- a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4.cs
+++ b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using static PKHeX.Core.GroundTileAllowed;
namespace PKHeX.Core;
@@ -243,7 +244,7 @@ private bool IsMatchLevel(PKM pk, EvoCriteria evo)
private bool IsMatchPartial(PKM pk) => Gift && pk.Ball != (byte)FixedBall;
- public static bool IsMatchRoamerLocation(ulong permit, int location, int first)
+ public static bool IsMatchRoamerLocation([ConstantExpected] ulong permit, int location, int first)
{
var value = location - first;
if ((uint)value >= 64)
@@ -251,7 +252,7 @@ public static bool IsMatchRoamerLocation(ulong permit, int location, int first)
return (permit & (1ul << value)) != 0;
}
- public static bool IsMatchRoamerLocation(uint permit, int location, int first)
+ public static bool IsMatchRoamerLocation([ConstantExpected] uint permit, int location, int first)
{
var value = location - first;
if ((uint)value >= 32)
diff --git a/PKHeX.Core/Legality/Encounters/Templates/Shared/EncounterEgg.cs b/PKHeX.Core/Legality/Encounters/Templates/Shared/EncounterEgg.cs
index 891c8e3e4..0a7ae5e51 100644
--- a/PKHeX.Core/Legality/Encounters/Templates/Shared/EncounterEgg.cs
+++ b/PKHeX.Core/Legality/Encounters/Templates/Shared/EncounterEgg.cs
@@ -16,7 +16,7 @@ public sealed record EncounterEgg(ushort Species, byte Form, byte Level, int Gen
public bool IsShiny => false;
public int Location => 0;
public int EggLocation => Locations.GetDaycareLocation(Generation, Version);
- public Ball FixedBall => BallBreedLegality.GetDefaultBall(Version, Species);
+ public Ball FixedBall => Generation <= 5 ? Ball.Poke : Ball.None;
public Shiny Shiny => Shiny.Random;
public AbilityPermission Ability => AbilityPermission.Any12H;
diff --git a/PKHeX.Core/Legality/Verifiers/Ability/AbilityBreedLegality.cs b/PKHeX.Core/Legality/Verifiers/Ability/AbilityBreedLegality.cs
index 4fe27e4c0..6cf487ee8 100644
--- a/PKHeX.Core/Legality/Verifiers/Ability/AbilityBreedLegality.cs
+++ b/PKHeX.Core/Legality/Verifiers/Ability/AbilityBreedLegality.cs
@@ -144,28 +144,11 @@ internal static class AbilityBreedLegality
(int)Rotom + (5 << 11),
};
- //
- // Species that cannot be bred with a Hidden Ability originating in
- //
- // internal static readonly HashSet BanHidden8 = new(); // none as of DLC 1!
-
///
/// Species that cannot be bred with a Hidden Ability originating in
///
internal static readonly HashSet BanHidden8b = new()
{
- (int)Rotom,
- (int)Rotom + (1 << 11),
- (int)Rotom + (2 << 11),
- (int)Rotom + (3 << 11),
- (int)Rotom + (4 << 11),
- (int)Rotom + (5 << 11),
-
- (int)Baltoy,
- (int)Claydol,
- (int)Solrock,
- (int)Lunatone,
-
(int)Phione,
};
}
diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs
deleted file mode 100644
index e98135f08..000000000
--- a/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs
+++ /dev/null
@@ -1,985 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using static PKHeX.Core.Species;
-
-namespace PKHeX.Core;
-
-///
-/// Tables used for
-///
-internal static class BallBreedLegality
-{
- ///
- /// Species that are male only in Generation 6; for ball inheritance, these behave the same as Genderless species (no ball inherited).
- ///
- internal static readonly HashSet BreedMaleOnly6 = new()
- {
- (int)Tauros,
- (int)Rufflet,
- (int)Braviary,
- (int)Tyrogue,
- (int)Sawk,
- (int)Throh,
- };
-
- internal static readonly HashSet PastGenAlolanScans = new()
- {
- (int)Bellsprout,
- (int)Rhyhorn,
- (int)Horsea,
- (int)Chikorita,
- (int)Cyndaquil,
- (int)Totodile,
- (int)Swinub,
- (int)Spheal,
- (int)Venipede,
- (int)Gothita,
- (int)Klink,
- (int)Litwick,
- (int)Axew,
- (int)Deino,
- (int)Honedge,
- (int)Marill, (int)Azurill,
- (int)Roselia, (int)Budew,
- (int)Togepi,
- (int)Slakoth,
- (int)Starly,
- (int)Shinx,
- (int)Snivy,
- (int)Solosis,
- (int)Tepig,
- (int)Oshawott,
- (int)Timburr,
- (int)Sewaddle,
- (int)Tynamo,
- (int)Charmander,
- (int)Squirtle,
- (int)Onix,
- (int)Talonflame,
- (int)Scatterbug,
- (int)Bulbasaur,
- (int)Ralts,
- (int)Weedle,
- (int)Torchic,
- (int)Treecko,
- (int)Mudkip,
- (int)Piplup,
- (int)Turtwig,
- (int)Pidgey,
- (int)Chimchar,
- (int)Aron,
- (int)Rotom,
- (int)Chespin,
- (int)Fennekin,
- (int)Froakie,
- };
-
- internal static readonly HashSet Inherit_Sport = new()
- {
- (int)Caterpie,
- (int)Weedle,
- (int)Paras,
- (int)Venonat,
- (int)Scyther,
- (int)Pinsir,
- (int)Wurmple,
- (int)Nincada,
- (int)Illumise,
- (int)Kricketot,
- (int)Combee,
- (int)Volbeat,
- };
-
- internal static readonly HashSet Inherit_Safari = new()
- {
- (int)Pidgey,
- (int)Rattata,
- (int)Spearow,
- (int)Ekans,
- (int)Sandshrew,
- (int)NidoranF,
- (int)Zubat,
- (int)Oddish,
- (int)Paras,
- (int)Venonat,
- (int)Diglett,
- (int)Psyduck,
- (int)Poliwag,
- (int)Abra,
- (int)Machop,
- (int)Bellsprout,
- (int)Geodude,
- (int)Ponyta,
- (int)Slowpoke,
- (int)Farfetchd,
- (int)Doduo,
- (int)Grimer,
- (int)Gastly,
- (int)Onix,
- (int)Drowzee,
- (int)Krabby,
- (int)Exeggcute,
- (int)Cubone,
- (int)Lickitung,
- (int)Koffing,
- (int)Rhyhorn,
- (int)Chansey,
- (int)Tangela,
- (int)Kangaskhan,
- (int)Goldeen,
- (int)MrMime,
- (int)Scyther,
- (int)Pinsir,
- (int)Magikarp,
- (int)Lapras,
- (int)Dratini,
- (int)Sentret,
- (int)Hoothoot,
- (int)Ledyba,
- (int)Spinarak,
- (int)Natu,
- (int)Mareep,
- (int)Marill,
- (int)Hoppip,
- (int)Jumpluff,
- (int)Aipom,
- (int)Sunkern,
- (int)Yanma,
- (int)Wooper,
- (int)Murkrow,
- (int)Misdreavus,
- (int)Wobbuffet,
- (int)Girafarig,
- (int)Pineco,
- (int)Gligar,
- (int)Snubbull,
- (int)Shuckle,
- (int)Heracross,
- (int)Teddiursa,
- (int)Remoraid,
- (int)Houndour,
- (int)Phanpy,
- (int)Stantler,
- (int)Smeargle,
- (int)Miltank,
- (int)Larvitar,
- (int)Zigzagoon,
- (int)Linoone,
- (int)Lotad,
- (int)Seedot,
- (int)Surskit,
- (int)Shroomish,
- (int)Slakoth,
- (int)Nosepass,
- (int)Aron,
- (int)Lairon,
- (int)Meditite,
- (int)Electrike,
- (int)Illumise,
- (int)Roselia,
- (int)Gulpin,
- (int)Carvanha,
- (int)Torkoal,
- (int)Spinda,
- (int)Trapinch,
- (int)Cacnea,
- (int)Zangoose,
- (int)Seviper,
- (int)Barboach,
- (int)Corphish,
- (int)Kecleon,
- (int)Shuppet,
- (int)Duskull,
- (int)Tropius,
- (int)Chimecho,
- (int)Spheal,
- (int)Bagon,
- (int)Starly,
- (int)Bidoof,
- (int)Shinx,
- (int)Budew,
- (int)Pachirisu,
- (int)Buizel,
- (int)Chingling,
- (int)Gible,
- (int)Riolu,
- (int)Hippopotas,
- (int)Skorupi,
- (int)Croagunk,
- (int)Carnivine,
-
- // Splitbreed/Baby
- (int)NidoranM, // Via Nidoran-F
- (int)Volbeat, // Via Illumise
- (int)Pichu,
- (int)Cleffa,
- (int)Igglybuff,
- (int)Elekid,
- (int)Magby,
- (int)Azurill,
- (int)Wynaut,
- (int)Budew,
- (int)Chingling,
- (int)MimeJr,
- (int)Happiny,
- };
-
- internal static readonly HashSet Inherit_Dream = new()
- {
- (int)Caterpie,
- (int)Weedle,
- (int)Pidgey,
- (int)Rattata,
- (int)Spearow,
- (int)Ekans,
- (int)Sandshrew,
- (int)NidoranF,
- (int)Vulpix,
- (int)Zubat,
- (int)Oddish,
- (int)Paras,
- (int)Venonat,
- (int)Diglett,
- (int)Meowth,
- (int)Psyduck,
- (int)Mankey,
- (int)Growlithe,
- (int)Poliwag,
- (int)Abra,
- (int)Machop,
- (int)Bellsprout,
- (int)Tentacool,
- (int)Geodude,
- (int)Ponyta,
- (int)Slowpoke,
- (int)Farfetchd,
- (int)Doduo,
- (int)Seel,
- (int)Grimer,
- (int)Shellder,
- (int)Gastly,
- (int)Onix,
- (int)Drowzee,
- (int)Krabby,
- (int)Exeggcute,
- (int)Cubone,
- (int)Lickitung,
- (int)Koffing,
- (int)Rhyhorn,
- (int)Chansey,
- (int)Tangela,
- (int)Kangaskhan,
- (int)Horsea,
- (int)Goldeen,
- (int)MrMime,
- (int)Scyther,
- (int)Pinsir,
- (int)Magikarp,
- (int)Lapras,
- (int)Eevee,
- (int)Omanyte,
- (int)Kabuto,
- (int)Aerodactyl,
- (int)Snorlax,
- (int)Dratini,
- (int)Sentret,
- (int)Hoothoot,
- (int)Ledyba,
- (int)Spinarak,
- (int)Chinchou,
- (int)Cleffa,
- (int)Igglybuff,
- (int)Togepi,
- (int)Natu,
- (int)Mareep,
- (int)Marill,
- (int)Sudowoodo,
- (int)Hoppip,
- (int)Aipom,
- (int)Sunkern,
- (int)Yanma,
- (int)Wooper,
- (int)Murkrow,
- (int)Misdreavus,
- (int)Wobbuffet,
- (int)Girafarig,
- (int)Pineco,
- (int)Dunsparce,
- (int)Gligar,
- (int)Snubbull,
- (int)Qwilfish,
- (int)Shuckle,
- (int)Heracross,
- (int)Sneasel,
- (int)Teddiursa,
- (int)Slugma,
- (int)Swinub,
- (int)Corsola,
- (int)Remoraid,
- (int)Delibird,
- (int)Mantine,
- (int)Skarmory,
- (int)Houndour,
- (int)Phanpy,
- (int)Stantler,
- (int)Smeargle,
- (int)Smoochum,
- (int)Elekid,
- (int)Magby,
- (int)Miltank,
- (int)Larvitar,
- (int)Poochyena,
- (int)Zigzagoon,
- (int)Wurmple,
- (int)Lotad,
- (int)Seedot,
- (int)Taillow,
- (int)Wingull,
- (int)Ralts,
- (int)Surskit,
- (int)Shroomish,
- (int)Slakoth,
- (int)Nincada,
- (int)Whismur,
- (int)Makuhita,
- (int)Nosepass,
- (int)Skitty,
- (int)Sableye,
- (int)Mawile,
- (int)Aron,
- (int)Meditite,
- (int)Electrike,
- (int)Plusle,
- (int)Minun,
- (int)Illumise,
- (int)Roselia,
- (int)Gulpin,
- (int)Carvanha,
- (int)Wailmer,
- (int)Numel,
- (int)Torkoal,
- (int)Spoink,
- (int)Spinda,
- (int)Trapinch,
- (int)Cacnea,
- (int)Swablu,
- (int)Zangoose,
- (int)Seviper,
- (int)Barboach,
- (int)Corphish,
- (int)Lileep,
- (int)Anorith,
- (int)Feebas,
- (int)Castform,
- (int)Kecleon,
- (int)Shuppet,
- (int)Duskull,
- (int)Tropius,
- (int)Chimecho,
- (int)Absol,
- (int)Snorunt,
- (int)Spheal,
- (int)Clamperl,
- (int)Relicanth,
- (int)Luvdisc,
- (int)Bagon,
- (int)Starly,
- (int)Bidoof,
- (int)Kricketot,
- (int)Shinx,
- (int)Cranidos,
- (int)Shieldon,
- (int)Burmy,
- (int)Combee,
- (int)Pachirisu,
- (int)Buizel,
- (int)Cherubi,
- (int)Shellos,
- (int)Drifloon,
- (int)Buneary,
- (int)Glameow,
- (int)Stunky,
- (int)Chatot,
- (int)Spiritomb,
- (int)Gible,
- (int)Riolu,
- (int)Hippopotas,
- (int)Skorupi,
- (int)Croagunk,
- (int)Carnivine,
- (int)Finneon,
- (int)Snover,
- (int)Munna,
- (int)Pidove,
- (int)Boldore,
- (int)Drilbur,
- (int)Audino,
- (int)Gurdurr,
- (int)Tympole,
- (int)Scolipede,
- (int)Cottonee,
- (int)Petilil,
- (int)Basculin,
- (int)Krookodile,
- (int)Maractus,
- (int)Crustle,
- (int)Scraggy,
- (int)Sigilyph,
- (int)Tirtouga,
- (int)Duosion,
- (int)Ducklett,
- (int)Vanillish,
- (int)Emolga,
- (int)Karrablast,
- (int)Alomomola,
- (int)Galvantula,
- (int)Elgyem,
- (int)Axew,
- (int)Shelmet,
- (int)Stunfisk,
- (int)Druddigon,
- (int)Pawniard,
- (int)Heatmor,
- (int)Durant,
- (int)NidoranM, // Via Nidoran-F
- (int)Volbeat, // Via Illumise
-
- // Via Evolution
- (int)Roggenrola,
- (int)Timburr,
- (int)Venipede,
- (int)Sandile,
- (int)Dwebble,
- (int)Solosis,
- (int)Vanillite,
- (int)Joltik,
-
- // Via Incense Breeding
- (int)Azurill,
- (int)Wynaut,
- (int)Budew,
- (int)Chingling,
- (int)Bonsly,
- (int)MimeJr,
- (int)Happiny,
- (int)Munchlax,
- (int)Mantyke,
- };
-
- internal static readonly HashSet Ban_DreamHidden = new()
- {
- (int)Plusle,
- (int)Minun,
- (int)Kecleon,
- (int)Duskull,
- };
-
- internal static readonly HashSet Ban_Gen3Ball = new()
- {
- (int)Treecko, (int)Torchic, (int)Mudkip,
- (int)Turtwig, (int)Chimchar, (int)Piplup,
- (int)Snivy, (int)Tepig, (int)Oshawott,
-
- // Fossil Only obtain
- (int)Archen, (int)Tyrunt, (int)Amaura,
- };
-
- internal static readonly HashSet Ban_Gen3BallHidden = new()
- {
- // can have HA and can be in gen 3 ball as eggs but can not at same time.
- (int)Chikorita, (int)Cyndaquil, (int)Totodile,
- (int)Deerling + (1 << 11), //Deerling-Summer
- (int)Deerling + (2 << 11), //Deerling-Autumn
- (int)Deerling + (3 << 11), //Deerling-Winter
- (int)Pumpkaboo + (3 << 11), //Pumpkaboo-Super
- };
-
- internal static readonly HashSet Ban_Gen4Ball_6 = new()
- {
- (int)Chikorita, (int)Cyndaquil, (int)Totodile,
- (int)Treecko, (int)Torchic, (int)Mudkip,
- (int)Turtwig, (int)Chimchar, (int)Piplup,
- (int)Snivy, (int)Tepig, (int)Oshawott,
-
- // Fossil Only obtain
- (int)Archen, (int)Tyrunt, (int)Amaura,
- };
-
- internal static readonly HashSet Inherit_Apricorn6 = new()
- {
- (int)Caterpie,
- (int)Weedle,
- (int)Pidgey,
- (int)Rattata,
- (int)Spearow,
- (int)Ekans,
- (int)Sandshrew,
- (int)NidoranF,
- (int)Vulpix,
- (int)Zubat,
- (int)Oddish,
- (int)Paras,
- (int)Venonat,
- (int)Diglett,
- (int)Meowth,
- (int)Psyduck,
- (int)Mankey,
- (int)Growlithe,
- (int)Poliwag,
- (int)Abra,
- (int)Machop,
- (int)Bellsprout,
- (int)Tentacool,
- (int)Geodude,
- (int)Ponyta,
- (int)Slowpoke,
- (int)Farfetchd,
- (int)Doduo,
- (int)Seel,
- (int)Grimer,
- (int)Shellder,
- (int)Gastly,
- (int)Onix,
- (int)Drowzee,
- (int)Krabby,
- (int)Exeggcute,
- (int)Cubone,
- (int)Lickitung,
- (int)Koffing,
- (int)Rhyhorn,
- (int)Chansey,
- (int)Tangela,
- (int)Kangaskhan,
- (int)Horsea,
- (int)Goldeen,
- (int)MrMime,
- (int)Magikarp,
- (int)Lapras,
- (int)Snorlax,
- (int)Dratini,
- (int)Sentret,
- (int)Hoothoot,
- (int)Ledyba,
- (int)Spinarak,
- (int)Chinchou,
- (int)Natu,
- (int)Mareep,
- (int)Marill,
- (int)Sudowoodo,
- (int)Hoppip,
- (int)Aipom,
- (int)Sunkern,
- (int)Yanma,
- (int)Wooper,
- (int)Murkrow,
- (int)Misdreavus,
- (int)Wobbuffet,
- (int)Girafarig,
- (int)Pineco,
- (int)Dunsparce,
- (int)Gligar,
- (int)Snubbull,
- (int)Qwilfish,
- (int)Shuckle,
- (int)Heracross,
- (int)Sneasel,
- (int)Teddiursa,
- (int)Slugma,
- (int)Swinub,
- (int)Corsola,
- (int)Remoraid,
- (int)Delibird,
- (int)Mantine,
- (int)Skarmory,
- (int)Houndour,
- (int)Phanpy,
- (int)Stantler,
- (int)Smeargle,
- (int)Miltank,
- (int)Larvitar,
- (int)Poochyena,
- (int)Zigzagoon,
- (int)Wurmple,
- (int)Seedot,
- (int)Taillow,
- (int)Wingull,
- (int)Ralts,
- (int)Shroomish,
- (int)Slakoth,
- (int)Whismur,
- (int)Makuhita,
- (int)Sableye,
- (int)Mawile,
- (int)Meditite,
- (int)Plusle,
- (int)Minun,
- (int)Gulpin,
- (int)Numel,
- (int)Spoink,
- (int)Spinda,
- (int)Swablu,
- (int)Barboach,
- (int)Absol,
- (int)Clamperl,
- (int)Relicanth,
- (int)Luvdisc,
- (int)Starly,
- (int)Bidoof,
- (int)Kricketot,
- (int)Shinx,
- (int)Budew,
- (int)Burmy,
- (int)Combee,
- (int)Buizel,
- (int)Cherubi,
- (int)Buneary,
- (int)Chingling,
- (int)Chatot,
- (int)Carnivine,
- (int)NidoranM, // Via Nidoran-F
- (int)Happiny, // Via Chansey
- (int)Smoochum, // Via Jynx
- (int)Elekid, // Via Electabuzz
- (int)Magby, // Via Magmar
- (int)Azurill, // Via Marill
- (int)Wynaut, // Via Wobbuffet
- (int)Bonsly, // Via Sudowoodo
- (int)MimeJr, // Via Mr. Mime
- (int)Munchlax, // Via Snorlax
- (int)Mantyke, // Via Mantine
- (int)Chimecho, // Via Chingling
- (int)Pichu, // Via Pikachu
- (int)Cleffa, // Via Clefairy
- (int)Igglybuff, // Via Jigglypuff
- };
-
- internal static readonly HashSet AlolanCaptureOffspring = new()
- {
- (int)Caterpie,
- (int)Rattata,
- (int)Spearow,
- (int)Sandshrew,
- (int)Vulpix,
- (int)Jigglypuff,
- (int)Zubat,
- (int)Paras,
- (int)Diglett,
- (int)Meowth,
- (int)Psyduck,
- (int)Mankey,
- (int)Growlithe,
- (int)Poliwag,
- (int)Abra,
- (int)Machop,
- (int)Tentacool,
- (int)Geodude,
- (int)Slowpoke,
- (int)Magnemite,
- (int)Grimer,
- (int)Shellder,
- (int)Gastly,
- (int)Drowzee,
- (int)Exeggcute,
- (int)Cubone,
- (int)Chansey,
- (int)Kangaskhan,
- (int)Goldeen,
- (int)Staryu,
- (int)Scyther,
- (int)Pinsir,
- (int)Tauros,
- (int)Magikarp,
- (int)Lapras,
- (int)Ditto,
- (int)Eevee,
- (int)Snorlax,
- (int)Dratini,
- (int)Ledyba,
- (int)Spinarak,
- (int)Chinchou,
- (int)Pichu,
- (int)Cleffa,
- (int)Igglybuff,
- (int)Sudowoodo,
- (int)Murkrow,
- (int)Misdreavus,
- (int)Snubbull,
- (int)Scizor,
- (int)Sneasel,
- (int)Corsola,
- (int)Delibird,
- (int)Skarmory,
- (int)Smeargle,
- (int)Elekid,
- (int)Magby,
- (int)Miltank,
- (int)Wingull,
- (int)Surskit,
- (int)Makuhita,
- (int)Nosepass,
- (int)Sableye,
- (int)Carvanha,
- (int)Wailmer,
- (int)Torkoal,
- (int)Spinda,
- (int)Trapinch,
- (int)Barboach,
- (int)Feebas,
- (int)Castform,
- (int)Absol,
- (int)Snorunt,
- (int)Relicanth,
- (int)Luvdisc,
- (int)Bagon,
- (int)Beldum,
- (int)Shellos,
- (int)Drifloon,
- (int)Bonsly,
- (int)Happiny,
- (int)Gible,
- (int)Munchlax,
- (int)Riolu,
- (int)Finneon,
- (int)Lillipup,
- (int)Roggenrola,
- (int)Cottonee,
- (int)Petilil,
- (int)Sandile,
- (int)Trubbish,
- (int)Vanillite,
- (int)Emolga,
- (int)Alomomola,
- (int)Rufflet,
- (int)Vullaby,
- (int)Fletchling,
- (int)Pancham,
- (int)Carbink,
- (int)Goomy,
- (int)Klefki,
- (int)Phantump,
- (int)Pikipek,
- (int)Yungoos,
- (int)Grubbin,
- (int)Crabrawler,
- (int)Oricorio,
- (int)Cutiefly,
- (int)Rockruff,
- (int)Wishiwashi,
- (int)Mareanie,
- (int)Mudbray,
- (int)Dewpider,
- (int)Fomantis,
- (int)Morelull,
- (int)Salandit,
- (int)Stufful,
- (int)Bounsweet,
- (int)Comfey,
- (int)Oranguru,
- (int)Passimian,
- (int)Wimpod,
- (int)Sandygast,
- (int)Pyukumuku,
- (int)Minior,
- (int)Komala,
- (int)Turtonator,
- (int)Togedemaru,
- (int)Mimikyu,
- (int)Bruxish,
- (int)Drampa,
- (int)Dhelmise,
- (int)Jangmoo,
-
- // USUM Additions
- (int)Ekans,
- (int)Seel,
- (int)Lickitung,
- (int)MrMime,
- (int)Hoothoot,
- (int)Natu,
- (int)Mareep,
- (int)Aipom,
- (int)Pineco,
- (int)Dunsparce,
- (int)Heracross,
- (int)Remoraid,
- (int)Mantine,
- (int)Houndour,
- (int)Smoochum,
- (int)Larvitar,
- (int)Mawile,
- (int)Electrike,
- (int)Corphish,
- (int)Baltoy,
- (int)Kecleon,
- (int)Shuppet,
- (int)Tropius,
- (int)Clamperl,
- (int)Buneary,
- (int)MimeJr,
- (int)Mantyke,
- (int)Basculin,
- (int)Scraggy,
- (int)Zorua,
- (int)Minccino,
- (int)Frillish,
- (int)Elgyem,
- (int)Mienfoo,
- (int)Druddigon,
- (int)Golett,
- (int)Pawniard,
- (int)Larvesta,
- (int)Litleo,
- (int)Flabébé,
- (int)Furfrou,
- (int)Inkay,
- (int)Skrelp,
- (int)Clauncher,
- (int)Hawlucha,
- (int)Dedenne,
- (int)Noibat,
-
- // Wormhole
- (int)Swablu,
- (int)Yanma,
- (int)Sigilyph,
- (int)Ducklett,
- (int)Taillow,
- (int)Skorupi,
- (int)Audino,
- (int)Helioptile,
- (int)Seedot,
- (int)Spoink,
- (int)Snover,
- (int)Meditite,
- (int)Hippopotas,
- (int)Dwebble,
- (int)Slugma,
- (int)Binacle,
- (int)Lotad,
- (int)Stunfisk,
- (int)Buizel,
- (int)Wooper,
-
- // Static Encounters
- (int)Voltorb,
- };
-
- internal static readonly HashSet Ban_NoHidden7Apricorn = new()
- {
- (int)NidoranF,
- (int)NidoranM,
- (int)Voltorb,
- (int)Bronzor,
- (int)Flabébé + (3 << 11), // Flabébé-Blue
- };
-
- internal static readonly HashSet AlolanCaptureNoHeavyBall = new() { (int)Beldum, (int)TapuKoko, (int)TapuLele, (int)TapuBulu, (int)TapuFini };
-
- private static readonly HashSet Inherit_ApricornMale7 = new()
- {
- (int)Voltorb,
- (int)Baltoy,
- (int)Bronzor,
-
- // Others are capturable in the Alola region
- // Magnemite, Staryu, Tauros
- };
-
- internal static readonly HashSet Inherit_Apricorn7 = new(Inherit_Apricorn6.Concat(Inherit_ApricornMale7).Concat(PastGenAlolanScans).Concat(AlolanCaptureOffspring).Distinct());
-
- internal static readonly HashSet Inherit_SafariMale = new()
- {
- (int)Tauros,
-
- (int)Magnemite,
- (int)Voltorb,
- (int)Lunatone,
- (int)Solrock,
- (int)Beldum,
- (int)Bronzor,
- };
-
- internal static readonly HashSet Inherit_DreamMale = new()
- {
- // Starting with Gen7, Males pass Ball via breeding with Ditto.
- (int)Bulbasaur, (int)Charmander, (int)Squirtle,
- (int)Tauros,
- (int)Pichu,
- (int)Tyrogue,
- (int)Treecko,
- (int)Torchic, (int)Mudkip, (int)Turtwig,
- (int)Chimchar, (int)Piplup, (int)Pansage,
- (int)Pansear, (int)Panpour,
- (int)Throh, (int)Sawk,
- (int)Gothita,
-
- (int)Magnemite,
- (int)Voltorb,
- (int)Staryu,
- (int)Porygon,
- (int)Lunatone,
- (int)Solrock,
- (int)Baltoy,
- (int)Beldum,
- (int)Bronzor,
- (int)Rotom,
- (int)Klink,
- (int)Golett,
- };
-
- internal static readonly HashSet Ban_Gen3Ball_7 = new()
- {
- (int)Phione,
- (int)Archen,
- (int)Tyrunt,
- (int)Amaura,
- };
-
- // Same as Gen3 Balls
- internal static readonly HashSet Ban_Gen4Ball_7 = Ban_Gen3Ball_7;
-
- internal static readonly HashSet Ban_SafariBallHidden_7 = new()
- {
- (int)NidoranF,
- (int)NidoranM,
- (int)Volbeat, (int)Illumise,
- (int)Magnemite,
- (int)Voltorb,
- (int)Kangaskhan,
- (int)Tauros,
- (int)Ditto,
- (int)Miltank,
- (int)Beldum,
- (int)Bronzor,
- (int)Happiny,
- (int)Tyrogue,
- (int)Staryu,
- (int)Lunatone,
- (int)Solrock,
- (int)Rotom,
- (int)Klink,
- (int)Golett,
- };
-
- internal static readonly HashSet Ban_NoHidden8Apricorn = new()
- {
- // Nidoran, Bronzor -- Used to not be encounterable in Gen7 with HA; Gen8 now can via Raids
- (int)Voltorb, // Voltorb
- (int)Flabébé + (3 << 11), // Flabébé-Blue
- };
-
- ///
- /// Gets a legal value for a bred egg encounter.
- ///
- /// Version the egg was created on.
- /// 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, ushort species)
- {
- if (version.GetGeneration() <= 5)
- return Ball.Poke;
- return Ball.None;
- }
-}
diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallContext6.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallContext6.cs
new file mode 100644
index 000000000..7d0634d4a
--- /dev/null
+++ b/PKHeX.Core/Legality/Verifiers/Ball/BallContext6.cs
@@ -0,0 +1,192 @@
+using System;
+using static PKHeX.Core.Ball;
+using static PKHeX.Core.Species;
+using static PKHeX.Core.BallInheritanceResult;
+
+namespace PKHeX.Core;
+
+///
+/// Ball Inheritance Permissions for games.
+///
+public sealed class BallContext6 : IBallContext
+{
+ public static readonly BallContext6 Instance = new();
+
+ public bool CanBreedWithBall(ushort species, byte form, Ball ball)
+ {
+ // Eagerly return true for the most common case
+ if (ball is Poke)
+ return true;
+
+ if (species >= Permit.Length)
+ return false;
+ var permitBit = GetPermitBit(ball);
+ if (permitBit == BallType.None)
+ return false;
+
+ var permit = Permit[species];
+ if ((permit & (1 << (byte)permitBit)) == 0)
+ return false;
+ return true;
+ }
+
+ public BallInheritanceResult CanBreedWithBall(ushort species, byte form, Ball ball, PKM pk)
+ {
+ // Eagerly return true for the most common case
+ if (ball is Poke)
+ return Valid;
+
+ if (species >= Permit.Length)
+ return Invalid;
+ var permitBit = GetPermitBit(ball);
+ if (permitBit == BallType.None)
+ return Invalid;
+
+ var permit = Permit[species];
+ if ((permit & (1 << (byte)permitBit)) == 0)
+ return Invalid;
+
+ if (!BallContextHOME.IsAbilityPatchPossible(pk.Format, species) && !IsAbilityAllowed(species, form, pk, permitBit))
+ return BadAbility;
+ return Valid;
+ }
+
+ private static bool IsAbilityAllowed(ushort species, byte form, PKM pk, BallType permitBit) => permitBit switch
+ {
+ BallType.Gen3 => IsNotHidden(pk.AbilityNumber) || !IsBannedHiddenGen3(species, form),
+ BallType.Gen4 => IsNotHidden(pk.AbilityNumber) || !IsBannedHiddenGen4(species),
+ BallType.Safari => IsNotHidden(pk.AbilityNumber),
+ BallType.Apricorn => IsNotHidden(pk.AbilityNumber),
+ BallType.Sport => IsNotHidden(pk.AbilityNumber),
+ BallType.Dream => IsNotHidden(pk.AbilityNumber) || !IsBannedHiddenDream(species),
+ _ => true,
+ };
+
+ private static bool IsNotHidden(int pkAbilityNumber) => pkAbilityNumber != 4;
+
+ private static bool IsBannedHiddenGen4(ushort species) => species switch
+ {
+ (int)Chikorita => true,
+ (int)Cyndaquil => true,
+ (int)Totodile => true,
+ (int)Treecko => true,
+ (int)Torchic => true,
+ (int)Mudkip => true,
+ (int)Turtwig => true,
+ (int)Chimchar => true,
+ (int)Piplup => true,
+ (int)Snivy => true,
+ (int)Tepig => true,
+ (int)Oshawott => true,
+
+ // Fossil Only obtain
+ (int)Archen => true,
+ (int)Tyrunt => true,
+ (int)Amaura => true,
+ _ => false,
+ };
+
+ private static bool IsBannedHiddenGen3(ushort species, byte form) => species switch
+ {
+ // can have HA and can be in gen 3 ball as eggs but can not at same time.
+ (int)Chikorita => true,
+ (int)Cyndaquil => true,
+ (int)Totodile => true,
+ (int)Deerling => form != 0, // not Deerling-Spring
+ (int)Pumpkaboo => form == 3, //Pumpkaboo-Super
+ _ => false,
+ };
+
+ private static bool IsBannedHiddenDream(ushort species) => species switch
+ {
+ (int)Plusle => true,
+ (int)Minun => true,
+ (int)Kecleon => true,
+ (int)Duskull => true,
+ _ => false,
+ };
+
+ private static BallType GetPermitBit(Ball ball) => ball switch
+ {
+ Ultra => BallType.Gen3,
+ Great => BallType.Gen3,
+ Poke => BallType.Gen3,
+ Safari => BallType.Safari,
+
+ Net => BallType.Gen3,
+ Dive => BallType.Gen3,
+ Nest => BallType.Gen3,
+ Repeat => BallType.Gen3,
+ Timer => BallType.Gen3,
+ Luxury => BallType.Gen3,
+ Premier => BallType.Gen3,
+
+ Dusk => BallType.Gen4,
+ Heal => BallType.Gen4,
+ Quick => BallType.Gen4,
+
+ Fast => BallType.Apricorn,
+ Level => BallType.Apricorn,
+ Lure => BallType.Apricorn,
+ Heavy => BallType.Apricorn,
+ Love => BallType.Apricorn,
+ Friend => BallType.Apricorn,
+ Moon => BallType.Apricorn,
+
+ Sport => BallType.Sport,
+ Dream => BallType.Dream,
+ _ => BallType.None,
+ };
+
+ public static ReadOnlySpan Permit => new byte[]
+ {
+ 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x3B, 0x03, 0x03, 0x3B, 0x03, 0x03, 0x2F, 0x03, 0x03, 0x2F, // 000 - 019
+ 0x03, 0x2F, 0x03, 0x2F, 0x03, 0x03, 0x03, 0x2F, 0x03, 0x2F, 0x03, 0x03, 0x2F, 0x03, 0x03, 0x03, 0x03, 0x2B, 0x03, 0x03, // 020 - 039
+ 0x03, 0x2F, 0x03, 0x2F, 0x03, 0x03, 0x3F, 0x03, 0x3F, 0x03, 0x2F, 0x03, 0x2B, 0x03, 0x2F, 0x03, 0x2B, 0x03, 0x2B, 0x03, // 040 - 059
+ 0x2F, 0x03, 0x03, 0x2F, 0x03, 0x03, 0x2F, 0x03, 0x03, 0x2F, 0x03, 0x03, 0x2B, 0x03, 0x2F, 0x03, 0x03, 0x2F, 0x03, 0x2F, // 060 - 079
+ 0x03, 0x00, 0x00, 0x2F, 0x2F, 0x03, 0x2B, 0x03, 0x2F, 0x03, 0x2B, 0x03, 0x2F, 0x03, 0x03, 0x2F, 0x2F, 0x03, 0x2F, 0x03, // 080 - 099
+ 0x00, 0x00, 0x2F, 0x03, 0x2F, 0x03, 0x03, 0x03, 0x2F, 0x2F, 0x03, 0x2F, 0x03, 0x2F, 0x2F, 0x2F, 0x2B, 0x03, 0x2F, 0x03, // 100 - 119
+ 0x00, 0x00, 0x2F, 0x37, 0x03, 0x03, 0x03, 0x37, 0x00, 0x2F, 0x03, 0x2F, 0x00, 0x23, 0x03, 0x03, 0x03, 0x00, 0x23, 0x03, // 120 - 139
+ 0x23, 0x03, 0x23, 0x2B, 0x00, 0x00, 0x00, 0x2F, 0x03, 0x03, 0x00, 0x00, 0x81, 0x03, 0x03, 0x81, 0x03, 0x03, 0x81, 0x03, // 140 - 159
+ 0x03, 0x2F, 0x03, 0x2F, 0x03, 0x2F, 0x03, 0x2F, 0x03, 0x03, 0x2B, 0x03, 0x0F, 0x2F, 0x2F, 0x23, 0x03, 0x2F, 0x03, 0x2F, // 160 - 179
+ 0x03, 0x03, 0x03, 0x2F, 0x03, 0x2B, 0x03, 0x2F, 0x03, 0x07, 0x2F, 0x2F, 0x03, 0x2F, 0x2F, 0x03, 0x03, 0x03, 0x2F, 0x03, // 180 - 199
+ 0x2F, 0x00, 0x2F, 0x2F, 0x2F, 0x03, 0x2B, 0x2F, 0x03, 0x2F, 0x03, 0x2B, 0x03, 0x2F, 0x2F, 0x2B, 0x2F, 0x03, 0x2B, 0x03, // 200 - 219
+ 0x2B, 0x03, 0x2B, 0x2F, 0x03, 0x2B, 0x2B, 0x2B, 0x2F, 0x03, 0x03, 0x2F, 0x03, 0x00, 0x2F, 0x2F, 0x00, 0x03, 0x2B, 0x2F, // 220 - 239
+ 0x2F, 0x2F, 0x03, 0x00, 0x00, 0x00, 0x2F, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x03, 0x03, 0x00, 0x03, // 240 - 259
+ 0x03, 0x2B, 0x03, 0x2F, 0x07, 0x3B, 0x03, 0x03, 0x03, 0x03, 0x27, 0x03, 0x03, 0x2F, 0x03, 0x03, 0x2B, 0x03, 0x2B, 0x03, // 260 - 279
+ 0x2B, 0x03, 0x03, 0x27, 0x03, 0x2F, 0x03, 0x2F, 0x03, 0x03, 0x33, 0x03, 0x00, 0x2B, 0x03, 0x03, 0x2B, 0x03, 0x2F, 0x27, // 280 - 299
+ 0x23, 0x03, 0x2B, 0x2B, 0x27, 0x07, 0x03, 0x2F, 0x03, 0x27, 0x03, 0x2B, 0x2B, 0x37, 0x37, 0x27, 0x2F, 0x03, 0x27, 0x03, // 300 - 319
+ 0x23, 0x03, 0x2B, 0x03, 0x27, 0x2B, 0x03, 0x2F, 0x27, 0x03, 0x03, 0x27, 0x03, 0x2B, 0x03, 0x27, 0x27, 0x00, 0x00, 0x2F, // 320 - 339
+ 0x03, 0x27, 0x03, 0x00, 0x00, 0x23, 0x03, 0x23, 0x03, 0x23, 0x03, 0x23, 0x27, 0x27, 0x03, 0x27, 0x03, 0x27, 0x2F, 0x2B, // 340 - 359
+ 0x2F, 0x23, 0x03, 0x27, 0x03, 0x03, 0x2B, 0x03, 0x03, 0x2B, 0x2B, 0x27, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 360 - 379
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x03, 0x03, 0x00, 0x03, 0x03, 0x2F, 0x03, 0x03, 0x2F, // 380 - 399
+ 0x03, 0x3B, 0x03, 0x2F, 0x03, 0x03, 0x2F, 0x03, 0x23, 0x03, 0x23, 0x03, 0x2B, 0x03, 0x03, 0x3B, 0x03, 0x27, 0x2F, 0x03, // 400 - 419
+ 0x2B, 0x03, 0x23, 0x03, 0x03, 0x23, 0x03, 0x2B, 0x03, 0x03, 0x03, 0x23, 0x03, 0x2F, 0x23, 0x03, 0x00, 0x00, 0x2B, 0x2F, // 420 - 439
+ 0x2F, 0x2B, 0x23, 0x27, 0x03, 0x03, 0x2B, 0x27, 0x03, 0x27, 0x03, 0x27, 0x03, 0x27, 0x03, 0x2F, 0x23, 0x03, 0x2B, 0x23, // 440 - 459
+ 0x03, 0x03, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x03, 0x03, 0x03, 0x03, 0x00, // 460 - 479
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x03, // 480 - 499
+ 0x03, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x23, // 500 - 519
+ 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x23, 0x23, 0x03, 0x03, 0x23, 0x03, 0x03, 0x00, 0x00, // 520 - 539
+ 0x03, 0x03, 0x03, 0x23, 0x03, 0x03, 0x23, 0x03, 0x23, 0x03, 0x23, 0x23, 0x03, 0x03, 0x03, 0x03, 0x23, 0x23, 0x03, 0x23, // 540 - 559
+ 0x03, 0x23, 0x03, 0x03, 0x23, 0x03, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x03, // 560 - 579
+ 0x23, 0x03, 0x23, 0x03, 0x03, 0x83, 0x03, 0x23, 0x23, 0x03, 0x03, 0x03, 0x03, 0x03, 0x23, 0x23, 0x03, 0x03, 0x03, 0x00, // 580 - 599
+ 0x00, 0x00, 0x03, 0x03, 0x03, 0x23, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x03, 0x03, 0x03, 0x00, 0x23, 0x03, 0x23, 0x03, // 600 - 619
+ 0x03, 0x23, 0x00, 0x00, 0x23, 0x03, 0x03, 0x00, 0x00, 0x03, 0x03, 0x23, 0x23, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x00, // 620 - 639
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // 640 - 659
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // 660 - 679
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x03, 0x00, 0x03, // 680 - 699
+ 0x03, 0x03, 0x03, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x83, 0x03, 0x03, 0x03, 0x03, 0x03,
+ };
+
+ private enum BallType : byte
+ {
+ Gen3 = 0,
+ Gen4 = 1,
+ Safari = 2,
+ Apricorn = 3,
+ Sport = 4,
+ Dream = 5,
+
+ None = 9,
+ }
+}
diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallContext7.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallContext7.cs
new file mode 100644
index 000000000..909f979d1
--- /dev/null
+++ b/PKHeX.Core/Legality/Verifiers/Ball/BallContext7.cs
@@ -0,0 +1,228 @@
+using System;
+using static PKHeX.Core.Ball;
+using static PKHeX.Core.Species;
+using static PKHeX.Core.BallInheritanceResult;
+
+namespace PKHeX.Core;
+
+///
+/// Ball Inheritance Permissions for games.
+///
+public sealed class BallContext7 : IBallContext
+{
+ public static readonly BallContext7 Instance = new();
+
+ public bool CanBreedWithBall(ushort species, byte form, Ball ball)
+ {
+ // Eagerly return true for the most common case
+ if (ball is Poke)
+ return true;
+
+ if (species >= Permit.Length)
+ return false;
+ var permitBit = GetPermitBit(ball);
+ if (permitBit == BallType.None)
+ return false;
+
+ var permit = Permit[species];
+ if ((permit & (1 << (byte)permitBit)) == 0)
+ return false;
+ return true;
+ }
+
+ public BallInheritanceResult CanBreedWithBall(ushort species, byte form, Ball ball, PKM pk)
+ {
+ // Eagerly return true for the most common case
+ if (ball is Poke)
+ return Valid;
+
+ if (species >= Permit.Length)
+ return Invalid;
+ var permitBit = GetPermitBit(ball);
+ if (permitBit == BallType.None)
+ return Invalid;
+
+ var permit = Permit[species];
+ if ((permit & (1 << (byte)permitBit)) == 0)
+ return Invalid;
+
+ if (!BallContextHOME.IsAbilityPatchPossible(pk.Format, species) && !IsAbilityAllowed(species, form, pk, permitBit))
+ return BadAbility;
+ return Valid;
+ }
+
+ private static bool IsAbilityAllowed(ushort species, byte form, PKM pk, BallType permitBit) => permitBit switch
+ {
+ BallType.Gen3 => IsNotHidden(pk.AbilityNumber) || !IsBannedHiddenGen3(species),
+ BallType.Gen4 => IsNotHidden(pk.AbilityNumber) || !IsBannedHiddenGen4(species),
+ BallType.Safari => IsNotHidden(pk.AbilityNumber) || !IsBannedHiddenSafari(species),
+ BallType.Apricorn => IsNotHidden(pk.AbilityNumber) || !IsBannedHiddenApricorn(species, form),
+ BallType.Beast => IsNotHidden(pk.AbilityNumber) || !IsBannedHiddenBeast(species, form),
+ _ => true,
+ };
+
+ private static bool IsBannedHiddenBeast(ushort species, byte form) => species switch
+ {
+ (int)Voltorb => true,
+ (int)Flabébé => form == 3, // Flabébé-Blue
+ _ => false,
+ };
+
+ private static bool IsBannedHiddenApricorn(ushort species, byte form) => species switch
+ {
+ (int)NidoranF => true,
+ (int)NidoranM => true,
+ (int)Voltorb => true,
+ (int)Bronzor => true,
+ (int)Flabébé => form == 3, // Flabébé-Blue
+ _ => false,
+ };
+
+ private static bool IsBannedHiddenSafari(ushort species) => species switch
+ {
+ (int)NidoranF => true,
+ (int)NidoranM => true,
+ (int)Volbeat => true,
+ (int)Illumise => true,
+ (int)Magnemite => true,
+ (int)Voltorb => true,
+ (int)Kangaskhan => true,
+ (int)Tauros => true,
+ (int)Ditto => true,
+ (int)Miltank => true,
+ (int)Beldum => true,
+ (int)Bronzor => true,
+ (int)Happiny => true,
+ (int)Tyrogue => true,
+ (int)Staryu => true,
+ (int)Lunatone => true,
+ (int)Solrock => true,
+ (int)Rotom => true,
+ (int)Klink => true,
+ (int)Golett => true,
+ _ => false,
+ };
+
+ private static bool IsNotHidden(int pkAbilityNumber) => pkAbilityNumber != 4;
+
+ private static bool IsBannedHiddenGen4(ushort species) => species switch
+ {
+ (int)Chikorita => true,
+ (int)Cyndaquil => true,
+ (int)Totodile => true,
+ (int)Treecko => true,
+ (int)Torchic => true,
+ (int)Mudkip => true,
+ (int)Turtwig => true,
+ (int)Chimchar => true,
+ (int)Piplup => true,
+ (int)Snivy => true,
+ (int)Tepig => true,
+ (int)Oshawott => true,
+
+ // Fossil Only obtain
+ (int)Archen => true,
+ (int)Tyrunt => true,
+ (int)Amaura => true,
+ _ => false,
+ };
+
+ private static bool IsBannedHiddenGen3(ushort species) => species switch
+ {
+ // Fossil Only obtain
+ (int)Archen => true,
+ (int)Tyrunt => true,
+ (int)Amaura => true,
+ _ => false,
+ };
+
+ private static BallType GetPermitBit(Ball ball) => ball switch
+ {
+ Ultra => BallType.Gen3,
+ Great => BallType.Gen3,
+ Poke => BallType.Gen3,
+ Safari => BallType.Safari,
+
+ Net => BallType.Gen3,
+ Dive => BallType.Gen3,
+ Nest => BallType.Gen3,
+ Repeat => BallType.Gen3,
+ Timer => BallType.Gen3,
+ Luxury => BallType.Gen3,
+ Premier => BallType.Gen3,
+
+ Dusk => BallType.Gen4,
+ Heal => BallType.Gen4,
+ Quick => BallType.Gen4,
+
+ Fast => BallType.Apricorn,
+ Level => BallType.Apricorn,
+ Lure => BallType.Apricorn,
+ Heavy => BallType.Apricorn,
+ Love => BallType.Apricorn,
+ Friend => BallType.Apricorn,
+ Moon => BallType.Apricorn,
+
+ Sport => BallType.Sport,
+ Dream => BallType.Dream,
+ Beast => BallType.Beast,
+ _ => BallType.None,
+ };
+
+ public static ReadOnlySpan Permit => new byte[]
+ {
+ 0x00, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x7B, 0x03, 0x03, 0x7B, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x6F, // 000-019
+ 0x03, 0x6F, 0x03, 0x6F, 0x03, 0x03, 0x03, 0x6F, 0x03, 0x2F, 0x03, 0x03, 0x2F, 0x03, 0x03, 0x03, 0x03, 0x6B, 0x03, 0x4B, // 020-039
+ 0x03, 0x6F, 0x03, 0x2F, 0x03, 0x03, 0x7F, 0x03, 0x3F, 0x03, 0x6F, 0x03, 0x6B, 0x03, 0x6F, 0x03, 0x6B, 0x03, 0x6B, 0x03, // 040-059
+ 0x6F, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x6B, 0x03, 0x6F, 0x03, 0x03, 0x2F, 0x03, 0x6F, // 060-079
+ 0x03, 0x6F, 0x03, 0x2F, 0x2F, 0x03, 0x6B, 0x03, 0x6F, 0x03, 0x6B, 0x03, 0x6F, 0x03, 0x03, 0x6F, 0x6F, 0x03, 0x2F, 0x03, // 080-099
+ 0x6F, 0x03, 0x6F, 0x03, 0x6F, 0x03, 0x03, 0x03, 0x6F, 0x2F, 0x03, 0x6F, 0x03, 0x6F, 0x2F, 0x6F, 0x6B, 0x03, 0x6F, 0x03, // 100-119
+ 0x6B, 0x03, 0x6F, 0x7F, 0x03, 0x03, 0x03, 0x7F, 0x6F, 0x6F, 0x03, 0x6F, 0x00, 0x6B, 0x03, 0x03, 0x03, 0x23, 0x23, 0x03, // 120-139
+ 0x23, 0x03, 0x23, 0x6B, 0x00, 0x00, 0x00, 0x6F, 0x03, 0x03, 0x00, 0x00, 0x4B, 0x03, 0x03, 0x4B, 0x03, 0x03, 0x4B, 0x03, // 140-159
+ 0x03, 0x2F, 0x03, 0x6F, 0x03, 0x6F, 0x03, 0x6F, 0x03, 0x03, 0x6B, 0x03, 0x6F, 0x6F, 0x6F, 0x6B, 0x03, 0x6F, 0x03, 0x6F, // 160-179
+ 0x03, 0x03, 0x03, 0x6F, 0x03, 0x6B, 0x03, 0x2F, 0x03, 0x07, 0x6F, 0x2F, 0x03, 0x6F, 0x6F, 0x03, 0x03, 0x03, 0x6F, 0x03, // 180-199
+ 0x6F, 0x00, 0x2F, 0x2F, 0x6F, 0x03, 0x6B, 0x2F, 0x03, 0x6F, 0x03, 0x2B, 0x4B, 0x2F, 0x6F, 0x6B, 0x2F, 0x03, 0x6B, 0x03, // 200-219
+ 0x6B, 0x03, 0x6B, 0x6F, 0x03, 0x6B, 0x6B, 0x6B, 0x6F, 0x03, 0x03, 0x2F, 0x03, 0x03, 0x2F, 0x6F, 0x23, 0x03, 0x6B, 0x6F, // 220-239
+ 0x6F, 0x6F, 0x03, 0x00, 0x00, 0x00, 0x6F, 0x03, 0x03, 0x00, 0x00, 0x00, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x6B, 0x03, // 240-259
+ 0x03, 0x2B, 0x03, 0x2F, 0x07, 0x3B, 0x03, 0x03, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x6B, 0x03, 0x6B, 0x03, // 260-279
+ 0x6B, 0x03, 0x03, 0x6F, 0x03, 0x2F, 0x03, 0x6F, 0x03, 0x03, 0x33, 0x03, 0x03, 0x2B, 0x03, 0x03, 0x6B, 0x03, 0x6F, 0x6F, // 280-299
+ 0x23, 0x03, 0x6B, 0x6B, 0x6F, 0x07, 0x03, 0x6F, 0x03, 0x6F, 0x03, 0x2B, 0x2B, 0x37, 0x37, 0x6F, 0x2F, 0x03, 0x6F, 0x03, // 300-319
+ 0x6B, 0x03, 0x2B, 0x03, 0x6F, 0x6B, 0x03, 0x6F, 0x6F, 0x03, 0x03, 0x27, 0x03, 0x6B, 0x03, 0x27, 0x27, 0x27, 0x27, 0x6F, // 320-339
+ 0x03, 0x6F, 0x03, 0x6B, 0x03, 0x23, 0x03, 0x23, 0x03, 0x6B, 0x03, 0x6B, 0x6F, 0x6F, 0x03, 0x27, 0x03, 0x6F, 0x2F, 0x6B, // 340-359
+ 0x2F, 0x6B, 0x03, 0x6F, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x6B, 0x6B, 0x6F, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x00, 0x00, 0x00, // 360-379
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x2F, // 380-399
+ 0x03, 0x3B, 0x03, 0x6F, 0x03, 0x03, 0x6F, 0x03, 0x23, 0x03, 0x23, 0x03, 0x2B, 0x03, 0x03, 0x3B, 0x03, 0x27, 0x6F, 0x03, // 400-419
+ 0x2B, 0x03, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x6B, 0x03, 0x03, 0x03, 0x23, 0x03, 0x2F, 0x23, 0x03, 0x2F, 0x03, 0x6B, 0x6F, // 420-439
+ 0x6F, 0x2B, 0x23, 0x6F, 0x03, 0x03, 0x6B, 0x6F, 0x03, 0x6F, 0x03, 0x6F, 0x03, 0x27, 0x03, 0x2F, 0x6B, 0x03, 0x6B, 0x6B, // 440-459
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x6B, // 460-479
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x03, 0x03, 0x4B, 0x03, // 480-499
+ 0x03, 0x4B, 0x03, 0x03, 0x03, 0x03, 0x4B, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x23, 0x03, 0x23, 0x03, 0x23, 0x03, 0x23, // 500-519
+ 0x03, 0x03, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x6B, 0x6B, 0x03, 0x03, 0x23, 0x03, 0x03, 0x23, 0x23, // 520-539
+ 0x4B, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x6B, 0x03, 0x6B, 0x6B, 0x03, 0x03, 0x03, 0x03, 0x23, 0x6B, 0x03, 0x6B, // 540-559
+ 0x03, 0x6B, 0x03, 0x03, 0x23, 0x03, 0x00, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x03, // 560-579
+ 0x6B, 0x03, 0x6B, 0x03, 0x03, 0x03, 0x03, 0x6B, 0x23, 0x03, 0x03, 0x03, 0x4B, 0x03, 0x6B, 0x23, 0x03, 0x03, 0x03, 0x6B, // 580-599
+ 0x03, 0x03, 0x4B, 0x03, 0x03, 0x6B, 0x03, 0x4B, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x6B, 0x4B, // 600-619
+ 0x03, 0x6B, 0x6B, 0x03, 0x6B, 0x03, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x23, 0x23, 0x4B, 0x03, 0x03, 0x4B, 0x03, 0x00, 0x00, // 620-639
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x03, 0x03, 0x4B, 0x03, 0x03, 0x4B, 0x03, 0x03, 0x03, // 640-659
+ 0x03, 0x4B, 0x03, 0x4B, 0x4B, 0x03, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x03, 0x03, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x03, 0x4B, // 660-679
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x00, 0x03, 0x00, 0x03, // 680-699
+ 0x03, 0x4B, 0x4B, 0x4B, 0x4B, 0x03, 0x03, 0x4B, 0x4B, 0x03, 0x03, 0x03, 0x03, 0x03, 0x4B, 0x03, 0x00, 0x00, 0x00, 0x00, // 700-719
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x43, 0x43, 0x4B, 0x43, 0x4B, 0x43, 0x43, 0x4B, // 720-739
+ 0x43, 0x4B, 0x4B, 0x43, 0x4B, 0x43, 0x4B, 0x4B, 0x43, 0x4B, 0x43, 0x4B, 0x43, 0x4B, 0x43, 0x4B, 0x43, 0x4B, 0x43, 0x4B, // 740-759
+ 0x43, 0x4B, 0x43, 0x43, 0x4B, 0x4B, 0x4B, 0x4B, 0x43, 0x4B, 0x43, 0x4B, 0x00, 0x00, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, // 760-779
+ 0x4B, 0x4B, 0x4B, 0x43, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 780-799
+ };
+
+ private enum BallType : byte
+ {
+ Gen3 = 0,
+ Gen4 = 1,
+ Safari = 2,
+ Apricorn = 3,
+ Sport = 4,
+ Dream = 5,
+ Beast = 6,
+
+ None = 9,
+ }
+}
diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallContextHOME.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallContextHOME.cs
new file mode 100644
index 000000000..6243f7d3f
--- /dev/null
+++ b/PKHeX.Core/Legality/Verifiers/Ball/BallContextHOME.cs
@@ -0,0 +1,158 @@
+using System;
+using static PKHeX.Core.Ball;
+using static PKHeX.Core.BallInheritanceResult;
+
+namespace PKHeX.Core;
+
+///
+/// Ball Inheritance Permissions for games interconnected to HOME.
+///
+public sealed class BallContextHOME : IBallContext
+{
+ public static readonly BallContextHOME Instance = new();
+
+ public bool CanBreedWithBall(ushort species, byte form, Ball ball)
+ {
+ // Eagerly return true for the most common case
+ if (ball is Poke)
+ return true;
+
+ if (species >= Permit.Length)
+ return false;
+ var permitBit = GetPermitBit(ball);
+ if (permitBit == BallType.None)
+ return false;
+
+ var permit = Permit[species];
+ if ((permit & (1 << (byte)permitBit)) == 0)
+ return false;
+ return true;
+ }
+
+ ///
+ /// Checks if a past ball inheritance context's restriction for Hidden Ability exclusions can be ignored.
+ ///
+ /// Current Entity format
+ /// Original encounter species
+ /// True if the restriction can be ignored, false if not.
+ /// Ability Patch can be used on any species that has a Hidden Ability distinct from its regular ability.
+ public static bool IsAbilityPatchPossible(int format, ushort species)
+ {
+ if (format <= 7)
+ return false;
+
+ // Species (that can breed, from Gen6/7) that have never had a Hidden Ability distinct from their regular ability
+ // Gen8+ has encounters with HA, so this is no longer a concern for anything originating from Gen8+.
+ return species switch
+ {
+ (int)Species.Lunatone => false, // Levitate
+ (int)Species.Solrock => false, // Levitate
+ (int)Species.Rotom => false, // Levitate
+ (int)Species.Archen => false, // Defeatist
+ _ => true,
+ };
+ }
+
+ public BallInheritanceResult CanBreedWithBall(ushort species, byte form, Ball ball, PKM pk) => CanBreedWithBall(species, form, ball) ? Valid : Invalid;
+
+ private static BallType GetPermitBit(Ball ball) => ball switch
+ {
+ Ultra => BallType.Gen3,
+ Great => BallType.Gen3,
+ Poke => BallType.Gen3,
+ Safari => BallType.Safari,
+
+ Net => BallType.Gen3,
+ Dive => BallType.Gen3,
+ Nest => BallType.Gen3,
+ Repeat => BallType.Gen3,
+ Timer => BallType.Gen3,
+ Luxury => BallType.Gen3,
+ Premier => BallType.Gen3,
+
+ Dusk => BallType.Gen4,
+ Heal => BallType.Gen4,
+ Quick => BallType.Gen4,
+
+ Fast => BallType.Apricorn,
+ Level => BallType.Apricorn,
+ Lure => BallType.Apricorn,
+ Heavy => BallType.Apricorn,
+ Love => BallType.Apricorn,
+ Friend => BallType.Apricorn,
+ Moon => BallType.Apricorn,
+
+ Sport => BallType.Sport,
+ Dream => BallType.Dream,
+ Beast => BallType.Beast,
+ _ => BallType.None,
+ };
+
+ public static ReadOnlySpan Permit => new byte[]
+ {
+ 0x00, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7B, 0x0B, 0x0B, 0x6F, 0x0B, 0x0B, 0x6F, // 000-019
+ 0x0B, 0x6F, 0x0B, 0x6F, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 020-039
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x0B, 0x7F, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6B, 0x6B, 0x7F, 0x7F, // 040-059
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6F, 0x6B, 0x6B, 0x7F, 0x7F, 0x6F, 0x6B, 0x6B, 0x7F, 0x7F, 0x7F, // 060-079
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x2F, 0x0B, 0x6B, 0x0B, 0x6F, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6F, 0x6B, 0x7F, 0x7F, // 080-099
+ 0x6F, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 100-119
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x00, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 120-139
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x00, 0x00, 0x00, 0x7F, 0x7F, 0x7F, 0x00, 0x00, 0x4B, 0x0B, 0x0B, 0x6B, 0x6B, 0x6B, 0x4B, 0x0B, // 140-159
+ 0x0B, 0x6F, 0x6B, 0x7F, 0x7F, 0x6F, 0x0B, 0x6F, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6F, // 160-179
+ 0x6B, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6F, 0x6B, 0x6F, 0x6F, 0x6F, 0x6B, 0x6F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6F, 0x7F, // 180-199
+ 0x6F, 0x00, 0x7F, 0x6F, 0x6F, 0x6B, 0x7F, 0x6F, 0x7F, 0x6F, 0x0B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6F, 0x6B, 0x6B, 0x6B, // 200-219
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6F, 0x6B, 0x7F, 0x6F, 0x6B, 0x7F, 0x6F, 0x6F, 0x7F, 0x7F, 0x7F, 0x7F, // 220-239
+ 0x7F, 0x7F, 0x7F, 0x00, 0x00, 0x00, 0x7F, 0x7F, 0x7F, 0x00, 0x00, 0x00, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 240-259
+ 0x7F, 0x6B, 0x6B, 0x7F, 0x7F, 0x3B, 0x0B, 0x0B, 0x0B, 0x0B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6B, 0x0B, 0x7F, 0x7F, // 260-279
+ 0x7F, 0x7F, 0x7F, 0x6F, 0x6B, 0x6F, 0x6B, 0x6F, 0x6B, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6B, 0x6B, 0x7F, 0x6F, // 280-299
+ 0x2B, 0x0B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6F, 0x6B, 0x7F, 0x7F, 0x2B, 0x2B, 0x7F, 0x7F, 0x7F, 0x6F, 0x6B, 0x7F, 0x7F, // 300-319
+ 0x7F, 0x7F, 0x6B, 0x6B, 0x7F, 0x6B, 0x6B, 0x6F, 0x7F, 0x7F, 0x7F, 0x6F, 0x6B, 0x7F, 0x7F, 0x6F, 0x6F, 0x7F, 0x7F, 0x7F, // 320-339
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6B, 0x6F, 0x6F, 0x6B, 0x7F, 0x7F, 0x6F, 0x6F, 0x7F, // 340-359
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6B, 0x0B, 0x0B, 0x7F, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x00, 0x00, 0x00, // 360-379
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6B, 0x0B, 0x0B, 0x6B, 0x0B, 0x0B, 0x6B, 0x0B, 0x0B, 0x6F, 0x6B, 0x6B, 0x2F, // 380-399
+ 0x0B, 0x7B, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x23, 0x0B, 0x23, 0x0B, 0x2B, 0x0B, 0x0B, 0x7F, 0x7F, 0x6F, 0x6F, 0x6B, // 400-419
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x6B, 0x6B, 0x2B, 0x0B, 0x6F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 420-439
+ 0x7F, 0x2B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x2F, 0x6B, 0x6B, 0x7F, 0x7F, // 440-459
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6B, 0x7F, 0x7F, 0x6B, 0x7F, 0x7F, 0x7F, 0x6B, 0x7F, 0x7F, 0x7F, // 460-479
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x03, 0x03, 0x4B, 0x03, // 480-499
+ 0x03, 0x6B, 0x6B, 0x6B, 0x03, 0x03, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x23, 0x03, 0x23, 0x03, 0x23, 0x03, 0x7F, 0x7F, 0x7F, // 500-519
+ 0x7F, 0x7F, 0x03, 0x03, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 520-539
+ 0x6B, 0x6B, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 540-559
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 560-579
+ 0x6B, 0x6B, 0x7F, 0x7F, 0x7F, 0x6B, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 580-599
+ 0x7F, 0x7F, 0x6B, 0x6B, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 600-619
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x00, 0x00, // 620-639
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x7F, // 640-659
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x7F, 0x7F, 0x4B, 0x7F, 0x7F, 0x7F, // 660-679
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 680-699
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x00, 0x00, 0x00, 0x00, // 700-719
+ 0x00, 0x00, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x4B, 0x43, 0x43, 0x6B, 0x6B, 0x7F, 0x7F, 0x7F, 0x6B, // 720-739
+ 0x6B, 0x6B, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 740-759
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x00, 0x00, 0x4B, 0x6B, 0x7F, 0x7F, 0x7F, 0x6B, // 760-779
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 780-799
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x7F, // 800-819
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 820-839
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 840-859
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, // 860-879
+ 0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F, 0x7F, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6B, // 880-899
+ 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, // 900-919
+ 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, // 920-939
+ 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, // 940-959
+ 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, // 960-979
+ 0x6B, 0x6B, 0x6B, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6B, 0x6B, 0x6B, 0x00, // 980-999
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6B, 0x6B, 0x6B,
+ };
+
+ private enum BallType : byte
+ {
+ Gen3 = 0,
+ Gen4 = 1,
+ Safari = 2,
+ Apricorn = 3,
+ Sport = 4,
+ Dream = 5,
+ Beast = 6,
+
+ None = 9,
+ }
+}
diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallContextUtil.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallContextUtil.cs
new file mode 100644
index 000000000..3e684717d
--- /dev/null
+++ b/PKHeX.Core/Legality/Verifiers/Ball/BallContextUtil.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Text;
+
+namespace PKHeX.Core;
+
+#if DEBUG
+public static class BallContextUtil
+{
+ ///
+ /// Creates a new string with the table formatted as is shown in the source code.
+ ///
+ public static string Export(ReadOnlySpan permit)
+ {
+ var sb = new StringBuilder(8 * permit.Length);
+ const int entriesPerLine = 20;
+ for (int i = 0; i < permit.Length; i++)
+ {
+ sb.Append($"0x{permit[i]:X2}, ");
+ if (i % entriesPerLine == entriesPerLine - 1)
+ sb.AppendLine($"// {i - 19:000}-{i:000}");
+ }
+ return sb.ToString();
+ }
+}
+#endif
diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallInheritanceResult.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallInheritanceResult.cs
new file mode 100644
index 000000000..05db7a921
--- /dev/null
+++ b/PKHeX.Core/Legality/Verifiers/Ball/BallInheritanceResult.cs
@@ -0,0 +1,8 @@
+namespace PKHeX.Core;
+
+public enum BallInheritanceResult
+{
+ Valid,
+ BadAbility,
+ Invalid,
+}
diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallUseLegality.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallUseLegality.cs
index 5b6af1286..13793639c 100644
--- a/PKHeX.Core/Legality/Verifiers/Ball/BallUseLegality.cs
+++ b/PKHeX.Core/Legality/Verifiers/Ball/BallUseLegality.cs
@@ -1,10 +1,28 @@
+using System.Diagnostics.CodeAnalysis;
using static PKHeX.Core.Ball;
namespace PKHeX.Core;
internal static class BallUseLegality
{
- internal static ulong GetWildBalls(int generation, GameVersion game) => generation switch
+ ///
+ /// In Sun/Moon, capturing with Heavy Ball is impossible in Sun/Moon for specific hard to catch species.
+ ///
+ /// Encounter species.
+ ///
+ /// Catch rate for these species is 3. Due to the heavy ball modifier adding [-20], the catch rate becomes 0.
+ ///
+ /// True if it is impossible to capture in a ball.
+ public static bool IsAlolanCaptureNoHeavyBall(ushort species) => species is (int)Species.Beldum or (int)Species.TapuKoko or (int)Species.TapuLele or (int)Species.TapuBulu or (int)Species.TapuFini;
+
+ public static bool IsBallPermitted([ConstantExpected] ulong permit, int ball)
+ {
+ if ((uint)ball >= 64)
+ return false;
+ return (permit & (1ul << ball)) != 0;
+ }
+
+ public static ulong GetWildBalls(int generation, GameVersion game) => generation switch
{
1 => WildPokeBalls1,
2 => WildPokeBalls2,
@@ -61,17 +79,17 @@ internal static class BallUseLegality
private const ulong WildPokeBalls2 = WildPokeBalls1;
private const ulong WildPokeBalls3 = WildPokeRegular | WildPokeEnhance3;
private const ulong WildPokeBalls4_DPPt = WildPokeBalls3 | WildPokeEnhance4;
- private const ulong WildPokeBalls4_HGSS = WildPokeBalls4_DPPt | WildPokeKurt4;
+ public const ulong WildPokeBalls4_HGSS = WildPokeBalls4_DPPt | WildPokeKurt4;
private const ulong WildPokeBalls5 = WildPokeBalls4_DPPt;
- internal const ulong DreamWorldBalls = WildPokeBalls5 | WildPokeEnhance5;
- internal const ulong WildPokeballs6 = WildPokeBalls5; // Same as Gen5
- internal const ulong WildPokeballs7 = WildPokeBalls4_HGSS | WildPokeEnhance7; // Same as HGSS + Beast
- internal const ulong WildPokeballs8 = WildPokeballs7 | WildPokeEnhance8;
+ public const ulong DreamWorldBalls = WildPokeBalls5 | WildPokeEnhance5;
+ private const ulong WildPokeballs6 = WildPokeBalls5; // Same as Gen5
+ private const ulong WildPokeballs7 = WildPokeBalls4_HGSS | WildPokeEnhance7; // Same as HGSS + Beast
+ private const ulong WildPokeballs8 = WildPokeballs7 | WildPokeEnhance8;
private const ulong WildPokeballs7b = WildPokeRegular | (1 << (int)Premier);
- internal const ulong WildPokeballs8g_WithRaid = WildPokeballs7b & ~(1ul << (int)Master); // Ultra Great Poke Premier, no Master
- internal const ulong WildPokeballs8g_WithoutRaid = WildPokeRegular & ~(1ul << (int)Master); // Ultra Great Poke, no Premier/Master
+ public const ulong WildPokeballs8g_WithRaid = WildPokeballs7b & ~(1ul << (int)Master); // Ultra Great Poke Premier, no Master
+ public const ulong WildPokeballs8g_WithoutRaid = WildPokeRegular & ~(1ul << (int)Master); // Ultra Great Poke, no Premier/Master
- internal const ulong WildPokeballs9 = WildPokeballs7 | WildPokeEnhance5; // Same as Gen7 + Dream
+ public const ulong WildPokeballs9 = WildPokeballs7 | WildPokeEnhance5; // Same as Gen7 + Dream
}
diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs
index 20ed2496d..0248f4052 100644
--- a/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.Ball;
@@ -9,7 +11,6 @@ namespace PKHeX.Core;
public sealed class BallVerifier : Verifier
{
protected override CheckIdentifier Identifier => CheckIdentifier.Ball;
- private CheckResult NONE => GetInvalid(LBallNone);
public override void Verify(LegalityAnalysis data)
{
@@ -60,438 +61,153 @@ private CheckResult VerifyBall(LegalityAnalysis data)
}
// Capturing with Heavy Ball is impossible in Sun/Moon for specific species.
- if (pk is { Ball: (int)Heavy, SM: true } && enc is not EncounterEgg && BallBreedLegality.AlolanCaptureNoHeavyBall.Contains(enc.Species))
+ if (pk is { Ball: (int)Heavy, SM: true } && enc is not EncounterEgg && BallUseLegality.IsAlolanCaptureNoHeavyBall(enc.Species))
return GetInvalid(LBallHeavy); // Heavy Ball, can inherit if from egg (US/UM fixed catch rate calc)
return enc switch
{
- EncounterStatic5Entree => VerifyBallEquals(data, BallUseLegality.DreamWorldBalls),
+ EncounterStatic5Entree => VerifyBallEquals((Ball)pk.Ball, BallUseLegality.DreamWorldBalls),
EncounterEgg => VerifyBallEgg(data),
EncounterInvalid => VerifyBallEquals(data, pk.Ball), // ignore ball, pass whatever
- _ => VerifyBallEquals(data, BallUseLegality.GetWildBalls(data.Info.Generation, enc.Version)),
+ _ => VerifyBallEquals((Ball)pk.Ball, BallUseLegality.GetWildBalls(data.Info.Generation, enc.Version)),
};
}
private CheckResult VerifyBallEgg(LegalityAnalysis data)
{
var pk = data.Entity;
- if (data.Info.Generation < 6) // No inheriting Balls
+ var info = data.Info;
+ if (info.Generation < 6) // No inheriting Balls
return VerifyBallEquals(data, (int)Poke); // Must be Pokéball -- no ball inheritance.
return pk.Ball switch
{
(int)Master => GetInvalid(LBallEggMaster), // Master Ball
(int)Cherish => GetInvalid(LBallEggCherish), // Cherish Ball
- _ => VerifyBallInherited(data),
+ _ => VerifyBallInherited(data, info.EncounterMatch.Context),
};
}
- private CheckResult VerifyBallInherited(LegalityAnalysis data) => data.Info.EncounterMatch.Context switch
+ private CheckResult VerifyBallInherited(LegalityAnalysis data, EntityContext context) => context switch
{
EntityContext.Gen6 => VerifyBallEggGen6(data), // Gen6 Inheritance Rules
EntityContext.Gen7 => VerifyBallEggGen7(data), // Gen7 Inheritance Rules
EntityContext.Gen8 => VerifyBallEggGen8(data),
EntityContext.Gen8b => VerifyBallEggGen8BDSP(data),
EntityContext.Gen9 => VerifyBallEggGen9(data),
- _ => NONE,
+ _ => GetInvalid(LBallNone),
};
private CheckResult VerifyBallEggGen6(LegalityAnalysis data)
{
var pk = data.Entity;
- if (pk.Ball == (int)Poke)
- return GetValid(LBallEnc); // Poké Ball
-
- var enc = data.EncounterMatch;
- var species = enc.Species;
- if (pk.Gender == 2 || BallBreedLegality.BreedMaleOnly6.Contains(species)) // Genderless
- return VerifyBallEquals(data, (int)Poke); // Must be Pokéball as ball can only pass via mother (not Ditto!)
-
- Ball ball = (Ball)pk.Ball;
-
- if (ball == Safari) // Safari Ball
- {
- if (!BallBreedLegality.Inherit_Safari.Contains(species))
- return GetInvalid(LBallSpecies);
- if (IsHiddenAndNotPossible(pk))
- return GetInvalid(LBallAbility);
- return GetValid(LBallSpeciesPass);
- }
- if (ball.IsApricornBall()) // Apricorn Ball
- {
- if (!BallBreedLegality.Inherit_Apricorn6.Contains(species))
- return GetInvalid(LBallSpecies);
- if (IsHiddenAndNotPossible(pk))
- return GetInvalid(LBallAbility);
- return GetValid(LBallSpeciesPass);
- }
- if (ball == Sport) // Sport Ball
- {
- if (!BallBreedLegality.Inherit_Sport.Contains(species))
- return GetInvalid(LBallSpecies);
- if (IsHiddenAndNotPossible(pk))
- return GetInvalid(LBallAbility);
- return GetValid(LBallSpeciesPass);
- }
- if (ball == Dream) // Dream Ball
- {
- if (BallBreedLegality.Ban_DreamHidden.Contains(species) && IsHiddenAndNotPossible(pk))
- return GetInvalid(LBallAbility);
- if (BallBreedLegality.Inherit_Dream.Contains(species))
- return GetValid(LBallSpeciesPass);
- return GetInvalid(LBallSpecies);
- }
- if (ball is >= Dusk and <= Quick) // Dusk Heal Quick
- {
- if (!BallBreedLegality.Ban_Gen4Ball_6.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.Contains(species))
- return GetInvalid(LBallSpecies);
- if (BallBreedLegality.Ban_Gen3BallHidden.Contains((ushort)(species | (enc.Form << 11))) && IsHiddenAndNotPossible(pk))
- return GetInvalid(LBallAbility);
- return GetValid(LBallSpeciesPass);
- }
-
- if (species > 650 && species != 700) // Sylveon
- {
- if (IsBallPermitted(BallUseLegality.WildPokeballs6, pk.Ball))
- return GetValid(LBallSpeciesPass);
- return GetInvalid(LBallSpecies);
- }
-
- if (ball >= Dream)
+ var ball = (Ball)pk.Ball;
+ var instance = BallContext6.Instance;
+ if (ball > Dream)
return GetInvalid(LBallUnavailable);
- return NONE;
+ var enc = data.EncounterMatch;
+ var result = instance.CanBreedWithBall(enc.Species, enc.Form, ball, pk);
+ return result switch
+ {
+ BallInheritanceResult.Valid => GetValid(LBallSpeciesPass),
+ BallInheritanceResult.BadAbility => GetInvalid(LBallAbility),
+ _ => GetInvalid(LBallSpecies),
+ };
}
private CheckResult VerifyBallEggGen7(LegalityAnalysis data)
{
var pk = data.Entity;
- if (pk.Ball == (int)Poke)
- return GetValid(LBallEnc); // Poké Ball
-
- var species = data.EncounterMatch.Species;
- if (species is >= 722 and <= 730) // G7 Starters
- return VerifyBallEquals(data, (int)Poke);
-
- Ball ball = (Ball)pk.Ball;
-
- 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(pk))
- return GetInvalid(LBallAbility);
- return GetValid(LBallSpeciesPass);
- }
- if (ball.IsApricornBall()) // Apricorn Ball
- {
- if (!BallBreedLegality.Inherit_Apricorn7.Contains(species))
- return GetInvalid(LBallSpecies);
- if (BallBreedLegality.Ban_NoHidden7Apricorn.Contains((ushort)(species | (pk.Form << 11))) && IsHiddenAndNotPossible(pk))
- 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(pk)) // 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);
- }
-
- if (ball == Beast)
- {
- if (species == (int)Species.Flabébé && pk.Form == 3 && IsHiddenAndNotPossible(pk))
- return GetInvalid(LBallAbility); // Can't obtain Flabébé-Blue with Hidden Ability in wild
- if (species == (int)Species.Voltorb && IsHiddenAndNotPossible(pk))
- return GetInvalid(LBallAbility); // Can't obtain with Hidden Ability in wild (can only breed with Ditto)
- if ((species is >= (int)Species.Pikipek and <= (int)Species.Kommoo) || BallBreedLegality.AlolanCaptureOffspring.Contains(species))
- return GetValid(LBallSpeciesPass);
- if (BallBreedLegality.PastGenAlolanScans.Contains(species))
- return GetValid(LBallSpeciesPass);
- // next statement catches all new alolans
- }
-
- if (species > (int)Species.Volcanion)
- return VerifyBallEquals(data, BallUseLegality.WildPokeballs7);
-
+ var ball = (Ball)pk.Ball;
+ var instance = BallContext7.Instance;
if (ball > Beast)
return GetInvalid(LBallUnavailable);
- return NONE;
+ var enc = data.EncounterMatch;
+ var result = instance.CanBreedWithBall(enc.Species, enc.Form, ball, pk);
+ return result switch
+ {
+ BallInheritanceResult.Valid => GetValid(LBallSpeciesPass),
+ BallInheritanceResult.BadAbility => GetInvalid(LBallAbility),
+ _ => GetInvalid(LBallSpecies),
+ };
}
private CheckResult VerifyBallEggGen8BDSP(LegalityAnalysis data)
{
var species = data.EncounterMatch.Species;
- if (species == (int)Species.Phione)
- return VerifyBallEquals(data, (int)Poke);
-
- if (species is (int)Species.Cranidos or (int)Species.Shieldon)
- return VerifyBallEquals(data, BallUseLegality.DreamWorldBalls);
-
var pk = data.Entity;
- Ball ball = (Ball)pk.Ball;
- var balls = BallUseLegality.GetWildBalls(8, GameVersion.BDSP);
- if (IsBallPermitted(balls, (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 (IsBallPermitted(BallUseLegality.WildPokeballs8, (int)ball))
- return GetValid(LBallSpeciesPass);
- }
- if (IsPaldeaCatchAndBreed(species))
- {
- if (IsBallPermitted(BallUseLegality.WildPokeballs9, (int)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(pk))
- 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((ushort)(species | (pk.Form << 11))) && IsHiddenAndNotPossible(pk)) // 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(pk)) // 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
- return GetValid(LBallSpeciesPass);
- if (ball is >= Ultra and <= Premier) // Don't worry, Safari was already checked.
- return GetValid(LBallSpeciesPass);
-
- if (ball == Beast)
- {
- // Most were already caught by Galar ball logic. Check for stuff not in SW/SH.
- if (BallBreedLegality.AlolanCaptureOffspring.Contains(species))
- return GetValid(LBallSpeciesPass);
- if (BallBreedLegality.PastGenAlolanScans.Contains(species))
- return GetValid(LBallSpeciesPass);
- }
+ var ball = (Ball)pk.Ball;
+ if (species is (int)Species.Spinda) // Can't transfer via HOME.
+ return VerifyBallEquals(ball, BallUseLegality.WildPokeBalls4_HGSS);
+ var instance = BallContextHOME.Instance;
if (ball > Beast)
return GetInvalid(LBallUnavailable);
- return GetInvalid(LBallEncMismatch);
+ var enc = data.EncounterMatch;
+ var result = instance.CanBreedWithBall(species, enc.Form, ball, pk);
+ return result switch
+ {
+ BallInheritanceResult.Valid => GetValid(LBallSpeciesPass),
+ BallInheritanceResult.BadAbility => GetInvalid(LBallAbility),
+ _ => GetInvalid(LBallSpecies),
+ };
}
private CheckResult VerifyBallEggGen8(LegalityAnalysis data)
{
var pk = data.Entity;
- if (pk.Ball == (int)Poke)
- return GetValid(LBallEnc); // Poké Ball
-
- var species = data.EncounterMatch.Species;
- if (IsGalarCatchAndBreed(species))
- {
- if (IsBallPermitted(BallUseLegality.WildPokeballs8, pk.Ball))
- return GetValid(LBallSpeciesPass);
- }
- if (IsPaldeaCatchAndBreed(species))
- {
- if (IsBallPermitted(BallUseLegality.WildPokeballs9, pk.Ball))
- return GetValid(LBallSpeciesPass);
- }
-
- Ball ball = (Ball)pk.Ball;
-
- 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(pk))
- 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((ushort)(species | (pk.Form << 11))) && IsHiddenAndNotPossible(pk)) // 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(pk)) // 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);
- }
-
- if (ball == Beast)
- {
- if (species == (int)Species.Flabébé && pk.Form == 3 && IsHiddenAndNotPossible(pk))
- return GetInvalid(LBallAbility); // Can't obtain Flabébé-Blue with Hidden Ability in wild
- if ((species is >= (int)Species.Pikipek and <= (int)Species.Kommoo) || BallBreedLegality.AlolanCaptureOffspring.Contains(species))
- return GetValid(LBallSpeciesPass);
- if (BallBreedLegality.PastGenAlolanScans.Contains(species))
- return GetValid(LBallSpeciesPass);
- // next statement catches all new alolans
- }
-
- if (species > Legal.MaxSpeciesID_7_USUM)
- return VerifyBallEquals(data, BallUseLegality.WildPokeballs8);
-
- if (species > (int)Species.Volcanion)
- return VerifyBallEquals(data, BallUseLegality.WildPokeballs7);
-
+ var ball = (Ball)pk.Ball;
+ var instance = BallContextHOME.Instance;
if (ball > Beast)
return GetInvalid(LBallUnavailable);
- return NONE;
+ var enc = data.EncounterMatch;
+ var result = instance.CanBreedWithBall(enc.Species, enc.Form, ball, pk);
+ return result switch
+ {
+ BallInheritanceResult.Valid => GetValid(LBallSpeciesPass),
+ BallInheritanceResult.BadAbility => GetInvalid(LBallAbility),
+ _ => GetInvalid(LBallSpecies),
+ };
}
private CheckResult VerifyBallEggGen9(LegalityAnalysis data)
{
var enc = data.EncounterMatch;
var species = enc.Species;
- if (species == (int)Species.Phione)
- return VerifyBallEquals(data, (int)Poke);
+ var pk = data.Entity;
+ var ball = (Ball)pk.Ball;
// Paldea Starters: Only via GO (Adventures Abound)
if (species is >= (int)Species.Sprigatito and <= (int)Species.Quaquaval)
- return VerifyBallEquals(data, BallUseLegality.WildPokeballs8g_WithoutRaid);
+ return VerifyBallEquals(ball, BallUseLegality.WildPokeballs8g_WithoutRaid);
// PLA Voltorb: Only via PLA (transfer only, not wild) and GO
if (enc is { Species: (ushort)Species.Voltorb, Form: 1 })
- return VerifyBallEquals(data, BallUseLegality.WildPokeballs8g_WithRaid);
+ return VerifyBallEquals(ball, BallUseLegality.WildPokeballs8g_WithRaid);
// S/V Tauros forms > 1: Only local Wild Balls for Blaze/Aqua breeds -- can't inherit balls from Kantonian/Combat.
if (enc is { Species: (ushort)Species.Tauros, Form: > 1 })
- return VerifyBallEquals(data, BallUseLegality.WildPokeballs9);
+ return VerifyBallEquals(ball, BallUseLegality.WildPokeballs9);
- var pk = data.Entity;
- if (IsPaldeaCatchAndBreed(species) && IsBallPermitted(BallUseLegality.WildPokeballs9, pk.Ball))
- return GetValid(LBallSpeciesPass);
+ var instance = BallContextHOME.Instance;
+ if (ball > Beast)
+ return GetInvalid(LBallUnavailable);
- if (species > Legal.MaxSpeciesID_8)
- return VerifyBallEquals(data, BallUseLegality.WildPokeballs9);
-
- return VerifyBallEggGen8(data);
- }
-
- private static bool IsHiddenAndNotPossible(PKM pk)
- {
- if (pk.AbilityNumber != 4)
- return false;
- var abilities = (IPersonalAbility12H)pk.PersonalInfo;
- return !AbilityVerifier.CanAbilityPatch(pk.Format, abilities, pk.Species);
- }
-
- private static bool IsGalarCatchAndBreed(ushort species)
- {
- if (species is >= (int)Species.Grookey and <= (int)Species.Inteleon) // starter
- return false;
-
- // Everything breed-able that is in the Galar Dex can be captured in-game.
- var pt = PersonalTable.SWSH;
- var pi = pt.GetFormEntry(species, 0);
- if (pi.IsInDex)
- return true;
-
- // Foreign Captures
- if (species is >= (int)Species.Treecko and <= (int)Species.Swampert) // Dynamax Adventures
- return true;
- if (species is >= (int)Species.Rowlet and <= (int)Species.Primarina) // Distribution Raids
- return true;
-
- return false;
- }
-
- private static bool IsPaldeaCatchAndBreed(ushort species)
- {
- if (species is >= (int)Species.Sprigatito and <= (int)Species.Quaquaval) // paldea starter
- return false;
- if (species is >= (int)Species.Turtwig and <= (int)Species.Empoleon) // sinnoh starter
- return false;
- var pt = PersonalTable.SV;
- var pi = pt.GetFormEntry(species, 0);
- if (pi.IsPresentInGame)
- return true;
-
- return false;
+ var result = instance.CanBreedWithBall(enc.Species, enc.Form, ball, pk);
+ return result switch
+ {
+ BallInheritanceResult.Valid => GetValid(LBallSpeciesPass),
+ BallInheritanceResult.BadAbility => GetInvalid(LBallAbility),
+ _ => GetInvalid(LBallSpecies),
+ };
}
private CheckResult VerifyBallEquals(LegalityAnalysis data, int ball) => GetResult(ball == data.Entity.Ball);
- private CheckResult VerifyBallEquals(LegalityAnalysis data, ulong permit) => GetResult(IsBallPermitted(permit, data.Entity.Ball));
-
- private static bool IsBallPermitted(ulong permit, int ball)
- {
- if ((uint)ball >= 64)
- return false;
- return (permit & (1ul << ball)) != 0;
- }
+ private CheckResult VerifyBallEquals(Ball ball, [ConstantExpected] ulong permit) => GetResult(BallUseLegality.IsBallPermitted(permit, (int)ball));
private CheckResult GetResult(bool valid) => valid ? GetValid(LBallEnc) : GetInvalid(LBallEncMismatch);
}
diff --git a/PKHeX.Core/Legality/Verifiers/Ball/IBallContext.cs b/PKHeX.Core/Legality/Verifiers/Ball/IBallContext.cs
new file mode 100644
index 000000000..16191af12
--- /dev/null
+++ b/PKHeX.Core/Legality/Verifiers/Ball/IBallContext.cs
@@ -0,0 +1,19 @@
+namespace PKHeX.Core;
+
+///
+/// Context for determining if a ball can be inherited.
+///
+public interface IBallContext
+{
+ ///
+ /// Checks if the can be bred with the given .
+ ///
+ /// Encounter Species
+ /// Encounter Form
+ /// Encounter Ball
+ /// Current Entity
+ BallInheritanceResult CanBreedWithBall(ushort species, byte form, Ball ball, PKM pk);
+
+ ///
+ bool CanBreedWithBall(ushort species, byte form, Ball ball);
+}
diff --git a/Tests/PKHeX.Core.Tests/Legality/BallTests.cs b/Tests/PKHeX.Core.Tests/Legality/BallTests.cs
new file mode 100644
index 000000000..aa42787e1
--- /dev/null
+++ b/Tests/PKHeX.Core.Tests/Legality/BallTests.cs
@@ -0,0 +1,24 @@
+using FluentAssertions;
+using Xunit;
+
+namespace PKHeX.Core.Tests.Legality;
+
+public class BallTests
+{
+ ///
+ /// Some species can't use ability patch yet.
+ ///
+ [Theory]
+ [InlineData(Species.Lunatone)]
+ [InlineData(Species.Solrock)]
+ [InlineData(Species.Rotom)]
+ [InlineData(Species.Archen)]
+ [InlineData(Species.Pikachu, true)] // can use ability patch
+ public void IsAbilityPatchPossible(Species species, bool expect = false)
+ {
+ var p7 = BallContextHOME.IsAbilityPatchPossible(7, (ushort)species);
+ p7.Should().Be(false);
+ var p8 = BallContextHOME.IsAbilityPatchPossible(8, (ushort)species);
+ p8.Should().Be(expect);
+ }
+}