Add learnset moves similar to games

Instead of looping, if the moveset is full and a new move is added, the game shifts all arr[1..] down one slot then adds the move at the end.
Since we don't need to keep track of PP/PP Ups, we can just defer the shifting and do n % 4 rotations at the end instead of n rotations (one on each move added).
This commit is contained in:
Kurt 2023-04-08 12:20:18 -07:00
parent 7b0313aa6a
commit 6182f889a6
10 changed files with 110 additions and 127 deletions

View file

@ -111,14 +111,14 @@ public static class MoveShopRecordApplicator
if (shop.GetMasteredRecordFlag(index))
return;
if (level < (uint)learn.GetMoveLevel(move)) // Can't learn it yet; must purchase.
if (level < (uint)learn.GetLevelLearnMove(move)) // Can't learn it yet; must purchase.
{
shop.SetPurchasedRecordFlag(index, true);
shop.SetMasteredRecordFlag(index, true);
return;
}
if (level < (uint)mastery.GetMoveLevel(move)) // Can't master it yet; must Seed of Mastery
if (level < (uint)mastery.GetLevelLearnMove(move)) // Can't master it yet; must Seed of Mastery
shop.SetMasteredRecordFlag(index, true);
}
@ -141,7 +141,7 @@ public static class MoveShopRecordApplicator
// and it is high enough level to master it, the game will automatically
// give it the "Mastered" flag but not the "Purchased" flag
// For moves that are not in the learnset, it returns -1 which is true, thus set as mastered.
if (level >= mastery.GetMoveLevel(move))
if (level >= mastery.GetLevelLearnMove(move))
shop.SetMasteredRecordFlag(index, true);
}
}

View file

@ -87,8 +87,7 @@ public sealed class LearnGroup1 : ILearnGroup
private static void FlagFishyMoveSlots(Span<MoveResult> result, ReadOnlySpan<ushort> current, IEncounterTemplate enc)
{
var occupied = current.Length - current.Count((ushort)0);
if (occupied == 4)
if (!current.Contains<ushort>(0))
return;
Span<ushort> moves = stackalloc ushort[4];
@ -155,9 +154,9 @@ public sealed class LearnGroup1 : ILearnGroup
private static void GetEncounterMoves(IEncounterTemplate enc, Span<ushort> moves)
{
if (enc.Version is GameVersion.YW or GameVersion.RBY)
LearnSource1YW.Instance.GetEncounterMoves(enc, moves);
LearnSource1YW.Instance.GetEncounterMoves(enc, enc.LevelMin, moves);
else
LearnSource1RB.Instance.GetEncounterMoves(enc, moves);
LearnSource1RB.Instance.GetEncounterMoves(enc, enc.LevelMin, moves);
}
private static void Check(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvoCriteria evo, int stage, LearnOption option = LearnOption.Current, MoveSourceType types = MoveSourceType.All)

View file

@ -100,7 +100,7 @@ public sealed class LearnSource1RB : ILearnSource<PersonalInfo1>
}
}
public void GetEncounterMoves(IEncounterTemplate enc, Span<ushort> init)
public void GetEncounterMoves(ISpeciesForm enc, int level, Span<ushort> init)
{
var species = enc.Species;
if (!TryGetPersonal(species, 0, out var personal))
@ -108,7 +108,7 @@ public sealed class LearnSource1RB : ILearnSource<PersonalInfo1>
var learn = Learnsets[species];
personal.GetMoves(init);
var start = (4 - init.Count<ushort>(0)) & 3;
learn.SetEncounterMoves(enc.LevelMin, init, start);
var start = (init.LastIndexOfAnyExcept<ushort>(0) + 1) & 3;
learn.SetEncounterMoves(level, init, start);
}
}

View file

@ -100,7 +100,7 @@ public sealed class LearnSource1YW : ILearnSource<PersonalInfo1>
}
}
public void GetEncounterMoves(IEncounterTemplate enc, Span<ushort> init)
public void GetEncounterMoves(ISpeciesForm enc, int level, Span<ushort> init)
{
var species = enc.Species;
if (!TryGetPersonal(species, 0, out var personal))
@ -108,7 +108,7 @@ public sealed class LearnSource1YW : ILearnSource<PersonalInfo1>
var learn = Learnsets[species];
personal.GetMoves(init);
var start = (4 - init.Count((ushort)0)) & 3;
learn.SetEncounterMoves(enc.LevelMin, init, start);
var start = (init.LastIndexOfAnyExcept<ushort>(0) + 1) & 3;
learn.SetEncounterMoves(level, init, start);
}
}

