Performance: Slightly reduce allocations in moveset validation (#3460)

* Reuses move parse result objects for each encounter parsed in a LegalityCheck attempt, instead of creating a new object.
* Ensures the objects are never-null, and makes cleanup easier.

Slightly adjusts some other parts of the moveset validation to reduce allocations.
This commit is contained in:
Kurt 2022-03-12 17:06:03 -08:00 committed by Kurt
parent 93255efcb8
commit 69fafcab83
21 changed files with 402 additions and 354 deletions

View file

@ -1,6 +1,6 @@
namespace PKHeX.Core
{
public enum SlotOrigin
public enum SlotOrigin : byte
{
Party = 0,
Box = 1,

View file

@ -36,6 +36,5 @@ namespace PKHeX.Core
}
private bool IsDeferredSafari3(bool IsSafariBall) => IsSafariBall != Locations.IsSafariZoneLocation3(Location);
}
}

View file

@ -1,4 +1,4 @@
using System.Linq;
using System;
using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core
@ -90,20 +90,20 @@ namespace PKHeX.Core
if (pkm.Format >= 6)
{
VerifyRelearnMoves.VerifyRelearn(pkm, info.EncounterOriginal, relearn);
if (relearn.Any(z => !z.Valid) && iterator.PeekIsNext())
if (!Array.TrueForAll(relearn, z => z.Valid) && iterator.PeekIsNext())
return false;
}
else
{
for (int i = 0; i < 4; i++)
relearn[i] = VerifyRelearnMoves.DummyValid;
foreach (var p in relearn)
VerifyRelearnMoves.DummyValid(p);
}
info.Moves = VerifyCurrentMoves.VerifyMoves(pkm, info);
if (info.Moves.Any(z => !z.Valid) && iterator.PeekIsNext())
VerifyCurrentMoves.VerifyMoves(pkm, info);
if (!Array.TrueForAll(info.Moves, z => z.Valid) && iterator.PeekIsNext())
return false;
if (info.Parse.Any(z => !z.Valid) && iterator.PeekIsNext())
if (!info.Parse.TrueForAll(z => z.Valid) && iterator.PeekIsNext())
return false;
var evo = EvolutionVerifier.VerifyEvolution(pkm, info);
@ -128,7 +128,7 @@ namespace PKHeX.Core
}
else if (pkm is PK1 pk1)
{
if (info.Moves.Any(z => z.Generation is not 1) && !PK1.IsCatchRateHeldItem(pk1.Catch_Rate))
if (!Array.TrueForAll(info.Moves, z => z.Generation is 1) && !PK1.IsCatchRateHeldItem(pk1.Catch_Rate))
return false;
}
@ -149,7 +149,7 @@ namespace PKHeX.Core
info.Parse.Add(new CheckResult(Severity.Invalid, hint, CheckIdentifier.Encounter));
VerifyRelearnMoves.VerifyRelearn(pkm, info.EncounterOriginal, info.Relearn);
info.Moves = VerifyCurrentMoves.VerifyMoves(pkm, info);
VerifyCurrentMoves.VerifyMoves(pkm, info);
}
private static string GetHintWhyNotFound(PKM pkm, int gen)

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core
@ -270,7 +269,7 @@ namespace PKHeX.Core
return new CheckResult(Severity.Invalid, LG4InvalidTileR45Surf, CheckIdentifier.Encounter);
break;
case 7:
if (s.EggLocation == Locations.Daycare5 && pkm.RelearnMoves.Any(m => m != 0)) // Eevee gift egg
if (s.EggLocation == Locations.Daycare5 && pkm.RelearnMove1 != 0) // Eevee gift egg
return new CheckResult(Severity.Invalid, LEncStaticRelearn, CheckIdentifier.RelearnMove); // not gift egg
break;
}
@ -286,13 +285,14 @@ namespace PKHeX.Core
private static CheckResult VerifyEncounterTrade(ISpeciesForm pkm, EncounterTrade trade)
{
if (trade.EvolveOnTrade && trade.Species == pkm.Species)
var species = pkm.Species;
if (trade.EvolveOnTrade && trade.Species == species)
{
// Pokemon that evolve on trade can not be in the phase evolution after the trade
// If the trade holds an everstone EvolveOnTrade will be false for the encounter
var species = ParseSettings.SpeciesStrings;
var unevolved = species[pkm.Species];
var evolved = species[pkm.Species + 1];
var names = ParseSettings.SpeciesStrings;
var evolved = names[species + 1];
var unevolved = names[species];
return new CheckResult(Severity.Invalid, string.Format(LEvoTradeReq, unevolved, evolved), CheckIdentifier.Encounter);
}
return new CheckResult(Severity.Valid, LEncTradeMatch, CheckIdentifier.Encounter);

View file

@ -21,51 +21,53 @@ namespace PKHeX.Core
/// <param name="pkm">Data to check</param>
/// <param name="info">Encounter conditions and legality info</param>
/// <returns>Validity of the <see cref="PKM.Moves"/></returns>
public static CheckMoveResult[] VerifyMoves(PKM pkm, LegalInfo info)
public static void VerifyMoves(PKM pkm, LegalInfo info)
{
var parse = info.Moves;
Array.ForEach(parse, p => p.Reset());
var currentMoves = pkm.Moves;
var res = ParseMovesForEncounters(pkm, info, currentMoves);
ParseMovesForEncounters(pkm, parse, info, currentMoves);
// Duplicate Moves Check
VerifyNoEmptyDuplicates(currentMoves, res);
VerifyNoEmptyDuplicates(currentMoves, parse);
if (currentMoves[0] == 0) // Can't have an empty move slot for the first move.
res[0] = new CheckMoveResult(res[0], Invalid, LMoveSourceEmpty, CurrentMove);
return res;
parse[0].FlagIllegal(LMoveSourceEmpty, CurrentMove);
}
private static CheckMoveResult[] ParseMovesForEncounters(PKM pkm, LegalInfo info, int[] currentMoves)
private static void ParseMovesForEncounters(PKM pkm, CheckMoveResult[] parse, 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
{
ParseMovesForSmeargle(pkm, parse, currentMoves, info); // Smeargle can have any moves except a few
return;
}
// gather valid moves for encounter species
info.EncounterMoves = new ValidEncounterMoves(pkm, info.EncounterMatch, info.EvoChainsAllGens);
var res = info.Generation < 6
? ParseMovesPre3DS(pkm, currentMoves, info)
: ParseMoves3DS(pkm, currentMoves, info);
if (res.All(x => x.Valid))
return res;
return res;
if (info.Generation >= 6)
ParseMoves3DS(pkm, parse, currentMoves, info);
else
ParseMovesPre3DS(pkm, parse, currentMoves, info);
}
private static CheckMoveResult[] ParseMovesForSmeargle(PKM pkm, IReadOnlyList<int> currentMoves, LegalInfo info)
private static void ParseMovesForSmeargle(PKM pkm, CheckMoveResult[] parse, int[] currentMoves, LegalInfo info)
{
if (!pkm.IsEgg)
return ParseMovesSketch(pkm, currentMoves);
{
ParseMovesSketch(pkm, parse, currentMoves);
return;
}
// can only know sketch as egg
var levelup = new int[info.EvoChainsAllGens.Length][];
levelup[pkm.Format] = new[] {166};
levelup[pkm.Format] = new[] { (int)Move.Sketch };
info.EncounterMoves = new ValidEncounterMoves(levelup);
var source = new MoveParseSource { CurrentMoves = currentMoves };
return ParseMoves(pkm, source, info);
ParseMoves(pkm, source, info, parse);
}
private static CheckMoveResult[] ParseMovesWasEggPreRelearn(PKM pkm, IReadOnlyList<int> currentMoves, LegalInfo info, EncounterEgg e)
private static void ParseMovesWasEggPreRelearn(PKM pkm, CheckMoveResult[] parse, IReadOnlyList<int> currentMoves, LegalInfo info, EncounterEgg e)
{
// Level up moves could not be inherited if Ditto is parent,
// that means genderless species and male only species (except Nidoran-M and Volbeat; they breed with Nidoran-F and Illumise) could not have level up moves as an egg
@ -94,51 +96,54 @@ namespace PKHeX.Core
EggLevelUpSource = LevelUp,
EggMoveSource = Egg,
};
return ParseMoves(pkm, source, info);
ParseMoves(pkm, source, info, parse);
}
private static CheckMoveResult[] ParseMovesSketch(PKM pkm, IReadOnlyList<int> currentMoves)
private static void ParseMovesSketch(PKM pkm, CheckMoveResult[] parse, ReadOnlySpan<int> currentMoves)
{
var res = new CheckMoveResult[4];
for (int i = 0; i < 4; i++)
for (int i = parse.Length - 1; i >= 0; i--)
{
var r = parse[i];
var move = currentMoves[i];
res[i] = Legal.IsValidSketch(move, pkm.Format)
? new CheckMoveResult(Sketch, pkm.Format, CurrentMove)
: new CheckMoveResult(Unknown, pkm.Format, Invalid, LMoveSourceInvalidSketch, CurrentMove);
if (move == 0)
r.Set(None, pkm.Format, Valid, LMoveSourceEmpty, CurrentMove);
else if (Legal.IsValidSketch(move, pkm.Format))
r.Set(Sketch, pkm.Format, Valid, L_AValid, CurrentMove);
else
r.Set(Unknown, pkm.Format, Invalid, LMoveSourceInvalidSketch, CurrentMove);
}
return res;
}
private static CheckMoveResult[] ParseMoves3DS(PKM pkm, IReadOnlyList<int> currentMoves, LegalInfo info)
private static void ParseMoves3DS(PKM pkm, CheckMoveResult[] parse, IReadOnlyList<int> currentMoves, LegalInfo info)
{
info.EncounterMoves.Relearn = info.Generation >= 6 ? pkm.RelearnMoves : Array.Empty<int>();
return info.EncounterMatch is IMoveset
? ParseMovesSpecialMoveset(pkm, currentMoves, info)
: ParseMovesRelearn(pkm, currentMoves, info);
if (info.EncounterMatch is IMoveset)
ParseMovesSpecialMoveset(pkm, currentMoves, info, parse);
else
ParseMovesRelearn(pkm, currentMoves, info, parse);
}
private static CheckMoveResult[] ParseMovesPre3DS(PKM pkm, int[] currentMoves, LegalInfo info)
private static void ParseMovesPre3DS(PKM pkm, CheckMoveResult[] parse, int[] currentMoves, LegalInfo info)
{
if (info.EncounterMatch is EncounterEgg e)
{
return pkm.IsEgg
? VerifyPreRelearnEggBase(currentMoves, e)
: ParseMovesWasEggPreRelearn(pkm, currentMoves, info, e);
if (pkm.IsEgg)
VerifyRelearnMoves.VerifyEggMoveset(e, parse, currentMoves, CurrentMove);
else
ParseMovesWasEggPreRelearn(pkm, parse, currentMoves, info, e);
return;
}
// Not all games have a re-learner. Initial moves may not fill out all 4 slots.
int gen = info.EncounterMatch.Generation;
if (gen == 1 || (gen == 2 && !AllowGen2MoveReminder(pkm)))
return ParseMovesGenGB(pkm, currentMoves, info);
ParseMovesGenGB(pkm, currentMoves, info, parse);
return ParseMovesSpecialMoveset(pkm, currentMoves, info);
ParseMovesSpecialMoveset(pkm, currentMoves, info, parse);
}
private static CheckMoveResult[] ParseMovesGenGB(PKM pkm, IReadOnlyList<int> currentMoves, LegalInfo info)
private static void ParseMovesGenGB(PKM pkm, IReadOnlyList<int> currentMoves, LegalInfo info, CheckMoveResult[] parse)
{
var res = new CheckMoveResult[4];
var enc = info.EncounterMatch;
var evos = info.EvoChainsAllGens[enc.Generation];
var level = evos.Count > 0 ? evos[^1].MinLevel : enc.LevelMin;
@ -149,7 +154,7 @@ namespace PKHeX.Core
{
var VerInitialMoves = enc is IMoveset {Moves.Count: not 0 } x ? (int[])x.Moves : MoveLevelUp.GetEncounterMoves(enc.Species, 0, level, ver);
if (VerInitialMoves.Intersect(InitialMoves).Count() == VerInitialMoves.Length)
return res;
return;
var source = new MoveParseSource
{
@ -157,14 +162,14 @@ namespace PKHeX.Core
SpecialSource = SpecialMoves,
Base = VerInitialMoves,
};
res = ParseMoves(pkm, source, info);
ParseMoves(pkm, source, info, parse);
// Must have a minimum count of moves, depending on the tradeback state.
if (pkm is PK1 pk1)
{
int count = GBRestrictions.GetRequiredMoveCount(pk1, source.CurrentMoves, info, source.Base);
if (count == 1)
return res;
return;
// Reverse for loop and break instead of 0..count continue -- early-breaks for the vast majority of cases.
// We already flag for empty interstitial moveslots.
@ -179,24 +184,23 @@ namespace PKHeX.Core
// Evolution canceling also leads to incorrect assumptions in the above used method, so just indicate them as fishy in that case.
// Not leveled up? Not possible to be missing the move slot.
var severity = enc.LevelMin == pkm.CurrentLevel ? Invalid : Fishy;
res[m] = new CheckMoveResult(None, pkm.Format, severity, LMoveSourceEmpty, CurrentMove);
parse[m].Set(None, pkm.Format, severity, LMoveSourceEmpty, CurrentMove);
}
}
if (res.All(r => r.Valid))
return res;
if (Array.TrueForAll(parse, z => z.Valid))
return;
InitialMoves = VerInitialMoves;
}
return res;
}
private static CheckMoveResult[] ParseMovesSpecialMoveset(PKM pkm, IReadOnlyList<int> currentMoves, LegalInfo info)
private static void ParseMovesSpecialMoveset(PKM pkm, IReadOnlyList<int> currentMoves, LegalInfo info, CheckMoveResult[] parse)
{
var source = new MoveParseSource
{
CurrentMoves = currentMoves,
SpecialSource = GetSpecialMoves(info.EncounterMatch),
};
return ParseMoves(pkm, source, info);
ParseMoves(pkm, source, info, parse);
}
private static IReadOnlyList<int> GetSpecialMoves(IEncounterTemplate enc)
@ -206,7 +210,7 @@ namespace PKHeX.Core
return Array.Empty<int>();
}
private static CheckMoveResult[] ParseMovesRelearn(PKM pkm, IReadOnlyList<int> currentMoves, LegalInfo info)
private static void ParseMovesRelearn(PKM pkm, IReadOnlyList<int> currentMoves, LegalInfo info, CheckMoveResult[] parse)
{
var source = new MoveParseSource
{
@ -217,22 +221,22 @@ namespace PKHeX.Core
if (info.EncounterMatch is EncounterEgg e)
source.EggMoveSource = MoveEgg.GetEggMoves(pkm.PersonalInfo, e.Species, e.Form, e.Version, e.Generation);
var res = ParseMoves(pkm, source, info);
ParseMoves(pkm, source, info, parse);
var relearn = pkm.RelearnMoves;
for (int i = 0; i < 4; i++)
for (int i = parse.Length - 1; i >= 0; i--)
{
if ((pkm.IsEgg || res[i].IsRelearn) && !relearn.Contains(currentMoves[i]))
res[i] = new CheckMoveResult(res[i], Invalid, string.Format(LMoveRelearnFMiss_0, res[i].Comment), res[i].Identifier);
}
var r = parse[i];
if (!r.IsRelearn && !pkm.IsEgg)
continue;
if (relearn.Contains(currentMoves[i]))
continue;
return res;
r.FlagIllegal(string.Format(LMoveRelearnFMiss_0, r.Comment));
}
}
private static CheckMoveResult[] ParseMoves(PKM pkm, MoveParseSource source, LegalInfo info)
private static void ParseMoves(PKM pkm, MoveParseSource source, LegalInfo info, CheckMoveResult[] parse)
{
var res = new CheckMoveResult[4];
bool AllParsed() => res.All(z => z != null);
// Special considerations!
const int NoMinGeneration = 0;
int minGeneration = NoMinGeneration;
@ -243,17 +247,18 @@ namespace PKHeX.Core
}
// Check empty moves and relearn moves before generation specific moves
for (int m = 0; m < 4; m++)
for (int m = parse.Length - 1; m >= 0; m--)
{
var move = source.CurrentMoves[m];
var r = parse[m];
if (move == 0)
res[m] = new CheckMoveResult(None, pkm.Format, Valid, LMoveSourceEmpty, CurrentMove);
r.Set(None, pkm.Format, Valid, LMoveSourceEmpty, CurrentMove);
else if (minGeneration == NoMinGeneration && info.EncounterMoves.Relearn.Contains(move))
res[m] = new CheckMoveResult(Relearn, info.Generation, Valid, LMoveSourceRelearn, CurrentMove);
r.Set(Relearn, info.Generation, Valid, LMoveSourceRelearn, CurrentMove);
}
if (AllParsed())
return res;
if (Array.TrueForAll(parse, z => z.IsParsed))
return;
// Encapsulate arguments to simplify method calls
var moveInfo = new LearnInfo(pkm, source);
@ -269,49 +274,52 @@ namespace PKHeX.Core
int lastgen = generations[^1];
foreach (var gen in generations)
{
ParseMovesByGeneration(pkm, res, gen, info, moveInfo, lastgen);
if (AllParsed())
return res;
ParseMovesByGeneration(pkm, parse, gen, info, moveInfo, lastgen);
if (Array.TrueForAll(parse, z => z.IsParsed))
return;
}
}
if (pkm.Species == (int)Species.Shedinja && info.Generation <= 4)
ParseShedinjaEvolveMoves(pkm, res, source.CurrentMoves, info.EvoChainsAllGens);
ParseShedinjaEvolveMoves(pkm, parse, source.CurrentMoves, info.EvoChainsAllGens);
// ReSharper disable once ConstantNullCoalescingCondition
for (int m = 0; m < 4; m++)
res[m] ??= new CheckMoveResult(Unknown, info.Generation, Invalid, LMoveSourceInvalid, CurrentMove);
return res;
foreach (var r in parse)
{
if (!r.IsParsed)
r.Set(Unknown, info.Generation, Invalid, LMoveSourceInvalid, CurrentMove);
}
}
private static void ParseMovesByGeneration(PKM pkm, CheckMoveResult[] res, int gen, LegalInfo info, LearnInfo learnInfo, int last)
private static void ParseMovesByGeneration(PKM pkm, CheckMoveResult[] parse, int gen, LegalInfo info, LearnInfo learnInfo, int last)
{
GetHMCompatibility(pkm, res, gen, learnInfo.Source.CurrentMoves, out bool[] HMLearned, out bool KnowDefogWhirlpool);
ParseMovesByGeneration(pkm, res, gen, info, learnInfo);
Span<bool> HMLearned = stackalloc bool[parse.Length];
bool KnowDefogWhirlpool = GetHMCompatibility(pkm, parse, gen, learnInfo.Source.CurrentMoves, HMLearned);
ParseMovesByGeneration(pkm, parse, gen, info, learnInfo);
if (gen == last)
ParseMovesByGenerationLast(pkm, res, learnInfo, info.EncounterMatch);
ParseMovesByGenerationLast(parse, learnInfo, info.EncounterMatch);
switch (gen)
{
case 1 or 2:
ParseMovesByGeneration12(pkm, res, learnInfo.Source.CurrentMoves, gen, info, learnInfo);
ParseMovesByGeneration12(pkm, parse, learnInfo.Source.CurrentMoves, gen, info, learnInfo);
break;
case 3 or 4:
if (pkm.Format > gen)
FlagIncompatibleTransferHMs45(res, learnInfo.Source.CurrentMoves, gen, HMLearned, KnowDefogWhirlpool);
FlagIncompatibleTransferHMs45(parse, learnInfo.Source.CurrentMoves, gen, HMLearned, KnowDefogWhirlpool);
break;
}
}
private static void ParseMovesByGeneration(PKM pkm, IList<CheckMoveResult> res, int gen, LegalInfo info, LearnInfo learnInfo)
private static void ParseMovesByGeneration(PKM pkm, CheckMoveResult[] parse, int gen, LegalInfo info, LearnInfo learnInfo)
{
var moves = learnInfo.Source.CurrentMoves;
bool native = gen == pkm.Format;
for (int m = 0; m < 4; m++)
for (int m = parse.Length - 1; m >= 0; m--)
{
if (IsCheckValid(res[m])) // already validated with another generation
var r = parse[m];
if (IsCheckValid(r)) // already validated with another generation
continue;
int move = moves[m];
if (move == 0)
@ -321,26 +329,26 @@ namespace PKHeX.Core
{
if (gen == 2 && !native && move > Legal.MaxMoveID_1 && pkm.VC1)
{
res[m] = new CheckMoveResult(Unknown, gen, Invalid, LMoveSourceInvalid, CurrentMove);
r.Set(Unknown, gen, Invalid, LMoveSourceInvalid, CurrentMove);
continue;
}
if (gen == 2 && learnInfo.Source.EggMoveSource.Contains(move))
res[m] = new CheckMoveResult(EggMove, gen, Valid, LMoveSourceEgg, CurrentMove);
r.Set(EggMove, gen, Valid, LMoveSourceEgg, CurrentMove);
else if (learnInfo.Source.Base.Contains(move))
res[m] = new CheckMoveResult(Initial, gen, Valid, native ? LMoveSourceDefault : string.Format(LMoveFDefault_0, gen), CurrentMove);
r.Set(Initial, gen, Valid, native ? LMoveSourceDefault : string.Format(LMoveFDefault_0, gen), CurrentMove);
}
if (info.EncounterMoves.LevelUpMoves[gen].Contains(move))
res[m] = new CheckMoveResult(LevelUp, gen, Valid, native ? LMoveSourceLevelUp : string.Format(LMoveFLevelUp_0, gen), CurrentMove);
r.Set(LevelUp, gen, Valid, native ? LMoveSourceLevelUp : string.Format(LMoveFLevelUp_0, gen), CurrentMove);
else if (info.EncounterMoves.TMHMMoves[gen].Contains(move))
res[m] = new CheckMoveResult(TMHM, gen, Valid, native ? LMoveSourceTMHM : string.Format(LMoveFTMHM_0, gen), CurrentMove);
r.Set(TMHM, gen, Valid, native ? LMoveSourceTMHM : string.Format(LMoveFTMHM_0, gen), CurrentMove);
else if (info.EncounterMoves.TutorMoves[gen].Contains(move))
res[m] = new CheckMoveResult(Tutor, gen, Valid, native ? LMoveSourceTutor : string.Format(LMoveFTutor_0, gen), CurrentMove);
r.Set(Tutor, gen, Valid, native ? LMoveSourceTutor : string.Format(LMoveFTutor_0, gen), CurrentMove);
else if (gen == info.Generation && learnInfo.Source.SpecialSource.Contains(move))
res[m] = new CheckMoveResult(Special, gen, Valid, LMoveSourceSpecial, CurrentMove);
r.Set(Special, gen, Valid, LMoveSourceSpecial, CurrentMove);
else if (gen >= 8 && MoveEgg.GetIsSharedEggMove(pkm, gen, move))
res[m] = new CheckMoveResult(Shared, gen, Valid, native ? LMoveSourceShared : string.Format(LMoveSourceSharedF, gen), CurrentMove);
r.Set(Shared, gen, Valid, native ? LMoveSourceShared : string.Format(LMoveSourceSharedF, gen), CurrentMove);
if (gen >= 3 || !IsCheckValid(res[m]))
if (gen >= 3 || !IsCheckValid(r))
continue;
// Gen1/Gen2 only below
@ -357,41 +365,41 @@ namespace PKHeX.Core
}
}
private static void ParseMovesByGeneration12(PKM pkm, CheckMoveResult[] res, IReadOnlyList<int> currentMoves, int gen, LegalInfo info, LearnInfo learnInfo)
private static void ParseMovesByGeneration12(PKM pkm, CheckMoveResult[] parse, IReadOnlyList<int> currentMoves, int gen, LegalInfo info, LearnInfo learnInfo)
{
// Mark the gen 1 exclusive moves as illegal because the pokemon also have Non tradeback egg moves.
if (learnInfo.MixedGen12NonTradeback)
{
foreach (int m in learnInfo.Gen1Moves)
res[m] = new CheckMoveResult(res[m], Invalid, LG1MoveExclusive, CurrentMove);
parse[m].FlagIllegal(LG1MoveExclusive, CurrentMove);
foreach (int m in learnInfo.Gen2PreevoMoves)
res[m] = new CheckMoveResult(res[m], Invalid, LG1TradebackPreEvoMove, CurrentMove);
parse[m].FlagIllegal(LG1TradebackPreEvoMove, CurrentMove);
}
if (gen == 1 && pkm.Format == 1 && !AllowGen1Tradeback)
{
ParseRedYellowIncompatibleMoves(pkm, res, currentMoves);
ParseEvolutionsIncompatibleMoves(pkm, res, currentMoves, info.EncounterMoves.TMHMMoves[1]);
ParseRedYellowIncompatibleMoves(pkm, parse, currentMoves);
ParseEvolutionsIncompatibleMoves(pkm, parse, currentMoves, info.EncounterMoves.TMHMMoves[1]);
}
}
private static void ParseMovesByGenerationLast(PKM pkm, CheckMoveResult[] res, LearnInfo learnInfo, IEncounterTemplate enc)
private static void ParseMovesByGenerationLast(CheckMoveResult[] parse, LearnInfo learnInfo, IEncounterTemplate enc)
{
int gen = enc.Generation;
ParseEggMovesInherited(pkm, res, gen, learnInfo);
ParseEggMoves(pkm, res, gen, learnInfo);
ParseEggMovesRemaining(pkm, res, learnInfo, enc);
ParseEggMovesInherited(parse, gen, learnInfo);
ParseEggMoves(parse, gen, learnInfo);
ParseEggMovesRemaining(parse, learnInfo, enc);
}
private static void ParseEggMovesInherited(PKM pkm, CheckMoveResult[] res, int gen, LearnInfo learnInfo)
private static void ParseEggMovesInherited(CheckMoveResult[] parse, int gen, LearnInfo learnInfo)
{
var moves = learnInfo.Source.CurrentMoves;
// Check higher-level moves after all the moves but just before egg moves to differentiate it from normal level up moves
// Also check if the base egg moves is a non tradeback move
for (int m = 0; m < 4; m++)
for (int m = parse.Length - 1; m >= 0; m--)
{
var r = res[m];
var r = parse[m];
if (IsCheckValid(r)) // already validated
{
if (gen == 2 && r.Generation != 1)
@ -406,12 +414,12 @@ namespace PKHeX.Core
if (learnInfo.IsGen2Pkm && learnInfo.Gen1Moves.Count != 0 && move > Legal.MaxMoveID_1)
{
res[m] = new CheckMoveResult(InheritLevelUp, gen, Invalid, LG1MoveTradeback, CurrentMove);
r.Set(InheritLevelUp, gen, Invalid, LG1MoveTradeback, CurrentMove);
learnInfo.MixedGen12NonTradeback = true;
}
else
{
res[m] = new CheckMoveResult(InheritLevelUp, gen, Valid, LMoveEggLevelUp, CurrentMove);
r.Set(InheritLevelUp, gen, Valid, LMoveEggLevelUp, CurrentMove);
}
learnInfo.LevelUpEggMoves.Add(m);
if (gen == 2 && learnInfo.Gen1Moves.Contains(m))
@ -419,14 +427,15 @@ namespace PKHeX.Core
}
}
private static void ParseEggMoves(PKM pkm, CheckMoveResult[] res, int gen, LearnInfo learnInfo)
private static void ParseEggMoves(CheckMoveResult[] parse, int gen, LearnInfo learnInfo)
{
var moves = learnInfo.Source.CurrentMoves;
// Check egg moves after all the generations and all the moves, every move that can't be learned in another source should have preference
// the moves that can only be learned from egg moves should in the future check if the move combinations can be breed in gens 2 to 5
for (int m = 0; m < 4; m++)
for (int m = parse.Length - 1; m >= 0; m--)
{
if (IsCheckValid(res[m]))
var r = parse[m];
if (IsCheckValid(r))
continue;
int move = moves[m];
if (move == 0)
@ -439,12 +448,12 @@ namespace PKHeX.Core
// without removing moves above MaxMoveID_1, egg moves above MaxMoveID_1 and gen 1 moves are incompatible
if (learnInfo.IsGen2Pkm && learnInfo.Gen1Moves.Count != 0 && move > Legal.MaxMoveID_1)
{
res[m] = new CheckMoveResult(EggMove, gen, Invalid, LG1MoveTradeback, CurrentMove);
r.Set(EggMove, gen, Invalid, LG1MoveTradeback, CurrentMove);
learnInfo.MixedGen12NonTradeback = true;
}
else
{
res[m] = new CheckMoveResult(EggMove, gen, Valid, LMoveSourceEgg, CurrentMove);
r.Set(EggMove, gen, Valid, LMoveSourceEgg, CurrentMove);
}
learnInfo.EggMovesLearned.Add(m);
@ -456,19 +465,19 @@ namespace PKHeX.Core
{
if (learnInfo.IsGen2Pkm && learnInfo.Gen1Moves.Count != 0 && move > Legal.MaxMoveID_1)
{
res[m] = new CheckMoveResult(SpecialEgg, gen, Invalid, LG1MoveTradeback, CurrentMove);
r.Set(SpecialEgg, gen, Invalid, LG1MoveTradeback, CurrentMove);
learnInfo.MixedGen12NonTradeback = true;
}
else
{
res[m] = new CheckMoveResult(SpecialEgg, gen, Valid, LMoveSourceEggEvent, CurrentMove);
r.Set(SpecialEgg, gen, Valid, LMoveSourceEggEvent, CurrentMove);
}
}
learnInfo.EventEggMoves.Add(m);
}
}
private static void ParseEggMovesRemaining(PKM pkm, CheckMoveResult[] res, LearnInfo learnInfo, IEncounterTemplate enc)
private static void ParseEggMovesRemaining(CheckMoveResult[] parse, LearnInfo learnInfo, IEncounterTemplate enc)
{
// A pokemon could have normal egg moves and regular egg moves
// Only if all regular egg moves are event egg moves or all event egg moves are regular egg moves
@ -483,14 +492,14 @@ namespace PKHeX.Core
if (isEvent)
{
if (!learnInfo.EggMovesLearned.Contains(m))
res[m] = new CheckMoveResult(res[m], Invalid, LMoveEggIncompatibleEvent, CurrentMove);
parse[m].FlagIllegal(LMoveEggIncompatibleEvent, CurrentMove);
}
else
{
if (learnInfo.EggMovesLearned.Contains(m))
res[m] = new CheckMoveResult(res[m], Invalid, LMoveEggIncompatible, CurrentMove);
parse[m].FlagIllegal(LMoveEggIncompatible, CurrentMove);
else if (learnInfo.LevelUpEggMoves.Contains(m))
res[m] = new CheckMoveResult(res[m], Invalid, LMoveEventEggLevelUp, CurrentMove);
parse[m].FlagIllegal(LMoveEventEggLevelUp, CurrentMove);
}
}
}
@ -501,22 +510,22 @@ namespace PKHeX.Core
foreach (int m in RegularEggMovesLearned)
{
if (learnInfo.EggMovesLearned.Contains(m))
res[m] = new CheckMoveResult(res[m], Invalid, gift ? LMoveEggMoveGift : LMoveEggInvalidEvent, CurrentMove);
parse[m].FlagIllegal(gift ? LMoveEggMoveGift : LMoveEggInvalidEvent, CurrentMove);
else if (learnInfo.LevelUpEggMoves.Contains(m))
res[m] = new CheckMoveResult(res[m], Invalid, gift ? LMoveEggInvalidEventLevelUpGift : LMoveEggInvalidEventLevelUp, CurrentMove);
parse[m].FlagIllegal(gift ? LMoveEggInvalidEventLevelUpGift : LMoveEggInvalidEventLevelUp, CurrentMove);
}
}
}
private static void ParseRedYellowIncompatibleMoves(PKM pkm, IList<CheckMoveResult> res, IReadOnlyList<int> currentMoves)
private static void ParseRedYellowIncompatibleMoves(PKM pkm, CheckMoveResult[] parse, IReadOnlyList<int> currentMoves)
{
var incompatible = GetIncompatibleRBYMoves(pkm, currentMoves);
if (incompatible.Count == 0)
return;
for (int m = 0; m < 4; m++)
for (int m = parse.Length - 1; m >= 0; m--)
{
if (incompatible.Contains(currentMoves[m]))
res[m] = new CheckMoveResult(res[m], Invalid, LG1MoveLearnSameLevel, CurrentMove);
parse[m].FlagIllegal(LG1MoveLearnSameLevel, CurrentMove);
}
}
@ -532,12 +541,12 @@ namespace PKHeX.Core
case (int)Species.Vaporeon when pkm.CurrentLevel < 47 && currentMoves.Contains((int)Move.AcidArmor):
{
var incompatible = new List<int>(3);
if (currentMoves.Contains((int)Move.Mist))
incompatible.Add((int)Move.Mist);
if (currentMoves.Contains((int)Move.Haze))
incompatible.Add((int)Move.Haze);
if (currentMoves.Contains((int)Move.Mist)) incompatible.Add((int)Move.Mist);
if (currentMoves.Contains((int)Move.Haze)) incompatible.Add((int)Move.Haze);
if (incompatible.Count != 0)
incompatible.Add((int)Move.AcidArmor);
else
return Array.Empty<int>();
return incompatible;
}
@ -550,7 +559,7 @@ namespace PKHeX.Core
}
}
private static void ParseEvolutionsIncompatibleMoves(PKM pkm, IList<CheckMoveResult> res, IReadOnlyList<int> moves, IReadOnlyList<int> tmhm)
private static void ParseEvolutionsIncompatibleMoves(PKM pkm, CheckMoveResult[] parse, IReadOnlyList<int> moves, IReadOnlyList<int> tmhm)
{
GBRestrictions.GetIncompatibleEvolutionMoves(pkm, moves, tmhm,
out var prevSpeciesID,
@ -562,18 +571,18 @@ namespace PKHeX.Core
var prev = SpeciesStrings[prevSpeciesID];
var curr = SpeciesStrings[pkm.Species];
for (int m = 0; m < 4; m++)
for (int m = parse.Length - 1; m >= 0; m--)
{
if (incompatCurr.Contains(moves[m]))
res[m] = new CheckMoveResult(res[m], Invalid, string.Format(LMoveEvoFLower, curr, prev), CurrentMove);
parse[m].FlagIllegal(string.Format(LMoveEvoFLower, curr, prev), CurrentMove);
if (incompatPrev.Contains(moves[m]))
res[m] = new CheckMoveResult(res[m], Invalid, string.Format(LMoveEvoFHigher, curr, prev), CurrentMove);
parse[m].FlagIllegal(string.Format(LMoveEvoFHigher, curr, prev), CurrentMove);
}
}
private static void ParseShedinjaEvolveMoves(PKM pkm, IList<CheckMoveResult> res, IReadOnlyList<int> currentMoves, IReadOnlyList<IReadOnlyList<EvoCriteria>> evos)
private static void ParseShedinjaEvolveMoves(PKM pkm, CheckMoveResult[] parse, IReadOnlyList<int> currentMoves, IReadOnlyList<IReadOnlyList<EvoCriteria>> evos)
{
var ShedinjaEvoMovesLearned = new List<int>();
int shedinjaEvoMoveIndex = 0;
var format = pkm.Format;
for (int gen = Math.Min(format, 4); gen >= 3; gen--)
{
@ -585,77 +594,88 @@ namespace PKHeX.Core
var maxLevel = pkm.CurrentLevel;
var ninjaskMoves = MoveList.GetShedinjaEvolveMoves(pkm, gen, maxLevel);
bool native = gen == format;
for (int m = 0; m < 4; m++)
for (int m = parse.Length - 1; m >= 0; m--)
{
if (IsCheckValid(res[m])) // already validated
var r = parse[m];
if (IsCheckValid(r)) // already validated
continue;
if (!ninjaskMoves.Contains(currentMoves[m]))
continue;
// Can't have more than one Ninjask exclusive move on Shedinja
if (shedinjaEvoMoveIndex != 0)
{
r.FlagIllegal(LMoveNincada, CurrentMove);
continue;
}
var msg = native ? LMoveNincadaEvo : string.Format(LMoveNincadaEvoF_0, gen);
res[m] = new CheckMoveResult(ShedinjaEvo, gen, Valid, msg, CurrentMove);
ShedinjaEvoMovesLearned.Add(m);
r.Set(ShedinjaEvo, gen, Valid, msg, CurrentMove);
shedinjaEvoMoveIndex = m;
}
}
if (ShedinjaEvoMovesLearned.Count == 0)
if (shedinjaEvoMoveIndex == 0)
return;
if (ShedinjaEvoMovesLearned.Count > 1)
{
// Can't have more than one Ninjask exclusive move on Shedinja
foreach (int m in ShedinjaEvoMovesLearned)
res[m] = new CheckMoveResult(res[m], Invalid, LMoveNincada, CurrentMove);
return;
}
// Double check that the Ninjask move level isn't less than any Nincada move level
int move = ShedinjaEvoMovesLearned[0];
int g = res[move].Generation;
int levelJ = MoveList.GetShedinjaMoveLevel((int)Species.Ninjask, currentMoves[move], g);
for (int m = 0; m < 4; m++)
{
if (m != move)
continue;
if (res[m].Source != LevelUp)
continue;
int levelS = MoveList.GetShedinjaMoveLevel((int)Species.Shedinja, currentMoves[m], res[m].Generation);
if (levelS > 0)
continue;
var r = parse[shedinjaEvoMoveIndex];
if (r.Source != LevelUp)
return;
int levelN = MoveList.GetShedinjaMoveLevel((int)Species.Nincada, currentMoves[m], res[m].Generation);
var move = currentMoves[shedinjaEvoMoveIndex];
int levelS = MoveList.GetShedinjaMoveLevel((int)Species.Shedinja, move, r.Generation);
if (levelS > 0)
return;
int levelN = MoveList.GetShedinjaMoveLevel((int)Species.Nincada, move, r.Generation);
int levelJ = MoveList.GetShedinjaMoveLevel((int)Species.Ninjask, move, r.Generation);
if (levelN > levelJ)
res[m] = new CheckMoveResult(res[m], Invalid, string.Format(LMoveEvoFHigher, SpeciesStrings[(int)Species.Nincada], SpeciesStrings[(int)Species.Ninjask]), CurrentMove);
r.FlagIllegal(string.Format(LMoveEvoFHigher, SpeciesStrings[(int)Species.Nincada], SpeciesStrings[(int)Species.Ninjask]), CurrentMove);
}
}
private static void GetHMCompatibility(PKM pkm, IReadOnlyList<CheckResult> res, int gen, IReadOnlyList<int> moves, out bool[] HMLearned, out bool KnowDefogWhirlpool)
private static bool GetHMCompatibility(PKM pkm, IReadOnlyList<CheckMoveResult> parse, int gen, IReadOnlyList<int> moves, Span<bool> HMLearned)
{
HMLearned = new bool[4];
// Check if pokemon knows HM moves from generation 3 and 4 but are not valid yet, that means it cant learn the HMs in future generations
if (gen == 4 && pkm.Format > 4)
{
IsHMSource(HMLearned, Legal.HM_4_RemovePokeTransfer);
KnowDefogWhirlpool = moves.Where((m, i) => IsDefogWhirl(m) && IsCheckInvalid(res[i])).Count() == 2;
return;
FlagIsHMSource(HMLearned, Legal.HM_4_RemovePokeTransfer);
return moves.Where((m, i) => IsDefogWhirl(m) && IsCheckInvalid(parse[i])).Count() == 2;
}
KnowDefogWhirlpool = false;
if (gen == 3 && pkm.Format > 3)
IsHMSource(HMLearned, Legal.HM_3);
FlagIsHMSource(HMLearned, Legal.HM_3);
return false;
void IsHMSource(IList<bool> flags, ICollection<int> source)
void FlagIsHMSource(Span<bool> flags, ICollection<int> source)
{
for (int i = 0; i < 4; i++)
flags[i] = IsCheckInvalid(res[i]) && source.Contains(moves[i]);
for (int i = parse.Count - 1; i >= 0; i--)
flags[i] = IsCheckInvalid(parse[i]) && source.Contains(moves[i]);
}
}
private static bool IsDefogWhirl(int move) => move is (int)Move.Defog or (int)Move.Whirlpool;
private static bool IsCheckInvalid(CheckResult? chk) => !(chk?.Valid ?? false);
private static bool IsCheckValid(CheckResult? chk) => chk?.Valid ?? false;
private static int GetDefogWhirlCount(IReadOnlyList<CheckMoveResult> parse, IReadOnlyList<int> moves)
{
int ctr = 0;
for (int i = moves.Count - 1; i >= 0; i--)
{
if (!IsDefogWhirl(moves[i]))
continue;
var r = parse[i];
if (!r.Valid || r.Generation >= 5)
continue;
ctr++;
}
return ctr;
}
private static void FlagIncompatibleTransferHMs45(CheckMoveResult[] res, IReadOnlyList<int> currentMoves, int gen, ReadOnlySpan<bool> HMLearned, bool KnowDefogWhirlpool)
private static bool IsCheckInvalid(CheckMoveResult chk) => chk.IsParsed && !chk.Valid;
private static bool IsCheckValid(CheckMoveResult chk) => chk.IsParsed && chk.Valid;
private static void FlagIncompatibleTransferHMs45(IReadOnlyList<CheckMoveResult> parse, IReadOnlyList<int> currentMoves, int gen, ReadOnlySpan<bool> HMLearned, bool KnowDefogWhirlpool)
{
// After all the moves from the generations 3 and 4,
// including egg moves if is the origin generation because some hidden moves are also special egg moves in gen 3
@ -663,36 +683,29 @@ namespace PKHeX.Core
// the hidden move was learned in gen 3 or 4 but was not removed when transfer to 4 or 5
if (KnowDefogWhirlpool)
{
int invalidCount = currentMoves.Where((m, i) => IsDefogWhirl(m) && IsCheckValid(res[i])).Count();
int invalidCount = GetDefogWhirlCount(parse, currentMoves);
if (invalidCount == 2) // can't know both at the same time
{
for (int i = 0; i < 4; i++) // flag both moves
for (int i = parse.Count - 1; i >= 0; i--) // flag both moves
{
if (IsDefogWhirl(currentMoves[i]))
res[i] = new CheckMoveResult(res[i], Invalid, LTransferMoveG4HM, CurrentMove);
parse[i].FlagIllegal(LTransferMoveG4HM, CurrentMove);
}
}
}
// Flag moves that are only legal when learned from a past-gen HM source
for (int i = 0; i < HMLearned.Length; i++)
for (int i = HMLearned.Length - 1; i >= 0; i--)
{
if (HMLearned[i] && IsCheckValid(res[i]))
res[i] = new CheckMoveResult(res[i], Invalid, string.Format(LTransferMoveHM, gen, gen + 1), CurrentMove);
if (HMLearned[i] && IsCheckValid(parse[i]))
parse[i].FlagIllegal(string.Format(LTransferMoveHM, gen, gen + 1), CurrentMove);
}
}
private static CheckMoveResult[] VerifyPreRelearnEggBase(int[] currentMoves, EncounterEgg e)
{
CheckMoveResult[] result = new CheckMoveResult[4];
_ = VerifyRelearnMoves.VerifyEggMoveset(e, result, currentMoves, CurrentMove);
return result;
}
private static void VerifyNoEmptyDuplicates(ReadOnlySpan<int> moves, CheckMoveResult[] res)
private static void VerifyNoEmptyDuplicates(ReadOnlySpan<int> moves, ReadOnlySpan<CheckMoveResult> parse)
{
bool emptySlot = false;
for (int i = 0; i < 4; i++)
for (int i = 0; i < parse.Length; i++)
{
var move = moves[i];
if (move == 0)
@ -704,34 +717,34 @@ namespace PKHeX.Core
// If an empty slot was noted for a prior move, flag the empty slots.
if (emptySlot)
{
FlagEmptySlotsBeforeIndex(moves, res, i);
FlagEmptySlotsBeforeIndex(moves, parse, i);
emptySlot = false;
continue;
}
// Check for same move in next move slots
FlagDuplicateMovesAfterIndex(moves, res, i, move);
FlagDuplicateMovesAfterIndex(moves, parse, i, move);
}
}
private static void FlagDuplicateMovesAfterIndex(ReadOnlySpan<int> moves, CheckMoveResult[] res, int index, int move)
private static void FlagDuplicateMovesAfterIndex(ReadOnlySpan<int> moves, ReadOnlySpan<CheckMoveResult> parse, int index, int move)
{
for (int i = index + 1; i < 4; i++)
for (int i = parse.Length - 1; i > index; i--)
{
if (moves[i] != move)
continue;
res[index] = new CheckMoveResult(res[index], Invalid, LMoveSourceDuplicate);
parse[index].FlagIllegal(LMoveSourceEmpty);
return;
}
}
private static void FlagEmptySlotsBeforeIndex(ReadOnlySpan<int> moves, CheckMoveResult[] res, int index)
private static void FlagEmptySlotsBeforeIndex(ReadOnlySpan<int> moves, ReadOnlySpan<CheckMoveResult> parse, int index)
{
for (int i = index - 1; i >= 0; i--)
{
if (moves[i] != 0)
return;
res[i] = new CheckMoveResult(res[i], Invalid, LMoveSourceEmpty);
parse[i].FlagIllegal(LMoveSourceEmpty);
}
}
}

