Extract bonus move interface from slot6ao/8b-under

This commit is contained in:
Kurt 2025-11-15 22:15:25 -06:00
parent 4d0bfa46df
commit d5bf6e67d5
7 changed files with 77 additions and 67 deletions

View File

@ -88,7 +88,7 @@ public static void GetSuggestedRelearnMoves(this LegalityAnalysis legal, Span<us
if (enc is MysteryGift or IEncounterEgg)
return;
if (enc is EncounterSlot6AO {CanDexNav: true} dn)
if (enc is ISingleMoveBonus {IsMoveBonusPossible: true} bonus)
{
var chk = legal.Info.Moves;
for (int i = 0; i < chk.Length; i++)
@ -97,34 +97,17 @@ public static void GetSuggestedRelearnMoves(this LegalityAnalysis legal, Span<us
continue;
var move = legal.Entity.GetMove(i);
if (!dn.CanBeDexNavMove(move))
continue;
moves.Clear();
moves[0] = move;
return;
}
}
if (enc is EncounterSlot8b { IsUnderground: true } ug)
{
var chk = legal.Info.Moves;
for (int i = 0; i < chk.Length; i++)
{
if (!chk[i].ShouldBeInRelearnMoves())
continue;
var move = legal.Entity.GetMove(i);
if (!ug.CanBeUndergroundMove(move))
if (!bonus.IsMoveBonus(move))
continue;
moves.Clear();
moves[0] = move;
return;
}
if (ug.GetBaseEggMove(out var any))
if (bonus.IsMoveBonusRequired && bonus.TryGetRandomMoveBonus(out var bonusMove))
{
moves.Clear();
moves[0] = any;
moves[0] = bonusMove;
return;
}
}

View File

@ -432,10 +432,8 @@ private static bool HasAllNeededMovesSlot(ReadOnlySpan<ushort> 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;

View File

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// Encounter Slot found in <see cref="GameVersion.ORAS"/>.
/// </summary>
public sealed record EncounterSlot6AO(EncounterArea6AO Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax)
: IEncounterable, IEncounterMatch, IEncounterConvertible<PK6>, IEncounterFormRandom, IEncounterDownlevel
: IEncounterable, IEncounterMatch, IEncounterConvertible<PK6>, 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;
/// <summary>
/// DexNav encounters can provide a move bonus.
/// </summary>
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<ushort> GetDexNavMoves()
public ReadOnlySpan<ushort> 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

View File

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// Encounter Slot found in <see cref="EntityContext.Gen8b"/>.
/// </summary>
public sealed record EncounterSlot8b(EncounterArea8b Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax)
: IEncounterable, IEncounterMatch, IEncounterConvertible<PB8>
: IEncounterable, IEncounterMatch, IEncounterConvertible<PB8>, 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);
/// <summary>
/// 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.
/// </summary>
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<ushort> 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
}

View File

@ -0,0 +1,16 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Interface for encounter objects that conditionally provide a special extra move.
/// </summary>
public interface ISingleMoveBonus
{
bool IsMoveBonusPossible { get; }
ReadOnlySpan<ushort> GetMoveBonusPossible();
bool IsMoveBonus(ushort move);
bool IsMoveBonusRequired { get; }
bool TryGetRandomMoveBonus(out ushort move);
}

View File

@ -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<MoveResult> result, ReadOnlySpan<ushor
if (result[i].Valid)
continue;
var move = current[i];
if (!dexnav.CanBeDexNavMove(move))
if (!dexnav.IsMoveBonus(move))
continue;
result[i] = new(new(LearnMethod.Encounter, LearnEnvironment.ORAS), Generation: Generation);
break;

View File

@ -16,10 +16,8 @@ public static void Verify(Span<MoveResult> 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<MoveResult> result, EncounterSlot6AO slot)
private static void VerifyRelearnBonusMove(PKM pk, Span<MoveResult> 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<MoveResult> 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<MoveResult> result)