Add Breeding move ordering logic, and use in legality analysis (#3183)

* Initial bred moveset validation logic

Unpeel the inheritance via recursion and permitted moves

* Volt tackle considerations

* Optimize out empty slot skips

* Add tests, fix off-by-one's

* Require all base moves if empty slot in moveset

* Add test to prove failure per Anubis' provided test

* Tweak enum labels for easier debugging

When two enums share the same underlying value, the ToString/name of the value may be either of the two (or the last defined one, in my debugging). Just give it a separate magic value.

* Fix recursion oopsie

Also check for scenario where no-base-moves but not enough moves to push base moves out

* Add Crystal tutor checks

* Add specialized gen2 verification method

Game loops through father's moves and pushes in one iteration, rather than checking by type.

* Add another case with returning base move

* Add push-out requirement for re-added base moves

* Minor tweaks

Condense tests, fix another off-by-one noticed when creating tests

* Disallow inherited parent levelup moves

Disallow volt tackle on Gen2/R/S

* Split MoveBreed into generation specific classes

Gen2 behaves slightly different from Gen3/4, which behaves slightly different from Gen5... and Gen6 behaves differently too.

Add some xmldoc as the api is starting to solidify

* Add method overload that returns the parse

Verify that the parse order is as expected

* Add reordering suggestion logic

Try sorting first, then go nuclear with rebuilding.

* Return base moves if complete fail

* Set base moves when generating eggs, only.

* Use breed logic to check for egg ordering legality

Don't bother helping for split-breed species
This commit is contained in:
Kurt 2021-04-04 18:30:01 -07:00 committed by GitHub
parent ecf1f361fe
commit 0626b0c29b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1176 additions and 232 deletions

View file

@ -74,7 +74,7 @@ namespace PKHeX.Core
return m;
enc ??= legal.EncounterMatch;
if (enc is MysteryGift || enc is EncounterEgg)
if (enc is MysteryGift or EncounterEgg)
return m;
if (enc is EncounterSlot6AO {CanDexNav: true} dn)

View file

@ -1,12 +1,11 @@
using System;
using System.Linq;
namespace PKHeX.Core
{
/// <summary>
/// Egg Encounter Data
/// </summary>
public record EncounterEgg : IEncounterable
public sealed record EncounterEgg : IEncounterable
{
public int Species { get; }
public int Form { get; }
@ -47,7 +46,8 @@ namespace PKHeX.Core
pk.Ball = (int)Ball.Poke;
pk.OT_Friendship = pk.PersonalInfo.BaseFriendship;
int[] moves = SetEncounterMoves(pk, version);
SetEncounterMoves(pk, version);
pk.HealPP();
SetPINGA(pk, criteria);
if (gen <= 2 && version != GameVersion.C)
@ -69,7 +69,10 @@ namespace PKHeX.Core
SetForm(pk, sav);
pk.SetRandomEC();
pk.RelearnMoves = moves;
pk.RelearnMove1 = pk.Move1;
pk.RelearnMove2 = pk.Move2;
pk.RelearnMove3 = pk.Move3;
pk.RelearnMove4 = pk.Move4;
return pk;
}
@ -122,31 +125,14 @@ namespace PKHeX.Core
pk.Met_Location = Math.Max(0, EncounterSuggestion.GetSuggestedEggMetLocation(pk));
}
private int[] SetEncounterMoves(PKM pk, GameVersion version)
private void SetEncounterMoves(PKM pk, GameVersion version)
{
int[] moves = GetCurrentEggMoves(pk, version);
pk.Moves = moves;
pk.SetMaximumPPCurrent(moves);
return moves;
var learnset = GameData.GetLearnset(version, Species, Form);
var baseMoves = learnset.GetBaseEggMoves(Level);
if (baseMoves.Length == 0) return; pk.Move1 = baseMoves[0];
if (baseMoves.Length == 1) return; pk.Move2 = baseMoves[1];
if (baseMoves.Length == 2) return; pk.Move3 = baseMoves[2];
if (baseMoves.Length == 3) return; pk.Move4 = baseMoves[3];
}
private int[] GetCurrentEggMoves(PKM pk, GameVersion version)
{
var moves = MoveEgg.GetEggMoves(pk.PersonalInfo, Species, Form, version, Generation);
if (moves.Length == 0)
return MoveLevelUp.GetEncounterMoves(pk, Level, version);
if (moves.Length >= 4 || pk.Format < 6)
return moves;
// Sprinkle in some default level up moves
var lvl = MoveList.GetBaseEggMoves(pk, Species, Form, version, Level);
return lvl.Concat(moves).ToArray();
}
}
public sealed record EncounterEggSplit : EncounterEgg
{
public int OtherSpecies { get; }
public EncounterEggSplit(int species, int form, int level, int gen, GameVersion game, int otherSpecies) : base(species, form, level, gen, game) => OtherSpecies = otherSpecies;
}
}

View file

@ -52,9 +52,9 @@ namespace PKHeX.Core
if (o.Species <= max && Breeding.CanHatchAsEgg(o.Species, o.Form, ver))
{
yield return new EncounterEggSplit(o.Species, o.Form, lvl, generation, ver, e.Species);
yield return new EncounterEgg(o.Species, o.Form, lvl, generation, ver);
if (generation > 5 && (pkm.WasTradedEgg || all) && HasOtherGamePair(ver))
yield return new EncounterEggSplit(o.Species, o.Form, lvl, generation, GetOtherTradePair(ver), e.Species);
yield return new EncounterEgg(o.Species, o.Form, lvl, generation, GetOtherTradePair(ver));
}
}

View file

