Memoize Ball Breeding permission tables (#4050)

O(1) lookup for arbitrary species, some edge handling rules for specific game islands/forms. With HOME, there's only 3 islands of permissions. No allocation besides the singletons which aren't really necessary.

No longer need to peek within multiple hashsets, just fetch the "is possible" bit from the species listing and check if the bit is set.

Will be fun if ball shell swaps are added 🤞, get to set all bits for anything that can enter game with that feature (if ever added).

Add unit test for gen67 no-patch exclusion
This commit is contained in:
Kurt 2023-11-02 15:55:26 -07:00 committed by GitHub
parent 8e28ca8cf4
commit bced546c63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 754 additions and 1367 deletions

View file

@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using static PKHeX.Core.GroundTileAllowed;
namespace PKHeX.Core;
@ -243,7 +244,7 @@ public sealed record EncounterStatic4(GameVersion Version)
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 sealed record EncounterStatic4(GameVersion Version)
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)

View file

@ -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;

View file

@ -144,28 +144,11 @@ internal static class AbilityBreedLegality
(int)Rotom + (5 << 11),
};
// <summary>
// Species that cannot be bred with a Hidden Ability originating in <see cref="GameVersion.Gen8"/>
// </summary>
// internal static readonly HashSet<ushort> BanHidden8 = new(); // none as of DLC 1!
/// <summary>
/// Species that cannot be bred with a Hidden Ability originating in <see cref="GameVersion.BDSP"/>
/// </summary>
internal static readonly HashSet<ushort> 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,
};
}

View file

@ -1,985 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using static PKHeX.Core.Species;
namespace PKHeX.Core;
/// <summary>
/// Tables used for <see cref="BallVerifier"/>
/// </summary>
internal static class BallBreedLegality
{
/// <summary>
/// Species that are male only in Generation 6; for ball inheritance, these behave the same as Genderless species (no ball inherited).
/// </summary>
internal static readonly HashSet<ushort> BreedMaleOnly6 = new()
{
(int)Tauros,
(int)Rufflet,
(int)Braviary,
(int)Tyrogue,
(int)Sawk,
(int)Throh,
};
internal static readonly HashSet<ushort> 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<ushort> 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<ushort> 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<ushort> 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<ushort> Ban_DreamHidden = new()
{
(int)Plusle,
(int)Minun,
(int)Kecleon,
(int)Duskull,
};
internal static readonly HashSet<ushort> 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<ushort> 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<ushort> 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<ushort> 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<ushort> 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<ushort> Ban_NoHidden7Apricorn = new()
{
(int)NidoranF,
(int)NidoranM,
(int)Voltorb,
(int)Bronzor,
(int)Flabébé + (3 << 11), // Flabébé-Blue
};
internal static readonly HashSet<ushort> AlolanCaptureNoHeavyBall = new() { (int)Beldum, (int)TapuKoko, (int)TapuLele, (int)TapuBulu, (int)TapuFini };
private static readonly HashSet<ushort> Inherit_ApricornMale7 = new()
{
(int)Voltorb,
(int)Baltoy,
(int)Bronzor,
// Others are capturable in the Alola region
// Magnemite, Staryu, Tauros
};
internal static readonly HashSet<ushort> Inherit_Apricorn7 = new(Inherit_Apricorn6.Concat(Inherit_ApricornMale7).Concat(PastGenAlolanScans).Concat(AlolanCaptureOffspring).Distinct());
internal static readonly HashSet<ushort> Inherit_SafariMale = new()
{
(int)Tauros,
(int)Magnemite,
(int)Voltorb,
(int)Lunatone,
(int)Solrock,
(int)Beldum,
(int)Bronzor,
};
internal static readonly HashSet<ushort> 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<ushort> Ban_Gen3Ball_7 = new()
{
(int)Phione,
(int)Archen,
(int)Tyrunt,
(int)Amaura,
};
// Same as Gen3 Balls
internal static readonly HashSet<ushort> Ban_Gen4Ball_7 = Ban_Gen3Ball_7;
internal static readonly HashSet<ushort> 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<ushort> 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
};
/// <summary>
/// Gets a legal <see cref="Ball"/> value for a bred egg encounter.
/// </summary>
/// <param name="version">Version the egg was created on.</param>
/// <param name="species">Species the egg contained.</param>
/// <returns>Valid ball to hatch with.</returns>
/// <remarks>Not all things can hatch with a Poké Ball!</remarks>
#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;
}
}

