mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-10 06:34:19 +00:00
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:
parent
ecf1f361fe
commit
0626b0c29b
21 changed files with 1176 additions and 232 deletions
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
35
PKHeX.Core/Legality/Moves/Breeding/BreedInfo.cs
Normal file
35
PKHeX.Core/Legality/Moves/Breeding/BreedInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
111
PKHeX.Core/Legality/Moves/Breeding/EggSource.cs
Normal file
111
PKHeX.Core/Legality/Moves/Breeding/EggSource.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
116
PKHeX.Core/Legality/Moves/Breeding/MoveBreed.cs
Normal file
116
PKHeX.Core/Legality/Moves/Breeding/MoveBreed.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
155
PKHeX.Core/Legality/Moves/Breeding/MoveBreed2.cs
Normal file
155
PKHeX.Core/Legality/Moves/Breeding/MoveBreed2.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
147
PKHeX.Core/Legality/Moves/Breeding/MoveBreed3.cs
Normal file
147
PKHeX.Core/Legality/Moves/Breeding/MoveBreed3.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
148
PKHeX.Core/Legality/Moves/Breeding/MoveBreed4.cs
Normal file
148
PKHeX.Core/Legality/Moves/Breeding/MoveBreed4.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
147
PKHeX.Core/Legality/Moves/Breeding/MoveBreed5.cs
Normal file
147
PKHeX.Core/Legality/Moves/Breeding/MoveBreed5.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
139
PKHeX.Core/Legality/Moves/Breeding/MoveBreed6.cs
Normal file
139
PKHeX.Core/Legality/Moves/Breeding/MoveBreed6.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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" />
|
||||
|
|
81
Tests/PKHeX.Core.Tests/Legality/BreedTests.cs
Normal file
81
Tests/PKHeX.Core.Tests/Legality/BreedTests.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue