diff --git a/PKHeX.Core/Legality/LearnSource/Group/LearnGroup1.cs b/PKHeX.Core/Legality/LearnSource/Group/LearnGroup1.cs index 4640a5b0c..a53a8b3c0 100644 --- a/PKHeX.Core/Legality/LearnSource/Group/LearnGroup1.cs +++ b/PKHeX.Core/Legality/LearnSource/Group/LearnGroup1.cs @@ -173,9 +173,6 @@ private static void Check(Span result, ReadOnlySpan current, if (!yw.TryGetPersonal(evo.Species, evo.Form, out var yp)) return; // should never happen. - if (ParseSettings.AllowGen1Tradeback && ParseSettings.AllowGen2MoveReminder(pk)) - evo = evo with { LevelMin = 1 }; - for (int i = result.Length - 1; i >= 0; i--) { ref var entry = ref result[i]; @@ -229,9 +226,6 @@ public void GetAllMoves(Span result, PKM pk, EvolutionHistory history, IEn private static void GetAllMoves(Span result, PKM pk, EvoCriteria evo, MoveSourceType types) { - if (ParseSettings.AllowGen1Tradeback && ParseSettings.AllowGen2MoveReminder(pk)) - evo = evo with { LevelMin = 1 }; - LearnSource1YW.Instance.GetAllMoves(result, pk, evo, types); LearnSource1RB.Instance.GetAllMoves(result, pk, evo, types); } diff --git a/PKHeX.Core/Legality/LearnSource/Group/LearnGroup2.cs b/PKHeX.Core/Legality/LearnSource/Group/LearnGroup2.cs index dc63e2c7e..55095c603 100644 --- a/PKHeX.Core/Legality/LearnSource/Group/LearnGroup2.cs +++ b/PKHeX.Core/Legality/LearnSource/Group/LearnGroup2.cs @@ -107,9 +107,7 @@ private static void Check(Span result, ReadOnlySpan current, if (!c.TryGetPersonal(evo.Species, evo.Form, out var cp)) return; // should never happen. - if (ParseSettings.AllowGen2MoveReminder(pk)) - evo = evo with { LevelMin = 1 }; - + bool stad2 = ParseSettings.AllowGen2MoveReminder(pk); bool kor = pk.Korean; // Crystal is not available to Korean games. for (int i = result.Length - 1; i >= 0; i--) @@ -132,6 +130,13 @@ private static void Check(Span result, ReadOnlySpan current, chk = c.GetCanLearn(pk, cp, evo, move, types); if (chk != default && GetIsPreferable(entry, chk, stage)) entry = new(chk, (byte)stage, Generation); + + if (stad2) + { + chk = LearnSource2Stadium.Instance.GetCanRelearn(evo, move, types); + if (chk != default && GetIsPreferable(entry, chk, stage)) + entry = new(chk, (byte)stage, Generation); + } } } @@ -169,13 +174,12 @@ public void GetAllMoves(Span result, PKM pk, EvolutionHistory history, IEn private static void GetAllMoves(Span result, PKM pk, EvoCriteria evo, MoveSourceType types) { - if (ParseSettings.AllowGen2MoveReminder(pk)) - evo = evo with { LevelMin = 1 }; - LearnSource2GS.Instance.GetAllMoves(result, pk, evo, types); if (pk.Korean) return; LearnSource2C.Instance.GetAllMoves(result, pk, evo, types); + if (ParseSettings.AllowGen2MoveReminder(pk)) + LearnSource2Stadium.Instance.GetAllMoves(result, pk, evo, types); } private static void FlagEncounterMoves(PKM pk, IEncounterTemplate enc, Span result) diff --git a/PKHeX.Core/Legality/LearnSource/LearnEnvironment.cs b/PKHeX.Core/Legality/LearnSource/LearnEnvironment.cs index c3230a8d0..b4406627a 100644 --- a/PKHeX.Core/Legality/LearnSource/LearnEnvironment.cs +++ b/PKHeX.Core/Legality/LearnSource/LearnEnvironment.cs @@ -11,7 +11,7 @@ public enum LearnEnvironment : byte None, /* Gen1 */ RB, YW, - /* Gen2 */ GS, C, + /* Gen2 */ GS, C, Stadium2, /* Gen3 */ RS, E, FR, LG, /* Gen4 */ DP, Pt, HGSS, /* Gen5 */ BW, B2W2, diff --git a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1RB.cs b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1RB.cs index 78b43b7be..94217e384 100644 --- a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1RB.cs +++ b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1RB.cs @@ -82,8 +82,7 @@ public void GetAllMoves(Span result, PKM pk, EvoCriteria evo, MoveSourceTy if (types.HasFlag(MoveSourceType.LevelUp)) { var learn = Learnsets[evo.Species]; - var min = ParseSettings.AllowGen1Tradeback && ParseSettings.AllowGen2MoveReminder(pk) ? 1 : evo.LevelMin; - var span = learn.GetMoveRange(evo.LevelMax, min); + var span = learn.GetMoveRange(evo.LevelMax, evo.LevelMin); foreach (var move in span) result[move] = true; } diff --git a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1YW.cs b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1YW.cs index da0b4daaf..4a28f00ee 100644 --- a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1YW.cs +++ b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource1YW.cs @@ -81,9 +81,8 @@ public void GetAllMoves(Span result, PKM pk, EvoCriteria evo, MoveSourceTy if (types.HasFlag(MoveSourceType.LevelUp)) { - var learn = GetLearnset(evo.Species, evo.Form); - var min = ParseSettings.AllowGen1Tradeback && ParseSettings.AllowGen2MoveReminder(pk) ? 1 : evo.LevelMin; - var span = learn.GetMoveRange(evo.LevelMax, min); + var learn = Learnsets[evo.Species]; + var span = learn.GetMoveRange(evo.LevelMax, evo.LevelMin); foreach (var move in span) result[move] = true; } diff --git a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2C.cs b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2C.cs index ee422198f..2c44e6af8 100644 --- a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2C.cs +++ b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2C.cs @@ -97,9 +97,8 @@ public void GetAllMoves(Span result, PKM pk, EvoCriteria evo, MoveSourceTy bool removeVC = pk.Format == 1 || pk.VC1; if (types.HasFlag(MoveSourceType.LevelUp)) { - var learn = GetLearnset(evo.Species, evo.Form); - var min = ParseSettings.AllowGen2MoveReminder(pk) ? 1 : evo.LevelMin; - var span = learn.GetMoveRange(evo.LevelMax, min); + var learn = Learnsets[evo.Species]; + var span = learn.GetMoveRange(evo.LevelMax, evo.LevelMin); foreach (var move in span) { if (!removeVC || move <= Legal.MaxMoveID_1) diff --git a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2GS.cs b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2GS.cs index ade04be3d..a8e4bc0d9 100644 --- a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2GS.cs +++ b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2GS.cs @@ -115,8 +115,7 @@ public void GetAllMoves(Span result, PKM pk, EvoCriteria evo, MoveSourceTy if (types.HasFlag(MoveSourceType.LevelUp)) { var learn = Learnsets[evo.Species]; - var min = ParseSettings.AllowGen2MoveReminder(pk) ? 1 : evo.LevelMin; - var span = learn.GetMoveRange(evo.LevelMax, min); + var span = learn.GetMoveRange(evo.LevelMax, evo.LevelMin); foreach (var move in span) { if (!removeVC || move <= Legal.MaxMoveID_1) diff --git a/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2Stadium.cs b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2Stadium.cs new file mode 100644 index 000000000..c5c6bb2d1 --- /dev/null +++ b/PKHeX.Core/Legality/LearnSource/Sources/LearnSource2Stadium.cs @@ -0,0 +1,64 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using static PKHeX.Core.LearnMethod; +using static PKHeX.Core.LearnEnvironment; + +namespace PKHeX.Core; + +public sealed class LearnSource2Stadium : ILearnSource, IEggSource +{ + public static readonly LearnSource2Stadium Instance = new(); + private static readonly LearnsetStadium[] Learnsets = LearnsetStadium.GetArray(BinLinkerAccessor.Get(Util.GetBinaryResource("reminder_stad2.pkl"), "s2"u8)); + + private const int MaxSpecies = Legal.MaxSpeciesID_2; + private const LearnEnvironment Game = Stadium2; + public LearnEnvironment Environment => Game; + + public Learnset GetLearnset(ushort species, byte form) => throw new InvalidOperationException("Not supported."); + public bool TryGetPersonal(ushort species, byte form, [NotNullWhen(true)] out PersonalInfo2? pi) => throw new InvalidOperationException("Not supported."); + public bool GetIsEggMove(ushort species, byte form, ushort move) => false; + public ReadOnlySpan GetEggMoves(ushort species, byte form) => []; + private static bool IsSpeciesInGame(ushort species, byte form) => form is 0 && species is <= MaxSpecies and not 0; + public LearnsetStadium GetLearnsetStadium(ushort species, byte form) => Learnsets[species < Learnsets.Length ? species : 0]; + + public MoveLearnInfo GetCanLearn(PKM pk, PersonalInfo2 pi, EvoCriteria evo, ushort move, MoveSourceType types = MoveSourceType.All, LearnOption option = LearnOption.Current) + { + return GetCanRelearn(evo, move, types); + } + + public MoveLearnInfo GetCanRelearn(EvoCriteria evo, ushort move, MoveSourceType types) + { + if (move > Legal.MaxMoveID_2) // byte + return default; + + if (types.HasFlag(MoveSourceType.LevelUp)) + { + var learn = GetLearnsetStadium(evo.Species, evo.Form); + if (learn.CanRelearn(move, evo.LevelMax, out var level)) + return new(LevelUp, Game, level); + } + + return default; + } + + public void GetAllMoves(Span result, PKM pk, EvoCriteria evo, MoveSourceType types = MoveSourceType.All) + { + if (!IsSpeciesInGame(evo.Species, evo.Form)) + return; + + if (!types.HasFlag(MoveSourceType.LevelUp)) + return; + + bool removeVC = pk.Format == 1 || pk.VC1; + var learn = GetLearnsetStadium(evo.Species, evo.Form); + var span = learn.GetMoveRange(evo.LevelMax); + foreach (var tuple in span) + { + if (!tuple.Source.IsAbleToBeRelearned()) + continue; + var move = tuple.Move; + if (!removeVC || move <= Legal.MaxMoveID_1) + result[move] = true; + } + } +} diff --git a/PKHeX.Core/Legality/Learnset/LearnsetStadium.cs b/PKHeX.Core/Legality/Learnset/LearnsetStadium.cs index c36c8c9a8..724a984d5 100644 --- a/PKHeX.Core/Legality/Learnset/LearnsetStadium.cs +++ b/PKHeX.Core/Legality/Learnset/LearnsetStadium.cs @@ -1,6 +1,6 @@ using System; using System.Runtime.InteropServices; -using static PKHeX.Core.LearnSourceStadium; +using static PKHeX.Core.LearnMethodStadium2; namespace PKHeX.Core; @@ -53,9 +53,11 @@ public bool TryGetMove(ushort move, out StadiumTuple result) /// /// Move ID to try and remember. /// Current level of the Pokémon. + /// Level at which the move can be relearned, if applicable. /// True if the move can be relearned. - public bool CanRelearn(ushort move, byte level) + public bool CanRelearn(ushort move, byte level, out byte criteriaLevel) { + criteriaLevel = 0; foreach (var learn in Learn) { if (level < learn.Level) @@ -64,6 +66,7 @@ public bool CanRelearn(ushort move, byte level) continue; if (!learn.Source.IsAbleToBeRelearned()) continue; + criteriaLevel = learn.Level; // remember the minimum level to relearn return true; } return false; @@ -107,6 +110,27 @@ public static LearnsetStadium[] GetArray(BinLinkerAccessor entries) result[i] = new LearnsetStadium(entries[i]); return result; } + + /// + /// Gets the range of moves that can be learned between the specified levels. + /// + /// Maximum level to learn moves. + /// >Minimum level to learn moves. + public ReadOnlySpan GetMoveRange(byte levelMax, byte levelMin = 1) + { + // find start + int start = 0; + while (start < Learn.Length && Learn[start].Level < levelMin) + start++; + // find end + int end = start; + while (end < Learn.Length && Learn[end].Level <= levelMax) + end++; + // return the range + if (start >= end) + return []; // no moves in range + return Learn.AsSpan(start, end - start); + } } /// @@ -115,7 +139,7 @@ public static LearnsetStadium[] GetArray(BinLinkerAccessor entries) /// Minimum level to learn the move. /// >Move ID. /// >Source of the move (e.g., level up, tutor, egg). -public readonly record struct StadiumTuple(byte Level, byte Move, LearnSourceStadium Source) +public readonly record struct StadiumTuple(byte Level, byte Move, LearnMethodStadium2 Source) { public override string ToString() => $"Lv{Level} {(Move)Move} // {Source}"; } @@ -124,13 +148,15 @@ public static LearnsetStadium[] GetArray(BinLinkerAccessor entries) /// Flags for the source of moves learned in Stadium 2. /// [Flags] -public enum LearnSourceStadium : byte +public enum LearnMethodStadium2 : byte { - None, + None = 0, + // Able to be remembered LevelUpRB = 1 << 0, LevelUpYW = 1 << 1, LevelUpGS = 1 << 2, LevelUpC = 1 << 3, + // Permitted to know by legality checker, but not able to remember. TutorC = 1 << 4, EggC = 1 << 5, EggGS = 1 << 6, @@ -142,5 +168,5 @@ public static class LearnSourceStadiumExtensions /// /// Checks if the source can be relearned. /// - public static bool IsAbleToBeRelearned(this LearnSourceStadium source) => (source & (LevelUpRB | LevelUpYW | LevelUpGS | LevelUpC)) != 0; + public static bool IsAbleToBeRelearned(this LearnMethodStadium2 source) => (source & (LevelUpRB | LevelUpYW | LevelUpGS | LevelUpC)) != 0; } diff --git a/PKHeX.Core/Legality/Moves/GameData.cs b/PKHeX.Core/Legality/Moves/GameData.cs index ecf0553cf..6b3742b3d 100644 --- a/PKHeX.Core/Legality/Moves/GameData.cs +++ b/PKHeX.Core/Legality/Moves/GameData.cs @@ -64,7 +64,7 @@ public static class GameData Gen9 => LearnSource9SV.Instance, Stadium => LearnSource1YW.Instance, - Stadium2 => LearnSource2GS.Instance, + Stadium2 => LearnSource2Stadium.Instance, _ => throw new ArgumentOutOfRangeException(nameof(game), $"{game} is not a valid entry in the expression."), }; diff --git a/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs b/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs index 8a4aee639..7e4e906ba 100644 --- a/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs @@ -49,6 +49,9 @@ public override void Verify(LegalityAnalysis data) switch (pk) { + case SK2 sk2: + VerifyIsMovesetAllowed(data, sk2); + break; case PK5 pk5: VerifyGen5Stats(data, pk5); break; @@ -167,6 +170,28 @@ private void VerifyMiscScaleValues(LegalityAnalysis data, PKM pk, IEncounterTemp data.AddLine(GetInvalid(LStatIncorrectHeightValue)); } + private void VerifyIsMovesetAllowed(LegalityAnalysis data, SK2 sk2) + { + Span moves = stackalloc ushort[4]; + sk2.GetMoves(moves); + Span flags = stackalloc bool[4]; + + var learn = LearnSource2Stadium.Instance.GetLearnsetStadium(sk2.Species, sk2.Form); + if (learn.Validate(moves, sk2.CurrentLevel, flags)) + return; + + var parse = data.Info.Moves; + for (int i = 0; i < flags.Length; i++) + { + if (!flags[i]) + continue; + ref var m = ref parse[i]; + if (!m.Valid) + continue; + m = m with { Info = m.Info with { Method = LearnMethod.Unobtainable, Environment = LearnEnvironment.Stadium2 } }; + } + } + private static void VerifyGen5Stats(LegalityAnalysis data, PK5 pk5) { var enc = data.EncounterMatch;