2018-06-24 05:00:01 +00:00
|
|
|
using static PKHeX.Core.LegalityCheckStrings;
|
2018-08-26 18:15:32 +00:00
|
|
|
using static PKHeX.Core.Ball;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
namespace PKHeX.Core;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Verifies the <see cref="PKM.Ball"/> value.
|
|
|
|
/// </summary>
|
|
|
|
public sealed class BallVerifier : Verifier
|
2018-06-24 05:00:01 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
protected override CheckIdentifier Identifier => CheckIdentifier.Ball;
|
|
|
|
private CheckResult NONE => GetInvalid(LBallNone);
|
|
|
|
|
|
|
|
public override void Verify(LegalityAnalysis data)
|
|
|
|
{
|
|
|
|
if (data.Entity.Format <= 2)
|
|
|
|
return; // no ball info saved
|
|
|
|
var result = VerifyBall(data);
|
|
|
|
data.AddLine(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static int IsReplacedBall(IVersion enc, PKM pk) => pk switch
|
2018-06-24 05:00:01 +00:00
|
|
|
{
|
2022-08-21 08:39:16 +00:00
|
|
|
// Trading from PLA origin -> SW/SH will replace the Legends: Arceus ball with a regular Poké Ball
|
2022-06-18 18:04:24 +00:00
|
|
|
PK8 when enc.Version == GameVersion.PLA => (int)Poke,
|
2022-08-21 08:39:16 +00:00
|
|
|
|
|
|
|
// No replacement done.
|
2022-06-18 18:04:24 +00:00
|
|
|
_ => (int)None,
|
|
|
|
};
|
2018-06-30 17:34:09 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private CheckResult VerifyBall(LegalityAnalysis data)
|
|
|
|
{
|
|
|
|
var Info = data.Info;
|
|
|
|
var enc = Info.EncounterMatch;
|
|
|
|
|
|
|
|
var ball = IsReplacedBall(enc, data.Entity);
|
|
|
|
if (ball != 0)
|
|
|
|
return VerifyBallEquals(data, ball);
|
|
|
|
|
|
|
|
// Fixed ball cases -- can be only one ball ever
|
|
|
|
switch (enc)
|
2018-06-24 05:00:01 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
case MysteryGift g:
|
|
|
|
return VerifyBallMysteryGift(data, g);
|
|
|
|
case EncounterTrade t:
|
|
|
|
return VerifyBallEquals(data, t.Ball);
|
|
|
|
case EncounterStatic {Gift: true} s:
|
|
|
|
return VerifyBallEquals(data, s.Ball);
|
|
|
|
case EncounterSlot8GO: // Already a strict match
|
|
|
|
return GetResult(true);
|
|
|
|
case EncounterSlot8b {IsMarsh: true}:
|
|
|
|
return VerifyBallEquals(data, (int)Safari);
|
2018-06-30 17:34:09 +00:00
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
// Capture / Inherit cases -- can be one of many balls
|
|
|
|
var pk = data.Entity;
|
|
|
|
if (pk.Species == (int)Species.Shedinja && enc.Species != (int)Species.Shedinja) // Shedinja. For gen3, copy the ball from Nincada
|
2022-05-31 04:43:52 +00:00
|
|
|
{
|
2022-08-21 08:39:16 +00:00
|
|
|
// Only a Gen3 origin Shedinja can copy the wild ball.
|
2022-06-18 18:04:24 +00:00
|
|
|
// Evolution chains will indicate if it could have existed as Shedinja in Gen3.
|
|
|
|
// The special move verifier has a similar check!
|
2023-01-22 04:02:33 +00:00
|
|
|
if (pk is { HGSS: true, Ball: (int)Sport }) // Can evolve in DP to retain the HG/SS ball (separate byte) -- not able to be captured in any other ball
|
2022-06-18 18:04:24 +00:00
|
|
|
return VerifyBallEquals(data, (int)Sport);
|
2022-08-21 08:39:16 +00:00
|
|
|
if (Info.Generation != 3 || Info.EvoChainsAllGens.Gen3.Length != 2) // not evolved in Gen3 Nincada->Shedinja
|
|
|
|
return VerifyBallEquals(data, (int)Poke); // Poké ball Only
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2022-05-31 04:43:52 +00:00
|
|
|
|
2022-08-21 08:39:16 +00:00
|
|
|
// Capturing with Heavy Ball is impossible in Sun/Moon for specific species.
|
2023-01-22 04:02:33 +00:00
|
|
|
if (pk is { Ball: (int)Heavy, SM: true } && enc is not EncounterEgg && BallBreedLegality.AlolanCaptureNoHeavyBall.Contains(enc.Species))
|
2022-08-21 08:39:16 +00:00
|
|
|
return GetInvalid(LBallHeavy); // Heavy Ball, can inherit if from egg (US/UM fixed catch rate calc)
|
2022-06-18 18:04:24 +00:00
|
|
|
|
|
|
|
return enc switch
|
2018-06-30 17:34:09 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
EncounterStatic e => VerifyBallStatic(data, e),
|
|
|
|
EncounterSlot w => VerifyBallWild(data, w),
|
|
|
|
EncounterEgg => VerifyBallEgg(data),
|
|
|
|
EncounterInvalid => VerifyBallEquals(data, pk.Ball), // ignore ball, pass whatever
|
|
|
|
_ => VerifyBallEquals(data, (int)Poke),
|
|
|
|
};
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
2023-01-22 04:02:33 +00:00
|
|
|
private CheckResult VerifyBallMysteryGift(LegalityAnalysis data, MysteryGift gift)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
2023-01-22 04:02:33 +00:00
|
|
|
if (gift is { Generation: 4, Species: (int)Species.Manaphy, Ball: 0 }) // there is no ball data in Manaphy PGT Mystery Gift from Gen4
|
2022-06-18 18:04:24 +00:00
|
|
|
return VerifyBallEquals(data, (int)Poke); // Pokeball
|
2023-01-22 04:02:33 +00:00
|
|
|
return VerifyBallEquals(data, gift.Ball);
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2022-05-31 04:43:52 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private CheckResult VerifyBallStatic(LegalityAnalysis data, EncounterStatic s)
|
|
|
|
{
|
2023-01-22 04:02:33 +00:00
|
|
|
if (s is EncounterStatic5 { EntreeForestDreamWorld: true })
|
2022-06-18 18:04:24 +00:00
|
|
|
return VerifyBallEquals(data, BallUseLegality.DreamWorldBalls);
|
|
|
|
return VerifyBallEquals(data, BallUseLegality.GetWildBalls(data.Info.Generation, s.Version));
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private CheckResult VerifyBallWild(LegalityAnalysis data, EncounterSlot w)
|
|
|
|
{
|
|
|
|
var req = w.FixedBall;
|
|
|
|
if (req != None)
|
|
|
|
return VerifyBallEquals(data, (int) req);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
return VerifyBallEquals(data, BallUseLegality.GetWildBalls(data.Info.Generation, w.Version));
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private CheckResult VerifyBallEgg(LegalityAnalysis data)
|
|
|
|
{
|
|
|
|
var pk = data.Entity;
|
|
|
|
if (data.Info.Generation < 6) // No inheriting Balls
|
|
|
|
return VerifyBallEquals(data, (int)Poke); // Must be Pokéball -- no ball inheritance.
|
2018-06-24 05:00:01 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
return pk.Ball switch
|
2018-06-30 17:34:09 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
(int)Master => GetInvalid(LBallEggMaster), // Master Ball
|
|
|
|
(int)Cherish => GetInvalid(LBallEggCherish), // Cherish Ball
|
|
|
|
_ => VerifyBallInherited(data),
|
|
|
|
};
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
2022-08-20 05:37:27 +00:00
|
|
|
private CheckResult VerifyBallInherited(LegalityAnalysis data) => data.Info.EncounterMatch.Context switch
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
2022-08-20 05:37:27 +00:00
|
|
|
EntityContext.Gen6 => VerifyBallEggGen6(data), // Gen6 Inheritance Rules
|
|
|
|
EntityContext.Gen7 => VerifyBallEggGen7(data), // Gen7 Inheritance Rules
|
|
|
|
EntityContext.Gen8 => VerifyBallEggGen8(data),
|
|
|
|
EntityContext.Gen8b => VerifyBallEggGen8BDSP(data),
|
2022-11-25 01:42:17 +00:00
|
|
|
EntityContext.Gen9 => VerifyBallEggGen9(data),
|
2022-06-18 18:04:24 +00:00
|
|
|
_ => NONE,
|
|
|
|
};
|
2018-07-27 02:34:27 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private CheckResult VerifyBallEggGen6(LegalityAnalysis data)
|
|
|
|
{
|
|
|
|
var pk = data.Entity;
|
|
|
|
if (pk.Ball == (int)Poke)
|
|
|
|
return GetValid(LBallEnc); // Poké Ball
|
2018-06-30 17:34:09 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
var enc = data.EncounterMatch;
|
2022-08-27 06:43:36 +00:00
|
|
|
var species = enc.Species;
|
2022-06-18 18:04:24 +00:00
|
|
|
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!)
|
2018-07-27 02:34:27 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
Ball ball = (Ball)pk.Ball;
|
2018-06-30 17:34:09 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (ball == Safari) // Safari Ball
|
|
|
|
{
|
|
|
|
if (!BallBreedLegality.Inherit_Safari.Contains(species))
|
|
|
|
return GetInvalid(LBallSpecies);
|
|
|
|
if (IsHiddenAndNotPossible(pk))
|
|
|
|
return GetInvalid(LBallAbility);
|
|
|
|
return GetValid(LBallSpeciesPass);
|
2018-06-30 17:34:09 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
if (ball.IsApricornBall()) // Apricorn Ball
|
2018-06-30 17:34:09 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
if (!BallBreedLegality.Inherit_Apricorn6.Contains(species))
|
|
|
|
return GetInvalid(LBallSpecies);
|
|
|
|
if (IsHiddenAndNotPossible(pk))
|
|
|
|
return GetInvalid(LBallAbility);
|
|
|
|
return GetValid(LBallSpeciesPass);
|
|
|
|
}
|
|
|
|
if (ball == Sport) // Sport Ball
|
2018-06-24 05:00:01 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
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))
|
2018-09-01 21:11:12 +00:00
|
|
|
return GetValid(LBallSpeciesPass);
|
2022-06-18 18:04:24 +00:00
|
|
|
return GetInvalid(LBallSpecies);
|
|
|
|
}
|
|
|
|
if (ball is >= Dusk and <= Quick) // Dusk Heal Quick
|
|
|
|
{
|
|
|
|
if (!BallBreedLegality.Ban_Gen4Ball_6.Contains(species))
|
2018-09-01 21:11:12 +00:00
|
|
|
return GetValid(LBallSpeciesPass);
|
2022-06-18 18:04:24 +00:00
|
|
|
return GetInvalid(LBallSpecies);
|
|
|
|
}
|
|
|
|
if (ball is >= Ultra and <= Premier) // Don't worry, Safari was already checked.
|
|
|
|
{
|
|
|
|
if (BallBreedLegality.Ban_Gen3Ball.Contains(species))
|
2018-09-01 21:11:12 +00:00
|
|
|
return GetInvalid(LBallSpecies);
|
2022-08-27 06:43:36 +00:00
|
|
|
if (BallBreedLegality.Ban_Gen3BallHidden.Contains((ushort)(species | (enc.Form << 11))) && IsHiddenAndNotPossible(pk))
|
2022-06-18 18:04:24 +00:00
|
|
|
return GetInvalid(LBallAbility);
|
|
|
|
return GetValid(LBallSpeciesPass);
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (species > 650 && species != 700) // Sylveon
|
|
|
|
{
|
2022-08-26 08:20:49 +00:00
|
|
|
if (IsBallPermitted(BallUseLegality.WildPokeballs6, pk.Ball))
|
2022-06-18 18:04:24 +00:00
|
|
|
return GetValid(LBallSpeciesPass);
|
|
|
|
return GetInvalid(LBallSpecies);
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (ball >= Dream)
|
|
|
|
return GetInvalid(LBallUnavailable);
|
2018-08-26 18:15:32 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
return NONE;
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private CheckResult VerifyBallEggGen7(LegalityAnalysis data)
|
|
|
|
{
|
|
|
|
var pk = data.Entity;
|
|
|
|
if (pk.Ball == (int)Poke)
|
|
|
|
return GetValid(LBallEnc); // Poké Ball
|
2021-12-04 18:52:57 +00:00
|
|
|
|
2022-08-27 06:43:36 +00:00
|
|
|
var species = data.EncounterMatch.Species;
|
2022-06-18 18:04:24 +00:00
|
|
|
if (species is >= 722 and <= 730) // G7 Starters
|
|
|
|
return VerifyBallEquals(data, (int)Poke);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
Ball ball = (Ball)pk.Ball;
|
2018-07-01 17:13:26 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (ball == Safari)
|
|
|
|
{
|
|
|
|
if (!(BallBreedLegality.Inherit_Safari.Contains(species) || BallBreedLegality.Inherit_SafariMale.Contains(species)))
|
2018-09-01 21:11:12 +00:00
|
|
|
return GetInvalid(LBallSpecies);
|
2022-06-18 18:04:24 +00:00
|
|
|
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))
|
2018-09-01 21:11:12 +00:00
|
|
|
return GetInvalid(LBallSpecies);
|
2022-08-27 06:43:36 +00:00
|
|
|
if (BallBreedLegality.Ban_NoHidden7Apricorn.Contains((ushort)(species | (pk.Form << 11))) && IsHiddenAndNotPossible(pk))
|
2022-06-18 18:04:24 +00:00
|
|
|
return GetInvalid(LBallAbility);
|
|
|
|
return GetValid(LBallSpeciesPass);
|
|
|
|
}
|
|
|
|
if (ball == Sport) // Sport Ball
|
|
|
|
{
|
|
|
|
if (!BallBreedLegality.Inherit_Sport.Contains(species))
|
2018-09-01 21:11:12 +00:00
|
|
|
return GetInvalid(LBallSpecies);
|
2022-06-18 18:04:24 +00:00
|
|
|
if ((species is (int)Species.Volbeat or (int)Species.Illumise) && IsHiddenAndNotPossible(pk)) // Volbeat/Illumise
|
|
|
|
return GetInvalid(LBallAbility);
|
|
|
|
return GetValid(LBallSpeciesPass);
|
2018-06-24 05:00:01 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
if (ball == Dream) // Dream Ball
|
2021-11-20 02:23:49 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
if (BallBreedLegality.Inherit_Dream.Contains(species) || BallBreedLegality.Inherit_DreamMale.Contains(species))
|
2022-05-31 04:43:52 +00:00
|
|
|
return GetValid(LBallSpeciesPass);
|
2022-06-18 18:04:24 +00:00
|
|
|
return GetInvalid(LBallSpecies);
|
|
|
|
}
|
|
|
|
if (ball is >= Dusk and <= Quick) // Dusk Heal Quick
|
|
|
|
{
|
|
|
|
if (!BallBreedLegality.Ban_Gen4Ball_7.Contains(species))
|
2022-05-31 04:43:52 +00:00
|
|
|
return GetValid(LBallSpeciesPass);
|
2022-06-18 18:04:24 +00:00
|
|
|
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)
|
2022-06-30 02:39:08 +00:00
|
|
|
if ((species is >= (int)Species.Pikipek and <= (int)Species.Kommoo) || BallBreedLegality.AlolanCaptureOffspring.Contains(species))
|
2022-05-31 04:43:52 +00:00
|
|
|
return GetValid(LBallSpeciesPass);
|
2022-06-18 18:04:24 +00:00
|
|
|
if (BallBreedLegality.PastGenAlolanScans.Contains(species))
|
2022-05-31 04:43:52 +00:00
|
|
|
return GetValid(LBallSpeciesPass);
|
2022-06-18 18:04:24 +00:00
|
|
|
// next statement catches all new alolans
|
|
|
|
}
|
2021-11-20 02:23:49 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (species > (int)Species.Volcanion)
|
|
|
|
return VerifyBallEquals(data, BallUseLegality.WildPokeballs7);
|
2022-05-31 04:43:52 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (ball > Beast)
|
|
|
|
return GetInvalid(LBallUnavailable);
|
2022-05-31 04:43:52 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
return NONE;
|
|
|
|
}
|
2021-11-20 02:23:49 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private CheckResult VerifyBallEggGen8BDSP(LegalityAnalysis data)
|
|
|
|
{
|
2022-08-27 06:43:36 +00:00
|
|
|
var species = data.EncounterMatch.Species;
|
2022-06-18 18:04:24 +00:00
|
|
|
if (species == (int)Species.Phione)
|
|
|
|
return VerifyBallEquals(data, (int)Poke);
|
2021-12-04 18:52:57 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (species is (int)Species.Cranidos or (int)Species.Shieldon)
|
|
|
|
return VerifyBallEquals(data, BallUseLegality.DreamWorldBalls);
|
2019-09-23 23:56:47 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
var pk = data.Entity;
|
|
|
|
Ball ball = (Ball)pk.Ball;
|
|
|
|
var balls = BallUseLegality.GetWildBalls(8, GameVersion.BDSP);
|
2022-08-26 08:20:49 +00:00
|
|
|
if (IsBallPermitted(balls, (int)ball))
|
2022-06-18 18:04:24 +00:00
|
|
|
return GetValid(LBallSpeciesPass);
|
2019-11-17 01:45:51 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (species is (int)Species.Spinda)
|
|
|
|
return GetInvalid(LBallSpecies); // Can't enter or exit, needs to adhere to wild balls.
|
2019-09-23 23:56:47 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
// Cross-game inheritance
|
|
|
|
if (IsGalarCatchAndBreed(species))
|
|
|
|
{
|
2022-08-26 08:20:49 +00:00
|
|
|
if (IsBallPermitted(BallUseLegality.WildPokeballs8, (int)ball))
|
2019-09-23 23:56:47 +00:00
|
|
|
return GetValid(LBallSpeciesPass);
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ball == Safari)
|
|
|
|
{
|
|
|
|
if (!(BallBreedLegality.Inherit_Safari.Contains(species) || BallBreedLegality.Inherit_SafariMale.Contains(species)))
|
2019-09-23 23:56:47 +00:00
|
|
|
return GetInvalid(LBallSpecies);
|
2022-06-18 18:04:24 +00:00
|
|
|
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))
|
2019-09-23 23:56:47 +00:00
|
|
|
return GetInvalid(LBallSpecies);
|
2022-08-27 06:43:36 +00:00
|
|
|
if (BallBreedLegality.Ban_NoHidden8Apricorn.Contains((ushort)(species | (pk.Form << 11))) && IsHiddenAndNotPossible(pk)) // lineage is 3->2->origin
|
2022-06-18 18:04:24 +00:00
|
|
|
return GetInvalid(LBallAbility);
|
|
|
|
return GetValid(LBallSpeciesPass);
|
|
|
|
}
|
|
|
|
if (ball == Sport) // Sport Ball
|
|
|
|
{
|
|
|
|
if (!BallBreedLegality.Inherit_Sport.Contains(species))
|
2019-09-23 23:56:47 +00:00
|
|
|
return GetInvalid(LBallSpecies);
|
2022-06-18 18:04:24 +00:00
|
|
|
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)
|
|
|
|
{
|
2022-06-30 02:39:08 +00:00
|
|
|
// Most were already caught by Galar ball logic. Check for stuff not in SW/SH.
|
|
|
|
if (BallBreedLegality.AlolanCaptureOffspring.Contains(species))
|
|
|
|
return GetValid(LBallSpeciesPass);
|
2022-06-18 18:04:24 +00:00
|
|
|
if (BallBreedLegality.PastGenAlolanScans.Contains(species))
|
|
|
|
return GetValid(LBallSpeciesPass);
|
|
|
|
}
|
2019-09-23 23:56:47 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (ball > Beast)
|
|
|
|
return GetInvalid(LBallUnavailable);
|
2019-09-23 23:56:47 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
return GetInvalid(LBallEncMismatch);
|
|
|
|
}
|
2019-09-23 23:56:47 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private CheckResult VerifyBallEggGen8(LegalityAnalysis data)
|
|
|
|
{
|
|
|
|
var pk = data.Entity;
|
|
|
|
if (pk.Ball == (int)Poke)
|
|
|
|
return GetValid(LBallEnc); // Poké Ball
|
2019-09-23 23:56:47 +00:00
|
|
|
|
2022-08-27 06:43:36 +00:00
|
|
|
var species = data.EncounterMatch.Species;
|
2022-06-18 18:04:24 +00:00
|
|
|
if (species is >= (int)Species.Grookey and <= (int)Species.Inteleon) // G8 Starters
|
|
|
|
return VerifyBallEquals(data, (int)Poke);
|
2019-09-23 23:56:47 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (IsGalarCatchAndBreed(species))
|
|
|
|
{
|
2022-08-26 08:20:49 +00:00
|
|
|
if (IsBallPermitted(BallUseLegality.WildPokeballs8, pk.Ball))
|
2022-06-18 18:04:24 +00:00
|
|
|
return GetValid(LBallSpeciesPass);
|
|
|
|
if (species >= (int)Species.Grookey)
|
|
|
|
return GetInvalid(LBallSpecies);
|
2019-09-23 23:56:47 +00:00
|
|
|
}
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
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);
|
2022-08-27 06:43:36 +00:00
|
|
|
if (BallBreedLegality.Ban_NoHidden8Apricorn.Contains((ushort)(species | (pk.Form << 11))) && IsHiddenAndNotPossible(pk)) // lineage is 3->2->origin
|
2022-06-18 18:04:24 +00:00
|
|
|
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
|
2020-10-10 19:30:57 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
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);
|
2020-10-10 19:30:57 +00:00
|
|
|
}
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (ball == Beast)
|
2019-11-17 01:45:51 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
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
|
2022-06-30 02:39:08 +00:00
|
|
|
if ((species is >= (int)Species.Pikipek and <= (int)Species.Kommoo) || BallBreedLegality.AlolanCaptureOffspring.Contains(species))
|
2022-06-18 18:04:24 +00:00
|
|
|
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);
|
2020-10-24 18:16:01 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (species > (int)Species.Volcanion)
|
|
|
|
return VerifyBallEquals(data, BallUseLegality.WildPokeballs7);
|
2020-10-26 01:07:13 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (ball > Beast)
|
|
|
|
return GetInvalid(LBallUnavailable);
|
|
|
|
|
|
|
|
return NONE;
|
|
|
|
}
|
2020-10-26 01:07:13 +00:00
|
|
|
|
2022-11-25 01:42:17 +00:00
|
|
|
private CheckResult VerifyBallEggGen9(LegalityAnalysis data)
|
|
|
|
{
|
2022-11-25 19:15:40 +00:00
|
|
|
var species = data.EncounterMatch.Species;
|
|
|
|
if (species is >= (int)Species.Sprigatito and <= (int)Species.Quaquaval) // G9 Starters
|
|
|
|
return VerifyBallEquals(data, (int)Poke);
|
|
|
|
|
2022-11-25 01:42:17 +00:00
|
|
|
var pk = data.Entity;
|
|
|
|
if (IsBallPermitted(BallUseLegality.WildPokeballs9, pk.Ball))
|
|
|
|
return GetValid(LBallSpeciesPass);
|
|
|
|
return NONE;
|
|
|
|
}
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private static bool IsHiddenAndNotPossible(PKM pk)
|
|
|
|
{
|
|
|
|
if (pk.AbilityNumber != 4)
|
2020-10-26 01:07:13 +00:00
|
|
|
return false;
|
2022-09-02 17:20:19 +00:00
|
|
|
var abilities = (IPersonalAbility12H)pk.PersonalInfo;
|
|
|
|
return !AbilityVerifier.CanAbilityPatch(pk.Format, abilities, pk.Species);
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2019-11-17 01:45:51 +00:00
|
|
|
|
2022-08-27 06:43:36 +00:00
|
|
|
private static bool IsGalarCatchAndBreed(ushort species)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
|
|
|
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;
|
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
|
|
|
var pi = pt.GetFormEntry(species, 0);
|
2022-06-18 18:04:24 +00:00
|
|
|
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;
|
2018-06-24 05:00:01 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
|
|
|
private CheckResult VerifyBallEquals(LegalityAnalysis data, int ball) => GetResult(ball == data.Entity.Ball);
|
2022-08-26 08:20:49 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private CheckResult GetResult(bool valid) => valid ? GetValid(LBallEnc) : GetInvalid(LBallEncMismatch);
|
2018-06-24 05:00:01 +00:00
|
|
|
}
|