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); }