@ -34,7 +34,7 @@ namespace PKHeX.Core
return res;
}
private static CheckMoveResult[] ParseMovesForEncounters(PKM pkm, LegalInfo info, IReadOnlyList<int> currentMoves)
private static CheckMoveResult[] ParseMovesForEncounters(PKM pkm, LegalInfo info, int[] currentMoves)
{
if (pkm.Species == (int)Species.Smeargle) // special handling for Smeargle
return ParseMovesForSmeargle(pkm, currentMoves, info); // Smeargle can have any moves except a few
@ -94,12 +94,6 @@ namespace PKHeX.Core
return ParseMoves(pkm, source, info);
}
private static CheckMoveResult[] ParseMovesIsEggPreRelearn(PKM pkm, IReadOnlyList<int> currentMoves, EncounterEgg e)
{
var infoset = new EggInfoSource(pkm, e);
return VerifyPreRelearnEggBase(pkm, currentMoves, infoset);
}
private static CheckMoveResult[] ParseMovesWasEggPreRelearn(PKM pkm, IReadOnlyList<int> currentMoves, LegalInfo info, EncounterEgg e)
{
var EventEggMoves = GetSpecialMoves(info.EncounterMatch);
@ -157,12 +151,12 @@ namespace PKHeX.Core
: ParseMovesRelearn(pkm, currentMoves, info);
}
private static CheckMoveResult[] ParseMovesPre3DS(PKM pkm, IReadOnlyList<int> currentMoves, LegalInfo info)
private static CheckMoveResult[] ParseMovesPre3DS(PKM pkm, int[] currentMoves, LegalInfo info)
{
if (info.EncounterMatch is EncounterEgg e)
{
return pkm.IsEgg
? ParseMovesIsEggPreRelearn(pkm, currentMoves, e)
? VerifyPreRelearnEggBase(pkm, currentMoves, e)
: ParseMovesWasEggPreRelearn(pkm, currentMoves, info, e);
}
@ -746,72 +740,11 @@ namespace PKHeX.Core
}
}
/* Similar to verifyRelearnEgg but in pre relearn generation is the moves what should match the expected order but only if the pokemon is inside an egg */
private static CheckMoveResult[] VerifyPreRelearnEggBase(PKM pkm, IReadOnlyList<int> currentMoves, EggInfoSource infoset)
private static CheckMoveResult[] VerifyPreRelearnEggBase(PKM pkm, int[] currentMoves, EncounterEgg e)
{
CheckMoveResult[] res = new CheckMoveResult[4];
var gen = pkm.Generation;
// Obtain level1 moves
var reqBase = GetRequiredBaseMoveCount(currentMoves, infoset);
var sb = new System.Text.StringBuilder();
// Check if the required amount of Base Egg Moves are present.
for (int i = 0; i < reqBase; i++)
{
if (infoset.Base.Contains(currentMoves[i]))
{
res[i] = new CheckMoveResult(Initial, gen, Valid, LMoveRelearnEgg, CurrentMove);
continue;
}
// mark remaining base egg moves missing
for (int z = i; z < reqBase; z++)
res[z] = new CheckMoveResult(Initial, gen, Invalid, LMoveRelearnEggMissing, CurrentMove);
// provide the list of suggested base moves for the last required slot
sb.Append(string.Join(", ", GetMoveNames(infoset.Base)));
break;
}
if (sb.Length != 0)
res[reqBase > 0 ? reqBase - 1 : 0].Comment = string.Format(Environment.NewLine + LMoveFExpect_0, sb);
// Inherited moves appear after the required base moves.
var AllowInheritedSeverity = infoset.AllowInherited ? Valid : Invalid;
for (int i = reqBase; i < 4; i++)
{
if (currentMoves[i] == 0) // empty
res[i] = new CheckMoveResult(None, gen, Valid, LMoveSourceEmpty, CurrentMove);
else if (infoset.Egg.Contains(currentMoves[i])) // inherited egg move
res[i] = new CheckMoveResult(EggMove, gen, AllowInheritedSeverity, infoset.AllowInherited ? LMoveEggInherited : LMoveEggInvalidEvent, CurrentMove);
else if (infoset.LevelUp.Contains(currentMoves[i])) // inherited lvl moves
res[i] = new CheckMoveResult(InheritLevelUp, gen, AllowInheritedSeverity, infoset.AllowInherited ? LMoveEggLevelUp : LMoveEggInvalidEventLevelUp, CurrentMove);
else if (infoset.TMHM.Contains(currentMoves[i])) // inherited TMHM moves
res[i] = new CheckMoveResult(TMHM, gen, AllowInheritedSeverity, infoset.AllowInherited ? LMoveEggTMHM : LMoveEggInvalidEventTMHM, CurrentMove);
else if (infoset.Tutor.Contains(currentMoves[i])) // inherited tutor moves
res[i] = new CheckMoveResult(Tutor, gen, AllowInheritedSeverity, infoset.AllowInherited ? LMoveEggInheritedTutor : LMoveEggInvalidEventTutor, CurrentMove);
else // not inheritable, flag
res[i] = new CheckMoveResult(Unknown, gen, Invalid, LMoveEggInvalid, CurrentMove);
}
return res;
}
private static int GetRequiredBaseMoveCount(IReadOnlyList<int> currentMoves, EggInfoSource infoset)
{
int baseCt = infoset.Base.Count;
if (baseCt > 4) baseCt = 4;
// Obtain Inherited moves
var inherited = currentMoves.Where(m => m != 0 && infoset.IsInherited(m)).ToList();
int inheritCt = inherited.Count;
// Get required amount of base moves
int unique = infoset.Base.Union(inherited).Count();
int reqBase = inheritCt == 4 || baseCt + inheritCt > 4 ? 4 - inheritCt : baseCt;
if (currentMoves.Count(m => m != 0) < Math.Min(4, infoset.Base.Count))
reqBase = Math.Min(4, unique);
return reqBase;
CheckMoveResult[] result = new CheckMoveResult[4];
_ = VerifyRelearnMoves.VerifyEggMoveset(pkm, e, result, currentMoves, CurrentMove);
return result;
}
private static void VerifyNoEmptyDuplicates(IReadOnlyList<int> moves, CheckMoveResult[] res)

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.ParseSettings;
@ -22,7 +21,7 @@ namespace PKHeX.Core
return enc switch
{
IRelearn s when s.Relearn.Count > 0 => VerifyRelearnSpecifiedMoveset(pkm, s.Relearn, result),
EncounterEgg e => VerifyRelearnEggBase(pkm, e, result),
EncounterEgg e => VerifyEggMoveset(pkm, e, result, pkm.RelearnMoves),
EncounterSlot6AO z when pkm.RelearnMove1 != 0 && z.CanDexNav => VerifyRelearnDexNav(pkm, result),
_ => VerifyRelearnNone(pkm, result)
};
@ -70,113 +69,40 @@ namespace PKHeX.Core
return result;
}
private static CheckResult[] VerifyRelearnEggBase(PKM pkm, EncounterEgg e, CheckResult[] result)
internal static CheckResult[] VerifyEggMoveset(PKM pkm, EncounterEgg e, CheckResult[] result, int[] moves, CheckIdentifier type = CheckIdentifier.RelearnMove)
{
int[] RelearnMoves = pkm.RelearnMoves;
// Level up moves cannot be inherited if Ditto is the parent
// that means genderless species and male only species except Nidoran and Volbeat (they breed with female nidoran and illumise) could not have level up moves as an egg
bool inheritLvlMoves = Breeding.GetCanInheritMoves(e.Species);
var origins = MoveBreed.Process(e.Generation, e.Species, e.Form, e.Version, moves, out var valid);
if (valid)
{
for (int i = 0; i < result.Length; i++)
{
var msg = EggSourceExtensions.GetSource(origins, i);
result[i] = new CheckMoveResult(MoveSource.EggMove, e.Generation, Severity.Valid, msg, type);
}
}
else
{
var fix = MoveBreed.GetExpectedMoves(moves, e);
for (int i = 0; i < moves.Length; i++)
{
var msg = EggSourceExtensions.GetSource(origins, i);
if (moves[i] == fix[i])
{
result[i] = new CheckMoveResult(MoveSource.EggMove, e.Generation, Severity.Valid, msg, type);
continue;
}
// Obtain level1 moves
var baseMoves = MoveList.GetBaseEggMoves(pkm, e.Species, e.Form, e.Version, 1);
int baseCt = Math.Min(4, baseMoves.Length);
// Obtain Inherited moves
var inheritMoves = MoveList.GetValidRelearn(pkm, e.Species, e.Form, inheritLvlMoves, e.Version).ToList();
int reqBase = GetRequiredBaseMoves(RelearnMoves, baseMoves, baseCt, inheritMoves);
// Check if the required amount of Base Egg Moves are present.
FlagBaseEggMoves(result, reqBase, baseMoves, RelearnMoves);
// Non-Base moves that can magically appear in the regular movepool
if (Legal.LightBall.Contains(pkm.Species))
inheritMoves.Add((int)Move.VoltTackle);
// If any splitbreed moves are invalid, flag accordingly
IReadOnlyList<int> splitMoves = e is EncounterEggSplit s
? MoveList.GetValidRelearn(pkm, s.OtherSpecies, s.Form, inheritLvlMoves, e.Version).ToList()
: Array.Empty<int>();
// Inherited moves appear after the required base moves.
// If the pkm is capable of split-species breeding and any inherited move is from the other split scenario, flag accordingly.
bool splitInvalid = FlagInvalidInheritedMoves(result, reqBase, RelearnMoves, inheritMoves, splitMoves);
if (splitInvalid && e is EncounterEggSplit x)
FlagSplitbreedMoves(result, reqBase, x);
msg = string.Format(LMoveRelearnFExpect_0, GetMoveName(fix[i]) + $" ({msg})");
result[i] = new CheckMoveResult(MoveSource.EggMove, e.Generation, Severity.Invalid, msg, type);
}
}
var dupe = IsAnyRelearnMoveDuplicate(pkm);
if (dupe > 0)
result[dupe] = new CheckResult(Severity.Invalid, LMoveSourceDuplicate, CheckIdentifier.RelearnMove);
result[dupe] = new CheckMoveResult(MoveSource.EggMove, e.Generation, Severity.Invalid, LMoveSourceDuplicate, type);
return result;
}
private static void FlagBaseEggMoves(CheckResult[] result, int required, IReadOnlyList<int> baseMoves, IReadOnlyList<int> RelearnMoves)
{
for (int i = 0; i < required; i++)
{
if (!baseMoves.Contains(RelearnMoves[i]))
{
FlagRelearnMovesMissing(result, required, baseMoves, i);
return;
}
result[i] = new CheckResult(Severity.Valid, LMoveRelearnEgg, CheckIdentifier.RelearnMove);
}
}
private static void FlagRelearnMovesMissing(CheckResult[] result, int required, IReadOnlyList<int> baseMoves, int start)
{
for (int z = start; z < required; z++)
result[z] = new CheckResult(Severity.Invalid, LMoveRelearnEggMissing, CheckIdentifier.RelearnMove);
// provide the list of suggested base moves for the last required slot
string em = string.Join(", ", GetMoveNames(baseMoves));
result[required - 1].Comment += string.Format(Environment.NewLine + LMoveRelearnFExpect_0, em);
}
private static bool FlagInvalidInheritedMoves(CheckResult[] result, int required, IReadOnlyList<int> RelearnMoves, IReadOnlyList<int> inheritMoves, IReadOnlyList<int> splitMoves)
{
bool splitInvalid = false;
bool isSplit = splitMoves.Count > 0;
for (int i = required; i < 4; i++)
{
if (RelearnMoves[i] == 0) // empty
result[i] = new CheckResult(Severity.Valid, LMoveSourceEmpty, CheckIdentifier.RelearnMove);
else if (inheritMoves.Contains(RelearnMoves[i])) // inherited
result[i] = new CheckResult(Severity.Valid, LMoveSourceRelearn, CheckIdentifier.RelearnMove);
else if (isSplit && splitMoves.Contains(RelearnMoves[i])) // inherited
splitInvalid = true;
else // not inheritable, flag
result[i] = new CheckResult(Severity.Invalid, LMoveRelearnInvalid, CheckIdentifier.RelearnMove);
}
return splitInvalid;
}
private static void FlagSplitbreedMoves(CheckResult?[] res, int required, EncounterEggSplit x)
{
var other = x.OtherSpecies;
for (int i = required; i < 4; i++)
{
if (res[i] != null)
continue;
string message = string.Format(LMoveEggFIncompatible0_1, SpeciesStrings[other], SpeciesStrings[x.Species]);
res[i] = new CheckResult(Severity.Invalid, message, CheckIdentifier.RelearnMove);
}
}
private static int GetRequiredBaseMoves(int[] RelearnMoves, IReadOnlyList<int> baseMoves, int baseCt, IReadOnlyList<int> inheritMoves)
{
var inherited = RelearnMoves.Where(m => m != 0 && (!baseMoves.Contains(m) || inheritMoves.Contains(m))).ToList();
int inheritCt = inherited.Count;
// Get required amount of base moves
int unique = baseMoves.Union(inherited).Count();
int reqBase = inheritCt == 4 || baseCt + inheritCt > 4 ? 4 - inheritCt : baseCt;
if (RelearnMoves.Count(m => m != 0) < Math.Min(4, baseMoves.Count))
reqBase = Math.Min(4, unique);
return reqBase;
}
private static int IsAnyRelearnMoveDuplicate(PKM pk)
{
int m1 = pk.RelearnMove1;

View file

@ -185,5 +185,29 @@ namespace PKHeX.Core
}
return -1;
}
public ReadOnlySpan<int> GetBaseEggMoves(int level)
{
// Count moves <= level
var count = 0;
foreach (var x in Levels)
{
if (x > level)
break;
count++;
}
// Return a slice containing the moves <= level.
if (count == 0)
return ReadOnlySpan<int>.Empty;
int start = 0;
if (count > 4)
{
start = count - 4;
count = 4;
}
return Moves.AsSpan(start, count);
}
}
}

