using System;
namespace PKHeX.Core;
///
/// Inheritance logic for bred eggs.
///
/// Refer to the associated Egg Source enums used by the associated child classes for inheritance ordering.
public static class MoveBreed
{
///
/// Verifies the input using the breeding rules of the associated and .
///
/// Generation rule-set the egg was created in
/// Entity species in the egg
/// Entity form in the egg
/// Version the egg was created in
/// Moves the egg supposedly originated with
/// Output buffer indicating the origin of each index within
/// True if the moves are ordered correctly, without missing moves.
public static bool Validate(int generation, int species, int form, GameVersion version, ReadOnlySpan moves, Span origins) => generation switch
{
2 => MoveBreed2.Validate(species, version, moves, origins),
3 => MoveBreed3.Validate(species, version, moves, origins),
4 => MoveBreed4.Validate(species, version, moves, origins),
5 => MoveBreed5.Validate(species, version, moves, origins),
_ => MoveBreed6.Validate(generation, species, form, version, moves, origins),
};
///
/// Gets the expected moves the egg should come with, using an input of requested that are requested to be in the output.
///
/// Moves requested to be in the expected moves result
/// Encounter detail interface wrapper; should always be .
/// Result moves that are valid
/// Validates the requested moves first prior to trying a more expensive computation.
/// True if the is valid using the input . If not valid, the will be base egg moves, probably valid.
public static bool GetExpectedMoves(ReadOnlySpan moves, IEncounterTemplate enc, Span result)
{
Span origins = stackalloc byte[moves.Length];
var valid = Validate(enc.Generation, enc.Species, enc.Form, enc.Version, moves, origins);
if (valid)
{
moves.CopyTo(result);
return true;
}
return GetExpectedMoves(enc.Generation, enc.Species, enc.Form, enc.Version, moves, origins, result);
}
///
/// A more expensive method of getting the expected moves the egg should come with, using an input of requested that are requested to be in the output.
///
/// Uses inputs calculated from . Don't call this directly unless already parsed the input as invalid.
/// Expected moves for the encounter
///
public static bool GetExpectedMoves(int generation, int species, int form, GameVersion version, ReadOnlySpan moves, Span origins, Span result)
{
// Try rearranging the order of the moves.
// Group and order moves by their possible origin flags.
Span expected = stackalloc MoveOrder[moves.Length];
GetSortedMoveOrder(generation, moves, origins, expected);
// Don't mutate the expected list any more.
// Temp buffer for the validation origin flags, unused in current scope but used inside the called method.
Span temp = stackalloc byte[moves.Length];
// Try checking if the rearranged order from above is valid.
for (int i = 0; i < moves.Length; i++)
result[i] = expected[i].Move;
var valid = Validate(generation, species, form, version, result, temp);
if (valid) // If true, the result buffer is now valid.
return true;
// Well, that didn't work; probably because one or more moves aren't valid.
// Let's remove all present base moves, and get a fresh set of base moves.
var learn = GameData.GetLearnsets(version);
var table = GameData.GetPersonal(version);
var index = table.GetFormIndex(species, form);
var learnset = learn[index];
var eggLevel = EggStateLegality.GetEggLevel(generation);
var baseMoves = learnset.GetBaseEggMoves(eggLevel);
RebuildMoves(baseMoves, expected, result);
// Check if that worked...
temp.Clear();
valid = Validate(generation, species, form, version, result, temp);
if (valid) // If true, the result buffer is now valid.
return true;
// Total failure; just return the base moves.
baseMoves.CopyTo(result);
for (int i = baseMoves.Length; i < result.Length; i++)
result[i] = 0;
return false;
}
private static void GetSortedMoveOrder(int generation, ReadOnlySpan moves, Span origins, Span expected)
{
if (generation == 2)
{
GetSortedMoveOrder2(moves, origins, expected);
return;
}
int count = 0;
for (int i = moves.Length - 1; i >= 0; i--)
{
var origin = origins[i];
if (origin == 0) // invalid/empty
continue;
int insertIndex = GetInsertIndex(expected, origin, count);
if (insertIndex < count)
ShiftAllItems(expected, insertIndex);
expected[insertIndex] = new MoveOrder((ushort)moves[i], origin);
count++;
}
}
private static void ShiftAllItems(Span details, int insertIndex)
{
// Shifts all indexes starting at insertIndex to the right by one.
// Empty slot at the end is overwritten, but that slot was zero (unused).
for (int i = details.Length - 1; i > insertIndex; i--)
details[i] = details[i - 1];
}
private static void GetSortedMoveOrder2(ReadOnlySpan moves, Span origins, Span expected)
{
// Base moves first, then non-base.
// Empty/invalid move slots are ignored -- default struct value is an empty move.
int baseMoves = origins.Count((byte)EggSource2.Base);
int ctrBase = 0;
int ctrNonBase = 0;
for (int i = 0; i < moves.Length; i++)
{
var origin = origins[i];
if (origin == 0) // invalid/empty
continue;
int index = origin == (byte)EggSource2.Base ? ctrBase++ : baseMoves + ctrNonBase++;
expected[index] = new MoveOrder((ushort)moves[i], origin);
}
}
private static int GetInsertIndex(ReadOnlySpan expected, byte origin, int count)
{
// Return the first index that has an origin lower than the entry present in the slot.
// If no such index exists, return the current count (insert at the end).
int i = 0;
for (; i < count; i++)
{
if (origin < expected[i].Origin)
break;
}
return i;
}
private static void RebuildMoves(ReadOnlySpan baseMoves, ReadOnlySpan expected, Span result)
{
// Build a list of moves that are not present in the base moves list.
// Use the expected order (sorted by origin flags) when assembling the result.
Span notBase = stackalloc int[expected.Length];
int notBaseCount = 0;
foreach (var (move, origin) in expected)
{
if (origin == 0) // invalid/empty
continue;
if (!baseMoves.Contains(move))
notBase[notBaseCount++] = move;
}
int baseCount = expected.Length - notBaseCount;
if (baseCount > baseMoves.Length)
baseCount = baseMoves.Length;
int ctr = 0;
// Start with base moves
for (; ctr < baseCount; ctr++)
result[ctr] = baseMoves[baseMoves.Length - baseCount + ctr];
// Then with non-base moves
for (var i = 0; i < notBaseCount; i++)
result[ctr++] = notBase[i];
// Then clear the remainder
for (int i = ctr; i < result.Length; i++)
result[i] = 0;
}
///
/// Simple tuple to track the move and its possible origin flags.
///
private readonly record struct MoveOrder(ushort Move, byte Origin);
}