mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-10-02 15:36:51 -05:00
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
This commit is contained in:
@@ -12,19 +12,19 @@ namespace PKHeX.Core
|
||||
/// </summary>
|
||||
public partial class LegalityAnalysis
|
||||
{
|
||||
private readonly PKM pkm;
|
||||
internal readonly PKM pkm;
|
||||
private readonly bool Error;
|
||||
private readonly List<CheckResult> Parse = new List<CheckResult>();
|
||||
|
||||
private IEncounterable EncounterOriginalGB;
|
||||
private IEncounterable EncounterMatch => Info.EncounterMatch;
|
||||
public IEncounterable EncounterMatch => Info.EncounterMatch;
|
||||
public IEncounterable EncounterOriginal => EncounterOriginalGB ?? EncounterMatch;
|
||||
|
||||
private CheckResult Encounter, History;
|
||||
public CheckResult Encounter;
|
||||
|
||||
public readonly bool Parsed;
|
||||
public readonly bool Valid;
|
||||
private readonly PersonalInfo PersonalInfo;
|
||||
public readonly PersonalInfo PersonalInfo;
|
||||
public LegalInfo Info { get; private set; }
|
||||
public string Report(bool verbose = false) => verbose ? GetVerboseLegalityReport() : GetLegalityReport();
|
||||
private IEnumerable<int> AllSuggestedMoves
|
||||
@@ -134,11 +134,10 @@ private void ParsePK1()
|
||||
UpdateInfo();
|
||||
UpdateTypeInfo();
|
||||
VerifyNickname();
|
||||
VerifyDVs();
|
||||
VerifyEVs();
|
||||
VerifyLevelG1();
|
||||
VerifyOTG1();
|
||||
VerifyMiscG1();
|
||||
Trainer.VerifyOTG1(this);
|
||||
Misc.VerifyMiscG1(this);
|
||||
if (pkm.Format == 2)
|
||||
VerifyItem();
|
||||
}
|
||||
@@ -147,7 +146,7 @@ private void ParsePK3()
|
||||
UpdateInfo();
|
||||
UpdateChecks();
|
||||
if (pkm.Format > 3)
|
||||
VerifyTransferLegalityG3();
|
||||
Transfer.VerifyTransferLegalityG3(this);
|
||||
|
||||
if (pkm.Version == (int)GameVersion.CXD)
|
||||
VerifyCXD();
|
||||
@@ -160,7 +159,7 @@ private void ParsePK4()
|
||||
UpdateInfo();
|
||||
UpdateChecks();
|
||||
if (pkm.Format > 4)
|
||||
VerifyTransferLegalityG4();
|
||||
Transfer.VerifyTransferLegalityG4(this);
|
||||
}
|
||||
private void ParsePK5()
|
||||
{
|
||||
@@ -180,8 +179,8 @@ private void ParsePK7()
|
||||
UpdateChecks();
|
||||
}
|
||||
|
||||
private void AddLine(Severity s, string c, CheckIdentifier i) => AddLine(new CheckResult(s, c, i));
|
||||
private void AddLine(CheckResult chk) => Parse.Add(chk);
|
||||
public void AddLine(Severity s, string c, CheckIdentifier i) => AddLine(new CheckResult(s, c, i));
|
||||
public void AddLine(CheckResult chk) => Parse.Add(chk);
|
||||
|
||||
private void UpdateVCTransferInfo()
|
||||
{
|
||||
@@ -192,7 +191,7 @@ private void UpdateVCTransferInfo()
|
||||
if (!(Info.EncounterMatch is EncounterStatic s) || !EncounterStaticGenerator.IsVCStaticTransferEncounterValid(pkm, s))
|
||||
{ AddLine(Severity.Invalid, V80, CheckIdentifier.Encounter); return; }
|
||||
|
||||
foreach (var z in VerifyVCEncounter(pkm, EncounterOriginalGB, s, Info.Moves))
|
||||
foreach (var z in TransferVerifier.VerifyVCEncounter(pkm, EncounterOriginalGB, s, Info.Moves))
|
||||
AddLine(z);
|
||||
}
|
||||
private void UpdateInfo()
|
||||
@@ -249,14 +248,13 @@ private void UpdateChecks()
|
||||
VerifyEncounterType(); // Gen 6->7 transfer deletes encounter type data
|
||||
if (pkm.Format >= 6)
|
||||
{
|
||||
History = VerifyHistory();
|
||||
AddLine(History);
|
||||
VerifyOTMemory();
|
||||
VerifyHTMemory();
|
||||
VerifyHistory();
|
||||
Memory.VerifyOTMemory(this);
|
||||
Memory.VerifyHTMemory(this);
|
||||
VerifyHyperTraining();
|
||||
VerifyMedals();
|
||||
VerifyConsoleRegion();
|
||||
VerifyVersionEvolution();
|
||||
Misc.VerifyVersionEvolution(this);
|
||||
}
|
||||
}
|
||||
private string GetLegalityReport()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
/// <summary> Identification flair for what properties a <see cref="CheckResult"/> pertains to </summary>
|
||||
internal enum CheckIdentifier
|
||||
public enum CheckIdentifier
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="CheckResult"/> pertains to the <see cref="PKM.Moves"/>.
|
||||
@@ -35,11 +35,11 @@ internal enum CheckIdentifier
|
||||
/// The <see cref="CheckResult"/> pertains to the <see cref="PKM.Gender"/>.
|
||||
/// </summary>
|
||||
Gender,
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// The <see cref="CheckResult"/> pertains to the <see cref="PKM.EVs"/>.
|
||||
/// </summary>
|
||||
EVs,
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// The <see cref="CheckResult"/> pertains to the <see cref="PKM.Language"/>.
|
||||
/// </summary>
|
||||
Language,
|
||||
@@ -91,7 +91,7 @@ internal enum CheckIdentifier
|
||||
/// The <see cref="CheckResult"/> pertains to the <see cref="PKM"/> ribbon data.
|
||||
/// </summary>
|
||||
Ribbon,
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// The <see cref="CheckResult"/> pertains to the <see cref="PKM"/> super training data.
|
||||
/// </summary>
|
||||
Training,
|
||||
@@ -112,5 +112,9 @@ internal enum CheckIdentifier
|
||||
/// <remarks>This is used for parsing checks to ensure the <see cref="PKM"/> didn't debut on a future <see cref="PKM.GenNumber"/></remarks>
|
||||
/// </summary>
|
||||
GameOrigin,
|
||||
/// <summary>
|
||||
/// The CheckResult pertains to the <see cref="PKM.HeldItem"/>.
|
||||
/// </summary>
|
||||
HeldItem
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
internal static class RibbonVerifier
|
||||
internal class RibbonVerifier : Verifier
|
||||
{
|
||||
internal static List<string> GetIncorrectRibbons(PKM pkm, object encounterContent, int gen)
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Ribbon;
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
var pkm = data.pkm;
|
||||
var Info = data.Info;
|
||||
// Check Unobtainable Ribbons
|
||||
var encounterContent = (EncounterMatch as MysteryGift)?.Content ?? EncounterMatch;
|
||||
if (pkm.IsEgg)
|
||||
{
|
||||
if (GetIncorrectRibbonsEgg(pkm, encounterContent))
|
||||
data.AddLine(GetInvalid(V603));
|
||||
return;
|
||||
}
|
||||
|
||||
int gen = Info.Generation < 3 ? 7 : Info.Generation; // Flag VC (Gen1/2) ribbons using Gen7 origin rules.
|
||||
var result = GetIncorrectRibbons(pkm, encounterContent, gen);
|
||||
if (result.Count != 0)
|
||||
{
|
||||
var msg = string.Join(Environment.NewLine, result.Where(s => !string.IsNullOrEmpty(s)));
|
||||
data.AddLine(GetInvalid(msg));
|
||||
}
|
||||
else
|
||||
data.AddLine(GetValid(V602));
|
||||
}
|
||||
|
||||
private static List<string> GetIncorrectRibbons(PKM pkm, object encounterContent, int gen)
|
||||
{
|
||||
List<string> missingRibbons = new List<string>();
|
||||
List<string> invalidRibbons = new List<string>();
|
||||
@@ -21,7 +48,7 @@ internal static List<string> GetIncorrectRibbons(PKM pkm, object encounterConten
|
||||
result.Add(string.Format(V601, string.Join(", ", invalidRibbons.Select(z => z.Replace("Ribbon", "")))));
|
||||
return result;
|
||||
}
|
||||
internal static bool GetIncorrectRibbonsEgg(PKM pkm, object encounterContent)
|
||||
private static bool GetIncorrectRibbonsEgg(PKM pkm, object encounterContent)
|
||||
{
|
||||
var RibbonNames = ReflectUtil.GetPropertiesStartWithPrefix(pkm.GetType(), "Ribbon");
|
||||
if (encounterContent is IRibbonSetEvent3 event3)
|
||||
|
||||
246
PKHeX.Core/Legality/Verifiers/AbilityVerifier.cs
Normal file
246
PKHeX.Core/Legality/Verifiers/AbilityVerifier.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class AbilityVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Ability;
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var Info = data.Info;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
|
||||
int[] abilities = pkm.PersonalInfo.Abilities;
|
||||
int abilval = Array.IndexOf(abilities, pkm.Ability);
|
||||
if (abilval < 0)
|
||||
{
|
||||
data.AddLine(GetInvalid(V107));
|
||||
return;
|
||||
}
|
||||
|
||||
bool? AbilityUnchanged = true;
|
||||
// 3 states flag: true for unchanged, false for changed, null for uncertain/allowing PID mismatch
|
||||
// if true, check encounter ability
|
||||
// if true or false, check PID/AbilityNumber
|
||||
if (3 <= pkm.Format && pkm.Format <= 5 && abilities[0] != abilities[1]) // 3-5 and have 2 distinct ability now
|
||||
AbilityUnchanged = VerifyAbilityPreCapsule(data, abilities, abilval);
|
||||
|
||||
if (EncounterMatch is PCD d)
|
||||
{
|
||||
if (VerifyAbilityPCD(data, d, AbilityUnchanged, abilities))
|
||||
return;
|
||||
}
|
||||
else // Check Ability Mismatches
|
||||
{
|
||||
int? EncounterAbility = (EncounterMatch as EncounterStatic)?.Ability ??
|
||||
(EncounterMatch as EncounterTrade)?.Ability ??
|
||||
(EncounterMatch as EncounterLink)?.Ability;
|
||||
|
||||
if (EncounterAbility != null && VerifySetAbility(data, EncounterAbility, AbilityUnchanged, abilities, abilval))
|
||||
return; // result added via VerifySetAbility
|
||||
}
|
||||
switch (Info.Generation)
|
||||
{
|
||||
case 5: VerifyAbility5(data, abilities); break;
|
||||
case 6: VerifyAbility6(data, abilities); break;
|
||||
case 7: VerifyAbility7(data, abilities); break;
|
||||
}
|
||||
|
||||
if (3 <= Info.Generation && Info.Generation <= 4 && pkm.AbilityNumber == 4)
|
||||
data.AddLine(GetInvalid(V112));
|
||||
else if (AbilityUnchanged != null && abilities[pkm.AbilityNumber >> 1] != pkm.Ability)
|
||||
data.AddLine(GetInvalid(pkm.Format < 6 ? V113 : V114));
|
||||
else
|
||||
data.AddLine(GetValid(V115));
|
||||
}
|
||||
private bool VerifyAbilityPCD(LegalityAnalysis data, PCD pcd, bool? abilityUnchanged, int[] abilities)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (pcd.Species == pkm.Species && pkm.Ability == pcd.Gift.PK.Ability) // Edge case (PID ability gift mismatch)
|
||||
data.AddLine(GetValid(V115));
|
||||
else if (pkm.Format >= 6 && abilities[0] == abilities[1] && pkm.AbilityNumber == 1)
|
||||
data.AddLine(GetValid(V115)); // gen3-5 transfer with same ability -> 1st ability that matches
|
||||
else if (pkm.Format >= 6 && abilities[0] != abilities[1] && pkm.AbilityNumber < 4) // Ability Capsule can change between 1/2
|
||||
data.AddLine(GetValid(V109));
|
||||
else
|
||||
{
|
||||
if (!(abilityUnchanged ?? false))
|
||||
return false;
|
||||
data.AddLine(GetInvalid(V223));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private bool VerifySetAbility(LegalityAnalysis data, int? EncounterAbility, bool? AbilityUnchanged, int[] abilities, int abilval)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (pkm.AbilityNumber == 4 && EncounterAbility != 4)
|
||||
{
|
||||
data.AddLine(GetInvalid(V108));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data.EncounterMatch is EncounterTradePID z)
|
||||
{
|
||||
if (z.Species != pkm.Species)
|
||||
return false; // Must match PID ability, handle via default check path
|
||||
if (EncounterAbility == 1 << abilval)
|
||||
{
|
||||
data.AddLine(GetValid(V115));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(AbilityUnchanged ?? false) || EncounterAbility == 0 || pkm.AbilityNumber == EncounterAbility)
|
||||
return false;
|
||||
if (IsAbilityCapsuleModified(pkm, abilities, EncounterAbility))
|
||||
data.AddLine(GetValid(V109));
|
||||
else
|
||||
data.AddLine(GetInvalid(V223));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ability Capsule can change between 1/2
|
||||
private static bool IsAbilityCapsuleModified(PKM pkm, IReadOnlyList<int> abilities, int? EncounterAbility)
|
||||
{
|
||||
if (pkm.Format < 6)
|
||||
return false; // Ability Capsule does not exist
|
||||
if (abilities[0] == abilities[1])
|
||||
return false; // Cannot alter ability index if it is the same as the other ability.
|
||||
if (pkm.AbilityNumber == 4)
|
||||
return false; // Cannot alter to hidden ability.
|
||||
if (EncounterAbility == 4)
|
||||
return false; // Cannot alter from hidden ability.
|
||||
return true;
|
||||
}
|
||||
private bool? VerifyAbilityPreCapsule(LegalityAnalysis data, int[] abilities, int abilval)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
// CXD pokemon could have any ability without maching PID
|
||||
if (pkm.Version == (int)GameVersion.CXD && pkm.Format == 3)
|
||||
return null;
|
||||
|
||||
// gen3 native or gen4/5 origin
|
||||
if (pkm.Format == 3 || !pkm.InhabitedGeneration(3))
|
||||
return true;
|
||||
|
||||
// Evovled in gen4/5
|
||||
if (pkm.Species > Legal.MaxSpeciesID_3)
|
||||
return false;
|
||||
|
||||
// gen3Species will be zero for pokemon with illegal gen 3 encounters, like Infernape with gen 3 "origin"
|
||||
var gen3Species = data.Info.EvoChainsAllGens[3].FirstOrDefault()?.Species ?? 0;
|
||||
if (gen3Species == 0)
|
||||
return true;
|
||||
|
||||
// Fall through when gen3 pkm transferred to gen4/5
|
||||
return VerifyAbilityGen3Transfer(data, abilities, abilval, gen3Species);
|
||||
}
|
||||
private bool? VerifyAbilityGen3Transfer(LegalityAnalysis data, int[] abilities, int abilval, int Species_g3)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var Info = data.Info;
|
||||
var pers = (PersonalInfoG3)PersonalTable.E[Species_g3];
|
||||
if (pers.Ability1 != pers.Ability2) // Excluding Colosseum/XD, a gen3 pkm must match PID if it has 2 unique abilities
|
||||
return pkm.Version != (int)GameVersion.CXD;
|
||||
|
||||
int Species_g4 = Info.EvoChainsAllGens[4].FirstOrDefault()?.Species ?? 0;
|
||||
int Species_g5 = pkm.Format == 5 ? Info.EvoChainsAllGens[5].FirstOrDefault()?.Species ?? 0 : 0;
|
||||
if (Math.Max(Species_g5, Species_g4) > Species_g3) // it has evolved in either gen 4 or gen 5; the ability must match PID
|
||||
return false;
|
||||
|
||||
var Evolutions_g45 = Math.Max(Info.EvoChainsAllGens[4].Count, pkm.Format == 5 ? Info.EvoChainsAllGens[5].Count : 0);
|
||||
if (Evolutions_g45 > 1)
|
||||
{
|
||||
// Evolutions_g45 > 1 and Species_g45 = Species_g3 with means both options, evolve in gen 4-5 or not evolve, are possible
|
||||
if (pkm.Ability == pers.Ability1)
|
||||
// It could evolve in gen 4-5 an have generation 3 only ability
|
||||
// that means it have not actually evolved in gen 4-5, ability do not need to match PID
|
||||
return null;
|
||||
if (pkm.Ability == abilities[1])
|
||||
// It could evolve in gen4-5 an have generation 4 second ability
|
||||
// that means it have actually evolved in gen 4-5, ability must match PID
|
||||
return false;
|
||||
}
|
||||
// Evolutions_g45 == 1 means it have not evolved in gen 4-5 games,
|
||||
// ability do not need to match PID, but only generation 3 ability is allowed
|
||||
if (pkm.Ability != pers.Ability1)
|
||||
// Not evolved in gen4-5 but do not have generation 3 only ability
|
||||
data.AddLine(GetInvalid(V373));
|
||||
return null;
|
||||
}
|
||||
private void VerifyAbility5(LegalityAnalysis data, int[] abilities)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
switch (data.EncounterMatch)
|
||||
{
|
||||
case PGF g:
|
||||
VerifyAbilityMG456(data, abilities, g.AbilityType);
|
||||
break;
|
||||
|
||||
case EncounterSlot w:
|
||||
// Hidden Abilities for Wild Encounters are only available at a Hidden Grotto
|
||||
bool grotto = w.Type == SlotType.HiddenGrotto;
|
||||
if (pkm.AbilityNumber == 4 ^ grotto)
|
||||
data.AddLine(GetInvalid(grotto ? V217 : V108));
|
||||
break;
|
||||
|
||||
case EncounterEgg e when pkm.AbilityNumber == 4:
|
||||
// Hidden Abilities for some are unbreedable (male only distribution)
|
||||
if (Legal.MixedGenderBreeding.Contains(e.Species) || Legal.FixedGenderFromBiGender.Contains(e.Species))
|
||||
break; // from female
|
||||
if ((pkm.PersonalInfo.Gender & 0xFF) == 0 || Legal.Ban_BreedHidden.Contains(e.Species))
|
||||
data.AddLine(GetInvalid(V112));
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void VerifyAbility6(LegalityAnalysis data, int[] abilities)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
if (EncounterMatch is EncounterSlot slot && pkm.AbilityNumber == 4)
|
||||
{
|
||||
bool valid = slot.Permissions.DexNav || slot.Type == SlotType.FriendSafari || slot.Type == SlotType.Horde;
|
||||
if (!valid)
|
||||
data.AddLine(GetInvalid(V300));
|
||||
}
|
||||
else if (EncounterMatch is WC6 g)
|
||||
VerifyAbilityMG456(data, abilities, g.AbilityType);
|
||||
else if (Legal.Ban_NoHidden6.Contains(pkm.SpecForm) && pkm.AbilityNumber == 4)
|
||||
data.AddLine(GetInvalid(V112));
|
||||
}
|
||||
private void VerifyAbility7(LegalityAnalysis data, int[] abilities)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
if (EncounterMatch is EncounterSlot slot && pkm.AbilityNumber == 4)
|
||||
{
|
||||
bool valid = slot.Type == SlotType.SOS;
|
||||
if (!valid)
|
||||
data.AddLine(GetInvalid(V111));
|
||||
}
|
||||
else if (EncounterMatch is WC7 g)
|
||||
VerifyAbilityMG456(data, abilities, g.AbilityType);
|
||||
else if (Legal.Ban_NoHidden7.Contains(pkm.SpecForm) && pkm.AbilityNumber == 4)
|
||||
data.AddLine(GetInvalid(V112));
|
||||
}
|
||||
private void VerifyAbilityMG456(LegalityAnalysis data, int[] abilities, int cardtype)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
int abilNumber = pkm.AbilityNumber;
|
||||
if (cardtype < 3 && abilNumber != 1 << cardtype) // set number
|
||||
{
|
||||
// Ability can be flipped 0/1 if Ability Capsule is available, is not Hidden Ability, and Abilities are different.
|
||||
if (pkm.Format >= 6 && cardtype < 2 && abilNumber < 3 && abilities[0] != abilities[1])
|
||||
data.AddLine(GetValid(V109));
|
||||
else
|
||||
data.AddLine(GetInvalid(V110));
|
||||
}
|
||||
else if (cardtype == 3 && abilNumber == 4) // 1/2 only
|
||||
data.AddLine(GetInvalid(V110));
|
||||
}
|
||||
}
|
||||
}
|
||||
330
PKHeX.Core/Legality/Verifiers/BallVerifier.cs
Normal file
330
PKHeX.Core/Legality/Verifiers/BallVerifier.cs
Normal file
@@ -0,0 +1,330 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class BallVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Ball;
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (pkm.Format < 3)
|
||||
return; // no ball info saved
|
||||
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
var Info = data.Info;
|
||||
|
||||
if (EncounterMatch is MysteryGift g)
|
||||
{
|
||||
if (pkm.Species == 490 && g.Ball == 0)
|
||||
// there is no ball data in Manaphy Mystery Gift
|
||||
VerifyBallEquals(data, 4); // Pokeball
|
||||
else
|
||||
VerifyBallEquals(data, g.Ball);
|
||||
return;
|
||||
}
|
||||
if (EncounterMatch is EncounterLink l)
|
||||
{
|
||||
VerifyBallEquals(data, l.Ball);
|
||||
return;
|
||||
}
|
||||
if (EncounterMatch is EncounterTrade t)
|
||||
{
|
||||
VerifyBallEquals(data, t.Ball); // Pokeball
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkm.Species == 292 && Info.Generation > 3) // Shedinja. For gen3, copy the ball from Nincada
|
||||
{
|
||||
VerifyBallEquals(data, 4); // Pokeball Only
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkm.Ball == 0x14 && !Info.EncounterMatch.EggEncounter && pkm.SM) // Heavy Ball
|
||||
{
|
||||
var lineage = EvolutionChain.GetLineage(pkm);
|
||||
if (lineage.Any(e => Legal.AlolanCaptureNoHeavyBall.Contains(e)))
|
||||
{
|
||||
data.AddLine(GetInvalid(V116));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (EncounterMatch is EncounterStatic s)
|
||||
{
|
||||
if (s.Gift)
|
||||
VerifyBallEquals(data, s.Ball);
|
||||
else if (s.Location == 75 && s.Generation == 5) // Entree Forest (Dream World)
|
||||
VerifyBallEquals(data, Legal.DreamWorldBalls);
|
||||
else
|
||||
VerifyBallEquals(data, Legal.GetWildBalls(pkm));
|
||||
return;
|
||||
}
|
||||
if (EncounterMatch is EncounterSlot w)
|
||||
{
|
||||
if (w.Location == 30016 && w.Generation == 7) // Poké Pelago
|
||||
VerifyBallEquals(data, 4); // Pokeball
|
||||
// For gen3/4 Safari Zones and BCC getValidWildEncounters already filter to not return
|
||||
// mixed possible encounters between safari, BCC and other encounters
|
||||
// That means is the first encounter is not safari then there is no safari encounter in the array
|
||||
else if (3 <= Info.Generation && Info.Generation <= 4 && w.Type.IsSafariType())
|
||||
VerifyBallEquals(data, 5); // Safari Ball
|
||||
else if (Info.Generation == 4 && w.Type == SlotType.BugContest)
|
||||
VerifyBallEquals(data, 0x18); // Sport Ball
|
||||
else
|
||||
VerifyBallEquals(data, Legal.GetWildBalls(pkm));
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkm.WasEgg)
|
||||
{
|
||||
VerifyBallEgg(data);
|
||||
return;
|
||||
}
|
||||
|
||||
VerifyBallEquals(data, 4); // Pokeball
|
||||
}
|
||||
private void VerifyBallEgg(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (data.Info.Generation < 6) // No inheriting Balls
|
||||
{
|
||||
VerifyBallEquals(data, 4); // Must be Pokéball -- no ball inheritance.
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkm.Ball == 0x01) // Master Ball
|
||||
{ data.AddLine(GetInvalid(V117)); return; }
|
||||
if (pkm.Ball == 0x10) // Cherish Ball
|
||||
{ data.AddLine(GetInvalid(V120)); return; }
|
||||
if (pkm.Ball == 0x04) // Poké Ball
|
||||
{ data.AddLine(GetValid(V119)); return; }
|
||||
|
||||
switch (data.Info.Generation)
|
||||
{
|
||||
case 6: // Gen6 Inheritance Rules
|
||||
VerifyBallEggGen6(data);
|
||||
return;
|
||||
case 7: // Gen7 Inheritance Rules
|
||||
VerifyBallEggGen7(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
private void VerifyBallEggGen6(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (pkm.Gender == 2) // Genderless
|
||||
{
|
||||
VerifyBallEquals(data, 4); // Must be Pokéball as ball can only pass via mother (not Ditto!)
|
||||
return;
|
||||
}
|
||||
if (Legal.BreedMaleOnly.Contains(pkm.Species))
|
||||
{
|
||||
VerifyBallEquals(data, 4); // Must be Pokéball as ball can only pass via mother (not Ditto!)
|
||||
return;
|
||||
}
|
||||
|
||||
int ball = pkm.Ball;
|
||||
|
||||
if (ball >= 26)
|
||||
{
|
||||
data.AddLine(GetInvalid(V126));
|
||||
return;
|
||||
}
|
||||
if (ball == 0x05) // Safari Ball
|
||||
{
|
||||
if (EvolutionChain.GetLineage(pkm).All(e => !Legal.Inherit_Safari.Contains(e)))
|
||||
data.AddLine(GetInvalid(V121));
|
||||
else if (pkm.AbilityNumber == 4)
|
||||
data.AddLine(GetInvalid(V122));
|
||||
else
|
||||
data.AddLine(GetValid(V123));
|
||||
|
||||
return;
|
||||
}
|
||||
if (0x10 < ball && ball < 0x18) // Apricorn Ball
|
||||
{
|
||||
if (EvolutionChain.GetLineage(pkm).All(e => !Legal.Inherit_Apricorn6.Contains(e)))
|
||||
data.AddLine(GetInvalid(V121));
|
||||
if (pkm.AbilityNumber == 4)
|
||||
data.AddLine(GetInvalid(V122));
|
||||
else
|
||||
data.AddLine(GetValid(V123));
|
||||
|
||||
return;
|
||||
}
|
||||
if (ball == 0x18) // Sport Ball
|
||||
{
|
||||
if (EvolutionChain.GetLineage(pkm).All(e => !Legal.Inherit_Sport.Contains(e)))
|
||||
data.AddLine(GetInvalid(V121));
|
||||
else if (pkm.AbilityNumber == 4)
|
||||
data.AddLine(GetInvalid(V122));
|
||||
else
|
||||
data.AddLine(GetValid(V123));
|
||||
|
||||
return;
|
||||
}
|
||||
if (ball == 0x19) // Dream Ball
|
||||
{
|
||||
if (EvolutionChain.GetLineage(pkm).Any(e => Legal.Inherit_Dream.Contains(e)))
|
||||
data.AddLine(GetValid(V123));
|
||||
else
|
||||
data.AddLine(GetInvalid(V121));
|
||||
|
||||
if (pkm.AbilityNumber == 4 && Legal.Ban_DreamHidden.Contains(pkm.Species))
|
||||
data.AddLine(GetInvalid(V122));
|
||||
|
||||
return;
|
||||
}
|
||||
if (0x0D <= ball && ball <= 0x0F)
|
||||
{
|
||||
if (!Legal.Ban_Gen4Ball_6.Contains(pkm.Species))
|
||||
data.AddLine(GetValid(V123));
|
||||
else
|
||||
data.AddLine(GetInvalid(V121));
|
||||
|
||||
return;
|
||||
}
|
||||
if (0x02 <= ball && ball <= 0x0C) // Don't worry, Ball # 0x05 was already checked.
|
||||
{
|
||||
if (Legal.Ban_Gen3Ball.Contains(pkm.Species))
|
||||
data.AddLine(GetInvalid(V121));
|
||||
else if (pkm.AbilityNumber == 4 && Legal.Ban_Gen3BallHidden.Contains(pkm.SpecForm))
|
||||
data.AddLine(GetInvalid(V122));
|
||||
else
|
||||
data.AddLine(GetValid(V123));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkm.Species > 650 && pkm.Species != 700) // Sylveon
|
||||
{
|
||||
if (Legal.GetWildBalls(pkm).Contains(pkm.Ball))
|
||||
data.AddLine(GetValid(V123));
|
||||
else
|
||||
data.AddLine(GetInvalid(V121));
|
||||
return;
|
||||
}
|
||||
|
||||
data.AddLine(GetInvalid(V125));
|
||||
}
|
||||
private void VerifyBallEggGen7(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var Lineage = EvolutionChain.GetLineage(pkm).ToArray();
|
||||
if (722 <= pkm.Species && pkm.Species <= 730) // G7 Starters
|
||||
{
|
||||
VerifyBallEquals(data, 4);
|
||||
return;
|
||||
}
|
||||
|
||||
int ball = pkm.Ball;
|
||||
if (ball == 0x05) // Safari Ball
|
||||
{
|
||||
if (!Lineage.Any(e => Legal.Inherit_Safari.Contains(e) || Legal.Inherit_SafariMale.Contains(e)))
|
||||
data.AddLine(GetInvalid(V121));
|
||||
else if (pkm.AbilityNumber == 4 && Lineage.Any(e => Legal.Ban_SafariBallHidden_7.Contains(e)))
|
||||
data.AddLine(GetInvalid(V122));
|
||||
else
|
||||
data.AddLine(GetValid(V123));
|
||||
|
||||
return;
|
||||
}
|
||||
if (0x10 < ball && ball < 0x18) // Apricorn Ball
|
||||
{
|
||||
if (!Lineage.Any(e => Legal.Inherit_Apricorn7.Contains(e)))
|
||||
data.AddLine(GetInvalid(V121));
|
||||
else if (pkm.AbilityNumber == 4 && Legal.Ban_NoHidden7Apricorn.Contains(Lineage.Last() | pkm.AltForm << 11)) // lineage is 3->2->origin
|
||||
data.AddLine(GetInvalid(V122));
|
||||
else
|
||||
data.AddLine(GetValid(V123));
|
||||
|
||||
return;
|
||||
}
|
||||
if (ball == 0x18) // Sport Ball
|
||||
{
|
||||
if (!Lineage.Any(e => Legal.Inherit_Sport.Contains(e)))
|
||||
data.AddLine(GetInvalid(V121));
|
||||
else if (pkm.AbilityNumber == 4 && (Lineage.Contains(313) || Lineage.Contains(314))) // Volbeat/Illumise
|
||||
data.AddLine(GetInvalid(V122));
|
||||
else
|
||||
data.AddLine(GetValid(V123));
|
||||
|
||||
return;
|
||||
}
|
||||
if (ball == 0x19) // Dream Ball
|
||||
{
|
||||
if (Lineage.Any(e => Legal.Inherit_Dream.Contains(e) || Legal.Inherit_DreamMale.Contains(e)))
|
||||
data.AddLine(GetValid(V123));
|
||||
else
|
||||
data.AddLine(GetInvalid(V121));
|
||||
|
||||
return;
|
||||
}
|
||||
if (0x0D <= ball && ball <= 0x0F) // Dusk Heal Quick
|
||||
{
|
||||
if (!Legal.Ban_Gen4Ball_7.Contains(pkm.Species))
|
||||
data.AddLine(GetValid(V123));
|
||||
else
|
||||
data.AddLine(GetInvalid(V121));
|
||||
|
||||
return;
|
||||
}
|
||||
if (0x02 <= ball && ball <= 0x0C) // Don't worry, Ball # 0x05 was already checked.
|
||||
{
|
||||
if (!Legal.Ban_Gen3Ball_7.Contains(pkm.Species))
|
||||
data.AddLine(GetValid(V123));
|
||||
else
|
||||
data.AddLine(GetInvalid(V121));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ball == 26)
|
||||
{
|
||||
if (Lineage[0] == 669 && pkm.AltForm == 3 && pkm.AbilityNumber == 4)
|
||||
{
|
||||
// Can't obtain Flabébé-Blue with Hidden Ability in wild
|
||||
data.AddLine(GetInvalid(V122));
|
||||
return;
|
||||
}
|
||||
if ((pkm.Species > 731 && pkm.Species <= 785) || Lineage.Any(e => Legal.PastGenAlolanNatives.Contains(e) && !Legal.PastGenAlolanNativesUncapturable.Contains(e)))
|
||||
{
|
||||
data.AddLine(GetValid(V123));
|
||||
return;
|
||||
}
|
||||
if (Lineage.Any(e => Legal.PastGenAlolanScans.Contains(e)))
|
||||
{
|
||||
data.AddLine(GetValid(V123));
|
||||
return;
|
||||
}
|
||||
// next statement catches all new alolans
|
||||
}
|
||||
|
||||
if (pkm.Species > 721)
|
||||
{
|
||||
VerifyBallEquals(data, Legal.GetWildBalls(pkm));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ball >= 27)
|
||||
{
|
||||
data.AddLine(GetInvalid(V126));
|
||||
return;
|
||||
}
|
||||
data.AddLine(GetInvalid(V125));
|
||||
}
|
||||
|
||||
private void VerifyBallEquals(LegalityAnalysis data, int ball) => AddBallLine(data, ball == data.pkm.Ball);
|
||||
private void VerifyBallEquals(LegalityAnalysis data, ICollection<int> balls) => AddBallLine(data, balls.Contains(data.pkm.Ball));
|
||||
private void AddBallLine(LegalityAnalysis data, bool valid)
|
||||
{
|
||||
if (valid)
|
||||
data.AddLine(GetValid(V119));
|
||||
else
|
||||
data.AddLine(GetInvalid(V118));
|
||||
}
|
||||
}
|
||||
}
|
||||
42
PKHeX.Core/Legality/Verifiers/CXDVerifier.cs
Normal file
42
PKHeX.Core/Legality/Verifiers/CXDVerifier.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class CXDVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Misc;
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (data.EncounterMatch is EncounterStatic)
|
||||
VerifyCXDStarterCorrelation(data);
|
||||
else if (pkm.WasEgg) // can't obtain eggs in CXD
|
||||
data.AddLine(GetInvalid(V80, CheckIdentifier.Encounter)); // invalid encounter
|
||||
|
||||
if (pkm.OT_Gender == 1)
|
||||
data.AddLine(GetInvalid(V407, CheckIdentifier.Trainer));
|
||||
}
|
||||
private static void VerifyCXDStarterCorrelation(LegalityAnalysis data)
|
||||
{
|
||||
var pidiv = data.Info.PIDIV;
|
||||
if (pidiv.Type != PIDType.CXD)
|
||||
return;
|
||||
|
||||
bool valid;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
var pkm = data.pkm;
|
||||
switch (EncounterMatch.Species)
|
||||
{
|
||||
case 133:
|
||||
valid = LockFinder.IsXDStarterValid(pidiv.OriginSeed, pkm.TID, pkm.SID); break;
|
||||
case 196:
|
||||
case 197:
|
||||
valid = pidiv.Type == PIDType.CXD_ColoStarter; break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
if (!valid)
|
||||
data.AddLine(GetInvalid(V400, CheckIdentifier.PID));
|
||||
}
|
||||
}
|
||||
}
|
||||
23
PKHeX.Core/Legality/Verifiers/ConsoleRegionVerifier.cs
Normal file
23
PKHeX.Core/Legality/Verifiers/ConsoleRegionVerifier.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class ConsoleRegionVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Geography;
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var result = VerifyConsoleRegion(data.pkm);
|
||||
data.AddLine(result);
|
||||
}
|
||||
private static CheckResult VerifyConsoleRegion(PKM pkm)
|
||||
{
|
||||
int consoleRegion = pkm.ConsoleRegion;
|
||||
if (consoleRegion >= 7)
|
||||
return new CheckResult(Severity.Invalid, V301, CheckIdentifier.Geography);
|
||||
return Legal.IsConsoleRegionCountryValid(consoleRegion, pkm.Country)
|
||||
? new CheckResult(Severity.Valid, V303, CheckIdentifier.Geography)
|
||||
: new CheckResult(Severity.Invalid, V302, CheckIdentifier.Geography);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs
Normal file
47
PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public sealed class EffortValueVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.EVs;
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
var evs = pkm.EVs;
|
||||
int sum = pkm.EVTotal;
|
||||
if (sum > 0 && pkm.IsEgg)
|
||||
data.AddLine(GetInvalid(V22, CheckIdentifier.EVs));
|
||||
if (pkm.Format >= 3 && sum > 510)
|
||||
data.AddLine(GetInvalid(V25, CheckIdentifier.EVs));
|
||||
if (pkm.Format >= 6 && evs.Any(ev => ev > 252))
|
||||
data.AddLine(GetInvalid(V26, CheckIdentifier.EVs));
|
||||
if (pkm.Format == 4 && pkm.Gen4 && EncounterMatch.LevelMin == 100)
|
||||
{
|
||||
// Cannot EV train at level 100 -- Certain events are distributed at level 100.
|
||||
if (evs.Any(ev => ev > 100)) // EVs can only be increased by vitamins to a max of 100.
|
||||
data.AddLine(GetInvalid(V367, CheckIdentifier.EVs));
|
||||
}
|
||||
else if (pkm.Format < 5)
|
||||
{
|
||||
// In Generations I and II, when a Pokémon is taken out of the Day Care, its experience will lower to the minimum value for its current level.
|
||||
if (pkm.Format < 3) // can abuse daycare for EV training without EXP gain
|
||||
return;
|
||||
|
||||
const int maxEV = 100; // Vitamin Max
|
||||
if (PKX.GetEXP(EncounterMatch.LevelMin, pkm.Species) == pkm.EXP && evs.Any(ev => ev > maxEV))
|
||||
data.AddLine(GetInvalid(string.Format(V418, maxEV), CheckIdentifier.EVs));
|
||||
}
|
||||
|
||||
// Only one of the following can be true: 0, 508, and x%6!=0
|
||||
if (sum == 0 && !EncounterMatch.IsWithinRange(pkm))
|
||||
data.AddLine(Get(V23, Severity.Fishy));
|
||||
else if (sum == 508)
|
||||
data.AddLine(Get(V24, Severity.Fishy));
|
||||
else if (evs[0] != 0 && evs.All(ev => evs[0] == ev))
|
||||
data.AddLine(Get(V27, Severity.Fishy));
|
||||
}
|
||||
}
|
||||
}
|
||||
31
PKHeX.Core/Legality/Verifiers/EncounterTypeVerifier.cs
Normal file
31
PKHeX.Core/Legality/Verifiers/EncounterTypeVerifier.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class EncounterTypeVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Encounter;
|
||||
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var type = GetEncounterTypeValue(data.pkm, data.EncounterMatch);
|
||||
var result = !type.Contains(data.pkm.EncounterType) ? GetInvalid(V381) : GetValid(V380);
|
||||
data.AddLine(result);
|
||||
}
|
||||
|
||||
private static EncounterType GetEncounterTypeValue(PKM pkm, IEncounterable enc)
|
||||
{
|
||||
// Encounter type data is only stored for gen 4 encounters
|
||||
// All eggs have encounter type none, even if they are from static encounters
|
||||
if (!pkm.Gen4 || pkm.WasEgg)
|
||||
return EncounterType.None;
|
||||
|
||||
if (enc is EncounterSlot w)
|
||||
return w.TypeEncounter;
|
||||
if (enc is EncounterStaticTyped s)
|
||||
return s.TypeEncounter;
|
||||
|
||||
return EncounterType.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
259
PKHeX.Core/Legality/Verifiers/FormVerifier.cs
Normal file
259
PKHeX.Core/Legality/Verifiers/FormVerifier.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class FormVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Form;
|
||||
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (pkm.Format < 4)
|
||||
return; // no forms exist
|
||||
|
||||
var PersonalInfo = data.PersonalInfo;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
var Info = data.Info;
|
||||
|
||||
int count = PersonalInfo.FormeCount;
|
||||
if (count <= 1 && pkm.AltForm == 0)
|
||||
return; // no forms to check
|
||||
|
||||
if (!PersonalInfo.IsFormeWithinRange(pkm.AltForm) && !FormConverter.IsValidOutOfBoundsForme(pkm.Species, pkm.AltForm, Info.Generation))
|
||||
{
|
||||
data.AddLine(GetInvalid(string.Format(V304, count - 1, pkm.AltForm)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (EncounterMatch is EncounterSlot w && w.Type == SlotType.FriendSafari)
|
||||
VerifyFormFriendSafari(data);
|
||||
else if (EncounterMatch is EncounterEgg)
|
||||
{
|
||||
if (FormConverter.IsTotemForm(pkm.Species, pkm.AltForm))
|
||||
{
|
||||
data.AddLine(GetInvalid(V317));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (pkm.Species)
|
||||
{
|
||||
case 25 when Info.Generation == 6: // Pikachu Cosplay
|
||||
bool isStatic = EncounterMatch is EncounterStatic;
|
||||
if (isStatic != (pkm.AltForm != 0))
|
||||
{
|
||||
string msg = isStatic ? V305 : V306;
|
||||
data.AddLine(GetInvalid(msg));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 25 when Info.Generation == 7: // Pikachu Cap
|
||||
bool IsValidPikachuCap()
|
||||
{
|
||||
switch (EncounterMatch)
|
||||
{
|
||||
default: return pkm.AltForm == 0;
|
||||
case WC7 wc7: return wc7.Form == pkm.AltForm;
|
||||
case EncounterStatic s: return s.Form == pkm.AltForm;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsValidPikachuCap())
|
||||
{
|
||||
bool gift = EncounterMatch is WC7 g && g.Form != pkm.AltForm;
|
||||
var msg = gift ? V307 : V317;
|
||||
data.AddLine(GetInvalid(msg));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 201 when Info.Generation == 2 && pkm.AltForm >= 26:
|
||||
data.AddLine(GetInvalid(string.Format(V304, "Z", pkm.AltForm == 26 ? "!" : "?")));
|
||||
break;
|
||||
case 487: // Giratina
|
||||
if (pkm.AltForm == 1 ^ pkm.HeldItem == 112) // Origin form only with Griseous Orb
|
||||
{
|
||||
data.AddLine(GetInvalid(V308));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 493: // Arceus
|
||||
{
|
||||
int item = pkm.HeldItem;
|
||||
int form = 0;
|
||||
if (298 <= item && item <= 313 || item == 644)
|
||||
form = Array.IndexOf(Legal.Arceus_Plate, item) + 1;
|
||||
else if (777 <= item && item <= 793)
|
||||
form = Array.IndexOf(Legal.Arceus_ZCrystal, item) + 1;
|
||||
if (pkm.Format == 4 && form >= 9)
|
||||
form++; // ??? type Form shifts everything by 1
|
||||
|
||||
if (form != pkm.AltForm)
|
||||
data.AddLine(GetInvalid(V308));
|
||||
else if (form != 0)
|
||||
data.AddLine(GetValid(V309));
|
||||
}
|
||||
break;
|
||||
case 647: // Keldeo
|
||||
{
|
||||
if (pkm.Gen5) // can mismatch in gen5 via BW tutor and transfer up
|
||||
break;
|
||||
int index = Array.IndexOf(pkm.Moves, 548); // Secret Sword
|
||||
bool noSword = index < 0;
|
||||
if (pkm.AltForm == 0 ^ noSword) // mismatch
|
||||
Info.Moves[noSword ? 0 : index] = new CheckMoveResult(Info.Moves[noSword ? 0 : index], Severity.Invalid, V169, CheckIdentifier.Move);
|
||||
break;
|
||||
}
|
||||
case 649: // Genesect
|
||||
{
|
||||
int item = pkm.HeldItem;
|
||||
int form = 0;
|
||||
if (116 <= item && item <= 119)
|
||||
form = item - 115;
|
||||
|
||||
if (form != pkm.AltForm)
|
||||
data.AddLine(GetInvalid(V308));
|
||||
else
|
||||
data.AddLine(GetValid(V309));
|
||||
}
|
||||
break;
|
||||
case 658: // Greninja
|
||||
if (pkm.AltForm > 1) // Ash Battle Bond active
|
||||
{
|
||||
data.AddLine(GetInvalid(V310));
|
||||
return;
|
||||
}
|
||||
if (pkm.AltForm != 0 && !(EncounterMatch is MysteryGift)) // Formes are not breedable, MysteryGift already checked
|
||||
{
|
||||
data.AddLine(GetInvalid(string.Format(V304, 0, pkm.AltForm)));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 664: // Scatterbug
|
||||
case 665: // Spewpa
|
||||
if (pkm.AltForm > 17) // Fancy & Pokéball
|
||||
{
|
||||
data.AddLine(GetInvalid(V311));
|
||||
return;
|
||||
}
|
||||
if (!Legal.CheckVivillonPattern(pkm.AltForm, pkm.Country, pkm.Region))
|
||||
data.AddLine(Get(V312, Severity.Fishy));
|
||||
break;
|
||||
case 666: // Vivillon
|
||||
if (pkm.AltForm > 17) // Fancy & Pokéball
|
||||
{
|
||||
if (!(EncounterMatch is MysteryGift))
|
||||
data.AddLine(GetInvalid(V312));
|
||||
else
|
||||
data.AddLine(GetValid(V313));
|
||||
|
||||
return;
|
||||
}
|
||||
if (!Legal.CheckVivillonPattern(pkm.AltForm, pkm.Country, pkm.Region))
|
||||
data.AddLine(Get(V312, Severity.Fishy));
|
||||
break;
|
||||
case 670: // Floette
|
||||
if (pkm.AltForm == 5) // Eternal Flower -- Never Released
|
||||
{
|
||||
if (!(EncounterMatch is MysteryGift))
|
||||
data.AddLine(GetInvalid(V314));
|
||||
else
|
||||
data.AddLine(GetValid(V315));
|
||||
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 678: // Meowstic
|
||||
if (pkm.AltForm != pkm.Gender)
|
||||
data.AddLine(GetInvalid(V203));
|
||||
break;
|
||||
case 773: // Silvally
|
||||
{
|
||||
int item = pkm.HeldItem;
|
||||
int form = 0;
|
||||
if ((904 <= item && item <= 920) || item == 644)
|
||||
form = item - 903;
|
||||
if (form != pkm.AltForm)
|
||||
data.AddLine(GetInvalid(V308));
|
||||
else if (form != 0)
|
||||
data.AddLine(GetValid(V309));
|
||||
break;
|
||||
}
|
||||
|
||||
case 744 when Info.EncounterMatch.EggEncounter && pkm.AltForm == 1 && pkm.SM:
|
||||
case 745 when Info.EncounterMatch.EggEncounter && pkm.AltForm == 2 && pkm.SM:
|
||||
data.AddLine(GetInvalid(V317));
|
||||
return;
|
||||
|
||||
// Impossible Egg forms
|
||||
case 479 when pkm.IsEgg: // Rotom
|
||||
case 676 when pkm.IsEgg: // Furfrou
|
||||
if (pkm.AltForm != 0) // has form
|
||||
{
|
||||
data.AddLine(GetInvalid(V50));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
// Party Only Forms
|
||||
case 492: // Shaymin
|
||||
case 676: // Furfrou
|
||||
case 720: // Hoopa
|
||||
if (pkm.AltForm != 0 && pkm.Box > -1 && pkm.Format <= 6) // has form but stored in box
|
||||
{
|
||||
data.AddLine(GetInvalid(V316));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
// Battle only Forms with other legal forms allowed
|
||||
case 718 when pkm.AltForm >= 4: // Zygarde Complete
|
||||
case 774 when pkm.AltForm < 7: // Minior Shield
|
||||
case 800 when pkm.AltForm == 3: // Ultra Necrozma
|
||||
data.AddLine(GetInvalid(V310));
|
||||
return;
|
||||
case 800 when pkm.AltForm < 3: // Necrozma Fused forms & default
|
||||
case 778 when pkm.AltForm == 2: // Totem disguise Mimikyu
|
||||
data.AddLine(GetValid(V318));
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkm.Format >= 7 && Info.Generation < 7 && pkm.AltForm != 0)
|
||||
{
|
||||
if (pkm.Species == 25 || Legal.AlolanOriginForms.Contains(pkm.Species)
|
||||
|| Legal.AlolanVariantEvolutions12.Contains(data.EncounterOriginal.Species))
|
||||
{ data.AddLine(GetInvalid(V317)); return; }
|
||||
}
|
||||
if (pkm.AltForm > 0 && new[] { Legal.BattleForms, Legal.BattleMegas, Legal.BattlePrimals }.Any(arr => arr.Contains(pkm.Species)))
|
||||
{ data.AddLine(GetInvalid(V310)); return; }
|
||||
|
||||
data.AddLine(GetValid(V318));
|
||||
}
|
||||
|
||||
|
||||
private void VerifyFormFriendSafari(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
switch (pkm.Species)
|
||||
{
|
||||
case 670: // Floette
|
||||
case 671: // Florges
|
||||
if (!new[] { 0, 1, 3 }.Contains(pkm.AltForm)) // 0/1/3 - RBY
|
||||
data.AddLine(GetInvalid(V64));
|
||||
break;
|
||||
case 710 when pkm.AltForm != 0: // Pumpkaboo
|
||||
case 711 when pkm.AltForm != 0: // Goregeist Average
|
||||
data.AddLine(GetInvalid(V6));
|
||||
break;
|
||||
case 423 when pkm.AltForm != 0: // Gastrodon West
|
||||
data.AddLine(GetInvalid(V64));
|
||||
break;
|
||||
case 586 when pkm.AltForm != 0: // Sawsbuck
|
||||
data.AddLine(GetInvalid(V65));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
PKHeX.Core/Legality/Verifiers/GenderVerifier.cs
Normal file
85
PKHeX.Core/Legality/Verifiers/GenderVerifier.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class GenderVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Gender;
|
||||
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var pi = pkm.PersonalInfo;
|
||||
if (pi.Genderless != (pkm.Gender == 2))
|
||||
{
|
||||
// DP/HGSS shedinja glitch -- only generation 4 spawns
|
||||
bool ignore = pkm.Format == 4 && pkm.Species == 292 && pkm.Met_Level != pkm.CurrentLevel;
|
||||
if (!ignore)
|
||||
data.AddLine(GetInvalid(V203));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for PID relationship to Gender & Nature if applicable
|
||||
int gen = data.Info.Generation;
|
||||
|
||||
if (3 <= gen && gen <= 5)
|
||||
{
|
||||
// Gender-PID & Nature-PID relationship check
|
||||
if (IsValidGenderPID(data))
|
||||
data.AddLine(GetValid(V250));
|
||||
else
|
||||
data.AddLine(GetInvalid(V251));
|
||||
|
||||
if (gen != 5)
|
||||
VerifyNaturePID(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check fixed gender cases
|
||||
if ((pi.OnlyFemale && pkm.Gender != 1) || (pi.OnlyMale && pkm.Gender != 0))
|
||||
data.AddLine(GetInvalid(V203));
|
||||
}
|
||||
|
||||
private void VerifyNaturePID(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var result = pkm.EncryptionConstant % 25 == pkm.Nature
|
||||
? GetValid(V252, CheckIdentifier.Nature)
|
||||
: GetInvalid(V253, CheckIdentifier.Nature);
|
||||
data.AddLine(result);
|
||||
}
|
||||
|
||||
private bool IsValidGenderPID(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
bool genderValid = pkm.IsGenderValid();
|
||||
if (!genderValid)
|
||||
{
|
||||
if (pkm.Format == 4 && pkm.Species == 292) // Shedinja glitch
|
||||
{
|
||||
// should match original gender
|
||||
var gender = PKX.GetGenderFromPIDAndRatio(pkm.PID, 0x7F); // 50M-50F
|
||||
if (gender == pkm.Gender)
|
||||
return true;
|
||||
}
|
||||
else if (pkm.Format > 5 && (pkm.Species == 183 || pkm.Species == 184)) // Azurill/Marill Gender Ratio Change
|
||||
{
|
||||
var gv = pkm.PID & 0xFF;
|
||||
if (gv > 63 && pkm.Gender == 1) // evolved from azurill after transferring to keep gender
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for mixed->fixed gender incompatibility by checking the gender of the original species
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
if (Legal.FixedGenderFromBiGender.Contains(EncounterMatch.Species) && pkm.Gender != 2) // shedinja
|
||||
{
|
||||
var gender = PKX.GetGenderFromPID(EncounterMatch.Species, pkm.EncryptionConstant);
|
||||
if (gender != pkm.Gender) // gender must not be different from original
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
PKHeX.Core/Legality/Verifiers/HyperTrainingVerifier.cs
Normal file
40
PKHeX.Core/Legality/Verifiers/HyperTrainingVerifier.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class HyperTrainingVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Training;
|
||||
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (!(pkm is IHyperTrain t))
|
||||
return; // No Hyper Training before Gen7
|
||||
|
||||
if (!t.IsHyperTrained())
|
||||
return;
|
||||
|
||||
if (pkm.CurrentLevel != 100)
|
||||
{
|
||||
data.AddLine(GetInvalid(V40));
|
||||
return;
|
||||
}
|
||||
|
||||
int max = pkm.MaxIV;
|
||||
if (pkm.IVTotal == max * 6)
|
||||
{
|
||||
data.AddLine(GetInvalid(V41));
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 6; i++) // Check individual IVs
|
||||
{
|
||||
if (pkm.GetIV(i) != max || !t.IsHyperTrained(i))
|
||||
continue;
|
||||
data.AddLine(GetInvalid(V42));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs
Normal file
90
PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public sealed class IndividualValueVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.IVs;
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
switch (data.EncounterMatch)
|
||||
{
|
||||
case EncounterStatic s:
|
||||
VerifyIVsStatic(data, s);
|
||||
break;
|
||||
case EncounterSlot w:
|
||||
VerifyIVsSlot(data, w);
|
||||
break;
|
||||
case MysteryGift g:
|
||||
VerifyIVsMystery(data, g);
|
||||
break;
|
||||
}
|
||||
|
||||
var pkm = data.pkm;
|
||||
if (pkm.IVTotal == 0)
|
||||
data.AddLine(Get(V321, Severity.Fishy));
|
||||
else if (pkm.IVs[0] < 30 && pkm.IVs.All(iv => pkm.IVs[0] == iv))
|
||||
data.AddLine(Get(V32, Severity.Fishy));
|
||||
}
|
||||
|
||||
private void VerifyIVsMystery(LegalityAnalysis data, MysteryGift g)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
int[] IVs = GetMysteryGiftIVs(g);
|
||||
if (IVs == null)
|
||||
return;
|
||||
|
||||
var pkIVs = pkm.IVs;
|
||||
var ivflag = Array.Find(IVs, iv => (byte)(iv - 0xFC) < 3);
|
||||
if (ivflag == 0) // Random IVs
|
||||
{
|
||||
bool valid = GetIsFixedIVSequenceValid(IVs, pkIVs);
|
||||
if (!valid)
|
||||
data.AddLine(GetInvalid(V30));
|
||||
}
|
||||
else
|
||||
{
|
||||
int IVCount = ivflag - 0xFB; // IV2/IV3
|
||||
if (pkIVs.Count(iv => iv == 31) < IVCount)
|
||||
data.AddLine(GetInvalid(string.Format(V28, IVCount)));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetIsFixedIVSequenceValid(IReadOnlyList<int> IVs, IReadOnlyList<int> pkIVs)
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
if (IVs[i] <= 31 && IVs[i] != pkIVs[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int[] GetMysteryGiftIVs(MysteryGift g)
|
||||
{
|
||||
switch (g)
|
||||
{
|
||||
case WC7 wc7: return wc7.IVs;
|
||||
case WC6 wc6: return wc6.IVs;
|
||||
case PGF pgf: return pgf.IVs;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyIVsSlot(LegalityAnalysis data, EncounterSlot w)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
bool force2 = w.Type == SlotType.FriendSafari || w.Generation == 7 && pkm.AbilityNumber == 4;
|
||||
if (force2 && pkm.IVs.Count(iv => iv == 31) < 2)
|
||||
data.AddLine(GetInvalid(w.Type == SlotType.FriendSafari ? V29 : string.Format(V28, 2)));
|
||||
}
|
||||
|
||||
private void VerifyIVsStatic(LegalityAnalysis data, EncounterStatic s)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (s.FlawlessIVCount != 0 && pkm.IVs.Count(iv => iv == 31) < s.FlawlessIVCount)
|
||||
data.AddLine(GetInvalid(string.Format(V28, s.FlawlessIVCount)));
|
||||
}
|
||||
}
|
||||
}
|
||||
38
PKHeX.Core/Legality/Verifiers/ItemVerifier.cs
Normal file
38
PKHeX.Core/Legality/Verifiers/ItemVerifier.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class ItemVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.HeldItem;
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (!Legal.IsHeldItemAllowed(pkm))
|
||||
data.AddLine(GetInvalid(V204));
|
||||
|
||||
if (pkm.Format == 3 && pkm.HeldItem == 175) // Enigma Berry
|
||||
VerifyEReaderBerry(data);
|
||||
|
||||
if (pkm.IsEgg && pkm.HeldItem != 0)
|
||||
data.AddLine(GetInvalid(V419));
|
||||
}
|
||||
private void VerifyEReaderBerry(LegalityAnalysis data)
|
||||
{
|
||||
if (Legal.EReaderBerryIsEnigma) // no E-Reader berry data provided, can't hold berry.
|
||||
{
|
||||
data.AddLine(GetInvalid(V204));
|
||||
return;
|
||||
}
|
||||
|
||||
var matchUSA = Legal.EReaderBerriesNames_USA.Contains(Legal.EReaderBerryName);
|
||||
var matchJP = Legal.EReaderBerriesNames_JP.Contains(Legal.EReaderBerryName);
|
||||
if (!matchJP && !matchUSA) // Does not match any released E-Reader berry
|
||||
data.AddLine(GetInvalid(V369));
|
||||
else if (matchJP && !Legal.SavegameJapanese && Legal.SavegameLanguage >= 0) // E-Reader is region locked
|
||||
data.AddLine(GetInvalid(V370));
|
||||
else if (matchUSA && Legal.SavegameJapanese && Legal.SavegameLanguage >= 0) // E-Reader is region locked
|
||||
data.AddLine(GetInvalid(V371));
|
||||
}
|
||||
}
|
||||
}
|
||||
68
PKHeX.Core/Legality/Verifiers/LanguageVerifier.cs
Normal file
68
PKHeX.Core/Legality/Verifiers/LanguageVerifier.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public sealed class LanguageVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Language;
|
||||
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
int originalGeneration = data.Info.Generation;
|
||||
int currentLanguage = pkm.Language;
|
||||
int maxLanguageID = Legal.GetMaxLanguageID(originalGeneration);
|
||||
|
||||
if (!IsValidLanguageID(currentLanguage, maxLanguageID, pkm))
|
||||
{
|
||||
data.AddLine(GetInvalid(string.Format(V5, $"<={maxLanguageID}", currentLanguage)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Korean Gen4 games can not trade with other Gen4 languages, but can use Pal Park with any Gen3 game/language.
|
||||
if (pkm.Format == 4 && pkm.Gen4 && !IsValidG4Korean(currentLanguage))
|
||||
{
|
||||
bool kor = currentLanguage == (int)LanguageID.Korean;
|
||||
var msgpkm = kor ? V611 : V612;
|
||||
var msgsav = kor ? V612 : V611;
|
||||
data.AddLine(GetInvalid(string.Format(V610, msgpkm, msgsav)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Korean Crystal does not exist, neither do VC1
|
||||
if (originalGeneration <= 2 && pkm.Korean && !GameVersion.GS.Contains((GameVersion)pkm.Version))
|
||||
{
|
||||
data.AddLine(GetInvalid(string.Format(V5, $"!={(LanguageID)currentLanguage}", currentLanguage)));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsValidLanguageID(int currentLanguage, int maxLanguageID, PKM pkm)
|
||||
{
|
||||
if (currentLanguage == (int)LanguageID.UNUSED_6)
|
||||
return false; // Language ID 6 is unused.
|
||||
|
||||
if (currentLanguage > maxLanguageID)
|
||||
return false; // Language not available (yet)
|
||||
|
||||
if (currentLanguage <= (int) LanguageID.Hacked && !Legal.IsValidMissingLanguage(pkm))
|
||||
return false; // Missing Language value is not obtainable
|
||||
|
||||
return true; // Language is possible
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the <see cref="currentLanguage"/> can exist in the Generation 4 savefile.
|
||||
/// </summary>
|
||||
/// <param name="currentLanguage"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsValidG4Korean(int currentLanguage)
|
||||
{
|
||||
bool savKOR = Legal.SavegameLanguage == (int) LanguageID.Korean;
|
||||
bool pkmKOR = currentLanguage == (int) LanguageID.Korean;
|
||||
if (savKOR == pkmKOR)
|
||||
return true;
|
||||
|
||||
return Legal.SavegameLanguage < 0; // check not overriden by Legality settings
|
||||
}
|
||||
}
|
||||
}
|
||||
85
PKHeX.Core/Legality/Verifiers/LevelVerifier.cs
Normal file
85
PKHeX.Core/Legality/Verifiers/LevelVerifier.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class LevelVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Level;
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
if (EncounterMatch is MysteryGift gift)
|
||||
{
|
||||
if (gift.Level != pkm.Met_Level && pkm.HasOriginalMetLocation)
|
||||
{
|
||||
switch (gift)
|
||||
{
|
||||
case WC3 wc3 when wc3.Met_Level == pkm.Met_Level || wc3.IsEgg:
|
||||
break;
|
||||
case WC7 wc7 when wc7.MetLevel == pkm.Met_Level:
|
||||
break;
|
||||
default:
|
||||
data.AddLine(GetInvalid(V83));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (gift.Level > pkm.CurrentLevel)
|
||||
{
|
||||
data.AddLine(GetInvalid(V84));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (pkm.IsEgg)
|
||||
{
|
||||
int elvl = Legal.GetEggHatchLevel(pkm);
|
||||
if (elvl != pkm.CurrentLevel)
|
||||
data.AddLine(GetInvalid(string.Format(V52, elvl)));
|
||||
return;
|
||||
}
|
||||
|
||||
int lvl = pkm.CurrentLevel;
|
||||
if (lvl < pkm.Met_Level)
|
||||
data.AddLine(GetInvalid(V85));
|
||||
else if (!EncounterMatch.IsWithinRange(pkm) && lvl != 100 && pkm.EXP == PKX.GetEXP(lvl, pkm.Species))
|
||||
data.AddLine(Get(V87, Severity.Fishy));
|
||||
else
|
||||
data.AddLine(GetValid(V88));
|
||||
}
|
||||
public void VerifyG1(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
if (pkm.IsEgg)
|
||||
{
|
||||
int elvl = Legal.GetEggHatchLevel(pkm);
|
||||
if (elvl != pkm.CurrentLevel)
|
||||
data.AddLine(GetInvalid(string.Format(V52, elvl)));
|
||||
return;
|
||||
}
|
||||
if (pkm.Met_Location != 0) // crystal
|
||||
{
|
||||
int lvl = pkm.CurrentLevel;
|
||||
if (lvl < pkm.Met_Level)
|
||||
data.AddLine(GetInvalid(V85));
|
||||
}
|
||||
|
||||
// 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
|
||||
if (pkm.Format <= 2 && !(EncounterMatch is EncounterTrade) && EncounterMatch.Species == pkm.Species && Legal.Trade_Evolution1.Contains(EncounterMatch.Species))
|
||||
VerifyG1TradeEvo(data);
|
||||
}
|
||||
private void VerifyG1TradeEvo(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var mustevolve = pkm.TradebackStatus == TradebackType.WasTradeback || (pkm.Format == 1 && Legal.IsOutsider(pkm)) || Legal.IsTradedKadabraG1(pkm);
|
||||
if (!mustevolve)
|
||||
return;
|
||||
// Pokemon have been traded but it is not evolved, trade evos are sequential dex numbers
|
||||
var unevolved = LegalityAnalysis.SpeciesStrings[pkm.Species];
|
||||
var evolved = LegalityAnalysis.SpeciesStrings[pkm.Species + 1];
|
||||
data.AddLine(GetInvalid(string.Format(V405, unevolved, evolved)));
|
||||
}
|
||||
}
|
||||
}
|
||||
68
PKHeX.Core/Legality/Verifiers/MedalVerifier.cs
Normal file
68
PKHeX.Core/Legality/Verifiers/MedalVerifier.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class MedalVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Training;
|
||||
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
VerifyMedalsRegular(data);
|
||||
VerifyMedalsEvent(data);
|
||||
}
|
||||
private void VerifyMedalsRegular(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var Info = data.Info;
|
||||
uint value = BitConverter.ToUInt32(pkm.Data, 0x2C);
|
||||
if ((value & 3) != 0) // 2 unused flags
|
||||
data.AddLine(GetInvalid(V98));
|
||||
int TrainCount = pkm.SuperTrainingMedalCount();
|
||||
|
||||
if (pkm.IsEgg && TrainCount > 0)
|
||||
data.AddLine(GetInvalid(V89));
|
||||
else if (TrainCount > 0 && Info.Generation > 6)
|
||||
data.AddLine(GetInvalid(V90));
|
||||
else
|
||||
{
|
||||
if (pkm.Format >= 7)
|
||||
{
|
||||
if (pkm.SecretSuperTrainingUnlocked)
|
||||
data.AddLine(GetInvalid(V91));
|
||||
if (pkm.SecretSuperTrainingComplete)
|
||||
data.AddLine(GetInvalid(V92));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TrainCount == 30 ^ pkm.SecretSuperTrainingComplete)
|
||||
data.AddLine(GetInvalid(V93));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyMedalsEvent(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var Info = data.Info;
|
||||
byte value = pkm.Data[0x3A];
|
||||
if ((value & 0xC0) != 0) // 2 unused flags highest bits
|
||||
data.AddLine(GetInvalid(V98));
|
||||
|
||||
int TrainCount = 0;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if ((value & 1) != 0)
|
||||
TrainCount++;
|
||||
value >>= 1;
|
||||
}
|
||||
if (pkm.IsEgg && TrainCount > 0)
|
||||
data.AddLine(GetInvalid(V89));
|
||||
else if (TrainCount > 0 && Info.Generation > 6)
|
||||
data.AddLine(GetInvalid(V90));
|
||||
else if (TrainCount > 0)
|
||||
data.AddLine(Get(V94, Severity.Fishy));
|
||||
}
|
||||
}
|
||||
}
|
||||
392
PKHeX.Core/Legality/Verifiers/MemoryVerifier.cs
Normal file
392
PKHeX.Core/Legality/Verifiers/MemoryVerifier.cs
Normal file
@@ -0,0 +1,392 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class MemoryVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Memory;
|
||||
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var hist = VerifyHistory(data);
|
||||
data.AddLine(hist);
|
||||
}
|
||||
private CheckResult VerifyHistory(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var Info = data.Info;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
|
||||
if (Info.Generation < 6)
|
||||
{
|
||||
if (pkm.Format < 6)
|
||||
return new CheckResult(Severity.Valid, V128, CheckIdentifier.History);
|
||||
|
||||
if (pkm.OT_Affection != 0 && Info.Generation <= 2 || IsInvalidContestAffection(pkm))
|
||||
return new CheckResult(Severity.Invalid, V129, CheckIdentifier.History);
|
||||
if (pkm.OT_Memory > 0 || pkm.OT_Feeling > 0 || pkm.OT_Intensity > 0 || pkm.OT_TextVar > 0)
|
||||
return new CheckResult(Severity.Invalid, V130, CheckIdentifier.History);
|
||||
}
|
||||
|
||||
if (pkm.Format >= 6 && Info.Generation != pkm.Format && pkm.CurrentHandler != 1)
|
||||
return new CheckResult(Severity.Invalid, V124, CheckIdentifier.History);
|
||||
|
||||
if (pkm.HT_Gender > 1)
|
||||
return new CheckResult(Severity.Invalid, string.Format(V131, pkm.HT_Gender), CheckIdentifier.History);
|
||||
|
||||
if (EncounterMatch is WC6 wc6 && wc6.OT_Name.Length > 0)
|
||||
{
|
||||
if (pkm.OT_Friendship != PersonalTable.AO[EncounterMatch.Species].BaseFriendship)
|
||||
return new CheckResult(Severity.Invalid, V132, CheckIdentifier.History);
|
||||
if (pkm.OT_Affection != 0 && (pkm.AO || !pkm.IsUntraded) && IsInvalidContestAffection(pkm))
|
||||
return new CheckResult(Severity.Invalid, V133, CheckIdentifier.History);
|
||||
if (pkm.CurrentHandler != 1)
|
||||
return new CheckResult(Severity.Invalid, V134, CheckIdentifier.History);
|
||||
}
|
||||
else if (EncounterMatch is WC7 wc7 && wc7.OT_Name.Length > 0 && wc7.TID != 18075) // Ash Pikachu QR Gift doesn't set Current Handler
|
||||
{
|
||||
if (pkm.OT_Friendship != PersonalTable.USUM[EncounterMatch.Species].BaseFriendship)
|
||||
return new CheckResult(Severity.Invalid, V132, CheckIdentifier.History);
|
||||
if (pkm.OT_Affection != 0)
|
||||
return new CheckResult(Severity.Invalid, V133, CheckIdentifier.History);
|
||||
if (pkm.CurrentHandler != 1)
|
||||
return new CheckResult(Severity.Invalid, V134, CheckIdentifier.History);
|
||||
}
|
||||
else if (EncounterMatch is MysteryGift mg && mg.Format < 6 && pkm.Format >= 6)
|
||||
{
|
||||
if (pkm.OT_Affection != 0 && IsInvalidContestAffection(pkm))
|
||||
return new CheckResult(Severity.Invalid, V133, CheckIdentifier.History);
|
||||
if (pkm.CurrentHandler != 1)
|
||||
return new CheckResult(Severity.Invalid, V134, CheckIdentifier.History);
|
||||
}
|
||||
|
||||
// Geolocations
|
||||
var geo = new[]
|
||||
{
|
||||
pkm.Geo1_Country, pkm.Geo2_Country, pkm.Geo3_Country, pkm.Geo4_Country, pkm.Geo5_Country,
|
||||
pkm.Geo1_Region, pkm.Geo2_Region, pkm.Geo3_Region, pkm.Geo4_Region, pkm.Geo5_Region,
|
||||
};
|
||||
|
||||
// Check sequential order (no zero gaps)
|
||||
bool geoEnd = false;
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
if (geoEnd && geo[i] != 0)
|
||||
return new CheckResult(Severity.Invalid, V135, CheckIdentifier.History);
|
||||
|
||||
if (geo[i] != 0)
|
||||
continue;
|
||||
if (geo[i + 5] != 0)
|
||||
return new CheckResult(Severity.Invalid, V136, CheckIdentifier.History);
|
||||
geoEnd = true;
|
||||
}
|
||||
if (pkm.Format >= 7)
|
||||
return VerifyHistory7(data, geo);
|
||||
|
||||
// Determine if we should check for Handling Trainer Memories
|
||||
// A Pokémon is untraded if...
|
||||
bool untraded = pkm.HT_Name.Length == 0 || pkm.Geo1_Country == 0;
|
||||
if (EncounterMatch is MysteryGift gift)
|
||||
{
|
||||
untraded |= !pkm.WasEventEgg;
|
||||
untraded &= gift.IsEgg;
|
||||
}
|
||||
|
||||
if (EncounterMatch is EncounterLink link && !link.OT)
|
||||
untraded = false;
|
||||
else if (Info.Generation < 6)
|
||||
untraded = false;
|
||||
|
||||
if (untraded) // Is not Traded
|
||||
{
|
||||
if (pkm.HT_Name.Length != 0)
|
||||
return new CheckResult(Severity.Invalid, V146, CheckIdentifier.History);
|
||||
if (pkm.Geo1_Country != 0)
|
||||
return new CheckResult(Severity.Invalid, V147, CheckIdentifier.History);
|
||||
if (pkm.HT_Memory != 0)
|
||||
return new CheckResult(Severity.Invalid, V148, CheckIdentifier.History);
|
||||
if (pkm.CurrentHandler != 0) // Badly edited; PKHeX doesn't trip this.
|
||||
return new CheckResult(Severity.Invalid, V139, CheckIdentifier.History);
|
||||
if (pkm.HT_Friendship != 0)
|
||||
return new CheckResult(Severity.Invalid, V140, CheckIdentifier.History);
|
||||
if (pkm.HT_Affection != 0)
|
||||
return new CheckResult(Severity.Invalid, V141, CheckIdentifier.History);
|
||||
if (pkm.XY && pkm is IContestStats s && s.HasContestStats())
|
||||
return new CheckResult(Severity.Invalid, V138, CheckIdentifier.History);
|
||||
|
||||
if (VerifyHistoryUntradedHandler(pkm, out CheckResult chk1))
|
||||
return chk1;
|
||||
if (EncounterMatch.Species != pkm.Species && VerifyHistoryUntradedEvolution(pkm, Info.EvoChainsAllGens, out CheckResult chk2))
|
||||
return chk2;
|
||||
}
|
||||
else // Is Traded
|
||||
{
|
||||
if (pkm.Format == 6 && pkm.HT_Memory == 0 && !pkm.IsEgg)
|
||||
return new CheckResult(Severity.Invalid, V150, CheckIdentifier.History);
|
||||
}
|
||||
|
||||
// Memory ChecksResult
|
||||
if (pkm.IsEgg)
|
||||
{
|
||||
if (pkm.HT_Memory != 0)
|
||||
return new CheckResult(Severity.Invalid, V149, CheckIdentifier.History);
|
||||
if (pkm.OT_Memory != 0)
|
||||
return new CheckResult(Severity.Invalid, V151, CheckIdentifier.History);
|
||||
}
|
||||
else if (!(EncounterMatch is WC6))
|
||||
{
|
||||
if (pkm.OT_Memory == 0 ^ !pkm.Gen6)
|
||||
return new CheckResult(Severity.Invalid, V152, CheckIdentifier.History);
|
||||
if (Info.Generation < 6 && pkm.OT_Affection != 0)
|
||||
return new CheckResult(Severity.Invalid, V129, CheckIdentifier.History);
|
||||
}
|
||||
// Unimplemented: Ingame Trade Memories
|
||||
|
||||
return new CheckResult(Severity.Valid, V145, CheckIdentifier.History);
|
||||
}
|
||||
private CheckResult VerifyHistory7(LegalityAnalysis data, int[] geo)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
var Info = data.Info;
|
||||
|
||||
if (pkm.VC1)
|
||||
{
|
||||
var hasGeo = geo.Any(d => d != 0);
|
||||
|
||||
if (!hasGeo)
|
||||
return new CheckResult(Severity.Invalid, V137, CheckIdentifier.History);
|
||||
}
|
||||
|
||||
if ((2 >= Info.Generation || Info.Generation >= 7) && pkm is IContestStats s && s.HasContestStats())
|
||||
return new CheckResult(Severity.Invalid, V138, CheckIdentifier.History);
|
||||
|
||||
if (!pkm.WasEvent && pkm.HT_Name.Length == 0) // Is not Traded
|
||||
{
|
||||
if (VerifyHistoryUntradedHandler(pkm, out CheckResult chk1))
|
||||
return chk1;
|
||||
if (EncounterMatch.Species != pkm.Species && VerifyHistoryUntradedEvolution(pkm, Info.EvoChainsAllGens, out CheckResult chk2))
|
||||
return chk2;
|
||||
}
|
||||
|
||||
return new CheckResult(Severity.Valid, V145, CheckIdentifier.History);
|
||||
}
|
||||
private static bool VerifyHistoryUntradedHandler(PKM pkm, out CheckResult result)
|
||||
{
|
||||
result = null;
|
||||
if (pkm.CurrentHandler != 0) // Badly edited; PKHeX doesn't trip this.
|
||||
result = new CheckResult(Severity.Invalid, V139, CheckIdentifier.History);
|
||||
else if (pkm.HT_Friendship != 0)
|
||||
result = new CheckResult(Severity.Invalid, V140, CheckIdentifier.History);
|
||||
else if (pkm.HT_Affection != 0)
|
||||
result = new CheckResult(Severity.Invalid, V141, CheckIdentifier.History);
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
private static bool VerifyHistoryUntradedEvolution(PKM pkm, IReadOnlyList<EvoCriteria>[] chain, out CheckResult result)
|
||||
{
|
||||
result = null;
|
||||
// Handling Trainer string is empty implying it has not been traded.
|
||||
// If it must be trade evolved, flag it.
|
||||
|
||||
if (pkm.Species == 350) // Milotic
|
||||
{
|
||||
if (Legal.IsTradeEvolved(chain, pkm.Format))
|
||||
return false;
|
||||
if (pkm is IContestStats s && s.CNT_Beauty < 170) // Beauty Contest Stat Requirement
|
||||
result = new CheckResult(Severity.Invalid, V143, CheckIdentifier.History);
|
||||
else if (pkm.CurrentLevel == 1)
|
||||
result = new CheckResult(Severity.Invalid, V144, CheckIdentifier.History);
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
if (!Legal.IsTradeEvolved(chain, pkm.Format))
|
||||
return false;
|
||||
result = new CheckResult(Severity.Invalid, V142, CheckIdentifier.History);
|
||||
return true;
|
||||
}
|
||||
private CheckResult VerifyCommonMemory(PKM pkm, int handler)
|
||||
{
|
||||
Memories.GetMemoryVariables(pkm, out int m, out int t, out int i, out int f, out string tr, handler);
|
||||
int matchingMoveMemory = Array.IndexOf(Memories.MoveSpecificMemories[0], m);
|
||||
if (matchingMoveMemory != -1 && pkm.Species != 235 && !Legal.GetCanLearnMachineMove(pkm, Memories.MoveSpecificMemories[1][matchingMoveMemory], 6))
|
||||
return new CheckResult(Severity.Invalid, string.Format(V153, tr), CheckIdentifier.Memory);
|
||||
|
||||
if (m == 6 && !Memories.LocationsWithPKCenter[0].Contains(t))
|
||||
return new CheckResult(Severity.Invalid, string.Format(V154, tr), CheckIdentifier.Memory);
|
||||
|
||||
if (m == 21) // {0} saw {2} carrying {1} on its back. {4} that {3}.
|
||||
if (!Legal.GetCanLearnMachineMove(new PK6 { Species = t, EXP = PKX.GetEXP(100, t) }, 19, 6))
|
||||
return new CheckResult(Severity.Invalid, string.Format(V153, tr), CheckIdentifier.Memory);
|
||||
|
||||
if ((m == 16 || m == 48) && (t == 0 || !Legal.GetCanKnowMove(pkm, t, 6)))
|
||||
return new CheckResult(Severity.Invalid, string.Format(V153, tr), CheckIdentifier.Memory);
|
||||
|
||||
if (m == 49 && (t == 0 || !Legal.GetCanRelearnMove(pkm, t, 6))) // {0} was able to remember {2} at {1}'s instruction. {4} that {3}.
|
||||
return new CheckResult(Severity.Invalid, string.Format(V153, tr), CheckIdentifier.Memory);
|
||||
|
||||
if (i < Memories.MemoryMinIntensity[m])
|
||||
return new CheckResult(Severity.Invalid, string.Format(V254, tr, Memories.MemoryMinIntensity[m]), CheckIdentifier.Memory);
|
||||
|
||||
if (m != 4 && (Memories.MemoryFeelings[m] & (1 << f)) == 0)
|
||||
return new CheckResult(Severity.Invalid, string.Format(V255, tr), CheckIdentifier.Memory);
|
||||
|
||||
return new CheckResult(Severity.Valid, string.Format(V155, tr), CheckIdentifier.Memory);
|
||||
}
|
||||
|
||||
private void VerifyOTMemoryIs(LegalityAnalysis data, int m, int i, int t, int f)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (pkm.OT_Memory != m)
|
||||
data.AddLine(Severity.Invalid, string.Format(V197, V205, m), CheckIdentifier.Memory);
|
||||
if (pkm.OT_Intensity != i)
|
||||
data.AddLine(Severity.Invalid, string.Format(V198, V205, i), CheckIdentifier.Memory);
|
||||
if (pkm.OT_TextVar != t)
|
||||
data.AddLine(Severity.Invalid, string.Format(V199, V205, t), CheckIdentifier.Memory);
|
||||
if (pkm.OT_Feeling != f)
|
||||
data.AddLine(Severity.Invalid, string.Format(V200, V205, f), CheckIdentifier.Memory);
|
||||
}
|
||||
public void VerifyOTMemory(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (pkm.Format < 6)
|
||||
return;
|
||||
|
||||
var Info = data.Info;
|
||||
if (Info.Generation < 6 || pkm.IsEgg)
|
||||
{
|
||||
VerifyOTMemoryIs(data, 0, 0, 0, 0); // empty
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data.EncounterMatch)
|
||||
{
|
||||
case EncounterTrade _:
|
||||
switch (Info.Generation)
|
||||
{
|
||||
case 6:
|
||||
break; // Undocumented, uncommon, and insignificant -- don't bother.
|
||||
case 7:
|
||||
VerifyOTMemoryIs(data, 1, 3, 40, 5);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
case WC6 g when !g.IsEgg:
|
||||
VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling);
|
||||
return;
|
||||
case WC7 g when !g.IsEgg:
|
||||
VerifyOTMemoryIs(data,g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Info.Generation >= 7)
|
||||
{
|
||||
VerifyOTMemoryIs(data, 0, 0, 0, 0); // empty
|
||||
return;
|
||||
}
|
||||
|
||||
switch (pkm.OT_Memory)
|
||||
{
|
||||
case 2: // {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}.
|
||||
if (pkm.Egg_Location == 0)
|
||||
data.AddLine(Severity.Invalid, string.Format(V160, V205), CheckIdentifier.Memory);
|
||||
break;
|
||||
|
||||
case 4: // {0} became {1}’s friend when it arrived via Link Trade at... {2}. {4} that {3}.
|
||||
data.AddLine(Severity.Invalid, string.Format(V161, V205), CheckIdentifier.Memory);
|
||||
return;
|
||||
|
||||
case 6: // {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}.
|
||||
int matchingOriginGame = Array.IndexOf(Memories.LocationsWithPKCenter[0], pkm.OT_TextVar);
|
||||
if (matchingOriginGame != -1)
|
||||
{
|
||||
int gameID = Memories.LocationsWithPKCenter[1][matchingOriginGame];
|
||||
if (pkm.XY && gameID != 0 || pkm.AO && gameID != 1)
|
||||
data.AddLine(Severity.Invalid, string.Format(V162, V205), CheckIdentifier.Memory);
|
||||
}
|
||||
data.AddLine(VerifyCommonMemory(pkm, 0));
|
||||
return;
|
||||
|
||||
case 14:
|
||||
if (!Legal.GetCanBeCaptured(pkm.OT_TextVar, Info.Generation, (GameVersion)pkm.Version))
|
||||
data.AddLine(Severity.Invalid, string.Format(V165, V205), CheckIdentifier.Memory);
|
||||
else
|
||||
data.AddLine(Severity.Valid, string.Format(V164, V205), CheckIdentifier.Memory);
|
||||
return;
|
||||
}
|
||||
if (pkm.XY && Memories.Memory_NotXY.Contains(pkm.OT_Memory))
|
||||
data.AddLine(Severity.Invalid, string.Format(V163, V205), CheckIdentifier.Memory);
|
||||
if (pkm.AO && Memories.Memory_NotAO.Contains(pkm.OT_Memory))
|
||||
data.AddLine(Severity.Invalid, string.Format(V163, V205), CheckIdentifier.Memory);
|
||||
|
||||
data.AddLine(VerifyCommonMemory(pkm, 0));
|
||||
}
|
||||
public void VerifyHTMemory(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (pkm.Format < 6)
|
||||
return;
|
||||
|
||||
var Info = data.Info;
|
||||
if (pkm.Format >= 7)
|
||||
{
|
||||
/*
|
||||
* Bank Transfer adds in the Link Trade Memory.
|
||||
* Trading 7<->7 between games (not Bank) clears this data.
|
||||
*/
|
||||
if (pkm.HT_Memory == 0)
|
||||
{
|
||||
if (pkm.HT_TextVar != 0 || pkm.HT_Intensity != 0 || pkm.HT_Feeling != 0)
|
||||
data.AddLine(Severity.Invalid, V329, CheckIdentifier.Memory);
|
||||
return;
|
||||
}
|
||||
|
||||
// Transfer 6->7 & withdraw to same HT => keeps past gen memory
|
||||
// Don't require link trade memory for these past gen cases
|
||||
int gen = Info.Generation;
|
||||
if (3 <= gen && gen < 7 && pkm.CurrentHandler == 1)
|
||||
return;
|
||||
|
||||
if (pkm.HT_Memory != 4)
|
||||
data.AddLine(Severity.Invalid, V156, CheckIdentifier.Memory);
|
||||
if (pkm.HT_TextVar != 0)
|
||||
data.AddLine(Severity.Invalid, V157, CheckIdentifier.Memory);
|
||||
if (pkm.HT_Intensity != 1)
|
||||
data.AddLine(Severity.Invalid, V158, CheckIdentifier.Memory);
|
||||
if (pkm.HT_Feeling > 10)
|
||||
data.AddLine(Severity.Invalid, V159, CheckIdentifier.Memory);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (pkm.HT_Memory)
|
||||
{
|
||||
case 0:
|
||||
if (string.IsNullOrEmpty(pkm.HT_Name))
|
||||
return;
|
||||
data.AddLine(Severity.Invalid, V150, CheckIdentifier.Memory); return;
|
||||
case 1: // {0} met {1} at... {2}. {1} threw a Poké Ball at it, and they started to travel together. {4} that {3}.
|
||||
data.AddLine(Severity.Invalid, string.Format(V202, V206), CheckIdentifier.Memory); return;
|
||||
|
||||
case 2: // {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}.
|
||||
data.AddLine(Severity.Invalid, string.Format(V160, V206), CheckIdentifier.Memory); return;
|
||||
|
||||
case 14:
|
||||
if (Legal.GetCanBeCaptured(pkm.HT_TextVar, 6))
|
||||
data.AddLine(Severity.Valid, string.Format(V164, V206), CheckIdentifier.Memory);
|
||||
else
|
||||
data.AddLine(Severity.Invalid, string.Format(V165, V206), CheckIdentifier.Memory);
|
||||
return;
|
||||
}
|
||||
data.AddLine(VerifyCommonMemory(pkm, 1));
|
||||
}
|
||||
|
||||
// ORAS contests mistakenly apply 20 affection to the OT instead of the current handler's value
|
||||
private static bool IsInvalidContestAffection(PKM pkm) => pkm.OT_Affection != 255 && pkm.OT_Affection % 20 != 0;
|
||||
}
|
||||
}
|
||||
233
PKHeX.Core/Legality/Verifiers/MiscVerifier.cs
Normal file
233
PKHeX.Core/Legality/Verifiers/MiscVerifier.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
PKHeX.Core/Legality/Verifiers/NHarmoniaVerifier.cs
Normal file
46
PKHeX.Core/Legality/Verifiers/NHarmoniaVerifier.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class NHarmoniaVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Trainer;
|
||||
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
|
||||
bool checksRequired = EncounterMatch is EncounterStaticPID s && s.NSparkle;
|
||||
if (pkm is PK5 pk5)
|
||||
{
|
||||
bool has = pk5.NPokémon;
|
||||
if (checksRequired && !has)
|
||||
data.AddLine(GetInvalid(V326, CheckIdentifier.Fateful));
|
||||
if (!checksRequired && has)
|
||||
data.AddLine(GetInvalid(V327, CheckIdentifier.Fateful));
|
||||
}
|
||||
|
||||
if (!checksRequired)
|
||||
return;
|
||||
|
||||
if (pkm.IVs.Any(iv => iv != 30))
|
||||
data.AddLine(GetInvalid(V218, CheckIdentifier.IVs));
|
||||
if (!VerifyNsPKMOTValid(pkm))
|
||||
data.AddLine(GetInvalid(V219, CheckIdentifier.Trainer));
|
||||
if (pkm.IsShiny)
|
||||
data.AddLine(GetInvalid(V220, CheckIdentifier.Shiny));
|
||||
}
|
||||
private static bool VerifyNsPKMOTValid(PKM pkm)
|
||||
{
|
||||
if (pkm.TID != 00002 || pkm.SID != 00000)
|
||||
return false;
|
||||
var ot = pkm.OT_Name;
|
||||
if (ot.Length != 1)
|
||||
return false;
|
||||
var c = Legal.GetG5OT_NSparkle(pkm.Language);
|
||||
return c == ot;
|
||||
}
|
||||
}
|
||||
}
|
||||
386
PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs
Normal file
386
PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs
Normal file
@@ -0,0 +1,386 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public sealed class NicknameVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Nickname;
|
||||
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
|
||||
// If the Pokémon is not nicknamed, it should match one of the language strings.
|
||||
if (pkm.Nickname.Length == 0)
|
||||
{
|
||||
data.AddLine(GetInvalid(V2));
|
||||
return;
|
||||
}
|
||||
if (pkm.Species > PKX.SpeciesLang[0].Length)
|
||||
{
|
||||
data.AddLine(Get(V2, Severity.Indeterminate));
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkm.VC && pkm.IsNicknamed)
|
||||
{
|
||||
VerifyG1NicknameWithinBounds(data, pkm.Nickname);
|
||||
}
|
||||
else if (EncounterMatch is MysteryGift m)
|
||||
{
|
||||
if (pkm.IsNicknamed && !m.IsEgg)
|
||||
data.AddLine(Get(V0, Severity.Fishy));
|
||||
}
|
||||
|
||||
if (EncounterMatch is EncounterTrade)
|
||||
{
|
||||
VerifyNicknameTrade(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkm.IsEgg)
|
||||
{
|
||||
VerifyNicknameEgg(data);
|
||||
return;
|
||||
}
|
||||
|
||||
string nickname = pkm.Nickname.Replace("'", "’");
|
||||
if (VerifyUnNicknamedEncounter(data, pkm, nickname))
|
||||
return;
|
||||
|
||||
// Non-nicknamed strings have already been checked.
|
||||
if (Legal.CheckWordFilter && pkm.IsNicknamed && WordFilter.IsFiltered(nickname, out string bad))
|
||||
data.AddLine(GetInvalid($"Wordfilter: {bad}"));
|
||||
}
|
||||
|
||||
private bool VerifyUnNicknamedEncounter(LegalityAnalysis data, PKM pkm, string nickname)
|
||||
{
|
||||
if (pkm.IsNicknamed)
|
||||
{
|
||||
for (int i = 0; i < PKX.SpeciesLang.Length; i++)
|
||||
{
|
||||
if (!PKX.SpeciesDict[i].TryGetValue(nickname, out int index))
|
||||
continue;
|
||||
var msg = index == pkm.Species && i != pkm.Language ? V15 : V16;
|
||||
data.AddLine(Get(msg, Severity.Fishy));
|
||||
return true;
|
||||
}
|
||||
if (StringConverter.HasEastAsianScriptCharacters(nickname)) // East Asian Scripts
|
||||
{
|
||||
data.AddLine(GetInvalid(V222));
|
||||
return true;
|
||||
}
|
||||
data.AddLine(GetValid(V17));
|
||||
}
|
||||
else if (pkm.Format < 3)
|
||||
{
|
||||
// pk1/pk2 IsNicknamed getter checks for match, logic should only reach here if matches.
|
||||
data.AddLine(GetValid(V18));
|
||||
}
|
||||
else
|
||||
{
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
// Can't have another language name if it hasn't evolved or wasn't a language-traded egg.
|
||||
bool evolved = EncounterMatch.Species != pkm.Species;
|
||||
bool match = PKX.GetSpeciesNameGeneration(pkm.Species, pkm.Language, pkm.Format) == nickname;
|
||||
if (pkm.WasTradedEgg || evolved)
|
||||
match |= !PKX.IsNicknamedAnyLanguage(pkm.Species, nickname, pkm.Format);
|
||||
if (!match && pkm.Format == 5 && !pkm.IsNative) // transfer
|
||||
{
|
||||
if (evolved)
|
||||
match |= !PKX.IsNicknamedAnyLanguage(pkm.Species, nickname, 4);
|
||||
else
|
||||
match |= PKX.GetSpeciesNameGeneration(pkm.Species, pkm.Language, 4) == nickname;
|
||||
}
|
||||
|
||||
if (!match)
|
||||
{
|
||||
if (EncounterMatch is WC7 wc7 && wc7.IsAshGreninjaWC7(pkm))
|
||||
data.AddLine(GetValid(V19));
|
||||
else
|
||||
data.AddLine(GetInvalid(V20));
|
||||
}
|
||||
else
|
||||
data.AddLine(GetValid(V18));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void VerifyNicknameEgg(LegalityAnalysis data)
|
||||
{
|
||||
var Info = data.Info;
|
||||
var pkm = data.pkm;
|
||||
var EncounterMatch = Info.EncounterMatch;
|
||||
switch (pkm.Format)
|
||||
{
|
||||
case 4:
|
||||
if (pkm.IsNicknamed) // gen4 doesn't use the nickname flag for eggs
|
||||
data.AddLine(GetInvalid(V224, CheckIdentifier.Egg));
|
||||
break;
|
||||
case 7:
|
||||
if (EncounterMatch is EncounterStatic ^ !pkm.IsNicknamed) // gen7 doesn't use for ingame gifts
|
||||
data.AddLine(GetInvalid(pkm.IsNicknamed ? V224 : V12, CheckIdentifier.Egg));
|
||||
break;
|
||||
default:
|
||||
if (!pkm.IsNicknamed)
|
||||
data.AddLine(GetInvalid(V12, CheckIdentifier.Egg));
|
||||
break;
|
||||
}
|
||||
|
||||
if (pkm.Format == 2 && pkm.IsEgg && !PKX.IsNicknamedAnyLanguage(0, pkm.Nickname, 2))
|
||||
data.AddLine(GetValid(V14, CheckIdentifier.Egg));
|
||||
else if (PKX.GetSpeciesNameGeneration(0, pkm.Language, Info.Generation) != pkm.Nickname)
|
||||
data.AddLine(GetInvalid(V13, CheckIdentifier.Egg));
|
||||
else
|
||||
data.AddLine(GetValid(V14, CheckIdentifier.Egg));
|
||||
}
|
||||
private void VerifyNicknameTrade(LegalityAnalysis data)
|
||||
{
|
||||
switch (data.Info.Generation)
|
||||
{
|
||||
case 1:
|
||||
case 2: VerifyTrade12(data); return;
|
||||
case 3: VerifyTrade3(data); return;
|
||||
case 4: VerifyTrade4(data); return;
|
||||
case 5: VerifyTrade5(data); return;
|
||||
case 6: VerifyTrade6(data); return;
|
||||
case 7: VerifyTrade7(data); return;
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyG1NicknameWithinBounds(LegalityAnalysis data, string str)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (StringConverter.GetIsG1English(str))
|
||||
{
|
||||
if (str.Length > 10)
|
||||
data.AddLine(GetInvalid(V1));
|
||||
}
|
||||
else if (StringConverter.GetIsG1Japanese(str))
|
||||
{
|
||||
if (str.Length > 5)
|
||||
data.AddLine(GetInvalid(V1));
|
||||
}
|
||||
else if (pkm.Korean && StringConverter.GetIsG2Korean(str))
|
||||
{
|
||||
if (str.Length > 5)
|
||||
data.AddLine(GetInvalid(V38));
|
||||
}
|
||||
else
|
||||
{
|
||||
data.AddLine(GetInvalid(V422));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void VerifyTrade12(LegalityAnalysis data)
|
||||
{
|
||||
var et = (EncounterTrade)data.EncounterOriginal;
|
||||
if (et.TID != 0) // Gen2 Trade
|
||||
return; // already checked all relevant properties when fetching with getValidEncounterTradeVC2
|
||||
|
||||
if (!EncounterGenerator.IsEncounterTrade1Valid(data.pkm))
|
||||
data.AddLine(GetInvalid(V10, CheckIdentifier.Trainer));
|
||||
}
|
||||
private void VerifyTrade3(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
if (pkm.FRLG)
|
||||
{
|
||||
int lang = pkm.Language;
|
||||
if (EncounterMatch.Species == 124) // Jynx
|
||||
lang = DetectTradeLanguageG3DANTAEJynx(pkm, lang);
|
||||
VerifyTradeTable(data, Encounters3.TradeFRLG, Encounters3.TradeGift_FRLG, lang);
|
||||
}
|
||||
else
|
||||
VerifyTradeTable(data, Encounters3.TradeRSE, Encounters3.TradeGift_RSE);
|
||||
}
|
||||
private void VerifyTrade4(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
if (pkm.TID == 1000)
|
||||
{
|
||||
VerifyTrade4Ranch(data);
|
||||
return;
|
||||
}
|
||||
if (pkm.HGSS)
|
||||
{
|
||||
int lang = pkm.Language;
|
||||
if (EncounterMatch.Species == 25) // Pikachu
|
||||
{
|
||||
lang = DetectTradeLanguageG4SurgePikachu(pkm, lang);
|
||||
// flag korean magikarp on gen4 saves since the pkm.Language is German
|
||||
if (pkm.Format == 4 && lang == (int)LanguageID.Korean && Legal.SavegameLanguage != (int)LanguageID.Korean && Legal.SavegameLanguage >= 0)
|
||||
data.AddLine(GetInvalid(string.Format(V610, V611, V612), CheckIdentifier.Language));
|
||||
}
|
||||
VerifyTradeTable(data, Encounters4.TradeHGSS, Encounters4.TradeGift_HGSS, lang);
|
||||
}
|
||||
else // DPPt
|
||||
{
|
||||
int lang = pkm.Language;
|
||||
if (EncounterMatch.Species == 129) // Magikarp
|
||||
{
|
||||
lang = DetectTradeLanguageG4MeisterMagikarp(pkm, lang);
|
||||
// flag korean magikarp on gen4 saves since the pkm.Language is German
|
||||
if (pkm.Format == 4 && lang == (int)LanguageID.Korean && Legal.SavegameLanguage != (int)LanguageID.Korean && Legal.SavegameLanguage >= 0)
|
||||
data.AddLine(GetInvalid(string.Format(V610, V611, V612), CheckIdentifier.Language));
|
||||
}
|
||||
else if (!pkm.Pt && lang == 1) // DP English origin are Japanese lang
|
||||
{
|
||||
int index = Array.IndexOf(Encounters4.TradeGift_DPPt, data.EncounterMatch);
|
||||
if (Encounters4.TradeDPPt[1][index] != pkm.Nickname) // not japanese
|
||||
lang = 2; // English
|
||||
}
|
||||
VerifyTradeTable(data, Encounters4.TradeDPPt, Encounters4.TradeGift_DPPt, lang);
|
||||
}
|
||||
}
|
||||
private static int DetectTradeLanguageG3DANTAEJynx(PKM pk, int lang)
|
||||
{
|
||||
if (lang != (int)LanguageID.Italian)
|
||||
return lang;
|
||||
|
||||
if (pk.Version == (int)GameVersion.LG)
|
||||
lang = (int)LanguageID.English; // translation error; OT was not localized => same as English
|
||||
return lang;
|
||||
}
|
||||
private static int DetectTradeLanguageG4MeisterMagikarp(PKM pkm, int lang)
|
||||
{
|
||||
if (lang == (int)LanguageID.English)
|
||||
return (int)LanguageID.German;
|
||||
|
||||
// All have German, regardless of origin version.
|
||||
// Detect which language they originated from... roughly.
|
||||
var table = Encounters4.TradeDPPt;
|
||||
for (int i = 0; i < table.Length; i++)
|
||||
{
|
||||
if (table[i].Length == 0)
|
||||
continue;
|
||||
// Nick @ 3, OT @ 7
|
||||
if (table[i][7] != pkm.OT_Name)
|
||||
continue;
|
||||
lang = i;
|
||||
break;
|
||||
}
|
||||
if (lang == 2) // possible collision with FR/ES/DE. Check nickname
|
||||
return pkm.Nickname == table[3][3] ? (int)LanguageID.French : (int)LanguageID.Spanish; // Spanish is same as English
|
||||
|
||||
return lang;
|
||||
}
|
||||
private static int DetectTradeLanguageG4SurgePikachu(PKM pkm, int lang)
|
||||
{
|
||||
if (lang == (int)LanguageID.French)
|
||||
return (int)LanguageID.English;
|
||||
|
||||
// All have English, regardless of origin version.
|
||||
// Detect which language they originated from... roughly.
|
||||
var table = Encounters4.TradeHGSS;
|
||||
for (int i = 0; i < table.Length; i++)
|
||||
{
|
||||
if (table[i].Length == 0)
|
||||
continue;
|
||||
// Nick @ 6, OT @ 18
|
||||
if (table[i][18] != pkm.OT_Name)
|
||||
continue;
|
||||
lang = i;
|
||||
break;
|
||||
}
|
||||
if (lang == 2) // possible collision with ES/IT. Check nickname
|
||||
return pkm.Nickname == table[4][6] ? (int)LanguageID.Italian : (int)LanguageID.Spanish;
|
||||
|
||||
return lang;
|
||||
}
|
||||
private void VerifyTrade5(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
// Trades for JPN games have language ID of 0, not 1.
|
||||
if (pkm.BW)
|
||||
{
|
||||
int lang = pkm.Language;
|
||||
if (pkm.Format == 5 && lang == (int)LanguageID.Japanese)
|
||||
data.AddLine(GetInvalid(string.Format(V5, 0, (int)LanguageID.Japanese), CheckIdentifier.Language));
|
||||
|
||||
lang = Math.Max(lang, 1);
|
||||
VerifyTradeTable(data, Encounters5.TradeBW, Encounters5.TradeGift_BW, lang);
|
||||
}
|
||||
else // B2W2
|
||||
{
|
||||
if (Encounters5.TradeGift_B2W2_YancyCurtis.Contains(EncounterMatch))
|
||||
VerifyTradeOTOnly(data, pkm.OT_Gender == 0 ? Encounters5.TradeOT_B2W2_M : Encounters5.TradeOT_B2W2_F);
|
||||
else
|
||||
VerifyTradeTable(data, Encounters5.TradeB2W2, Encounters5.TradeGift_B2W2);
|
||||
}
|
||||
}
|
||||
private void VerifyTrade6(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (pkm.XY)
|
||||
VerifyTradeTable(data, Encounters6.TradeXY, Encounters6.TradeGift_XY, pkm.Language);
|
||||
else if (pkm.AO)
|
||||
VerifyTradeTable(data, Encounters6.TradeAO, Encounters6.TradeGift_AO, pkm.Language);
|
||||
}
|
||||
private void VerifyTrade7(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (pkm.SM)
|
||||
VerifyTradeTable(data, Encounters7.TradeSM, Encounters7.TradeGift_SM, pkm.Language);
|
||||
else if (pkm.USUM)
|
||||
VerifyTradeTable(data, Encounters7.TradeUSUM, Encounters7.TradeGift_USUM, pkm.Language);
|
||||
}
|
||||
private void VerifyTrade4Ranch(LegalityAnalysis data) => VerifyTradeOTOnly(data, Encounters4.RanchOTNames);
|
||||
|
||||
private void VerifyTradeTable(LegalityAnalysis data, string[][] ots, EncounterTrade[] table) => VerifyTradeTable(data, ots, table, data.pkm.Language);
|
||||
private void VerifyTradeTable(LegalityAnalysis data, string[][] ots, EncounterTrade[] table, int language)
|
||||
{
|
||||
var validOT = language >= ots.Length ? ots[0] : ots[language];
|
||||
var index = Array.IndexOf(table, data.EncounterMatch);
|
||||
VerifyTradeOTNick(data, validOT, index);
|
||||
}
|
||||
private void VerifyTradeOTOnly(LegalityAnalysis data, string[] validOT)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (pkm.IsNicknamed)
|
||||
data.AddLine(GetInvalid(V9, CheckIdentifier.Nickname));
|
||||
int lang = pkm.Language;
|
||||
if (validOT.Length <= lang)
|
||||
data.AddLine(GetInvalid(V8, CheckIdentifier.Trainer));
|
||||
else if (validOT[lang] != pkm.OT_Name)
|
||||
data.AddLine(GetInvalid(V10, CheckIdentifier.Trainer));
|
||||
else
|
||||
data.AddLine(GetValid(V11, CheckIdentifier.Nickname));
|
||||
}
|
||||
private void VerifyTradeOTNick(LegalityAnalysis data, string[] validOT, int index)
|
||||
{
|
||||
if (validOT.Length == 0)
|
||||
{
|
||||
data.AddLine(Get(V7, Severity.Indeterminate, CheckIdentifier.Trainer));
|
||||
return;
|
||||
}
|
||||
if (index == -1 || validOT.Length < index * 2)
|
||||
{
|
||||
data.AddLine(Get(V8, Severity.Indeterminate, CheckIdentifier.Trainer));
|
||||
return;
|
||||
}
|
||||
|
||||
string nick = validOT[index];
|
||||
string OT = validOT[validOT.Length / 2 + index];
|
||||
|
||||
var pkm = data.pkm;
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
if (nick != pkm.Nickname
|
||||
&& !(nick == "Quacklin’" && pkm.Nickname == "Quacklin'") // apostrophe farfetch'd edge case
|
||||
&& ((EncounterTrade)EncounterMatch).IsNicknamed) // trades that are not nicknamed (but are present in a table with others being named)
|
||||
data.AddLine(GetInvalid(V9, CheckIdentifier.Nickname));
|
||||
else
|
||||
data.AddLine(GetValid(V11, CheckIdentifier.Nickname));
|
||||
|
||||
if (OT != pkm.OT_Name)
|
||||
data.AddLine(GetInvalid(V10, CheckIdentifier.Trainer));
|
||||
}
|
||||
}
|
||||
}
|
||||
127
PKHeX.Core/Legality/Verifiers/PIDVerifier.cs
Normal file
127
PKHeX.Core/Legality/Verifiers/PIDVerifier.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class PIDVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.PID;
|
||||
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (pkm.Format >= 6)
|
||||
VerifyEC(data);
|
||||
|
||||
var EncounterMatch = data.EncounterMatch;
|
||||
if (EncounterMatch.Species == 265)
|
||||
VerifyECPIDWurmple(data);
|
||||
|
||||
if (pkm.PID == 0)
|
||||
data.AddLine(Get(V207, Severity.Fishy));
|
||||
|
||||
var Info = data.Info;
|
||||
if ((Info.Generation >= 6 || Info.Generation < 3 && pkm.Format >= 7) && pkm.PID == pkm.EncryptionConstant)
|
||||
data.AddLine(GetInvalid(V208)); // better to flag than 1:2^32 odds since RNG is not feasible to yield match
|
||||
|
||||
VerifyShiny(data);
|
||||
}
|
||||
|
||||
private void VerifyShiny(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var Info = data.Info;
|
||||
|
||||
switch (data.EncounterMatch)
|
||||
{
|
||||
case EncounterStatic s:
|
||||
if (!s.Shiny.IsValid(pkm))
|
||||
data.AddLine(GetInvalid(V209, CheckIdentifier.Shiny));
|
||||
|
||||
// gen5 correlation
|
||||
if (Info.Generation != 5)
|
||||
break;
|
||||
if (s.Location == 75) // Entree Forest
|
||||
break;
|
||||
if (s.Gift || s.Roaming || s.Ability != 4)
|
||||
break;
|
||||
if (s is EncounterStaticPID p && p.NSparkle)
|
||||
break;
|
||||
VerifyG5PID_IDCorrelation(data);
|
||||
break;
|
||||
|
||||
case EncounterSlot w:
|
||||
if (pkm.IsShiny && w.Type == SlotType.HiddenGrotto)
|
||||
data.AddLine(GetInvalid(V221, CheckIdentifier.Shiny));
|
||||
if (Info.Generation == 5 && w.Type != SlotType.HiddenGrotto)
|
||||
VerifyG5PID_IDCorrelation(data);
|
||||
break;
|
||||
|
||||
case PCD d: // fixed PID
|
||||
if (d.Gift.PK.PID != 1 && pkm.EncryptionConstant != d.Gift.PK.PID)
|
||||
data.AddLine(GetInvalid(V410, CheckIdentifier.Shiny));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyG5PID_IDCorrelation(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var pid = pkm.EncryptionConstant;
|
||||
var result = (pid & 1) ^ (pid >> 31) ^ (pkm.TID & 1) ^ (pkm.SID & 1);
|
||||
if (result != 0)
|
||||
data.AddLine(GetInvalid(V411));
|
||||
}
|
||||
private void VerifyECPIDWurmple(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
uint evoVal = PKX.GetWurmpleEvoVal(pkm.EncryptionConstant);
|
||||
|
||||
if (pkm.Species == 265)
|
||||
{
|
||||
var spec = evoVal == 0 ? LegalityAnalysis.SpeciesStrings[267] : LegalityAnalysis.SpeciesStrings[269];
|
||||
var msg = string.Format(V212, spec);
|
||||
data.AddLine(GetValid(msg, CheckIdentifier.EC));
|
||||
}
|
||||
else if (evoVal != Array.IndexOf(Legal.WurmpleEvolutions, pkm.Species) / 2)
|
||||
{
|
||||
data.AddLine(GetInvalid(V210, CheckIdentifier.EC));
|
||||
}
|
||||
}
|
||||
private void VerifyEC(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
var Info = data.Info;
|
||||
|
||||
if (pkm.EncryptionConstant == 0)
|
||||
data.AddLine(Get(V201, Severity.Fishy, CheckIdentifier.EC));
|
||||
if (3 <= Info.Generation && Info.Generation <= 5)
|
||||
VerifyTransferEC(data);
|
||||
else
|
||||
{
|
||||
int xor = pkm.TSV ^ pkm.PSV;
|
||||
if (xor < 16 && xor >= 8 && (pkm.PID ^ 0x80000000) == pkm.EncryptionConstant)
|
||||
data.AddLine(Get(V211, Severity.Fishy, CheckIdentifier.EC));
|
||||
}
|
||||
}
|
||||
private void VerifyTransferEC(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
// When transferred to Generation 6, the Encryption Constant is copied from the PID.
|
||||
// The PID is then checked to see if it becomes shiny with the new Shiny rules (>>4 instead of >>3)
|
||||
// If the PID is nonshiny->shiny, the top bit is flipped.
|
||||
|
||||
// Check to see if the PID and EC are properly configured.
|
||||
bool xorPID = ((pkm.TID ^ pkm.SID ^ (int)(pkm.PID & 0xFFFF) ^ (int)(pkm.PID >> 16)) & ~0x7) == 8;
|
||||
bool valid = xorPID
|
||||
? pkm.EncryptionConstant == (pkm.PID ^ 0x80000000)
|
||||
: pkm.EncryptionConstant == pkm.PID;
|
||||
|
||||
if (valid)
|
||||
return;
|
||||
|
||||
var msg = xorPID ? V215 : V216;
|
||||
data.AddLine(GetInvalid(msg, CheckIdentifier.EC));
|
||||
}
|
||||
}
|
||||
}
|
||||
116
PKHeX.Core/Legality/Verifiers/TrainerNameVerifier.cs
Normal file
116
PKHeX.Core/Legality/Verifiers/TrainerNameVerifier.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class TrainerNameVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Trainer;
|
||||
|
||||
private readonly string[] SuspiciousOTNames =
|
||||
{
|
||||
"PKHeX",
|
||||
};
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
switch (data.EncounterMatch)
|
||||
{
|
||||
case EncounterTrade _:
|
||||
case MysteryGift g when !g.IsEgg:
|
||||
case EncounterStaticPID s when s.NSparkle:
|
||||
return; // already verified
|
||||
}
|
||||
|
||||
var ot = pkm.OT_Name;
|
||||
if (ot.Length == 0)
|
||||
data.AddLine(GetInvalid(V106));
|
||||
|
||||
if (pkm.TID == 0 && pkm.SID == 0)
|
||||
data.AddLine(Get(V33, Severity.Fishy));
|
||||
else if (pkm.VC)
|
||||
{
|
||||
if (pkm.SID != 0)
|
||||
data.AddLine(GetInvalid(V34));
|
||||
}
|
||||
else if (pkm.TID == pkm.SID)
|
||||
data.AddLine(Get(V35, Severity.Fishy));
|
||||
else if (pkm.TID == 0)
|
||||
data.AddLine(Get(V36, Severity.Fishy));
|
||||
else if (pkm.SID == 0)
|
||||
data.AddLine(Get(V37, Severity.Fishy));
|
||||
else if (pkm.TID == 12345 && pkm.SID == 54321 || SuspiciousOTNames.Any(z => ot.StartsWith(z)))
|
||||
data.AddLine(Get(V417, Severity.Fishy));
|
||||
|
||||
if (pkm.VC)
|
||||
VerifyOTG1(data);
|
||||
|
||||
if (Legal.CheckWordFilter)
|
||||
{
|
||||
if (WordFilter.IsFiltered(ot, out string bad))
|
||||
data.AddLine(GetInvalid($"Wordfilter: {bad}"));
|
||||
if (WordFilter.IsFiltered(pkm.HT_Name, out bad))
|
||||
data.AddLine(GetInvalid($"Wordfilter: {bad}"));
|
||||
}
|
||||
}
|
||||
public void VerifyOTG1(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
string tr = pkm.OT_Name;
|
||||
|
||||
VerifyG1OTWithinBounds(data, tr);
|
||||
if (data.EncounterOriginal is EncounterStatic s && (s.Version == GameVersion.Stadium || s.Version == GameVersion.Stadium2))
|
||||
VerifyG1OTStadium(data, tr);
|
||||
|
||||
if (pkm.Species == 151)
|
||||
{
|
||||
var OTMatch = (tr == Legal.GetG1OT_GFMew((int)LanguageID.Japanese))
|
||||
|| (tr == Legal.GetG1OT_GFMew((int)LanguageID.English));
|
||||
if (!OTMatch || pkm.TID != 22796)
|
||||
data.AddLine(GetInvalid(V39));
|
||||
}
|
||||
|
||||
if (pkm.OT_Gender == 1 && (pkm.Format == 2 && pkm.Met_Location == 0 || pkm.Format > 2 && pkm.VC1))
|
||||
data.AddLine(GetInvalid(V408));
|
||||
}
|
||||
private void VerifyG1OTWithinBounds(LegalityAnalysis data, string str)
|
||||
{
|
||||
if (StringConverter.GetIsG1English(str))
|
||||
{
|
||||
if (str.Length > 7 && !(data.EncounterOriginal is EncounterTrade)) // OT already verified; GER shuckle has 8 chars
|
||||
data.AddLine(GetInvalid(V38));
|
||||
}
|
||||
else if (StringConverter.GetIsG1Japanese(str))
|
||||
{
|
||||
if (str.Length > 5)
|
||||
data.AddLine(GetInvalid(V38));
|
||||
}
|
||||
else if (data.pkm.Korean && StringConverter.GetIsG2Korean(str))
|
||||
{
|
||||
if (str.Length > 5)
|
||||
data.AddLine(GetInvalid(V38));
|
||||
}
|
||||
else
|
||||
{
|
||||
data.AddLine(GetInvalid(V421));
|
||||
}
|
||||
}
|
||||
private void VerifyG1OTStadium(LegalityAnalysis data, string tr)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
bool jp = pkm.Japanese;
|
||||
bool valid = GetIsStadiumOTIDValid(data, jp, tr);
|
||||
if (!valid)
|
||||
data.AddLine(GetInvalid(V402));
|
||||
else
|
||||
data.AddLine(GetValid(jp ? V404 : V403));
|
||||
}
|
||||
private bool GetIsStadiumOTIDValid(LegalityAnalysis data, bool jp, string tr)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (jp)
|
||||
return tr == "スタジアム" && pkm.TID == 1999;
|
||||
return tr == (data.Info.Generation == 1 ? "STADIUM" : "Stadium") && pkm.TID == 2000;
|
||||
}
|
||||
}
|
||||
}
|
||||
98
PKHeX.Core/Legality/Verifiers/TransferVerifier.cs
Normal file
98
PKHeX.Core/Legality/Verifiers/TransferVerifier.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
public class TransferVerifier : Verifier
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Encounter;
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void VerifyTransferLegalityG3(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (pkm.Format == 4 && pkm.Met_Location != Legal.Transfer3) // Pal Park
|
||||
data.AddLine(GetInvalid(V60));
|
||||
if (pkm.Format != 4 && pkm.Met_Location != Legal.Transfer4)
|
||||
data.AddLine(GetInvalid(V61));
|
||||
}
|
||||
|
||||
public void VerifyTransferLegalityG4(LegalityAnalysis data)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
// Transfer Legality
|
||||
int loc = pkm.Met_Location;
|
||||
if (loc != 30001) // PokéTransfer
|
||||
{
|
||||
// Crown
|
||||
switch (pkm.Species)
|
||||
{
|
||||
case 251: // Celebi
|
||||
if (loc != Legal.Transfer4_CelebiUnused && loc != Legal.Transfer4_CelebiUsed)
|
||||
data.AddLine(GetInvalid(V351));
|
||||
break;
|
||||
case 243: // Raikou
|
||||
case 244: // Entei
|
||||
case 245: // Suicune
|
||||
if (loc != Legal.Transfer4_CrownUnused && loc != Legal.Transfer4_CrownUsed)
|
||||
data.AddLine(GetInvalid(V351));
|
||||
break;
|
||||
default:
|
||||
data.AddLine(GetInvalid(V61));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<CheckResult> VerifyVCEncounter(PKM pkm, IEncounterable encounter, ILocation transfer, IList<CheckMoveResult> Moves)
|
||||
{
|
||||
// Check existing EncounterMatch
|
||||
if (encounter is EncounterInvalid || transfer == null)
|
||||
yield break; // Avoid duplicate invaild message
|
||||
|
||||
if (encounter is EncounterStatic v && (GameVersion.GBCartEraOnly.Contains(v.Version) || v.Version == GameVersion.VCEvents))
|
||||
{
|
||||
bool exceptions = false;
|
||||
exceptions |= v.Version == GameVersion.VCEvents && encounter.Species == 151 && pkm.TID == 22796;
|
||||
if (!exceptions)
|
||||
yield return new CheckResult(Severity.Invalid, V79, CheckIdentifier.Encounter);
|
||||
}
|
||||
|
||||
if (pkm.Met_Location != transfer.Location)
|
||||
yield return new CheckResult(Severity.Invalid, V81, CheckIdentifier.Encounter);
|
||||
if (pkm.Egg_Location != transfer.EggLocation)
|
||||
yield return new CheckResult(Severity.Invalid, V59, CheckIdentifier.Encounter);
|
||||
|
||||
// Flag Moves that cannot be transferred
|
||||
if (encounter is EncounterStatic s && s.Version == GameVersion.C && s.EggLocation == 256) // Dizzy Punch Gifts
|
||||
{
|
||||
// can't have Dizzy Punch at all
|
||||
int index = Array.IndexOf(pkm.Moves, 146); // Dizzy Punch
|
||||
if (index >= 0)
|
||||
{
|
||||
var chk = Moves[index];
|
||||
if (chk.Generation == 2) // not obtained from a future gen
|
||||
Moves[index] = new CheckMoveResult(chk.Source, chk.Generation, Severity.Invalid, V82, CheckIdentifier.Move);
|
||||
}
|
||||
}
|
||||
|
||||
bool checkShiny = pkm.VC2 || pkm.TradebackStatus == TradebackType.WasTradeback && pkm.VC1;
|
||||
if (!checkShiny)
|
||||
yield break;
|
||||
if (pkm.Gender == 1) // female
|
||||
{
|
||||
if (pkm.PersonalInfo.Gender == 31 && pkm.IsShiny) // impossible gender-shiny
|
||||
yield return new CheckResult(Severity.Invalid, V209, CheckIdentifier.PID);
|
||||
}
|
||||
else if (pkm.Species == 201) // unown
|
||||
{
|
||||
if (pkm.AltForm != 8 && pkm.AltForm != 21 && pkm.IsShiny) // impossibly form-shiny (not I or V)
|
||||
yield return new CheckResult(Severity.Invalid, V209, CheckIdentifier.PID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
PKHeX.Core/Legality/Verifiers/Verifier.cs
Normal file
27
PKHeX.Core/Legality/Verifiers/Verifier.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace PKHeX.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Verification that provides new <see cref="CheckResult"/> values for a <see cref="LegalityAnalysis"/>.
|
||||
/// </summary>
|
||||
public abstract class Verifier
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="CheckResult"/> category.
|
||||
/// </summary>
|
||||
protected abstract CheckIdentifier Identifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Processes the <see cref="data"/> and adds any relevant <see cref="CheckResult"/> values to the <see cref="LegalityAnalysis.Parse"/>.
|
||||
/// </summary>
|
||||
/// <param name="data">Analysis data to process</param>
|
||||
public abstract void Verify(LegalityAnalysis data);
|
||||
|
||||
protected CheckResult GetInvalid(string msg) => Get(msg, Severity.Invalid);
|
||||
protected CheckResult GetValid(string msg) => Get(msg, Severity.Valid);
|
||||
protected CheckResult Get(string msg, Severity s) => new CheckResult(s, msg, Identifier);
|
||||
|
||||
protected static CheckResult GetInvalid(string msg, CheckIdentifier c) => Get(msg, Severity.Invalid, c);
|
||||
protected static CheckResult GetValid(string msg, CheckIdentifier c) => Get(msg, Severity.Valid, c);
|
||||
protected static CheckResult Get(string msg, Severity s, CheckIdentifier c) => new CheckResult(s, msg, c);
|
||||
}
|
||||
}
|
||||
@@ -455,5 +455,10 @@ public override PKM ConvertToPKM(ITrainerInfo SAV)
|
||||
pk.RefreshChecksum();
|
||||
return pk;
|
||||
}
|
||||
|
||||
public bool IsAshGreninjaWC7(PKM pkm)
|
||||
{
|
||||
return CardID == 2046 && (pkm.SID << 16 | pkm.TID) == 0x79F57B49;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2051,5 +2051,10 @@ public static string LocalizeKOR2(string nick)
|
||||
{ "덩쿠리", "덩구리" }, // Tangela
|
||||
{ "슈륙챙이", "수륙챙이" }, // Poliwhirl
|
||||
};
|
||||
|
||||
public static bool HasEastAsianScriptCharacters(IEnumerable<char> str)
|
||||
{
|
||||
return str.Any(c => 0x4E00 <= c && c <= 0x9FFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user