View file

@ -108,46 +108,29 @@ namespace PKHeX.Core
/// Gets the current <see cref="PKM.RelearnMoves"/> array of four moves that might be legal.
/// </summary>
/// <remarks>Returns an empty array if it should not have any moves. Use <see cref="GetSuggestedRelearnMovesFromEncounter"/> instead of calling directly.</remarks>
private static IReadOnlyList<int> GetSuggestedRelearn(this IEncounterable enc, PKM pkm)
public static IReadOnlyList<int> GetSuggestedRelearn(this IEncounterable enc, PKM pkm)
{
if (enc.Generation < 6 || (pkm is IBattleVersion { BattleVersion: not 0 }))
return Array.Empty<int>();
if (enc.Generation < 6 || pkm is IBattleVersion { BattleVersion: not 0 })
return Empty;
// Invalid encounters won't be recognized as an EncounterEgg; check if it *should* be a bred egg.
return enc switch
{
IRelearn s when s.Relearn.Count > 0 => s.Relearn,
EncounterEgg e => MoveList.GetBaseEggMoves(pkm, e.Species, e.Form, e.Version, e.Level),
_ => Array.Empty<int>(),
EncounterEgg or EncounterInvalid { EggEncounter: true } => MoveBreed.GetExpectedMoves(pkm.RelearnMoves, enc),
_ => Empty,
};
}
private static readonly IReadOnlyList<int> Empty = new int[4];
/// <summary>
/// Gets the current <see cref="PKM.RelearnMoves"/> array of four moves that might be legal.
/// </summary>
public static IReadOnlyList<int> GetSuggestedRelearnMovesFromEncounter(this LegalityAnalysis analysis)
{
var info = analysis.Info;
if (info.Generation < 6)
return new int[4];
var pkm = analysis.pkm;
var enc = info.EncounterMatch;
var parsed = enc.GetSuggestedRelearn(pkm);
if (parsed.Count == 0) // Always true for Origins < 6 and encounters without relearn permitted.
return new int[4];
// Invalid encounters won't be recognized as an EncounterEgg; check if it *should* be a bred egg.
if (!enc.EggEncounter)
return parsed;
List<int> window = new(parsed.Where(z => z != 0));
window.AddRange(pkm.Moves.Where((_, i) => info.Moves[i].ShouldBeInRelearnMoves()));
window = window.Distinct().ToList();
int[] moves = new int[4];
int start = Math.Max(0, window.Count - 4);
int count = Math.Min(4, window.Count);
window.CopyTo(start, moves, 0, count);
return moves;
return info.Generation < 6 ? Empty : info.EncounterOriginal.GetSuggestedRelearn(analysis.pkm);
}
}
}

