PKHeX/PKHeX.Core/Legality/Moves/Breeding/MoveBreed.cs
Kurt e9c9627ad5
Relearn suggestions + Headbutt Slot Fix (#3190)
This change revises the logic for an Egg Move's RelearnMoves.

Removes a class that is no longer used
Passes the large value-passing struct byref via in keyword
Revises handling so that early-returned results from the MoveBreed processing are marked with their move source rather than leaving them unvisited.
The moveset processor uses depth search recursion, and returns false if the move that it is processing is not possible for the hatched moveset. If the depth search never reaches the check for base moves, all moves won't be marked for their origins, resulting in the result array not being accurate to use for suggestion purposes.

Yay multi-purpose logic (validation w/early returns & fixing needing fully processed data).
2021-04-08 15:58:09 -07:00

116 lines
4.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
public static class MoveBreed
{
public static bool Process(int generation, int species, int form, GameVersion version, int[] moves)
{
_ = Process(generation, species, form, version, moves, out var valid);
return valid;
}
public static object Process(int generation, int species, int form, GameVersion version, int[] moves, out bool valid) => generation switch
{
2 => MoveBreed2.Validate(species, version, moves, out valid),
3 => MoveBreed3.Validate(species, version, moves, out valid),
4 => MoveBreed4.Validate(species, version, moves, out valid),
5 => MoveBreed5.Validate(species, version, moves, out valid),
_ => MoveBreed6.Validate(generation, species, form, version, moves, out valid),
};
public static int[] GetExpectedMoves(int[] moves, IEncounterTemplate enc)
{
var parse = Process(enc.Generation, enc.Species, enc.Form, enc.Version, moves, out var valid);
if (valid)
return moves;
return GetExpectedMoves(enc.Generation, enc.Species, enc.Form, enc.Version, moves, parse);
}
public static int[] GetExpectedMoves(int generation, int species, int form, GameVersion version, int[] moves, object parse)
{
// Try rearranging the order of the moves.
// Build an info table
var x = (byte[])parse;
var details = new MoveOrder[moves.Length];
for (byte i = 0; i < x.Length; i++)
details[i] = new MoveOrder((ushort) moves[i], x[i]);
// Kick empty slots to the end, then order by source priority.
IOrderedEnumerable<MoveOrder> expect = generation != 2
? details.OrderBy(z => z.Move == 0).ThenBy(z => z.Source)
: details.OrderBy(z => z.Move == 0).ThenBy(z => z.Source != (byte) EggSource2.Base);
// Reorder the moves.
var reorder1 = new int[moves.Length];
var exp = expect.ToList();
for (int i = 0; i < moves.Length; i++)
reorder1[i] = exp[i].Move;
// Check if that worked...
_ = Process(generation, species, form, version, reorder1, out var valid);
if (valid)
return reorder1;
// Well, that didn't work; probably because the moves aren't valid. Let's remove all the base moves, and get a fresh set.
var reorder2 = reorder1; // reuse instead of reallocate
var learn = GameData.GetLearnsets(version);
var table = GameData.GetPersonal(version);
var index = table.GetFormIndex(species, form);
var learnset = learn[index];
var baseMoves = learnset.GetBaseEggMoves(generation >= 4 ? 1 : 5);
RebuildMoves(baseMoves, exp, reorder2);
// Check if that worked...
_ = Process(generation, species, form, version, reorder2, out valid);
if (valid)
return reorder2;
// Total failure; just return the base moves.
baseMoves.CopyTo(reorder2);
for (int i = baseMoves.Length; i < reorder2.Length; i++)
reorder2[i] = 0;
return reorder2;
}
private static void RebuildMoves(ReadOnlySpan<int> baseMoves, List<MoveOrder> exp, int[] result)
{
var notBase = new List<int>();
foreach (var m in exp)
{
if (m.Source == 0)
continue; // invalid
int move = m.Move;
if (baseMoves.IndexOf(move) == -1)
notBase.Add(move);
}
int baseCount = 4 - notBase.Count;
if (baseCount > baseMoves.Length)
baseCount = baseMoves.Length;
int ctr = 0;
for (; ctr < baseCount; ctr++)
result[ctr] = baseMoves[baseMoves.Length - baseCount + ctr];
foreach (var m in notBase)
result[ctr++] = m;
for (int i = ctr; i < result.Length; i++)
result[i] = 0;
}
private readonly struct MoveOrder
{
public readonly ushort Move;
public readonly byte Source;
public MoveOrder(ushort move, byte source)
{
Move = move;
Source = source;
}
}
}
}