diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs index b9b663214..c75c6f5a8 100644 --- a/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs @@ -20,16 +20,16 @@ public sealed class BallVerifier : Verifier data.AddLine(Localize(result)); } - private static byte IsReplacedBall(IVersion enc, PKM pk) => pk switch + private static Ball IsReplacedBall(IVersion enc, PKM pk) => pk switch { // Trading from PLA origin -> SW/SH will replace the Legends: Arceus ball with a regular Poké Ball - PK8 when enc.Version == GameVersion.PLA => (int)Poke, + PK8 when enc.Version == GameVersion.PLA => Poke, // No replacement done. _ => NoBallReplace, }; - private const int NoBallReplace = (int)None; + private const Ball NoBallReplace = None; public static BallVerificationResult VerifyBall(LegalityAnalysis data) { @@ -37,73 +37,83 @@ public sealed class BallVerifier : Verifier var enc = info.EncounterOriginal; var pk = data.Entity; - // Capture / Inherit cases -- can be one of many balls - if (pk.Species == (int)Species.Shedinja && enc.Species != (int)Species.Shedinja) // Shedinja. For Gen3, copy the ball from Nincada - { - // Only a Gen3 origin Shedinja can copy the wild ball. - // Evolution chains will indicate if it could have existed as Shedinja in Gen3. - // The special move verifier has a similar check! - if (enc is { Version: GameVersion.HG or GameVersion.SS, IsEgg: false } && pk is { Ball: (int)Sport }) // Can evolve in D/P to retain the HG/SS ball (separate byte) -- not able to be captured in any other ball - return GetResult(true); - if (enc.Generation != 3 || info.EvoChainsAllGens.Gen3.Length != 2) // not evolved in Gen3 Nincada->Shedinja - return VerifyBallEquals(pk, (int)Poke); // Poké Ball Only - } + Ball current = (Ball)pk.Ball; + if (enc.Species == (int)Species.Nincada && pk.Species == (int)Species.Shedinja) + return VerifyEvolvedShedinja(enc, current, pk, info); - return VerifyBall(pk, enc); + return VerifyBall(enc, current, pk); + } + + private static BallVerificationResult VerifyEvolvedShedinja(IEncounterable enc, Ball current, PKM pk, LegalInfo info) + { + // Nincada evolving into Shedinja normally reverts to Poké Ball. + + // Gen3 Evolution: Copy current ball. + if (enc is EncounterSlot3 && info.EvoChainsAllGens.Gen3.Length == 2) + return VerifyBall(enc, current, pk); + + // Gen4 D/P/Pt: Retain the HG/SS ball (stored in a separate byte) -- can only be caught in BCC with Sport Ball. + if (enc is EncounterSlot4 { Type: SlotType4.BugContest } && info.EvoChainsAllGens.Gen4.Length == 2) + return GetResult(current is Sport or Poke); + + return VerifyBallEquals(current, Poke); // Poké Ball Only } /// /// Verifies the currently set ball for the . /// + /// Encounter template + /// Current ball + /// Misc details like Version and Ability /// Call this directly instead of the overload if you've already ruled out the above cases needing Evolution chains. - public static BallVerificationResult VerifyBall(PKM pk, IEncounterTemplate enc) + public static BallVerificationResult VerifyBall(IEncounterTemplate enc, Ball current, PKM pk) { + // Capture / Inherit cases -- can be one of many balls var ball = IsReplacedBall(enc, pk); if (ball != NoBallReplace) - return VerifyBallEquals(pk, ball); + return VerifyBallEquals(current, ball); // Capturing with Heavy Ball is impossible in Sun/Moon for specific species. - if (pk is { Ball: (int)Heavy, SM: true } && enc is not EncounterEgg && BallUseLegality.IsAlolanCaptureNoHeavyBall(enc.Species)) + if (current is Heavy && enc is not EncounterEgg && pk is { SM: true } && BallUseLegality.IsAlolanCaptureNoHeavyBall(enc.Species)) return BadCaptureHeavy; // Heavy Ball, can inherit if from egg (US/UM fixed catch rate calc) return enc switch { EncounterSlot8GO => GetResult(true), // Already a strict match EncounterInvalid => GetResult(true), // ignore ball, pass whatever - IFixedBall { FixedBall: not None } s => VerifyBallEquals(pk, (byte)s.FixedBall), + IFixedBall { FixedBall: not None } s => VerifyBallEquals(current, s.FixedBall), - EncounterEgg => VerifyBallEgg(pk, enc), // Inheritance rules can vary. - EncounterStatic5Entree => VerifyBallEquals((Ball)pk.Ball, BallUseLegality.DreamWorldBalls), - _ => VerifyBallEquals((Ball)pk.Ball, BallUseLegality.GetWildBalls(enc.Generation, enc.Version)), + EncounterEgg => VerifyBallEgg(enc, current, pk), // Inheritance rules can vary. + EncounterStatic5Entree => VerifyBallEquals(current, BallUseLegality.DreamWorldBalls), + _ => VerifyBallEquals(current, BallUseLegality.GetWildBalls(enc.Generation, enc.Version)), }; } - private static BallVerificationResult VerifyBallEgg(PKM pk, IEncounterTemplate enc) + private static BallVerificationResult VerifyBallEgg(IEncounterTemplate enc, Ball ball, PKM pk) { if (enc.Generation < 6) // No inheriting Balls - return VerifyBallEquals(pk, (int)Poke); // Must be Poké Ball -- no ball inheritance. + return VerifyBallEquals(ball, Poke); // Must be Poké Ball -- no ball inheritance. - return pk.Ball switch + return ball switch { - (int)Master => BadInheritMaster, - (int)Cherish => BadInheritCherish, - _ => VerifyBallInherited(pk, enc), + Master => BadInheritMaster, + Cherish => BadInheritCherish, + _ => VerifyBallInherited(enc, ball, pk), }; } - private static BallVerificationResult VerifyBallInherited(PKM pk, IEncounterTemplate enc) => enc.Context switch + private static BallVerificationResult VerifyBallInherited(IEncounterTemplate enc, Ball ball, PKM pk) => enc.Context switch { - EntityContext.Gen6 => VerifyBallEggGen6(pk, enc), // Gen6 Inheritance Rules - EntityContext.Gen7 => VerifyBallEggGen7(pk, enc), // Gen7 Inheritance Rules - EntityContext.Gen8 => VerifyBallEggGen8(pk, enc), - EntityContext.Gen8b => VerifyBallEggGen8BDSP(pk, enc), - EntityContext.Gen9 => VerifyBallEggGen9(pk, enc), + EntityContext.Gen6 => VerifyBallEggGen6(enc, ball, pk), // Gen6 Inheritance Rules + EntityContext.Gen7 => VerifyBallEggGen7(enc, ball, pk), // Gen7 Inheritance Rules + EntityContext.Gen8 => VerifyBallEggGen8(enc, ball), + EntityContext.Gen8b => VerifyBallEggGen8BDSP(enc, ball), + EntityContext.Gen9 => VerifyBallEggGen9(enc, ball), _ => BadEncounter, }; - private static BallVerificationResult VerifyBallEggGen6(PKM pk, IEncounterTemplate enc) + private static BallVerificationResult VerifyBallEggGen6(IEncounterTemplate enc, Ball ball, PKM pk) { - var ball = (Ball)pk.Ball; if (ball > Dream) return BadOutOfRange; @@ -111,9 +121,8 @@ public sealed class BallVerifier : Verifier return GetResult(result); } - private static BallVerificationResult VerifyBallEggGen7(PKM pk, IEncounterTemplate enc) + private static BallVerificationResult VerifyBallEggGen7(IEncounterTemplate enc, Ball ball, PKM pk) { - var ball = (Ball)pk.Ball; if (ball > Beast) return BadOutOfRange; @@ -121,9 +130,8 @@ public sealed class BallVerifier : Verifier return GetResult(result); } - private static BallVerificationResult VerifyBallEggGen8BDSP(PKM pk, IEncounterTemplate enc) + private static BallVerificationResult VerifyBallEggGen8BDSP(IEncounterTemplate enc, Ball ball) { - var ball = (Ball)pk.Ball; if (ball > Beast) return BadOutOfRange; @@ -135,9 +143,8 @@ public sealed class BallVerifier : Verifier return GetResult(result); } - private static BallVerificationResult VerifyBallEggGen8(PKM pk, IEncounterTemplate enc) + private static BallVerificationResult VerifyBallEggGen8(IEncounterTemplate enc, Ball ball) { - var ball = (Ball)pk.Ball; if (ball > Beast) return BadOutOfRange; @@ -145,9 +152,8 @@ public sealed class BallVerifier : Verifier return GetResult(result); } - private static BallVerificationResult VerifyBallEggGen9(PKM pk, IEncounterTemplate enc) + private static BallVerificationResult VerifyBallEggGen9(IEncounterTemplate enc, Ball ball) { - var ball = (Ball)pk.Ball; if (ball > Beast) return BadOutOfRange; @@ -160,7 +166,7 @@ public sealed class BallVerifier : Verifier return GetResult(result); } - private static BallVerificationResult VerifyBallEquals(PKM pk, byte ball) => GetResult(ball == pk.Ball); + private static BallVerificationResult VerifyBallEquals(Ball ball, Ball permit) => GetResult(ball == permit); private static BallVerificationResult VerifyBallEquals(Ball ball, ulong permit) => GetResult(BallUseLegality.IsBallPermitted(permit, (byte)ball)); private static BallVerificationResult GetResult(bool valid) => valid ? ValidEncounter : BadEncounter;