View file

@ -0,0 +1,35 @@
using System;
namespace PKHeX.Core
{
/// <summary>
/// Value passing object to simplify some initialization.
/// </summary>
/// <typeparam name="T">Egg Move source type enumeration.</typeparam>
internal readonly ref struct BreedInfo<T> where T : Enum
{
/// <summary> Indicates the analyzed source of each move. </summary>
public readonly T[] Actual;
/// <summary> Indicates all possible sources of each move. </summary>
public readonly byte[] Possible;
/// <summary> Level Up entry for the egg. </summary>
public readonly Learnset Learnset;
/// <summary> Moves the egg knows after it is finalized. </summary>
public readonly int[] Moves;
/// <summary> Level the egg originated at. </summary>
public readonly int Level;
public BreedInfo(int count, Learnset learnset, int[] moves, int level)
{
Possible = new byte[count];
Actual = new T[count];
Learnset = learnset;
Moves = moves;
Level = level;
}
}
}

View file

@ -0,0 +1,111 @@
using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core
{
public enum EggSource2 : byte
{
None,
Base,
FatherEgg,
FatherTM,
ParentLevelUp,
Tutor,
Max,
}
public enum EggSource34 : byte
{
None,
Base,
FatherEgg,
FatherTM,
ParentLevelUp,
Max,
VoltTackle,
}
public enum EggSource5 : byte
{
None,
Base,
FatherEgg,
ParentLevelUp,
FatherTM, // after level up, unlike Gen3/4!
Max,
VoltTackle,
}
public enum EggSource6 : byte
{
None,
Base,
ParentLevelUp,
ParentEgg,
Max,
VoltTackle,
}
public static class EggSourceExtensions
{
#pragma warning disable RCS1224 // Make method an extension method.
public static string GetSource(object parse, int index) => parse switch
#pragma warning restore RCS1224 // Make method an extension method.
{
EggSource2[] x when index < x.Length => x[index].GetSource(),
EggSource34[] x when index < x.Length => x[index].GetSource(),
EggSource5[] x when index < x.Length => x[index].GetSource(),
EggSource6[] x when index < x.Length => x[index].GetSource(),
_ => LMoveSourceEmpty,
};
public static string GetSource(this EggSource2 source) => source switch
{
EggSource2.Base => LMoveRelearnEgg,
EggSource2.FatherEgg => LMoveEggLevelUp,
EggSource2.FatherTM => LMoveEggTMHM,
EggSource2.ParentLevelUp => LMoveEggInherited,
EggSource2.Tutor => LMoveEggInheritedTutor,
EggSource2.Max => "Any",
_ => LMoveEggInvalid,
};
public static string GetSource(this EggSource34 source) => source switch
{
EggSource34.Base => LMoveRelearnEgg,
EggSource34.FatherEgg => LMoveEggLevelUp,
EggSource34.FatherTM => LMoveEggTMHM,
EggSource34.ParentLevelUp => LMoveEggInherited,
EggSource34.VoltTackle => LMoveSourceSpecial,
EggSource34.Max => "Any",
_ => LMoveEggInvalid,
};
public static string GetSource(this EggSource5 source) => source switch
{
EggSource5.Base => LMoveRelearnEgg,
EggSource5.FatherEgg => LMoveEggLevelUp,
EggSource5.FatherTM => LMoveEggTMHM,
EggSource5.ParentLevelUp => LMoveEggInherited,
EggSource5.VoltTackle => LMoveSourceSpecial,
EggSource5.Max => "Any",
_ => LMoveEggInvalid,
};
public static string GetSource(this EggSource6 source) => source switch
{
EggSource6.Base => LMoveRelearnEgg,
EggSource6.ParentEgg => LMoveEggLevelUp,
EggSource6.ParentLevelUp => LMoveEggInherited,
EggSource6.VoltTackle => LMoveSourceSpecial,
EggSource6.Max => "Any",
_ => LMoveEggInvalid,
};
}
}