View file

@ -10,10 +10,9 @@ namespace PKHeX.Core
/// </summary>
public static class VerifyRelearnMoves
{
internal static readonly CheckResult DummyValid = new(CheckIdentifier.RelearnMove);
private static readonly CheckResult DummyNone = new(Severity.Invalid, LMoveRelearnNone, CheckIdentifier.RelearnMove);
internal static void DummyValid(CheckMoveResult p) => p.Set(MoveSource.Relearn, 0, Severity.Valid, L_AValid, CheckIdentifier.RelearnMove);
public static CheckResult[] VerifyRelearn(PKM pkm, IEncounterTemplate enc, CheckResult[] result)
public static CheckMoveResult[] VerifyRelearn(PKM pkm, IEncounterTemplate enc, CheckMoveResult[] result)
{
if (ShouldNotHaveRelearnMoves(enc, pkm))
return VerifyRelearnNone(pkm, result);
@ -22,7 +21,7 @@ namespace PKHeX.Core
{
IRelearn s when s.Relearn.Count != 0 => VerifyRelearnSpecifiedMoveset(pkm, s.Relearn, result),
EncounterEgg e => VerifyEggMoveset(e, result, pkm.RelearnMoves),
EncounterSlot6AO z when pkm.RelearnMove1 != 0 && z.CanDexNav => VerifyRelearnDexNav(pkm, result, z),
EncounterSlot6AO {CanDexNav:true} z when pkm.RelearnMove1 != 0 => VerifyRelearnDexNav(pkm, result, z),
EncounterSlot8b {IsUnderground:true} u => VerifyRelearnUnderground(pkm, result, u),
_ => VerifyRelearnNone(pkm, result),
};
@ -30,63 +29,79 @@ namespace PKHeX.Core
public static bool ShouldNotHaveRelearnMoves(IGeneration enc, PKM pkm) => enc.Generation < 6 || pkm is IBattleVersion {BattleVersion: not 0};
private static CheckResult[] VerifyRelearnSpecifiedMoveset(PKM pkm, IReadOnlyList<int> required, CheckResult[] result)
private static CheckMoveResult[] VerifyRelearnSpecifiedMoveset(PKM pkm, IReadOnlyList<int> required, CheckMoveResult[] result)
{
result[3] = CheckResult(pkm.RelearnMove4, required[3]);
result[2] = CheckResult(pkm.RelearnMove3, required[2]);
result[1] = CheckResult(pkm.RelearnMove2, required[1]);
result[0] = CheckResult(pkm.RelearnMove1, required[0]);
CheckResult(pkm.RelearnMove4, required[3], result[3]);
CheckResult(pkm.RelearnMove3, required[2], result[2]);
CheckResult(pkm.RelearnMove2, required[1], result[1]);
CheckResult(pkm.RelearnMove1, required[0], result[0]);
return result;
static CheckResult CheckResult(int move, int require)
static void CheckResult(int move, int require, CheckMoveResult p)
{
if (move == require)
return DummyValid;
return new CheckResult(Severity.Invalid, string.Format(LMoveFExpect_0, MoveStrings[require]), CheckIdentifier.RelearnMove);
{
DummyValid(p);
return;
}
var c = string.Format(LMoveFExpect_0, MoveStrings[require]);
p.Set(MoveSource.Relearn, 0, Severity.Invalid, c, CheckIdentifier.RelearnMove);
}
}
private static CheckResult[] VerifyRelearnDexNav(PKM pkm, CheckResult[] result, EncounterSlot6AO slot)
private static void ParseExpectEmpty(CheckMoveResult p, int move)
{
if (move == 0)
DummyValid(p);
else
p.Set(MoveSource.Relearn, 0, Severity.Invalid, LMoveRelearnNone, CheckIdentifier.RelearnMove);
}
private static CheckMoveResult[] VerifyRelearnDexNav(PKM pkm, CheckMoveResult[] result, EncounterSlot6AO slot)
{
// All other relearn moves must be empty.
ParseExpectEmpty(result[3], pkm.RelearnMove4);
ParseExpectEmpty(result[2], pkm.RelearnMove3);
ParseExpectEmpty(result[1], pkm.RelearnMove2);
// DexNav Pokémon can have 1 random egg move as a relearn move.
result[0] = !slot.CanBeDexNavMove(pkm.RelearnMove1) // not found
? new CheckResult(Severity.Invalid, LMoveRelearnDexNav, CheckIdentifier.RelearnMove)
: DummyValid;
// All other relearn moves must be empty.
result[3] = pkm.RelearnMove4 == 0 ? DummyValid : DummyNone;
result[2] = pkm.RelearnMove3 == 0 ? DummyValid : DummyNone;
result[1] = pkm.RelearnMove2 == 0 ? DummyValid : DummyNone;
var p = result[0];
if (!slot.CanBeDexNavMove(pkm.RelearnMove1)) // not found
p.Set(MoveSource.Relearn, 6, Severity.Invalid, LMoveRelearnDexNav, CheckIdentifier.RelearnMove);
else
DummyValid(p);
return result;
}
private static CheckResult[] VerifyRelearnUnderground(PKM pkm, CheckResult[] result, EncounterSlot8b slot)
private static CheckMoveResult[] VerifyRelearnUnderground(PKM pkm, CheckMoveResult[] result, EncounterSlot8b slot)
{
// Underground Pokémon can have 1 random egg move as a relearn move.
result[0] = !slot.CanBeUndergroundMove(pkm.RelearnMove1) // not found
? new CheckResult(Severity.Invalid, LMoveRelearnUnderground, CheckIdentifier.RelearnMove)
: DummyValid;
// All other relearn moves must be empty.
result[3] = pkm.RelearnMove4 == 0 ? DummyValid : DummyNone;
result[2] = pkm.RelearnMove3 == 0 ? DummyValid : DummyNone;
result[1] = pkm.RelearnMove2 == 0 ? DummyValid : DummyNone;
ParseExpectEmpty(result[3], pkm.RelearnMove4);
ParseExpectEmpty(result[2], pkm.RelearnMove3);
ParseExpectEmpty(result[1], pkm.RelearnMove2);
// Underground Pokémon can have 1 random egg move as a relearn move.
var p = result[0];
if (!slot.CanBeUndergroundMove(pkm.RelearnMove1)) // not found
p.Set(MoveSource.Relearn, 0, Severity.Invalid, LMoveRelearnUnderground, CheckIdentifier.RelearnMove);
else
DummyValid(p);
return result;
}
private static CheckResult[] VerifyRelearnNone(PKM pkm, CheckResult[] result)
private static CheckMoveResult[] VerifyRelearnNone(PKM pkm, CheckMoveResult[] result)
{
// No relearn moves should be present.
result[3] = pkm.RelearnMove4 == 0 ? DummyValid : DummyNone;
result[2] = pkm.RelearnMove3 == 0 ? DummyValid : DummyNone;
result[1] = pkm.RelearnMove2 == 0 ? DummyValid : DummyNone;
result[0] = pkm.RelearnMove1 == 0 ? DummyValid : DummyNone;
ParseExpectEmpty(result[3], pkm.RelearnMove4);
ParseExpectEmpty(result[2], pkm.RelearnMove3);
ParseExpectEmpty(result[1], pkm.RelearnMove2);
ParseExpectEmpty(result[0], pkm.RelearnMove1);
return result;
}
internal static CheckResult[] VerifyEggMoveset(EncounterEgg e, CheckResult[] result, int[] moves, CheckIdentifier type = CheckIdentifier.RelearnMove)
internal static CheckMoveResult[] VerifyEggMoveset(EncounterEgg e, CheckMoveResult[] result, int[] moves, CheckIdentifier type = CheckIdentifier.RelearnMove)
{
int gen = e.Generation;
var origins = MoveBreed.Process(gen, e.Species, e.Form, e.Version, moves, out var valid);
@ -95,7 +110,7 @@ namespace PKHeX.Core
for (int i = 0; i < result.Length; i++)
{
var msg = EggSourceUtil.GetSource(origins, gen, i);
result[i] = new CheckMoveResult(MoveSource.EggMove, gen, Severity.Valid, msg, type);
result[i].Set(MoveSource.EggMove, gen, Severity.Valid, msg, type);
}
}
else
@ -106,23 +121,22 @@ namespace PKHeX.Core
{
var msg = EggSourceUtil.GetSource(origins, gen, i);
var expect = expected[i];
CheckMoveResult line;
var p = result[i];
if (moves[i] == expect)
{
line = new CheckMoveResult(MoveSource.EggMove, gen, Severity.Valid, msg, type);
p.Set(MoveSource.EggMove, gen, Severity.Valid, msg, type);
}
else
{
msg = string.Format(LMoveRelearnFExpect_0, MoveStrings[expect], msg);
line = new CheckMoveResult(MoveSource.EggMove, gen, Severity.Invalid, msg, type);
p.Set(MoveSource.EggMove, gen, Severity.Invalid, msg, type);
}
result[i] = line;
}
}
var dupe = IsAnyMoveDuplicate(moves);
if (dupe != NO_DUPE)
result[dupe] = new CheckMoveResult(MoveSource.EggMove, gen, Severity.Invalid, LMoveSourceDuplicate, type);
result[dupe].Set(MoveSource.EggMove, gen, Severity.Invalid, LMoveSourceDuplicate, type);
return result;
}

