#define SUPPRESS
using System;
using System.Collections.Generic;
using System.Linq;
using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core
{
///
/// Legality Check object containing the data and overview values from the parse.
///
public partial class LegalityAnalysis
{
internal readonly PKM pkm;
internal readonly PersonalInfo PersonalInfo;
private readonly List Parse = new List();
///
/// Parse result list allowing view of the legality parse.
///
public IReadOnlyList Results => Parse;
///
/// Only use this when trying to mutate the legality. Not for use when checking legality.
///
public void ResetParse() => Parse.Clear();
///
/// Matched encounter data for the .
///
public IEncounterable EncounterMatch => Info.EncounterMatch;
///
/// Original encounter data for the .
///
///
/// Generation 1/2 that are transferred forward to Generation 7 are restricted to new encounter details.
/// By retaining their original match, more information can be provided by the parse.
///
public IEncounterable EncounterOriginal => Info.EncounterOriginal;
///
/// Indicates if all checks ran to completion.
///
/// This value is false if any checks encountered an error.
public readonly bool Parsed;
///
/// Indicates if all checks returned a result.
///
public readonly bool Valid;
///
/// Contains various data reused for multiple checks.
///
public readonly LegalInfo Info;
///
/// Creates a report message with optional verbosity for in-depth analysis.
///
/// Include all details in the parse, including valid check messages.
/// Single line string
public string Report(bool verbose = false) => verbose ? GetVerboseLegalityReport() : GetLegalityReport();
private IEnumerable AllSuggestedMoves
{
get
{
if (!Parsed)
return new int[4];
return _allSuggestedMoves ??= GetSuggestedCurrentMoves(true, true, true);
}
}
private IEnumerable AllSuggestedRelearnMoves
{
get
{
if (!Parsed)
return new int[4];
return _allSuggestedRelearnMoves ??= MoveList.GetValidRelearn(pkm, Info.EncounterMatch.Species, Info.EncounterMatch.Form, (GameVersion)pkm.Version).ToArray();
}
}
private int[]? _allSuggestedMoves, _allSuggestedRelearnMoves;
public int[] AllSuggestedMovesAndRelearn() => AllSuggestedMoves.Concat(AllSuggestedRelearnMoves).ToArray();
private string EncounterName
{
get
{
var enc = EncounterOriginal;
var str = SpeciesStrings;
var name = (uint) enc.Species < str.Count ? str[enc.Species] : enc.Species.ToString();
return $"{enc.LongName} ({name})";
}
}
private string? EncounterLocation
{
get
{
var enc = EncounterOriginal as ILocation;
return enc?.GetEncounterLocation(Info.Generation, pkm.Version);
}
}
///
/// Checks the input data for legality. This is the best method for checking with context, as some games do not have all Alternate Form data available.
///
/// Input data to check
/// specific personal data
public LegalityAnalysis(PKM pk, PersonalTable table) : this(pk, table.GetFormeEntry(pk.Species, pk.AltForm)) { }
///
/// Checks the input data for legality.
///
/// Input data to check
public LegalityAnalysis(PKM pk) : this(pk, pk.PersonalInfo) { }
///
/// Checks the input data for legality.
///
/// Input data to check
/// Personal info to parse with
public LegalityAnalysis(PKM pk, PersonalInfo pi)
{
pkm = pk;
PersonalInfo = pi;
if (pkm.Format <= 2) // prior to storing GameVersion
pkm.TradebackStatus = GBRestrictions.GetTradebackStatusInitial(pkm);
#if SUPPRESS
try
#endif
{
Info = EncounterFinder.FindVerifiedEncounter(pkm);
if (!pkm.IsOriginValid)
AddLine(Severity.Invalid, LEncConditionBadSpecies, CheckIdentifier.GameOrigin);
GetParseMethod()();
if (Parse.Count == 0) // shouldn't ever happen as at least one is yielded above.
{
AddLine(Severity.Invalid, L_AError, CheckIdentifier.Misc);
return;
}
Valid = Parse.All(chk => chk.Valid)
&& Info.Moves.All(m => m.Valid)
&& Info.Relearn.All(m => m.Valid);
if (!Valid && pkm.FatefulEncounter && Info.Relearn.Any(chk => !chk.Valid) && EncounterMatch is EncounterInvalid)
AddLine(Severity.Indeterminate, LFatefulGiftMissing, CheckIdentifier.Fateful);
Parsed = true;
}
#if SUPPRESS
// We want to swallow any error from malformed input data from the user. The Valid state is all that we really need.
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception e)
#pragma warning restore CA1031 // Do not catch general exception types
{
System.Diagnostics.Debug.WriteLine(e.Message);
Info = new LegalInfo(pkm);
Valid = false;
AddLine(Severity.Invalid, L_AError, CheckIdentifier.Misc);
}
#endif
}
private Action GetParseMethod()
{
if (pkm.Format <= 2) // prior to storing GameVersion
return ParsePK1;
int gen = pkm.GenNumber;
if (gen <= 0)
gen = pkm.Format;
return gen switch
{
3 => ParsePK3,
4 => ParsePK4,
5 => ParsePK5,
6 => ParsePK6,
1 => ParsePK7,
2 => ParsePK7,
7 => ParsePK7,
8 => ParsePK8,
_ => throw new Exception()
};
}
private void ParsePK1()
{
UpdateInfo();
if (pkm.TradebackStatus == TradebackType.Any && Info.Generation != pkm.Format)
pkm.TradebackStatus = TradebackType.WasTradeback; // Example: GSC Pokemon with only possible encounters in RBY, like the legendary birds
Nickname.Verify(this);
Level.Verify(this);
Level.VerifyG1(this);
Trainer.VerifyOTG1(this);
Misc.VerifyMiscG1(this);
if (pkm.Format == 2)
Item.Verify(this);
}
private void ParsePK3()
{
UpdateInfo();
UpdateChecks();
if (pkm.Format > 3)
Transfer.VerifyTransferLegalityG3(this);
if (pkm.Version == (int)GameVersion.CXD)
CXD.Verify(this);
if (Info.EncounterMatch is WC3 z && z.NotDistributed)
AddLine(Severity.Invalid, LEncUnreleased, CheckIdentifier.Encounter);
if (pkm.Format >= 8)
Transfer.VerifyTransferLegalityG8(this);
}
private void ParsePK4()
{
UpdateInfo();
UpdateChecks();
if (pkm.Format > 4)
Transfer.VerifyTransferLegalityG4(this);
if (pkm.Format >= 8)
Transfer.VerifyTransferLegalityG8(this);
}
private void ParsePK5()
{
UpdateInfo();
UpdateChecks();
NHarmonia.Verify(this);
if (pkm.Format >= 8)
Transfer.VerifyTransferLegalityG8(this);
}
private void ParsePK6()
{
UpdateInfo();
UpdateChecks();
if (pkm.Format >= 8)
Transfer.VerifyTransferLegalityG8(this);
}
private void ParsePK7()
{
UpdateInfo();
if (pkm.VC)
UpdateVCTransferInfo();
UpdateChecks();
if (pkm.Format >= 8)
Transfer.VerifyTransferLegalityG8(this);
}
private void ParsePK8()
{
UpdateInfo();
UpdateChecks();
Transfer.VerifyTransferLegalityG8(this);
}
///
/// Adds a new Check parse value.
///
/// Check severity
/// Check comment
/// Check type
internal void AddLine(Severity s, string c, CheckIdentifier i) => AddLine(new CheckResult(s, c, i));
///
/// Adds a new Check parse value.
///
/// Check result to add.
internal void AddLine(CheckResult chk) => Parse.Add(chk);
private void UpdateVCTransferInfo()
{
var enc = (Info.EncounterOriginalGB = EncounterMatch);
if (enc is EncounterInvalid)
return;
var vc = EncounterStaticGenerator.GetVCStaticTransferEncounter(pkm, enc);
Info.EncounterMatch = vc;
foreach (var z in Transfer.VerifyVCEncounter(pkm, enc, vc, Info.Moves))
AddLine(z);
Transfer.VerifyTransferLegalityG12(this);
}
private void UpdateInfo()
{
Parse.AddRange(Info.Parse);
}
private void UpdateChecks()
{
PIDEC.Verify(this);
Nickname.Verify(this);
Language.Verify(this);
Trainer.Verify(this);
IndividualValues.Verify(this);
EffortValues.Verify(this);
Level.Verify(this);
Ribbon.Verify(this);
Ability.Verify(this);
Ball.Verify(this);
Form.Verify(this);
Misc.Verify(this);
Gender.Verify(this);
Item.Verify(this);
if (pkm.Format <= 6 && pkm.Format >= 4)
EncounterType.Verify(this); // Gen 6->7 transfer deletes encounter type data
Contest.Verify(this);
if (pkm.Format < 6)
return;
if (pkm.Format < 8)
ConsoleRegion.Verify(this); // Gen 7->8 transfer deletes geolocation tracking data
History.Verify(this);
if (pkm is ITrainerMemories)
Memory.Verify(this);
if (pkm is ISuperTrain)
Medal.Verify(this);
if (pkm.Format < 7)
return;
HyperTraining.Verify(this);
Misc.VerifyVersionEvolution(this);
if (pkm.Format < 8)
return;
Mark.Verify(this);
}
private string GetLegalityReport()
{
if (Valid)
return L_ALegal;
if (!Parsed)
return L_AnalysisUnavailable;
var lines = new List();
var vMoves = Info.Moves;
var vRelearn = Info.Relearn;
for (int i = 0; i < 4; i++)
{
if (!vMoves[i].Valid)
lines.Add(vMoves[i].Format(L_F0_M_1_2, i + 1));
}
if (pkm.Format >= 6)
{
for (int i = 0; i < 4; i++)
{
if (!vRelearn[i].Valid)
lines.Add(vRelearn[i].Format(L_F0_RM_1_2, i + 1));
}
}
// Build result string...
var outputLines = Parse.Where(chk => !chk.Valid);
lines.AddRange(outputLines.Select(chk => chk.Format(L_F0_1)));
return string.Join(Environment.NewLine, lines);
}
private string GetVerboseLegalityReport()
{
if (!Parsed)
return L_AnalysisUnavailable;
const string separator = "===";
string[] br = {separator, string.Empty};
var lines = new List {br[1]};
lines.AddRange(br);
int rl = lines.Count;
var vMoves = Info.Moves;
var vRelearn = Info.Relearn;
for (int i = 0; i < 4; i++)
{
var move = vMoves[i];
if (!move.Valid)
continue;
var msg = move.Format(L_F0_M_1_2, i + 1);
if (pkm.Format != move.Generation)
msg += $" [Gen{move.Generation}]";
lines.Add(msg);
}
if (pkm.Format >= 6)
{
for (int i = 0; i < 4; i++)
{
if (vRelearn[i].Valid)
lines.Add(vRelearn[i].Format(L_F0_RM_1_2, i + 1));
}
}
if (rl != lines.Count) // move info added, break for next section
lines.Add(br[1]);
var outputLines = Parse.Where(chk => chk?.Valid == true && chk.Comment != L_AValid).OrderBy(chk => chk.Judgement); // Fishy sorted to top
lines.AddRange(outputLines.Select(chk => chk.Format(L_F0_1)));
lines.AddRange(br);
lines.Add(string.Format(L_FEncounterType_0, EncounterName));
var loc = EncounterLocation;
if (!string.IsNullOrEmpty(loc))
lines.Add(string.Format(L_F0_1, "Location", loc));
if (pkm.VC)
lines.Add(string.Format(L_F0_1, nameof(GameVersion), Info.Game));
if (!Info.PIDParsed)
Info.PIDIV = MethodFinder.Analyze(pkm);
var pidiv = Info.PIDIV;
{
if (!pidiv.NoSeed)
lines.Add(string.Format(L_FOriginSeed_0, pidiv.OriginSeed.ToString("X8")));
lines.Add(string.Format(L_FPIDType_0, pidiv.Type));
}
if (!Valid && Info.InvalidMatches != null)
{
lines.Add("Other match(es):");
lines.AddRange(Info.InvalidMatches.Select(z => $"{z.LongName}: {z.Reason}"));
}
return GetLegalityReport() + string.Join(Environment.NewLine, lines);
}
///
/// Gets the current array of four moves that might be legal.
///
public IReadOnlyList GetSuggestedRelearnMovesFromEncounter()
{
var parsed = VerifyRelearnMoves.GetSuggestedRelearn(pkm, Info.EncounterOriginal, Info.Relearn);
if (parsed.Count == 0) // Always true for Origins < 6 and encounters without relearn permitted.
return new int[4];
if (!EncounterMatch.EggEncounter)
return parsed;
List window = new List(parsed.Where(z => z != 0));
window.AddRange(pkm.Moves.Where((_, i) => Info.Moves[i].ShouldBeInRelearnMoves()));
window = window.Distinct().ToList();
int[] moves = new int[4];
int start = Math.Max(0, window.Count - 4);
int count = Math.Min(4, window.Count);
window.CopyTo(start, moves, 0, count);
return moves;
}
///
/// Gets four moves which can be learned depending on the input arguments.
///
/// Allow TM moves
/// Allow Tutor moves
/// Allow Move Reminder
public int[] GetSuggestedCurrentMoves(bool tm, bool tutor, bool reminder)
{
if (!Parsed)
return new int[4];
if (pkm.IsEgg && pkm.Format >= 6)
return pkm.RelearnMoves;
return MoveListSuggest.GetSuggestedMoves(pkm, Info.EvoChainsAllGens, tm, !pkm.IsEgg && tutor, reminder, EncounterOriginal);
}
}
}