View file

@ -0,0 +1,116 @@
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;
}
}
}
}

View file

@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using static PKHeX.Core.EggSource2;
namespace PKHeX.Core
{
public static class MoveBreed2
{
private const int level = 5;
public static EggSource2[] Validate(int species, GameVersion version, int[] moves, out bool valid)
{
var count = Array.IndexOf(moves, 0);
if (count == 0)
{
valid = false; // empty moveset
return Array.Empty<EggSource2>();
}
if (count == -1)
count = moves.Length;
var learn = GameData.GetLearnsets(version);
var table = GameData.GetPersonal(version);
var learnset = learn[species];
var pi = table[species];
var egg = (version == GameVersion.C ? Legal.EggMovesC : Legal.EggMovesGS)[species].Moves;
var value = new BreedInfo<EggSource2>(count, learnset, moves, level);
bool inherit = Breeding.GetCanInheritMoves(species);
MarkMovesForOrigin(value, egg, count, inherit, pi, version);
valid = RecurseMovesForOrigin(value, count - 1);
return value.Actual;
}
private static bool RecurseMovesForOrigin(BreedInfo<EggSource2> info, int start, EggSource2 type = Max)
{
int i = start;
do
{
if (type != Base)
{
if (RecurseMovesForOrigin(info, i, Base))
return true;
}
var flag = 1 << (int)Base;
if (type != Base)
flag = ~flag;
var permit = info.Possible[i];
if ((permit & flag) == 0)
return false;
info.Actual[i] = type == Base ? Base : GetFirstType(permit);
} while (--i >= 0);
return VerifyBaseMoves(info);
}
private static EggSource2 GetFirstType(byte permit)
{
for (var type = FatherEgg; type < Max; type++)
{
if ((permit & (1 << (int)type)) != 0)
return type;
}
throw new ArgumentOutOfRangeException(nameof(permit), permit, null);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool VerifyBaseMoves(BreedInfo<EggSource2> info)
{
var count = 0;
foreach (var x in info.Actual)
{
if (x == Base)
count++;
else
break;
}
var moves = info.Moves;
if (count == -1)
return moves[moves.Length - 1] != 0;
var baseMoves = info.Learnset.GetBaseEggMoves(info.Level);
if (baseMoves.Length < count)
return false;
if (moves[moves.Length - 1] == 0 && count != baseMoves.Length)
return false;
for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--)
{
var move = moves[i];
var expect = baseMoves[b];
if (expect != move)
return false;
}
// A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it.
if (baseMoves.Length == count)
return true;
for (int i = count; i < info.Actual.Length; i++)
{
var isBase = (info.Possible[i] & (1 << (int)Base)) != 0;
if (!isBase)
continue;
var baseIndex = baseMoves.IndexOf(moves[i]);
var min = moves.Length - baseMoves.Length + baseIndex;
if (i <= min + count)
return false;
}
return true;
}
private static void MarkMovesForOrigin(BreedInfo<EggSource2> value, ICollection<int> eggMoves, int count, bool inheritLevelUp, PersonalInfo info, GameVersion version)
{
var possible = value.Possible;
var learn = value.Learnset;
var baseEgg = value.Learnset.GetBaseEggMoves(value.Level);
var tm = info.TMHM;
var moves = value.Moves;
for (int i = 0; i < count; i++)
{
var move = moves[i];
if (baseEgg.IndexOf(move) != -1)
possible[i] |= 1 << (int)Base;
if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1)
possible[i] |= 1 << (int)ParentLevelUp;
if (eggMoves.Contains(move))
possible[i] |= 1 << (int)FatherEgg;
var tmIndex = Array.IndexOf(Legal.TMHM_GSC, move, 0, 50);
if (tmIndex != -1 && tm[tmIndex])
possible[i] |= 1 << (int)FatherTM;
if (version is GameVersion.C)
{
var tutorIndex = Array.IndexOf(Legal.Tutors_GSC, move);
if (tutorIndex != -1 && tm[57 + tutorIndex])
possible[i] |= 1 << (int)Tutor;
}
}
}
}
}

