PKHeX/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs
Kurt 69fafcab83 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.
2022-03-12 17:39:00 -08:00

193 lines
8.8 KiB
C#

using System;
using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core
{
/// <summary>
/// Finds matching <see cref="IEncounterable"/> data and relevant <see cref="LegalInfo"/> for a <see cref="PKM"/>.
/// </summary>
public static class EncounterFinder
{
/// <summary>
/// Iterates through all possible encounters until a sufficient match is found
/// </summary>
/// <remarks>
/// The iterator lazily finds matching encounters, then verifies secondary checks to weed out any nonexact matches.
/// </remarks>
/// <param name="pkm">Source data to find a match for</param>
/// <param name="info">Object to store matched encounter info</param>
/// <returns>
/// Information containing the matched encounter and any parsed checks.
/// If no clean match is found, the last checked match is returned.
/// If no match is found, an invalid encounter object is returned.
/// </returns>
public static void FindVerifiedEncounter(PKM pkm, LegalInfo info)
{
var encounters = EncounterGenerator.GetEncounters(pkm, info);
using var encounter = new PeekEnumerator<IEncounterable>(encounters);
if (!encounter.PeekIsNext())
{
VerifyWithoutEncounter(pkm, info);
return;
}
var first = encounter.Current;
var EncounterValidator = EncounterVerifier.GetEncounterVerifierMethod(first.Generation);
while (encounter.MoveNext())
{
var enc = encounter.Current;
// Check for basic compatibility.
var e = EncounterValidator(pkm, enc);
if (!e.Valid && encounter.PeekIsNext())
continue;
// Looks like we might have a good enough match. Check if this is really a good match.
info.EncounterMatch = enc;
info.Parse.Add(e);
if (!VerifySecondaryChecks(pkm, info, encounter))
continue;
// Sanity Check -- Some secondary checks might not be as thorough as the partial-match leak-through checks done by the encounter.
if (enc is not IEncounterMatch mx)
break;
var match = mx.GetMatchRating(pkm);
if (match != EncounterMatchRating.PartialMatch)
break;
// Reaching here implies the encounter wasn't valid. Try stepping to the next encounter.
if (encounter.PeekIsNext())
continue;
// We ran out of possible encounters without finding a suitable match; add a message indicating that the encounter is not a complete match.
info.Parse.Add(new CheckResult(Severity.Invalid, LEncInvalid, CheckIdentifier.Encounter));
break;
}
if (!info.FrameMatches && info.EncounterMatch is EncounterSlot {Version: not GameVersion.CXD}) // if false, all valid RNG frame matches have already been consumed
info.Parse.Add(new CheckResult(ParseSettings.RNGFrameNotFound, LEncConditionBadRNGFrame, CheckIdentifier.PID)); // todo for further confirmation
if (!info.PIDIVMatches) // if false, all valid PIDIV matches have already been consumed
info.Parse.Add(new CheckResult(Severity.Invalid, LPIDTypeMismatch, CheckIdentifier.PID));
}
/// <summary>
/// Checks supplementary info to see if the encounter is still valid.
/// </summary>
/// <remarks>
/// When an encounter is initially validated, only encounter-related checks are performed.
/// By checking Moves, Evolution, and <see cref="PIDIV"/> data, a best match encounter can be found.
/// If the encounter is not valid, the method will not reject it unless another encounter is available to check.
/// </remarks>
/// <param name="pkm">Source data to check the match for</param>
/// <param name="info">Information containing the matched encounter</param>
/// <param name="iterator">Peekable iterator </param>
/// <returns>Indication whether or not the encounter passes secondary checks</returns>
private static bool VerifySecondaryChecks(PKM pkm, LegalInfo info, PeekEnumerator<IEncounterable> iterator)
{
var relearn = info.Relearn;
if (pkm.Format >= 6)
{
VerifyRelearnMoves.VerifyRelearn(pkm, info.EncounterOriginal, relearn);
if (!Array.TrueForAll(relearn, z => z.Valid) && iterator.PeekIsNext())
return false;
}
else
{
foreach (var p in relearn)
VerifyRelearnMoves.DummyValid(p);
}
VerifyCurrentMoves.VerifyMoves(pkm, info);
if (!Array.TrueForAll(info.Moves, z => z.Valid) && iterator.PeekIsNext())
return false;
if (!info.Parse.TrueForAll(z => z.Valid) && iterator.PeekIsNext())
return false;
var evo = EvolutionVerifier.VerifyEvolution(pkm, info);
if (!evo.Valid && iterator.PeekIsNext())
return false;
// Memories of Knowing a move which is later forgotten can be problematic with encounters that have special moves.
if (pkm is ITrainerMemories m)
{
if (m is IMemoryOT o && MemoryPermissions.IsMemoryOfKnownMove(o.OT_Memory))
{
var mem = MemoryVariableSet.Read(m, 0);
if (!MemoryPermissions.CanKnowMove(pkm, mem, info.EncounterMatch.Generation, info))
return false;
}
if (m is IMemoryHT h && MemoryPermissions.IsMemoryOfKnownMove(h.HT_Memory) && !pkm.HasMove(h.HT_TextVar))
{
var mem = MemoryVariableSet.Read(m, 1);
if (!MemoryPermissions.CanKnowMove(pkm, mem, pkm.Format, info))
return false;
}
}
else if (pkm is PK1 pk1)
{
if (!Array.TrueForAll(info.Moves, z => z.Generation is 1) && !PK1.IsCatchRateHeldItem(pk1.Catch_Rate))
return false;
}
info.Parse.Add(evo);
return true;
}
/// <summary>
/// Returns legality info for an unmatched encounter scenario, including a hint as to what the actual match could be.
/// </summary>
/// <param name="pkm">Source data to check the match for</param>
/// <param name="info">Information containing the unmatched encounter</param>
/// <returns>Updated information pertaining to the unmatched encounter</returns>
private static void VerifyWithoutEncounter(PKM pkm, LegalInfo info)
{
info.EncounterMatch = new EncounterInvalid(pkm);
string hint = GetHintWhyNotFound(pkm, info.EncounterMatch.Generation);
info.Parse.Add(new CheckResult(Severity.Invalid, hint, CheckIdentifier.Encounter));
VerifyRelearnMoves.VerifyRelearn(pkm, info.EncounterOriginal, info.Relearn);
VerifyCurrentMoves.VerifyMoves(pkm, info);
}
private static string GetHintWhyNotFound(PKM pkm, int gen)
{
if (WasGiftEgg(pkm, gen, pkm.Egg_Location))
return LEncGift;
if (WasEventEgg(pkm, gen))
return LEncGiftEggEvent;
if (WasEvent(pkm, gen))
return LEncGiftNotFound;
return LEncInvalid;
}
private static bool WasGiftEgg(PKM pkm, int gen, int loc) => !pkm.FatefulEncounter && gen switch
{
3 => pkm.IsEgg && pkm.Met_Location == 253, // Gift Egg, indistinguible from normal eggs after hatch
4 => Legal.GiftEggLocation4.Contains(loc) || (pkm.Format != 4 && (loc == Locations.Faraway4 && pkm.HGSS)),
5 => loc is Locations.Breeder5,
_ => loc is Locations.Breeder6,
};
private static bool WasEventEgg(PKM pkm, int gen) => gen switch
{
// Event Egg, indistinguible from normal eggs after hatch
// can't tell after transfer
3 => pkm.Format == 3 && pkm.IsEgg && pkm.Met_Location == 255,
// Manaphy was the only generation 4 released event egg
_ => pkm.Egg_Location is not 0 && pkm.FatefulEncounter,
};
private static bool WasEvent(PKM pkm, int gen) => pkm.FatefulEncounter || gen switch
{
3 => (pkm.Met_Location == 255 && pkm.Format == 3),
4 => (Locations.IsEventLocation4(pkm.Met_Location) && pkm.Format == 4),
>=5 => Locations.IsEventLocation5(pkm.Met_Location),
_ => false,
};
}
}