mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-05-15 08:31:21 -05:00
186 lines
6.7 KiB
C#
186 lines
6.7 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
using static PKHeX.Core.LearnMethodStadium2;
|
|
|
|
namespace PKHeX.Core;
|
|
|
|
/// <summary>
|
|
/// Stadium 2's Move Reminder
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Entries are sorted by ascending level; so the first entry for a move is the minimum level to learn it.
|
|
/// Moves may appear multiple times in the learnset, but only the first needs to be satisfied to be "valid" for Stadium 2's checks.
|
|
/// https://bluemoonfalls.com/pages/general/move-reminder
|
|
/// </remarks>
|
|
public sealed class LearnsetStadium
|
|
{
|
|
private readonly StadiumTuple[] Learn;
|
|
public LearnsetStadium(ReadOnlySpan<byte> input)
|
|
=> Learn = MemoryMarshal.Cast<byte, StadiumTuple>(input).ToArray();
|
|
|
|
/// <summary> Gets all entries. </summary>
|
|
public ReadOnlySpan<StadiumTuple> GetMoves() => Learn;
|
|
|
|
/// <summary>
|
|
/// Gets the move info for a specific move ID.
|
|
/// </summary>
|
|
/// <param name="move">Move ID</param>
|
|
/// <param name="result">Move info for the specified move ID.</param>
|
|
/// <returns>True if the move exists in the learnset. Will return the minimum level move entry if multiple entries exist.</returns>
|
|
public bool TryGetMove(ushort move, out StadiumTuple result)
|
|
{
|
|
foreach (var learn in Learn)
|
|
{
|
|
if (move != learn.Move)
|
|
continue;
|
|
result = learn;
|
|
return true;
|
|
}
|
|
result = default;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the move can be learned at the specified level.
|
|
/// </summary>
|
|
/// <param name="move">Move ID</param>
|
|
/// <param name="level">Current level of the Pokémon.</param>
|
|
/// <returns>True if the move can be known at the requested level.</returns>
|
|
public bool CanKnow(ushort move, byte level) => TryGetMove(move, out var result) && result.Level <= level;
|
|
|
|
/// <summary>
|
|
/// Checks if the move can be relearned by Stadium 2 to the current moves.
|
|
/// </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, out byte criteriaLevel)
|
|
{
|
|
criteriaLevel = 0;
|
|
foreach (var learn in Learn)
|
|
{
|
|
if (level < learn.Level)
|
|
return false; // moves are sorted by level; eager return.
|
|
if (move != learn.Move)
|
|
continue;
|
|
if (!learn.Source.IsAbleToBeRelearned())
|
|
continue;
|
|
criteriaLevel = learn.Level; // remember the minimum level to relearn
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks the moveset.
|
|
/// </summary>
|
|
/// <remarks>Smeargle should be checked separately.</remarks>
|
|
/// <param name="moves">Currently known moves.</param>
|
|
/// <param name="level">Current level of the Pokémon.</param>
|
|
/// <param name="flag">Invalid moves will be marked as true in this span.</param>
|
|
/// <returns>True if all moves are valid.</returns>
|
|
public bool Validate(ReadOnlySpan<ushort> moves, byte level, Span<bool> flag)
|
|
{
|
|
bool anyInvalid = false;
|
|
|
|
// Stadium2 is not smart enough to disallow egg moves+event, or multiple event moves (Pikachu).
|
|
// Naive checker only checks if all individual moves are in its list with sufficient level.
|
|
for (int i = 0; i < moves.Length; i++)
|
|
{
|
|
var move = moves[i];
|
|
if (move == 0)
|
|
continue; // skip empty moves
|
|
if (CanKnow(move, level))
|
|
continue;
|
|
if (i >= flag.Length)
|
|
break; // avoid out of bounds, shouldn't happen but just in case
|
|
anyInvalid = flag[i] = true;
|
|
}
|
|
return !anyInvalid;
|
|
}
|
|
|
|
public static bool ValidateSmeargle(ReadOnlySpan<ushort> moves, Span<bool> flag)
|
|
{
|
|
bool anyInvalid = false;
|
|
for (int i = 0; i < moves.Length; i++)
|
|
anyInvalid |= flag[i] = !IsSketchValid(moves[i]);
|
|
return !anyInvalid;
|
|
}
|
|
|
|
/// <summary>
|
|
/// All others are covered by Mainline possibility. Baton Pass is the only error.
|
|
/// </summary>
|
|
public static bool IsSketchValid(ushort move) => move is not (ushort)Move.BatonPass;
|
|
|
|
/// <summary>
|
|
/// Converts a <see cref="BinLinkerAccessor"/> into an array of <see cref="LearnsetStadium"/>.
|
|
/// </summary>
|
|
public static LearnsetStadium[] GetArray(BinLinkerAccessor16 entries)
|
|
{
|
|
var result = new LearnsetStadium[entries.Length];
|
|
for (int i = 0; i < entries.Length; i++)
|
|
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>
|
|
/// Value tuple for Stadium 2's move reminder.
|
|
/// </summary>
|
|
/// <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, LearnMethodStadium2 Source)
|
|
{
|
|
public override string ToString() => $"Lv{Level} {(Move)Move} // {Source}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flags for the source of moves learned in Stadium 2.
|
|
/// </summary>
|
|
[Flags]
|
|
public enum LearnMethodStadium2 : byte
|
|
{
|
|
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,
|
|
Event = 1 << 7,
|
|
}
|
|
|
|
public static class LearnSourceStadiumExtensions
|
|
{
|
|
/// <summary>
|
|
/// Checks if the source can be relearned.
|
|
/// </summary>
|
|
public static bool IsAbleToBeRelearned(this LearnMethodStadium2 source) => (source & (LevelUpRB | LevelUpYW | LevelUpGS | LevelUpC)) != 0;
|
|
}
|