Gen5: fix basculin-blue reckless ability handling

Closes #2058

so many spots needing updating:
- ability verifier (legality check)
- encounter template=>pk5 (generating)
- editor load abilities (show the ability in list)
- editor save ability (acknowledge ability index)
This commit is contained in:
Kurt
2026-06-10 23:40:30 -05:00
parent 740a3c5b90
commit c514865082
10 changed files with 73 additions and 9 deletions

View File

@@ -135,10 +135,18 @@ private static void LoadAbilityList(IPersonalAbility pi, Span<ComboItem> 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<string> 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);
}
}

View File

@@ -83,6 +83,6 @@ public static EncounterMutation GetSuggested(EntityContext targetContext, byte l
_ => false,
},
OnlyHidden => end.CanBeHidden() || mutation.HasFlag(CanAbilityPatch),
_ => false,
_ => true,
};
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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();

View File

@@ -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
{

View File

@@ -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)

View File

@@ -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;