2016-03-12 17:16:41 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
2016-03-11 04:36:32 +00:00
|
|
|
|
|
|
|
|
|
namespace PKHeX
|
|
|
|
|
{
|
|
|
|
|
public enum Severity
|
|
|
|
|
{
|
|
|
|
|
Indeterminate = -2,
|
|
|
|
|
Invalid = -1,
|
|
|
|
|
Fishy = 0,
|
|
|
|
|
Valid = 1,
|
|
|
|
|
NotImplemented = 2,
|
|
|
|
|
}
|
|
|
|
|
public class LegalityCheck
|
|
|
|
|
{
|
2016-03-12 03:43:40 +00:00
|
|
|
|
public Severity Judgement = Severity.Valid;
|
2016-03-18 01:28:51 +00:00
|
|
|
|
public readonly string Comment = "Valid";
|
2016-03-11 04:36:32 +00:00
|
|
|
|
public bool Valid => Judgement >= Severity.Fishy;
|
|
|
|
|
|
|
|
|
|
public LegalityCheck() { }
|
|
|
|
|
public LegalityCheck(Severity s, string c)
|
|
|
|
|
{
|
|
|
|
|
Judgement = s;
|
|
|
|
|
Comment = c;
|
|
|
|
|
}
|
2016-03-14 03:19:04 +00:00
|
|
|
|
}
|
|
|
|
|
public partial class LegalityAnalysis
|
|
|
|
|
{
|
|
|
|
|
private LegalityCheck verifyECPID()
|
2016-03-11 04:36:32 +00:00
|
|
|
|
{
|
|
|
|
|
// Secondary Checks
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (pk6.EncryptionConstant == 0)
|
2016-03-11 04:36:32 +00:00
|
|
|
|
return new LegalityCheck(Severity.Fishy, "Encryption Constant is not set.");
|
|
|
|
|
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (pk6.PID == 0)
|
2016-03-11 04:36:32 +00:00
|
|
|
|
return new LegalityCheck(Severity.Fishy, "PID is not set.");
|
|
|
|
|
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (pk6.Gen6)
|
2016-03-11 04:36:32 +00:00
|
|
|
|
return new LegalityCheck();
|
|
|
|
|
|
|
|
|
|
// 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.
|
2016-03-14 03:19:04 +00:00
|
|
|
|
bool xorPID = ((pk6.TID ^ pk6.SID ^ (int)(pk6.PID & 0xFFFF) ^ (int)(pk6.PID >> 16)) & 0x7) == 8;
|
2016-03-11 04:36:32 +00:00
|
|
|
|
bool valid = xorPID
|
2016-03-14 03:19:04 +00:00
|
|
|
|
? pk6.EncryptionConstant == (pk6.PID ^ 0x8000000)
|
|
|
|
|
: pk6.EncryptionConstant == pk6.PID;
|
2016-03-11 04:36:32 +00:00
|
|
|
|
|
|
|
|
|
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 new LegalityCheck();
|
|
|
|
|
}
|
2016-03-14 03:19:04 +00:00
|
|
|
|
private LegalityCheck verifyNickname()
|
2016-03-11 04:36:32 +00:00
|
|
|
|
{
|
|
|
|
|
// If the Pokémon is not nicknamed, it should match one of the language strings.
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (pk6.Nickname.Length == 0)
|
2016-03-16 01:56:18 +00:00
|
|
|
|
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.");
|
2016-03-20 01:06:02 +00:00
|
|
|
|
if (!Encounter.Valid)
|
|
|
|
|
return new LegalityCheck(Severity.Valid, "Skipped Nickname check due to other check being invalid.");
|
|
|
|
|
|
2016-03-16 04:15:40 +00:00
|
|
|
|
if (pk6.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.");
|
|
|
|
|
}
|
2016-03-16 04:40:11 +00:00
|
|
|
|
string nickname = pk6.Nickname.Replace("'", "’");
|
2016-03-16 01:56:18 +00:00
|
|
|
|
if (pk6.IsNicknamed)
|
2016-03-11 04:36:32 +00:00
|
|
|
|
{
|
2016-03-20 01:06:02 +00:00
|
|
|
|
for (int i = 0; i < PKX.SpeciesLang.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
string[] lang = PKX.SpeciesLang[i];
|
|
|
|
|
int index = Array.IndexOf(lang, nickname);
|
|
|
|
|
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.");
|
|
|
|
|
}
|
|
|
|
|
return new LegalityCheck(Severity.Valid, "Nickname does not match another species name.");
|
2016-03-16 01:56:18 +00:00
|
|
|
|
}
|
|
|
|
|
// else
|
|
|
|
|
{
|
|
|
|
|
// Can't have another language name if it hasn't evolved.
|
2016-03-16 04:40:11 +00:00
|
|
|
|
return Legal.getHasEvolved(pk6) && PKX.SpeciesLang.Any(lang => lang[pk6.Species] == nickname)
|
|
|
|
|
|| PKX.SpeciesLang[pk6.Language][pk6.Species] == nickname
|
2016-03-16 02:06:13 +00:00
|
|
|
|
? new LegalityCheck(Severity.Valid, "Nickname matches species name.")
|
|
|
|
|
: new LegalityCheck(Severity.Invalid, "Nickname does not match species name.");
|
2016-03-11 04:36:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-14 03:19:04 +00:00
|
|
|
|
private LegalityCheck verifyEVs()
|
2016-03-11 04:36:32 +00:00
|
|
|
|
{
|
2016-03-14 03:19:04 +00:00
|
|
|
|
var evs = pk6.EVs;
|
|
|
|
|
int sum = evs.Sum();
|
|
|
|
|
if (sum == 0 && pk6.Met_Level != pk6.Stat_Level && pk6.Stat_Level > 1)
|
2016-03-11 04:36:32 +00:00
|
|
|
|
return new LegalityCheck(Severity.Fishy, "All EVs are zero, but leveled above Met Level");
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (sum == 508)
|
2016-03-11 04:36:32 +00:00
|
|
|
|
return new LegalityCheck(Severity.Fishy, "2 EVs remaining.");
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (sum > 510)
|
2016-03-11 04:36:32 +00:00
|
|
|
|
return new LegalityCheck(Severity.Invalid, "EV total cannot be above 510.");
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (evs.Any(ev => ev > 252))
|
2016-03-11 04:36:32 +00:00
|
|
|
|
return new LegalityCheck(Severity.Invalid, "EVs cannot go above 252.");
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (evs.All(ev => pk6.EVs[0] == ev) && evs[0] != 0)
|
2016-03-11 04:36:32 +00:00
|
|
|
|
return new LegalityCheck(Severity.Fishy, "EVs are all equal.");
|
|
|
|
|
|
|
|
|
|
return new LegalityCheck();
|
|
|
|
|
}
|
2016-03-14 03:19:04 +00:00
|
|
|
|
private LegalityCheck verifyIVs()
|
2016-03-11 04:36:32 +00:00
|
|
|
|
{
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (pk6.IVs.Sum() == 0)
|
2016-03-11 04:36:32 +00:00
|
|
|
|
return new LegalityCheck(Severity.Fishy, "All IVs are zero.");
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (pk6.IVs[0] < 30 && pk6.IVs.All(iv => pk6.IVs[0] == iv))
|
2016-03-11 04:36:32 +00:00
|
|
|
|
return new LegalityCheck(Severity.Fishy, "All IVs are equal.");
|
|
|
|
|
return new LegalityCheck();
|
|
|
|
|
}
|
2016-03-14 03:19:04 +00:00
|
|
|
|
private LegalityCheck verifyID()
|
2016-03-11 04:36:32 +00:00
|
|
|
|
{
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (pk6.TID == 0 && pk6.SID == 0)
|
2016-03-11 04:36:32 +00:00
|
|
|
|
return new LegalityCheck(Severity.Fishy, "TID and SID are zero.");
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (pk6.TID == 0)
|
2016-03-11 04:36:32 +00:00
|
|
|
|
return new LegalityCheck(Severity.Fishy, "TID is zero.");
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (pk6.SID == 0)
|
2016-03-11 04:36:32 +00:00
|
|
|
|
return new LegalityCheck(Severity.Fishy, "SID is zero.");
|
|
|
|
|
return new LegalityCheck();
|
|
|
|
|
}
|
2016-03-14 03:19:04 +00:00
|
|
|
|
private LegalityCheck verifyEncounter()
|
2016-03-12 03:43:40 +00:00
|
|
|
|
{
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (!pk6.Gen6)
|
2016-03-12 03:43:40 +00:00
|
|
|
|
return new LegalityCheck {Judgement = Severity.NotImplemented};
|
|
|
|
|
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (pk6.WasLink)
|
2016-03-12 03:43:40 +00:00
|
|
|
|
{
|
2016-03-16 05:04:04 +00:00
|
|
|
|
// Should NOT be Fateful, and should be in Database
|
2016-03-23 02:47:13 +00:00
|
|
|
|
return pk6.FatefulEncounter || EncounterMatch == null
|
2016-03-16 05:04:04 +00:00
|
|
|
|
? new LegalityCheck(Severity.Invalid, "Not a valid Link gift.")
|
|
|
|
|
: new LegalityCheck(Severity.Valid, "Valid Link gift.");
|
2016-03-12 03:43:40 +00:00
|
|
|
|
}
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (pk6.WasEvent || pk6.WasEventEgg)
|
2016-03-12 03:43:40 +00:00
|
|
|
|
{
|
2016-03-14 03:19:04 +00:00
|
|
|
|
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.");
|
2016-03-12 03:43:40 +00:00
|
|
|
|
}
|
2016-03-23 02:47:13 +00:00
|
|
|
|
|
|
|
|
|
EncounterMatch = null; // Reset object
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (pk6.WasEgg)
|
2016-03-12 03:43:40 +00:00
|
|
|
|
{
|
|
|
|
|
// Check Hatch Locations
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (pk6.Met_Level != 1)
|
2016-03-12 17:16:41 +00:00
|
|
|
|
return new LegalityCheck(Severity.Invalid, "Invalid met level, expected 1.");
|
2016-03-14 03:19:04 +00:00
|
|
|
|
if (pk6.IsEgg)
|
2016-03-12 17:16:41 +00:00
|
|
|
|
{
|
2016-03-16 01:56:18 +00:00
|
|
|
|
return pk6.Met_Location == 0
|
2016-03-12 17:16:41 +00:00
|
|
|
|
? new LegalityCheck(Severity.Valid, "Valid un-hatched egg.")
|
|
|
|
|
: new LegalityCheck(Severity.Invalid, "Invalid location for un-hatched egg (expected ID:0)");
|
|
|
|
|
}
|
2016-03-16 02:06:13 +00:00
|
|
|
|
if (pk6.XY)
|
2016-03-12 03:43:40 +00:00
|
|
|
|
{
|
2016-03-16 01:56:18 +00:00
|
|
|
|
return Legal.ValidMet_XY.Contains(pk6.Met_Location)
|
2016-03-12 17:16:41 +00:00
|
|
|
|
? new LegalityCheck(Severity.Valid, "Valid X/Y hatched egg.")
|
|
|
|
|
: new LegalityCheck(Severity.Invalid, "Invalid X/Y location for hatched egg.");
|
2016-03-12 03:43:40 +00:00
|
|
|
|
}
|
2016-03-16 02:06:13 +00:00
|
|
|
|
if (pk6.AO)
|
2016-03-12 03:43:40 +00:00
|
|
|
|
{
|
2016-03-16 01:56:18 +00:00
|
|
|
|
return Legal.ValidMet_AO.Contains(pk6.Met_Location)
|
2016-03-12 17:16:41 +00:00
|
|
|
|
? new LegalityCheck(Severity.Valid, "Valid OR/AS hatched egg.")
|
|
|
|
|
: new LegalityCheck(Severity.Invalid, "Invalid OR/AS location for hatched egg.");
|
2016-03-12 03:43:40 +00:00
|
|
|
|
}
|
|
|
|
|
return new LegalityCheck(Severity.Invalid, "Invalid location for hatched egg.");
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-23 02:47:13 +00:00
|
|
|
|
EncounterMatch = Legal.getValidStaticEncounter(pk6);
|
|
|
|
|
if (EncounterMatch != null)
|
2016-03-13 21:56:23 +00:00
|
|
|
|
return new LegalityCheck(Severity.Valid, "Valid gift/static encounter.");
|
|
|
|
|
|
2016-03-17 02:15:49 +00:00
|
|
|
|
if (Legal.getIsFossil(pk6))
|
|
|
|
|
{
|
|
|
|
|
return pk6.AbilityNumber != 4
|
|
|
|
|
? new LegalityCheck(Severity.Valid, "Valid revived fossil.")
|
|
|
|
|
: new LegalityCheck(Severity.Invalid, "Hidden ability on revived fossil.");
|
|
|
|
|
}
|
2016-03-23 05:31:30 +00:00
|
|
|
|
EncounterMatch = Legal.getValidFriendSafari(pk6);
|
|
|
|
|
if (EncounterMatch != null)
|
2016-03-16 01:35:40 +00:00
|
|
|
|
{
|
|
|
|
|
if (pk6.Species == 670 || pk6.Species == 671) // Floette
|
|
|
|
|
if (pk6.AltForm % 2 != 0) // 0/2/4
|
|
|
|
|
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.");
|
|
|
|
|
|
2016-03-13 21:56:23 +00:00
|
|
|
|
return new LegalityCheck(Severity.Valid, "Valid friend safari encounter.");
|
2016-03-16 01:35:40 +00:00
|
|
|
|
}
|
2016-03-23 02:47:13 +00:00
|
|
|
|
|
|
|
|
|
EncounterMatch = Legal.getValidWildEncounters(pk6);
|
|
|
|
|
if (EncounterMatch != null)
|
2016-03-13 23:42:03 +00:00
|
|
|
|
{
|
2016-03-23 02:47:13 +00:00
|
|
|
|
return ((EncounterSlot[]) EncounterMatch).Any(slot => !slot.DexNav)
|
|
|
|
|
? new LegalityCheck(Severity.Valid, "Valid encounter at location.")
|
|
|
|
|
: new LegalityCheck(Severity.Valid, "Valid DexNav encounter at location.");
|
2016-03-13 23:42:03 +00:00
|
|
|
|
}
|
2016-03-23 02:47:13 +00:00
|
|
|
|
EncounterMatch = Legal.getValidIngameTrade(pk6);
|
|
|
|
|
if (EncounterMatch != null)
|
2016-03-18 00:25:21 +00:00
|
|
|
|
{
|
|
|
|
|
return new LegalityCheck(Severity.Valid, "Valid ingame trade.");
|
|
|
|
|
}
|
2016-03-12 03:43:40 +00:00
|
|
|
|
return new LegalityCheck(Severity.Invalid, "Not a valid encounter.");
|
|
|
|
|
}
|
2016-03-19 05:49:21 +00:00
|
|
|
|
private LegalityCheck verifyLevel()
|
|
|
|
|
{
|
2016-03-22 04:31:06 +00:00
|
|
|
|
if (MatchedWC6 != null && MatchedWC6.Level != pk6.Met_Level)
|
|
|
|
|
return new LegalityCheck(Severity.Invalid, "Met Level does not match Wonder Card level.");
|
2016-03-19 05:49:21 +00:00
|
|
|
|
return pk6.CurrentLevel < pk6.Met_Level
|
|
|
|
|
? new LegalityCheck(Severity.Invalid, "Current level is below met level.")
|
|
|
|
|
: new LegalityCheck(Severity.Valid, "Current level is not below met level.");
|
|
|
|
|
}
|
2016-03-20 22:20:11 +00:00
|
|
|
|
private LegalityCheck verifyRibbons()
|
|
|
|
|
{
|
|
|
|
|
if (!Encounter.Valid)
|
|
|
|
|
return new LegalityCheck(Severity.Valid, "Skipped Ribbon check due to other check being invalid.");
|
|
|
|
|
|
|
|
|
|
List<string> missingRibbons = new List<string>();
|
|
|
|
|
List<string> invalidRibbons = new List<string>();
|
|
|
|
|
|
|
|
|
|
// Check Event Ribbons
|
|
|
|
|
bool[] EventRib =
|
|
|
|
|
{
|
|
|
|
|
pk6.RIB2_6, pk6.RIB2_7, pk6.RIB3_0, pk6.RIB3_1, pk6.RIB3_2,
|
|
|
|
|
pk6.RIB3_3, pk6.RIB3_4, pk6.RIB3_5, pk6.RIB3_6, pk6.RIB3_7,
|
|
|
|
|
pk6.RIB4_0, pk6.RIB4_1, pk6.RIB4_2, pk6.RIB4_3, pk6.RIB4_4
|
|
|
|
|
};
|
2016-03-21 15:01:08 +00:00
|
|
|
|
if (MatchedWC6 != null) // Wonder Card
|
2016-03-20 22:20:11 +00:00
|
|
|
|
{
|
|
|
|
|
bool[] wc6rib =
|
|
|
|
|
{
|
|
|
|
|
MatchedWC6.RIB0_3, MatchedWC6.RIB0_4, MatchedWC6.RIB0_5, MatchedWC6.RIB0_6, MatchedWC6.RIB1_5,
|
|
|
|
|
MatchedWC6.RIB1_6, MatchedWC6.RIB0_7, MatchedWC6.RIB1_1, MatchedWC6.RIB1_2, MatchedWC6.RIB1_3,
|
|
|
|
|
MatchedWC6.RIB1_4, MatchedWC6.RIB0_0, MatchedWC6.RIB0_1, MatchedWC6.RIB0_2, MatchedWC6.RIB1_0
|
|
|
|
|
};
|
|
|
|
|
for (int i = 0; i < EventRib.Length; i++)
|
|
|
|
|
if (EventRib[i] ^ wc6rib[i]) // Mismatch
|
|
|
|
|
(wc6rib[i] ? missingRibbons : invalidRibbons).Add(EventRibName[i]);
|
|
|
|
|
}
|
2016-03-23 02:47:13 +00:00
|
|
|
|
else if (EncounterMatch?.GetType() == 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 (EventRib[4] ^ ((EncounterLink)EncounterMatch).Classic)
|
|
|
|
|
(EventRib[4] ? invalidRibbons : missingRibbons).Add(EventRibName[4]);
|
|
|
|
|
}
|
|
|
|
|
else // No ribbons
|
2016-03-20 22:20:11 +00:00
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < EventRib.Length; i++)
|
2016-03-23 02:47:13 +00:00
|
|
|
|
if (EventRib[i])
|
2016-03-20 22:20:11 +00:00
|
|
|
|
invalidRibbons.Add(EventRibName[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unobtainable ribbons for Gen6 Origin
|
|
|
|
|
if (pk6.RIB0_1)
|
|
|
|
|
invalidRibbons.Add("GBA Champion"); // RSE HoF
|
|
|
|
|
if (pk6.RIB0_2)
|
|
|
|
|
invalidRibbons.Add("Sinnoh Champ"); // DPPt HoF
|
2016-03-22 06:43:03 +00:00
|
|
|
|
if (pk6.RIB2_2)
|
2016-03-20 22:20:11 +00:00
|
|
|
|
invalidRibbons.Add("Artist"); // RSE Master Rank Portrait
|
2016-03-22 06:43:03 +00:00
|
|
|
|
if (pk6.RIB2_4)
|
2016-03-20 22:20:11 +00:00
|
|
|
|
invalidRibbons.Add("Record"); // Unobtainable
|
2016-03-22 06:43:03 +00:00
|
|
|
|
if (pk6.RIB2_5)
|
2016-03-20 22:20:11 +00:00
|
|
|
|
invalidRibbons.Add("Legend"); // HGSS Defeat Red @ Mt.Silver
|
|
|
|
|
if (pk6.Memory_ContestCount > 0)
|
|
|
|
|
invalidRibbons.Add("Contest Memory"); // Gen3/4 Contest
|
|
|
|
|
if (pk6.Memory_BattleCount > 0)
|
|
|
|
|
invalidRibbons.Add("Battle Memory"); // Gen3/4 Battle
|
|
|
|
|
|
|
|
|
|
if (missingRibbons.Count + invalidRibbons.Count == 0)
|
|
|
|
|
return new LegalityCheck(Severity.Valid, "All ribbons accounted for.");
|
|
|
|
|
|
|
|
|
|
string[] result = new string[2];
|
|
|
|
|
if (missingRibbons.Count > 0)
|
|
|
|
|
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))));
|
|
|
|
|
}
|
2016-03-21 15:01:08 +00:00
|
|
|
|
private LegalityCheck verifyAbility()
|
|
|
|
|
{
|
|
|
|
|
int index = Legal.PersonalAO[pk6.Species].FormeIndex(pk6.Species, pk6.AltForm);
|
|
|
|
|
byte[] abilities = Legal.PersonalAO[index].Abilities;
|
|
|
|
|
int abilval = Array.IndexOf(abilities, (byte)pk6.Ability);
|
|
|
|
|
if (abilval < 0)
|
2016-03-23 02:47:13 +00:00
|
|
|
|
return new LegalityCheck(Severity.Invalid, "Ability is not valid for species/form.");
|
2016-03-21 15:01:08 +00:00
|
|
|
|
|
2016-03-23 02:47:13 +00:00
|
|
|
|
if (EncounterMatch != null)
|
|
|
|
|
{
|
|
|
|
|
Type etype = EncounterMatch.GetType();
|
|
|
|
|
if (etype == typeof(EncounterStatic))
|
|
|
|
|
if (pk6.AbilityNumber == 4 ^ ((EncounterStatic)EncounterMatch).Ability == 4)
|
|
|
|
|
return new LegalityCheck(Severity.Invalid, "Hidden Ability mismatch for static encounter.");
|
|
|
|
|
if (etype == typeof(EncounterTrade))
|
|
|
|
|
if (pk6.AbilityNumber == 4 ^ ((EncounterTrade)EncounterMatch).Ability == 4)
|
|
|
|
|
return new LegalityCheck(Severity.Invalid, "Hidden Ability mismatch for ingame trade.");
|
2016-03-23 05:31:30 +00:00
|
|
|
|
if (etype == typeof (EncounterSlot[]) && pk6.AbilityNumber == 4)
|
|
|
|
|
if (((EncounterSlot[]) EncounterMatch).All(slot => slot.Type != SlotType.FriendSafari) &&
|
|
|
|
|
((EncounterSlot[]) EncounterMatch).All(slot => slot.Type != SlotType.Horde))
|
|
|
|
|
return new LegalityCheck(Severity.Invalid, "Hidden Ability on non-horde/friend safari wild encounter.");
|
2016-03-23 02:47:13 +00:00
|
|
|
|
}
|
2016-03-22 04:50:39 +00:00
|
|
|
|
|
2016-03-21 15:01:08 +00:00
|
|
|
|
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.");
|
|
|
|
|
}
|
|
|
|
|
private LegalityCheck verifyBall()
|
|
|
|
|
{
|
2016-03-23 02:47:13 +00:00
|
|
|
|
if (!Encounter.Valid)
|
|
|
|
|
return new LegalityCheck(Severity.Valid, "Skipped Ball check due to other check being invalid.");
|
|
|
|
|
|
2016-03-21 15:01:08 +00:00
|
|
|
|
if (MatchedWC6 != null)
|
|
|
|
|
return pk6.Ball != MatchedWC6.Pokéball
|
|
|
|
|
? new LegalityCheck(Severity.Invalid, "Ball does not match specified Wonder Card Ball.")
|
|
|
|
|
: new LegalityCheck(Severity.Valid, "Ball matches Wonder Card.");
|
|
|
|
|
|
|
|
|
|
if (pk6.WasEgg)
|
|
|
|
|
{
|
2016-03-23 02:47:13 +00:00
|
|
|
|
if (pk6.Species > 650 && pk6.Species != 700) // Sylveon
|
2016-03-21 15:01:08 +00:00
|
|
|
|
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 (pk6.Ball == 0x10) // Cherish
|
|
|
|
|
return new LegalityCheck(Severity.Invalid, "Cherish Ball on non-event.");
|
|
|
|
|
if (pk6.Ball == 5 && pk6.Species > 493) // Gen5
|
|
|
|
|
return new LegalityCheck(Severity.Invalid, "Safari Ball on GenV species.");
|
|
|
|
|
|
|
|
|
|
// Feel free to improve, there's a lot of very minor things to check for some species.
|
2016-03-22 04:50:39 +00:00
|
|
|
|
return new LegalityCheck(Severity.Valid, "Obtainable ball for past gen origin parent.");
|
2016-03-21 15:01:08 +00:00
|
|
|
|
}
|
2016-03-22 04:31:06 +00:00
|
|
|
|
if (pk6.WasLink)
|
|
|
|
|
{
|
|
|
|
|
if (pk6.Species == 251)
|
|
|
|
|
return pk6.Ball != 11 // Luxury
|
|
|
|
|
? new LegalityCheck(Severity.Invalid, "Incorrect ball on Link gift.")
|
|
|
|
|
: new LegalityCheck(Severity.Valid, "Correct ball on Link gift.");
|
|
|
|
|
|
|
|
|
|
return pk6.Ball != 4 // Pokeball
|
|
|
|
|
? new LegalityCheck(Severity.Invalid, "Incorrect ball on Link gift.")
|
|
|
|
|
: new LegalityCheck(Severity.Valid, "Correct ball on Link gift.");
|
|
|
|
|
}
|
2016-03-23 02:47:13 +00:00
|
|
|
|
Type etype = EncounterMatch?.GetType();
|
|
|
|
|
if (etype == 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 (etype == typeof(EncounterTrade))
|
2016-03-21 15:01:08 +00:00
|
|
|
|
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.");
|
|
|
|
|
|
2016-03-23 02:47:13 +00:00
|
|
|
|
return !Legal.WildPokeballs.Contains(pk6.Ball)
|
|
|
|
|
? new LegalityCheck(Severity.Invalid, "Unobtainable ball on captured encounter.")
|
|
|
|
|
: new LegalityCheck(Severity.Valid, "Obtainable ball on captured encounter.");
|
2016-03-21 15:01:08 +00:00
|
|
|
|
}
|
2016-03-22 04:31:06 +00:00
|
|
|
|
private LegalityCheck verifyHandlerMemories()
|
|
|
|
|
{
|
2016-03-23 02:47:13 +00:00
|
|
|
|
if (!Encounter.Valid)
|
|
|
|
|
return new LegalityCheck(Severity.Valid, "Skipped Memory check due to other check being invalid.");
|
|
|
|
|
|
2016-03-22 04:31:06 +00:00
|
|
|
|
if (MatchedWC6?.OT.Length > 0) // Has Event OT -- null propagation yields false if MatchedWC6=null
|
|
|
|
|
{
|
|
|
|
|
if (pk6.OT_Friendship != PKX.getBaseFriendship(pk6.Species))
|
|
|
|
|
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 (!pk6.WasEvent && (pk6.HT_Name.Length == 0 || pk6.Geo1_Country == 0)) // Is not Traded
|
|
|
|
|
{
|
|
|
|
|
if (pk6.HT_Name.Length != 0)
|
|
|
|
|
return new LegalityCheck(Severity.Invalid, "GeoLocation -- HT Name present but has no previous Country.");
|
|
|
|
|
if (pk6.Geo1_Country != 0)
|
|
|
|
|
return new LegalityCheck(Severity.Invalid, "GeoLocation -- 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.");
|
|
|
|
|
|
|
|
|
|
// We know it is untraded (HT is empty), if it must be trade evolved flag it.
|
|
|
|
|
if (Legal.getHasTradeEvolved(pk6))
|
|
|
|
|
{
|
|
|
|
|
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.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else // Is Traded
|
|
|
|
|
{
|
|
|
|
|
if (pk6.HT_Memory == 0)
|
|
|
|
|
return new LegalityCheck(Severity.Invalid, "Memory -- missing Handling Trainer Memory.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unimplemented: Ingame Trade Memories
|
|
|
|
|
|
|
|
|
|
return new LegalityCheck(Severity.Valid, "History is valid.");
|
|
|
|
|
}
|
2016-03-14 03:19:04 +00:00
|
|
|
|
private LegalityCheck[] verifyMoves()
|
2016-03-12 17:16:41 +00:00
|
|
|
|
{
|
|
|
|
|
int[] Moves = pk6.Moves;
|
|
|
|
|
LegalityCheck[] res = new LegalityCheck[4];
|
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
|
res[i] = new LegalityCheck();
|
|
|
|
|
if (!pk6.Gen6)
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
var validMoves = Legal.getValidMoves(pk6).ToArray();
|
|
|
|
|
if (pk6.Species == 235)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
|
res[i] = Legal.InvalidSketch.Contains(Moves[i])
|
|
|
|
|
? new LegalityCheck(Severity.Invalid, "Invalid Sketch move.")
|
|
|
|
|
: new LegalityCheck();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
int[] RelearnMoves = pk6.RelearnMoves;
|
2016-03-14 03:19:04 +00:00
|
|
|
|
int[] WC6Moves = MatchedWC6?.Moves ?? new int[0];
|
2016-03-12 17:16:41 +00:00
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
|
{
|
|
|
|
|
if (Moves[i] == Legal.Struggle)
|
|
|
|
|
res[i] = new LegalityCheck(Severity.Invalid, "Invalid Move: Struggle.");
|
|
|
|
|
else if (validMoves.Contains(Moves[i]))
|
|
|
|
|
res[i] = new LegalityCheck(Severity.Valid, "Level-up.");
|
|
|
|
|
else if (RelearnMoves.Contains(Moves[i]))
|
|
|
|
|
res[i] = new LegalityCheck(Severity.Valid, "Relearn Move.");
|
2016-03-14 03:19:04 +00:00
|
|
|
|
else if (WC6Moves.Contains(Moves[i]))
|
2016-03-21 15:01:08 +00:00
|
|
|
|
res[i] = new LegalityCheck(Severity.Valid, "Wonder Card Non-Relearn Move.");
|
2016-03-12 17:16:41 +00:00
|
|
|
|
else
|
|
|
|
|
res[i] = new LegalityCheck(Severity.Invalid, "Invalid Move.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (Moves[0] == 0)
|
|
|
|
|
res[0] = new LegalityCheck(Severity.Invalid, "Invalid Move.");
|
|
|
|
|
|
2016-03-15 06:54:13 +00:00
|
|
|
|
|
|
|
|
|
if (pk6.Species == 647) // Keldeo
|
2016-03-19 14:53:55 +00:00
|
|
|
|
if (pk6.AltForm == 1 ^ pk6.Moves.Contains(548))
|
|
|
|
|
res[0] = new LegalityCheck(Severity.Invalid, "Secret Sword / Resolute Keldeo Mismatch.");
|
2016-03-15 06:54:13 +00:00
|
|
|
|
|
2016-03-15 06:26:51 +00:00
|
|
|
|
// 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.");
|
|
|
|
|
|
2016-03-12 17:16:41 +00:00
|
|
|
|
return res;
|
|
|
|
|
}
|
2016-03-14 03:19:04 +00:00
|
|
|
|
private LegalityCheck[] verifyRelearn()
|
2016-03-12 17:16:41 +00:00
|
|
|
|
{
|
|
|
|
|
LegalityCheck[] res = new LegalityCheck[4];
|
2016-03-14 03:19:04 +00:00
|
|
|
|
MatchedWC6 = null; // Reset
|
2016-03-23 02:47:13 +00:00
|
|
|
|
EncounterMatch = null;
|
|
|
|
|
|
2016-03-12 17:16:41 +00:00
|
|
|
|
int[] Moves = pk6.RelearnMoves;
|
|
|
|
|
if (!pk6.Gen6)
|
|
|
|
|
goto noRelearn;
|
|
|
|
|
if (pk6.WasLink)
|
|
|
|
|
{
|
2016-03-23 02:47:13 +00:00
|
|
|
|
EncounterMatch = Legal.getValidLinkGifts(pk6);
|
|
|
|
|
if (EncounterMatch == null)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
|
res[i] = new LegalityCheck();
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int[] moves = ((EncounterLink)EncounterMatch).RelearnMoves;
|
2016-03-12 17:16:41 +00:00
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
|
res[i] = moves[i] != Moves[i]
|
|
|
|
|
? new LegalityCheck(Severity.Invalid, $"Expected ID:{moves[i]}.")
|
|
|
|
|
: new LegalityCheck();
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
if (pk6.WasEvent || pk6.WasEventEgg)
|
|
|
|
|
{
|
|
|
|
|
// Get WC6's that match
|
|
|
|
|
IEnumerable<WC6> vwc6 = Legal.getValidWC6s(pk6);
|
|
|
|
|
foreach (var wc in vwc6)
|
|
|
|
|
{
|
|
|
|
|
int[] moves = wc.RelearnMoves;
|
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
|
res[i] = moves[i] != Moves[i]
|
|
|
|
|
? new LegalityCheck(Severity.Invalid, $"Expected ID:{moves[i]}.")
|
|
|
|
|
: new LegalityCheck(Severity.Valid, $"Matched WC #{wc.CardID.ToString("0000")}");
|
|
|
|
|
if (res.All(r => r.Valid))
|
2016-03-14 03:19:04 +00:00
|
|
|
|
{ MatchedWC6 = wc; return res; }
|
2016-03-12 17:16:41 +00:00
|
|
|
|
}
|
|
|
|
|
goto noRelearn; // No WC match
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pk6.WasEgg)
|
|
|
|
|
{
|
|
|
|
|
const int games = 2;
|
|
|
|
|
bool checkAllGames = pk6.WasTradedEgg;
|
|
|
|
|
bool splitBreed = Legal.SplitBreed.Contains(pk6.Species);
|
|
|
|
|
|
|
|
|
|
int iterate = (checkAllGames ? games : 1) * (splitBreed ? 2 : 1);
|
|
|
|
|
for (int i = 0; i < iterate; i++)
|
|
|
|
|
{
|
|
|
|
|
int gameSource = !checkAllGames ? -1 : i % iterate / (splitBreed ? 2 : 1);
|
|
|
|
|
int skipOption = splitBreed && iterate / 2 <= i ? 1 : 0;
|
|
|
|
|
|
2016-03-12 22:07:57 +00:00
|
|
|
|
// Obtain level1 moves
|
2016-03-12 18:35:17 +00:00
|
|
|
|
List<int> baseMoves = new List<int>(Legal.getBaseEggMoves(pk6, skipOption, gameSource));
|
|
|
|
|
int baseCt = baseMoves.Count;
|
|
|
|
|
if (baseCt > 4) baseCt = 4;
|
2016-03-12 22:07:57 +00:00
|
|
|
|
|
2016-03-12 17:16:41 +00:00
|
|
|
|
// Obtain Nonstandard moves
|
2016-03-12 22:07:57 +00:00
|
|
|
|
var relearnMoves = Legal.getValidRelearn(pk6, skipOption).ToArray();
|
|
|
|
|
var relearn = pk6.RelearnMoves.Where(move => move != 0
|
|
|
|
|
&& (!baseMoves.Contains(move) || relearnMoves.Contains(move))
|
|
|
|
|
).ToArray();
|
2016-03-12 17:16:41 +00:00
|
|
|
|
int relearnCt = relearn.Length;
|
2016-03-12 22:07:57 +00:00
|
|
|
|
|
2016-03-12 18:35:17 +00:00
|
|
|
|
// Get Move Window
|
|
|
|
|
List<int> window = new List<int>(baseMoves);
|
|
|
|
|
window.AddRange(relearn);
|
|
|
|
|
int[] moves = window.Skip(baseCt + relearnCt - 4).Take(4).ToArray();
|
2016-03-12 17:16:41 +00:00
|
|
|
|
Array.Resize(ref moves, 4);
|
|
|
|
|
|
2016-03-12 18:35:17 +00:00
|
|
|
|
int req;
|
|
|
|
|
if (relearnCt == 4)
|
|
|
|
|
req = 0;
|
|
|
|
|
else if (baseCt + relearnCt > 4)
|
|
|
|
|
req = 4 - relearnCt;
|
|
|
|
|
else
|
|
|
|
|
req = baseCt;
|
2016-03-12 17:16:41 +00:00
|
|
|
|
|
|
|
|
|
// Movepool finalized! Check validity.
|
|
|
|
|
|
|
|
|
|
int[] rl = pk6.RelearnMoves;
|
2016-03-12 18:35:17 +00:00
|
|
|
|
string em = string.Join(", ", baseMoves);
|
2016-03-12 17:16:41 +00:00
|
|
|
|
// Base Egg Move
|
|
|
|
|
for (int j = 0; j < req; j++)
|
2016-03-12 18:35:17 +00:00
|
|
|
|
res[j] = !baseMoves.Contains(rl[j])
|
|
|
|
|
? new LegalityCheck(Severity.Invalid, $"Base egg move missing; expected one of: {em}.")
|
2016-03-12 17:16:41 +00:00
|
|
|
|
: new LegalityCheck(Severity.Valid, "Base egg move.");
|
|
|
|
|
|
|
|
|
|
// Non-Base
|
2016-03-12 22:07:57 +00:00
|
|
|
|
if (Legal.LightBall.Contains(pk6.Species))
|
|
|
|
|
relearnMoves = relearnMoves.Concat(new[] { 344 }).ToArray();
|
2016-03-12 17:16:41 +00:00
|
|
|
|
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, "Relearn move.");
|
|
|
|
|
|
|
|
|
|
if (res.All(r => r.Valid))
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
if (Moves[0] != 0) // DexNav only?
|
|
|
|
|
{
|
|
|
|
|
// Check DexNav
|
|
|
|
|
if (!Legal.getDexNavValid(pk6))
|
|
|
|
|
goto noRelearn;
|
|
|
|
|
|
|
|
|
|
res[0] = !Legal.getValidRelearn(pk6, 0).Contains(Moves[0])
|
|
|
|
|
? new LegalityCheck(Severity.Invalid, "Not an expected DexNav move.")
|
|
|
|
|
: new LegalityCheck();
|
|
|
|
|
for (int i = 1; i < 4; i++)
|
|
|
|
|
res[i] = Moves[i] != 0
|
|
|
|
|
? new LegalityCheck(Severity.Invalid, "Expected no Relearn Move in slot.")
|
|
|
|
|
: new LegalityCheck();
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Should have no relearn moves.
|
|
|
|
|
noRelearn:
|
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
|
res[i] = Moves[i] != 0
|
|
|
|
|
? new LegalityCheck(Severity.Invalid, "Expected no Relearn Moves.")
|
|
|
|
|
: new LegalityCheck();
|
|
|
|
|
return res;
|
|
|
|
|
}
|
2016-03-23 02:47:13 +00:00
|
|
|
|
|
|
|
|
|
private readonly string[] EventRibName =
|
|
|
|
|
{
|
|
|
|
|
"Country", "National", "Earth", "World", "Classic",
|
|
|
|
|
"Premier", "Event", "Birthday", "Special", "Souvenir",
|
|
|
|
|
"Wishing", "Battle Champ", "Regional Champ", "National Champ", "World Champ"
|
|
|
|
|
};
|
2016-03-11 04:36:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|