View file

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using static PKHeX.Core.EggSource34;
namespace PKHeX.Core
{
/// <summary>
/// Inheritance logic for Generation 3.
/// </summary>
/// <remarks>Refer to <see cref="EggSource34"/> for inheritance ordering.</remarks>
public static class MoveBreed3
{
private const int level = 5;
public static EggSource34[] Validate(int species, GameVersion version, int[] moves, out bool valid)
{
var count = Array.IndexOf(moves, 0);
if (count == 0)
{
valid = false; // empty moveset
return Array.Empty<EggSource34>();
}
if (count == -1)
count = moves.Length;
var learn = GameData.GetLearnsets(version);
var table = GameData.GetPersonal(version);
var learnset = learn[species];
var pi = table[species];
var egg = Legal.EggMovesRS[species].Moves;
var value = new BreedInfo<EggSource34>(count, learnset, moves, level);
if (version == GameVersion.E && moves[count - 1] is (int)Move.VoltTackle)
{
if (--count == 0)
{
valid = false; // must have base moves; sanity check
return Array.Empty<EggSource34>();
}
value.Actual[count] = VoltTackle;
}
bool inherit = Breeding.GetCanInheritMoves(species);
MarkMovesForOrigin(value, egg, count, inherit, pi);
valid = RecurseMovesForOrigin(value, count - 1);
return value.Actual;
}
private static bool RecurseMovesForOrigin(BreedInfo<EggSource34> info, int start, EggSource34 type = Max - 1)
{
int i = start;
do
{
var unpeel = type - 1;
if (unpeel != 0 && RecurseMovesForOrigin(info, i, unpeel))
return true;
var permit = info.Possible[i];
if ((permit & (1 << (int)type)) == 0)
return false;
info.Actual[i] = type;
} while (--i >= 0);
return VerifyBaseMoves(info);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool VerifyBaseMoves(BreedInfo<EggSource34> info)
{
var count = 0;
foreach (var x in info.Actual)
{
if (x == Base)
count++;
else
break;
}
var moves = info.Moves;
if (count == -1)
return moves[moves.Length - 1] != 0;
var baseMoves = info.Learnset.GetBaseEggMoves(info.Level);
if (baseMoves.Length < count)
return false;
if (moves[moves.Length - 1] == 0 && count != baseMoves.Length)
return false;
for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--)
{
var move = moves[i];
var expect = baseMoves[b];
if (expect != move)
return false;
}
// A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it.
if (baseMoves.Length == count)
return true;
for (int i = count; i < info.Actual.Length; i++)
{
var isBase = (info.Possible[i] & (1 << (int)Base)) != 0;
if (!isBase)
continue;
var baseIndex = baseMoves.IndexOf(moves[i]);
var min = moves.Length - baseMoves.Length + baseIndex;
if (i <= min + count)
return false;
}
return true;
}
private static void MarkMovesForOrigin(BreedInfo<EggSource34> value, ICollection<int> eggMoves, int count, bool inheritLevelUp, PersonalInfo info)
{
var possible = value.Possible;
var learn = value.Learnset;
var baseEgg = value.Learnset.GetBaseEggMoves(value.Level);
var tm = info.TMHM;
var tmlist = Legal.TM_3.AsSpan(0, 50);
var moves = value.Moves;
for (int i = 0; i < count; i++)
{
var move = moves[i];
if (baseEgg.IndexOf(move) != -1)
possible[i] |= 1 << (int)Base;
if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1)
possible[i] |= 1 << (int)ParentLevelUp;
if (eggMoves.Contains(move))
possible[i] |= 1 << (int)FatherEgg;
var tmIndex = tmlist.IndexOf(move);
if (tmIndex != -1 && tm[tmIndex])
possible[i] |= 1 << (int)FatherTM;
}
}
}
}

View file

@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using static PKHeX.Core.EggSource34;
using static PKHeX.Core.GameVersion;
namespace PKHeX.Core
{
/// <summary>
/// Inheritance logic for Generation 4.
/// </summary>
/// <remarks>Refer to <see cref="EggSource34"/> for inheritance ordering.</remarks>
public static class MoveBreed4
{
private const int level = 1;
public static EggSource34[] Validate(int species, GameVersion version, int[] moves, out bool valid)
{
var count = Array.IndexOf(moves, 0);
if (count == 0)
{
valid = false; // empty moveset
return Array.Empty<EggSource34>();
}
if (count == -1)
count = moves.Length;
var learn = GameData.GetLearnsets(version);
var table = GameData.GetPersonal(version);
var learnset = learn[species];
var pi = table[species];
var egg = (version is HG or SS ? Legal.EggMovesHGSS : Legal.EggMovesDPPt)[species].Moves;
var value = new BreedInfo<EggSource34>(count, learnset, moves, level);
if (moves[count - 1] is (int)Move.VoltTackle)
{
if (--count == 0)
{
valid = false; // must have base moves; sanity check
return Array.Empty<EggSource34>();
}
value.Actual[count] = VoltTackle;
}
bool inherit = Breeding.GetCanInheritMoves(species);
MarkMovesForOrigin(value, egg, count, inherit, pi);
valid = RecurseMovesForOrigin(value, count - 1);
return value.Actual;
}
private static bool RecurseMovesForOrigin(BreedInfo<EggSource34> info, int start, EggSource34 type = Max - 1)
{
int i = start;
do
{
var unpeel = type - 1;
if (unpeel != 0 && RecurseMovesForOrigin(info, i, unpeel))
return true;
var permit = info.Possible[i];
if ((permit & (1 << (int)type)) == 0)
return false;
info.Actual[i] = type;
} while (--i >= 0);
return VerifyBaseMoves(info);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool VerifyBaseMoves(BreedInfo<EggSource34> info)
{
var count = 0;
foreach (var x in info.Actual)
{
if (x == Base)
count++;
else
break;
}
var moves = info.Moves;
if (count == -1)
return moves[moves.Length - 1] != 0;
var baseMoves = info.Learnset.GetBaseEggMoves(info.Level);
if (baseMoves.Length < count)
return false;
if (moves[moves.Length - 1] == 0 && count != baseMoves.Length)
return false;
for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--)
{
var move = moves[i];
var expect = baseMoves[b];
if (expect != move)
return false;
}
// A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it.
if (baseMoves.Length == count)
return true;
for (int i = count; i < info.Actual.Length; i++)
{
var isBase = (info.Possible[i] & (1 << (int)Base)) != 0;
if (!isBase)
continue;
var baseIndex = baseMoves.IndexOf(moves[i]);
var min = moves.Length - baseMoves.Length + baseIndex;
if (i <= min + count)
return false;
}
return true;
}
private static void MarkMovesForOrigin(BreedInfo<EggSource34> value, ICollection<int> eggMoves, int count, bool inheritLevelUp, PersonalInfo info)
{
var possible = value.Possible;
var learn = value.Learnset;
var baseEgg = value.Learnset.GetBaseEggMoves(value.Level);
var tm = info.TMHM;
var tmlist = Legal.TM_4.AsSpan(0, 92);
var moves = value.Moves;
for (int i = 0; i < count; i++)
{
var move = moves[i];
if (baseEgg.IndexOf(move) != -1)
possible[i] |= 1 << (int)Base;
if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1)
possible[i] |= 1 << (int)ParentLevelUp;
if (eggMoves.Contains(move))
possible[i] |= 1 << (int)FatherEgg;
var tmIndex = tmlist.IndexOf(move);
if (tmIndex != -1 && tm[tmIndex])
possible[i] |= 1 << (int)FatherTM;
}
}
}
}

