PKHeX/PKHeX.Core/Legality/Structures/Learnset.cs
Kurt 858aa50689 Refactor encounter matching
exercise in deferred execution/state machine, only calculate possible
matches until a sufficiently valid match is obtained. Previous setup
would try to calculate the 'best match' and had band-aid workarounds in
cases where a subsequent check may determine it to be a false match.

There's still more ways to improve speed:
- precalculate relationships for Encounter Slots rather than iterating
over every area
- yielding individual slots instead of an entire area
- group non-egg wondercards by ID in a dict/hashtable for faster
retrieval

reworked some internals:
- EncounterMatch is always an IEncounterable instead of an object, for
easy pattern matching.
- Splitbreed checking is done per encounter and is stored in the
EncounterEgg result
- Encounter validation uses Encounter/Move/RelearnMove/Evolution to
whittle to the final encounter.

As a part of the encounter matching, a lazy peek is used to check if an
invalid encounter should be retained instead of discarded; if another
encounter has not been checked, it'll stop the invalid checks and move
on. If it is the last encounter, no other valid encounters exist so it
will keep the parse for the invalid encounter.

If no encounters are yielded, then there is no encountermatch. An
EncounterInvalid is created to store basic details, and the parse is
carried out.

Breaks some legality checking features for flagging invalid moves in
more detail, but those can be re-added in a separate check (if
splitbreed & any move invalid -> check for other split moves).

Should now be easier to follow the flow & maintain 😄
2017-05-27 21:17:57 -07:00

151 lines
5.6 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
namespace PKHeX.Core
{
public abstract class Learnset
{
protected int Count;
protected int[] Moves;
protected int[] Levels;
/// <summary>
/// Returns the moves a Pokémon can learn between the specified level range.
/// </summary>
/// <param name="maxLevel">Maximum level</param>
/// <param name="minLevel">Minimum level</param>
/// <returns>Array of Move IDs</returns>
public int[] getMoves(int maxLevel, int minLevel = 0)
{
if (minLevel <= 1 && maxLevel >= 100)
return Moves;
int start = Array.FindIndex(Levels, z => z >= minLevel);
if (start < 0)
return new int[0];
int end = Array.FindLastIndex(Levels, z => z <= maxLevel);
if (end < 0)
return new int[0];
int[] result = new int[end - start + 1];
Array.Copy(Moves, start, result, 0, result.Length);
return result;
}
/// <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>
/// <param name="count">The amount of move slots to return.</param>
/// <returns>Array of Move IDs</returns>
public int[] getEncounterMoves(int level, int count = 4)
{
if (count == 0 || Moves.Length == 0)
return new int[0];
int end = Array.FindLastIndex(Levels, z => z <= level);
if (end < 0)
return new int[0];
count = Math.Min(count, 4);
int start = end - count + 1;
if (start < 0) start = 0;
int[] result = new int[end - start + 1];
Array.Copy(Moves, start, result, 0, result.Length);
return result;
}
/// <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);
}
/// <summary>Returns the level that a Pokémon can learn the specified move.</summary>
/// <param name="move">Move ID</param>
/// <returns>Level the move is learned at. If the result is below 0, it cannot be learned by levelup.</returns>
public int getLevelLearnMove(int move)
{
int index = Array.IndexOf(Moves, move);
return index < 0 ? 0 : Levels[index];
}
}
public class Learnset1 : Learnset
{
private Learnset1(byte[] data, ref int offset)
{
var moves = new List<int>();
var levels = new List<int>();
while (data[offset] != 0)
{
levels.Add(data[offset++]);
moves.Add(data[offset++]);
}
++offset;
Moves = moves.ToArray();
Levels = levels.ToArray();
Count = Moves.Length;
}
public static Learnset[] getArray(byte[] input, int maxSpecies)
{
var data = new Learnset[maxSpecies + 1];
int offset = 0;
for (int s = 0; s < data.Length; s++)
data[s] = new Learnset1(input, ref offset);
return data;
}
}
public class Learnset6 : Learnset
{
private Learnset6(byte[] data)
{
if (data.Length < 4 || data.Length % 4 != 0)
{ Count = 0; Levels = new int[0]; Moves = new int[0]; return; }
Count = data.Length / 4 - 1;
Moves = new int[Count];
Levels = new int[Count];
using (BinaryReader br = new BinaryReader(new MemoryStream(data)))
for (int i = 0; i < Count; i++)
{
Moves[i] = br.ReadInt16();
Levels[i] = br.ReadInt16();
}
}
public static Learnset[] getArray(byte[][] entries)
{
Learnset[] data = new Learnset[entries.Length];
for (int i = 0; i < data.Length; i++)
data[i] = new Learnset6(entries[i]);
return data;
}
}
public class Learnset7 : Learnset
{
private Learnset7(byte[] data)
{
if (data.Length < 4 || data.Length % 4 != 0)
{ Count = 0; Levels = new int[0]; Moves = new int[0]; return; }
Count = data.Length / 4 - 1;
Moves = new int[Count];
Levels = new int[Count];
using (BinaryReader br = new BinaryReader(new MemoryStream(data)))
for (int i = 0; i < Count; i++)
{
Moves[i] = br.ReadInt16();
Levels[i] = br.ReadInt16();
}
}
public static Learnset[] getArray(byte[][] entries)
{
Learnset[] data = new Learnset[entries.Length];
for (int i = 0; i < data.Length; i++)
data[i] = new Learnset7(entries[i]);
return data;
}
}
}