PKHeX/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs
Kurt f632aedd15
Encounter Templates: Searching and Creating (#3955)
We implement simple state machine iterators to iterate through every split type encounter array, and more finely control the path we iterate through. And, by using generics, we can have the compiler generate optimized code to avoid virtual calls.

In addition to this, we shift away from the big-5 encounter types and not inherit from an abstract class. This allows for creating a PK* of a specific type and directly writing properties (no virtual calls). Plus we can now fine-tune each encounter type to call specific code, and not have to worry about future game encounter types bothering the generation routines.
2023-08-12 16:01:16 -07:00

141 lines
5.2 KiB
C#

using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core;
/// <summary>
/// Verifies the <see cref="PKM.CurrentLevel"/>.
/// </summary>
public sealed class LevelVerifier : Verifier
{
protected override CheckIdentifier Identifier => CheckIdentifier.Level;
public override void Verify(LegalityAnalysis data)
{
var pk = data.Entity;
var enc = data.EncounterOriginal;
if (enc is MysteryGift gift)
{
if (!IsMetLevelMatchEncounter(gift, pk))
{
data.AddLine(GetInvalid(LLevelMetGift));
return;
}
if (gift.Level > pk.CurrentLevel)
{
data.AddLine(GetInvalid(LLevelMetGiftFail));
return;
}
}
if (pk.IsEgg)
{
int elvl = enc.LevelMin;
if (elvl != pk.CurrentLevel)
{
data.AddLine(GetInvalid(string.Format(LEggFMetLevel_0, elvl)));
return;
}
var reqEXP = enc is EncounterStatic2 { DizzyPunchEgg: true }
? 125 // Gen2 Dizzy Punch gifts always have 125 EXP, even if it's more than the Lv5 exp required.
: Experience.GetEXP(elvl, pk.PersonalInfo.EXPGrowth);
if (reqEXP != pk.EXP)
data.AddLine(GetInvalid(LEggEXP));
return;
}
int lvl = pk.CurrentLevel;
if (lvl >= 100)
{
var expect = Experience.GetEXP(100, pk.PersonalInfo.EXPGrowth);
if (pk.EXP != expect)
data.AddLine(GetInvalid(LLevelEXPTooHigh));
}
if (lvl < pk.Met_Level)
data.AddLine(GetInvalid(LLevelMetBelow));
else if (!enc.IsWithinEncounterRange(pk) && lvl != 100 && pk.EXP == Experience.GetEXP(lvl, pk.PersonalInfo.EXPGrowth))
data.AddLine(Get(LLevelEXPThreshold, Severity.Fishy));
else
data.AddLine(GetValid(LLevelMetSane));
}
private static bool IsMetLevelMatchEncounter(MysteryGift gift, PKM pk)
{
if (gift.Level == pk.Met_Level)
return true;
if (!pk.HasOriginalMetLocation)
return true;
return gift switch
{
WC3 wc3 when wc3.Met_Level == pk.Met_Level || wc3.IsEgg => true,
WC7 wc7 when wc7.MetLevel == pk.Met_Level => true,
PGT { IsManaphyEgg: true } when pk.Met_Level == 0 => true,
_ => false,
};
}
public void VerifyG1(LegalityAnalysis data)
{
var pk = data.Entity;
var enc = data.EncounterMatch;
if (pk.IsEgg)
{
const int elvl = 5;
if (elvl != pk.CurrentLevel)
data.AddLine(GetInvalid(string.Format(LEggFMetLevel_0, elvl)));
return;
}
if (pk.Met_Location != 0) // crystal
{
int lvl = pk.CurrentLevel;
if (lvl < pk.Met_Level)
data.AddLine(GetInvalid(LLevelMetBelow));
}
if (IsTradeEvolutionRequired(data, enc))
{
// Pokemon has been traded illegally between games without evolving.
// Trade evolution species IDs for Gen1 are sequential dex numbers.
var species = enc.Species;
var evolved = ParseSettings.SpeciesStrings[species + 1];
var unevolved = ParseSettings.SpeciesStrings[species];
data.AddLine(GetInvalid(string.Format(LEvoTradeReqOutsider, unevolved, evolved)));
}
}
/// <summary>
/// Checks if a Gen1 trade evolution must have occurred.
/// </summary>
private static bool IsTradeEvolutionRequired(LegalityAnalysis data, IEncounterTemplate enc)
{
// There is no way to prevent a Gen1 trade evolution, as held items (Everstone) did not exist.
// Machoke, Graveler, Haunter and Kadabra captured in the second phase evolution, excluding in-game trades, are already checked
var pk = data.Entity;
var species = pk.Species;
// This check is only applicable if it's a trade evolution that has not been evolved.
if (enc.Species != species)
return false;
if (!GBRestrictions.IsTradeEvolution1(enc.Species))
return false;
// Context check is only applicable to gen1/2; transferring to Gen2 is a trade.
// Stadium 2 can transfer across game/generation boundaries without initiating a trade.
// Ignore this check if the environment's loaded trainer is not from Gen1/2 or is from GB Era.
if (ParseSettings.ActiveTrainer.Generation >= 3 || ParseSettings.AllowGBStadium2)
return false;
var moves = data.Info.Moves;
// Gen2 stuff can be traded between Gen2 games holding an Everstone, assuming it hasn't been transferred to Gen1 for special moves.
if (enc.Generation == 2)
return MoveInfo.IsAnyFromGeneration(1, moves);
// Gen1 stuff can only be un-evolved if it was never traded from the OT.
if (MoveInfo.IsAnyFromGeneration(2, moves))
return true; // traded to Gen2 for special moves
if (pk.Format != 1)
return true; // traded to Gen2 (current state)
return !ParseSettings.IsFromActiveTrainer(pk); // not with OT
}
}