View file

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using static PKHeX.Core.EggSource5;
namespace PKHeX.Core
{
/// <summary>
/// Inheritance logic for Generation 5.
/// </summary>
/// <remarks>Refer to <see cref="EggSource5"/> for inheritance ordering.</remarks>
public static class MoveBreed5
{
private const int level = 1;
public static EggSource5[] Validate(int species, GameVersion version, int[] moves, out bool valid)
{
var count = Array.IndexOf(moves, 0);
if (count == 0)
{
valid = false; // empty moveset
return Array.Empty<EggSource5>();
}
if (count == -1)
count = moves.Length;
var learn = GameData.GetLearnsets(version);
var table = GameData.GetPersonal(version);
var learnset = learn[species];
var pi = table[species];
var egg = Legal.EggMovesBW[species].Moves;
var value = new BreedInfo<EggSource5>(count, learnset, moves, level);
if (moves[count - 1] is (int)Move.VoltTackle)
{
if (--count == 0)
{
valid = false; // must have base moves; sanity check
return Array.Empty<EggSource5>();
}
value.Actual[count] = VoltTackle;
}
bool inherit = Breeding.GetCanInheritMoves(species);
MarkMovesForOrigin(value, egg, count, inherit, pi);
valid = RecurseMovesForOrigin(value, count - 1);
return value.Actual;
}
private static bool RecurseMovesForOrigin(BreedInfo<EggSource5> info, int start, EggSource5 type = Max - 1)
{
int i = start;
do
{
var unpeel = type - 1;
if (unpeel != 0 && RecurseMovesForOrigin(info, i, unpeel))
return true;
var permit = info.Possible[i];
if ((permit & (1 << (int)type)) == 0)
return false;
info.Actual[i] = type;
} while (--i >= 0);
return VerifyBaseMoves(info);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool VerifyBaseMoves(BreedInfo<EggSource5> info)
{
var count = 0;
foreach (var x in info.Actual)
{
if (x == Base)
count++;
else
break;
}
var moves = info.Moves;
if (count == -1)
return moves[moves.Length - 1] != 0;
var baseMoves = info.Learnset.GetBaseEggMoves(info.Level);
if (baseMoves.Length < count)
return false;
if (moves[moves.Length - 1] == 0 && count != baseMoves.Length)
return false;
for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--)
{
var move = moves[i];
var expect = baseMoves[b];
if (expect != move)
return false;
}
// A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it.
if (baseMoves.Length == count)
return true;
for (int i = count; i < info.Actual.Length; i++)
{
var isBase = (info.Possible[i] & (1 << (int)Base)) != 0;
if (!isBase)
continue;
var baseIndex = baseMoves.IndexOf(moves[i]);
var min = moves.Length - baseMoves.Length + baseIndex;
if (i <= min + count)
return false;
}
return true;
}
private static void MarkMovesForOrigin(BreedInfo<EggSource5> value, ICollection<int> eggMoves, int count, bool inheritLevelUp, PersonalInfo info)
{
var possible = value.Possible;
var learn = value.Learnset;
var baseEgg = value.Learnset.GetBaseEggMoves(value.Level);
var tm = info.TMHM;
var tmlist = Legal.TMHM_BW.AsSpan(0, 95); // actually 96, but TM96 is unavailable (Snarl - Lock Capsule)
var moves = value.Moves;
for (int i = 0; i < count; i++)
{
var move = moves[i];
if (baseEgg.IndexOf(move) != -1)
possible[i] |= 1 << (int)Base;
if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1)
possible[i] |= 1 << (int)ParentLevelUp;
if (eggMoves.Contains(move))
possible[i] |= 1 << (int)FatherEgg;
var tmIndex = tmlist.IndexOf(move);
if (tmIndex != -1 && tm[tmIndex])
possible[i] |= 1 << (int)FatherTM;
}
}
}
}

View file

@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using static PKHeX.Core.EggSource6;
namespace PKHeX.Core
{
/// <summary>
/// Inheritance logic for Generations 6+.
/// </summary>
/// <remarks>Refer to <see cref="EggSource6"/> for inheritance ordering.</remarks>
public static class MoveBreed6
{
public static EggSource6[] Validate(int generation, int species, int form, GameVersion version, int[] moves, out bool valid)
{
var count = Array.IndexOf(moves, 0);
if (count == 0)
{
valid = false; // empty moveset
return Array.Empty<EggSource6>();
}
if (count == -1)
count = moves.Length;
var learn = GameData.GetLearnsets(version);
var table = GameData.GetPersonal(version);
var index = table.GetFormIndex(species, form);
var learnset = learn[index];
var egg = MoveEgg.GetEggMoves(generation, species, form, version);
var value = new BreedInfo<EggSource6>(count, learnset, moves, 1);
if (moves[count - 1] is (int)Move.VoltTackle)
{
if (--count == 0)
{
valid = false; // must have base moves; sanity check
return Array.Empty<EggSource6>();
}
value.Actual[count] = VoltTackle;
}
bool inherit = Breeding.GetCanInheritMoves(species);
MarkMovesForOrigin(value, egg, count, inherit);
valid = RecurseMovesForOrigin(value, count - 1);
return value.Actual;
}
private static bool RecurseMovesForOrigin(BreedInfo<EggSource6> info, int start, EggSource6 type = Max - 1)
{
int i = start;
do
{
var unpeel = type - 1;
if (unpeel != 0 && RecurseMovesForOrigin(info, i, unpeel))
return true;
var permit = info.Possible[i];
if ((permit & (1 << (int)type)) == 0)
return false;
info.Actual[i] = type;
} while (--i >= 0);
return VerifyBaseMoves(info);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool VerifyBaseMoves(BreedInfo<EggSource6> info)
{
var count = 0;
foreach (var x in info.Actual)
{
if (x == Base)
count++;
else
break;
}
var moves = info.Moves;
if (count == -1)
return moves[moves.Length - 1] != 0;
var baseMoves = info.Learnset.GetBaseEggMoves(info.Level);
if (baseMoves.Length < count)
return false;
if (moves[moves.Length - 1] == 0 && count != baseMoves.Length)
return false;
for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--)
{
var move = moves[i];
var expect = baseMoves[b];
if (expect != move)
return false;
}
// A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it.
if (baseMoves.Length == count)
return true;
for (int i = count; i < info.Actual.Length; i++)
{
var isBase = (info.Possible[i] & (1 << (int)Base)) != 0;
if (!isBase)
continue;
var baseIndex = baseMoves.IndexOf(info.Moves[i]);
var min = info.Moves.Length - baseMoves.Length + baseIndex;
if (i <= min + count)
return false;
}
return true;
}
private static void MarkMovesForOrigin(BreedInfo<EggSource6> value, ICollection<int> eggMoves, int count, bool inheritLevelUp)
{
var possible = value.Possible;
var learn = value.Learnset;
var baseEgg = value.Learnset.GetBaseEggMoves(value.Level);
var moves = value.Moves;
for (int i = 0; i < count; i++)
{
var move = moves[i];
if (baseEgg.IndexOf(move) != -1)
possible[i] |= 1 << (int)Base;
if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1)
possible[i] |= 1 << (int)ParentLevelUp;
if (eggMoves.Contains(move))
possible[i] |= 1 << (int)ParentEgg;
}
}
}
}

