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); + } +}