View file

@ -0,0 +1,192 @@
using System;
using static PKHeX.Core.Ball;
using static PKHeX.Core.Species;
using static PKHeX.Core.BallInheritanceResult;
namespace PKHeX.Core;
/// <summary>
/// Ball Inheritance Permissions for <see cref="EntityContext.Gen6"/> games.
/// </summary>
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<byte> 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,
}
}

View file

@ -0,0 +1,228 @@
using System;
using static PKHeX.Core.Ball;
using static PKHeX.Core.Species;
using static PKHeX.Core.BallInheritanceResult;
namespace PKHeX.Core;
/// <summary>
/// Ball Inheritance Permissions for <see cref="EntityContext.Gen7"/> games.
/// </summary>
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<byte> 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,
}
}

View file

@ -0,0 +1,158 @@
using System;
using static PKHeX.Core.Ball;
using static PKHeX.Core.BallInheritanceResult;
namespace PKHeX.Core;
/// <summary>
/// Ball Inheritance Permissions for games interconnected to HOME.
/// </summary>
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;
}
/// <summary>
/// Checks if a past ball inheritance context's restriction for Hidden Ability exclusions can be ignored.
/// </summary>
/// <param name="format">Current Entity format</param>
/// <param name="species">Original encounter species</param>
/// <returns>True if the restriction can be ignored, false if not.</returns>
/// <remarks>Ability Patch can be used on any species that has a Hidden Ability distinct from its regular ability.</remarks>
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<byte> 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,
}
}

View file

@ -0,0 +1,25 @@
using System;
using System.Text;
namespace PKHeX.Core;
#if DEBUG
public static class BallContextUtil
{
/// <summary>
/// Creates a new string with the table formatted as is shown in the source code.
/// </summary>
public static string Export(ReadOnlySpan<byte> 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

View file

@ -0,0 +1,8 @@
namespace PKHeX.Core;
public enum BallInheritanceResult
{
Valid,
BadAbility,
Invalid,
}

View file

@ -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
/// <summary>
/// In Sun/Moon, capturing with Heavy Ball is impossible in Sun/Moon for specific hard to catch species.
/// </summary>
/// <param name="species">Encounter species.</param>
/// <remarks>
/// Catch rate for these species is 3. Due to the heavy ball modifier adding [-20], the catch rate becomes 0.
/// </remarks>
/// <returns>True if it is impossible to capture in a <see cref="Heavy"/> ball.</returns>
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
}

View file

@ -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 @@ public sealed class BallVerifier : Verifier
}
// 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);
}

View file

@ -0,0 +1,19 @@
namespace PKHeX.Core;
/// <summary>
/// Context for determining if a ball can be inherited.
/// </summary>
public interface IBallContext
{
/// <summary>
/// Checks if the <see cref="PKM"/> can be bred with the given <see cref="Ball"/>.
/// </summary>
/// <param name="species">Encounter Species</param>
/// <param name="form">Encounter Form</param>
/// <param name="ball">Encounter Ball</param>
/// <param name="pk">Current Entity</param>
BallInheritanceResult CanBreedWithBall(ushort species, byte form, Ball ball, PKM pk);
/// <inheritdoc cref="CanBreedWithBall(ushort, byte, Ball, PKM)"/>
bool CanBreedWithBall(ushort species, byte form, Ball ball);
}

View file

@ -0,0 +1,24 @@
using FluentAssertions;
using Xunit;
namespace PKHeX.Core.Tests.Legality;
public class BallTests
{
/// <summary>
/// Some species can't use ability patch yet.
/// </summary>
[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);
}
}