mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-05-20 12:48:30 -05:00
* 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.
193 lines
8.8 KiB
C#
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,
|
|
};
|
|
}
|
|
}
|