diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot5.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot5.cs index 31c488b3a..b30853084 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot5.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot5.cs @@ -12,6 +12,8 @@ public EncounterSlot5(EncounterArea5 area, int species, int form, int min, int m { } + public bool IsHiddenGrotto() => Area.Type == SlotType.HiddenGrotto; + protected override HiddenAbilityPermission IsHiddenAbilitySlot() => Area.Type == SlotType.HiddenGrotto ? HiddenAbilityPermission.Always : HiddenAbilityPermission.Never; } } diff --git a/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs index 288233c77..9d6e1d71d 100644 --- a/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs @@ -32,8 +32,8 @@ private CheckResult VerifyAbility(LegalityAnalysis data) // Check ability is possible (within bounds) int ability = pkm.Ability; - int abilval = pi.GetAbilityIndex(ability); - if (abilval < 0) + int abilIndex = pi.GetAbilityIndex(ability); + if (abilIndex < 0) return GetInvalid(LAbilityUnexpected); var abilities = pi.Abilities; @@ -43,12 +43,12 @@ private CheckResult VerifyAbility(LegalityAnalysis data) // 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); + return INVALID; // Check AbilityNumber points to ability int an = num >> 1; if (an >= abilities.Count || abilities[an] != ability) - return GetInvalid(LAbilityMismatchFlag); + return INVALID; // Check AbilityNumber for transfers without unique abilities int gen = data.Info.Generation; @@ -60,9 +60,9 @@ private CheckResult VerifyAbility(LegalityAnalysis data) { // Check if any pre-evolution could have it flipped. var evos = data.Info.EvoChainsAllGens[6]; - var pt = GameData.GetPersonal(GameUtil.GetVersion(pkm.Format)); + var pt = GameData.GetPersonal(GameUtil.GetVersion(format)); if (!GetWasDual(evos, pt, pkm)) - return GetInvalid(LAbilityMismatchFlag); + return INVALID; } } } @@ -85,9 +85,9 @@ private CheckResult VerifyAbility(LegalityAnalysis data) return VerifyAbilityMG(data, g, abilities); if (format < 6) - return VerifyAbility345(data, enc, abilities, abilval); + return VerifyAbility345(data, enc, abilities, abilIndex); - return VerifyAbility(data, abilities, abilval); + return VerifyAbility(data, abilities, abilIndex); } private static bool GetWasDual(IReadOnlyList evos, PersonalTable pt, ISpeciesForm pk) @@ -106,7 +106,7 @@ private static bool GetWasDual(IReadOnlyList evos, PersonalTable pt return false; } - private CheckResult VerifyAbility(LegalityAnalysis data, IReadOnlyList abilities, int abilnum) + private CheckResult VerifyAbility(LegalityAnalysis data, IReadOnlyList abilities, int abilIndex) { var enc = data.EncounterMatch; var eabil = GetEncounterFixedAbilityNumber(enc); @@ -115,10 +115,10 @@ private CheckResult VerifyAbility(LegalityAnalysis data, IReadOnlyList abil if ((data.pkm.AbilityNumber == 4) != (eabil == 4)) return GetInvalid(LAbilityHiddenFail); if (eabil > 0) - return VerifyFixedAbility(data, abilities, AbilityState.CanMismatch, eabil, abilnum); + return VerifyFixedAbility(data, abilities, AbilityState.CanMismatch, eabil, abilIndex); } - var gen = data.Info.Generation; + var gen = enc.Generation; return gen switch { 5 => VerifyAbility5(data, enc, abilities), @@ -129,7 +129,7 @@ private CheckResult VerifyAbility(LegalityAnalysis data, IReadOnlyList abil }; } - private CheckResult VerifyAbility345(LegalityAnalysis data, IEncounterTemplate enc, IReadOnlyList abilities, int abilnum) + private CheckResult VerifyAbility345(LegalityAnalysis data, IEncounterTemplate enc, IReadOnlyList abilities, int abilIndex) { var pkm = data.pkm; int format = pkm.Format; @@ -137,44 +137,46 @@ private CheckResult VerifyAbility345(LegalityAnalysis data, IEncounterTemplate e if (format is (3 or 4 or 5) && abilities[0] != abilities[1]) // 3-4/5 and have 2 distinct abilities now state = VerifyAbilityPreCapsule(data, abilities); - int eabil = GetEncounterFixedAbilityNumber(enc); - if (eabil >= 0) + int encounterAbility = GetEncounterFixedAbilityNumber(enc); + if (encounterAbility >= 0) { - if ((pkm.AbilityNumber == 4) != (eabil == 4)) + if ((pkm.AbilityNumber == 4) != (encounterAbility == 4)) return GetInvalid(LAbilityHiddenFail); - if (eabil > 0) - return VerifyFixedAbility(data, abilities, state, eabil, abilnum); + if (encounterAbility > 0) + return VerifyFixedAbility(data, abilities, state, encounterAbility, abilIndex); } - int gen = data.Info.Generation; + int gen = enc.Generation; if (gen == 5) return VerifyAbility5(data, enc, abilities); return CheckMatch(pkm, abilities, gen, state); } - private CheckResult VerifyFixedAbility(LegalityAnalysis data, IReadOnlyList abilities, AbilityState state, int EncounterAbility, int abilval) + private CheckResult VerifyFixedAbility(LegalityAnalysis data, IReadOnlyList abilities, AbilityState state, int encounterAbility, int abilIndex) { var pkm = data.pkm; - if (data.Info.EncounterMatch.Generation >= 6) + var enc = data.Info.EncounterMatch; + if (enc.Generation >= 6) { - if (IsAbilityCapsuleModified(pkm, abilities, EncounterAbility)) + if (IsAbilityCapsuleModified(pkm, abilities, encounterAbility)) return GetValid(LAbilityCapsuleUsed); - if (pkm.AbilityNumber != EncounterAbility) + if (pkm.AbilityNumber != encounterAbility) return INVALID; return VALID; } - if ((pkm.AbilityNumber == 4) != (EncounterAbility == 4)) + if ((pkm.AbilityNumber == 4) != (encounterAbility == 4)) return GetInvalid(LAbilityHiddenFail); - if (data.EncounterMatch.Species != pkm.Species && state != AbilityState.CanMismatch) // evolved + bool hasEvolved = enc.Species != pkm.Species; + if (hasEvolved && state != AbilityState.CanMismatch) { // Evolving in Gen3 does not mutate the ability bit, so any mismatched abilities will stay mismatched. - if (pkm.Gen3) + if (enc.Generation == 3) { - if (EncounterAbility == 1 << abilval) - return GetValid(LAbilityFlag); + if (encounterAbility == 1 << abilIndex) + return VALID; // If it is in a future game and does not match the fixed ability, then it must match the PID. if (pkm.Format != 3) @@ -184,19 +186,19 @@ private CheckResult VerifyFixedAbility(LegalityAnalysis data, IReadOnlyList return INVALID; } - return CheckMatch(pkm, abilities, data.Info.Generation, AbilityState.MustMatch); + return CheckMatch(pkm, abilities, enc.Generation, AbilityState.MustMatch); } - if (EncounterAbility == 1 << abilval) - return GetValid(LAbilityFlag); - - if (pkm.AbilityNumber == EncounterAbility) + if (encounterAbility == 1 << abilIndex) return VALID; - if (state == AbilityState.CanMismatch || EncounterAbility == 0) - return CheckMatch(pkm, abilities, data.Info.Generation, AbilityState.MustMatch); + if (pkm.AbilityNumber == encounterAbility) + return VALID; - if (IsAbilityCapsuleModified(pkm, abilities, EncounterAbility)) + if (state == AbilityState.CanMismatch || encounterAbility == 0) + return CheckMatch(pkm, abilities, enc.Generation, AbilityState.MustMatch); + + if (IsAbilityCapsuleModified(pkm, abilities, encounterAbility)) return GetValid(LAbilityCapsuleUsed); return INVALID; @@ -204,25 +206,34 @@ private CheckResult VerifyFixedAbility(LegalityAnalysis data, IReadOnlyList private AbilityState VerifyAbilityPreCapsule(LegalityAnalysis data, IReadOnlyList abilities) { - var pkm = data.pkm; - // CXD pokemon can have any ability without matching PID - if (pkm.Version == (int)GameVersion.CXD && pkm.Format == 3) - return AbilityState.CanMismatch; - - // Gen3 native or Gen4/5 origin - if (pkm.Format == 3 || data.Info.Generation != 3) + var info = data.Info; + // Gen4/5 origin + if (info.Generation != 3) return AbilityState.MustMatch; + // Gen3 origin... a lot of edge cases to check. + var pkm = data.pkm; + var format = pkm.Format; + // CXD pokemon can have any ability without matching PID + if (format == 3) + { + if (pkm.Version == (int)GameVersion.CXD) + return AbilityState.CanMismatch; + return AbilityState.MustMatch; + } + // Evovled in Gen4/5 if (pkm.Species > Legal.MaxSpeciesID_3) return AbilityState.MustMatch; // If the species could not exist in Gen3, must match. - if (data.Info.EvoChainsAllGens[3].Count == 0) + var g3 = info.EvoChainsAllGens[3]; + if (g3.Count == 0) return AbilityState.MustMatch; // Fall through when gen3 pkm transferred to gen4/5 - return VerifyAbilityGen3Transfer(data, abilities, data.Info.EvoChainsAllGens[3][0].Species); + var maxGen3Species = g3[0].Species; + return VerifyAbilityGen3Transfer(data, abilities, maxGen3Species); } private AbilityState VerifyAbilityGen3Transfer(LegalityAnalysis data, IReadOnlyList abilities, int maxGen3Species) @@ -292,7 +303,7 @@ private CheckResult VerifyAbilityMG(LegalityAnalysis data, MysteryGift g, IReadO return GetValid(LAbilityCapsuleUsed); } - return GetInvalid(pkm.Format < 6 ? LAbilityMismatchPID : LAbilityMismatchFlag); + return pkm.Format < 6 ? GetInvalid(LAbilityMismatchPID) : INVALID; } private CheckResult VerifyAbilityPCD(LegalityAnalysis data, IReadOnlyList abilities, PCD pcd) @@ -305,7 +316,7 @@ private CheckResult VerifyAbilityPCD(LegalityAnalysis data, IReadOnlyList a { // Gen3-5 transfer with same ability -> 1st ability that matches if (pkm.AbilityNumber == 1) - return GetValid(LAbilityFlag); + return VALID; return CheckMatch(pkm, abilities, 4, AbilityState.MustMatch); // evolved, must match } if (pkm.AbilityNumber < 4) // Ability Capsule can change between 1/2 @@ -322,23 +333,14 @@ private CheckResult VerifyAbilityPCD(LegalityAnalysis data, IReadOnlyList a private CheckResult VerifyAbility5(LegalityAnalysis data, IEncounterTemplate enc, IReadOnlyList abilities) { var pkm = data.pkm; - switch (enc) - { - case EncounterSlot w: - // Hidden Abilities for Wild Encounters are only available at a Hidden Grotto - bool grotto = w.Area.Type == SlotType.HiddenGrotto; - if (pkm.AbilityNumber == 4 ^ grotto) - return GetInvalid(grotto ? LAbilityMismatchGrotto : LAbilityHiddenFail); - break; - case EncounterEgg e when pkm.AbilityNumber == 4: - // Hidden Abilities for some are unbreedable or unreleased - if (AbilityBreedLegality.BanHidden5.Contains(e.Species)) - return GetInvalid(LAbilityHiddenUnavailable); - break; - } - var state = pkm.Format == 5 ? AbilityState.MustMatch : AbilityState.CanMismatch; - return CheckMatch(data.pkm, abilities, 5, state); + // Eggs and Encounter Slots are not yet checked for Hidden Ability potential. + return enc switch + { + EncounterSlot5 w when pkm.AbilityNumber == 4 != w.IsHiddenGrotto() => GetInvalid(w.IsHiddenGrotto() ? LAbilityMismatchGrotto : LAbilityHiddenFail), + EncounterEgg e when pkm.AbilityNumber == 4 && AbilityBreedLegality.BanHidden5.Contains(e.Species) => GetInvalid(LAbilityHiddenUnavailable), + _ => CheckMatch(data.pkm, abilities, 5, pkm.Format == 5 ? AbilityState.MustMatch : AbilityState.CanMismatch) + }; } private CheckResult VerifyAbility6(LegalityAnalysis data, IEncounterTemplate enc) @@ -347,32 +349,31 @@ private CheckResult VerifyAbility6(LegalityAnalysis data, IEncounterTemplate enc if (pkm.AbilityNumber != 4) return VALID; - // hidden abilities - if (enc is EncounterSlot slot) + // Eggs and Encounter Slots are not yet checked for Hidden Ability potential. + return enc switch { - bool valid = slot is EncounterSlot6AO {CanDexNav: true} || slot.Area.Type is SlotType.FriendSafari or SlotType.Horde; - if (!valid) - return GetInvalid(LAbilityMismatchHordeSafari); - } - if (AbilityBreedLegality.BanHidden6.Contains(enc.Species | (enc.Form << 11))) - return GetInvalid(LAbilityHiddenUnavailable); + EncounterSlot6XY slot when slot.Area.Type is SlotType.FriendSafari or SlotType.Horde => VALID, + EncounterSlot6AO slot when slot.Area.Type is SlotType.Horde => VALID, + EncounterSlot6AO {CanDexNav: true} => VALID, + EncounterSlot => GetInvalid(LAbilityMismatchHordeSafari), - return VALID; + EncounterEgg egg when AbilityBreedLegality.BanHidden6.Contains(egg.Species | (egg.Form << 11)) => GetInvalid(LAbilityHiddenUnavailable), + _ => VALID + }; } private CheckResult VerifyAbility7(LegalityAnalysis data, IEncounterTemplate enc) { var pkm = data.pkm; - if (enc is EncounterSlot slot && pkm.AbilityNumber == 4) - { - bool valid = slot.Area.Type == SlotType.SOS; - if (!valid) - return GetInvalid(LAbilityMismatchSOS); - } - if (AbilityBreedLegality.BanHidden7.Contains(enc.Species | (enc.Form << 11)) && pkm.AbilityNumber == 4) - return GetInvalid(LAbilityHiddenUnavailable); + if (pkm.AbilityNumber != 4) + return VALID; - return VALID; + return enc switch + { + EncounterSlot7 slot when slot.Area.Type != SlotType.SOS => GetInvalid(LAbilityMismatchSOS), + EncounterEgg egg when AbilityBreedLegality.BanHidden7.Contains(egg.Species | (egg.Form << 11)) => GetInvalid(LAbilityHiddenUnavailable), + _ => VALID + }; } /// @@ -423,6 +424,7 @@ private CheckResult CheckMatch(PKM pkm, IReadOnlyList abilities, int gen, A private CheckResult GetPIDAbilityMatch(PKM pkm, IReadOnlyList abilities) { + // Ability Number bits are already verified as clean. var abil = abilities[pkm.AbilityNumber >> 1]; if (abil != pkm.Ability) return GetInvalid(LAbilityMismatchPID); @@ -431,13 +433,13 @@ private CheckResult GetPIDAbilityMatch(PKM pkm, IReadOnlyList abilities) } // Ability Capsule can change between 1/2 - private static bool IsAbilityCapsuleModified(PKM pkm, IReadOnlyList abilities, int EncounterAbility) + private static bool IsAbilityCapsuleModified(PKM pkm, IReadOnlyList abilities, int encounterAbility) { if (!CanAbilityCapsule(pkm.Format, abilities)) return false; if (pkm.AbilityNumber == 4) return false; // Cannot alter to hidden ability. - if (EncounterAbility == 4) + if (encounterAbility == 4) return false; // Cannot alter from hidden ability. return true; }