View file

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
@ -18,6 +17,8 @@ public sealed class Learnset
/// </summary>
private readonly byte[] Levels;
private const byte MagicEvolutionMoveLevel = 0;
public Learnset(ushort[] moves, byte[] levels)
{
Moves = moves;
@ -30,26 +31,47 @@ public sealed class Learnset
return (true, 0, Moves.Length - 1);
if (minLevel > maxLevel)
return default;
int start = Array.FindIndex(Levels, z => z >= minLevel);
int start = FindGrq(minLevel);
if (start < 0)
return default;
int end = Array.FindLastIndex(Levels, z => z <= maxLevel);
int end = FindLastLeq(maxLevel);
if (end < 0)
return default;
return (true, start, end);
}
/// <summary>Returns the moves a Pokémon would have if it were encountered at the specified level.</summary>
/// <remarks>In Generation 1, it is not possible to learn any moves lower than these encounter moves.</remarks>
/// <param name="level">The level the Pokémon was encountered at.</param>
/// <returns>Array of Move IDs</returns>
public ushort[] GetEncounterMoves(int level)
private int FindGrq(int level, int start = 0)
{
const int count = 4;
var moves = new ushort[count];
SetEncounterMoves(level, moves);
return moves;
var levels = Levels;
for (int i = start; i < levels.Length; i++)
{
if (levels[i] >= level)
return i;
}
return -1;
}
private int FindGr(int level, int start)
{
var levels = Levels;
for (int i = start; i < levels.Length; i++)
{
if (levels[i] >= level)
return i;
}
return -1;
}
private int FindLastLeq(int level, int end = 0)
{
var levels = Levels;
for (int i = levels.Length - 1; i >= end; i--)
{
if (levels[i] <= level)
return i;
}
return -1;
}
/// <summary>Returns the moves a Pokémon would have if it were encountered at the specified level.</summary>
@ -65,18 +87,40 @@ public sealed class Learnset
if (Levels[i] > level)
break;
var move = Moves[i];
if (moves.Contains(move))
continue;
AddMoveShiftLater(moves, ref ctr, Moves[i]);
}
RectifyOrderShift(moves, ctr);
}
moves[ctr++] = move;
ctr &= 3;
private static void AddMoveShiftLater(Span<ushort> moves, ref int ctr, ushort move)
{
if (!moves.Contains(move))
moves[(ctr++) & 3] = move;
}
private static void RectifyOrderShift(Span<ushort> moves, int ctr)
{
// Perform (n & 3) rotations as if we were inserting moves, but a minimal amount of times.
// This skips the rotation for when moves are inserted and then overwritten by later inserted moves.
if (ctr <= moves.Length)
return;
var rotation = ctr & 3;
if (rotation == 0)
return;
// rotate n times in-place
for (int i = 0; i < rotation; i++)
{
var move = moves[0];
for (int j = 0; j < 3; j++)
moves[j] = moves[j + 1];
moves[3] = move;
}
}
public void SetEncounterMovesBackwards(int level, Span<ushort> moves, int ctr = 0)
{
int index = Array.FindLastIndex(Levels, z => z <= level);
int index = FindLastLeq(level);
while (true)
{
@ -106,18 +150,16 @@ public sealed class Learnset
/// <summary>Adds the learned moves by level up to the specified level.</summary>
public void SetLevelUpMoves(int startLevel, int endLevel, Span<ushort> moves, int ctr = 0)
{
int startIndex = Array.FindIndex(Levels, z => z >= startLevel);
int endIndex = Array.FindIndex(Levels, z => z > endLevel);
for (int i = startIndex; i < endIndex; i++)
{
var move = Moves[i];
bool alreadyHasMove = moves.IndexOf(move) >= 0;
if (alreadyHasMove)
continue;
int startIndex = FindGrq(startLevel);
if (startIndex == -1)
return;
int endIndex = FindGr(endLevel, startIndex);
if (endIndex == -1)
endIndex = Levels.Length;
moves[ctr++] = move;
ctr &= 3;
}
for (int i = startIndex; i < endIndex; i++)
AddMoveShiftLater(moves, ref ctr, Moves[i]);
RectifyOrderShift(moves, ctr);
}
/// <summary>Adds the moves that are gained upon evolving.</summary>
@ -125,28 +167,24 @@ public sealed class Learnset
/// <param name="ctr">Starting index to begin overwriting at</param>
public void SetEvolutionMoves(Span<ushort> moves, int ctr = 0)
{
// Evolution moves are always at the lowest indexes of the learnset.
for (int i = 0; i < Moves.Length; i++)
{
if (Levels[i] != 0)
if (Levels[i] != MagicEvolutionMoveLevel)
break;
var move = Moves[i];
bool alreadyHasMove = moves.IndexOf(move) >= 0;
if (alreadyHasMove)
continue;
moves[ctr++] = move;
ctr &= 3;
AddMoveShiftLater(moves, ref ctr, Moves[i]);
}
RectifyOrderShift(moves, ctr);
}
/// <summary>Adds the learned moves by level up to the specified level.</summary>
public void SetLevelUpMoves(int startLevel, int endLevel, Span<ushort> moves, ReadOnlySpan<ushort> ignore, int ctr = 0)
{
int startIndex = Array.FindIndex(Levels, z => z >= startLevel);
int startIndex = FindGrq(startLevel);
if (startIndex == -1)
return; // No more remain
int endIndex = Array.FindIndex(Levels, z => z > endLevel);
int endIndex = FindGr(endLevel, startIndex);
if (endIndex == -1)
endIndex = Levels.Length;
for (int i = startIndex; i < endIndex; i++)
@ -155,13 +193,9 @@ public sealed class Learnset
if (ignore.IndexOf(move) >= 0)
continue;
bool alreadyHasMove = moves.IndexOf(move) >= 0;
if (alreadyHasMove)
continue;
moves[ctr++] = move;
ctr &= 3;
AddMoveShiftLater(moves, ref ctr, move);
}
RectifyOrderShift(moves, ctr);
}
/// <summary>Adds the moves that are gained upon evolving.</summary>
@ -172,53 +206,16 @@ public sealed class Learnset
{
for (int i = 0; i < Moves.Length; i++)
{
if (Levels[i] != 0)
if (Levels[i] != MagicEvolutionMoveLevel)
break;
var move = Moves[i];
if (ignore.IndexOf(move) >= 0)
continue;
bool alreadyHasMove = moves.IndexOf(move) >= 0;
if (alreadyHasMove)
continue;
moves[ctr++] = move;
ctr &= 3;
AddMoveShiftLater(moves, ref ctr, move);
}
}
/// <summary>Returns the index of the lowest level move if the Pokémon were encountered at the specified level.</summary>
/// <remarks>Helps determine the minimum level an encounter can be at.</remarks>
/// <param name="level">The level the Pokémon was encountered at.</param>
/// <returns>Array of Move IDs</returns>
public int GetMinMoveLevel(int level)
{
if (Levels.Length == 0)
return 1;
int end = Array.FindLastIndex(Levels, z => z <= level);
return Math.Max(end - 4, 1);
}
public int GetMoveLevel(ushort move)
{
var index = Array.LastIndexOf(Moves, move);
if (index == -1)
return -1;
return Levels[index];
}
private Dictionary<ushort, byte>? Learn;
private Dictionary<ushort, byte> GetDictionary()
{
// Create a dictionary, with the move as the key and the level as the value.
// Due to the ordering of the object, this will result in fetching the lowest level for a move.
var dict = new Dictionary<ushort, byte>(Moves.Length);
for (int i = Moves.Length - 1; i >= 0; i--)
dict[Moves[i]] = Levels[i];
return dict;
RectifyOrderShift(moves, ctr);
}
/// <summary>Returns the level that a Pokémon can learn the specified move.</summary>
@ -226,32 +223,17 @@ public sealed class Learnset
/// <returns>Level the move is learned at. If the result is below 0, the move cannot be learned by leveling up.</returns>
public int GetLevelLearnMove(ushort move)
{
return (Learn ??= GetDictionary()).TryGetValue(move, out var level) ? level : -1;
}
/// <summary>Returns the level that a Pokémon can learn the specified move.</summary>
/// <param name="move">Move ID</param>
/// <param name="min">Minimum level to start looking at.</param>
/// <returns>Level the move is learned at. If the result is below 0, the move cannot be learned by leveling up.</returns>
public int GetLevelLearnMove(ushort move, int min)
{
for (int i = 0; i < Moves.Length; i++)
{
if (move != Moves[i])
continue;
var lv = Levels[i];
if (lv >= min)
return lv;
}
return -1;
var index = Array.IndexOf(Moves, move);
if (index == -1)
return -1;
return Levels[index];
}
public ReadOnlySpan<ushort> GetBaseEggMoves(int level)
{
// Count moves <= level
var count = 0;
foreach (var x in Levels)
foreach (ref var x in Levels.AsSpan())
{
if (x > level)
break;

View file

@ -48,7 +48,7 @@ public static class MystryMew
return seed;
uint position = (random % (MewPerRestrictedSeed - 1)) + 1;
for (int i = 0; i < position; i++)
for (uint i = 0; i < position; i++)
seed = LCRNG.Next5(seed);
return seed;
@ -61,11 +61,13 @@ public static class MystryMew
public static int GetSeedIndex(uint seed)
{
var seeds = Seeds;
if (seed <= ushort.MaxValue)
return seeds.BinarySearch((ushort)seed);
for (int i = 0; i < 5; i++)
{
seed = LCRNG.Prev5(seed);
if (seed <= ushort.MaxValue)
return seeds.BinarySearch((ushort)seed);
seed = LCRNG.Prev5(seed);
}
return -1;

View file

@ -57,7 +57,7 @@ public sealed class EffortValueVerifier : Verifier
data.AddLine(Get(LEffortEXPIncreased, Severity.Fishy));
else if (sum == 508)
data.AddLine(Get(LEffort2Remaining, Severity.Fishy));
else if (evs[0] != 0 && evs.Count(evs[0]) == evs.Length)
else if (evs[0] != 0 && evs.IndexOfAnyExcept(evs[0]) == -1)
data.AddLine(Get(LEffortAllEqual, Severity.Fishy));
}
}

View file

@ -155,9 +155,9 @@ public sealed class LegendsArceusVerifier : Verifier
// Check if we can swap it into the moveset after it evolves.
var move = purchased[i];
var baseLevel = baseLearn.GetMoveLevel(move);
var baseLevel = baseLearn.GetLevelLearnMove(move);
var mustKnow = baseLevel is not -1 && baseLevel <= pa.Met_Level;
if (!mustKnow && currentLearn.GetMoveLevel(move) != level)
if (!mustKnow && currentLearn.GetLevelLearnMove(move) != level)
continue;
if (current.IndexOf(move) == -1)

View file

@ -76,7 +76,7 @@ public static class MoveShop8MasteryExtensions
var move = permit[i];
// Can only purchase a move if it is not already in the available learnset.
var learnLevel = learn.GetMoveLevel(move);
var learnLevel = learn.GetLevelLearnMove(move);
if ((uint)learnLevel <= level)
return false;
@ -101,7 +101,7 @@ public static class MoveShop8MasteryExtensions
bool purchased = shop.GetPurchasedRecordFlag(index);
bool mastered = shop.GetMasteredRecordFlag(index);
var masteryLevel = mastery.GetMoveLevel(move);
var masteryLevel = mastery.GetLevelLearnMove(move);
if (masteryLevel > metLevel && move != alphaMove) // no master flag set
{
if (!mastered)
@ -109,7 +109,7 @@ public static class MoveShop8MasteryExtensions
if (purchased)
continue;
// Check for seed of mastery usage
if (learn.GetMoveLevel(move) > metLevel)
if (learn.GetLevelLearnMove(move) > metLevel)
return false;
}
else

View file

@ -35,7 +35,7 @@ public class FlagUtilTests
// does nothing on empty
Span<byte> copy = stackalloc byte[data.Length];
FlagUtil.SetFlag(copy, byteIndex, bitIndex, false);
copy.Count((byte)0).Should().Be(copy.Length);
copy.IndexOfAnyExcept<byte>(0).Should().Be(-1);
// doesn't clear any other flag
data.CopyTo(copy);