PKHeX/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs
Kurt e29cf2a903 Rework secondary check flow
Checks.cs initially started out small, but over the years it has grown
to handle multiple types of checks. With all these checks next to
eachother, it's hard to see the overall groups. Splitting them up
(potentially further?) allows for more focused maintenance &
understanding.

Not sure if I'm happy with the overall bandaids used (checks no longer
done within LegalityAnalysis so variable repointing is excessively
used), but I'm happier the way it is now compared to the huge Checks.cs
2018-06-23 22:00:01 -07:00

233 lines
10 KiB
C#

using System.Linq;
using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core
{
public class MiscVerifier : Verifier
{
protected override CheckIdentifier Identifier => CheckIdentifier.Misc;
public override void Verify(LegalityAnalysis data)
{
var pkm = data.pkm;
if (pkm.IsEgg)
{
VerifyMiscEggCommon(data);
if (pkm is IContestStats s && s.HasContestStats())
data.AddLine(GetInvalid(V320, CheckIdentifier.Egg));
if (pkm is PK4 pk4)
{
if (pk4.ShinyLeaf != 0)
data.AddLine(GetInvalid(V414, CheckIdentifier.Egg));
if (pk4.PokéathlonStat != 0)
data.AddLine(GetInvalid(V415, CheckIdentifier.Egg));
}
if (pkm is PK3)
{
if (pkm.Language != 1) // All Eggs are Japanese and flagged specially for localized string
data.AddLine(GetInvalid(string.Format(V5, LanguageID.Japanese, (LanguageID)pkm.Language), CheckIdentifier.Egg));
}
}
VerifyMiscFatefulEncounter(data);
}
public void VerifyMiscG1(LegalityAnalysis data)
{
var pkm = data.pkm;
if (pkm.IsEgg)
{
VerifyMiscEggCommon(data);
if (pkm.PKRS_Cured || pkm.PKRS_Infected)
data.AddLine(GetInvalid(V368, CheckIdentifier.Egg));
}
if (!(pkm is PK1 pk1))
return;
VerifyMiscG1Types(data, pk1);
VerifyMiscG1CatchRate(data, pk1);
}
private void VerifyMiscG1Types(LegalityAnalysis data, PK1 pk1)
{
var Type_A = pk1.Type_A;
var Type_B = pk1.Type_B;
if (pk1.Species == 137)
{
// Porygon can have any type combination of any generation 1 species because of the move Conversion,
// that change Porygon type to match the oponent types
var Type_A_Match = Legal.Types_Gen1.Any(t => t == Type_A);
var Type_B_Match = Legal.Types_Gen1.Any(t => t == Type_B);
if (!Type_A_Match)
data.AddLine(GetInvalid(V386));
if (!Type_B_Match)
data.AddLine(GetInvalid(V387));
if (Type_A_Match && Type_B_Match)
{
var TypesAB_Match = PersonalTable.RB.IsValidTypeCombination(Type_A, Type_B);
if (TypesAB_Match)
data.AddLine(GetValid(V391));
else
data.AddLine(GetInvalid(V388));
}
}
else // Types must match species types
{
var Type_A_Match = Type_A == PersonalTable.RB[pk1.Species].Type1;
var Type_B_Match = Type_B == PersonalTable.RB[pk1.Species].Type2;
var first = Type_A_Match ? GetValid(V392) : GetInvalid(V389);
var second = Type_B_Match ? GetValid(V393) : GetInvalid(V390);
data.AddLine(first);
data.AddLine(second);
}
}
private void VerifyMiscG1CatchRate(LegalityAnalysis data, PK1 pk1)
{
var EncounterMatch = data.EncounterMatch;
var catch_rate = pk1.Catch_Rate;
switch (pk1.TradebackStatus)
{
case TradebackType.Any:
case TradebackType.WasTradeback:
if (catch_rate == 0 || Legal.HeldItems_GSC.Any(h => h == catch_rate))
data.AddLine(GetValid(V394));
else if (pk1.TradebackStatus == TradebackType.WasTradeback)
data.AddLine(GetInvalid(V395));
else
goto case TradebackType.Gen1_NotTradeback;
break;
case TradebackType.Gen1_NotTradeback:
if ((EncounterMatch as EncounterStatic)?.Version == GameVersion.Stadium || EncounterMatch is EncounterTradeCatchRate)
// Encounters detected by the catch rate, cant be invalid if match this encounters
data.AddLine(GetValid(V398));
else if (pk1.Species == 149 && catch_rate == PersonalTable.Y[149].CatchRate || Legal.Species_NotAvailable_CatchRate.Contains(pk1.Species) && catch_rate == PersonalTable.RB[pk1.Species].CatchRate)
data.AddLine(GetInvalid(V396));
else if (!data.Info.EvoChainsAllGens[1].Any(e => catch_rate == PersonalTable.RB[e.Species].CatchRate || catch_rate == PersonalTable.Y[e.Species].CatchRate))
data.AddLine(GetInvalid(pk1.Gen1_NotTradeback ? V397 : V399));
else
data.AddLine(GetValid(V398));
break;
}
}
private void VerifyMiscFatefulEncounter(LegalityAnalysis data)
{
var pkm = data.pkm;
var EncounterMatch = data.EncounterMatch;
switch (EncounterMatch)
{
case WC3 w when w.Fateful:
if (w.IsEgg)
{
// Eggs hatched in RS clear the obedience flag!
if (pkm.Format != 3)
return; // possible hatched in either game, don't bother checking
if (pkm.Met_Location <= 087) // hatched in RS
break; // ensure fateful is not active
// else, ensure fateful is active (via below)
}
VerifyFatefulIngameActive(data);
VerifyWC3Shiny(data, w);
return;
case WC3 w:
if (w.Version == GameVersion.XD)
return; // Can have either state
VerifyWC3Shiny(data, w);
break;
case MysteryGift g when g.Format != 3: // WC3
VerifyFatefulMysteryGift(data, g);
return;
case EncounterStatic s when s.Fateful: // ingame fateful
case EncounterSlot _ when pkm.Version == 15: // ingame pokespot
case EncounterTrade t when t.Fateful:
VerifyFatefulIngameActive(data);
return;
}
if (pkm.FatefulEncounter)
data.AddLine(GetInvalid(V325, CheckIdentifier.Fateful));
}
private void VerifyMiscEggCommon(LegalityAnalysis data)
{
var pkm = data.pkm;
if (pkm.Move1_PPUps > 0 || pkm.Move2_PPUps > 0 || pkm.Move3_PPUps > 0 || pkm.Move4_PPUps > 0)
data.AddLine(GetInvalid(V319, CheckIdentifier.Egg));
if (pkm.Move1_PP != pkm.GetMovePP(pkm.Move1, 0) || pkm.Move2_PP != pkm.GetMovePP(pkm.Move2, 0)
|| pkm.Move3_PP != pkm.GetMovePP(pkm.Move3, 0) || pkm.Move4_PP != pkm.GetMovePP(pkm.Move4, 0))
data.AddLine(GetInvalid(V420, CheckIdentifier.Egg));
var EncounterMatch = data.EncounterMatch;
var HatchCycles = (EncounterMatch as EncounterStatic)?.EggCycles;
if (HatchCycles == 0 || HatchCycles == null)
HatchCycles = pkm.PersonalInfo.HatchCycles;
if (pkm.CurrentFriendship > HatchCycles)
data.AddLine(GetInvalid(V374, CheckIdentifier.Egg));
if (pkm.Format >= 6 && EncounterMatch is EncounterEgg && !pkm.Moves.SequenceEqual(pkm.RelearnMoves))
{
var moves = string.Join(", ", LegalityAnalysis.GetMoveNames(pkm.Moves));
var msg = string.Format(V343, moves);
data.AddLine(GetInvalid(msg, CheckIdentifier.Egg));
}
}
private void VerifyFatefulMysteryGift(LegalityAnalysis data, MysteryGift g)
{
var pkm = data.pkm;
if (g is PGF p && p.IsShiny)
{
var Info = data.Info;
Info.PIDIV = MethodFinder.Analyze(pkm);
if (Info.PIDIV.Type != PIDType.G5MGShiny && pkm.Egg_Location != 30003)
data.AddLine(GetInvalid(V411, CheckIdentifier.PID));
}
if (pkm.FatefulEncounter)
data.AddLine(GetValid(V321, CheckIdentifier.Fateful));
else
data.AddLine(GetInvalid(V322, CheckIdentifier.Fateful));
}
private void VerifyWC3Shiny(LegalityAnalysis data, WC3 g3)
{
// check for shiny locked gifts
if (!g3.Shiny.IsValid(data.pkm))
data.AddLine(GetInvalid(V409, CheckIdentifier.Fateful));
}
private void VerifyFatefulIngameActive(LegalityAnalysis data)
{
var pkm = data.pkm;
if (pkm.Version == 15 && pkm is XK3 xk3 && data.Info.WasXD)
{
// can't have fateful until traded away, which clears ShadowID
if (xk3.FatefulEncounter && xk3.ShadowID != 0 && data.EncounterMatch is EncounterStaticShadow)
data.AddLine(GetInvalid(V325, CheckIdentifier.Fateful));
return; // fateful is set when transferred away
}
if (pkm.FatefulEncounter)
data.AddLine(GetValid(V323, CheckIdentifier.Fateful));
else
data.AddLine(GetInvalid(V324, CheckIdentifier.Fateful));
}
public void VerifyVersionEvolution(LegalityAnalysis data)
{
var pkm = data.pkm;
if (pkm.Format < 7 || data.EncounterMatch.Species == pkm.Species)
return;
// No point using the evolution tree. Just handle certain species.
switch (pkm.Species)
{
case 745 when (pkm.AltForm == 0 && Moon()) || (pkm.AltForm == 1 && Sun()): // Lycanroc
case 791 when Moon(): // Solgaleo
case 792 when Sun(): // Lunala
bool Sun() => pkm.Version == (int)GameVersion.SN || pkm.Version == (int)GameVersion.US;
bool Moon() => pkm.Version == (int)GameVersion.MN || pkm.Version == (int)GameVersion.UM;
if (pkm.IsUntraded)
data.AddLine(GetInvalid(V328, CheckIdentifier.Evolution));
break;
}
}
}
}