View file

@ -8,6 +8,14 @@ namespace PKHeX.Core
public static Learnset[] GetLearnsets(GameVersion game) => Learnsets(game);
public static PersonalTable GetPersonal(GameVersion game) => Personal(game);
public static Learnset GetLearnset(GameVersion game, int species, int form)
{
var pt = Personal(game);
var index = pt.GetFormIndex(species, form);
var sets = Learnsets(game);
return sets[index];
}
private static Learnset[] Learnsets(GameVersion game) => game switch
{
RD or GN or BU or RB => Legal.LevelUpRB,

View file

@ -32,6 +32,7 @@ namespace PKHeX.Core
public static IReadOnlyList<string> MoveStrings = Util.GetMovesList(GameLanguage.DefaultLanguage);
public static IReadOnlyList<string> SpeciesStrings = Util.GetSpeciesList(GameLanguage.DefaultLanguage);
public static string GetMoveName(int move) => (uint)move >= MoveStrings.Count ? LegalityCheckStrings.L_AError : MoveStrings[move];
public static IEnumerable<string> GetMoveNames(IEnumerable<int> moves) => moves.Select(m => (uint)m >= MoveStrings.Count ? LegalityCheckStrings.L_AError : MoveStrings[m]);
public static void ChangeLocalizationStrings(IReadOnlyList<string> moves, IReadOnlyList<string> species)

View file

@ -14,6 +14,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>
<ItemGroup>
<None Remove="Resources\byte\eggmove_ao.pkl" />
<None Remove="Resources\byte\eggmove_bw.pkl" />

View file

@ -0,0 +1,81 @@
using System.Linq;
using FluentAssertions;
using FluentAssertions.Common;
using PKHeX.Core;
using Xunit;
using static PKHeX.Core.Move;
using static PKHeX.Core.Species;
using static PKHeX.Core.GameVersion;
namespace PKHeX.Tests.Legality
{
public class BreedTests
{
private static int[] GetMoves(Move[] moves)
{
var result = new int[4];
for (int i = 0; i < moves.Length; i++)
result[i] = (int) moves[i];
return result;
}
[Theory]
[InlineData(GD, Bulbasaur, 0, Tackle, Growl)]
[InlineData(SV, Igglybuff, 0, FeintAttack, Pound, Curse, ZapCannon)]
[InlineData( C, Igglybuff, 0, FeintAttack, Pound, Flamethrower, Sing)]
[InlineData( B, Heracross, 0, Megahorn, NightSlash, CloseCombat, StoneEdge)]
[InlineData( B, Heracross, 0, Bide, Megahorn, Counter, Reversal)]
[InlineData( B, Heracross, 0, HornAttack, Endure, Megahorn, TakeDown)]
[InlineData( B, Heracross, 0, Endure, Megahorn, FocusPunch, Feint)]
[InlineData( B, Heracross, 0, Megahorn, Reversal, Bulldoze, Fling)]
[InlineData( X, Growlithe, 0, Bite, Roar, FlareBlitz, MorningSun)]
[InlineData(OR, Growlithe, 0, MorningSun, IronTail, Crunch, HeatWave)]
[InlineData(OR, Dratini, 0, Wrap, Leer, DragonDance, ExtremeSpeed)]
[InlineData(OR, Rotom, 0, Astonish, ThunderWave, ThunderShock, ConfuseRay)]
public void VerifyBreed(GameVersion game, Species species, int form, params Move[] movelist)
{
var gen = game.GetGeneration();
var moves = GetMoves(movelist);
var test = MoveBreed.Process(gen, (int) species, form, game, moves, out var valid);
valid.Should().BeTrue();
var x = ((byte[])test);
if (gen != 2)
x.SequenceEqual(x.OrderBy(z => z)).Should().BeTrue();
else
x.SequenceEqual(x.OrderBy(z => z != (byte)EggSource2.Base)).Should().BeTrue();
}
[Theory]
[InlineData(C, Igglybuff, 0, Charm, DefenseCurl, Sing, Flamethrower)] // invalid push-out order
[InlineData(SH, Honedge, 0, FuryCutter, WideGuard, DestinyBond)] // insufficient move count
[InlineData(OR, Rotom, 0, Discharge, Charge, Trick, ConfuseRay)] // invalid push-out order
[InlineData(OR, Rotom, 0, ThunderWave, ThunderShock, ConfuseRay, Discharge)] // no inheriting levelup
public void CheckBad(GameVersion game, Species species, int form, params Move[] movelist)
{
var gen = game.GetGeneration();
var moves = GetMoves(movelist);
var test = MoveBreed.Process(gen, (int)species, form, game, moves);
test.Should().BeFalse();
}
[Theory]
[InlineData(GD, Bulbasaur, 0, Growl, Tackle)] // swap order, two base moves
[InlineData(UM, Charmander, 0, Ember, BellyDrum, Scratch, Growl)] // swap order, inherit + egg moves
public void CheckFix(GameVersion game, Species species, int form, params Move[] movelist)
{
var gen = game.GetGeneration();
var moves = GetMoves(movelist);
var test = MoveBreed.Process(gen, (int)species, form, game, moves, out var valid);
valid.Should().BeFalse();
var reorder = MoveBreed.GetExpectedMoves(gen, (int)species, form, game, moves, test);
// fixed order should be different now.
reorder.SequenceEqual(moves).Should().BeFalse();
// nonzero move count should be same
reorder.Count(z => z != 0).Should().IsSameOrEqualTo(moves.Count(z => z != 0));
}
}
}