View file

@ -1,7 +1,7 @@
namespace PKHeX.Core
{
/// <summary> Identification flair for what properties a <see cref="CheckResult"/> pertains to </summary>
public enum CheckIdentifier
public enum CheckIdentifier : byte
{
/// <summary>
/// The <see cref="CheckResult"/> pertains to the <see cref="PKM.Moves"/>.

View file

@ -8,7 +8,7 @@ namespace PKHeX.Core
/// Severity == <see cref="Fishy"/> is yellow
/// Severity &lt;= <see cref="Invalid"/> is red
/// </remarks>
public enum Severity
public enum Severity : sbyte
{
/// <summary>
/// Cannot determine validity; not valid.

View file

@ -365,7 +365,7 @@ namespace PKHeX.Core
public static string LMoveRelearnUnderground { get; set; } = "Not an expected Underground egg move.";
public static string LMoveRelearnEgg { get; set; } = "Base Egg move.";
public static string LMoveRelearnEggMissing { get; set; } = "Base Egg move missing.";
public static string LMoveRelearnFExpect_0 { get; set; } = "Expected the following Relearn Moves: {0} ({1}";
public static string LMoveRelearnFExpect_0 { get; set; } = "Expected the following Relearn Moves: {0} ({1})";
public static string LMoveRelearnFMiss_0 { get; set; } = "Relearn Moves missing: {0}";
public static string LMoveRelearnInvalid { get; set; } = "Not an expected Relearnable move.";
public static string LMoveRelearnNone { get; set; } = "Expected no Relearn Move in slot.";

View file

@ -41,7 +41,7 @@ namespace PKHeX.Core
}
}
public static void AddRelearn(CheckResult[] relearn, List<string> lines, bool state)
public static void AddRelearn(CheckMoveResult[] relearn, List<string> lines, bool state)
{
for (int i = 0; i < relearn.Length; i++)
{

View file

@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static PKHeX.Core.LegalityAnalyzers;
using static PKHeX.Core.LegalityCheckStrings;
@ -101,9 +100,9 @@ namespace PKHeX.Core
AddLine(Severity.Invalid, LEncConditionBadSpecies, CheckIdentifier.GameOrigin);
GetParseMethod()();
Valid = Parse.All(chk => chk.Valid)
&& Info.Moves.All(m => m.Valid)
&& Info.Relearn.All(m => m.Valid);
Valid = Parse.TrueForAll(chk => chk.Valid)
&& Array.TrueForAll(Info.Moves, m => m.Valid)
&& Array.TrueForAll(Info.Relearn, m => m.Valid);
if (!Valid && IsPotentiallyMysteryGift(Info, pkm))
AddLine(Severity.Indeterminate, LFatefulGiftMissing, CheckIdentifier.Fateful);
@ -116,17 +115,17 @@ namespace PKHeX.Core
System.Diagnostics.Debug.WriteLine(e.Message);
Valid = false;
var moves = Info.Moves;
// Moves and Relearn arrays can potentially be empty on error.
// ReSharper disable once ConstantNullCoalescingCondition
for (int i = 0; i < moves.Length; i++)
moves[i] ??= new CheckMoveResult(MoveSource.None, pkm.Format, Severity.Indeterminate, L_AError, CheckIdentifier.CurrentMove);
var relearn = Info.Relearn;
// ReSharper disable once ConstantNullCoalescingCondition
for (int i = 0; i < relearn.Length; i++)
relearn[i] ??= new CheckResult(Severity.Indeterminate, L_AError, CheckIdentifier.RelearnMove);
foreach (var p in Info.Moves)
{
if (!p.IsParsed)
p.Set(MoveSource.Unknown, pkm.Format, Severity.Indeterminate, L_AError, CheckIdentifier.CurrentMove);
}
foreach (var p in Info.Relearn)
{
if (!p.IsParsed)
p.Set(MoveSource.Unknown, 0, Severity.Indeterminate, L_AError, CheckIdentifier.RelearnMove);
}
AddLine(Severity.Invalid, L_AError, CheckIdentifier.Misc);
}
#endif
@ -142,7 +141,7 @@ namespace PKHeX.Core
return false;
if (enc.Generation < 6)
return true;
if (info.Relearn.Any(chk => !chk.Valid))
if (Array.TrueForAll(info.Relearn, chk => !chk.Valid))
return true;
return false;
}

View file

@ -6,7 +6,7 @@ namespace PKHeX.Core
/// 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
internal readonly ref struct BreedInfo<T> where T : unmanaged
{
/// <summary> Indicates the analyzed source of each move. </summary>
public readonly T[] Actual;

View file

@ -1,49 +1,61 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Move specific <see cref="CheckResult"/> to contain in which Generation it was learned &amp; source.
/// </summary>
public sealed record CheckMoveResult : ICheckResult
{
/// <summary>
/// Move specific <see cref="CheckResult"/> to contain in which Generation it was learned &amp; source.
/// </summary>
public sealed class CheckMoveResult : CheckResult
public Severity Judgement { get; private set; }
public CheckIdentifier Identifier { get; private set; }
public string Comment { get; private set; } = string.Empty;
public bool Valid => Judgement >= Severity.Fishy;
public string Rating => Judgement.Description();
/// <summary> Method of learning the move. </summary>
public MoveSource Source { get; private set; }
/// <summary> Generation the move was learned in. </summary>
public int Generation { get; private set; }
/// <summary> Indicates if the source of the move was validated from the <see cref="PKM.RelearnMoves"/> </summary>
public bool IsRelearn => Source == MoveSource.Relearn || (Source == MoveSource.EggMove && Generation >= 6);
/// <summary> Indicates if the source of the move was validated as originating from an egg. </summary>
public bool IsEggSource => Source is MoveSource.EggMove or MoveSource.InheritLevelUp;
/// <summary> Indicates if the entry was parsed by the legality checker. Should never be true when the parent legality check is finalized. </summary>
internal bool IsParsed => Source is not MoveSource.NotParsed;
/// <summary> Sets <see cref="IsParsed"/> to false. </summary>
internal void Reset() => Source = MoveSource.NotParsed;
/// <summary> Checks if the Move should be present in a Relearn move pool (assuming Gen6+ origins). </summary>
/// <remarks>Invalid moves that can't be validated should be here, hence the inclusion.</remarks>
public bool ShouldBeInRelearnMoves() => Source != MoveSource.None && (!Valid || IsRelearn);
internal void Set(MoveSource m, int g, Severity s, string c, CheckIdentifier i)
{
/// <summary>
/// Method of learning the move.
/// </summary>
public readonly MoveSource Source;
/// <summary>
/// Generation the move was learned in.
/// </summary>
public readonly int Generation;
/// <summary>
/// Indicates if the source of the move was validated from the <see cref="PKM.RelearnMoves"/>
/// </summary>
public bool IsRelearn => Source == MoveSource.Relearn || (Source == MoveSource.EggMove && Generation >= 6);
/// <summary>
/// Indicates if the source of the move was validated as originating from an egg.
/// </summary>
public bool IsEggSource => Source is MoveSource.EggMove or MoveSource.InheritLevelUp;
/// <summary>
/// Checks if the Move should be present in a Relearn move pool (assuming Gen6+ origins).
/// </summary>
/// <remarks>Invalid moves that can't be validated should be here, hence the inclusion.</remarks>
public bool ShouldBeInRelearnMoves() => Source != MoveSource.None && (!Valid || IsRelearn);
internal CheckMoveResult(MoveSource m, int g, CheckIdentifier i) : base(i)
{
Source = m;
Generation = g;
}
internal CheckMoveResult(MoveSource m, int g, Severity s, string c, CheckIdentifier i) : base(s, c, i)
{
Source = m;
Generation = g;
}
internal CheckMoveResult(CheckMoveResult original, Severity s, string c, CheckIdentifier i) : this(original.Source, original.Generation, s, c, i) { }
internal CheckMoveResult(CheckMoveResult original, Severity s, string c) : this(original.Source, original.Generation, s, c, original.Identifier) { }
Judgement = s;
Comment = c;
Identifier = i;
Source = m;
Generation = g;
}
internal void FlagIllegal(string comment)
{
Judgement = Severity.Invalid;
Comment = comment;
}
internal void FlagIllegal(string comment, CheckIdentifier identifier)
{
Judgement = Severity.Invalid;
Comment = comment;
Identifier = identifier;
}
public string Format(string format) => string.Format(format, Rating, Comment);
public string Format(string format, int index) => string.Format(format, Rating, index, Comment);
}

View file

@ -1,34 +1,16 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Result of a Legality Check
/// </summary>
public sealed record CheckResult(Severity Judgement, string Comment, CheckIdentifier Identifier) : ICheckResult
{
/// <summary>
/// Result of a Legality Check
/// </summary>
public class CheckResult
{
public Severity Judgement { get; }
public CheckIdentifier Identifier { get; }
public string Comment { get; }
public bool Valid => Judgement >= Severity.Fishy;
public string Rating => Judgement.Description();
public bool Valid => Judgement >= Severity.Fishy;
public string Rating => Judgement.Description();
internal CheckResult(CheckIdentifier i) : this(Severity.Valid, LegalityCheckStrings.L_AValid, i) { }
public override string ToString() => $"{Identifier}: {Comment}";
internal CheckResult(CheckIdentifier i)
{
Judgement = Severity.Valid;
Comment = LegalityCheckStrings.L_AValid;
Identifier = i;
}
internal CheckResult(Severity s, string c, CheckIdentifier i)
{
Judgement = s;
Comment = c;
Identifier = i;
}
public string Format(string format) => string.Format(format, Rating, Comment);
public string Format(string format, int index) => string.Format(format, Rating, index, Comment);
}
public override string ToString() => $"{Identifier}: {Comment}";
public string Format(string format) => string.Format(format, Rating, Comment);
public string Format(string format, int index) => string.Format(format, Rating, index, Comment);
}

View file

@ -0,0 +1,11 @@
namespace PKHeX.Core;
public interface ICheckResult
{
Severity Judgement { get; }
CheckIdentifier Identifier { get; }
string Comment { get; }
bool Valid { get; }
string Rating { get; }
}

View file

@ -41,8 +41,17 @@ namespace PKHeX.Core
/// <summary>Top level Legality Check result list for the <see cref="EncounterMatch"/>.</summary>
internal readonly List<CheckResult> Parse;
public readonly CheckResult[] Relearn = new CheckResult[4];
public CheckMoveResult[] Moves { get; internal set; } = new CheckMoveResult[4];
private const int MoveCount = 4;
public readonly CheckMoveResult[] Relearn = GetArray();
public readonly CheckMoveResult[] Moves = GetArray();
private static CheckMoveResult[] GetArray()
{
var result = new CheckMoveResult[MoveCount];
for (int i = 0; i < result.Length; i++)
result[i] = new CheckMoveResult();
return result;
}
private static readonly ValidEncounterMoves NONE = new();
public ValidEncounterMoves EncounterMoves { get; internal set; } = NONE;

View file

@ -3,8 +3,9 @@
/// <summary>
/// Indicates the method of learning a move
/// </summary>
public enum MoveSource
public enum MoveSource : byte
{
NotParsed,
Unknown,
None,
Relearn,

View file

@ -74,7 +74,7 @@ public sealed class LegendsArceusVerifier : Verifier
{
// Expected move should never be empty, but just future-proof against any revisions.
var msg = expect[i] != 0 ? string.Format(LMoveFExpect_0, ParseSettings.MoveStrings[expect[i]]) : LMoveSourceEmpty;
data.Info.Moves[i] = new CheckMoveResult(data.Info.Moves[i], Severity.Invalid, msg, CheckIdentifier.CurrentMove);
data.Info.Moves[i].FlagIllegal(msg, CheckIdentifier.CurrentMove);
}
}

View file

@ -250,7 +250,7 @@ namespace PKHeX.Core
var chk = Moves[index];
if (chk.Generation == gen) // not obtained from a future gen
Moves[index] = new CheckMoveResult(chk.Source, chk.Generation, Severity.Invalid, LTransferMove, CheckIdentifier.CurrentMove);
Moves[index].FlagIllegal(LTransferMove, CheckIdentifier.CurrentMove);
}
}
}

View file

@ -432,6 +432,14 @@ namespace PKHeX.Core
public int MoveCount => Convert.ToInt32(Move1 != 0) + Convert.ToInt32(Move2 != 0) + Convert.ToInt32(Move3 != 0) + Convert.ToInt32(Move4 != 0);
public void GetMoves(Span<int> value)
{
value[3] = Move4;
value[2] = Move3;
value[1] = Move2;
value[0] = Move1;
}
public void SetMoves(IReadOnlyList<int> value)
{
Move1 = value.Count > 0 ? value[0] : 0;

View file

@ -89,7 +89,7 @@ namespace PKHeX.Tests.Legality
if (isValid)
{
var info = legality.Info;
var result = legality.Results.Concat(info.Moves).Concat(info.Relearn);
var result = legality.Results.Cast<ICheckResult>().Concat(info.Moves).Concat(info.Relearn);
// ReSharper disable once ConstantConditionalAccessQualifier
var invalid = result.Where(z => z?.Valid == false);
var msg = string.Join(Environment.NewLine, invalid.Select(z => z.Comment));