Extract stadium reminder moveset verifier step

Now flags as Stadium2 as source
This commit is contained in:
Kurt 2025-03-27 14:27:17 -05:00
parent 49061f0047
commit 542d6541c3
11 changed files with 139 additions and 30 deletions

View File

@ -173,9 +173,6 @@ private static void Check(Span<MoveResult> result, ReadOnlySpan<ushort> 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<bool> result, PKM pk, EvolutionHistory history, IEn
private static void GetAllMoves(Span<bool> 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);
}

View File

@ -107,9 +107,7 @@ private static void Check(Span<MoveResult> result, ReadOnlySpan<ushort> 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<MoveResult> result, ReadOnlySpan<ushort> 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<bool> result, PKM pk, EvolutionHistory history, IEn
private static void GetAllMoves(Span<bool> 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<bool> result)

View File

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

View File

@ -82,8 +82,7 @@ public void GetAllMoves(Span<bool> 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;
}

View File

@ -81,9 +81,8 @@ public void GetAllMoves(Span<bool> 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;
}

View File

@ -97,9 +97,8 @@ public void GetAllMoves(Span<bool> 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)

View File

@ -115,8 +115,7 @@ public void GetAllMoves(Span<bool> 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)

View File

@ -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<PersonalInfo2>, 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<ushort> 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<bool> 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;
}
}
}

View File

@ -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)
/// </summary>
/// <param name="move">Move ID to try and remember.</param>
/// <param name="level">Current level of the Pokémon.</param>
/// <param name="criteriaLevel">Level at which the move can be relearned, if applicable.</param>
/// <returns>True if the move can be relearned.</returns>
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;
}
/// <summary>
/// Gets the range of moves that can be learned between the specified levels.
/// </summary>
/// <param name="levelMax">Maximum level to learn moves.</param>
/// <param name="levelMin">>Minimum level to learn moves.</param>
public ReadOnlySpan<StadiumTuple> 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);
}
}
/// <summary>
@ -115,7 +139,7 @@ public static LearnsetStadium[] GetArray(BinLinkerAccessor entries)
/// <param name="Level">Minimum level to learn the move.</param>
/// <param name="Move">>Move ID.</param>
/// <param name="Source">>Source of the move (e.g., level up, tutor, egg).</param>
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.
/// </summary>
[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
/// <summary>
/// Checks if the source can be relearned.
/// </summary>
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;
}

View File

@ -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."),
};

View File

@ -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<ushort> moves = stackalloc ushort[4];
sk2.GetMoves(moves);
Span<bool> 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;