diff --git a/PKHeX.Core/Game/GameStrings/FilteredGameDataSource.cs b/PKHeX.Core/Game/GameStrings/FilteredGameDataSource.cs index b38d3a57e..08ed5e645 100644 --- a/PKHeX.Core/Game/GameStrings/FilteredGameDataSource.cs +++ b/PKHeX.Core/Game/GameStrings/FilteredGameDataSource.cs @@ -135,10 +135,18 @@ private static void LoadAbilityList(IPersonalAbility pi, Span list, R for (int i = 0; i < list.Length; i++) { var value = pi.GetAbilityAtIndex(i); - var name = names[value]; char suffix = i == 2 ? HiddenAbilitySuffix : (char)(AbilityIndexSuffix + i); - var display = $"{name} ({suffix})"; - list[i] = new ComboItem(display, value); + var item = GetAbilityItem(names, value, suffix); + list[i] = item; } } + + public static ComboItem GetAbilityItem(ReadOnlySpan names, int value, char suffix) + => GetAbilityItem(names[value], suffix, value); + + public static ComboItem GetAbilityItem(string name, char suffix, int value) + { + var display = $"{name} ({suffix})"; + return new ComboItem(display, value); + } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/EncounterMutation.cs b/PKHeX.Core/Legality/Encounters/Generator/EncounterMutation.cs index 94b56802a..a19651bdc 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/EncounterMutation.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/EncounterMutation.cs @@ -83,6 +83,6 @@ public static EncounterMutation GetSuggested(EntityContext targetContext, byte l _ => false, }, OnlyHidden => end.CanBeHidden() || mutation.HasFlag(CanAbilityPatch), - _ => false, + _ => true, }; } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterEgg5.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterEgg5.cs index a0416eed1..704e6ea78 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterEgg5.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterEgg5.cs @@ -77,6 +77,8 @@ public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) pk.PID = pid; pk.Gender = gender; pk.RefreshAbility(ability); + if (ability == 0 && this is { Species: (int)Core.Species.Basculin, Form: 1 }) + pk.Ability = (int)Core.Ability.Reckless; return pk; } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterSlot5.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterSlot5.cs index 0ac4e9a42..a627a8f93 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterSlot5.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterSlot5.cs @@ -90,6 +90,8 @@ private void SetPINGA(PK5 pk, in EncounterCriteria criteria, PersonalInfo5B2W2 p var abilityIndex = Ability == AbilityPermission.OnlyHidden ? 2 : (int)((pk.PID >> 16) & 1); pk.RefreshAbility(abilityIndex); criteria.SetRandomIVs(pk); + if (abilityIndex == 0 && this is { Species: (int)Core.Species.Basculin, Form: 1, Version: GameVersion.B or GameVersion.W }) + pk.Ability = (int)Core.Ability.Reckless; } #endregion diff --git a/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs index ba4c3ec14..7bcfba611 100644 --- a/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs @@ -30,6 +30,8 @@ private CheckResult VerifyAbility(LegalityAnalysis data) var pk = data.Entity; if (pk is PA9 pa9) return VerifyBirthAbility(data, pa9); + if (pk is PK5 pk5 && IsEdgeCaseBasculin(data, pk5, out var basc)) + return basc; var abilities = (IPersonalAbility12)data.PersonalInfo; @@ -95,6 +97,42 @@ private CheckResult VerifyAbility(LegalityAnalysis data) return VerifyAbility(data, abilities, abilIndex); } + private bool IsEdgeCaseBasculin(LegalityAnalysis data, PK5 pk5, out CheckResult result) + { + result = default; + // Gen5 Only Edge Case issue: + // - Basculin-Blue (form 1) *should* have Rock Head. + // In B/W, Blue-Striped Basculin have Reckless as its Ability (incorrect) due to referencing the wrong Personal Info. + // - However, the in-game trade Blue-Striped Basculin in White that has Rock Head (correct). + // In B2/W2, wild Blue-Striped Basculin have Rock Head (correct), but bred Blue-Striped Basculin have Reckless (incorrect). + // - When transferred to Pokémon Bank, any Blue-Striped Basculin with Reckless have their Ability changed to Rock Head (fixed). + // Impacted encounter types: Egg|Wild (B/W), Egg (B2/W2) + + if (pk5 is not { Species: (int)Species.Basculin, Form: 1, HiddenAbility: false, Ability: not (int)Ability.Adaptability }) + return false; // check abilities as usual. + + var expect = (int)Ability.RockHead; // correct. + if (pk5.Version is GameVersion.B2 or GameVersion.W2) + { + var enc = data.EncounterMatch; + if (enc is EncounterEgg5) + expect = (int)Ability.Reckless; // incorrect + } + else if (pk5.Version is GameVersion.B or GameVersion.W) + { + var enc = data.EncounterMatch; + if (enc is not EncounterTrade5BW) + expect = (int)Ability.Reckless; // incorrect + } + + var current = pk5.Ability; + if (current == expect) + result = VALID; + else + result = GetInvalid(AbilityUnexpected); + return true; // tell the analyzer to use this result + } + public static bool IsValidAbilityBits(int bitNum) => bitNum is 1 or 2 or 4; private CheckResult VerifyAbility(LegalityAnalysis data, IPersonalAbility12 abilities, int abilIndex) diff --git a/PKHeX.Core/PKM/PK5.cs b/PKHeX.Core/PKM/PK5.cs index e55169d6f..de29cef29 100644 --- a/PKHeX.Core/PKM/PK5.cs +++ b/PKHeX.Core/PKM/PK5.cs @@ -508,6 +508,13 @@ public PK6 ConvertToPK6() StringConverter345.TransferGlyphs56(pk6.NicknameTrash); StringConverter345.TransferString56(OriginalTrainerTrash, pk6.OriginalTrainerTrash); + // Fix Basculin-Blue + if (Species is (ushort)Core.Species.Basculin && Form == 1 && Ability == (int)Core.Ability.Reckless) + { + pk6.Ability = (int)Core.Ability.RockHead; + pk6.AbilityNumber = 1; + } + // Fix Checksum pk6.RefreshChecksum(); diff --git a/PKHeX.WinForms/Controls/PKM Editor/EditPK5.cs b/PKHeX.WinForms/Controls/PKM Editor/EditPK5.cs index e8bab6c17..cca5cb534 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/EditPK5.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/EditPK5.cs @@ -22,7 +22,7 @@ private void PopulateFieldsPK5() if (HaX) DEV_Ability.SelectedValue = pk5.Ability; else if (pk5.HiddenAbility) - CB_Ability.SelectedIndex = CB_Ability.Items.Count - 1; + CB_Ability.SelectedIndex = 2; else LoadAbility4(pk5); @@ -45,7 +45,7 @@ private PK5 PreparePK5() pk5.PokeStarFame = (byte)NUD_PokeStarFame.Value; if (!HaX) { - pk5.HiddenAbility = CB_Ability.SelectedIndex is not (0 or 1); + pk5.HiddenAbility = CB_Ability.SelectedIndex is 2; } else { diff --git a/PKHeX.WinForms/Controls/PKM Editor/LoadSave.cs b/PKHeX.WinForms/Controls/PKM Editor/LoadSave.cs index ef4131111..183dfb02d 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/LoadSave.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/LoadSave.cs @@ -398,11 +398,16 @@ private void LoadAbility4(PKM pk) private static int GetAbilityIndex4(PKM pk) { var pi = pk.PersonalInfo; - int abilityIndex = pi.GetIndexOfAbility(pk.Ability); - if (abilityIndex < 0) - return 0; + var ability = pk.Ability; + int abilityIndex = pi.GetIndexOfAbility(ability); if (abilityIndex >= 2) return 2; + if (abilityIndex < 0) + { + if (ability == (int)Ability.Reckless && pk is { Context: EntityContext.Gen5, Species: (ushort)Species.Basculin, Form: 1 }) + return 3; // manually appended "extra" bug case for Gen5 Basculin-Blue. + return 0; // fall back to first ability. + } var abils = (IPersonalAbility12)pi; if (abils.IsAbility12Same) diff --git a/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs b/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs index c578eb8ac..a1c86a2bd 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs @@ -513,6 +513,8 @@ private void SetAbilityList() bool tmp = FieldsLoaded; FieldsLoaded = false; var items = GameInfo.FilteredSources.GetAbilityList(Entity.PersonalInfo); + if (Entity is { Context: EntityContext.Gen5, Species: (ushort)Species.Basculin, Form: 1 }) + items = [..items, FilteredGameDataSource.GetAbilityItem(GameInfo.Strings.abilitylist, (int)Ability.Reckless, '*')]; CB_Ability.DataSource = items; CB_Ability.SelectedIndex = Math.Clamp(ability, 0, items.Count - 1); // restore original index if available FieldsLoaded = tmp; diff --git a/Tests/PKHeX.Core.Tests/Legality/Legal/Generation 5/0550-01 - Basculin - C46358B4FA76 wrongAbility.pk5 b/Tests/PKHeX.Core.Tests/Legality/Legal/Generation 5/0550-01 - Basculin - C46358B4FA76 wrongAbility.pk5 new file mode 100644 index 000000000..019b75d91 Binary files /dev/null and b/Tests/PKHeX.Core.Tests/Legality/Legal/Generation 5/0550-01 - Basculin - C46358B4FA76 wrongAbility.pk5 differ