diff --git a/PKHeX.Core/Editing/Applicators/MoveSetApplicator.cs b/PKHeX.Core/Editing/Applicators/MoveSetApplicator.cs index e77604d55..71606fae8 100644 --- a/PKHeX.Core/Editing/Applicators/MoveSetApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/MoveSetApplicator.cs @@ -88,7 +88,7 @@ public static void GetSuggestedRelearnMoves(this LegalityAnalysis legal, Span needs, IEncounter { if (slot is IMoveset m) return m.Moves.ContainsAll(needs); - if (needs.Length == 1 && slot is EncounterSlot6AO dn) - return dn.CanDexNav && dn.CanBeDexNavMove(needs[0]); - if (needs.Length == 1 && slot is EncounterSlot8b ug) - return ug.IsUnderground && ug.CanBeUndergroundMove(needs[0]); + if (needs.Length == 1 && slot is ISingleMoveBonus bonus) + return bonus is { IsMoveBonusPossible: true, IsMoveBonusRequired: true } && bonus.IsMoveBonus(needs[0]); if (slot.Generation <= 2) return HasAllNeededMovesEncounter2(needs, slot); return false; diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterSlot6AO.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterSlot6AO.cs index dfb474d0c..a0e5757cc 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterSlot6AO.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterSlot6AO.cs @@ -7,7 +7,7 @@ namespace PKHeX.Core; /// Encounter Slot found in . /// public sealed record EncounterSlot6AO(EncounterArea6AO Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax) - : IEncounterable, IEncounterMatch, IEncounterConvertible, IEncounterFormRandom, IEncounterDownlevel + : IEncounterable, IEncounterMatch, IEncounterConvertible, IEncounterFormRandom, IEncounterDownlevel, ISingleMoveBonus { public byte Generation => 6; public EntityContext Context => EntityContext.Gen6; @@ -24,18 +24,25 @@ public sealed record EncounterSlot6AO(EncounterArea6AO Parent, ushort Species, b public ushort Location => Parent.Location; public SlotType6 Type => Parent.Type; public bool CanDexNav => Type != Rock_Smash; + + /// + /// DexNav encounters can provide a move bonus. + /// + public bool IsMoveBonusPossible => CanDexNav; + public bool IsMoveBonusRequired => false; + public bool IsHorde => Type == Horde; - private HiddenAbilityPermission IsHiddenAbilitySlot() => CanDexNav || IsHorde ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; + private HiddenAbilityPermission IsHiddenAbilitySlot() => IsMoveBonusPossible || IsHorde ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; - private ReadOnlySpan GetDexNavMoves() + public ReadOnlySpan GetMoveBonusPossible() { var et = EvolutionTree.Evolves6; var baby = et.GetBaseSpeciesForm(Species, Form); return LearnSource6AO.Instance.GetEggMoves(baby.Species, baby.Form); } - public bool CanBeDexNavMove(ushort move) => GetDexNavMoves().Contains(move); + public bool IsMoveBonus(ushort move) => GetMoveBonusPossible().Contains(move); public AbilityPermission Ability => IsHiddenAbilitySlot() switch { @@ -85,9 +92,9 @@ public PK6 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) SetPINGA(pk, criteria, pi); EncounterUtil.SetEncounterMoves(pk, Version, LevelMin); - if (CanDexNav) + if (IsMoveBonusPossible) { - var eggMoves = GetDexNavMoves(); + var eggMoves = GetMoveBonusPossible(); if (eggMoves.Length != 0) pk.RelearnMove1 = eggMoves[Util.Rand.Next(eggMoves.Length)]; } @@ -114,6 +121,18 @@ private void SetPINGA(PK6 pk, in EncounterCriteria criteria, PersonalInfo6AO pi) pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability)); criteria.SetRandomIVs(pk); } + + public bool TryGetRandomMoveBonus(out ushort move) + { + var moves = GetMoveBonusPossible(); + if (moves.Length == 0) + { + move = 0; + return false; + } + move = moves[Util.Rand.Next(moves.Length)]; + return true; + } #endregion #region Matching diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterSlot8b.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterSlot8b.cs index b9fb9aef6..380fee297 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterSlot8b.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8b/EncounterSlot8b.cs @@ -7,7 +7,7 @@ namespace PKHeX.Core; /// Encounter Slot found in . /// public sealed record EncounterSlot8b(EncounterArea8b Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax) - : IEncounterable, IEncounterMatch, IEncounterConvertible + : IEncounterable, IEncounterMatch, IEncounterConvertible, ISingleMoveBonus { public byte Generation => 8; public EntityContext Context => EntityContext.Gen8b; @@ -16,6 +16,13 @@ public sealed record EncounterSlot8b(EncounterArea8b Parent, ushort Species, byt public bool IsShiny => false; public ushort EggLocation => 0; public bool IsUnderground => Locations8b.IsUnderground(Parent.Location); + + /// + /// Each Pokémon also has one of their Egg Moves when you catch them in the Underground, provided they have any Egg Moves to begin with. + /// + public bool IsMoveBonusPossible => IsUnderground; + public bool IsMoveBonusRequired => true; + public bool IsMarsh => Locations8b.IsMarsh(Parent.Location); public Ball FixedBall => GetRequiredBall(); private Ball GetRequiredBall(Ball fallback = Ball.None) => IsMarsh ? Ball.Safari : fallback; @@ -26,7 +33,7 @@ public sealed record EncounterSlot8b(EncounterArea8b Parent, ushort Species, byt public ushort Location => Parent.Location; public SlotType8b Type => Parent.Type; - public bool CanUseRadar => Type is Grass && !IsUnderground && !IsMarsh && CanUseRadarOverworld(Location); + public bool CanUseRadar => Type is Grass && !IsMoveBonusPossible && !IsMarsh && CanUseRadarOverworld(Location); private static bool CanUseRadarOverworld(ushort location) => location switch { @@ -87,7 +94,7 @@ public PB8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) }; SetPINGA(pk, criteria, pi); EncounterUtil.SetEncounterMoves(pk, Version, LevelMin); - if (IsUnderground && GetBaseEggMove(out var move1, pi)) + if (IsMoveBonusPossible && TryGetRandomMoveBonus(out var move1)) pk.RelearnMove1 = move1; pk.ResetPartyStats(); return pk; @@ -107,12 +114,17 @@ private void SetPINGA(PB8 pk, in EncounterCriteria criteria, PersonalInfo8BDSP p pk.WeightScalar = PokeSizeUtil.GetRandomScalar(rnd); } - public bool GetBaseEggMove(out ushort move) => GetBaseEggMove(out move, PersonalTable.BDSP[Species, Form]); - - private static bool GetBaseEggMove(out ushort move, PersonalInfo8BDSP pi) + public ReadOnlySpan GetMoveBonusPossible() { - var species = pi.HatchSpecies; - var baseEgg = LearnSource8BDSP.Instance.GetEggMoves(species, 0); + var et = PersonalTable.BDSP; + var sf = et.GetFormEntry(Species, Form); + var species = sf.HatchSpecies; + return LearnSource8BDSP.Instance.GetEggMoves(species, 0); + } + + public bool TryGetRandomMoveBonus(out ushort move) + { + var baseEgg = GetMoveBonusPossible(); if (baseEgg.Length == 0) { move = 0; @@ -176,15 +188,10 @@ public EncounterMatchRating GetMatchRating(PKM pk) _ => false, }; - public bool CanBeUndergroundMove(ushort move) + public bool IsMoveBonus(ushort move) { - var et = PersonalTable.BDSP; - var sf = et.GetFormEntry(Species, Form); - var species = sf.HatchSpecies; - var baseEgg = LearnSource8BDSP.Instance.GetEggMoves(species, 0); - if (baseEgg.Length == 0) - return move == 0; - return baseEgg.Contains(move); + var moves = GetMoveBonusPossible(); + return (moves.Length == 0 && move == 0) || moves.Contains(move); } #endregion } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/ISingleMoveBonus.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/ISingleMoveBonus.cs new file mode 100644 index 000000000..b6b6db94b --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/Properties/ISingleMoveBonus.cs @@ -0,0 +1,16 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Interface for encounter objects that conditionally provide a special extra move. +/// +public interface ISingleMoveBonus +{ + bool IsMoveBonusPossible { get; } + ReadOnlySpan GetMoveBonusPossible(); + bool IsMoveBonus(ushort move); + + bool IsMoveBonusRequired { get; } + bool TryGetRandomMoveBonus(out ushort move); +} diff --git a/PKHeX.Core/Legality/LearnSource/Group/LearnGroup6.cs b/PKHeX.Core/Legality/LearnSource/Group/LearnGroup6.cs index a822aa300..953c31209 100644 --- a/PKHeX.Core/Legality/LearnSource/Group/LearnGroup6.cs +++ b/PKHeX.Core/Legality/LearnSource/Group/LearnGroup6.cs @@ -26,7 +26,7 @@ public sealed class LearnGroup6 : ILearnGroup { if (enc is EncounterEgg6 egg) CheckEncounterMoves(result, current, egg); - else if (enc is EncounterSlot6AO { CanDexNav: true } dexnav && pk.IsOriginalMovesetDeleted()) + else if (enc is EncounterSlot6AO { IsMoveBonusPossible: true } dexnav && pk.IsOriginalMovesetDeleted()) CheckDexNavMoves(result, current, dexnav); } @@ -62,7 +62,7 @@ private static void CheckDexNavMoves(Span result, ReadOnlySpan result, IEncounterTemplate enc, PKM p VerifyRelearnSpecifiedMoveset(pk, x, result); else if (enc is IEncounterEgg e) VerifyEggMoveset(e, result, pk); - else if (enc is EncounterSlot6AO { CanDexNav: true } z && pk.RelearnMove1 != 0) - VerifyRelearnDexNav(pk, result, z); - else if (enc is EncounterSlot8b { IsUnderground: true } u) - VerifyRelearnUnderground(pk, result, u); + else if (enc is ISingleMoveBonus { IsMoveBonusPossible: true } z && (z.IsMoveBonusRequired || pk.RelearnMove1 != 0)) + VerifyRelearnBonusMove(pk, result, z); else VerifyRelearnNone(pk, result); } @@ -44,26 +42,15 @@ private static MoveResult ParseExpect(ushort move, ushort expect = 0) return MoveResult.Relearn; } - private static void VerifyRelearnDexNav(PKM pk, Span result, EncounterSlot6AO slot) + private static void VerifyRelearnBonusMove(PKM pk, Span result, ISingleMoveBonus slot) { // Only has one relearn move from the encounter. Every other relearn move must be empty. result[3] = ParseExpect(pk.RelearnMove4); result[2] = ParseExpect(pk.RelearnMove3); result[1] = ParseExpect(pk.RelearnMove2); - // DexNav Pokémon can have 1 random egg move as a relearn move. - result[0] = slot.CanBeDexNavMove(pk.RelearnMove1) ? MoveResult.Relearn : MoveResult.Unobtainable(); // DexNav - } - - private static void VerifyRelearnUnderground(PKM pk, Span result, EncounterSlot8b slot) - { - // Only has one relearn move from the encounter. Every other relearn move must be empty. - result[3] = ParseExpect(pk.RelearnMove4); - result[2] = ParseExpect(pk.RelearnMove3); - result[1] = ParseExpect(pk.RelearnMove2); - - // Underground Pokémon can have 1 random egg move as a relearn move. - result[0] = slot.CanBeUndergroundMove(pk.RelearnMove1) ? MoveResult.Relearn : MoveResult.Unobtainable(); // Underground + // 1 random egg move as a relearn move. + result[0] = slot.IsMoveBonus(pk.RelearnMove1) ? MoveResult.Relearn : MoveResult.Unobtainable(); } private static void VerifyRelearnNone(PKM pk, Span result)