From 0fec576831e2683c815528cb72817221fc4fa89e Mon Sep 17 00:00:00 2001 From: Kaphotics Date: Sun, 23 Oct 2016 12:48:49 -0700 Subject: [PATCH] Refactor Legality engine Beginnings of multi-generational support; removed all references to PK6 (child) and changed to PKM (abstract parent). The core portion still needs revision to not be hard-coded for XY/ORAS info tables. --- PKHeX/Legality/Analysis.cs | 146 +-- PKHeX/Legality/Checks.cs | 1445 +++++++++++++++++------------ PKHeX/Legality/Core.cs | 355 +++---- PKHeX/Legality/Tables6.cs | 10 +- PKHeX/Legality/Tables7.cs | 5 + PKHeX/MainWindow/Main.cs | 26 +- PKHeX/MysteryGifts/MysteryGift.cs | 10 +- PKHeX/MysteryGifts/PGF.cs | 6 +- PKHeX/MysteryGifts/PGT.cs | 24 +- PKHeX/MysteryGifts/WC6.cs | 8 +- PKHeX/PKM/CK3.cs | 2 +- PKHeX/PKM/PK3.cs | 2 +- PKHeX/PKM/PK6.cs | 25 +- PKHeX/PKM/PK7.cs | 25 +- PKHeX/PKM/PKM.cs | 15 + PKHeX/PKM/XK3.cs | 2 +- PKHeX/Util/ReflectUtil.cs | 4 + 17 files changed, 1241 insertions(+), 869 deletions(-) diff --git a/PKHeX/Legality/Analysis.cs b/PKHeX/Legality/Analysis.cs index a956cf503..7ea4a31d1 100644 --- a/PKHeX/Legality/Analysis.cs +++ b/PKHeX/Legality/Analysis.cs @@ -6,78 +6,116 @@ namespace PKHeX { public partial class LegalityAnalysis { - private readonly PK6 pk6; - private object EncounterMatch; - private List CardMatch; - private Type EncounterType; - private LegalityCheck ECPID, Nickname, IDs, IVs, EVs, Encounter, Level, Ribbons, Ability, Ball, History, OTMemory, HTMemory, Region, Form, Misc, Gender; - private LegalityCheck[] Checks => new[] { Encounter, Level, Form, Ball, Ability, Ribbons, ECPID, Nickname, IVs, EVs, IDs, History, OTMemory, HTMemory, Region, Misc, Gender }; + private PKM pkm; + private readonly List Parse = new List(); - public bool Valid = true; - public bool SecondaryChecked; - public int[] RelearnBase; - public LegalityCheck[] vMoves = new LegalityCheck[4]; - public LegalityCheck[] vRelearn = new LegalityCheck[4]; + private object EncounterMatch; + private Type EncounterType; + private List EventGiftMatch; + private CheckResult Encounter, History; + private int[] RelearnBase; + // private bool SecondaryChecked; + + public readonly bool Parsed; + public readonly bool Valid; + public CheckResult[] vMoves = new CheckResult[4]; + public CheckResult[] vRelearn = new CheckResult[4]; public string Report => getLegalityReport(); public string VerboseReport => getVerboseLegalityReport(); + public bool Native => pkm.GenNumber == pkm.Format; public LegalityAnalysis(PKM pk) { - if (!(pk is PK6)) - return; - pk6 = (PK6) pk; try { - updateRelearnLegality(); - updateMoveLegality(); - updateChecks(); - getLegalityReport(); + switch (pk.Format) + { + case 6: parsePK6(pk); break; + case 7: parsePK7(pk); break; + default: return; + } + Valid = Parse.Any() && Parse.All(chk => chk.Valid); + if (vMoves.Any(m => m.Valid != true)) + Valid = false; + else if (vRelearn.Any(m => m.Valid != true)) + Valid = false; } catch { Valid = false; } + Parsed = true; } - public void updateRelearnLegality() + private void AddLine(Severity s, string c, CheckIdentifier i) + { + AddLine(new CheckResult(s, c, i)); + } + private void AddLine(CheckResult chk) + { + Parse.Add(chk); + } + private void parsePK6(PKM pk) + { + if (!(pk is PK6)) + return; + pkm = pk; + + updateRelearnLegality(); + updateMoveLegality(); + updateChecks(); + getLegalityReport(); + } + private void parsePK7(PKM pk) + { + if (!(pk is PK7)) + return; + pkm = pk; + + updateRelearnLegality(); + updateMoveLegality(); + updateChecks(); + getLegalityReport(); + } + + private void updateRelearnLegality() { try { vRelearn = verifyRelearn(); } - catch { for (int i = 0; i < 4; i++) vRelearn[i] = new LegalityCheck(Severity.Invalid, "Internal error."); } - SecondaryChecked = false; + catch { for (int i = 0; i < 4; i++) vRelearn[i] = new CheckResult(Severity.Invalid, "Internal error.", CheckIdentifier.RelearnMove); } + // SecondaryChecked = false; } - public void updateMoveLegality() + private void updateMoveLegality() { try { vMoves = verifyMoves(); } - catch { for (int i = 0; i < 4; i++) vMoves[i] = new LegalityCheck(Severity.Invalid, "Internal error."); } - SecondaryChecked = false; + catch { for (int i = 0; i < 4; i++) vMoves[i] = new CheckResult(Severity.Invalid, "Internal error.", CheckIdentifier.Move); } + // SecondaryChecked = false; } private void updateChecks() { Encounter = verifyEncounter(); EncounterType = EncounterMatch?.GetType(); - ECPID = verifyECPID(); - Nickname = verifyNickname(); - IDs = verifyID(); - IVs = verifyIVs(); - EVs = verifyEVs(); - Level = verifyLevel(); - Ribbons = verifyRibbons(); - Ability = verifyAbility(); - Ball = verifyBall(); History = verifyHistory(); - OTMemory = verifyOTMemory(); - HTMemory = verifyHTMemory(); - Region = verifyRegion(); - Form = verifyForm(); - Misc = verifyMisc(); - Gender = verifyGender(); - SecondaryChecked = true; + + verifyECPID(); + verifyNickname(); + verifyID(); + verifyIVs(); + verifyEVs(); + verifyLevel(); + verifyRibbons(); + verifyAbility(); + verifyBall(); + verifyOTMemory(); + verifyHTMemory(); + verifyRegion(); + verifyForm(); + verifyMisc(); + verifyGender(); + // SecondaryChecked = true; } private string getLegalityReport() { - if (pk6 == null || !pk6.Gen6) - return "Analysis only available for Pokémon that originate from X/Y & OR/AS."; + if (!Parsed) + return "Analysis not available for this Pokémon."; - var chks = Checks; - string r = ""; for (int i = 0; i < 4; i++) if (!vMoves[i].Valid) @@ -86,19 +124,18 @@ private string getLegalityReport() if (!vRelearn[i].Valid) r += $"{vRelearn[i].Judgement} Relearn Move {i + 1}: {vRelearn[i].Comment}" + Environment.NewLine; - if (r.Length == 0 && chks.All(chk => chk.Valid)) + if (r.Length == 0 && Parse.All(chk => chk.Valid)) return "Legal!"; - - Valid = false; + // Build result string... - r += chks.Where(chk => !chk.Valid).Aggregate("", (current, chk) => current + $"{chk.Judgement}: {chk.Comment}{Environment.NewLine}"); + r += Parse.Where(chk => !chk.Valid).Aggregate("", (current, chk) => current + $"{chk.Judgement}: {chk.Comment}{Environment.NewLine}"); return r.TrimEnd(); } private string getVerboseLegalityReport() { string r = getLegalityReport() + Environment.NewLine; - if (pk6 == null) + if (pkm == null) return r; r += "===" + Environment.NewLine + Environment.NewLine; int rl = r.Length; @@ -112,9 +149,8 @@ private string getVerboseLegalityReport() if (rl != r.Length) // move info added, break for next section r += Environment.NewLine; - - var chks = Checks; - r += chks.Where(chk => chk != null && chk.Valid && chk.Comment != "Valid").OrderBy(chk => chk.Judgement) // Fishy sorted to top + + r += Parse.Where(chk => chk != null && chk.Valid && chk.Comment != "Valid").OrderBy(chk => chk.Judgement) // Fishy sorted to top .Aggregate("", (current, chk) => current + $"{chk.Judgement}: {chk.Comment}{Environment.NewLine}"); return r.TrimEnd(); } @@ -123,15 +159,17 @@ public int[] getSuggestedRelearn() { if (RelearnBase == null) return new int[4]; + if (pkm.GenNumber < 6) + return new int[4]; - if (!pk6.WasEgg) + if (!pkm.WasEgg) return RelearnBase; List window = new List(RelearnBase); for (int i = 0; i < 4; i++) if (!vMoves[i].Valid || vMoves[i].Flag) - window.Add(pk6.Moves[i]); + window.Add(pkm.Moves[i]); if (window.Count < 4) window.AddRange(new int[4 - window.Count]); diff --git a/PKHeX/Legality/Checks.cs b/PKHeX/Legality/Checks.cs index 0aeb61be0..99bb77c54 100644 --- a/PKHeX/Legality/Checks.cs +++ b/PKHeX/Legality/Checks.cs @@ -12,142 +12,212 @@ public enum Severity Valid = 1, NotImplemented = 2, } - public class LegalityCheck + public enum CheckIdentifier + { + Move, + RelearnMove, + Encounter, + History, + ECPID, + Shiny, + EC, + PID, + Gender, + EVs, + Language, + Trainer, + IVs, + None, + Level, + Ball, + Memory, + Geography, + Form, + Egg, + Misc, + Fateful, + Ribbon, + Training, + Ability + } + public class CheckResult { public Severity Judgement = Severity.Valid; public string Comment = "Valid"; public bool Valid => Judgement >= Severity.Fishy; public bool Flag; + public readonly CheckIdentifier Identifier; - public LegalityCheck() { } - public LegalityCheck(Severity s, string c) + public CheckResult(CheckIdentifier i) { } + public CheckResult(Severity s, string c, CheckIdentifier i) { Judgement = s; Comment = c; + Identifier = i; } } public partial class LegalityAnalysis { - private LegalityCheck verifyGender() + private void verifyGender() { - if (PersonalTable.AO[pk6.Species].Gender == 255 && pk6.Gender != 2) - return new LegalityCheck(Severity.Invalid, "Genderless Pokémon should not have a gender."); - - return new LegalityCheck(); + if (PersonalTable.AO[pkm.Species].Gender == 255 && pkm.Gender != 2) + { + AddLine(Severity.Invalid, "Genderless Pokémon should not have a gender.", CheckIdentifier.Gender); + // return; + } } - private LegalityCheck verifyECPID() + private void verifyECPID() { - // Secondary Checks - LegalityCheck c = new LegalityCheck(); - if (pk6.EncryptionConstant == 0) - c = new LegalityCheck(Severity.Fishy, "Encryption Constant is not set."); + if (pkm.EncryptionConstant == 0) + AddLine(Severity.Fishy, "Encryption Constant is not set.", CheckIdentifier.EC); - if (pk6.PID == 0) - c = new LegalityCheck(Severity.Fishy, "PID is not set."); + if (pkm.PID == 0) + AddLine(Severity.Fishy, "PID is not set.", CheckIdentifier.PID); + + if (pkm.GenNumber >= 6 && pkm.PID == pkm.EncryptionConstant) + AddLine(Severity.Fishy, "Encryption Constant matches PID.", CheckIdentifier.PID); if (EncounterType == typeof (EncounterStatic)) { var enc = (EncounterStatic) EncounterMatch; - if (enc.Shiny != null && (bool)enc.Shiny ^ pk6.IsShiny) - return new LegalityCheck(Severity.Invalid, "Encounter " + (enc.Shiny == true ? "must be" : "cannot be") + " shiny."); + if (enc.Shiny != null && (bool) enc.Shiny ^ pkm.IsShiny) + { + AddLine(Severity.Invalid, $"Encounter {(enc.Shiny == true ? "must be" : "cannot be")} shiny.", CheckIdentifier.Shiny); + return; + } } - string special = ""; - if (pk6.Gen6) + int wIndex = Array.IndexOf(Legal.WurmpleEvolutions, pkm.Species); + if (pkm.GenNumber >= 6) { // Wurmple -> Silcoon/Cascoon - int wIndex = Array.IndexOf(Legal.WurmpleFamily, pk6.Species); if (wIndex > -1) { // Check if Wurmple was the origin (only Egg and Wild Encounter) - if (pk6.WasEgg || (EncounterType == typeof(EncounterSlot[]) && (EncounterMatch as EncounterSlot[]).All(slot => slot.Species == 265))) - if ((pk6.EncryptionConstant >> 16) % 10 / 5 != wIndex / 2) - return new LegalityCheck(Severity.Invalid, "Wurmple evolution Encryption Constant mismatch."); + if (pkm.WasEgg || (EncounterType == typeof(EncounterSlot[]) && (EncounterMatch as EncounterSlot[]).All(slot => slot.Species == 265))) + if ((pkm.EncryptionConstant >> 16)%10/5 != wIndex/2) + { + AddLine(Severity.Invalid, "Wurmple evolution Encryption Constant mismatch.", CheckIdentifier.EC); + return; + } + } + else if (pkm.Species == 265) + { + AddLine(Severity.Valid, "Wurmple Evolution: " + ((pkm.EncryptionConstant >> 16)%10/5 == 0 ? "Silcoon" : "Cascoon"), CheckIdentifier.EC); + } + + int xor = pkm.TSV ^ pkm.PSV; + if (xor < 16 && xor >= 8 && (pkm.PID ^ 0x80000000) == pkm.EncryptionConstant) + { + AddLine(Severity.Fishy, "Encryption Constant matches shinyxored PID.", CheckIdentifier.EC); + return; } - else if (pk6.Species == 265) - special = "Wurmple Evolution: " + ((pk6.EncryptionConstant >> 16)%10/5 == 0 ? "Silcoon" : "Cascoon"); - - if (pk6.PID == pk6.EncryptionConstant) - return new LegalityCheck(Severity.Fishy, "Encryption Constant matches PID."); - - int xor = pk6.TSV ^ pk6.PSV; - if (xor < 16 && xor >= 8 && (pk6.PID ^ 0x80000000) == pk6.EncryptionConstant) - return new LegalityCheck(Severity.Fishy, "Encryption Constant matches shinyxored PID."); - - return special != "" ? new LegalityCheck(Severity.Valid, special) : c; } + if (pkm.Format < 6) + return; + + if (pkm.GenNumber >= 6) + return; // 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 = ((pk6.TID ^ pk6.SID ^ (int)(pk6.PID & 0xFFFF) ^ (int)(pk6.PID >> 16)) & 0x7) == 8; + bool xorPID = ((pkm.TID ^ pkm.SID ^ (int)(pkm.PID & 0xFFFF) ^ (int)(pkm.PID >> 16)) & 0x7) == 8; bool valid = xorPID - ? pk6.EncryptionConstant == (pk6.PID ^ 0x8000000) - : pk6.EncryptionConstant == pk6.PID; + ? pkm.EncryptionConstant == (pkm.PID ^ 0x8000000) + : pkm.EncryptionConstant == pkm.PID; if (!valid) - if (xorPID) - return new LegalityCheck(Severity.Invalid, "PID should be equal to EC [with top bit flipped]!"); - else - return new LegalityCheck(Severity.Invalid, "PID should be equal to EC!"); - - return c; + { + AddLine(Severity.Invalid, + xorPID + ? "PID should be equal to EC [with top bit flipped]!" + : "PID should be equal to EC!", CheckIdentifier.ECPID); + } } - private LegalityCheck verifyNickname() + private void verifyNickname() { // If the Pokémon is not nicknamed, it should match one of the language strings. - if (pk6.Nickname.Length == 0) - return new LegalityCheck(Severity.Indeterminate, "Nickname is empty."); - if (pk6.Species > PKX.SpeciesLang[0].Length) - return new LegalityCheck(Severity.Indeterminate, "Species index invalid for Nickname comparison."); + if (pkm.Nickname.Length == 0) + { + AddLine(Severity.Invalid, "Nickname is empty.", CheckIdentifier.EVs); + return; + } + if (pkm.Species > PKX.SpeciesLang[0].Length) + { + AddLine(Severity.Indeterminate, "Species index invalid for Nickname comparison.", CheckIdentifier.EVs); + return; + } + if (!Encounter.Valid) - return new LegalityCheck(Severity.Valid, "Skipped Nickname check due to other check being invalid."); - - if (pk6.Language > 8) - return new LegalityCheck(Severity.Indeterminate, "Language ID > 8."); - + return; + + if (pkm.Format <= 6 && pkm.Language > 8) + { + AddLine(Severity.Indeterminate, "Language ID > 8.", CheckIdentifier.Language); + return; + } + if (pkm.Format <= 7 && pkm.Language > 9) + { + AddLine(Severity.Indeterminate, "Language ID > 9.", CheckIdentifier.Language); + return; + } + if (EncounterType == typeof(EncounterTrade)) { string[] validOT = new string[0]; int index = -1; - if (pk6.XY) + if (pkm.XY) { - validOT = Legal.TradeXY[pk6.Language]; + validOT = Legal.TradeXY[pkm.Language]; index = Array.IndexOf(Legal.TradeGift_XY, EncounterMatch); } - else if (pk6.AO) + else if (pkm.AO) { - validOT = Legal.TradeAO[pk6.Language]; + validOT = Legal.TradeAO[pkm.Language]; index = Array.IndexOf(Legal.TradeGift_AO, EncounterMatch); } + if (validOT.Length == 0) - return new LegalityCheck(Severity.Indeterminate, "Ingame Trade invalid version?"); - if (index == -1 || validOT.Length < index * 2) - return new LegalityCheck(Severity.Indeterminate, "Ingame Trade invalid lookup?"); + { + AddLine(Severity.Indeterminate, "Ingame Trade invalid version?", CheckIdentifier.Trainer); + return; + } + if (index == -1 || validOT.Length < index*2) + { + AddLine(Severity.Indeterminate, "Ingame Trade invalid lookup?", CheckIdentifier.Trainer); + return; + } string nick = validOT[index]; string OT = validOT[validOT.Length/2 + index]; - if (nick != pk6.Nickname) - return new LegalityCheck(Severity.Fishy, "Ingame Trade nickname has been altered."); - if (OT != pk6.OT_Name) - return new LegalityCheck(Severity.Invalid, "Ingame Trade OT has been altered."); + if (nick != pkm.Nickname) + AddLine(Severity.Fishy, "Ingame Trade nickname has been altered.", CheckIdentifier.EVs); + else if (OT != pkm.OT_Name) + AddLine(Severity.Invalid, "Ingame Trade OT has been altered.", CheckIdentifier.Trainer); + else + AddLine(Severity.Valid, "Ingame Trade OT/Nickname have not been altered.", CheckIdentifier.EVs); - return new LegalityCheck(Severity.Valid, "Ingame Trade OT/Nickname have not been altered."); + return; } - if (pk6.IsEgg) + if (pkm.IsEgg) { - if (!pk6.IsNicknamed) - return new LegalityCheck(Severity.Invalid, "Eggs must be nicknamed."); - return PKX.SpeciesLang[pk6.Language][0] == pk6.Nickname - ? new LegalityCheck(Severity.Valid, "Egg matches language Egg name.") - : new LegalityCheck(Severity.Invalid, "Egg name does not match language Egg name."); + if (!pkm.IsNicknamed) + AddLine(Severity.Invalid, "Eggs must be nicknamed.", CheckIdentifier.EVs); + else if (PKX.SpeciesLang[pkm.Language][0] != pkm.Nickname) + AddLine(Severity.Invalid, "Egg name does not match language Egg name.", CheckIdentifier.EVs); + else + AddLine(Severity.Valid, "Egg matches language Egg name.", CheckIdentifier.EVs); + + return; } - string nickname = pk6.Nickname.Replace("'", "’"); - if (pk6.IsNicknamed) + + string nickname = pkm.Nickname.Replace("'", "’"); + if (pkm.IsNicknamed) { for (int i = 0; i < PKX.SpeciesLang.Length; i++) { @@ -156,305 +226,349 @@ private LegalityCheck verifyNickname() if (index < 0) continue; - return index == pk6.Species && i != pk6.Language - ? new LegalityCheck(Severity.Fishy, "Nickname matches another species name (+language).") - : new LegalityCheck(Severity.Fishy, "Nickname flagged, matches species name."); + AddLine(Severity.Fishy, index == pkm.Species && i != pkm.Language + ? "Nickname matches another species name (+language)." + : "Nickname flagged, matches species name.", CheckIdentifier.EVs); + return; } - return new LegalityCheck(Severity.Valid, "Nickname does not match another species name."); + AddLine(Severity.Valid, "Nickname does not match another species name.", CheckIdentifier.EVs); + return; } + // else { // Can't have another language name if it hasn't evolved or wasn't a language-traded egg. - return (pk6.WasTradedEgg || Legal.getHasEvolved(pk6)) && PKX.SpeciesLang.Any(lang => lang[pk6.Species] == nickname) - || PKX.SpeciesLang[pk6.Language][pk6.Species] == nickname - ? new LegalityCheck(Severity.Valid, "Nickname matches species name.") - : new LegalityCheck(Severity.Invalid, "Nickname does not match species name."); + bool match = (pkm.WasTradedEgg || Legal.getHasEvolved(pkm)) && PKX.SpeciesLang.Any(lang => lang[pkm.Species] == nickname) + || PKX.SpeciesLang[pkm.Language][pkm.Species] == nickname; + + if (!match) + AddLine(Severity.Invalid, "Nickname does not match species name.", CheckIdentifier.EVs); + else + AddLine(Severity.Valid, "Nickname matches species name.", CheckIdentifier.EVs); + + // return; } } - private LegalityCheck verifyEVs() + private void verifyEVs() { - var evs = pk6.EVs; + var evs = pkm.EVs; int sum = evs.Sum(); - if (pk6.IsEgg && sum > 0) - return new LegalityCheck(Severity.Invalid, "Eggs cannot receive EVs."); - if (sum == 0 && pk6.Stat_Level - pk6.Met_Level > 0) - return new LegalityCheck(Severity.Fishy, "All EVs are zero, but leveled above Met Level."); - if (sum == 508) - return new LegalityCheck(Severity.Fishy, "2 EVs remaining."); - if (sum > 510) - return new LegalityCheck(Severity.Invalid, "EV total cannot be above 510."); - if (evs.Any(ev => ev > 252)) - return new LegalityCheck(Severity.Invalid, "EVs cannot go above 252."); - if (evs.All(ev => pk6.EVs[0] == ev) && evs[0] != 0) - return new LegalityCheck(Severity.Fishy, "EVs are all equal."); - - return new LegalityCheck(); + if (pkm.IsEgg && sum > 0) + AddLine(Severity.Invalid, "Eggs cannot receive EVs.", CheckIdentifier.EVs); + else if (sum == 0 && pkm.Stat_Level - pkm.Met_Level > 0) + AddLine(Severity.Fishy, "All EVs are zero, but leveled above Met Level.", CheckIdentifier.EVs); + else if (sum == 508) + AddLine(Severity.Fishy, "2 EVs remaining.", CheckIdentifier.EVs); + else if (sum > 510) + AddLine(Severity.Invalid, "EV total cannot be above 510.", CheckIdentifier.EVs); + else if (pkm.Format >= 6 && evs.Any(ev => ev > 252)) + AddLine(Severity.Invalid, "EVs cannot go above 252.", CheckIdentifier.EVs); + else if (evs.All(ev => pkm.EVs[0] == ev) && evs[0] != 0) + AddLine(Severity.Fishy, "EVs are all equal.", CheckIdentifier.EVs); } - private LegalityCheck verifyIVs() + private void verifyIVs() { - if (EncounterType == typeof(EncounterStatic) && (EncounterMatch as EncounterStatic)?.IV3 == true) - if (pk6.IVs.Count(iv => iv == 31) < 3) - return new LegalityCheck(Severity.Invalid, "Should have at least 3 IVs = 31."); - if (pk6.IVs.Sum() == 0) - return new LegalityCheck(Severity.Fishy, "All IVs are zero."); - if (pk6.IVs[0] < 30 && pk6.IVs.All(iv => pk6.IVs[0] == iv)) - return new LegalityCheck(Severity.Fishy, "All IVs are equal."); - return new LegalityCheck(); + if (EncounterType == typeof (EncounterStatic) && (EncounterMatch as EncounterStatic)?.IV3 == true) + { + if (pkm.IVs.Count(iv => iv == 31) < 3) + { + AddLine(Severity.Invalid, "Should have at least 3 IVs = 31.", CheckIdentifier.IVs); + return; + } + } + if (pkm.IVs.Sum() == 0) + AddLine(Severity.Fishy, "All IVs are zero.", CheckIdentifier.IVs); + else if (pkm.IVs[0] < 30 && pkm.IVs.All(iv => pkm.IVs[0] == iv)) + AddLine(Severity.Fishy, "All IVs are equal.", CheckIdentifier.IVs); } - private LegalityCheck verifyID() + private void verifyID() { if (EncounterType == typeof(EncounterTrade)) - return new LegalityCheck(); // Already matches Encounter Trade information - if (pk6.TID == 0 && pk6.SID == 0) - return new LegalityCheck(Severity.Fishy, "TID and SID are zero."); - if (pk6.TID == pk6.SID) - return new LegalityCheck(Severity.Fishy, "TID and SID are equal."); - if (pk6.TID == 0) - return new LegalityCheck(Severity.Fishy, "TID is zero."); - if (pk6.SID == 0) - return new LegalityCheck(Severity.Fishy, "SID is zero."); - return new LegalityCheck(); - } - private LegalityCheck verifyEncounter() - { - if (!pk6.Gen6) - return new LegalityCheck {Judgement = Severity.NotImplemented}; + return; // Already matches Encounter Trade information - if (pk6.WasLink) + if (pkm.TID == 0 && pkm.SID == 0) + AddLine(Severity.Fishy, "TID and SID are zero.", CheckIdentifier.Trainer); + else if (pkm.TID == pkm.SID) + AddLine(Severity.Fishy, "TID and SID are equal.", CheckIdentifier.Trainer); + else if (pkm.TID == 0) + AddLine(Severity.Fishy, "TID is zero.", CheckIdentifier.Trainer); + else if (pkm.SID == 0) + AddLine(Severity.Fishy, "SID is zero.", CheckIdentifier.Trainer); + } + private CheckResult verifyEncounter() + { + if (!pkm.Gen6) + return new CheckResult(Severity.NotImplemented, "Not Implemented.", CheckIdentifier.Encounter); + + if (pkm.WasLink) { // Should NOT be Fateful, and should be in Database EncounterLink enc = EncounterMatch as EncounterLink; if (enc == null) - return new LegalityCheck(Severity.Invalid, "Invalid Link Gift: unable to find matching gift."); + return new CheckResult(Severity.Invalid, "Invalid Link Gift: unable to find matching gift.", CheckIdentifier.Encounter); - if (pk6.XY && !enc.XY) - return new LegalityCheck(Severity.Invalid, "Invalid Link Gift: can't obtain in XY."); - if (pk6.AO && !enc.ORAS) - return new LegalityCheck(Severity.Invalid, "Invalid Link Gift: can't obtain in ORAS."); + if (pkm.XY && !enc.XY) + return new CheckResult(Severity.Invalid, "Invalid Link Gift: can't obtain in XY.", CheckIdentifier.Encounter); + if (pkm.AO && !enc.ORAS) + return new CheckResult(Severity.Invalid, "Invalid Link Gift: can't obtain in ORAS.", CheckIdentifier.Encounter); - if (enc.Shiny != null && (bool)enc.Shiny ^ pk6.IsShiny) - return new LegalityCheck(Severity.Invalid, "Shiny Link gift mismatch."); + if (enc.Shiny != null && (bool)enc.Shiny ^ pkm.IsShiny) + return new CheckResult(Severity.Invalid, "Shiny Link gift mismatch.", CheckIdentifier.Encounter); - return pk6.FatefulEncounter - ? new LegalityCheck(Severity.Invalid, "Invalid Link Gift: should not be Fateful Encounter.") - : new LegalityCheck(Severity.Valid, "Valid Link gift."); + return pkm.FatefulEncounter + ? new CheckResult(Severity.Invalid, "Invalid Link Gift: should not be Fateful Encounter.", CheckIdentifier.Encounter) + : new CheckResult(Severity.Valid, "Valid Link gift.", CheckIdentifier.Encounter); } - if (pk6.WasEvent || pk6.WasEventEgg) + if (pkm.WasEvent || pkm.WasEventEgg) { - WC6 MatchedWC6 = EncounterMatch as WC6; - return MatchedWC6 != null // Matched in RelearnMoves check. - ? new LegalityCheck(Severity.Valid, $"Matches #{MatchedWC6.CardID.ToString("0000")} ({MatchedWC6.CardTitle})") - : new LegalityCheck(Severity.Invalid, "Not a valid Wonder Card gift."); + MysteryGift MatchedGift = EncounterMatch as MysteryGift; + return MatchedGift == null + ? new CheckResult(Severity.Invalid, "Unable to match to a Mystery Gift in the database.", CheckIdentifier.Encounter) + : new CheckResult(Severity.Valid, $"Matches #{MatchedGift.CardID.ToString("0000")} ({MatchedGift.CardTitle})", CheckIdentifier.Encounter); } EncounterMatch = null; // Reset object - if (pk6.WasEgg) + if (pkm.WasEgg) { // Check Hatch Locations - if (pk6.Met_Level != 1) - return new LegalityCheck(Severity.Invalid, "Invalid met level, expected 1."); + if (pkm.Met_Level != 1) + return new CheckResult(Severity.Invalid, "Invalid met level, expected 1.", CheckIdentifier.Encounter); // Check species - if (Legal.NoHatchFromEgg.Contains(pk6.Species)) - return new LegalityCheck(Severity.Invalid, "Species cannot be hatched from an egg."); - if (pk6.IsEgg) + if (Legal.NoHatchFromEgg.Contains(pkm.Species)) + return new CheckResult(Severity.Invalid, "Species cannot be hatched from an egg.", CheckIdentifier.Encounter); + if (pkm.IsEgg) { - if (pk6.Egg_Location == 30002) - return new LegalityCheck(Severity.Invalid, "Egg location shouldn't be 'traded' for an un-hatched egg."); + if (pkm.Egg_Location == 30002) + return new CheckResult(Severity.Invalid, "Egg location shouldn't be 'traded' for an un-hatched egg.", CheckIdentifier.Encounter); - if (pk6.Met_Location == 30002) - return new LegalityCheck(Severity.Valid, "Valid traded un-hatched egg."); - return pk6.Met_Location == 0 - ? new LegalityCheck(Severity.Valid, "Valid un-hatched egg.") - : new LegalityCheck(Severity.Invalid, "Invalid location for un-hatched egg (expected no met location)."); + if (pkm.Met_Location == 30002) + return new CheckResult(Severity.Valid, "Valid traded un-hatched egg.", CheckIdentifier.Encounter); + return pkm.Met_Location == 0 + ? new CheckResult(Severity.Valid, "Valid un-hatched egg.", CheckIdentifier.Encounter) + : new CheckResult(Severity.Invalid, "Invalid location for un-hatched egg (expected no met location).", CheckIdentifier.Encounter); } - if (pk6.XY) + if (pkm.XY) { - if (pk6.Egg_Location == 318) - return new LegalityCheck(Severity.Invalid, "Invalid X/Y egg location."); - return Legal.ValidMet_XY.Contains(pk6.Met_Location) - ? new LegalityCheck(Severity.Valid, "Valid X/Y hatched egg.") - : new LegalityCheck(Severity.Invalid, "Invalid X/Y location for hatched egg."); + if (pkm.Egg_Location == 318) + return new CheckResult(Severity.Invalid, "Invalid X/Y egg location.", CheckIdentifier.Encounter); + return Legal.ValidMet_XY.Contains(pkm.Met_Location) + ? new CheckResult(Severity.Valid, "Valid X/Y hatched egg.", CheckIdentifier.Encounter) + : new CheckResult(Severity.Invalid, "Invalid X/Y location for hatched egg.", CheckIdentifier.Encounter); } - if (pk6.AO) + if (pkm.AO) { - return Legal.ValidMet_AO.Contains(pk6.Met_Location) - ? new LegalityCheck(Severity.Valid, "Valid OR/AS hatched egg.") - : new LegalityCheck(Severity.Invalid, "Invalid OR/AS location for hatched egg."); + return Legal.ValidMet_AO.Contains(pkm.Met_Location) + ? new CheckResult(Severity.Valid, "Valid OR/AS hatched egg.", CheckIdentifier.Encounter) + : new CheckResult(Severity.Invalid, "Invalid OR/AS location for hatched egg.", CheckIdentifier.Encounter); } - return new LegalityCheck(Severity.Invalid, "Invalid location for hatched egg."); + return new CheckResult(Severity.Invalid, "Invalid location for hatched egg.", CheckIdentifier.Encounter); } - EncounterMatch = Legal.getValidStaticEncounter(pk6); + EncounterMatch = Legal.getValidStaticEncounter(pkm); if (EncounterMatch != null) - return new LegalityCheck(Severity.Valid, "Valid gift/static encounter."); + return new CheckResult(Severity.Valid, "Valid gift/static encounter.", CheckIdentifier.Encounter); - if (Legal.getIsFossil(pk6)) + if (Legal.getIsFossil(pkm)) { - return pk6.AbilityNumber != 4 - ? new LegalityCheck(Severity.Valid, "Valid revived fossil.") - : new LegalityCheck(Severity.Invalid, "Hidden ability on revived fossil."); + return pkm.AbilityNumber != 4 + ? new CheckResult(Severity.Valid, "Valid revived fossil.", CheckIdentifier.Encounter) + : new CheckResult(Severity.Invalid, "Hidden ability on revived fossil.", CheckIdentifier.Encounter); } - EncounterMatch = Legal.getValidFriendSafari(pk6); + EncounterMatch = Legal.getValidFriendSafari(pkm); if (EncounterMatch != null) { - if (pk6.Species == 670 || pk6.Species == 671) // Floette - if (!new[] {0, 1, 3}.Contains(pk6.AltForm)) // 0/1/3 - RBY - return new LegalityCheck(Severity.Invalid, "Friend Safari: Not valid color."); - else if (pk6.Species == 710 || pk6.Species == 711) // Pumpkaboo - if (pk6.AltForm != 1) // Average - return new LegalityCheck(Severity.Invalid, "Friend Safari: Not average sized."); - else if (pk6.Species == 586) // Sawsbuck - if (pk6.AltForm != 0) - return new LegalityCheck(Severity.Invalid, "Friend Safari: Not Spring form."); + if (pkm.Species == 670 || pkm.Species == 671) // Floette + if (!new[] {0, 1, 3}.Contains(pkm.AltForm)) // 0/1/3 - RBY + return new CheckResult(Severity.Invalid, "Friend Safari: Not valid color.", CheckIdentifier.Encounter); + else if (pkm.Species == 710 || pkm.Species == 711) // Pumpkaboo + if (pkm.AltForm != 1) // Average + return new CheckResult(Severity.Invalid, "Friend Safari: Not average sized.", CheckIdentifier.Encounter); + else if (pkm.Species == 586) // Sawsbuck + if (pkm.AltForm != 0) + return new CheckResult(Severity.Invalid, "Friend Safari: Not Spring form.", CheckIdentifier.Encounter); - return new LegalityCheck(Severity.Valid, "Valid friend safari encounter."); + return new CheckResult(Severity.Valid, "Valid Friend Safari encounter.", CheckIdentifier.Encounter); } - EncounterMatch = Legal.getValidWildEncounters(pk6); + EncounterMatch = Legal.getValidWildEncounters(pkm); if (EncounterMatch != null) { EncounterSlot[] enc = (EncounterSlot[])EncounterMatch; if (enc.Any(slot => slot.Normal)) return enc.All(slot => slot.Pressure) - ? new LegalityCheck(Severity.Valid, "Valid encounter at location (Pressure/Hustle/Vital Spirit).") - : new LegalityCheck(Severity.Valid, "Valid encounter at location."); + ? new CheckResult(Severity.Valid, "Valid encounter at location (Pressure/Hustle/Vital Spirit).", CheckIdentifier.Encounter) + : new CheckResult(Severity.Valid, "Valid encounter at location.", CheckIdentifier.Encounter); // Decreased Level Encounters if (enc.Any(slot => slot.WhiteFlute)) return enc.All(slot => slot.Pressure) - ? new LegalityCheck(Severity.Valid, "Valid encounter at location (White Flute & Pressure/Hustle/Vital Spirit).") - : new LegalityCheck(Severity.Valid, "Valid encounter at location (White Flute)."); + ? new CheckResult(Severity.Valid, "Valid encounter at location (White Flute & Pressure/Hustle/Vital Spirit).", CheckIdentifier.Encounter) + : new CheckResult(Severity.Valid, "Valid encounter at location (White Flute).", CheckIdentifier.Encounter); // Increased Level Encounters if (enc.Any(slot => slot.BlackFlute)) return enc.All(slot => slot.Pressure) - ? new LegalityCheck(Severity.Valid, "Valid encounter at location (Black Flute & Pressure/Hustle/Vital Spirit).") - : new LegalityCheck(Severity.Valid, "Valid encounter at location (Black Flute)."); + ? new CheckResult(Severity.Valid, "Valid encounter at location (Black Flute & Pressure/Hustle/Vital Spirit).", CheckIdentifier.Encounter) + : new CheckResult(Severity.Valid, "Valid encounter at location (Black Flute).", CheckIdentifier.Encounter); if (enc.Any(slot => slot.Pressure)) - return new LegalityCheck(Severity.Valid, "Valid encounter at location (Pressure/Hustle/Vital Spirit)."); + return new CheckResult(Severity.Valid, "Valid encounter at location (Pressure/Hustle/Vital Spirit).", CheckIdentifier.Encounter); - return new LegalityCheck(Severity.Valid, "Valid encounter at location (DexNav)."); + return new CheckResult(Severity.Valid, "Valid encounter at location (DexNav).", CheckIdentifier.Encounter); } - EncounterMatch = Legal.getValidIngameTrade(pk6); + EncounterMatch = Legal.getValidIngameTrade(pkm); if (EncounterMatch != null) { - return new LegalityCheck(Severity.Valid, "Valid ingame trade."); + return new CheckResult(Severity.Valid, "Valid ingame trade.", CheckIdentifier.Encounter); } - return new LegalityCheck(Severity.Invalid, "Not a valid encounter."); + return new CheckResult(Severity.Invalid, "Not a valid encounter.", CheckIdentifier.Encounter); } - private LegalityCheck verifyLevel() + private void verifyLevel() { - WC6 MatchedWC6 = EncounterMatch as WC6; - if (MatchedWC6 != null && MatchedWC6.Level != pk6.Met_Level) - return new LegalityCheck(Severity.Invalid, "Met Level does not match Wonder Card level."); + MysteryGift MatchedGift = EncounterMatch as MysteryGift; + if (MatchedGift != null && MatchedGift.Level != pkm.Met_Level) + { + AddLine(new CheckResult(Severity.Invalid, "Met Level does not match Wonder Card level.", CheckIdentifier.Level)); + return; + } - int lvl = pk6.CurrentLevel; - if (lvl > 1 && pk6.IsEgg) - return new LegalityCheck(Severity.Invalid, "Current level for an egg is invalid."); - if (lvl < pk6.Met_Level) - return new LegalityCheck(Severity.Invalid, "Current level is below met level."); - if ((pk6.WasEgg || EncounterMatch == null) && !Legal.getEvolutionValid(pk6) && pk6.Species != 350) - return new LegalityCheck(Severity.Invalid, "Level is below evolution requirements."); - if (lvl > pk6.Met_Level && lvl > 1 && lvl != 100 && pk6.EXP == PKX.getEXP(pk6.Stat_Level, pk6.Species)) - return new LegalityCheck(Severity.Fishy, "Current experience matches level threshold."); - - return new LegalityCheck(Severity.Valid, "Current level is not below met level."); + int lvl = pkm.CurrentLevel; + if (lvl > 1 && pkm.IsEgg) + AddLine(Severity.Invalid, "Current level for an egg is invalid.", CheckIdentifier.Level); + else if (lvl < pkm.Met_Level) + AddLine(Severity.Invalid, "Current level is below met level.", CheckIdentifier.Level); + else if ((pkm.WasEgg || EncounterMatch == null) && !Legal.getEvolutionValid(pkm) && pkm.Species != 350) + AddLine(Severity.Invalid, "Level is below evolution requirements.", CheckIdentifier.Level); + else if (lvl > pkm.Met_Level && lvl > 1 && lvl != 100 && pkm.EXP == PKX.getEXP(pkm.Stat_Level, pkm.Species)) + AddLine(Severity.Fishy, "Current experience matches level threshold.", CheckIdentifier.Level); + else + AddLine(Severity.Valid, "Current level is not below met level.", CheckIdentifier.Level); } - private LegalityCheck verifyRibbons() + private void verifyRibbons() { if (!Encounter.Valid) - return new LegalityCheck(Severity.Valid, "Skipped Ribbon check due to other check being invalid."); - - var TrainNames = ReflectUtil.getPropertiesStartWithPrefix(pk6.GetType(), "SuperTrain").ToArray(); - if (TrainNames.Count(MissionName => ReflectUtil.GetValue(pk6, MissionName) as bool? == true) == 30 ^ pk6.SecretSuperTrainingComplete) - return new LegalityCheck(Severity.Invalid, "Super Training complete flag mismatch."); + return; List missingRibbons = new List(); List invalidRibbons = new List(); - if (pk6.IsEgg) + if (pkm.IsEgg) { - var RibbonNames = ReflectUtil.getPropertiesStartWithPrefix(pk6.GetType(), "Ribbon"); - foreach (object RibbonValue in RibbonNames.Select(RibbonName => ReflectUtil.GetValue(pk6, RibbonName))) + var RibbonNames = ReflectUtil.getPropertiesStartWithPrefix(pkm.GetType(), "Ribbon"); + foreach (object RibbonValue in RibbonNames.Select(RibbonName => ReflectUtil.GetValue(pkm, RibbonName))) { - if ((RibbonValue as int?) > 0) - return new LegalityCheck(Severity.Invalid, "Eggs should not have ribbons."); - if (RibbonValue as bool? == true) - return new LegalityCheck(Severity.Invalid, "Eggs should not have ribbons."); + if (RibbonValue as bool? == true) // Boolean + { AddLine(Severity.Invalid, "Eggs should not have ribbons.", CheckIdentifier.Ribbon); return; } + if ((RibbonValue as int?) > 0) // Count + { AddLine(Severity.Invalid, "Eggs should not have ribbons.", CheckIdentifier.Ribbon); return; } } - var DistNames = ReflectUtil.getPropertiesStartWithPrefix(pk6.GetType(), "DistSuperTrain"); - if (DistNames.Select(MissionName => ReflectUtil.GetValue(pk6, MissionName)).Any(Flag => Flag as bool? == true)) - return new LegalityCheck(Severity.Invalid, "Distribution Super Training missions on Egg."); + if (pkm.Format < 6) + return; - if (TrainNames.Select(MissionName => ReflectUtil.GetValue(pk6, MissionName)).Any(Flag => Flag as bool? == true)) - return new LegalityCheck(Severity.Invalid, "Super Training missions on Egg."); + var TrainNames = ReflectUtil.getPropertiesStartWithPrefix(pkm.GetType(), "SuperTrain").ToArray(); + if (TrainNames.Count(MissionName => ReflectUtil.GetValue(pkm, MissionName) as bool? == true) == 30 ^ pkm.SecretSuperTrainingComplete) + { + AddLine(Severity.Invalid, "Super Training complete flag mismatch.", CheckIdentifier.Training); + return; + } - return new LegalityCheck(); + var DistNames = ReflectUtil.getPropertiesStartWithPrefix(pkm.GetType(), "DistSuperTrain"); + if (DistNames.Select(MissionName => ReflectUtil.GetValue(pkm, MissionName)).Any(Flag => Flag as bool? == true)) + AddLine(Severity.Invalid, "Distribution Super Training missions on Egg.", CheckIdentifier.Training); + else if (TrainNames.Select(MissionName => ReflectUtil.GetValue(pkm, MissionName)).Any(Flag => Flag as bool? == true)) + AddLine(Severity.Invalid, "Super Training missions on Egg.", CheckIdentifier.Training); + + return; } // Check Event Ribbons - bool[] EventRib = + var RibbonData = ReflectUtil.getPropertiesStartWithPrefix(pkm.GetType(), "Ribbon"); + MysteryGift MatchedGift = EncounterMatch as MysteryGift; + string[] EventRib = { - pk6.RibbonCountry, pk6.RibbonNational, pk6.RibbonEarth, pk6.RibbonWorld, pk6.RibbonClassic, - pk6.RibbonPremier, pk6.RibbonEvent, pk6.RibbonBirthday, pk6.RibbonSpecial, pk6.RibbonSouvenir, - pk6.RibbonWishing, pk6.RibbonChampionBattle, pk6.RibbonChampionRegional, pk6.RibbonChampionNational, pk6.RibbonChampionWorld + "RibbonCountry", "RibbonNational", "RibbonEarth", "RibbonWorld", "RibbonClassic", + "RibbonPremier", "RibbonEvent", "RibbonBirthday", "RibbonSpecial", "RibbonSouvenir", + "RibbonWishing", "RibbonChampionBattle", "RibbonChampionRegional", "RibbonChampionNational", "RibbonChampionWorld" }; - WC6 MatchedWC6 = EncounterMatch as WC6; - if (MatchedWC6 != null) // Wonder Card + if (MatchedGift != null) // Wonder Card { - bool[] wc6rib = + var mgRibbons = ReflectUtil.getPropertiesStartWithPrefix(MatchedGift.Content.GetType(), "Ribbon"); + var commonRibbons = mgRibbons.Intersect(RibbonData).ToArray(); + + foreach (string r in commonRibbons) { - MatchedWC6.RibbonCountry, MatchedWC6.RibbonNational, MatchedWC6.RibbonEarth, MatchedWC6.RibbonWorld, MatchedWC6.RibbonClassic, - MatchedWC6.RibbonPremier, MatchedWC6.RibbonEvent, MatchedWC6.RibbonBirthday, MatchedWC6.RibbonSpecial, MatchedWC6.RibbonSouvenir, - MatchedWC6.RibbonWishing, MatchedWC6.RibbonChampionBattle, MatchedWC6.RibbonChampionRegional, MatchedWC6.RibbonChampionNational, MatchedWC6.RibbonChampionWorld - }; - for (int i = 0; i < EventRib.Length; i++) - if (EventRib[i] ^ wc6rib[i]) // Mismatch - (wc6rib[i] ? missingRibbons : invalidRibbons).Add(EventRibName[i]); + bool? pk = ReflectUtil.getBooleanState(pkm, r); + bool? mg = ReflectUtil.getBooleanState(MatchedGift, r); + if (pk != mg) // Mismatch + { + if (pk ?? false) + missingRibbons.Add(r); + else + invalidRibbons.Add(r); + } + } } else if (EncounterType == typeof(EncounterLink)) { // No Event Ribbons except Classic (unless otherwise specified, ie not for Demo) for (int i = 0; i < EventRib.Length; i++) - if (i != 4 && EventRib[i]) - invalidRibbons.Add(EventRibName[i]); + { + if (i == 4) + continue; - if (EventRib[4] ^ ((EncounterLink)EncounterMatch).Classic) - (EventRib[4] ? invalidRibbons : missingRibbons).Add(EventRibName[4]); + if (ReflectUtil.getBooleanState(pkm, EventRib[i]) == true) + invalidRibbons.Add(EventRibName[i]); + } + + bool classic = ReflectUtil.getBooleanState(pkm, EventRib[4]) == true; + if (classic ^ ((EncounterLink)EncounterMatch).Classic) + (classic ? invalidRibbons : missingRibbons).Add(EventRibName[4]); } else // No ribbons { for (int i = 0; i < EventRib.Length; i++) - if (EventRib[i]) + if (ReflectUtil.getBooleanState(pkm, EventRib[i]) == true) invalidRibbons.Add(EventRibName[i]); } - - // Unobtainable ribbons for Gen6 Origin - if (pk6.RibbonChampionG3Hoenn) - invalidRibbons.Add("GBA Champion"); // RSE HoF - if (pk6.RibbonChampionSinnoh) - invalidRibbons.Add("Sinnoh Champ"); // DPPt HoF - if (pk6.RibbonArtist) - invalidRibbons.Add("Artist"); // RSE Master Rank Portrait - if (pk6.RibbonRecord) + + // Unobtainable ribbons for Gen Origin + if (pkm.GenNumber > 3) + { + if (ReflectUtil.getBooleanState(pkm, "RibbonChampionG3Hoenn") == true) + invalidRibbons.Add("GBA Champion"); // RSE HoF + if (ReflectUtil.getBooleanState(pkm, "RibbonChampionG3Hoenn") == true) + invalidRibbons.Add("RibbonArtist"); // RSE Master Rank Portrait + if (ReflectUtil.getBooleanState(pkm, "RibbonChampionG3Hoenn") == true) + invalidRibbons.Add("GBA Champion"); // RSE HoF + } + if (pkm.GenNumber > 4) + { + if (ReflectUtil.getBooleanState(pkm, "RibbonChampionSinnoh") == true) + invalidRibbons.Add("Sinnoh Champ"); // DPPt HoF + if (ReflectUtil.getBooleanState(pkm, "RibbonLegend") == true) + invalidRibbons.Add("Legend"); // HGSS Defeat Red @ Mt.Silver + } + if (pkm.Format >= 6 && pkm.GenNumber >= 6) + { + if (ReflectUtil.getBooleanState(pkm, "RibbonCountMemoryContest") == true) + invalidRibbons.Add("Contest Memory"); // Gen3/4 Contest + if (ReflectUtil.getBooleanState(pkm, "RibbonCountMemoryBattle") == true) + invalidRibbons.Add("Battle Memory"); // Gen3/4 Battle + } + if (ReflectUtil.getBooleanState(pkm, "RibbonRecord") == true) invalidRibbons.Add("Record"); // Unobtainable - if (pk6.RibbonLegend) - invalidRibbons.Add("Legend"); // HGSS Defeat Red @ Mt.Silver - if (pk6.RibbonCountMemoryContest > 0) - invalidRibbons.Add("Contest Memory"); // Gen3/4 Contest - if (pk6.RibbonCountMemoryBattle > 0) - invalidRibbons.Add("Battle Memory"); // Gen3/4 Battle if (missingRibbons.Count + invalidRibbons.Count == 0) { - var DistNames = ReflectUtil.getPropertiesStartWithPrefix(pk6.GetType(), "DistSuperTrain"); - if (DistNames.Select(MissionName => ReflectUtil.GetValue(pk6, MissionName)).Any(Flag => Flag as bool? == true)) - return new LegalityCheck(Severity.Fishy, "Distribution Super Training missions are not released."); + var DistNames = ReflectUtil.getPropertiesStartWithPrefix(pkm.GetType(), "DistSuperTrain"); + if (DistNames.Select(MissionName => ReflectUtil.GetValue(pkm, MissionName)).Any(Flag => Flag as bool? == true)) + AddLine(Severity.Fishy, "Distribution Super Training missions are not released.", CheckIdentifier.Training); + else + AddLine(Severity.Valid, "All ribbons accounted for.", CheckIdentifier.Ribbon); - return new LegalityCheck(Severity.Valid, "All ribbons accounted for."); + return; } string[] result = new string[2]; @@ -462,224 +576,313 @@ private LegalityCheck verifyRibbons() result[0] = "Missing Ribbons: " + string.Join(", ", missingRibbons); if (invalidRibbons.Count > 0) result[1] = "Invalid Ribbons: " + string.Join(", ", invalidRibbons); - return new LegalityCheck(Severity.Invalid, string.Join(Environment.NewLine, result.Where(s=>!string.IsNullOrEmpty(s)))); + AddLine(Severity.Invalid, string.Join(Environment.NewLine, result.Where(s=>!string.IsNullOrEmpty(s))), CheckIdentifier.Ribbon); } - private LegalityCheck verifyAbility() + private void verifyAbility() { - int[] abilities = PersonalTable.AO.getAbilities(pk6.Species, pk6.AltForm); - int abilval = Array.IndexOf(abilities, pk6.Ability); + int[] abilities = PersonalTable.AO.getAbilities(pkm.Species, pkm.AltForm); + int abilval = Array.IndexOf(abilities, pkm.Ability); if (abilval < 0) - return new LegalityCheck(Severity.Invalid, "Ability is not valid for species/form."); + { + AddLine(Severity.Invalid, "Ability is not valid for species/form.", CheckIdentifier.Ability); + return; + } if (EncounterMatch != null) { - if (EncounterType == typeof(EncounterStatic)) - if (pk6.AbilityNumber == 4 ^ ((EncounterStatic)EncounterMatch).Ability == 4) - return new LegalityCheck(Severity.Invalid, "Hidden Ability mismatch for static encounter."); - if (EncounterType == typeof(EncounterTrade)) - if (pk6.AbilityNumber == 4 ^ ((EncounterTrade)EncounterMatch).Ability == 4) - return new LegalityCheck(Severity.Invalid, "Hidden Ability mismatch for ingame trade."); - if (EncounterType == typeof(EncounterSlot[]) && pk6.AbilityNumber == 4) + // Check Hidden Ability Mismatches + if (pkm.GenNumber >= 5) { - var slots = (EncounterSlot[])EncounterMatch; - bool valid = slots.Any(slot => slot.DexNav || - slot.Type == SlotType.FriendSafari || - slot.Type == SlotType.Horde); + if (EncounterType == typeof(EncounterStatic)) + if (pkm.AbilityNumber == 4 ^ ((EncounterStatic) EncounterMatch).Ability == 4) + { + AddLine(Severity.Invalid, "Hidden Ability mismatch for static encounter.", CheckIdentifier.Ability); + } + if (EncounterType == typeof(EncounterTrade)) + if (pkm.AbilityNumber == 4 ^ ((EncounterTrade) EncounterMatch).Ability == 4) + { + AddLine(Severity.Invalid, "Hidden Ability mismatch for ingame trade.", CheckIdentifier.Ability); + return; + } + } + if (pkm.GenNumber == 6) + { + if (EncounterType == typeof(EncounterSlot[]) && pkm.AbilityNumber == 4) + { + var slots = (EncounterSlot[])EncounterMatch; + bool valid = slots.Any(slot => slot.DexNav || + slot.Type == SlotType.FriendSafari || + slot.Type == SlotType.Horde); - if (!valid) - return new LegalityCheck(Severity.Invalid, "Hidden Ability on non-horde/friend safari wild encounter."); + if (!valid) + { + AddLine(Severity.Invalid, "Hidden Ability on non-horde/friend safari wild encounter.", CheckIdentifier.Ability); + return; + } + } } } - return abilities[pk6.AbilityNumber >> 1] != pk6.Ability - ? new LegalityCheck(Severity.Invalid, "Ability does not match ability number.") - : new LegalityCheck(Severity.Valid, "Ability matches ability number."); + if (pkm.GenNumber >= 6 && abilities[pkm.AbilityNumber >> 1] != pkm.Ability) + AddLine(Severity.Invalid, "Ability does not match ability number.", CheckIdentifier.Ability); + else if (pkm.GenNumber <= 5 && pkm.Version != (int)GameVersion.CXD && abilities[0] != abilities[1] && pkm.PIDAbility != abilval) + AddLine(Severity.Invalid, "Ability does not match PID.", CheckIdentifier.Ability); + else + AddLine(Severity.Valid, "Ability matches ability number.", CheckIdentifier.Ability); } - private LegalityCheck verifyBall() + private void verifyBall() { - if (!pk6.Gen6) - return new LegalityCheck(); - if (!Encounter.Valid) - return new LegalityCheck(Severity.Valid, "Skipped Ball check due to other check being invalid."); - - if (EncounterType == typeof(WC6)) - return pk6.Ball != ((WC6)EncounterMatch).Pokéball - ? new LegalityCheck(Severity.Invalid, "Ball does not match specified Wonder Card Ball.") - : new LegalityCheck(Severity.Valid, "Ball matches Wonder Card."); - if (EncounterType == typeof(EncounterLink)) - return ((EncounterLink)EncounterMatch).Ball != pk6.Ball - ? new LegalityCheck(Severity.Invalid, "Incorrect ball on Link gift.") - : new LegalityCheck(Severity.Valid, "Correct ball on Link gift."); - if (EncounterType == typeof(EncounterTrade)) - return pk6.Ball != 4 // Pokeball - ? new LegalityCheck(Severity.Invalid, "Incorrect ball on ingame trade encounter.") - : new LegalityCheck(Severity.Valid, "Correct ball on ingame trade encounter."); + if (pkm.GenNumber < 6) + return; // not implemented - if (pk6.Ball == 0x04) // Poké Ball - return new LegalityCheck(Severity.Valid, "Standard Poké Ball."); + if (!Encounter.Valid) + return; + + if (EncounterType == typeof (MysteryGift)) + { + if (pkm.Ball != ((MysteryGift) EncounterMatch).Ball) + AddLine(Severity.Invalid, "Ball does not match specified Mystery Gift Ball.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Ball matches Mystery Gift.", CheckIdentifier.Ball); + + return; + } + if (EncounterType == typeof (EncounterLink)) + { + if (((EncounterLink)EncounterMatch).Ball != pkm.Ball) + AddLine(Severity.Invalid, "Incorrect ball on Link gift.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Correct ball on Link gift.", CheckIdentifier.Ball); + + return; + } + if (EncounterType == typeof (EncounterTrade)) + { + if (pkm.Ball != 4) // Pokeball + AddLine(Severity.Invalid, "Incorrect ball on ingame trade encounter.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Correct ball on ingame trade encounter.", CheckIdentifier.Ball); + + return; + } + + if (pkm.Ball == 0x04) // Poké Ball + { + AddLine(Severity.Valid, "Standard Poké Ball.", CheckIdentifier.Ball); + return; + } if (EncounterType == typeof(EncounterStatic)) { EncounterStatic enc = EncounterMatch as EncounterStatic; - if (enc.Gift) - return enc.Ball != pk6.Ball // Pokéball by default - ? new LegalityCheck(Severity.Invalid, "Incorrect ball on ingame gift.") - : new LegalityCheck(Severity.Valid, "Correct ball on ingame gift."); + if (enc?.Gift ?? false) + { + if (enc.Ball != pkm.Ball) // Pokéball by default + AddLine(Severity.Invalid, "Incorrect ball on ingame gift.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Correct ball on ingame gift.", CheckIdentifier.Ball); - return !Legal.WildPokeballs.Contains(pk6.Ball) - ? new LegalityCheck(Severity.Invalid, "Incorrect ball on ingame static encounter.") - : new LegalityCheck(Severity.Valid, "Correct ball on ingame static encounter."); + return; + } + + if (!Legal.WildPokeballs.Contains(pkm.Ball)) + AddLine(Severity.Invalid, "Incorrect ball on ingame static encounter.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Correct ball on ingame static encounter.", CheckIdentifier.Ball); + + return; } - if (EncounterType == typeof(EncounterSlot[])) - return !Legal.WildPokeballs.Contains(pk6.Ball) - ? new LegalityCheck(Severity.Invalid, "Incorrect ball on ingame encounter.") - : new LegalityCheck(Severity.Valid, "Correct ball on ingame encounter."); - - if (pk6.WasEgg) + if (EncounterType == typeof (EncounterSlot[])) { - if (pk6.Ball == 0x01) // Master Ball - return new LegalityCheck(Severity.Invalid, "Master Ball on egg origin."); - if (pk6.Ball == 0x10) // Cherish Ball - return new LegalityCheck(Severity.Invalid, "Cherish Ball on non-event."); + if(!Legal.WildPokeballs.Contains(pkm.Ball)) + AddLine(Severity.Invalid, "Incorrect ball on ingame encounter.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Correct ball on ingame encounter.", CheckIdentifier.Ball); - if (pk6.Gender == 2) // Genderless - return pk6.Ball != 0x04 // Must be Pokéball as ball can only pass via mother (not Ditto!) - ? new LegalityCheck(Severity.Invalid, "Non-Pokéball on genderless egg.") - : new LegalityCheck(Severity.Valid, "Pokéball on genderless egg."); - if (Legal.BreedMaleOnly.Contains(pk6.Species)) - return pk6.Ball != 0x04 // Must be Pokéball as ball can only pass via mother (not Ditto!) - ? new LegalityCheck(Severity.Invalid, "Non-Pokéball on Male-Only egg.") - : new LegalityCheck(Severity.Valid, "Pokéball on Male-Only egg."); + return; + } - if (pk6.Ball == 0x05) // Safari Ball + if (pkm.WasEgg) + { + if (pkm.GenNumber < 6) // No inheriting Balls { - if (Legal.getLineage(pk6).All(e => !Legal.Inherit_Safari.Contains(e))) - return new LegalityCheck(Severity.Invalid, "Safari Ball not possible for species."); - if (pk6.AbilityNumber == 4) - return new LegalityCheck(Severity.Invalid, "Safari Ball with Hidden Ability."); - - return new LegalityCheck(Severity.Valid, "Safari Ball possible for species."); - } - if (0x10 < pk6.Ball && pk6.Ball < 0x18) // Apricorn Ball - { - if (Legal.getLineage(pk6).All(e => !Legal.Inherit_Apricorn.Contains(e))) - return new LegalityCheck(Severity.Invalid, "Apricorn Ball not possible for species."); - if (pk6.AbilityNumber == 4) - return new LegalityCheck(Severity.Invalid, "Apricorn Ball with Hidden Ability."); - - return new LegalityCheck(Severity.Valid, "Apricorn Ball possible for species."); - } - if (pk6.Ball == 0x18) // Sport Ball - { - if (Legal.getLineage(pk6).All(e => !Legal.Inherit_Sport.Contains(e))) - return new LegalityCheck(Severity.Invalid, "Sport Ball not possible for species."); - if (pk6.AbilityNumber == 4) - return new LegalityCheck(Severity.Invalid, "Sport Ball with Hidden Ability."); - - return new LegalityCheck(Severity.Valid, "Sport Ball possible for species."); - } - if (pk6.Ball == 0x19) // Dream Ball - { - if (Legal.getLineage(pk6).All(e => !Legal.Inherit_Dream.Contains(e))) - return new LegalityCheck(Severity.Invalid, "Dream Ball not possible for species."); - - return new LegalityCheck(Severity.Valid, "Dream Ball possible for species."); + if (pkm.Ball != 0x04) + AddLine(Severity.Invalid, "Ball should be Pokéball.", CheckIdentifier.Ball); + return; } - if (pk6.Species > 650 && pk6.Species != 700) // Sylveon - return !Legal.WildPokeballs.Contains(pk6.Ball) - ? new LegalityCheck(Severity.Invalid, "Unobtainable ball for Kalos origin.") - : new LegalityCheck(Severity.Valid, "Obtainable ball for Kalos origin."); - - if (0x0D <= pk6.Ball && pk6.Ball <= 0x0F) - { - if (Legal.Ban_Gen4Ball.Contains(pk6.Species)) - return new LegalityCheck(Severity.Invalid, "Unobtainable capture for Gen4 Ball."); + if (pkm.Ball == 0x01) // Master Ball + { AddLine(Severity.Invalid, "Master Ball on egg origin.", CheckIdentifier.Ball); return; } + if (pkm.Ball == 0x10) // Cherish Ball + { AddLine(Severity.Invalid, "Cherish Ball on non-event.", CheckIdentifier.Ball); return; } + if (pkm.Ball == 0x1A && !(pkm.Species >= 793 && pkm.Species <= 800)) // Aether Ball + { AddLine(Severity.Invalid, "Aether Ball on non-UB.", CheckIdentifier.Ball); return; } - return new LegalityCheck(Severity.Valid, "Obtainable capture for Gen4 Ball."); + if (pkm.Gender == 2) // Genderless + { + if (pkm.Ball != 0x04) // Must be Pokéball as ball can only pass via mother (not Ditto!) + AddLine(Severity.Invalid, "Non-Pokéball on genderless egg.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Pokéball on genderless egg.", CheckIdentifier.Ball); + + return; } - if (0x02 <= pk6.Ball && pk6.Ball <= 0x0C) // Don't worry, Ball # 0x05 was already checked. + if (Legal.BreedMaleOnly.Contains(pkm.Species)) { - if (Legal.Ban_Gen3Ball.Contains(pk6.Species)) - return new LegalityCheck(Severity.Invalid, "Unobtainable capture for Gen4 Ball."); - if (pk6.AbilityNumber == 4 && 152 <= pk6.Species && pk6.Species <= 160) - return new LegalityCheck(Severity.Invalid, "Ball not possible for species with hidden ability."); + if (pkm.Ball != 0x04) // Must be Pokéball as ball can only pass via mother (not Ditto!) + AddLine(Severity.Invalid, "Non-Pokéball on Male-Only egg.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Pokéball on Male-Only egg.", CheckIdentifier.Ball); - return new LegalityCheck(Severity.Valid, "Obtainable capture for Gen4 Ball."); + return; + } + + if (pkm.Ball == 0x05) // Safari Ball + { + if (Legal.getLineage(pkm).All(e => !Legal.Inherit_Safari.Contains(e))) + AddLine(Severity.Invalid, "Safari Ball not possible for species.", CheckIdentifier.Ball); + else if (pkm.AbilityNumber == 4) + AddLine(Severity.Invalid, "Safari Ball with Hidden Ability.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Safari Ball possible for species.", CheckIdentifier.Ball); + + return; + } + if (0x10 < pkm.Ball && pkm.Ball < 0x18) // Apricorn Ball + { + if (Legal.getLineage(pkm).All(e => !Legal.Inherit_Apricorn.Contains(e))) + AddLine(Severity.Invalid, "Apricorn Ball not possible for species.", CheckIdentifier.Ball); + if (pkm.AbilityNumber == 4) + AddLine(Severity.Invalid, "Apricorn Ball with Hidden Ability.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Apricorn Ball possible for species.", CheckIdentifier.Ball); + + return; + } + if (pkm.Ball == 0x18) // Sport Ball + { + if (Legal.getLineage(pkm).All(e => !Legal.Inherit_Sport.Contains(e))) + AddLine(Severity.Invalid, "Sport Ball not possible for species.", CheckIdentifier.Ball); + else if (pkm.AbilityNumber == 4) + AddLine(Severity.Invalid, "Sport Ball with Hidden Ability.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Sport Ball possible for species.", CheckIdentifier.Ball); + + return; + } + if (pkm.Ball == 0x19) // Dream Ball + { + if (Legal.getLineage(pkm).All(e => !Legal.Inherit_Dream.Contains(e))) + AddLine(Severity.Invalid, "Dream Ball not possible for species.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Dream Ball possible for species.", CheckIdentifier.Ball); + + return; + } + if (0x0D <= pkm.Ball && pkm.Ball <= 0x0F) + { + if (Legal.Ban_Gen4Ball.Contains(pkm.Species)) + AddLine(Severity.Invalid, "Unobtainable capture for Gen4 Ball.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Obtainable capture for Gen4 Ball.", CheckIdentifier.Ball); + + return; + } + if (0x02 <= pkm.Ball && pkm.Ball <= 0x0C) // Don't worry, Ball # 0x05 was already checked. + { + if (Legal.Ban_Gen3Ball.Contains(pkm.Species)) + AddLine(Severity.Invalid, "Unobtainable capture for Gen4 Ball.", CheckIdentifier.Ball); + else if (pkm.AbilityNumber == 4 && 152 <= pkm.Species && pkm.Species <= 160) + AddLine(Severity.Invalid, "Ball not possible for species with hidden ability.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Obtainable capture for Gen4 Ball.", CheckIdentifier.Ball); + + return; + } + + if (pkm.Species > 650 && pkm.Species != 700) // Sylveon + { + if (!Legal.WildPokeballs.Contains(pkm.Ball)) + AddLine(Severity.Invalid, "Unobtainable ball for Kalos origin.", CheckIdentifier.Ball); + else + AddLine(Severity.Valid, "Obtainable ball for Kalos origin.", CheckIdentifier.Ball); + return; } } - return new LegalityCheck(Severity.Invalid, "No ball check satisfied, assuming illegal."); + AddLine(Severity.Invalid, "No ball check satisfied, assuming illegal.", CheckIdentifier.Ball); } - private LegalityCheck verifyHistory() + private CheckResult verifyHistory() { if (!Encounter.Valid) - return new LegalityCheck(Severity.Valid, "Skipped History check due to other check being invalid."); + return new CheckResult(Severity.Valid, "Skipped History check due to other check being invalid.", CheckIdentifier.History); + if (pkm.GenNumber < 6) + return new CheckResult(Severity.Valid, "No History Block to check.", CheckIdentifier.History); WC6 MatchedWC6 = EncounterMatch as WC6; if (MatchedWC6?.OT.Length > 0) // Has Event OT -- null propagation yields false if MatchedWC6=null { - if (pk6.OT_Friendship != PersonalTable.AO[pk6.Species].BaseFriendship) - return new LegalityCheck(Severity.Invalid, "Event OT Friendship does not match base friendship."); - if (pk6.OT_Affection != 0) - return new LegalityCheck(Severity.Invalid, "Event OT Affection should be zero."); - if (pk6.CurrentHandler != 1) - return new LegalityCheck(Severity.Invalid, "Current handler should not be Event OT."); + if (pkm.OT_Friendship != PersonalTable.AO[pkm.Species].BaseFriendship) + return new CheckResult(Severity.Invalid, "Event OT Friendship does not match base friendship.", CheckIdentifier.History); + if (pkm.OT_Affection != 0) + return new CheckResult(Severity.Invalid, "Event OT Affection should be zero.", CheckIdentifier.History); + if (pkm.CurrentHandler != 1) + return new CheckResult(Severity.Invalid, "Current handler should not be Event OT.", CheckIdentifier.History); } - if (!pk6.WasEvent && !(pk6.WasLink && (EncounterMatch as EncounterLink)?.OT == false) && (pk6.HT_Name.Length == 0 || pk6.Geo1_Country == 0)) // Is not Traded + if (!pkm.WasEvent && !(pkm.WasLink && (EncounterMatch as EncounterLink)?.OT == false) && (pkm.HT_Name.Length == 0 || pkm.Geo1_Country == 0)) // Is not Traded { - if (pk6.HT_Name.Length != 0) - return new LegalityCheck(Severity.Invalid, "GeoLocation Memory -- HT Name present but has no previous Country."); - if (pk6.Geo1_Country != 0) - return new LegalityCheck(Severity.Invalid, "GeoLocation Memory -- Previous country of residence but no Handling Trainer."); - if (pk6.HT_Memory != 0) - return new LegalityCheck(Severity.Invalid, "Memory -- Handling Trainer memory present but no Handling Trainer."); - if (pk6.CurrentHandler != 0) // Badly edited; PKHeX doesn't trip this. - return new LegalityCheck(Severity.Invalid, "Untraded -- Current handler should not be the Handling Trainer."); - if (pk6.HT_Friendship != 0) - return new LegalityCheck(Severity.Invalid, "Untraded -- Handling Trainer Friendship should be zero."); - if (pk6.HT_Affection != 0) - return new LegalityCheck(Severity.Invalid, "Untraded -- Handling Trainer Affection should be zero."); - if (pk6.XY && pk6.CNTs.Any(stat => stat > 0)) - return new LegalityCheck(Severity.Invalid, "Untraded -- Contest stats on XY should be zero."); + if (pkm.HT_Name.Length != 0) + return new CheckResult(Severity.Invalid, "GeoLocation Memory -- HT Name present but has no previous Country.", CheckIdentifier.History); + if (pkm.Geo1_Country != 0) + return new CheckResult(Severity.Invalid, "GeoLocation Memory -- Previous country of residence but no Handling Trainer.", CheckIdentifier.History); + if (pkm.HT_Memory != 0) + return new CheckResult(Severity.Invalid, "Memory -- Handling Trainer memory present but no Handling Trainer.", CheckIdentifier.History); + if (pkm.CurrentHandler != 0) // Badly edited; PKHeX doesn't trip this. + return new CheckResult(Severity.Invalid, "Untraded -- Current handler should not be the Handling Trainer.", CheckIdentifier.History); + if (pkm.HT_Friendship != 0) + return new CheckResult(Severity.Invalid, "Untraded -- Handling Trainer Friendship should be zero.", CheckIdentifier.History); + if (pkm.HT_Affection != 0) + return new CheckResult(Severity.Invalid, "Untraded -- Handling Trainer Affection should be zero.", CheckIdentifier.History); + if (pkm.XY && pkm.CNTs.Any(stat => stat > 0)) + return new CheckResult(Severity.Invalid, "Untraded -- Contest stats on XY should be zero.", CheckIdentifier.History); // We know it is untraded (HT is empty), if it must be trade evolved flag it. - if (Legal.getHasTradeEvolved(pk6) && (EncounterMatch as EncounterSlot[])?.Any(slot => slot.Species == pk6.Species) != true) + if (Legal.getHasTradeEvolved(pkm) && (EncounterMatch as EncounterSlot[])?.Any(slot => slot.Species == pkm.Species) != true) { - if (pk6.Species != 350) // Milotic - return new LegalityCheck(Severity.Invalid, "Untraded -- requires a trade evolution."); - if (pk6.CNT_Beauty < 170) // Beauty Contest Stat Requirement - return new LegalityCheck(Severity.Invalid, "Untraded -- Beauty is not high enough for Levelup Evolution."); - if (pk6.CurrentLevel == 1) - return new LegalityCheck(Severity.Invalid, "Untraded -- Beauty is high enough but still Level 1."); + if (pkm.Species != 350) // Milotic + return new CheckResult(Severity.Invalid, "Untraded -- requires a trade evolution.", CheckIdentifier.History); + if (pkm.CNT_Beauty < 170) // Beauty Contest Stat Requirement + return new CheckResult(Severity.Invalid, "Untraded -- Beauty is not high enough for Levelup Evolution.", CheckIdentifier.History); + if (pkm.CurrentLevel == 1) + return new CheckResult(Severity.Invalid, "Untraded -- Beauty is high enough but still Level 1.", CheckIdentifier.History); } } else // Is Traded { - if (pk6.HT_Memory == 0) - return new LegalityCheck(Severity.Invalid, "Memory -- missing Handling Trainer Memory."); + if (pkm.HT_Memory == 0) + return new CheckResult(Severity.Invalid, "Memory -- missing Handling Trainer Memory.", CheckIdentifier.History); } - // Memory Checks - if (pk6.IsEgg) + // Memory ChecksResult + if (pkm.IsEgg) { - if (pk6.HT_Memory != 0) - return new LegalityCheck(Severity.Invalid, "Memory -- has Handling Trainer Memory."); - if (pk6.OT_Memory != 0) - return new LegalityCheck(Severity.Invalid, "Memory -- has Original Trainer Memory."); + if (pkm.HT_Memory != 0) + return new CheckResult(Severity.Invalid, "Memory -- has Handling Trainer Memory.", CheckIdentifier.History); + if (pkm.OT_Memory != 0) + return new CheckResult(Severity.Invalid, "Memory -- has Original Trainer Memory.", CheckIdentifier.History); } else if (EncounterType != typeof(WC6)) { - if (pk6.OT_Memory == 0 ^ !pk6.Gen6) - return new LegalityCheck(Severity.Invalid, "Memory -- missing Original Trainer Memory."); - if (!pk6.Gen6 && pk6.OT_Affection != 0) - return new LegalityCheck(Severity.Invalid, "OT Affection should be zero."); + if (pkm.OT_Memory == 0 ^ !pkm.Gen6) + return new CheckResult(Severity.Invalid, "Memory -- missing Original Trainer Memory.", CheckIdentifier.History); + if (!pkm.Gen6 && pkm.OT_Affection != 0) + return new CheckResult(Severity.Invalid, "OT Affection should be zero.", CheckIdentifier.History); } // Unimplemented: Ingame Trade Memories - return new LegalityCheck(Severity.Valid, "History is valid."); + return new CheckResult(Severity.Valid, "History is valid.", CheckIdentifier.History); } - private LegalityCheck verifyCommonMemory(int handler) + private CheckResult verifyCommonMemory(int handler) { int m = 0; int t = 0; @@ -687,282 +890,340 @@ private LegalityCheck verifyCommonMemory(int handler) switch (handler) { case 0: - m = pk6.OT_Memory; - t = pk6.OT_TextVar; + m = pkm.OT_Memory; + t = pkm.OT_TextVar; resultPrefix = "OT "; break; case 1: - m = pk6.HT_Memory; - t = pk6.HT_TextVar; + m = pkm.HT_Memory; + t = pkm.HT_TextVar; resultPrefix = "HT "; break; } int matchingMoveMemory = Array.IndexOf(Legal.MoveSpecificMemories[0], m); - if (matchingMoveMemory != -1 && pk6.Species != 235 && !Legal.getCanLearnMachineMove(pk6, Legal.MoveSpecificMemories[1][matchingMoveMemory])) + if (matchingMoveMemory != -1 && pkm.Species != 235 && !Legal.getCanLearnMachineMove(pkm, Legal.MoveSpecificMemories[1][matchingMoveMemory])) { - return new LegalityCheck(Severity.Invalid, resultPrefix + "Memory: Species cannot learn this move."); + return new CheckResult(Severity.Invalid, resultPrefix + "Memory: Species cannot learn this move.", CheckIdentifier.Memory); } if (m == 6 && !Legal.LocationsWithPKCenter[0].Contains(t)) { - return new LegalityCheck(Severity.Invalid, resultPrefix + "Memory: Location doesn't have a Pokemon Center."); + return new CheckResult(Severity.Invalid, resultPrefix + "Memory: Location doesn't have a Pokemon Center.", 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)) - return new LegalityCheck(Severity.Invalid, resultPrefix + "Memory: Argument Species cannot learn Fly."); + return new CheckResult(Severity.Invalid, resultPrefix + "Memory: Argument Species cannot learn Fly.", CheckIdentifier.Memory); } - if ((m == 16 || m == 48) && (t == 0 || !Legal.getCanKnowMove(pk6, t, 1))) + if ((m == 16 || m == 48) && (t == 0 || !Legal.getCanKnowMove(pkm, t, 1))) { - return new LegalityCheck(Severity.Invalid, resultPrefix + "Memory: Species cannot know this move."); + return new CheckResult(Severity.Invalid, resultPrefix + "Memory: Species cannot know this move.", CheckIdentifier.Memory); } - if (m == 49 && (t == 0 || !Legal.getCanRelearnMove(pk6, t, 1))) // {0} was able to remember {2} at {1}'s instruction. {4} that {3}. + if (m == 49 && (t == 0 || !Legal.getCanRelearnMove(pkm, t, 1))) // {0} was able to remember {2} at {1}'s instruction. {4} that {3}. { - return new LegalityCheck(Severity.Invalid, resultPrefix + "Memory: Species cannot relearn this move."); + return new CheckResult(Severity.Invalid, resultPrefix + "Memory: Species cannot relearn this move.", CheckIdentifier.Memory); } - return new LegalityCheck(Severity.Valid, resultPrefix + "Memory is valid."); + return new CheckResult(Severity.Valid, resultPrefix + "Memory is valid.", CheckIdentifier.Memory); } - private LegalityCheck verifyOTMemory() + private void verifyOTMemory() { if (!History.Valid) - return new LegalityCheck(Severity.Valid, "Skipped OT Memory check as History is not valid."); + return; + if (pkm.GenNumber < 6) + return; if (EncounterType == typeof(EncounterTrade)) { - return new LegalityCheck(Severity.Valid, "OT Memory (Ingame Trade) is valid."); + AddLine(Severity.Valid, "OT Memory (Ingame Trade) is valid.", CheckIdentifier.Memory); + return; } if (EncounterType == typeof(WC6)) { WC6 MatchedWC6 = EncounterMatch as WC6; - if (pk6.OT_Memory != MatchedWC6.OT_Memory) - return new LegalityCheck(Severity.Invalid, "Event " + (MatchedWC6.OT_Memory == 0 ? "should not have an OT Memory" : "OT Memory should be index " + MatchedWC6.OT_Memory) + "."); - if (pk6.OT_Intensity != MatchedWC6.OT_Intensity) - return new LegalityCheck(Severity.Invalid, "Event " + (MatchedWC6.OT_Intensity == 0 ? "should not have an OT Memory Intensity value" : "OT Memory Intensity should be index " + MatchedWC6.OT_Intensity) + "."); - if (pk6.OT_TextVar != MatchedWC6.OT_TextVar) - return new LegalityCheck(Severity.Invalid, "Event " + (MatchedWC6.OT_TextVar == 0 ? "should not have an OT Memory TextVar value" : "OT Memory TextVar should be index " + MatchedWC6.OT_TextVar) + "."); - if (pk6.OT_Feeling != MatchedWC6.OT_Feeling) - return new LegalityCheck(Severity.Invalid, "Event " + (MatchedWC6.OT_Feeling == 0 ? "should not have an OT Memory Feeling value" : "OT Memory Feeling should be index " + MatchedWC6.OT_Feeling) + "."); + if (pkm.OT_Memory != MatchedWC6.OT_Memory) + AddLine(Severity.Invalid, "Event " + (MatchedWC6.OT_Memory == 0 ? "should not have an OT Memory" : "OT Memory should be index " + MatchedWC6.OT_Memory) + ".", CheckIdentifier.Memory); + if (pkm.OT_Intensity != MatchedWC6.OT_Intensity) + AddLine(Severity.Invalid, "Event " + (MatchedWC6.OT_Intensity == 0 ? "should not have an OT Memory Intensity value" : "OT Memory Intensity should be index " + MatchedWC6.OT_Intensity) + ".", CheckIdentifier.Memory); + if (pkm.OT_TextVar != MatchedWC6.OT_TextVar) + AddLine(Severity.Invalid, "Event " + (MatchedWC6.OT_TextVar == 0 ? "should not have an OT Memory TextVar value" : "OT Memory TextVar should be index " + MatchedWC6.OT_TextVar) + ".", CheckIdentifier.Memory); + if (pkm.OT_Feeling != MatchedWC6.OT_Feeling) + AddLine(Severity.Invalid, "Event " + (MatchedWC6.OT_Feeling == 0 ? "should not have an OT Memory Feeling value" : "OT Memory Feeling should be index " + MatchedWC6.OT_Feeling) + ".", CheckIdentifier.Memory); } - switch (pk6.OT_Memory) + switch (pkm.OT_Memory) { case 2: // {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}. - if (!pk6.WasEgg && pk6.Egg_Location != 60004) - return new LegalityCheck(Severity.Invalid, "OT Memory: OT did not hatch this."); - return new LegalityCheck(Severity.Valid, "OT Memory is valid."); + if (!pkm.WasEgg && pkm.Egg_Location != 60004) + AddLine(Severity.Invalid, "OT Memory: OT did not hatch this.", CheckIdentifier.Memory); + break; + case 4: // {0} became {1}’s friend when it arrived via Link Trade at... {2}. {4} that {3}. - return new LegalityCheck(Severity.Invalid, "OT Memory: Link Trade is not a valid first memory."); + AddLine(Severity.Invalid, "OT Memory: Link Trade is not a valid first memory.", 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(Legal.LocationsWithPKCenter[0], pk6.OT_TextVar); + int matchingOriginGame = Array.IndexOf(Legal.LocationsWithPKCenter[0], pkm.OT_TextVar); if (matchingOriginGame != -1) { int gameID = Legal.LocationsWithPKCenter[1][matchingOriginGame]; - if (pk6.XY && gameID != 0 || pk6.AO && gameID != 1) - return new LegalityCheck(Severity.Invalid, "OT Memory: Location doesn't exist on Origin Game region."); + if (pkm.XY && gameID != 0 || pkm.AO && gameID != 1) + AddLine(Severity.Invalid, "OT Memory: Location doesn't exist on Origin Game region.", CheckIdentifier.Memory); } - return verifyCommonMemory(0); + AddLine(verifyCommonMemory(0)); + return; + case 14: - if (!Legal.getCanBeCaptured(pk6.OT_TextVar, pk6.Version)) - return new LegalityCheck(Severity.Invalid, "OT Memory: Captured Species can not be captured in game."); - return new LegalityCheck(Severity.Valid, "OT Memory: Captured Species can be captured in game."); + if (!Legal.getCanBeCaptured(pkm.OT_TextVar, pkm.Version)) + AddLine(Severity.Invalid, "OT Memory: Captured Species can not be captured in game.", CheckIdentifier.Memory); + else + AddLine(Severity.Valid, "OT Memory: Captured Species can be captured in game.", CheckIdentifier.Memory); + return; } - if (pk6.XY && Legal.Memory_NotXY.Contains(pk6.OT_Memory)) - return new LegalityCheck(Severity.Invalid, "OT Memory: OR/AS exclusive memory on X/Y origin."); - if (pk6.AO && Legal.Memory_NotAO.Contains(pk6.OT_Memory)) - return new LegalityCheck(Severity.Invalid, "OT Memory: X/Y exclusive memory on OR/AS origin."); + if (pkm.XY && Legal.Memory_NotXY.Contains(pkm.OT_Memory)) + AddLine(Severity.Invalid, "OT Memory: OR/AS exclusive memory on X/Y origin.", CheckIdentifier.Memory); + if (pkm.AO && Legal.Memory_NotAO.Contains(pkm.OT_Memory)) + AddLine(Severity.Invalid, "OT Memory: X/Y exclusive memory on OR/AS origin.", CheckIdentifier.Memory); - return verifyCommonMemory(0); + AddLine(verifyCommonMemory(0)); } - private LegalityCheck verifyHTMemory() + private void verifyHTMemory() { - if (!History.Valid) - return new LegalityCheck(Severity.Valid, "Skipped HT Memory check as History is not valid."); + if (pkm.Format < 6) + return; - switch (pk6.HT_Memory) + if (!History.Valid) + return; + + switch (pkm.HT_Memory) { case 1: // {0} met {1} at... {2}. {1} threw a Poké Ball at it, and they started to travel together. {4} that {3}. - return new LegalityCheck(Severity.Invalid, "HT Memory: Handling Trainer did not capture this."); + AddLine(Severity.Invalid, "HT Memory: Handling Trainer did not capture this.", CheckIdentifier.Memory); return; + case 2: // {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}. - return new LegalityCheck(Severity.Invalid, "HT Memory: Handling Trainer did not hatch this."); + AddLine(Severity.Invalid, "HT Memory: Handling Trainer did not hatch this.", CheckIdentifier.Memory); return; + case 14: - if (!Legal.getCanBeCaptured(pk6.HT_TextVar)) - return new LegalityCheck(Severity.Invalid, "HT Memory: Captured Species can not be captured in game."); - return new LegalityCheck(Severity.Valid, "HT Memory: Captured Species can be captured in game."); + if (!Legal.getCanBeCaptured(pkm.HT_TextVar)) + AddLine(Severity.Invalid, "HT Memory: Captured Species can not be captured in game.", CheckIdentifier.Memory); + else + AddLine(Severity.Valid, "HT Memory: Captured Species can be captured in game.", CheckIdentifier.Memory); + return; } - return verifyCommonMemory(1); + AddLine(verifyCommonMemory(1)); } - private LegalityCheck verifyRegion() + private void verifyRegion() { - bool valid = false; - switch (pk6.ConsoleRegion) + if (pkm.Format < 6) + return; + + bool pass; + switch (pkm.ConsoleRegion) { case 0: // Japan - valid = pk6.Country == 1; + pass = pkm.Country == 1; break; case 1: // Americas - valid = 8 <= pk6.Country && pk6.Country <= 52 || new[] {153, 156, 168, 174, 186}.Contains(pk6.Country); + pass = 8 <= pkm.Country && pkm.Country <= 52 || new[] {153, 156, 168, 174, 186}.Contains(pkm.Country); break; case 2: // Europe - valid = 64 <= pk6.Country && pk6.Country <= 127 || new[] {169, 184, 185}.Contains(pk6.Country); + pass = 64 <= pkm.Country && pkm.Country <= 127 || new[] {169, 184, 185}.Contains(pkm.Country); break; case 4: // China - valid = pk6.Country == 144 || pk6.Country == 160; + pass = pkm.Country == 144 || pkm.Country == 160; break; case 5: // Korea - valid = pk6.Country == 136; + pass = pkm.Country == 136; break; case 6: // Taiwan - valid = pk6.Country == 128; + pass = pkm.Country == 128; break; + default: + AddLine(new CheckResult(Severity.Invalid, "Invalid Console Region.", CheckIdentifier.Geography)); + return; } - return !valid - ? new LegalityCheck(Severity.Invalid, "Geolocation: Country is not in 3DS region.") - : new LegalityCheck(Severity.Valid, "Geolocation: Country is in 3DS region."); + + if (!pass) + AddLine(Severity.Invalid, "Geolocation: Country is not in 3DS region.", CheckIdentifier.Geography); + else + AddLine(Severity.Valid, "Geolocation: Country is in 3DS region.", CheckIdentifier.Geography); } - private LegalityCheck verifyForm() + private void verifyForm() { if (!Encounter.Valid) - return new LegalityCheck(Severity.Valid, "Skipped Form check due to other check being invalid."); + return; - switch (pk6.Species) + if (pkm.Format < 4) + return; + + switch (pkm.Species) { case 25: - if (pk6.AltForm != 0 ^ EncounterType == typeof(EncounterStatic)) - return EncounterType == typeof(EncounterStatic) - ? new LegalityCheck(Severity.Invalid, "Cosplay Pikachu cannot have the default form.") - : new LegalityCheck(Severity.Invalid, "Only Cosplay Pikachu can have this form."); + if (pkm.Format == 6 && pkm.AltForm != 0 ^ EncounterType == typeof(EncounterStatic)) + { + if (EncounterType == typeof(EncounterStatic)) + AddLine(Severity.Invalid, "Cosplay Pikachu cannot have the default form.", CheckIdentifier.Form); + else + AddLine(Severity.Invalid, "Only Cosplay Pikachu can have this form.", CheckIdentifier.Form); + + return; + } + if (pkm.Format == 7 && pkm.AltForm != 0 ^ EncounterType == typeof(MysteryGift)) + { + var gift = EncounterMatch as WC6; + if (gift != null && gift.Form != pkm.AltForm) + { + AddLine(Severity.Invalid, "Event Pikachu cannot have the default form.", CheckIdentifier.Form); + return; + } + } break; case 664: case 665: - if (pk6.AltForm > 17) // Fancy & Pokéball - return new LegalityCheck(Severity.Invalid, "Event Vivillon pattern on pre-evolution."); + if (pkm.AltForm > 17) // Fancy & Pokéball + { + AddLine(Severity.Invalid, "Event Vivillon pattern on pre-evolution.", CheckIdentifier.Form); + return; + } break; case 666: - if (pk6.AltForm > 17) // Fancy & Pokéball - return EncounterType != typeof (WC6) - ? new LegalityCheck(Severity.Invalid, "Invalid Vivillon pattern.") - : new LegalityCheck(Severity.Valid, "Valid Vivillon pattern."); + if (pkm.AltForm > 17) // Fancy & Pokéball + { + if (EncounterType != typeof (MysteryGift)) + AddLine(Severity.Invalid, "Invalid Vivillon pattern.", CheckIdentifier.Form); + else + AddLine(Severity.Valid, "Valid Vivillon pattern.", CheckIdentifier.Form); + + return; + } break; case 670: - if (pk6.AltForm == 5) // Eternal Flower - return EncounterType != typeof (WC6) - ? new LegalityCheck(Severity.Invalid, "Invalid Eternal Flower encounter.") - : new LegalityCheck(Severity.Valid, "Valid Eternal Flower encounter."); + if (pkm.AltForm == 5) // Eternal Flower -- Never Released + { + if (EncounterType != typeof(MysteryGift)) + AddLine(Severity.Invalid, "Invalid Eternal Flower encounter.", CheckIdentifier.Form); + else + AddLine(Severity.Valid, "Valid Eternal Flower encounter.", CheckIdentifier.Form); + + return; + } break; } + if ((pkm.Species == 774 && pkm.AltForm >= 7) || (pkm.Species == 664 && pkm.AltForm > 1)) // Minior and Greninja + { AddLine(Severity.Invalid, "Form cannot exist outside of a battle.", CheckIdentifier.Form); return; } + if (pkm.AltForm > 0 && new[] {Legal.BattleForms, Legal.BattleMegas, Legal.BattlePrimals}.Any(arr => arr.Contains(pkm.Species))) + { AddLine(Severity.Invalid, "Form cannot exist outside of a battle.", CheckIdentifier.Form); return; } - return pk6.AltForm > 0 && new[] {Legal.BattleForms, Legal.BattleMegas, Legal.BattlePrimals}.Any(arr => arr.Contains(pk6.Species)) - ? new LegalityCheck(Severity.Invalid, "Form cannot exist outside of a battle.") - : new LegalityCheck(); + AddLine(Severity.Valid, "Form is Valid.", CheckIdentifier.Form); } - private LegalityCheck verifyMisc() + private void verifyMisc() { - if (pk6.IsEgg) + if (pkm.IsEgg) { - if (new[] { pk6.Move1_PPUps, pk6.Move2_PPUps, pk6.Move3_PPUps, pk6.Move4_PPUps }.Any(ppup => ppup > 0)) - return new LegalityCheck(Severity.Invalid, "Cannot apply PP Ups to an Egg."); - if (pk6.CNTs.Any(stat => stat > 0)) - return new LegalityCheck(Severity.Invalid, "Cannot increase Contest Stats of an Egg."); + if (new[] {pkm.Move1_PPUps, pkm.Move2_PPUps, pkm.Move3_PPUps, pkm.Move4_PPUps}.Any(ppup => ppup > 0)) + { AddLine(Severity.Invalid, "Cannot apply PP Ups to an Egg.", CheckIdentifier.Misc); return; } + if (pkm.CNTs.Any(stat => stat > 0)) + { AddLine(Severity.Invalid, "Cannot increase Contest Stats of an Egg.", CheckIdentifier.Misc); return; } } - if (pk6.Gen6 && Encounter.Valid && EncounterType == typeof(WC6) ^ pk6.FatefulEncounter) + if (Encounter.Valid && EncounterType == typeof(MysteryGift) ^ pkm.FatefulEncounter) { - if (EncounterType == typeof(EncounterStatic) && pk6.Species == 386) // Deoxys Matched @ Sky Pillar - return new LegalityCheck(); - return new LegalityCheck(Severity.Invalid, "Fateful Encounter should " + (pk6.FatefulEncounter ? "not " : "") + "be checked."); + if (pkm.AO && EncounterType == typeof(EncounterStatic) && pkm.Species == 386) // Deoxys Matched @ Sky Pillar + { AddLine(Severity.Valid, "Sky Pillar Deoxys matched Fateful Encounter.", CheckIdentifier.Fateful); return; } + else + { AddLine(Severity.Invalid, "Fateful Encounter should " + (pkm.FatefulEncounter ? "not " : "") + "be checked.", CheckIdentifier.Fateful); return; } } - - return new LegalityCheck(); + else + { AddLine(Severity.Valid, "Fateful Encounter is Valid.", CheckIdentifier.Fateful); return; } } - private LegalityCheck[] verifyMoves() + private CheckResult[] verifyMoves() { - int[] Moves = pk6.Moves; - LegalityCheck[] res = new LegalityCheck[4]; + int[] Moves = pkm.Moves; + CheckResult[] res = new CheckResult[4]; for (int i = 0; i < 4; i++) - res[i] = new LegalityCheck(); - if (!pk6.Gen6) + res[i] = new CheckResult(CheckIdentifier.Move); + if (!pkm.Gen6) return res; - var validMoves = Legal.getValidMoves(pk6).ToArray(); - if (pk6.Species == 235) + var validMoves = Legal.getValidMoves(pkm).ToArray(); + if (pkm.Species == 235) // Smeargle { for (int i = 0; i < 4; i++) res[i] = Legal.InvalidSketch.Contains(Moves[i]) - ? new LegalityCheck(Severity.Invalid, "Invalid Sketch move.") - : new LegalityCheck(); + ? new CheckResult(Severity.Invalid, "Invalid Sketch move.", CheckIdentifier.Move) + : new CheckResult(CheckIdentifier.Move); } - else if (CardMatch?.Count > 1) // Multiple possible WC6 matched + else if (EventGiftMatch?.Count > 1) // Multiple possible Mystery Gifts matched { - int[] RelearnMoves = pk6.RelearnMoves; - foreach (var wc in CardMatch) + int[] RelearnMoves = pkm.RelearnMoves; + foreach (MysteryGift mg in EventGiftMatch) { for (int i = 0; i < 4; i++) { if (Moves[i] == Legal.Struggle) - res[i] = new LegalityCheck(Severity.Invalid, "Invalid Move: Struggle."); + res[i] = new CheckResult(Severity.Invalid, "Invalid Move: Struggle.", CheckIdentifier.Move); else if (validMoves.Contains(Moves[i])) - res[i] = new LegalityCheck(Severity.Valid, Moves[i] == 0 ? "Empty" : "Level-up."); + res[i] = new CheckResult(Severity.Valid, Moves[i] == 0 ? "Empty" : "Level-up.", CheckIdentifier.Move); else if (RelearnMoves.Contains(Moves[i])) - res[i] = new LegalityCheck(Severity.Valid, Moves[i] == 0 ? "Empty" : "Relearn Move.") { Flag = true }; - else if (wc.Moves.Contains(Moves[i])) - res[i] = new LegalityCheck(Severity.Valid, "Wonder Card Non-Relearn Move."); + res[i] = new CheckResult(Severity.Valid, Moves[i] == 0 ? "Empty" : "Relearn Move.", CheckIdentifier.Move) { Flag = true }; + else if (mg.Moves.Contains(Moves[i])) + res[i] = new CheckResult(Severity.Valid, "Wonder Card Non-Relearn Move.", CheckIdentifier.Move); else - res[i] = new LegalityCheck(Severity.Invalid, "Invalid Move."); + res[i] = new CheckResult(Severity.Invalid, "Invalid Move.", CheckIdentifier.Move); } if (res.Any(r => !r.Valid)) continue; - EncounterMatch = wc; - RelearnBase = wc.RelearnMoves; + EncounterMatch = mg; + RelearnBase = mg.RelearnMoves; break; } } else { - int[] RelearnMoves = pk6.RelearnMoves; - WC6 MatchedWC6 = EncounterMatch as WC6; - int[] WC6Moves = MatchedWC6?.Moves ?? new int[0]; + int[] RelearnMoves = pkm.RelearnMoves; + MysteryGift MatchedGift = EncounterMatch as MysteryGift; + int[] GiftMoves = MatchedGift?.Moves ?? new int[0]; for (int i = 0; i < 4; i++) { if (Moves[i] == Legal.Struggle) - res[i] = new LegalityCheck(Severity.Invalid, "Invalid Move: Struggle."); + res[i] = new CheckResult(Severity.Invalid, "Invalid Move: Struggle.", CheckIdentifier.Move); else if (validMoves.Contains(Moves[i])) - res[i] = new LegalityCheck(Severity.Valid, Moves[i] == 0 ? "Empty" : "Level-up."); + res[i] = new CheckResult(Severity.Valid, Moves[i] == 0 ? "Empty" : "Level-up.", CheckIdentifier.Move); else if (RelearnMoves.Contains(Moves[i])) - res[i] = new LegalityCheck(Severity.Valid, Moves[i] == 0 ? "Empty" : "Relearn Move.") { Flag = true }; - else if (WC6Moves.Contains(Moves[i])) - res[i] = new LegalityCheck(Severity.Valid, "Wonder Card Non-Relearn Move."); + res[i] = new CheckResult(Severity.Valid, Moves[i] == 0 ? "Empty" : "Relearn Move.", CheckIdentifier.Move) { Flag = true }; + else if (GiftMoves.Contains(Moves[i])) + res[i] = new CheckResult(Severity.Valid, "Wonder Card Non-Relearn Move.", CheckIdentifier.Move); else - res[i] = new LegalityCheck(Severity.Invalid, "Invalid Move."); + res[i] = new CheckResult(Severity.Invalid, "Invalid Move.", CheckIdentifier.Move); } } - if (Moves[0] == 0) - res[0] = new LegalityCheck(Severity.Invalid, "Invalid Move."); + if (Moves[0] == 0) // None + res[0] = new CheckResult(Severity.Invalid, "Invalid Move.", CheckIdentifier.Move); - if (pk6.Species == 647) // Keldeo - if (pk6.AltForm == 1 ^ pk6.Moves.Contains(548)) - res[Math.Max(Array.IndexOf(pk6.Moves, 548), 0)] = new LegalityCheck(Severity.Invalid, "Secret Sword / Resolute Keldeo Mismatch."); + if (pkm.Species == 647) // Keldeo + if (pkm.AltForm == 1 ^ pkm.Moves.Contains(548)) + res[Math.Max(Array.IndexOf(pkm.Moves, 548), 0)] = new CheckResult(Severity.Invalid, "Secret Sword / Resolute Keldeo Mismatch.", CheckIdentifier.Move); // Duplicate Moves Check for (int i = 0; i < 4; i++) if (Moves.Count(m => m != 0 && m == Moves[i]) > 1) - res[i] = new LegalityCheck(Severity.Invalid, "Duplicate Move."); + res[i] = new CheckResult(Severity.Invalid, "Duplicate Move.", CheckIdentifier.Move); return res; } - private LegalityCheck[] verifyRelearn() + private CheckResult[] verifyRelearn() { RelearnBase = null; - LegalityCheck[] res = new LegalityCheck[4]; + CheckResult[] res = new CheckResult[4]; - int[] Moves = pk6.RelearnMoves; - if (!pk6.Gen6) + int[] Moves = pkm.RelearnMoves; + if (pkm.GenNumber < 6) goto noRelearn; - if (pk6.WasLink) + if (pkm.WasLink) { - var Link = Legal.getValidLinkGifts(pk6); + var Link = Legal.getValidLinkGifts(pkm); if (Link == null) { for (int i = 0; i < 4; i++) - res[i] = new LegalityCheck(); + res[i] = new CheckResult(CheckIdentifier.RelearnMove); return res; } EncounterMatch = Link; @@ -971,38 +1232,38 @@ private LegalityCheck[] verifyRelearn() RelearnBase = moves; for (int i = 0; i < 4; i++) res[i] = moves[i] != Moves[i] - ? new LegalityCheck(Severity.Invalid, $"Expected: {movelist[moves[i]]}.") - : new LegalityCheck(); + ? new CheckResult(Severity.Invalid, $"Expected: {movelist[moves[i]]}.", CheckIdentifier.RelearnMove) + : new CheckResult(CheckIdentifier.RelearnMove); return res; } - if (pk6.WasEvent || pk6.WasEventEgg) + if (pkm.WasEvent || pkm.WasEventEgg) { // Get WC6's that match - CardMatch = new List(Legal.getValidWC6s(pk6)); - foreach (var wc in CardMatch.ToArray()) + EventGiftMatch = new List(Legal.getValidWC6s(pkm)); + foreach (MysteryGift mg in EventGiftMatch.ToArray()) { - int[] moves = wc.RelearnMoves; + int[] moves = mg.RelearnMoves; for (int i = 0; i < 4; i++) res[i] = moves[i] != Moves[i] - ? new LegalityCheck(Severity.Invalid, $"Expected ID: {movelist[moves[i]]}.") - : new LegalityCheck(Severity.Valid, $"Matched WC #{wc.CardID.ToString("0000")}"); + ? new CheckResult(Severity.Invalid, $"Expected ID: {movelist[moves[i]]}.", CheckIdentifier.RelearnMove) + : new CheckResult(Severity.Valid, $"Matched {mg.CardID}", CheckIdentifier.RelearnMove); if (res.Any(r => !r.Valid)) - CardMatch.Remove(wc); + EventGiftMatch.Remove(mg); } - if (CardMatch.Count > 1) + if (EventGiftMatch.Count > 1) return res; - if (CardMatch.Count == 1) - { EncounterMatch = CardMatch[0]; RelearnBase = CardMatch[0].RelearnMoves; return res; } + if (EventGiftMatch.Count == 1) + { EncounterMatch = EventGiftMatch[0]; RelearnBase = EventGiftMatch[0].RelearnMoves; return res; } EncounterMatch = EncounterType = null; goto noRelearn; // No WC match } - if (pk6.WasEgg && !Legal.NoHatchFromEgg.Contains(pk6.Species)) + if (pkm.WasEgg && !Legal.NoHatchFromEgg.Contains(pkm.Species)) { const int games = 2; - bool checkAllGames = pk6.WasTradedEgg; - bool splitBreed = Legal.SplitBreed.Contains(pk6.Species); + bool checkAllGames = pkm.WasTradedEgg; + bool splitBreed = Legal.SplitBreed.Contains(pkm.Species); int iterate = (checkAllGames ? games : 1) * (splitBreed ? 2 : 1); for (int i = 0; i < iterate; i++) @@ -1011,13 +1272,13 @@ private LegalityCheck[] verifyRelearn() int skipOption = splitBreed && iterate / 2 <= i ? 1 : 0; // Obtain level1 moves - List baseMoves = new List(Legal.getBaseEggMoves(pk6, skipOption, gameSource)); + List baseMoves = new List(Legal.getBaseEggMoves(pkm, skipOption, gameSource)); int baseCt = baseMoves.Count; if (baseCt > 4) baseCt = 4; // Obtain Nonstandard moves - var relearnMoves = Legal.getValidRelearn(pk6, skipOption).ToArray(); - var relearn = pk6.RelearnMoves.Where(move => move != 0 + var relearnMoves = Legal.getValidRelearn(pkm, skipOption).ToArray(); + var relearn = pkm.RelearnMoves.Where(move => move != 0 && (!baseMoves.Contains(move) || relearnMoves.Contains(move)) ).ToArray(); int relearnCt = relearn.Length; @@ -1038,31 +1299,31 @@ private LegalityCheck[] verifyRelearn() // Movepool finalized! Check validity. - int[] rl = pk6.RelearnMoves; + int[] rl = pkm.RelearnMoves; string em = string.Join(", ", baseMoves.Select(r => r >= movelist.Length ? "ERROR" : movelist[r])); RelearnBase = baseMoves.ToArray(); // Base Egg Move for (int j = 0; j < req; j++) { if (baseMoves.Contains(rl[j])) - res[j] = new LegalityCheck(Severity.Valid, "Base egg move."); + res[j] = new CheckResult(Severity.Valid, "Base egg move.", CheckIdentifier.RelearnMove); else { - res[j] = new LegalityCheck(Severity.Invalid, "Base egg move missing."); + res[j] = new CheckResult(Severity.Invalid, "Base egg move missing.", CheckIdentifier.RelearnMove); for (int f = j+1; f < req; f++) - res[f] = new LegalityCheck(Severity.Invalid, "Base egg move missing."); + res[f] = new CheckResult(Severity.Invalid, "Base egg move missing.", CheckIdentifier.RelearnMove); res[req-1].Comment += $"{Environment.NewLine}Expected the following Relearn Moves: {em}."; break; } } // Non-Base - if (Legal.LightBall.Contains(pk6.Species)) + if (Legal.LightBall.Contains(pkm.Species)) relearnMoves = relearnMoves.Concat(new[] { 344 }).ToArray(); for (int j = req; j < 4; j++) res[j] = !relearnMoves.Contains(rl[j]) - ? new LegalityCheck(Severity.Invalid, "Not an expected relearn move.") - : new LegalityCheck(Severity.Valid, rl[j] == 0 ? "Empty" : "Relearn move."); + ? new CheckResult(Severity.Invalid, "Not an expected relearn move.", CheckIdentifier.RelearnMove) + : new CheckResult(Severity.Valid, rl[j] == 0 ? "Empty" : "Relearn move.", CheckIdentifier.RelearnMove); if (res.All(r => r.Valid)) break; @@ -1072,16 +1333,16 @@ private LegalityCheck[] verifyRelearn() if (Moves[0] != 0) // DexNav only? { // Check DexNav - if (!Legal.getDexNavValid(pk6)) + if (!Legal.getDexNavValid(pkm)) goto noRelearn; - res[0] = !Legal.getValidRelearn(pk6, 0).Contains(Moves[0]) - ? new LegalityCheck(Severity.Invalid, "Not an expected DexNav move.") - : new LegalityCheck(); + res[0] = !Legal.getValidRelearn(pkm, 0).Contains(Moves[0]) + ? new CheckResult(Severity.Invalid, "Not an expected DexNav move.", CheckIdentifier.RelearnMove) + : new CheckResult(CheckIdentifier.RelearnMove); for (int i = 1; i < 4; i++) res[i] = Moves[i] != 0 - ? new LegalityCheck(Severity.Invalid, "Expected no Relearn Move in slot.") - : new LegalityCheck(); + ? new CheckResult(Severity.Invalid, "Expected no Relearn Move in slot.", CheckIdentifier.RelearnMove) + : new CheckResult(CheckIdentifier.RelearnMove); if (res[0].Valid) RelearnBase = new[] { Moves[0], 0, 0, 0 }; @@ -1092,8 +1353,8 @@ private LegalityCheck[] verifyRelearn() noRelearn: for (int i = 0; i < 4; i++) res[i] = Moves[i] != 0 - ? new LegalityCheck(Severity.Invalid, "Expected no Relearn Moves.") - : new LegalityCheck(); + ? new CheckResult(Severity.Invalid, "Expected no Relearn Moves.", CheckIdentifier.RelearnMove) + : new CheckResult(CheckIdentifier.RelearnMove); return res; } diff --git a/PKHeX/Legality/Core.cs b/PKHeX/Legality/Core.cs index f29c885f9..76a8f6661 100644 --- a/PKHeX/Legality/Core.cs +++ b/PKHeX/Legality/Core.cs @@ -24,10 +24,23 @@ public static partial class Legal private static readonly EncounterStatic[] StaticO; private static EncounterStatic[] getSpecial(GameVersion Game) { - if (Game == GameVersion.X || Game == GameVersion.Y) - return Encounter_XY.Where(s => s.Version == GameVersion.Any || s.Version == Game).ToArray(); - // else if (Game == GameVersion.AS || Game == GameVersion.OR) - return Encounter_AO.Where(s => s.Version == GameVersion.Any || s.Version == Game).ToArray(); + EncounterStatic[] table = null; + switch (Game) + { + case GameVersion.X: + case GameVersion.Y: + table = Encounter_XY; + break; + case GameVersion.AS: + case GameVersion.OR: + table = Encounter_AO; + break; + case GameVersion.SN: + case GameVersion.MN: + table = Encounter_SM; + break; + } + return table?.Where(s => s.Version == GameVersion.Any || s.Version == Game).ToArray(); } private static EncounterArea[] addXYAltTiles(EncounterArea[] GameSlots, EncounterArea[] SpecialSlots) { @@ -42,6 +55,7 @@ private static EncounterArea[] addXYAltTiles(EncounterArea[] GameSlots, Encounte static Legal() // Setup { + #region Gen6: XY & ORAS StaticX = getSpecial(GameVersion.X); StaticY = getSpecial(GameVersion.Y); StaticA = getSpecial(GameVersion.AS); @@ -92,28 +106,30 @@ private static EncounterArea[] addXYAltTiles(EncounterArea[] GameSlots, Encounte for (int i = 0; i < slotct; i++) area.Slots[i].AllowDexNav = area.Slots[i].Type != SlotType.Rock_Smash; } + #endregion } - internal static IEnumerable getValidMoves(PK6 pk6) - { return getValidMoves(pk6, -1, LVL: true, Relearn: false, Tutor: true, Machine: true); } - internal static IEnumerable getValidRelearn(PK6 pk6, int skipOption) + internal static IEnumerable getValidMoves(PKM pkm) + { return getValidMoves(pkm, -1, LVL: true, Relearn: false, Tutor: true, Machine: true); } + internal static IEnumerable getValidRelearn(PKM pkm, int skipOption) { List r = new List { 0 }; - int species = getBaseSpecies(pk6, skipOption); - r.AddRange(getLVLMoves(species, 1, pk6.AltForm)); - r.AddRange(getEggMoves(species, pk6.Species == 678 ? pk6.AltForm : 0)); - r.AddRange(getLVLMoves(species, 100, pk6.AltForm)); + int species = getBaseSpecies(pkm, skipOption); + r.AddRange(getLVLMoves(species, 1, pkm.AltForm)); + r.AddRange(getEggMoves(species, pkm.Species == 678 ? pkm.AltForm : 0)); + r.AddRange(getLVLMoves(species, 100, pkm.AltForm)); return r.Distinct(); } - internal static IEnumerable getBaseEggMoves(PK6 pk6, int skipOption, int gameSource) + internal static IEnumerable getBaseEggMoves(PKM pkm, int skipOption, int gameSource) { - int species = getBaseSpecies(pk6, skipOption); + int species = getBaseSpecies(pkm, skipOption); if (gameSource == -1) { - if (pk6.XY) + if (pkm.XY) return LevelUpXY[species].getMoves(1); - // if (pk6.Version == 26 || pk6.Version == 27) - return LevelUpAO[species].getMoves(1); + if (pkm.AO) + return LevelUpAO[species].getMoves(1); + return null; } if (gameSource == 0) // XY return LevelUpXY[species].getMoves(1); @@ -121,89 +137,89 @@ internal static IEnumerable getBaseEggMoves(PK6 pk6, int skipOption, int ga return LevelUpAO[species].getMoves(1); } - internal static IEnumerable getValidWC6s(PK6 pk6) + internal static IEnumerable getValidWC6s(PKM pkm) { - var vs = getValidPreEvolutions(pk6).ToArray(); - List validWC6 = new List(); + var vs = getValidPreEvolutions(pkm).ToArray(); + List validWC6 = new List(); foreach (WC6 wc in WC6DB.Where(wc => vs.Any(dl => dl.Species == wc.Species))) { - if (pk6.Egg_Location == 0) // Not Egg + if (pkm.Egg_Location == 0) // Not Egg { - if (wc.CardID != pk6.SID) continue; - if (wc.TID != pk6.TID) continue; - if (wc.OT != pk6.OT_Name) continue; - if (wc.OTGender != pk6.OT_Gender) continue; - if (wc.PIDType == 0 && pk6.PID != wc.PID) continue; - if (wc.PIDType == 2 && !pk6.IsShiny) continue; - if (wc.PIDType == 3 && pk6.IsShiny) continue; - if (wc.OriginGame != 0 && wc.OriginGame != pk6.Version) continue; - if (wc.EncryptionConstant != 0 && wc.EncryptionConstant != pk6.EncryptionConstant) continue; - if (wc.Language != 0 && wc.Language != pk6.Language) continue; + if (wc.CardID != pkm.SID) continue; + if (wc.TID != pkm.TID) continue; + if (wc.OT != pkm.OT_Name) continue; + if (wc.OTGender != pkm.OT_Gender) continue; + if (wc.PIDType == 0 && pkm.PID != wc.PID) continue; + if (wc.PIDType == 2 && !pkm.IsShiny) continue; + if (wc.PIDType == 3 && pkm.IsShiny) continue; + if (wc.OriginGame != 0 && wc.OriginGame != pkm.Version) continue; + if (wc.EncryptionConstant != 0 && wc.EncryptionConstant != pkm.EncryptionConstant) continue; + if (wc.Language != 0 && wc.Language != pkm.Language) continue; } - if (wc.Form != pk6.AltForm && vs.All(dl => !FormChange.Contains(dl.Species))) continue; - if (wc.MetLocation != pk6.Met_Location) continue; - if (wc.EggLocation != pk6.Egg_Location) continue; - if (wc.Level != pk6.Met_Level) continue; - if (wc.Pokéball != pk6.Ball) continue; - if (wc.OTGender < 3 && wc.OTGender != pk6.OT_Gender) continue; - if (wc.Nature != 0xFF && wc.Nature != pk6.Nature) continue; - if (wc.Gender != 3 && wc.Gender != pk6.Gender) continue; + if (wc.Form != pkm.AltForm && vs.All(dl => !FormChange.Contains(dl.Species))) continue; + if (wc.MetLocation != pkm.Met_Location) continue; + if (wc.EggLocation != pkm.Egg_Location) continue; + if (wc.Level != pkm.Met_Level) continue; + if (wc.Ball != pkm.Ball) continue; + if (wc.OTGender < 3 && wc.OTGender != pkm.OT_Gender) continue; + if (wc.Nature != 0xFF && wc.Nature != pkm.Nature) continue; + if (wc.Gender != 3 && wc.Gender != pkm.Gender) continue; - if (wc.CNT_Cool > pk6.CNT_Cool) continue; - if (wc.CNT_Beauty > pk6.CNT_Beauty) continue; - if (wc.CNT_Cute > pk6.CNT_Cute) continue; - if (wc.CNT_Smart > pk6.CNT_Smart) continue; - if (wc.CNT_Tough > pk6.CNT_Tough) continue; - if (wc.CNT_Sheen > pk6.CNT_Sheen) continue; + if (wc.CNT_Cool > pkm.CNT_Cool) continue; + if (wc.CNT_Beauty > pkm.CNT_Beauty) continue; + if (wc.CNT_Cute > pkm.CNT_Cute) continue; + if (wc.CNT_Smart > pkm.CNT_Smart) continue; + if (wc.CNT_Tough > pkm.CNT_Tough) continue; + if (wc.CNT_Sheen > pkm.CNT_Sheen) continue; // Some checks are best performed separately as they are caused by users screwing up valid data. - // if (!wc.RelearnMoves.SequenceEqual(pk6.RelearnMoves)) continue; // Defer to relearn legality - // if (wc.OT.Length > 0 && pk6.CurrentHandler != 1) continue; // Defer to ownership legality - // if (wc.OT.Length > 0 && pk6.OT_Friendship != PKX.getBaseFriendship(pk6.Species)) continue; // Friendship - // if (wc.Level > pk6.CurrentLevel) continue; // Defer to level legality + // if (!wc.RelearnMoves.SequenceEqual(pkm.RelearnMoves)) continue; // Defer to relearn legality + // if (wc.OT.Length > 0 && pkm.CurrentHandler != 1) continue; // Defer to ownership legality + // if (wc.OT.Length > 0 && pkm.OT_Friendship != PKX.getBaseFriendship(pkm.Species)) continue; // Friendship + // if (wc.Level > pkm.CurrentLevel) continue; // Defer to level legality // RIBBONS: Defer to ribbon legality validWC6.Add(wc); } return validWC6; } - internal static EncounterLink getValidLinkGifts(PK6 pk6) + internal static EncounterLink getValidLinkGifts(PKM pkm) { - return LinkGifts.FirstOrDefault(g => g.Species == pk6.Species && g.Level == pk6.Met_Level); + return LinkGifts.FirstOrDefault(g => g.Species == pkm.Species && g.Level == pkm.Met_Level); } - internal static EncounterSlot[] getValidWildEncounters(PK6 pk6) + internal static EncounterSlot[] getValidWildEncounters(PKM pkm) { List s = new List(); - foreach (var area in getEncounterAreas(pk6)) - s.AddRange(getValidEncounterSlots(pk6, area, DexNav: pk6.AO)); + foreach (var area in getEncounterAreas(pkm)) + s.AddRange(getValidEncounterSlots(pkm, area, DexNav: pkm.AO)); return s.Any() ? s.ToArray() : null; } - internal static EncounterStatic getValidStaticEncounter(PK6 pk6) + internal static EncounterStatic getValidStaticEncounter(PKM pkm) { // Get possible encounters - IEnumerable poss = getStaticEncounters(pk6); - // Back Check against pk6 + IEnumerable poss = getStaticEncounters(pkm); + // Back Check against pkm foreach (EncounterStatic e in poss) { - if (e.Nature != Nature.Random && pk6.Nature != (int)e.Nature) + if (e.Nature != Nature.Random && pkm.Nature != (int)e.Nature) continue; - if (e.EggLocation != pk6.Egg_Location) + if (e.EggLocation != pkm.Egg_Location) continue; - if (e.Location != 0 && e.Location != pk6.Met_Location) + if (e.Location != 0 && e.Location != pkm.Met_Location) continue; - if (e.Gender != -1 && e.Gender != pk6.Gender) + if (e.Gender != -1 && e.Gender != pkm.Gender) continue; - if (e.Level != pk6.Met_Level) + if (e.Level != pkm.Met_Level) continue; // Defer to EC/PID check - // if (e.Shiny != null && e.Shiny != pk6.IsShiny) + // if (e.Shiny != null && e.Shiny != pkm.IsShiny) // continue; // Defer ball check to later - // if (e.Gift && pk6.Ball != 4) // PokéBall + // if (e.Gift && pkm.Ball != 4) // PokéBall // continue; // Passes all checks, valid encounter @@ -211,119 +227,118 @@ internal static EncounterStatic getValidStaticEncounter(PK6 pk6) } return null; } - internal static EncounterTrade getValidIngameTrade(PK6 pk6) + internal static EncounterTrade getValidIngameTrade(PKM pkm) { - if (!pk6.WasIngameTrade) + if (!pkm.WasIngameTrade) return null; - int lang = pk6.Language; + int lang = pkm.Language; if (lang == 0) return null; // Get valid pre-evolutions - IEnumerable p = getValidPreEvolutions(pk6); + IEnumerable p = getValidPreEvolutions(pkm); EncounterTrade z = null; - if (pk6.XY) + if (pkm.XY) z = lang == 6 ? null : TradeGift_XY.FirstOrDefault(f => p.Any(r => r.Species == f.Species)); - if (pk6.AO) + if (pkm.AO) z = lang == 6 ? null : TradeGift_AO.FirstOrDefault(f => p.Any(r => r.Species == f.Species)); if (z == null) return null; for (int i = 0; i < 6; i++) - if (z.IVs[i] != -1 && z.IVs[i] != pk6.IVs[i]) + if (z.IVs[i] != -1 && z.IVs[i] != pkm.IVs[i]) return null; - if (z.Shiny ^ pk6.IsShiny) // Are PIDs static? + if (z.Shiny ^ pkm.IsShiny) // Are PIDs static? return null; - if (z.TID != pk6.TID) + if (z.TID != pkm.TID) return null; - if (z.SID != pk6.SID) + if (z.SID != pkm.SID) return null; - if (z.Location != pk6.Met_Location) + if (z.Location != pkm.Met_Location) return null; - if (z.Level != pk6.Met_Level) + if (z.Level != pkm.Met_Level) return null; - if (z.Nature != Nature.Random && (int)z.Nature != pk6.Nature) + if (z.Nature != Nature.Random && (int)z.Nature != pkm.Nature) return null; - if (z.Gender != pk6.Gender) + if (z.Gender != pkm.Gender) return null; - // if (z.Ability == 4 ^ pk6.AbilityNumber == 4) // defer to Ability + // if (z.Ability == 4 ^ pkm.AbilityNumber == 4) // defer to Ability // return null; return z; } - internal static EncounterSlot[] getValidFriendSafari(PK6 pk6) + internal static EncounterSlot[] getValidFriendSafari(PKM pkm) { - if (!pk6.XY) + if (!pkm.XY) return null; - if (pk6.Met_Location != 148) // Friend Safari + if (pkm.Met_Location != 148) // Friend Safari return null; - if (pk6.Met_Level != 30) + if (pkm.Met_Level != 30) return null; - IEnumerable vs = getValidPreEvolutions(pk6); + IEnumerable vs = getValidPreEvolutions(pkm); List slots = new List(); foreach (DexLevel d in vs.Where(d => FriendSafari.Contains(d.Species) && d.Level >= 30)) { - var slot = new EncounterSlot + slots.Add(new EncounterSlot { Species = d.Species, LevelMin = 30, LevelMax = 30, Form = 0, Type = SlotType.FriendSafari, - }; - slots.Add(slot); + }); } return slots.Any() ? slots.ToArray() : null; } - internal static bool getDexNavValid(PK6 pk6) + internal static bool getDexNavValid(PKM pkm) { - IEnumerable locs = getDexNavAreas(pk6); - return locs.Select(loc => getValidEncounterSlots(pk6, loc, DexNav: true)).Any(slots => slots.Any(slot => slot.AllowDexNav && slot.DexNav)); + IEnumerable locs = getDexNavAreas(pkm); + return locs.Select(loc => getValidEncounterSlots(pkm, loc, DexNav: true)).Any(slots => slots.Any(slot => slot.AllowDexNav && slot.DexNav)); } - internal static bool getHasEvolved(PK6 pk6) + internal static bool getHasEvolved(PKM pkm) { - return getValidPreEvolutions(pk6).Count() > 1; + return getValidPreEvolutions(pkm).Count() > 1; } - internal static bool getHasTradeEvolved(PK6 pk6) + internal static bool getHasTradeEvolved(PKM pkm) { - return Evolves[pk6.Species].Evos.Any(evo => evo.Level == 1); // 1: Trade, 0: Item, >=2: Levelup + return Evolves[pkm.Species].Evos.Any(evo => evo.Level == 1); // 1: Trade, 0: Item, >=2: Levelup } - internal static bool getIsFossil(PK6 pk6) + internal static bool getIsFossil(PKM pkm) { - if (pk6.Met_Level != 20) + if (pkm.Met_Level != 20) return false; - if (pk6.Egg_Location != 0) + if (pkm.Egg_Location != 0) return false; - if (pk6.XY && pk6.Met_Location == 44) - return Fossils.Contains(getBaseSpecies(pk6)); - if (pk6.AO && pk6.Met_Location == 190) - return Fossils.Contains(getBaseSpecies(pk6)); + if (pkm.XY && pkm.Met_Location == 44) + return Fossils.Contains(getBaseSpecies(pkm)); + if (pkm.AO && pkm.Met_Location == 190) + return Fossils.Contains(getBaseSpecies(pkm)); return false; } - internal static bool getEvolutionValid(PK6 pk6) + internal static bool getEvolutionValid(PKM pkm) { - var curr = getValidPreEvolutions(pk6); - var poss = getValidPreEvolutions(pk6, 100); + var curr = getValidPreEvolutions(pkm); + var poss = getValidPreEvolutions(pkm, 100); - if (SplitBreed.Contains(getBaseSpecies(pk6, 1))) + if (SplitBreed.Contains(getBaseSpecies(pkm, 1))) return curr.Count() >= poss.Count() - 1; return curr.Count() >= poss.Count(); } - internal static IEnumerable getLineage(PK6 pk6) + internal static IEnumerable getLineage(PKM pkm) { - int species = pk6.Species; + int species = pkm.Species; List res = new List{species}; for (int i = 0; i < Evolves.Length; i++) if (Evolves[i].Evos.Any(pk => pk.Species == species)) res.Add(i); for (int i = -1; i < 2; i++) - res.Add(getBaseSpecies(pk6, i)); + res.Add(getBaseSpecies(pkm, i)); return res.Distinct(); } @@ -363,45 +378,45 @@ internal static bool getCanBeCaptured(int species, int version = -1) } return false; } - internal static bool getCanLearnMachineMove(PK6 pk6, int move, int version = -1) + internal static bool getCanLearnMachineMove(PKM pkm, int move, int version = -1) { - return getValidMoves(pk6, version, Machine: true).Contains(move); + return getValidMoves(pkm, version, Machine: true).Contains(move); } - internal static bool getCanRelearnMove(PK6 pk6, int move, int version = -1) + internal static bool getCanRelearnMove(PKM pkm, int move, int version = -1) { - return getValidMoves(pk6, version, LVL: true, Relearn: true).Contains(move); + return getValidMoves(pkm, version, LVL: true, Relearn: true).Contains(move); } - internal static bool getCanLearnMove(PK6 pk6, int move, int version = -1) + internal static bool getCanLearnMove(PKM pkm, int move, int version = -1) { - return getValidMoves(pk6, version, Tutor: true, Machine: true).Contains(move); + return getValidMoves(pkm, version, Tutor: true, Machine: true).Contains(move); } - internal static bool getCanKnowMove(PK6 pk6, int move, int version = -1) + internal static bool getCanKnowMove(PKM pkm, int move, int version = -1) { - if (pk6.Species == 235 && !InvalidSketch.Contains(move)) + if (pkm.Species == 235 && !InvalidSketch.Contains(move)) return true; - return getValidMoves(pk6, Version: version, LVL: true, Relearn: true, Tutor: true, Machine: true).Contains(move); + return getValidMoves(pkm, Version: version, LVL: true, Relearn: true, Tutor: true, Machine: true).Contains(move); } - private static int getBaseSpecies(PK6 pk6, int skipOption = 0) + private static int getBaseSpecies(PKM pkm, int skipOption = 0) { - if (pk6.Species == 292) + if (pkm.Species == 292) return 290; - if (pk6.Species == 242 && pk6.CurrentLevel < 3) // Never Cleffa + if (pkm.Species == 242 && pkm.CurrentLevel < 3) // Never Cleffa return 113; - DexLevel[] evos = Evolves[pk6.Species].Evos; + DexLevel[] evos = Evolves[pkm.Species].Evos; switch (skipOption) { - case -1: return pk6.Species; - case 1: return evos.Length <= 1 ? pk6.Species : evos[evos.Length - 2].Species; - default: return evos.Length <= 0 ? pk6.Species : evos.Last().Species; + case -1: return pkm.Species; + case 1: return evos.Length <= 1 ? pkm.Species : evos[evos.Length - 2].Species; + default: return evos.Length <= 0 ? pkm.Species : evos.Last().Species; } } - private static IEnumerable getDexNavAreas(PK6 pk6) + private static IEnumerable getDexNavAreas(PKM pkm) { - bool alpha = pk6.Version == 26; - if (!alpha && pk6.Version != 27) + bool alpha = pkm.Version == 26; + if (!alpha && pkm.Version != 27) return new EncounterArea[0]; - return (alpha ? SlotsA : SlotsO).Where(l => l.Location == pk6.Met_Location); + return (alpha ? SlotsA : SlotsO).Where(l => l.Location == pkm.Met_Location); } private static IEnumerable getLVLMoves(int species, int lvl, int formnum) { @@ -409,41 +424,41 @@ private static IEnumerable getLVLMoves(int species, int lvl, int formnum) int ind_AO = PersonalTable.AO.getFormeIndex(species, formnum); return LevelUpXY[ind_XY].getMoves(lvl).Concat(LevelUpAO[ind_AO].getMoves(lvl)); } - private static IEnumerable getEncounterSlots(PK6 pk6) + private static IEnumerable getEncounterSlots(PKM pkm) { - switch (pk6.Version) + switch (pkm.Version) { case 24: // X - return getSlots(pk6, SlotsX); + return getSlots(pkm, SlotsX); case 25: // Y - return getSlots(pk6, SlotsY); + return getSlots(pkm, SlotsY); case 26: // AS - return getSlots(pk6, SlotsA); + return getSlots(pkm, SlotsA); case 27: // OR - return getSlots(pk6, SlotsO); + return getSlots(pkm, SlotsO); default: return new List(); } } - private static IEnumerable getStaticEncounters(PK6 pk6) + private static IEnumerable getStaticEncounters(PKM pkm) { - switch (pk6.Version) + switch (pkm.Version) { case 24: // X - return getStatic(pk6, StaticX); + return getStatic(pkm, StaticX); case 25: // Y - return getStatic(pk6, StaticY); + return getStatic(pkm, StaticY); case 26: // AS - return getStatic(pk6, StaticA); + return getStatic(pkm, StaticA); case 27: // OR - return getStatic(pk6, StaticO); + return getStatic(pkm, StaticO); default: return new List(); } } - private static IEnumerable getEncounterAreas(PK6 pk6) + private static IEnumerable getEncounterAreas(PKM pkm) { - return getEncounterSlots(pk6).Where(l => l.Location == pk6.Met_Location); + return getEncounterSlots(pkm).Where(l => l.Location == pkm.Met_Location); } - private static IEnumerable getValidEncounterSlots(PK6 pk6, EncounterArea loc, bool DexNav) + private static IEnumerable getValidEncounterSlots(PKM pkm, EncounterArea loc, bool DexNav) { const int fluteBoost = 4; const int dexnavBoost = 30; @@ -452,36 +467,36 @@ private static IEnumerable getValidEncounterSlots(PK6 pk6, Encoun List slotdata = new List(); // Get Valid levels - IEnumerable vs = getValidPreEvolutions(pk6); + IEnumerable vs = getValidPreEvolutions(pkm); // Get slots where pokemon can exist IEnumerable slots = loc.Slots.Where(slot => vs.Any(evo => evo.Species == slot.Species && evo.Level >= slot.LevelMin - df)); // Filter for Met Level - int lvl = pk6.Met_Level; + int lvl = pkm.Met_Level; var encounterSlots = slots.Where(slot => slot.LevelMin - df <= lvl && lvl <= slot.LevelMax + (slot.AllowDexNav ? dn : df)).ToList(); // Pressure Slot EncounterSlot slotMax = encounterSlots.OrderByDescending(slot => slot.LevelMax).FirstOrDefault(); if (slotMax != null) - slotMax = new EncounterSlot(slotMax) { Pressure = true, Form = pk6.AltForm }; + slotMax = new EncounterSlot(slotMax) { Pressure = true, Form = pkm.AltForm }; if (!DexNav) { // Filter for Form Specific - slotdata.AddRange(WildForms.Contains(pk6.Species) - ? encounterSlots.Where(slot => slot.Form == pk6.AltForm) + slotdata.AddRange(WildForms.Contains(pkm.Species) + ? encounterSlots.Where(slot => slot.Form == pkm.AltForm) : encounterSlots); if (slotMax != null) slotdata.Add(slotMax); return slotdata; } - List eslots = encounterSlots.Where(slot => !WildForms.Contains(pk6.Species) || slot.Form == pk6.AltForm).ToList(); + List eslots = encounterSlots.Where(slot => !WildForms.Contains(pkm.Species) || slot.Form == pkm.AltForm).ToList(); if (slotMax != null) eslots.Add(slotMax); foreach (EncounterSlot s in eslots) { - bool nav = s.AllowDexNav && (pk6.RelearnMove1 != 0 || pk6.AbilityNumber == 4); + bool nav = s.AllowDexNav && (pkm.RelearnMove1 != 0 || pkm.AbilityNumber == 4); EncounterSlot slot = new EncounterSlot(s) { DexNav = nav }; if (slot.LevelMin > lvl) @@ -494,9 +509,9 @@ private static IEnumerable getValidEncounterSlots(PK6 pk6, Encoun } return slotdata; } - private static IEnumerable getSlots(PK6 pk6, IEnumerable tables) + private static IEnumerable getSlots(PKM pkm, IEnumerable tables) { - IEnumerable vs = getValidPreEvolutions(pk6); + IEnumerable vs = getValidPreEvolutions(pkm); List slotLocations = new List(); foreach (var loc in tables) { @@ -508,21 +523,21 @@ private static IEnumerable getSlots(PK6 pk6, IEnumerable getValidPreEvolutions(PK6 pk6, int lvl = -1) + private static IEnumerable getValidPreEvolutions(PKM pkm, int lvl = -1) { if (lvl < 0) - lvl = pk6.CurrentLevel; - if (pk6.Species == 292 && pk6.Met_Level + 1 <= lvl && lvl >= 20) + lvl = pkm.CurrentLevel; + if (pkm.Species == 292 && pkm.Met_Level + 1 <= lvl && lvl >= 20) return new List { new DexLevel { Species = 292, Level = lvl }, new DexLevel { Species = 290, Level = lvl-1 } }; - var evos = Evolves[pk6.Species].Evos; - List dl = new List { new DexLevel { Species = pk6.Species, Level = lvl } }; + var evos = Evolves[pkm.Species].Evos; + List dl = new List { new DexLevel { Species = pkm.Species, Level = lvl } }; foreach (DexLevel evo in evos) { - if (lvl >= pk6.Met_Level && lvl >= evo.Level) + if (lvl >= pkm.Met_Level && lvl >= evo.Level) dl.Add(new DexLevel {Species = evo.Species, Level = lvl}); else break; if (evo.Level > 2) // Level Up (from previous level) @@ -530,37 +545,37 @@ private static IEnumerable getValidPreEvolutions(PK6 pk6, int lvl = -1 } return dl; } - private static IEnumerable getStatic(PK6 pk6, IEnumerable table) + private static IEnumerable getStatic(PKM pkm, IEnumerable table) { - IEnumerable dl = getValidPreEvolutions(pk6); + IEnumerable dl = getValidPreEvolutions(pkm); return table.Where(e => dl.Any(d => d.Species == e.Species)); } - private static IEnumerable getValidMoves(PK6 pk6, int Version, bool LVL = false, bool Relearn = false, bool Tutor = false, bool Machine = false) + private static IEnumerable getValidMoves(PKM pkm, int Version, bool LVL = false, bool Relearn = false, bool Tutor = false, bool Machine = false) { List r = new List { 0 }; - int species = pk6.Species; - int lvl = pk6.CurrentLevel; - bool ORASTutors = Version == -1 || pk6.AO || !pk6.IsUntraded; + int species = pkm.Species; + int lvl = pkm.CurrentLevel; + bool ORASTutors = Version == -1 || pkm.AO || !pkm.IsUntraded; if (FormChangeMoves.Contains(species)) // Deoxys & Shaymin & Giratina (others don't have extra but whatever) { int formcount = PersonalTable.AO[species].FormeCount; for (int i = 0; i < formcount; i++) r.AddRange(getMoves(species, lvl, i, ORASTutors, Version, LVL, Tutor, Machine)); - if (Relearn) r.AddRange(pk6.RelearnMoves); + if (Relearn) r.AddRange(pkm.RelearnMoves); return r.Distinct().ToArray(); } - r.AddRange(getMoves(species, lvl, pk6.AltForm, ORASTutors, Version, LVL, Tutor, Machine)); - IEnumerable vs = getValidPreEvolutions(pk6); + r.AddRange(getMoves(species, lvl, pkm.AltForm, ORASTutors, Version, LVL, Tutor, Machine)); + IEnumerable vs = getValidPreEvolutions(pkm); foreach (DexLevel evo in vs) - r.AddRange(getMoves(evo.Species, evo.Level, pk6.AltForm, ORASTutors, Version, LVL, Tutor, Machine)); + r.AddRange(getMoves(evo.Species, evo.Level, pkm.AltForm, ORASTutors, Version, LVL, Tutor, Machine)); if (species == 479) // Rotom - r.Add(RotomMoves[pk6.AltForm]); + r.Add(RotomMoves[pkm.AltForm]); if (species == 25) // Pikachu - r.Add(PikachuMoves[pk6.AltForm]); + r.Add(PikachuMoves[pkm.AltForm]); - if (Relearn) r.AddRange(pk6.RelearnMoves); + if (Relearn) r.AddRange(pkm.RelearnMoves); return r.Distinct().ToArray(); } private static IEnumerable getMoves(int species, int lvl, int form, bool ORASTutors, int Version, bool LVL, bool Tutor, bool Machine) diff --git a/PKHeX/Legality/Tables6.cs b/PKHeX/Legality/Tables6.cs index 5555397c7..3333888c5 100644 --- a/PKHeX/Legality/Tables6.cs +++ b/PKHeX/Legality/Tables6.cs @@ -312,7 +312,13 @@ public static partial class Legal internal static readonly int[] Gen4EncounterTypes = { 1, 2, 4, 5, 7, 9, 10, 12, 23, 24 }; internal const int Struggle = 165; internal const int Chatter = 448; - internal static readonly int[] InvalidSketch = {Struggle, Chatter}; + internal static readonly int[] InvalidSketch = + { + // Regular Moves + Struggle, Chatter + // Z-Moves + + }; internal static readonly int[] EggLocations = {60002, 30002}; internal static readonly int[] LightBall = {25, 26, 172}; internal static readonly int[] Fossils = {138, 140, 142, 345, 347, 408, 410, 564, 566, 696, 698}; @@ -861,7 +867,7 @@ public static partial class Legal 497, 500, 503, //3 566, 567, 696, 697, 698, 699 // Fossil Only obtain }; - internal static readonly int[] WurmpleFamily = + internal static readonly int[] WurmpleEvolutions = { 266, 267, // Silcoon Beautifly 268, 269, // Cascoon Dustox diff --git a/PKHeX/Legality/Tables7.cs b/PKHeX/Legality/Tables7.cs index 38d0efe67..6affaaa67 100644 --- a/PKHeX/Legality/Tables7.cs +++ b/PKHeX/Legality/Tables7.cs @@ -37,5 +37,10 @@ public static partial class Legal 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 798, 799, 800, 801, 802, 803, 804, 805, 806, 836 }; internal static readonly ushort[] HeldItems_SM = new ushort[1].Concat(Pouch_Items_SM).Concat(Pouch_Berries_SM).Concat(Pouch_Medicine_SM).Concat(Pouch_ZCrystal_SM).ToArray(); + + private static readonly EncounterStatic[] Encounter_SM = + { + + }; } } diff --git a/PKHeX/MainWindow/Main.cs b/PKHeX/MainWindow/Main.cs index 851f55cd7..2a20bf4d8 100644 --- a/PKHeX/MainWindow/Main.cs +++ b/PKHeX/MainWindow/Main.cs @@ -2113,7 +2113,7 @@ private void updateRandomEC(object sender, EventArgs e) Util.Alert("EC should match PID."); } - int wIndex = Array.IndexOf(Legal.WurmpleFamily, Util.getIndex(CB_Species)); + int wIndex = Array.IndexOf(Legal.WurmpleEvolutions, Util.getIndex(CB_Species)); if (wIndex < 0) { TB_EC.Text = Util.rnd32().ToString("X8"); @@ -2774,6 +2774,11 @@ private void removedropCB(object sender, KeyEventArgs e) private void showLegality(PKM pk, bool tabs, bool verbose) { LegalityAnalysis la = new LegalityAnalysis(pk); + if (!la.Native) + { + Util.Alert($"Checking legality of PK{pk.Format} files that originated from Gen{pk.GenNumber} is not supported."); + return; + } if (tabs) updateLegality(la); Util.Alert(verbose ? la.VerboseReport : la.Report); @@ -2782,11 +2787,15 @@ private void updateLegality(LegalityAnalysis la = null) { if (!fieldsLoaded) return; - if (!(pkm is PK6)) + Legality = la ?? new LegalityAnalysis(pkm); + if (!Legality.Parsed || !Legality.Native || HaX) + { + PB_Legal.Visible = false; return; - Legality = la ?? new LegalityAnalysis((PK6) pkm); + } + PB_Legal.Visible = true; + PB_Legal.Image = Legality.Valid ? Properties.Resources.valid : Properties.Resources.warn; - PB_Legal.Visible = pkm.Gen6 /*&& pkm is PK6*/ && !HaX; // Refresh Move Legality for (int i = 0; i < 4; i++) @@ -3378,12 +3387,7 @@ private void clickLegality(object sender, EventArgs e) if (pk.Species == 0 || !pk.ChecksumValid) { SystemSounds.Asterisk.Play(); return; } - if (typeof (PK6) != pk.GetType()) - { - Util.Alert($"Checking legality of {pk.GetType().Name} files is not supported."); - return; - } - showLegality(pk as PK6, slot < 0, ModifierKeys == Keys.Control); + showLegality(pk, slot < 0, ModifierKeys == Keys.Control); } private void updateSaveSlot(object sender, EventArgs e) { @@ -3715,7 +3719,7 @@ private void getBox(object sender, EventArgs e) } private void switchDaycare(object sender, EventArgs e) { - if (!SAV.ORAS) return; + if (!SAV.HasTwoDaycares) return; if (DialogResult.Yes == Util.Prompt(MessageBoxButtons.YesNo, "Would you like to switch the view to the other Daycare?", $"Currently viewing daycare {SAV.DaycareIndex + 1}.")) // If ORAS, alter the daycare offset via toggle. diff --git a/PKHeX/MysteryGifts/MysteryGift.cs b/PKHeX/MysteryGifts/MysteryGift.cs index 28ae1a87b..8adb48e31 100644 --- a/PKHeX/MysteryGifts/MysteryGift.cs +++ b/PKHeX/MysteryGifts/MysteryGift.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; namespace PKHeX { @@ -90,10 +89,15 @@ public MysteryGift Clone() // Search Properties public virtual int Species { get { return -1; } set { } } - public virtual int[] Moves => new int[0]; + public virtual int[] Moves => new int[4]; + public virtual int[] RelearnMoves { get { return new int[4]; } set { } } public virtual bool IsShiny => false; public virtual bool IsEgg { get { return false; } set { } } public virtual int HeldItem { get { return -1; } set { } } + public virtual object Content => this; + + public abstract int Level { get; set; } + public abstract int Ball { get; set; } public bool Gen7 => Format == 7; public bool Gen6 => Format == 6; public bool Gen5 => Format == 5; diff --git a/PKHeX/MysteryGifts/PGF.cs b/PKHeX/MysteryGifts/PGF.cs index 7271e16fb..2b2ed8e72 100644 --- a/PKHeX/MysteryGifts/PGF.cs +++ b/PKHeX/MysteryGifts/PGF.cs @@ -40,7 +40,7 @@ public PGF(byte[] data = null) public bool RibbonChampionWorld { get { return (RIB1 & (1 << 6)) == 1 << 6; } set { RIB1 = (byte)(RIB1 & ~(1 << 6) | (value ? 1 << 6 : 0)); } } // World Champ Ribbon public bool RIB1_7 { get { return (RIB1 & (1 << 7)) == 1 << 7; } set { RIB1 = (byte)(RIB1 & ~(1 << 7) | (value ? 1 << 7 : 0)); } } // Empty - public int Pokéball { get { return Data[0x0E]; } set { Data[0x0E] = (byte)value; } } + public override int Ball { get { return Data[0x0E]; } set { Data[0x0E] = (byte)value; } } public override int HeldItem { get { return BitConverter.ToUInt16(Data, 0x10); } set { BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x10); } } public int Move1 { get { return BitConverter.ToUInt16(Data, 0x12); } set { BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x12); } } public int Move2 { get { return BitConverter.ToUInt16(Data, 0x14); } set { BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x14); } } @@ -79,7 +79,7 @@ public string Nickname get { return PKX.TrimFromFFFF(Encoding.Unicode.GetString(Data, 0x4A, 0x10)); } set { Encoding.Unicode.GetBytes(value.PadRight(0x08, (char)0xFFFF)).CopyTo(Data, 0x4A); } } public int OTGender { get { return Data[0x5A]; } set { Data[0x5A] = (byte)value; } } - public int Level { get { return Data[0x5B]; } set { Data[0x5C] = (byte)value; } } + public override int Level { get { return Data[0x5B]; } set { Data[0x5C] = (byte)value; } } public override bool IsEgg { get { return Data[0x5C] == 1; } set { Data[0x5C] = (byte)(value ? 1 : 0); } } // Unused 0x5D 0x5E 0x5F public override string CardTitle @@ -174,7 +174,7 @@ public override PKM convertToPKM(SaveFile SAV) AltForm = Form, Version = OriginGame == 0 ? new[] {20, 21, 22, 23}[Util.rnd32() & 0x3] : OriginGame, Language = Language == 0 ? SAV.Language : Language, - Ball = Pokéball, + Ball = Ball, Move1 = Move1, Move2 = Move2, Move3 = Move3, diff --git a/PKHeX/MysteryGifts/PGT.cs b/PKHeX/MysteryGifts/PGT.cs index 354526bde..bda39cf54 100644 --- a/PKHeX/MysteryGifts/PGT.cs +++ b/PKHeX/MysteryGifts/PGT.cs @@ -12,7 +12,17 @@ public sealed class PCD : MysteryGift { internal const int Size = 0x358; // 856 public override int Format => 4; - + public override int Level + { + get { return Gift.Level; } + set { Gift.Level = value; } + } + public override int Ball + { + get { return Gift.Ball; } + set { Gift.Ball = value; } + } + public PCD(byte[] data = null) { Data = (byte[])(data?.Clone() ?? new byte[Size]); @@ -25,6 +35,7 @@ public PCD(byte[] data = null) Array.Copy(Data, PGT.Size, Information, 0, Information.Length); } public readonly PGT Gift; + public override object Content => Gift.PK; public readonly byte[] Information; public override bool GiftUsed { get { return Gift.GiftUsed; } set { Gift.GiftUsed = value; } } @@ -65,6 +76,16 @@ public class PGT : MysteryGift { internal const int Size = 0x104; // 260 public override int Format => 4; + public override int Level + { + get { return IsPokémon ? PK.Met_Level : 0; } + set { if (IsPokémon) PK.Met_Level = value; } + } + public override int Ball + { + get { return IsPokémon ? PK.Ball : 0; } + set { if (IsPokémon) PK.Ball = value; } + } private enum GiftType { @@ -86,6 +107,7 @@ private enum GiftType public override string CardTitle { get { return "Raw Gift (PGT)"; } set { } } public override int CardID { get { return -1; } set { } } public override bool GiftUsed { get { return false; } set { } } + public override object Content => PK; public PGT(byte[] data = null) { diff --git a/PKHeX/MysteryGifts/WC6.cs b/PKHeX/MysteryGifts/WC6.cs index 9b6ad1c88..8499a542d 100644 --- a/PKHeX/MysteryGifts/WC6.cs +++ b/PKHeX/MysteryGifts/WC6.cs @@ -107,7 +107,7 @@ public WC6(byte[] data = null) public uint EncryptionConstant { get { return BitConverter.ToUInt32(Data, 0x70); } set { BitConverter.GetBytes(value).CopyTo(Data, 0x70); } } - public int Pokéball { + public override int Ball { get { return Data[0x76]; } set { Data[0x76] = (byte)value; } } public override int HeldItem { @@ -174,7 +174,7 @@ public WC6(byte[] data = null) public string OT { get { return Util.TrimFromZero(Encoding.Unicode.GetString(Data, 0xB6, 0x1A)); } set { Encoding.Unicode.GetBytes(value.PadRight(value.Length + 1, '\0')).CopyTo(Data, 0xB6); } } - public int Level { get { return Data[0xD0]; } set { Data[0xD0] = (byte)value; } } + public override int Level { get { return Data[0xD0]; } set { Data[0xD0] = (byte)value; } } public override bool IsEgg { get { return Data[0xD1] == 1; } set { Data[0xD1] = (byte)(value ? 1 : 0); } } public uint PID { get { return BitConverter.ToUInt32(Data, 0xD4); } @@ -223,7 +223,7 @@ public override int[] Moves { get { return new[] {Move1, Move2, Move3, Move4}; } } - public int[] RelearnMoves + public override int[] RelearnMoves { get { return new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; } set @@ -254,7 +254,7 @@ public override PKM convertToPKM(SaveFile SAV) EncryptionConstant = EncryptionConstant == 0 ? Util.rnd32() : EncryptionConstant, Version = OriginGame == 0 ? SAV.Game : OriginGame, Language = Language == 0 ? SAV.Language : Language, - Ball = Pokéball, + Ball = Ball, Country = SAV.Country, Region = SAV.SubRegion, ConsoleRegion = SAV.ConsoleRegion, diff --git a/PKHeX/PKM/CK3.cs b/PKHeX/PKM/CK3.cs index 0b2270164..8eec11b56 100644 --- a/PKHeX/PKM/CK3.cs +++ b/PKHeX/PKM/CK3.cs @@ -166,7 +166,7 @@ public CK3(byte[] decryptedData = null, string ident = null) public override int PKRS_Strain { get { return Data[0xCA] & 0xF; } set { Data[0xCA] = (byte)(value & 0xF); } } public override bool IsEgg { get { return Data[0xCB] == 1; } set { Data[0xCB] = (byte)(value ? 1 : 0); } } - public int AbilityNumber { get { return Data[0xCC]; } set { Data[0xCC] = (byte)(value & 1); } } + public override int AbilityNumber { get { return Data[0xCC]; } set { Data[0xCC] = (byte)(value & 1); } } public override bool Valid { get { return Data[0xCD] == 0; } set { if (value) Data[0xCD] = 0; } } // 0xCE unknown public override byte MarkByte { get { return Data[0xCF]; } protected set { Data[0xCF] = value; } } diff --git a/PKHeX/PKM/PK3.cs b/PKHeX/PKM/PK3.cs index c64bc399b..b6c357084 100644 --- a/PKHeX/PKM/PK3.cs +++ b/PKHeX/PKM/PK3.cs @@ -118,7 +118,7 @@ public PK3(byte[] decryptedData = null, string ident = null) public override int IV_SPA { get { return (int)(IV32 >> 20) & 0x1F; } set { IV32 = (uint)((IV32 & ~(0x1F << 20)) | (uint)((value > 31 ? 31 : value) << 20)); } } public override int IV_SPD { get { return (int)(IV32 >> 25) & 0x1F; } set { IV32 = (uint)((IV32 & ~(0x1F << 25)) | (uint)((value > 31 ? 31 : value) << 25)); } } public override bool IsEgg { get { return ((IV32 >> 30) & 1) == 1; } set { IV32 = (uint)((IV32 & ~0x40000000) | (uint)(value ? 0x40000000 : 0)); } } - public int AbilityNumber { get { return (int)((IV32 >> 31) & 1); } set { IV32 = (IV32 & 0x7FFFFFFF) | (value == 1 ? 0x80000000 : 0); } } + public override int AbilityNumber { get { return (int)((IV32 >> 31) & 1); } set { IV32 = (IV32 & 0x7FFFFFFF) | (value == 1 ? 0x80000000 : 0); } } private uint RIB0 { get { return BitConverter.ToUInt32(Data, 0x4C); } set { BitConverter.GetBytes(value).CopyTo(Data, 0x4C); } } public int RibbonCountG3Cool { get { return (int)(RIB0 >> 00) & 7; } set { RIB0 = (uint)((RIB0 & ~(7 << 00)) | (uint)(value & 7) << 00); } } diff --git a/PKHeX/PKM/PK6.cs b/PKHeX/PKM/PK6.cs index c9fcf0219..3be0df10d 100644 --- a/PKHeX/PKM/PK6.cs +++ b/PKHeX/PKM/PK6.cs @@ -67,7 +67,7 @@ public override uint EXP set { BitConverter.GetBytes(value).CopyTo(Data, 0x10); } } public override int Ability { get { return Data[0x14]; } set { Data[0x14] = (byte)value; } } - public int AbilityNumber { get { return Data[0x15]; } set { Data[0x15] = (byte)value; } } + public override int AbilityNumber { get { return Data[0x15]; } set { Data[0x15] = (byte)value; } } public int TrainingBagHits { get { return Data[0x16]; } set { Data[0x16] = (byte)value; } } public int TrainingBag { get { return Data[0x17]; } set { Data[0x17] = (byte)value; } } public override uint PID @@ -274,8 +274,8 @@ public override int RelearnMove4 get { return BitConverter.ToUInt16(Data, 0x70); } set { BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x70); } } - public bool SecretSuperTrainingUnlocked { get { return (Data[0x72] & 1) == 1; } set { Data[0x72] = (byte)((Data[0x72] & ~1) | (value ? 1 : 0)); } } - public bool SecretSuperTrainingComplete { get { return (Data[0x72] & 2) == 2; } set { Data[0x72] = (byte)((Data[0x72] & ~2) | (value ? 2 : 0)); } } + public override bool SecretSuperTrainingUnlocked { get { return (Data[0x72] & 1) == 1; } set { Data[0x72] = (byte)((Data[0x72] & ~1) | (value ? 1 : 0)); } } + public override bool SecretSuperTrainingComplete { get { return (Data[0x72] & 2) == 2; } set { Data[0x72] = (byte)((Data[0x72] & ~2) | (value ? 2 : 0)); } } public byte _0x73 { get { return Data[0x73]; } set { Data[0x73] = value; } } private uint IV32 { get { return BitConverter.ToUInt32(Data, 0x74); } set { BitConverter.GetBytes(value).CopyTo(Data, 0x74); } } public override int IV_HP { get { return (int)(IV32 >> 00) & 0x1F; } set { IV32 = (uint)((IV32 & ~(0x1F << 00)) | (uint)((value > 31 ? 31 : value) << 00)); } } @@ -381,9 +381,9 @@ public override string OT_Name public override int OT_Gender { get { return Data[0xDD] >> 7; } set { Data[0xDD] = (byte)((Data[0xDD] & ~0x80) | (value << 7)); } } public override int EncounterType { get { return Data[0xDE]; } set { Data[0xDE] = (byte)value; } } public override int Version { get { return Data[0xDF]; } set { Data[0xDF] = (byte)value; } } - public int Country { get { return Data[0xE0]; } set { Data[0xE0] = (byte)value; } } - public int Region { get { return Data[0xE1]; } set { Data[0xE1] = (byte)value; } } - public int ConsoleRegion { get { return Data[0xE2]; } set { Data[0xE2] = (byte)value; } } + public override int Country { get { return Data[0xE0]; } set { Data[0xE0] = (byte)value; } } + public override int Region { get { return Data[0xE1]; } set { Data[0xE1] = (byte)value; } } + public override int ConsoleRegion { get { return Data[0xE2]; } set { Data[0xE2] = (byte)value; } } public override int Language { get { return Data[0xE3]; } set { Data[0xE3] = (byte)value; } } #endregion #region Battle Stats @@ -410,7 +410,6 @@ public int OppositeFriendship public override int PSV => (int)((PID >> 16 ^ PID & 0xFFFF) >> 4); public override int TSV => (TID ^ SID) >> 4; - public bool IsUntraded => string.IsNullOrWhiteSpace(HT_Name); public bool IsUntradedEvent6 => Geo1_Country == 0 && Geo1_Region == 0 && Met_Location / 10000 == 4 && Gen6; // Complex Generated Attributes @@ -624,11 +623,11 @@ public void TradeFriendshipAffection(string SAV_TRAINER) } // Legality Properties - public bool WasLink => Met_Location == 30011; - public bool WasEgg => Legal.EggLocations.Contains(Egg_Location); - public bool WasEvent => Met_Location > 40000 && Met_Location < 50000 || FatefulEncounter && Species != 386; - public bool WasEventEgg => ((Egg_Location > 40000 && Egg_Location < 50000) || (FatefulEncounter && Egg_Location == 30002)) && Met_Level == 1; - public bool WasTradedEgg => Egg_Location == 30002; - public bool WasIngameTrade => Met_Location == 30001; + public override bool WasLink => Met_Location == 30011; + public override bool WasEgg => Legal.EggLocations.Contains(Egg_Location); + public override bool WasEvent => Met_Location > 40000 && Met_Location < 50000 || FatefulEncounter && Species != 386; + public override bool WasEventEgg => ((Egg_Location > 40000 && Egg_Location < 50000) || (FatefulEncounter && Egg_Location == 30002)) && Met_Level == 1; + public override bool WasTradedEgg => Egg_Location == 30002; + public override bool WasIngameTrade => Met_Location == 30001; } } diff --git a/PKHeX/PKM/PK7.cs b/PKHeX/PKM/PK7.cs index c39ef9056..c53616973 100644 --- a/PKHeX/PKM/PK7.cs +++ b/PKHeX/PKM/PK7.cs @@ -67,7 +67,7 @@ public override uint EXP set { BitConverter.GetBytes(value).CopyTo(Data, 0x10); } } public override int Ability { get { return Data[0x14]; } set { Data[0x14] = (byte)value; } } - public int AbilityNumber { get { return Data[0x15]; } set { Data[0x15] = (byte)value; } } + public override int AbilityNumber { get { return Data[0x15]; } set { Data[0x15] = (byte)value; } } public int TrainingBagHits { get { return Data[0x16]; } set { Data[0x16] = (byte)value; } } public int TrainingBag { get { return Data[0x17]; } set { Data[0x17] = (byte)value; } } public override uint PID @@ -282,8 +282,8 @@ public override int RelearnMove4 get { return BitConverter.ToUInt16(Data, 0x70); } set { BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x70); } } - public bool SecretSuperTrainingUnlocked { get { return (Data[0x72] & 1) == 1; } set { Data[0x72] = (byte)((Data[0x72] & ~1) | (value ? 1 : 0)); } } - public bool SecretSuperTrainingComplete { get { return (Data[0x72] & 2) == 2; } set { Data[0x72] = (byte)((Data[0x72] & ~2) | (value ? 2 : 0)); } } + public override bool SecretSuperTrainingUnlocked { get { return (Data[0x72] & 1) == 1; } set { Data[0x72] = (byte)((Data[0x72] & ~1) | (value ? 1 : 0)); } } + public override bool SecretSuperTrainingComplete { get { return (Data[0x72] & 2) == 2; } set { Data[0x72] = (byte)((Data[0x72] & ~2) | (value ? 2 : 0)); } } public byte _0x73 { get { return Data[0x73]; } set { Data[0x73] = value; } } private uint IV32 { get { return BitConverter.ToUInt32(Data, 0x74); } set { BitConverter.GetBytes(value).CopyTo(Data, 0x74); } } public override int IV_HP { get { return (int)(IV32 >> 00) & 0x1F; } set { IV32 = (uint)((IV32 & ~(0x1F << 00)) | (uint)((value > 31 ? 31 : value) << 00)); } } @@ -389,9 +389,9 @@ public override string OT_Name public override int OT_Gender { get { return Data[0xDD] >> 7; } set { Data[0xDD] = (byte)((Data[0xDD] & ~0x80) | (value << 7)); } } public override int EncounterType { get { return Data[0xDE]; } set { Data[0xDE] = (byte)value; } } public override int Version { get { return Data[0xDF]; } set { Data[0xDF] = (byte)value; } } - public int Country { get { return Data[0xE0]; } set { Data[0xE0] = (byte)value; } } - public int Region { get { return Data[0xE1]; } set { Data[0xE1] = (byte)value; } } - public int ConsoleRegion { get { return Data[0xE2]; } set { Data[0xE2] = (byte)value; } } + public override int Country { get { return Data[0xE0]; } set { Data[0xE0] = (byte)value; } } + public override int Region { get { return Data[0xE1]; } set { Data[0xE1] = (byte)value; } } + public override int ConsoleRegion { get { return Data[0xE2]; } set { Data[0xE2] = (byte)value; } } public override int Language { get { return Data[0xE3]; } set { Data[0xE3] = (byte)value; } } #endregion #region Battle Stats @@ -418,7 +418,6 @@ public int OppositeFriendship public override int PSV => (int)((PID >> 16 ^ PID & 0xFFFF) >> 4); public override int TSV => (TID ^ SID) >> 4; - public bool IsUntraded => string.IsNullOrWhiteSpace(HT_Name); public bool IsUntradedEvent6 => Geo1_Country == 0 && Geo1_Region == 0 && Met_Location / 10000 == 4 && Gen6; // Complex Generated Attributes @@ -632,11 +631,11 @@ public void TradeFriendshipAffection(string SAV_TRAINER) } // Legality Properties - public bool WasLink => Met_Location == 30011; - public bool WasEgg => Legal.EggLocations.Contains(Egg_Location); - public bool WasEvent => Met_Location > 40000 && Met_Location < 50000 || FatefulEncounter && Species != 386; - public bool WasEventEgg => ((Egg_Location > 40000 && Egg_Location < 50000) || (FatefulEncounter && Egg_Location == 30002)) && Met_Level == 1; - public bool WasTradedEgg => Egg_Location == 30002; - public bool WasIngameTrade => Met_Location == 30001; + public override bool WasLink => Met_Location == 30011; + public override bool WasEgg => Legal.EggLocations.Contains(Egg_Location); + public override bool WasEvent => Met_Location > 40000 && Met_Location < 50000 || FatefulEncounter && Species != 386; + public override bool WasEventEgg => ((Egg_Location > 40000 && Egg_Location < 50000) || (FatefulEncounter && Egg_Location == 30002)) && Met_Level == 1; + public override bool WasTradedEgg => Egg_Location == 30002; + public override bool WasIngameTrade => Met_Location == 30001; } } diff --git a/PKHeX/PKM/PKM.cs b/PKHeX/PKM/PKM.cs index 8efc6208d..27c45e01e 100644 --- a/PKHeX/PKM/PKM.cs +++ b/PKHeX/PKM/PKM.cs @@ -152,6 +152,10 @@ public byte[] Write() public virtual int Geo5_Country { get; set; } public virtual byte Enjoyment { get; set; } public virtual byte Fullness { get; set; } + public virtual int AbilityNumber { get; set; } + public virtual int Country { get; set; } + public virtual int Region { get; set; } + public virtual int ConsoleRegion { get; set; } /// /// The date the Pokémon was met. @@ -377,6 +381,17 @@ public virtual int HPType } } + // Legality Extensions + public virtual bool WasLink => false; + public virtual bool WasEgg => Egg_Location > 0; + public virtual bool WasEvent => Met_Location > 40000 && Met_Location < 50000 || FatefulEncounter; + public virtual bool WasEventEgg => ((Egg_Location > 40000 && Egg_Location < 50000) || (FatefulEncounter && Egg_Location > 0)) && Met_Level == 1; + public virtual bool WasTradedEgg => Egg_Location == 30002; + public virtual bool WasIngameTrade => Met_Location == 30001; + public virtual bool IsUntraded => string.IsNullOrWhiteSpace(HT_Name); + public virtual bool SecretSuperTrainingUnlocked { get { return false; } set { } } + public virtual bool SecretSuperTrainingComplete { get { return false; } set { } } + // Methods public abstract bool getGenderIsValid(); public void RefreshChecksum() { Checksum = CalculateChecksum(); } diff --git a/PKHeX/PKM/XK3.cs b/PKHeX/PKM/XK3.cs index e11f1c881..bd2870b25 100644 --- a/PKHeX/PKM/XK3.cs +++ b/PKHeX/PKM/XK3.cs @@ -66,7 +66,7 @@ public XK3(byte[] decryptedData = null, string ident = null) public bool UnusedFlag3 { get { return (XDPKMFLAGS & (1 << 3)) == 1 << 3; } set { XDPKMFLAGS = XDPKMFLAGS & ~(1 << 3) | (value ? 1 << 3 : 0); } } public bool BlockTrades { get { return (XDPKMFLAGS & (1 << 4)) == 1 << 4; } set { XDPKMFLAGS = XDPKMFLAGS & ~(1 << 4) | (value ? 1 << 4 : 0); } } public override bool Valid { get { return (XDPKMFLAGS & (1 << 5)) == 0; } set { XDPKMFLAGS = XDPKMFLAGS & ~(1 << 5) | (value ? 0 : 1 << 5); } } // invalid flag - public int AbilityNumber { get { return (XDPKMFLAGS >> 6) & 1; } set { XDPKMFLAGS = XDPKMFLAGS & ~(1 << 6) | (value << 6); } } + public override int AbilityNumber { get { return (XDPKMFLAGS >> 6) & 1; } set { XDPKMFLAGS = XDPKMFLAGS & ~(1 << 6) | (value << 6); } } public override bool IsEgg { get { return (XDPKMFLAGS & (1 << 7)) == 1 << 7; } set { XDPKMFLAGS = XDPKMFLAGS & ~(1 << 7) | (value ? 1 << 7 : 0); } } // 0x1E-0x1F Unknown public override uint EXP { get { return BigEndian.ToUInt32(Data, 0x20); } set { BigEndian.GetBytes(value).CopyTo(Data, 0x20); } } diff --git a/PKHeX/Util/ReflectUtil.cs b/PKHeX/Util/ReflectUtil.cs index 90ebe2642..7966ea16e 100644 --- a/PKHeX/Util/ReflectUtil.cs +++ b/PKHeX/Util/ReflectUtil.cs @@ -57,5 +57,9 @@ private static object ConvertValue(object value, Type type) // Convert.ChangeType is suitable for most things return Convert.ChangeType(value, type); } + internal static bool? getBooleanState(object obj, string prop) + { + return obj.GetType().HasProperty(prop) ? GetValue(obj, prop) as bool? : null; + } } }