diff --git a/PKHeX.Core/Legality/LegalityCheckStrings.cs b/PKHeX.Core/Legality/LegalityCheckStrings.cs index 8e798da76..260ec0919 100644 --- a/PKHeX.Core/Legality/LegalityCheckStrings.cs +++ b/PKHeX.Core/Legality/LegalityCheckStrings.cs @@ -64,6 +64,7 @@ namespace PKHeX.Core public static string L_XWurmpleEvo_0 { get; set; } = "Wurmple Evolution: {0}"; public static string LAbilityCapsuleUsed { get; set; } = "Ability modified with Ability Capsule."; + public static string LAbilityPatchUsed { get; set; } = "Ability modified with Ability Patch."; public static string LAbilityFlag { get; set; } = "Ability matches ability number."; public static string LAbilityHiddenFail { get; set; } = "Hidden Ability mismatch for encounter type."; public static string LAbilityHiddenUnavailable { get; set; } = "Hidden Ability not available."; diff --git a/PKHeX.Core/Legality/RNG/RaidRNG.cs b/PKHeX.Core/Legality/RNG/RaidRNG.cs index 48470242d..e16765362 100644 --- a/PKHeX.Core/Legality/RNG/RaidRNG.cs +++ b/PKHeX.Core/Legality/RNG/RaidRNG.cs @@ -111,8 +111,15 @@ namespace PKHeX.Core abil = ability_param; abil <<= 1; // 1/2/4 - if ((pk.AbilityNumber == 4) != (abil == 4)) + var current = pk.AbilityNumber; + if (current == 4 && abil != 4) + { + // Defer for Ability Checks + } + else if (abil == 4) + { return false; + } switch (gender_ratio) { diff --git a/PKHeX.Core/Legality/Verifiers/AbilityVerifier.cs b/PKHeX.Core/Legality/Verifiers/AbilityVerifier.cs index f5e261d1e..dafb375bc 100644 --- a/PKHeX.Core/Legality/Verifiers/AbilityVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/AbilityVerifier.cs @@ -36,24 +36,34 @@ namespace PKHeX.Core if (abilval < 0) return GetInvalid(LAbilityUnexpected); - var enc = data.EncounterMatch; var abilities = pi.Abilities; + int format = pkm.Format; + if (format >= 6) + { + // Check AbilityNumber is a single set bit + var num = pkm.AbilityNumber; + if (!(num != 0 && (num & (num - 1)) == 0)) // not [!zero, and power of 2] + return GetInvalid(LAbilityMismatchFlag); + + // Check AbilityNumber points to ability + int an = num >> 1; + if (an >= abilities.Count || abilities[an] != ability) + return GetInvalid(LAbilityMismatchFlag); + } + + if (format >= 8) // Ability Patch + { + if (pkm.AbilityNumber == 4 && CanAbilityPatch(format, abilities)) + return GetValid(LAbilityPatchUsed); + } + + var enc = data.EncounterMatch; if (enc is MysteryGift g && g.Format >= 4) return VerifyAbilityMG(data, g, abilities); - if (pkm.Format < 6) + if (format < 6) return VerifyAbility345(data, enc, abilities, abilval); - // Check AbilityNumber is a single set bit - var num = pkm.AbilityNumber; - if (num == 0 || num == 0b101 || num == 0b110 || num == 0b011) - return GetInvalid(LAbilityMismatchFlag); - - // Check AbilityNumber points to ability - int an = num >> 1; - if (an >= abilities.Count || abilities[an] != ability) - return GetInvalid(LAbilityMismatchFlag); - return VerifyAbility(data, abilities, abilval); } @@ -225,9 +235,10 @@ namespace PKHeX.Core private CheckResult VerifyAbilityPCD(LegalityAnalysis data, IReadOnlyList abilities, PCD pcd) { var pkm = data.pkm; - if (pkm.Format >= 6) + var format = pkm.Format; + if (format >= 6) { - if (abilities[0] == abilities[1]) + if (CanAbilityCapsule(format, abilities)) { // Gen3-5 transfer with same ability -> 1st ability that matches if (pkm.AbilityNumber == 1) @@ -334,10 +345,8 @@ namespace PKHeX.Core // Ability Capsule can change between 1/2 private static bool IsAbilityCapsuleModified(PKM pkm, IReadOnlyList abilities, int EncounterAbility) { - if (pkm.Format < 6) - return false; // Ability Capsule does not exist - if (abilities[0] == abilities[1]) - return false; // Cannot alter ability index if it is the same as the other ability. + if (!CanAbilityCapsule(pkm.Format, abilities)) + return false; if (pkm.AbilityNumber == 4) return false; // Cannot alter to hidden ability. if (EncounterAbility == 4) @@ -345,6 +354,21 @@ namespace PKHeX.Core return true; } + private static bool CanAbilityCapsule(int format, IReadOnlyList abilities) + { + if (format < 6) // Ability Capsule does not exist + return false; + return abilities[0] != abilities[1]; // Cannot alter ability index if it is the same as the other ability. + } + + public static bool CanAbilityPatch(int format, IReadOnlyList abilities) + { + if (format < 8) // Ability Patch does not exist + return false; + var h = abilities[2]; + return h != abilities[0] || h != abilities[1]; // Cannot alter ability index if it is the same as the other abilities. + } + private static int GetEncounterFixedAbilityNumber(IEncounterable enc) { return enc switch diff --git a/PKHeX.Core/Legality/Verifiers/BallVerifier.cs b/PKHeX.Core/Legality/Verifiers/BallVerifier.cs index 592325f13..2c9f352e0 100644 --- a/PKHeX.Core/Legality/Verifiers/BallVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/BallVerifier.cs @@ -122,7 +122,7 @@ namespace PKHeX.Core { if (!Legal.Inherit_Safari.Contains(species)) return GetInvalid(LBallSpecies); - if (pkm.AbilityNumber == 4) + if (IsHiddenAndNotPossible(pkm)) return GetInvalid(LBallAbility); return GetValid(LBallSpeciesPass); } @@ -130,7 +130,7 @@ namespace PKHeX.Core { if (!Legal.Inherit_Apricorn6.Contains(species)) return GetInvalid(LBallSpecies); - if (pkm.AbilityNumber == 4) + if (IsHiddenAndNotPossible(pkm)) return GetInvalid(LBallAbility); return GetValid(LBallSpeciesPass); } @@ -138,13 +138,13 @@ namespace PKHeX.Core { if (!Legal.Inherit_Sport.Contains(species)) return GetInvalid(LBallSpecies); - if (pkm.AbilityNumber == 4) + if (IsHiddenAndNotPossible(pkm)) return GetInvalid(LBallAbility); return GetValid(LBallSpeciesPass); } if (ball == Dream) // Dream Ball { - if (pkm.AbilityNumber == 4 && Legal.Ban_DreamHidden.Contains(species)) + if (Legal.Ban_DreamHidden.Contains(species) && IsHiddenAndNotPossible(pkm)) return GetInvalid(LBallAbility); if (Legal.Inherit_Dream.Contains(species)) return GetValid(LBallSpeciesPass); @@ -160,7 +160,7 @@ namespace PKHeX.Core { if (Legal.Ban_Gen3Ball.Contains(species)) return GetInvalid(LBallSpecies); - if (pkm.AbilityNumber == 4 && Legal.Ban_Gen3BallHidden.Contains(pkm.SpecForm)) + if (Legal.Ban_Gen3BallHidden.Contains(pkm.SpecForm) && IsHiddenAndNotPossible(pkm)) return GetInvalid(LBallAbility); return GetValid(LBallSpeciesPass); } @@ -191,7 +191,7 @@ namespace PKHeX.Core { if (!(Legal.Inherit_Safari.Contains(species) || Legal.Inherit_SafariMale.Contains(species))) return GetInvalid(LBallSpecies); - if (pkm.AbilityNumber == 4 && Legal.Ban_SafariBallHidden_7.Contains(species)) + if (Legal.Ban_SafariBallHidden_7.Contains(species) && IsHiddenAndNotPossible(pkm)) return GetInvalid(LBallAbility); return GetValid(LBallSpeciesPass); } @@ -199,7 +199,7 @@ namespace PKHeX.Core { if (!Legal.Inherit_Apricorn7.Contains(species)) return GetInvalid(LBallSpecies); - if (pkm.AbilityNumber == 4 && Legal.Ban_NoHidden7Apricorn.Contains(species | pkm.AltForm << 11)) // lineage is 3->2->origin + if (Legal.Ban_NoHidden7Apricorn.Contains(species | pkm.AltForm << 11) && IsHiddenAndNotPossible(pkm)) return GetInvalid(LBallAbility); return GetValid(LBallSpeciesPass); } @@ -207,7 +207,7 @@ namespace PKHeX.Core { if (!Legal.Inherit_Sport.Contains(species)) return GetInvalid(LBallSpecies); - if (pkm.AbilityNumber == 4 && (species == (int)Species.Volbeat || species == (int)Species.Illumise)) // Volbeat/Illumise + if ((species == (int)Species.Volbeat || species == (int)Species.Illumise) && IsHiddenAndNotPossible(pkm)) // Volbeat/Illumise return GetInvalid(LBallAbility); return GetValid(LBallSpeciesPass); } @@ -232,9 +232,9 @@ namespace PKHeX.Core if (ball == Beast) { - if (species == (int)Species.Flabébé && pkm.AltForm == 3 && pkm.AbilityNumber == 4) + if (species == (int)Species.Flabébé && pkm.AltForm == 3 && IsHiddenAndNotPossible(pkm)) return GetInvalid(LBallAbility); // Can't obtain Flabébé-Blue with Hidden Ability in wild - if (species == (int)Species.Voltorb && pkm.AbilityNumber == 4) + if (species == (int)Species.Voltorb && IsHiddenAndNotPossible(pkm)) return GetInvalid(LBallAbility); // Can't obtain with Hidden Ability in wild (can only breed with Ditto) if (((int)Species.Pikipek <= species && species <= (int)Species.Kommoo) || (Legal.AlolanCaptureOffspring.Contains(species) && !Legal.PastGenAlolanNativesUncapturable.Contains(species))) return GetValid(LBallSpeciesPass); @@ -275,7 +275,7 @@ namespace PKHeX.Core { if (!(Legal.Inherit_Safari.Contains(species) || Legal.Inherit_SafariMale.Contains(species))) return GetInvalid(LBallSpecies); - if (pkm.AbilityNumber == 4 && Legal.Ban_SafariBallHidden_7.Contains(species)) + if (Legal.Ban_SafariBallHidden_7.Contains(species) && IsHiddenAndNotPossible(pkm)) return GetInvalid(LBallAbility); return GetValid(LBallSpeciesPass); } @@ -283,7 +283,7 @@ namespace PKHeX.Core { if (!Legal.Inherit_Apricorn7.Contains(species)) return GetInvalid(LBallSpecies); - if (pkm.AbilityNumber == 4 && Legal.Ban_NoHidden8Apricorn.Contains(species | pkm.AltForm << 11)) // lineage is 3->2->origin + if (Legal.Ban_NoHidden8Apricorn.Contains(species | pkm.AltForm << 11) && IsHiddenAndNotPossible(pkm)) // lineage is 3->2->origin return GetInvalid(LBallAbility); return GetValid(LBallSpeciesPass); } @@ -291,7 +291,7 @@ namespace PKHeX.Core { if (!Legal.Inherit_Sport.Contains(species)) return GetInvalid(LBallSpecies); - if (pkm.AbilityNumber == 4 && (species == (int)Species.Volbeat || species == (int)Species.Illumise)) // Volbeat/Illumise + if ((species == (int)Species.Volbeat || species == (int)Species.Illumise) && IsHiddenAndNotPossible(pkm)) // Volbeat/Illumise return GetInvalid(LBallAbility); return GetValid(LBallSpeciesPass); } @@ -316,7 +316,7 @@ namespace PKHeX.Core if (ball == Beast) { - if (species == (int)Species.Flabébé && pkm.AltForm == 3 && pkm.AbilityNumber == 4) + if (species == (int)Species.Flabébé && pkm.AltForm == 3 && IsHiddenAndNotPossible(pkm)) return GetInvalid(LBallAbility); // Can't obtain Flabébé-Blue with Hidden Ability in wild if (((int)Species.Pikipek <= species && species <= (int)Species.Kommoo) || (Legal.AlolanCaptureOffspring.Contains(species) && !Legal.PastGenAlolanNativesUncapturable.Contains(species))) return GetValid(LBallSpeciesPass); @@ -337,6 +337,13 @@ namespace PKHeX.Core return NONE; } + private static bool IsHiddenAndNotPossible(PKM pkm) + { + if (pkm.AbilityNumber != 4) + return false; + return !AbilityVerifier.CanAbilityPatch(pkm.Format, pkm.PersonalInfo.Abilities); + } + public static bool IsGalarCatchAndBreed(int species) { if ((int)Species.Grookey <= species && species <= (int)Species.Inteleon) // starter diff --git a/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs b/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs index 5619668dd..941583d8c 100644 --- a/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs @@ -80,7 +80,7 @@ namespace PKHeX.Core var pkm = data.pkm; if (pkm.GO) VerifyIVsGoTransfer(data); - else if (pkm.AbilityNumber == 4) + else if (pkm.AbilityNumber == 4 && !AbilityVerifier.CanAbilityPatch(pkm.Format, pkm.PersonalInfo.Abilities)) VerifyIVsFlawless(data, 2); // Chain of 10 yields 5% HA and 2 flawless IVs }