This commit is contained in:
Kurt
2025-07-27 21:00:38 -05:00
327 changed files with 8872 additions and 7522 deletions

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace PKHeX.Core;
@@ -14,8 +13,16 @@ public sealed record BattleTemplateLocalization(GameStrings Strings, BattleTempl
public const string DefaultLanguage = GameLanguage.DefaultLanguage; // English
private static readonly Dictionary<string, BattleTemplateLocalization> Cache = new();
private static readonly BattleTemplateConfigContext Context = new(LocalizationStorage<BattleTemplateConfig>.Options);
public static readonly LocalizationStorage<BattleTemplateConfig> ConfigCache = new("battle", Context.BattleTemplateConfig);
public static readonly BattleTemplateLocalization Default = GetLocalization(DefaultLanguage);
/// <summary>
/// Gets the localization for the requested language.
/// </summary>
/// <param name="language">Language code</param>
public static BattleTemplateConfig GetConfig(string language) => ConfigCache.Get(language);
/// <param name="language"><see cref="LanguageID"/> index</param>
/// <inheritdoc cref="GetLocalization(string)"/>
public static BattleTemplateLocalization GetLocalization(LanguageID language) =>
@@ -37,17 +44,6 @@ public static BattleTemplateLocalization GetLocalization(string language)
return result;
}
private static string GetJson(string language) => Util.GetStringResource($"battle_{language}.json");
private static BattleTemplateConfigContext GetContext() => new();
private static BattleTemplateConfig GetConfig(string language)
{
var text = GetJson(language);
var result = JsonSerializer.Deserialize(text, GetContext().BattleTemplateConfig)
?? throw new JsonException($"Failed to deserialize {nameof(BattleTemplateConfig)} for {language}");
return result;
}
/// <summary>
/// Force loads all localizations.
/// </summary>

View File

@@ -994,7 +994,7 @@ private ReadOnlySpan<char> ParseLineMove(ReadOnlySpan<char> line, GameStrings st
// Defined Hidden Power
var type = GetHiddenPowerType(moveString[(hiddenPowerName.Length + 1)..]);
var types = strings.types.AsSpan(1, HiddenPower.TypeCount);
var types = strings.HiddenPowerTypes;
int hpVal = StringUtil.FindIndexIgnoreCase(types, type); // Get HP Type
if (hpVal == -1)
return hiddenPowerName;

View File

@@ -76,7 +76,7 @@ public static class BatchMods
value => value.StartsWith(CONST_SHINY),
(pk, cmd) => CommonEdits.SetShiny(pk, GetRequestedShinyState(cmd.PropertyValue))),
new ComplexSet(nameof(PKM.Species), value => value is "0", (pk, _) => pk.Data.AsSpan().Clear()),
new ComplexSet(nameof(PKM.Species), value => value is "0", (pk, _) => pk.Data.Clear()),
new ComplexSet(nameof(PKM.IsNicknamed), value => value.Equals("false", StringComparison.OrdinalIgnoreCase), (pk, _) => pk.SetDefaultNickname()),
// Complicated

View File

@@ -22,7 +22,7 @@ public class EntitySummary : IFatefulEncounterReadOnly // do NOT seal, allow inh
public string Nature => Get(Strings.natures, (byte)Entity.StatNature);
public string Gender => Get(GenderSymbols, Entity.Gender);
public string ESV => Entity.PSV.ToString("0000");
public string HP_Type => Get(Strings.types, Entity.HPType + 1);
public string HP_Type => GetSpan(Strings.HiddenPowerTypes, Entity.HPType);
public string Ability => Get(Strings.abilitylist, Entity.Ability);
public string Move1 => Get(Strings.movelist, Entity.Move1);
public string Move2 => Get(Strings.movelist, Entity.Move2);
@@ -100,7 +100,7 @@ public class EntitySummary : IFatefulEncounterReadOnly // do NOT seal, allow inh
public string Relearn2 => Get(Strings.movelist, Entity.RelearnMove2);
public string Relearn3 => Get(Strings.movelist, Entity.RelearnMove3);
public string Relearn4 => Get(Strings.movelist, Entity.RelearnMove4);
public ushort Checksum => Entity is ISanityChecksum s ? s.Checksum : Checksums.CRC16_CCITT(Entity.Data.AsSpan(Entity.SIZE_STORED));
public ushort Checksum => Entity is ISanityChecksum s ? s.Checksum : Checksums.CRC16_CCITT(Entity.Data[Entity.SIZE_STORED..]);
public int Friendship => Entity.OriginalTrainerFriendship;
public int EggYear => Entity.EggMetDate.GetValueOrDefault().Year;
public int EggMonth => Entity.EggMetDate.GetValueOrDefault().Month;

View File

@@ -106,7 +106,7 @@ private static SaveFile GetBlankSaveFile(GameVersion version, SaveFile? current)
var sav = SaveUtil.GetBlankSAV(version, tr, lang);
if (sav.Version == GameVersion.Invalid) // will fail to load
{
var max = GameInfo.VersionDataSource.MaxBy(z => z.Value)!;
var max = GameInfo.Sources.BallDataSource.MaxBy(z => z.Value)!;
var maxVer = (GameVersion)max.Value;
sav = SaveUtil.GetBlankSAV(maxVer, tr, lang);
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
@@ -62,7 +61,7 @@ private static List<SlotInfoMisc> GetExtraSlots3(SAV3 sav)
return None;
return
[
new(sav.Large.AsMemory(0x3C98), 0) {Type = StorageSlotType.Daycare},
new(sav.LargeBuffer[0x3C98..], 0) {Type = StorageSlotType.Daycare},
];
}

View File

@@ -31,11 +31,12 @@ public FilteredGameDataSource(SaveFile sav, GameDataSource source, bool HaX = fa
var gamelist = GameUtil.GetVersionsWithinRange(sav, sav.Generation).ToList();
Games = Source.VersionDataSource.Where(g => gamelist.Contains((GameVersion)g.Value) || g.Value == 0).ToList();
Languages = GameDataSource.LanguageDataSource(sav.Generation);
Languages = Source.LanguageDataSource(sav.Generation);
Balls = Source.BallDataSource.Where(b => b.Value <= sav.MaxBallID).ToList();
Abilities = Source.AbilityDataSource.Where(a => a.Value <= sav.MaxAbilityID).ToList();
G4GroundTiles = Source.GroundTileDataSource;
ConsoleRegions = Source.Regions;
Natures = Source.NatureDataSource;
}
@@ -113,7 +114,7 @@ private static List<ComboItem> GetMovesWithoutDummy(IReadOnlyList<ComboItem> leg
public readonly IReadOnlyList<ComboItem> Abilities;
public readonly IReadOnlyList<ComboItem> Natures;
public readonly IReadOnlyList<ComboItem> G4GroundTiles;
public readonly IReadOnlyList<ComboItem> ConsoleRegions = GameDataSource.Regions;
public readonly IReadOnlyList<ComboItem> ConsoleRegions;
private const char HiddenAbilitySuffix = 'H';
private const char AbilityIndexSuffix = '1';

View File

@@ -11,30 +11,24 @@ public sealed class GameDataSource
/// <summary>
/// List of <see cref="Region3DSIndex"/> values to display.
/// </summary>
public static readonly IReadOnlyList<ComboItem> Regions =
[
new ("Japan (日本)", 0),
new ("Americas (NA/SA)", 1),
new ("Europe (EU/AU)", 2),
new ("China (中国大陆)", 4),
new ("Korea (한국)", 5),
new ("Taiwan (香港/台灣)", 6),
];
public readonly IReadOnlyList<ComboItem> Regions;
/// <summary>
/// List of <see cref="LanguageID"/> values to display.
/// </summary>
private static readonly ComboItem[] LanguageList =
private readonly ComboItem[] LanguageList;
private static ReadOnlySpan<byte> LanguageIDs =>
[
new ("JPN (日本語)", (int)LanguageID.Japanese),
new ("ENG (English)", (int)LanguageID.English),
new ("FRE (Français)", (int)LanguageID.French),
new ("ITA (Italiano)", (int)LanguageID.Italian),
new ("GER (Deutsch)", (int)LanguageID.German),
new ("ESP (Español)", (int)LanguageID.Spanish),
new ("KOR (한국어)", (int)LanguageID.Korean),
new ("CHS (简体中文)", (int)LanguageID.ChineseS),
new ("CHT (繁體中文)", (int)LanguageID.ChineseT),
(byte)LanguageID.Japanese,
(byte)LanguageID.English,
(byte)LanguageID.French,
(byte)LanguageID.Italian,
(byte)LanguageID.German,
(byte)LanguageID.Spanish,
(byte)LanguageID.Korean,
(byte)LanguageID.ChineseS,
(byte)LanguageID.ChineseT,
];
/// <summary>
@@ -42,7 +36,7 @@ public sealed class GameDataSource
/// </summary>
/// <param name="generation">Generation to get the language list for.</param>
/// <returns>List of languages to display.</returns>
public static IReadOnlyList<ComboItem> LanguageDataSource(byte generation) => generation switch
public IReadOnlyList<ComboItem> LanguageDataSource(byte generation) => generation switch
{
3 => LanguageList[..6], // No Korean+
< 7 => LanguageList[..7], // No Chinese+
@@ -57,6 +51,8 @@ public GameDataSource(GameStrings s)
NatureDataSource = Util.GetCBList(s.natures);
AbilityDataSource = Util.GetCBList(s.abilitylist);
GroundTileDataSource = Util.GetUnsortedCBList(s.groundtiletypes, GroundTileTypeExtensions.ValidTileTypes);
Regions = Util.GetUnsortedCBList(s.console3ds, Locale3DS.DefinedLocales);
LanguageList = Util.GetUnsortedCBList(s.languageNames, LanguageIDs);
var moves = Util.GetCBList(s.movelist);
HaXMoveDataSource = moves;

View File

@@ -30,7 +30,7 @@ public static GameStrings GetStrings(string lang)
public static string GetVersionName(GameVersion version)
{
foreach (var kvp in VersionDataSource)
foreach (var kvp in Sources.VersionDataSource)
{
if (kvp.Value == (int)version)
return kvp.Text;
@@ -38,19 +38,8 @@ public static string GetVersionName(GameVersion version)
return version.ToString();
}
// DataSource providing
public static IReadOnlyList<ComboItem> ItemDataSource => FilteredSources.Items;
public static IReadOnlyList<ComboItem> SpeciesDataSource => Sources.SpeciesDataSource;
public static IReadOnlyList<ComboItem> BallDataSource => Sources.BallDataSource;
public static IReadOnlyList<ComboItem> NatureDataSource => Sources.NatureDataSource;
public static IReadOnlyList<ComboItem> AbilityDataSource => Sources.AbilityDataSource;
public static IReadOnlyList<ComboItem> VersionDataSource => Sources.VersionDataSource;
public static IReadOnlyList<ComboItem> MoveDataSource => Sources.HaXMoveDataSource;
public static IReadOnlyList<ComboItem> GroundTileDataSource => Sources.GroundTileDataSource;
public static IReadOnlyList<ComboItem> Regions => GameDataSource.Regions;
public static IReadOnlyList<ComboItem> LanguageDataSource(byte generation)
=> GameDataSource.LanguageDataSource(generation);
=> Sources.LanguageDataSource(generation);
/// <summary>
/// Gets the location name for the specified parameters.

View File

@@ -27,8 +27,12 @@ public sealed class GameStrings : IBasicStrings
public readonly string[] wallpapernames, puffs, walkercourses;
public readonly string[] uggoods, ugspheres, ugtraps, ugtreasures;
public readonly string[] seals, accessories, backdrops, poketchapps;
public readonly string[] console3ds, languageNames;
private readonly string LanguageFilePrefix;
public ReadOnlySpan<string> HiddenPowerTypes => types.AsSpan(1, HiddenPower.TypeCount);
public readonly RibbonStrings Ribbons;
public LanguageID Language { get; }
public string EggName { get; }
public IReadOnlyList<string> Species => specieslist;
@@ -59,6 +63,7 @@ internal GameStrings(string langFilePrefix)
Language = GameLanguage.GetLanguage(LanguageFilePrefix = langFilePrefix);
ribbons = Get("ribbons");
Ribbons = new(ribbons);
// Past Generation strings
g3items = Get("ItemsG3");
@@ -124,6 +129,8 @@ internal GameStrings(string langFilePrefix)
accessories = Get("accessories");
backdrops = Get("backdrops");
poketchapps = Get("poketchapps");
console3ds = Get("console3ds");
languageNames = Get("language");
EggName = specieslist[0];
Gen4 = Get4("hgss");
@@ -784,11 +791,25 @@ private string[] GetItemStrings3(GameVersion game)
return g3items;
var g3ItemsWithEBerry = (string[])g3items.Clone();
g3ItemsWithEBerry[175] = EReaderBerrySettings.DisplayName;
g3ItemsWithEBerry[175] = GetEnigmaBerryName3(Language, EReaderBerrySettings.Name);
return g3ItemsWithEBerry;
}
}
private static string GetEnigmaBerryName3(LanguageID language, string berryName) => string.Format(language switch
{
Japanese => "{0}のみ",
English => "{0} BERRY",
German => "{0}BEERE",
French => "BAIE {0}",
Italian => "BACCA{0}",
Spanish => "BAYA {0}",
Korean => "{0}열매",
ChineseS => "{0}果",
ChineseT => "{0}果",
_ => throw new ArgumentOutOfRangeException(nameof(language), language, null),
}, berryName);
/// <summary>
/// Gets the location name for the specified parameters.
/// </summary>

View File

@@ -4,6 +4,8 @@
namespace PKHeX.Core.Bulk;
public readonly record struct BulkCheckResult(CheckResult Result, string Comment);
/// <summary>
/// Analyzes content within a <see cref="SaveFile"/> for overall <see cref="PKM"/> legality analysis.
/// </summary>
@@ -12,7 +14,7 @@ public sealed class BulkAnalysis
public readonly IReadOnlyList<SlotCache> AllData;
public readonly IReadOnlyList<LegalityAnalysis> AllAnalysis;
public readonly ITrainerInfo Trainer;
public readonly List<CheckResult> Parse = [];
public readonly List<BulkCheckResult> Parse = [];
public readonly Dictionary<ulong, SlotCache> Trackers = [];
public readonly bool Valid;
@@ -41,7 +43,7 @@ public BulkAnalysis(SaveFile sav, BulkAnalysisSettings settings)
CloneFlags = new bool[AllData.Count];
ScanAll();
Valid = Parse.Count == 0 || Parse.TrueForAll(static z => z.Valid);
Valid = Parse.Count == 0 || Parse.TrueForAll(static z => z.Result.Valid);
}
// Remove things that aren't actual stored data, or already flagged by legality checks.
@@ -79,21 +81,21 @@ private void ScanAll()
/// <summary>
/// Adds a new entry to the <see cref="Parse"/> list.
/// </summary>
public void AddLine(SlotCache first, SlotCache second, string msg, CheckIdentifier i, Severity s = Severity.Invalid)
public void AddLine(SlotCache first, SlotCache second, LegalityCheckResultCode msg, CheckIdentifier i, Severity s = Severity.Invalid)
{
var c = $"{msg}{Environment.NewLine}{GetSummary(first)}{Environment.NewLine}{GetSummary(second)}{Environment.NewLine}";
var chk = new CheckResult(s, i, c);
Parse.Add(chk);
var line = $"{msg}{Environment.NewLine}{GetSummary(first)}{Environment.NewLine}{GetSummary(second)}{Environment.NewLine}";
var chk = CheckResult.Get(s, i, msg);
Parse.Add(new(chk, line));
}
/// <summary>
/// Adds a new entry to the <see cref="Parse"/> list.
/// </summary>
public void AddLine(SlotCache first, string msg, CheckIdentifier i, Severity s = Severity.Invalid)
public void AddLine(SlotCache first, LegalityCheckResultCode msg, CheckIdentifier i, Severity s = Severity.Invalid)
{
var c = $"{msg}{Environment.NewLine}{GetSummary(first)}{Environment.NewLine}";
var chk = new CheckResult(s, i, c);
Parse.Add(chk);
var line = $"{msg}{Environment.NewLine}{GetSummary(first)}{Environment.NewLine}";
var chk = CheckResult.Get(s, i, msg);
Parse.Add(new(chk, line));
}
private static LegalityAnalysis[] GetIndividualAnalysis(ReadOnlySpan<SlotCache> list)

View File

@@ -61,10 +61,10 @@ private static void VerifyECShare(BulkAnalysis input, CombinedReference pr, Comb
{
if (ca.Info.Generation != gen)
{
input.AddLine(ps, cs, "EC sharing across generations detected.", ident);
input.AddLine(ps, cs, LegalityCheckResultCode.BulkSharingEncryptionConstantGenerationDifferent, ident);
return;
}
input.AddLine(ps, cs, "EC sharing for 3DS-onward origin detected.", ident);
input.AddLine(ps, cs, LegalityCheckResultCode.BulkSharingEncryptionConstantGenerationSame, ident);
return;
}
@@ -76,7 +76,7 @@ private static void VerifyECShare(BulkAnalysis input, CombinedReference pr, Comb
if (eggMysteryCurrent != eggMysteryPrevious)
{
input.AddLine(ps, cs, "EC sharing across RNG encounters detected.", ident);
input.AddLine(ps, cs, LegalityCheckResultCode.BulkSharingEncryptionConstantEncounterType, ident);
}
}
}

View File

@@ -51,7 +51,7 @@ private static void CheckDuplicateOwnedGifts(BulkAnalysis input)
var grp = tidGroup[0];
var first = grp[0].Slot;
var second = grp[1].Slot;
input.AddLine(first, second, $"Receipt of the same egg mystery gifts detected: {dupe.Key}", Encounter);
input.AddLine(first, second, LegalityCheckResultCode.BulkDuplicateMysteryGiftEggReceived, Encounter);
}
}

View File

@@ -58,14 +58,14 @@ private static void VerifyPIDShare(BulkAnalysis input, CombinedReference pr, Com
if (ca.Info.Generation != gen)
{
input.AddLine(ps, cs, "PID sharing across generations detected.", ident);
input.AddLine(ps, cs, LegalityCheckResultCode.BulkSharingPIDGenerationDifferent, ident);
return;
}
bool gbaNDS = gen is 3 or 4 or 5;
if (!gbaNDS)
{
input.AddLine(ps, cs, "PID sharing for 3DS-onward origin detected.", ident);
input.AddLine(ps, cs, LegalityCheckResultCode.BulkSharingPIDGenerationSame, ident);
return;
}
@@ -77,7 +77,7 @@ private static void VerifyPIDShare(BulkAnalysis input, CombinedReference pr, Com
if (eggMysteryCurrent != eggMysteryPrevious)
{
input.AddLine(ps, cs, "PID sharing across RNG encounters detected.", ident);
input.AddLine(ps, cs, LegalityCheckResultCode.BulkSharingPIDEncounterType, ident);
}
}
}

View File

@@ -68,7 +68,7 @@ private static bool VerifyIDReuse(BulkAnalysis input, SlotCache ps, LegalityAnal
// Trainer-ID-SID16 should only occur for one version
if (IsSharedVersion(pp, pa, cp, ca))
{
input.AddLine(ps, cs, "TID sharing across versions detected.", ident);
input.AddLine(ps, cs, LegalityCheckResultCode.BulkSharingTrainerVersion, ident);
return true;
}
@@ -76,7 +76,7 @@ private static bool VerifyIDReuse(BulkAnalysis input, SlotCache ps, LegalityAnal
if (pp.OriginalTrainerName != cp.OriginalTrainerName)
{
var severity = ca.Info.Generation == 4 ? Severity.Fishy : Severity.Invalid;
input.AddLine(ps, cs, "TID sharing across different trainer names detected.", ident, severity);
input.AddLine(ps, cs, LegalityCheckResultCode.BulkSharingTrainerIDs, ident, severity);
}
return false;

View File

@@ -1,6 +1,6 @@
using System;
using static PKHeX.Core.CheckIdentifier;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core.Bulk;
@@ -44,7 +44,7 @@ private static void Verify(BulkAnalysis input, SlotCache cs, LegalityAnalysis la
var shouldBe0 = tr.IsFromTrainer(pk);
byte expect = shouldBe0 ? (byte)0 : (byte)1;
if (!HistoryVerifier.IsHandlerStateCorrect(la.EncounterOriginal, pk, current, expect))
input.AddLine(cs, LTransferCurrentHandlerInvalid, Trainer);
input.AddLine(cs, TransferCurrentHandlerInvalid, Trainer);
if (current == 1)
CheckHandlingTrainerEquals(input, pk, tr, cs);
@@ -58,14 +58,14 @@ private static void CheckHandlingTrainerEquals(BulkAnalysis data, PKM pk, SaveFi
ht = ht[..len];
if (!ht.SequenceEqual(tr.OT))
data.AddLine(cs, LTransferHTMismatchName, Trainer);
data.AddLine(cs, TransferHandlerMismatchName, Trainer);
if (pk.HandlingTrainerGender != tr.Gender)
data.AddLine(cs, LTransferHTMismatchGender, Trainer);
data.AddLine(cs, TransferHandlerMismatchGender, Trainer);
// If the format exposes a language, check if it matches.
// Can be mismatched as the game only checks OT/Gender equivalence -- if it matches, don't update everything else.
// Statistically unlikely that players will play in different languages, but it's technically possible.
if (pk is IHandlerLanguage h && h.HandlingTrainerLanguage != tr.Language)
data.AddLine(cs, LTransferHTMismatchLanguage, Trainer, Severity.Fishy);
data.AddLine(cs, TransferHandlerMismatchLanguage, Trainer, Severity.Fishy);
}
}

View File

@@ -38,7 +38,7 @@ private static void CheckClones(BulkAnalysis input)
}
input.SetIsClone(i, true);
input.AddLine(ps, cs, "Clone detected (Details).", Encounter);
input.AddLine(ps, cs, LegalityCheckResultCode.BulkCloneDetectedDetails, Encounter);
}
}
@@ -54,7 +54,7 @@ private static void CheckClonedTrackerHOME(BulkAnalysis input, IHomeTrack home,
private static void CheckTrackerPresent(BulkAnalysis input, SlotCache cs, ulong tracker)
{
if (input.Trackers.TryGetValue(tracker, out var clone))
input.AddLine(cs, clone, "Clone detected (Duplicate Tracker).", Encounter);
input.AddLine(cs, clone, LegalityCheckResultCode.BulkCloneDetectedTracker, Encounter);
else
input.Trackers.Add(tracker, cs);
}

View File

@@ -1,5 +1,5 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -45,7 +45,7 @@ public static void FindVerifiedEncounter(PKM pk, LegalInfo info)
// Looks like we might have a good enough match. Check if this is really a good match.
info.EncounterMatch = enc;
if (e.Comment.Length != 0)
if (e.Result != Valid)
info.Parse.Add(e);
if (!VerifySecondaryChecks(pk, info, encounter))
continue;
@@ -63,7 +63,7 @@ public static void FindVerifiedEncounter(PKM pk, LegalInfo info)
continue;
// We ran out of possible encounters without finding a suitable match; add a message indicating that the encounter is not a complete match.
info.Parse.Add(new CheckResult(Severity.Invalid, CheckIdentifier.Encounter, LEncInvalid));
info.Parse.Add(CheckResult.Get(Severity.Invalid, CheckIdentifier.Encounter, EncInvalid));
break;
}
@@ -71,9 +71,9 @@ public static void FindVerifiedEncounter(PKM pk, LegalInfo info)
if (manual != EncounterYieldFlag.None)
{
if (!info.FrameMatches) // if false, all valid RNG frame matches have already been consumed
info.Parse.Add(new CheckResult(ParseSettings.Settings.FramePattern.GetSeverity(info.Generation), CheckIdentifier.PID, LEncConditionBadRNGFrame));
info.Parse.Add(CheckResult.Get(ParseSettings.Settings.FramePattern.GetSeverity(info.Generation), CheckIdentifier.PID, EncConditionBadRNGFrame));
else if (!info.PIDIVMatches) // if false, all valid PID/IV matches have already been consumed
info.Parse.Add(new CheckResult(Severity.Invalid, CheckIdentifier.PID, LPIDTypeMismatch));
info.Parse.Add(CheckResult.Get(Severity.Invalid, CheckIdentifier.PID, PIDTypeMismatch));
}
}
@@ -164,22 +164,22 @@ private static bool VerifySecondaryChecks(PKM pk, LegalInfo info, PeekEnumerator
private static void VerifyWithoutEncounter(PKM pk, LegalInfo info)
{
info.EncounterMatch = new EncounterInvalid(pk);
string hint = GetHintWhyNotFound(pk, info.EncounterMatch.Generation);
var hint = GetHintWhyNotFound(pk, info.EncounterMatch.Generation);
info.Parse.Add(new CheckResult(Severity.Invalid, CheckIdentifier.Encounter, hint));
info.Parse.Add(CheckResult.Get(Severity.Invalid, CheckIdentifier.Encounter, hint));
LearnVerifierRelearn.Verify(info.Relearn, info.EncounterOriginal, pk);
LearnVerifier.Verify(info.Moves, pk, info.EncounterMatch, info.EvoChainsAllGens);
}
private static string GetHintWhyNotFound(PKM pk, byte generation)
private static LegalityCheckResultCode GetHintWhyNotFound(PKM pk, byte generation)
{
if (WasGiftEgg(pk, generation, pk.EggLocation))
return LEncGift;
return EncGift;
if (WasEventEgg(pk, generation))
return LEncGiftEggEvent;
return EncGiftEggEvent;
if (WasEvent(pk, generation))
return LEncGiftNotFound;
return LEncInvalid;
return EncGiftNotFound;
return EncInvalid;
}
private static bool WasGiftEgg(PKM pk, byte generation, ushort eggLocation) => !pk.FatefulEncounter && generation switch

View File

@@ -118,7 +118,8 @@ private bool SeekForward<TArea>(TArea[] areas)
var species = Entity.Species;
if (species is (int)Species.Dudunsparce or (int)Species.Maushold)
{
if (!EvolutionRestrictions.GetIsExpectedEvolveFormEC100(species, Entity.Form, Entity.EncryptionConstant % 100 == 0))
var expectRare = EvolutionRestrictions.IsEvolvedSpeciesFormRare(Entity.EncryptionConstant);
if (!EvolutionRestrictions.GetIsExpectedEvolveFormEC100(species, Entity.Form, expectRare))
return SeekMode.Reverse;
}
else if (EvolutionRestrictions.IsFormArgEvolution(species))

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core;
@@ -9,15 +8,21 @@ namespace PKHeX.Core;
/// </summary>
public static class EncounterText
{
public static IReadOnlyList<string> GetTextLines(this IEncounterInfo enc, bool verbose = false) => GetTextLines(enc, GameInfo.Strings, verbose);
private static EncounterDisplayContext GetContext(string language = GameLanguage.DefaultLanguage) => new()
{
Localization = EncounterDisplayLocalization.Get(language),
Strings = GameInfo.GetStrings(language),
};
public static IReadOnlyList<string> GetTextLines(this IEncounterInfo enc, GameStrings strings, bool verbose = false)
public static IReadOnlyList<string> GetTextLines(this IEncounterInfo enc, bool verbose = false, string language = GameLanguage.DefaultLanguage) => GetTextLines(enc, GetContext(language), verbose);
public static IReadOnlyList<string> GetTextLines(this IEncounterInfo enc, EncounterDisplayContext ctx, bool verbose = false)
{
var lines = new List<string>();
var str = strings.Species;
var name = (uint)enc.Species < str.Count ? str[enc.Species] : enc.Species.ToString();
var EncounterName = $"{(enc is IEncounterable ie ? ie.LongName : "Special")} ({name})";
lines.Add(string.Format(L_FEncounterType_0, EncounterName));
var loc = ctx.Localization;
var name = ctx.GetSpeciesName(enc);
lines.Add(string.Format(loc.Format, loc.EncounterType, name));
if (enc is MysteryGift mg)
{
lines.AddRange(mg.GetDescription());
@@ -26,21 +31,15 @@ public static IReadOnlyList<string> GetTextLines(this IEncounterInfo enc, GameSt
{
var moves = m.Moves;
if (moves.HasMoves)
{
string result = moves.GetMovesetLine(strings.movelist);
lines.Add(result);
}
lines.Add(ctx.GetMoveset(moves));
}
var loc = enc.GetEncounterLocation(enc.Generation, enc.Version);
if (!string.IsNullOrEmpty(loc))
lines.Add(string.Format(L_F0_1, "Location", loc));
var location = enc.GetEncounterLocation(enc.Generation, enc.Version);
if (!string.IsNullOrEmpty(location))
lines.Add(string.Format(loc.Format, loc.Location, location));
var game = enc.Version.IsValidSavedVersion() ? strings.gamelist[(int)enc.Version] : enc.Version.ToString();
lines.Add(string.Format(L_F0_1, nameof(GameVersion), game));
lines.Add(enc.LevelMin == enc.LevelMax
? $"Level: {enc.LevelMin}"
: $"Level: {enc.LevelMin}-{enc.LevelMax}");
lines.Add(ctx.GetVersionDisplay(enc));
lines.Add(ctx.GetLevelDisplay(enc));
if (!verbose)
return lines;
@@ -56,3 +55,37 @@ public static IReadOnlyList<string> GetTextLines(this IEncounterInfo enc, GameSt
return lines;
}
}
public record struct EncounterDisplayContext
{
public required EncounterDisplayLocalization Localization { get; init; }
public required GameStrings Strings { get; init; }
public string GetSpeciesName(IEncounterTemplate enc)
{
var encSpecies = enc.Species;
var str = Strings.Species;
var name = (uint)encSpecies < str.Count ? str[encSpecies] : encSpecies.ToString();
var EncounterName = $"{(enc is IEncounterable ie ? ie.LongName : "Special")} ({name})";
return EncounterName;
}
public string GetMoveset(Moveset moves) => moves.GetMovesetLine(Strings.movelist);
public string GetVersionDisplay(IEncounterTemplate enc)
{
var version = enc.Version;
var versionName = enc.Version.IsValidSavedVersion()
? Strings.gamelist[(int)enc.Version]
: enc.Version.ToString();
return string.Format(Localization.Format, Localization.Version, versionName);
}
public string GetLevelDisplay(IEncounterTemplate enc)
{
if (enc.LevelMin == enc.LevelMax)
return string.Format(Localization.Format, Localization.Level, enc.LevelMin);
return string.Format(Localization.Format, Localization.LevelRange, enc.LevelMin, enc.LevelMax);
}
}

View File

@@ -1,5 +1,5 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -21,15 +21,15 @@ public static class EncounterVerifier
private static CheckResult VerifyEncounter(PKM pk, IEncounterTemplate enc) => enc switch
{
EncounterShadow3Colo { IsEReader: true } when pk.Language != (int)LanguageID.Japanese => GetInvalid(LG3EReader),
EncounterStatic3 { Species: (int)Species.Mew } when pk.Language != (int)LanguageID.Japanese => GetInvalid(LEncUnreleasedEMewJP),
EncounterStatic3 { Species: (int)Species.Deoxys, Location: 200 } when pk.Language == (int)LanguageID.Japanese => GetInvalid(LEncUnreleased),
EncounterStatic4 { IsRoaming: true } when pk is G4PKM { MetLocation: 193, GroundTile: GroundTileType.Water } => GetInvalid(LG4InvalidTileR45Surf),
EncounterShadow3Colo { IsEReader: true } when pk.Language != (int)LanguageID.Japanese => GetInvalid(G3EReader),
EncounterStatic3 { Species: (int)Species.Mew } when pk.Language != (int)LanguageID.Japanese => GetInvalid(EncUnreleasedEMewJP),
EncounterStatic3 { Species: (int)Species.Deoxys, Location: 200 } when pk.Language == (int)LanguageID.Japanese => GetInvalid(EncUnreleased),
EncounterStatic4 { IsRoaming: true } when pk is G4PKM { MetLocation: 193, GroundTile: GroundTileType.Water } => GetInvalid(G4InvalidTileR45Surf),
MysteryGift g => VerifyEncounterEvent(pk, g),
IEncounterEgg e when pk.IsEgg => VerifyEncounterEggUnhatched(pk, e),
{ IsEgg: true } when !pk.IsEgg => VerifyEncounterEggHatched(pk, enc.Context),
EncounterInvalid => GetInvalid(LEncInvalid),
_ => GetValid(string.Empty), // todo: refactor
EncounterInvalid => GetInvalid(EncInvalid),
_ => GetValid(Valid),
};
private static CheckResult VerifyEncounterG12(PKM pk, IEncounterTemplate enc)
@@ -39,11 +39,11 @@ private static CheckResult VerifyEncounterG12(PKM pk, IEncounterTemplate enc)
return enc switch
{
EncounterSlot1 => GetValid(LEncCondition),
EncounterSlot1 => GetValid(EncCondition),
EncounterSlot2 s2 => VerifyWildEncounterGen2(pk, s2),
EncounterTrade1 t => VerifyEncounterTrade(pk, t),
EncounterTrade2 => GetValid(LEncTradeMatch),
_ => GetValid(string.Empty), // todo: refactor
EncounterTrade2 => GetValid(EncTradeMatch),
_ => GetValid(Valid),
};
}
@@ -51,9 +51,9 @@ private static CheckResult VerifyEncounterG12(PKM pk, IEncounterTemplate enc)
private static CheckResult VerifyWildEncounterGen2(ITrainerID16 pk, EncounterSlot2 enc) => enc.Type switch
{
SlotType2.Headbutt or SlotType2.HeadbuttSpecial => enc.IsTreeAvailable(pk.TID16)
? GetValid(LG2TreeID)
: GetInvalid(LG2InvalidTileTreeNotFound),
_ => GetValid(LEncCondition),
? GetValid(G2TreeID)
: GetInvalid(G2InvalidTileTreeNotFound),
_ => GetValid(EncCondition),
};
// Eggs
@@ -68,7 +68,7 @@ private static CheckResult VerifyEncounterG12(PKM pk, IEncounterTemplate enc)
EncounterEgg8b=> VerifyUnhatchedEgg(pk, Locations.LinkTrade6NPC, Locations.Default8bNone),
EncounterEgg8 => VerifyUnhatchedEgg(pk, Locations.LinkTrade6),
EncounterEgg9 => VerifyUnhatchedEgg(pk, Locations.LinkTrade6),
_ => GetInvalid(LEggLocationInvalid),
_ => GetInvalid(EggLocationInvalid),
};
private static CheckResult VerifyEncounterEggHatched(PKM pk, EntityContext context) => context switch
@@ -82,46 +82,46 @@ private static CheckResult VerifyEncounterG12(PKM pk, IEncounterTemplate enc)
EntityContext.Gen8b=> VerifyEncounterEgg8BDSP(pk),
EntityContext.Gen8 => VerifyEncounterEgg8(pk),
EntityContext.Gen9 => VerifyEncounterEgg9(pk),
_ => GetInvalid(LEggLocationInvalid),
_ => GetInvalid(EggLocationInvalid),
};
private static CheckResult VerifyEncounterEgg2(PKM pk)
{
if (pk is not ICaughtData2 { CaughtData: not 0 } c2)
return GetValid(LEggLocation);
return GetValid(EggLocation);
if (c2.MetLevel != EggStateLegality.EggMetLevel)
return GetInvalid(string.Format(LEggFMetLevel_0, EggStateLegality.EggMetLevel));
return GetInvalid(EggFMetLevel_0, EggStateLegality.EggMetLevel);
if (pk.MetLocation > 95)
return GetInvalid(LEggMetLocationFail);
return GetInvalid(EggMetLocationFail);
// Any met location is fine.
return GetValid(LEggLocation);
return GetValid(EggLocation);
}
private static CheckResult VerifyUnhatchedEgg2(PKM pk)
{
if (pk is not ICaughtData2 { CaughtData: not 0 } c2)
return new CheckResult(CheckIdentifier.Encounter);
return CheckResult.GetValid(CheckIdentifier.Encounter);
if (c2.MetLevel != EggStateLegality.EggMetLevel)
return GetInvalid(string.Format(LEggFMetLevel_0, EggStateLegality.EggMetLevel));
return GetInvalid(EggFMetLevel_0, EggStateLegality.EggMetLevel);
if (c2.MetLocation != 0)
return GetInvalid(LEggLocationInvalid);
return GetValid(LEggLocation);
return GetInvalid(EggLocationInvalid);
return GetValid(EggLocation);
}
private static CheckResult VerifyUnhatchedEgg3(PKM pk)
{
if (pk.MetLevel != EggStateLegality.EggMetLevel34)
return GetInvalid(string.Format(LEggFMetLevel_0, EggStateLegality.EggMetLevel34));
return GetInvalid(EggFMetLevel_0, EggStateLegality.EggMetLevel34);
// Only EncounterEgg should reach here.
var loc = pk.FRLG ? Locations.HatchLocationFRLG : Locations.HatchLocationRSE;
if (pk.MetLocation != loc)
return GetInvalid(LEggMetLocationFail);
return GetInvalid(EggMetLocationFail);
return GetValid(LEggLocation);
return GetValid(EggLocation);
}
private static CheckResult VerifyEncounterEgg3(PKM pk)
@@ -130,46 +130,47 @@ private static CheckResult VerifyEncounterEgg3(PKM pk)
return VerifyEncounterEgg3Transfer(pk);
if (pk.MetLevel != EggStateLegality.EggMetLevel34)
return GetInvalid(string.Format(LEggFMetLevel_0, EggStateLegality.EggMetLevel34));
return GetInvalid(EggFMetLevel_0, EggStateLegality.EggMetLevel34);
// Check the origin game list.
var met = (byte)pk.MetLocation;
bool valid = EggHatchLocation3.IsValidMet3(met, pk.Version);
if (valid)
return GetValid(LEggLocation);
return GetValid(EggLocation);
// Version isn't updated when hatching on a different game. Check any game.
if (EggHatchLocation3.IsValidMet3Any(met))
return GetValid(LEggLocationTrade);
return GetInvalid(LEggLocationInvalid);
return GetValid(EggLocationTrade);
return GetInvalid(EggLocationInvalid);
}
private static CheckResult GetInvalid(string message, CheckIdentifier ident = CheckIdentifier.Encounter) => new(Severity.Invalid, ident, message);
private static CheckResult GetValid(string message) => new(Severity.Valid, CheckIdentifier.Encounter, message);
private static CheckResult GetInvalid(LegalityCheckResultCode message, CheckIdentifier ident = CheckIdentifier.Encounter) => CheckResult.Get(Severity.Invalid, ident, message);
private static CheckResult GetInvalid(LegalityCheckResultCode message, byte value, CheckIdentifier ident = CheckIdentifier.Encounter) => CheckResult.Get(Severity.Invalid, ident, message, value);
private static CheckResult GetValid(LegalityCheckResultCode message) => CheckResult.Get(Severity.Valid, CheckIdentifier.Encounter, message);
private static CheckResult VerifyEncounterEgg3Transfer(PKM pk)
{
if (pk.IsEgg)
return GetInvalid(LTransferEgg);
return GetInvalid(TransferEgg);
if (pk.MetLevel < EggStateLegality.EggLevel23)
return GetInvalid(LTransferEggMetLevel);
return GetInvalid(TransferEggMetLevel);
var expectEgg = pk is PB8 ? Locations.Default8bNone : 0;
if (pk.EggLocation != expectEgg)
return GetInvalid(LEggLocationNone);
return GetInvalid(EggLocationNone);
if (pk.Format != 4)
{
if (pk.MetLocation != Locations.Transfer4)
return GetInvalid(LTransferEggLocationTransporter);
return GetInvalid(TransferEggLocationTransporter);
}
else
{
if (pk.MetLocation != Locations.Transfer3)
return GetInvalid(LEggLocationPalPark);
return GetInvalid(EggLocationPalPark);
}
return GetValid(LEggLocation);
return GetValid(EggLocation);
}
private static CheckResult VerifyEncounterEgg4(PKM pk)
@@ -177,49 +178,49 @@ private static CheckResult VerifyEncounterEgg4(PKM pk)
if (pk.Format != 4) // transferred
{
if (pk.IsEgg)
return GetInvalid(LTransferEgg);
return GetInvalid(TransferEgg);
if (pk.MetLevel < EggStateLegality.EggLevel)
return GetInvalid(LTransferEggMetLevel);
return GetInvalid(TransferEggMetLevel);
if (pk.MetLocation != Locations.Transfer4)
return GetInvalid(LTransferEggLocationTransporter);
return GetValid(LEggLocation);
return GetInvalid(TransferEggLocationTransporter);
return GetValid(EggLocation);
}
// Native
const byte level = EggStateLegality.EggMetLevel34;
if (pk.MetLevel != level)
return GetInvalid(string.Format(LEggFMetLevel_0, level));
return GetInvalid(EggFMetLevel_0, level);
var met = pk.MetLocation;
bool valid = EggHatchLocation4.IsValidMet4(met, pk.Version);
if (valid)
return GetValid(LEggLocation);
return GetValid(EggLocation);
// Version isn't updated when hatching on a different game. Check any game.
if (pk.EggLocation == Locations.LinkTrade4 && EggHatchLocation4.IsValidMet4Any(met))
return GetValid(LEggLocationTrade);
return GetInvalid(LEggLocationInvalid);
return GetValid(EggLocationTrade);
return GetInvalid(EggLocationInvalid);
}
private static CheckResult VerifyEncounterEgg5(PKM pk)
{
const byte level = EggStateLegality.EggMetLevel;
if (pk.MetLevel != level)
return GetInvalid(string.Format(LEggFMetLevel_0, level));
return GetInvalid(EggFMetLevel_0, level);
var met = pk.MetLocation;
bool valid = EggHatchLocation5.IsValidMet5(met, pk.Version);
if (valid)
return GetValid(LEggLocation);
return GetInvalid(LEggLocationInvalid);
return GetValid(EggLocation);
return GetInvalid(EggLocationInvalid);
}
private static CheckResult VerifyEncounterEgg6(PKM pk)
{
const byte level = EggStateLegality.EggMetLevel;
if (pk.MetLevel != level)
return GetInvalid(string.Format(LEggFMetLevel_0, level));
return GetInvalid(EggFMetLevel_0, level);
var met = pk.MetLocation;
bool valid = pk.XY
@@ -227,15 +228,15 @@ private static CheckResult VerifyEncounterEgg6(PKM pk)
: EggHatchLocation6.IsValidMet6AO(met);
if (valid)
return GetValid(LEggLocation);
return GetInvalid(LEggLocationInvalid);
return GetValid(EggLocation);
return GetInvalid(EggLocationInvalid);
}
private static CheckResult VerifyEncounterEgg7(PKM pk)
{
const byte level = EggStateLegality.EggMetLevel;
if (pk.MetLevel != level)
return GetInvalid(string.Format(LEggFMetLevel_0, level));
return GetInvalid(EggFMetLevel_0, level);
var met = pk.MetLocation;
bool valid = pk.SM
@@ -243,20 +244,20 @@ private static CheckResult VerifyEncounterEgg7(PKM pk)
: EggHatchLocation7.IsValidMet7USUM(met);
if (valid)
return GetValid(LEggLocation);
return GetInvalid(LEggLocationInvalid);
return GetValid(EggLocation);
return GetInvalid(EggLocationInvalid);
}
private static CheckResult VerifyEncounterEgg8(PKM pk)
{
const byte level = EggStateLegality.EggMetLevel;
if (pk.MetLevel != level)
return GetInvalid(string.Format(LEggFMetLevel_0, level));
return GetInvalid(EggFMetLevel_0, level);
var valid = IsValidMetForeignEggSWSH(pk, pk.MetLocation);
if (valid)
return GetValid(LEggLocation);
return GetInvalid(LEggLocationInvalid);
return GetValid(EggLocation);
return GetInvalid(EggLocationInvalid);
}
private static bool IsValidMetForeignEggSWSH(PKM pk, ushort met)
@@ -275,7 +276,7 @@ private static CheckResult VerifyEncounterEgg8BDSP(PKM pk)
const byte level = EggStateLegality.EggMetLevel;
if (pk.MetLevel != level)
return GetInvalid(string.Format(LEggFMetLevel_0, level));
return GetInvalid(EggFMetLevel_0, level);
var met = pk.MetLocation;
bool valid = pk.Version == GameVersion.BD
@@ -283,8 +284,8 @@ private static CheckResult VerifyEncounterEgg8BDSP(PKM pk)
: EggHatchLocation8b.IsValidMet8SP(met);
if (valid)
return GetValid(LEggLocation);
return GetInvalid(LEggLocationInvalid);
return GetValid(EggLocation);
return GetInvalid(EggLocationInvalid);
}
private static CheckResult VerifyEncounterEgg9(PKM pk)
@@ -294,7 +295,7 @@ private static CheckResult VerifyEncounterEgg9(PKM pk)
const byte level = EggStateLegality.EggMetLevel;
if (pk.MetLevel != level)
return GetInvalid(string.Format(LEggFMetLevel_0, level));
return GetInvalid(EggFMetLevel_0, level);
var met = pk.MetLocation;
bool valid = pk.Version == GameVersion.SL
@@ -302,40 +303,40 @@ private static CheckResult VerifyEncounterEgg9(PKM pk)
: EggHatchLocation9.IsValidMet9VL(met);
if (valid)
return GetValid(LEggLocation);
return GetInvalid(LEggLocationInvalid);
return GetValid(EggLocation);
return GetInvalid(EggLocationInvalid);
}
private static CheckResult VerifyUnhatchedEgg(PKM pk, int tradeLoc, ushort noneLoc = 0)
{
var eggLevel = pk.Format is 3 or 4 ? EggStateLegality.EggMetLevel34 : EggStateLegality.EggMetLevel;
if (pk.MetLevel != eggLevel)
return GetInvalid(string.Format(LEggFMetLevel_0, eggLevel));
return GetInvalid(EggFMetLevel_0, eggLevel);
if (pk.EggLocation == tradeLoc)
return GetInvalid(LEggLocationTradeFail);
return GetInvalid(EggLocationTradeFail);
var met = pk.MetLocation;
if (met == tradeLoc)
return GetValid(LEggLocationTrade);
return GetValid(EggLocationTrade);
return met == noneLoc
? GetValid(LEggUnhatched)
: GetInvalid(LEggLocationNone);
? GetValid(EggUnhatched)
: GetInvalid(EggLocationNone);
}
private static CheckResult VerifyUnhatchedEgg5(PKM pk)
{
const byte eggLevel = EggStateLegality.EggMetLevel;
if (pk.MetLevel != eggLevel)
return GetInvalid(string.Format(LEggFMetLevel_0, eggLevel));
return GetInvalid(EggFMetLevel_0, eggLevel);
if (pk.EggLocation is (Locations.LinkTrade5 or Locations.LinkTrade5NPC))
return GetInvalid(LEggLocationTradeFail);
return GetInvalid(EggLocationTradeFail);
var met = pk.MetLocation;
if (met is (Locations.LinkTrade5 or Locations.LinkTrade5NPC))
return GetValid(LEggLocationTrade);
return GetValid(EggLocationTrade);
return met == 0
? GetValid(LEggUnhatched)
: GetInvalid(LEggLocationNone);
? GetValid(EggUnhatched)
: GetInvalid(EggLocationNone);
}
private static CheckResult VerifyEncounterTrade(ISpeciesForm pk, EncounterTrade1 trade)
@@ -346,23 +347,13 @@ private static CheckResult VerifyEncounterTrade(ISpeciesForm pk, EncounterTrade1
// Pokémon that evolve on trade can not be in the phase evolution after the trade
// If the trade holds an Everstone, EvolveOnTrade will be false for the encounter
// No need to range check the species, as it matched to a valid encounter species.
var names = ParseSettings.SpeciesStrings;
var evolved = names[species + 1];
var unevolved = names[species];
return GetInvalid(string.Format(LEvoTradeReq, unevolved, evolved));
return GetInvalid(EncTradeShouldHaveEvolvedToSpecies_0);
}
return GetValid(LEncTradeMatch);
return GetValid(EncTradeMatch);
}
private static CheckResult VerifyEncounterEvent(PKM pk, MysteryGift gift)
{
switch (gift)
{
case PCD pcd:
if (!pcd.CanBeReceivedByVersion(pk.Version) && pcd.Gift.PK.Version == 0)
return GetInvalid(string.Format(L_XMatches0_1, gift.CardHeader, $"-- {LEncGiftVersionNotDistributed}"));
break;
}
if (!pk.IsEgg && gift.IsEgg) // hatched
{
var hatchCheck = VerifyEncounterEggHatched(pk, gift.Context);
@@ -371,6 +362,6 @@ private static CheckResult VerifyEncounterEvent(PKM pk, MysteryGift gift)
}
// Strict matching already performed by EncounterGenerator. May be worth moving some checks here to better flag invalid gifts.
return GetValid(string.Format(L_XMatches0_1, gift.CardHeader, string.Empty));
return GetValid(Valid);
}
}

View File

@@ -1,5 +1,5 @@
using static PKHeX.Core.EvolutionRestrictions;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -8,7 +8,7 @@ namespace PKHeX.Core;
/// </summary>
public static class EvolutionVerifier
{
private static readonly CheckResult VALID = new(CheckIdentifier.Evolution);
private static readonly CheckResult VALID = CheckResult.GetValid(CheckIdentifier.Evolution);
/// <summary>
/// Verifies Evolution scenarios of <see cref="IEncounterable"/> templates for an input <see cref="PKM"/> and relevant <see cref="LegalInfo"/>.
@@ -19,11 +19,11 @@ public static CheckResult VerifyEvolution(PKM pk, LegalInfo info)
{
// Check if basic evolution methods are satisfiable with this encounter.
if (!IsValidEvolution(pk, info.EvoChainsAllGens, info.EncounterOriginal))
return new CheckResult(Severity.Invalid, CheckIdentifier.Evolution, LEvoInvalid);
return CheckResult.Get(Severity.Invalid, CheckIdentifier.Evolution, EvoInvalid);
// Check if complex evolution methods are satisfiable with this encounter.
if (!IsValidEvolutionWithMove(pk, info))
return new CheckResult(Severity.Invalid, CheckIdentifier.Evolution, string.Format(LMoveEvoFCombination_0, ParseSettings.SpeciesStrings[pk.Species]));
return CheckResult.Get(Severity.Invalid, CheckIdentifier.Evolution, MoveEvoFCombination_0, pk.Species);
return VALID;
}

View File

@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
@@ -38,24 +38,24 @@ public static CheckResult VerifyGift(PKM pk, MysteryGift g)
{
bool restricted = TryGetRestriction(g, out var value);
if (!restricted)
return new CheckResult(CheckIdentifier.GameOrigin);
return CheckResult.GetValid(CheckIdentifier.GameOrigin);
var version = (int)value >> 16;
if (version != 0 && !CanVersionReceiveGift(g.Generation, version, pk.Version))
return new CheckResult(Severity.Invalid, CheckIdentifier.GameOrigin, LEncGiftVersionNotDistributed);
return CheckResult.Get(Severity.Invalid, CheckIdentifier.GameOrigin, EncGiftVersionNotDistributed);
var lang = value & MysteryGiftRestriction.LangRestrict;
if (lang != 0 && !lang.HasFlag((MysteryGiftRestriction) (1 << pk.Language)))
return new CheckResult(Severity.Invalid, CheckIdentifier.GameOrigin, string.Format(LOTLanguage, lang.GetSuggestedLanguage(), pk.Language));
return CheckResult.Get(Severity.Invalid, CheckIdentifier.GameOrigin, OTLanguageShouldBe_0, (ushort)lang.GetSuggestedLanguage());
if (pk is IRegionOriginReadOnly tr)
{
var region = value & MysteryGiftRestriction.RegionRestrict;
if (region != 0 && !region.HasFlag((MysteryGiftRestriction)((int)MysteryGiftRestriction.RegionBase << tr.ConsoleRegion)))
return new CheckResult(Severity.Invalid, CheckIdentifier.GameOrigin, LGeoHardwareRange);
return CheckResult.Get(Severity.Invalid, CheckIdentifier.GameOrigin, EncGiftRegionNotDistributed, (ushort)region.GetSuggestedRegion());
}
return new CheckResult(CheckIdentifier.GameOrigin);
return CheckResult.GetValid(CheckIdentifier.GameOrigin);
}
private static bool TryGetRestriction(MysteryGift g, out MysteryGiftRestriction val)

View File

@@ -82,10 +82,10 @@ public EvolutionCheckResult Check(PKM pk, byte lvl, byte levelMin, bool skipChec
// Version checks come in pairs, check for any pair match
LevelUpVersion or LevelUpVersionDay or LevelUpVersionNight when (((byte)pk.Version & 1) != (Argument & 1) && pk.IsUntraded) => skipChecks ? Valid : VisitVersion,
LevelUpKnowMoveEC100 when pk.EncryptionConstant % 100 != 0 => skipChecks ? Valid : WrongEC,
LevelUpKnowMoveECElse when pk.EncryptionConstant % 100 == 0 => skipChecks ? Valid : WrongEC,
LevelUpInBattleEC100 when pk.EncryptionConstant % 100 != 0 => skipChecks ? Valid : WrongEC,
LevelUpInBattleECElse when pk.EncryptionConstant % 100 == 0 => skipChecks ? Valid : WrongEC,
LevelUpKnowMoveEC100 when !EvolutionRestrictions.IsEvolvedSpeciesFormRare(pk.EncryptionConstant) => skipChecks ? Valid : WrongEC,
LevelUpKnowMoveECElse when EvolutionRestrictions.IsEvolvedSpeciesFormRare(pk.EncryptionConstant) => skipChecks ? Valid : WrongEC,
LevelUpInBattleEC100 when !EvolutionRestrictions.IsEvolvedSpeciesFormRare(pk.EncryptionConstant) => skipChecks ? Valid : WrongEC,
LevelUpInBattleECElse when EvolutionRestrictions.IsEvolvedSpeciesFormRare(pk.EncryptionConstant) => skipChecks ? Valid : WrongEC,
_ => Valid,
};

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core;
@@ -12,49 +11,52 @@ public sealed class BaseLegalityFormatter : ILegalityFormatter
/// <summary>
/// Gets a minimal report string for the analysis.
/// </summary>
public string GetReport(LegalityAnalysis l)
public string GetReport(in LegalityLocalizationContext la)
{
var l = la.Analysis;
if (l.Valid)
return L_ALegal;
return la.Settings.Lines.Legal;
if (!l.Parsed)
return L_AnalysisUnavailable;
return la.Settings.Lines.AnalysisUnavailable;
List<string> lines = [];
GetLegalityReportLines(l, lines);
GetLegalityReportLines(la, lines);
return string.Join(Environment.NewLine, lines);
}
/// <summary>
/// Gets a verbose report string for the analysis.
/// </summary>
public string GetReportVerbose(LegalityAnalysis l)
public string GetReportVerbose(in LegalityLocalizationContext la)
{
var l = la.Analysis;
if (!l.Parsed)
return L_AnalysisUnavailable;
return la.Settings.Lines.AnalysisUnavailable;
var lines = GetVerboseLegalityReportLines(l);
var lines = GetVerboseLegalityReportLines(la);
return string.Join(Environment.NewLine, lines);
}
private static void GetLegalityReportLines(LegalityAnalysis l, List<string> lines)
private static void GetLegalityReportLines(in LegalityLocalizationContext la, List<string> lines)
{
var l = la.Analysis;
var info = l.Info;
var pk = l.Entity;
var evos = info.EvoChainsAllGens;
LegalityFormatting.AddMoves(info.Moves, lines, pk.Format, false, pk, evos);
LegalityFormatting.AddMoves(la, info.Moves, lines, pk.Format, false);
if (pk.Format >= 6)
LegalityFormatting.AddRelearn(info.Relearn, lines, false, pk, evos);
LegalityFormatting.AddSecondaryChecksInvalid(l.Results, lines);
LegalityFormatting.AddRelearn(la, info.Relearn, lines, false);
LegalityFormatting.AddSecondaryChecksInvalid(la, l.Results, lines);
}
private static List<string> GetVerboseLegalityReportLines(LegalityAnalysis l)
private static List<string> GetVerboseLegalityReportLines(in LegalityLocalizationContext la)
{
var l = la.Analysis;
var lines = new List<string>();
if (l.Valid)
lines.Add(L_ALegal);
lines.Add(la.Settings.Lines.Legal);
else
GetLegalityReportLines(l, lines);
GetLegalityReportLines(la, lines);
var info = l.Info;
var pk = l.Entity;
const string separator = "===";
@@ -63,20 +65,19 @@ private static List<string> GetVerboseLegalityReportLines(LegalityAnalysis l)
int initialCount = lines.Count;
var format = pk.Format;
var evos = info.EvoChainsAllGens;
LegalityFormatting.AddMoves(info.Moves, lines, format, true, pk, evos);
LegalityFormatting.AddMoves(la, info.Moves, lines, format, true);
if (format >= 6)
LegalityFormatting.AddRelearn(info.Relearn, lines, true, pk, evos);
LegalityFormatting.AddRelearn(la, info.Relearn, lines, true);
if (lines.Count != initialCount) // move info added, break for next section
lines.Add(string.Empty);
LegalityFormatting.AddSecondaryChecksValid(l.Results, lines);
LegalityFormatting.AddSecondaryChecksValid(la, l.Results, lines);
lines.Add(separator);
lines.Add(string.Empty);
LegalityFormatting.AddEncounterInfo(l, lines);
LegalityFormatting.AddEncounterInfo(la, lines);
return lines;
}

View File

@@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary>
/// Formats legality results into a <see cref="T:System.String"/> for display.
@@ -8,10 +8,10 @@ public interface ILegalityFormatter
/// <summary>
/// Gets a small summary of the legality analysis.
/// </summary>
string GetReport(LegalityAnalysis l);
string GetReport(in LegalityLocalizationContext l);
/// <summary>
/// Gets a verbose summary of the legality analysis.
/// </summary>
string GetReportVerbose(LegalityAnalysis l);
string GetReportVerbose(in LegalityLocalizationContext l);
}

View File

@@ -1,523 +0,0 @@
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
using System.Diagnostics.CodeAnalysis;
namespace PKHeX.Core;
/// <summary>
/// Legality Check Message Strings to indicate why certain <see cref="PKM"/> <see cref="LegalInfo"/> values are flagged.
/// </summary>
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public static class LegalityCheckStrings
{
// Message String Name format: L/F[Category][Summary]
#region General Strings
/// <summary>Default text for indicating validity.</summary>
public static string L_AValid { get; set; } = "Valid.";
/// <summary>Default text for indicating legality.</summary>
public static string L_ALegal { get; set; } = "Legal!";
/// <summary>Default text for indicating an error has occurred.</summary>
public static string L_AError { get; set; } = "Internal error.";
/// <summary>Analysis not available for the <see cref="PKM"/></summary>
public static string L_AnalysisUnavailable { get; set; } = "Analysis not available for this Pokémon.";
/// <summary>Format text for exporting a legality check result.</summary>
public static string L_F0_1 { get; set; } = "{0}: {1}";
/// <summary>Format text for exporting a legality check result for a Move.</summary>
public static string L_F0_M_1_2 { get; set; } = "{0} Move {1}: {2}";
/// <summary>Format text for exporting a legality check result for a Relearn Move.</summary>
public static string L_F0_RM_1_2 { get; set; } = "{0} Relearn Move {1}: {2}";
/// <summary>Format text for exporting the type of Encounter that was matched for the <see cref="PKM"/></summary>
public static string L_FEncounterType_0 { get; set; } = "Encounter Type: {0}";
/// <summary>Format text for exporting the <see cref="PIDIV.OriginSeed"/> that was matched for the <see cref="PKM"/></summary>
public static string L_FOriginSeed_0 { get; set; } = "Origin Seed: {0}";
/// <summary>Format text for exporting the <see cref="PIDIV.Type"/> that was matched for the <see cref="PKM"/></summary>
public static string L_FPIDType_0 { get; set; } = "PID Type: {0}";
/// <summary>Severity string for <see cref="Severity.Invalid"/></summary>
public static string L_SInvalid { get; set; } = "Invalid";
/// <summary>Severity string for <see cref="Severity.Fishy"/></summary>
public static string L_SFishy { get; set; } = "Fishy";
/// <summary>Severity string for <see cref="Severity.Valid"/></summary>
public static string L_SValid { get; set; } = "Valid";
/// <summary>Severity string for anything not implemented.</summary>
public static string L_SNotImplemented { get; set; } = "Not Implemented";
public static string L_XOT { get; set; } = "OT";
public static string L_XHT { get; set; } = "HT";
public static string L_XNickname { get; set; } = "Nickname";
public static string L_XKorean { get; set; } = "Korean";
public static string L_XKoreanNon { get; set; } = "Non-Korean";
public static string L_XLocation { get; set; } = "Location";
public static string L_XEnigmaBerry_0 { get; set; } = "{0} Berry";
public static string L_XMatches0_1 { get; set; } = "Matches: {0} {1}";
public static string L_XWurmpleEvo_0 { get; set; } = "Wurmple Evolution: {0}";
public static string L_XRareFormEvo_0_1 { get; set; } = "Evolves into form: {0} (rare: {1})";
public static string LAbilityCapsuleUsed { get; set; } = "Ability available with Ability Capsule.";
public static string LAbilityPatchUsed { get; set; } = "Ability available with Ability Patch.";
public static string LAbilityPatchRevertUsed { get; set; } = "Ability available with Ability Patch Revert.";
public static string LAbilityFlag { get; set; } = "Ability matches ability number.";
public static string LAbilityHiddenFail { get; set; } = "Hidden Ability mismatch for encounter type.";
public static string LAbilityHiddenUnavailable { get; set; } = "Hidden Ability not available.";
public static string LAbilityMismatch { get; set; } = "Ability mismatch for encounter.";
public static string LAbilityMismatch3 { get; set; } = "Ability does not match Generation 3 species ability.";
public static string LAbilityMismatchFlag { get; set; } = "Ability does not match ability number.";
public static string LAbilityMismatchGift { get; set; } = "Ability does not match Mystery Gift.";
public static string LAbilityMismatchGrotto { get; set; } = "Hidden Grotto captures should have Hidden Ability.";
public static string LAbilityMismatchHordeSafari { get; set; } = "Hidden Ability on non-horde/friend safari wild encounter.";
public static string LAbilityMismatchPID { get; set; } = "Ability does not match PID.";
public static string LAbilityMismatchSOS { get; set; } = "Hidden Ability on non-SOS wild encounter.";
public static string LAbilityUnexpected { get; set; } = "Ability is not valid for species/form.";
public static string LAwakenedCap { get; set; } = "Individual AV cannot be greater than {0}.";
public static string LAwakenedShouldBeValue { get; set; } = "Individual AV ({1}) should be greater than {0}.";
public static string LBallAbility { get; set; } = "Can't obtain Hidden Ability with Ball.";
public static string LBallEggCherish { get; set; } = "Can't have Cherish Ball for regular Egg.";
public static string LBallEggMaster { get; set; } = "Can't have Master Ball for regular Egg.";
public static string LBallEnc { get; set; } = "Correct ball for encounter type.";
public static string LBallEncMismatch { get; set; } = "Can't have ball for encounter type.";
public static string LBallHeavy { get; set; } = "Can't have Heavy Ball for light, low-catch rate species (Gen VII).";
public static string LBallNone { get; set; } = "No check satisfied, assuming illegal.";
public static string LBallSpecies { get; set; } = "Can't obtain species in Ball.";
public static string LBallSpeciesPass { get; set; } = "Ball possible for species.";
public static string LBallUnavailable { get; set; } = "Ball unobtainable in origin Generation.";
public static string LContestZero { get; set; } = "Contest Stats should be 0.";
public static string LContestZeroSheen { get; set; } = "Contest Stat Sheen should be 0.";
public static string LContestSheenTooLow_0 { get; set; } = "Contest Stat Sheen should be >= {0}.";
public static string LContestSheenTooHigh_0 { get; set; } = "Contest Stat Sheen should be <= {0}.";
public static string LDateOutsideConsoleWindow { get; set; } = "Local Date is outside of console's local time window.";
public static string LDateTimeClockInvalid { get; set; } = "Local Time is not a valid timestamp.";
public static string LDateOutsideDistributionWindow { get; set; } = "Met Date is outside of distribution window.";
public static string LEggContest { get; set; } = "Cannot increase Contest Stats of an Egg.";
public static string LEggEXP { get; set; } = "Eggs cannot receive experience.";
public static string LEggFMetLevel_0 { get; set; } = "Invalid Met Level, expected {0}.";
public static string LEggHatchCycles { get; set; } = "Invalid Egg hatch cycles.";
public static string LEggLocation { get; set; } = "Able to hatch an Egg at Met Location.";
public static string LEggLocationInvalid { get; set; } = "Can't hatch an Egg at Met Location.";
public static string LEggLocationNone { get; set; } = "Invalid Egg Location, expected none.";
public static string LEggLocationPalPark { get; set; } = "Invalid Met Location, expected Pal Park.";
public static string LEggLocationTrade { get; set; } = "Able to hatch a traded Egg at Met Location.";
public static string LEggLocationTradeFail { get; set; } = "Invalid Egg Location, shouldn't be 'traded' while an Egg.";
public static string LEggMetLocationFail { get; set; } = "Can't obtain Egg from Egg Location.";
public static string LEggNature { get; set; } = "Eggs cannot have their Stat Nature changed.";
public static string LEggPokeathlon { get; set; } = "Eggs cannot have Pokéathlon stats.";
public static string LEggPokerus { get; set; } = "Eggs cannot be infected with Pokérus.";
public static string LEggPP { get; set; } = "Eggs cannot have modified move PP counts.";
public static string LEggPPUp { get; set; } = "Cannot apply PP Ups to an Egg.";
public static string LEggRelearnFlags { get; set; } = "Expected no Relearn Move Flags.";
public static string LEggShinyLeaf { get; set; } = "Eggs cannot have Shiny Leaf/Crown.";
public static string LEggShinyPokeStar { get; set; } = "Eggs cannot be a Pokéstar Studios star.";
public static string LEggSpecies { get; set; } = "Can't obtain Egg for this species.";
public static string LEggUnhatched { get; set; } = "Valid un-hatched Egg.";
public static string LEncCondition { get; set; } = "Valid Wild Encounter at location.";
public static string LEncConditionBadRNGFrame { get; set; } = "Unable to match encounter conditions to a possible RNG frame.";
public static string LEncConditionBadSpecies { get; set; } = "Species does not exist in origin game.";
public static string LEncConditionBlack { get; set; } = "Valid Wild Encounter at location (Black Flute).";
public static string LEncConditionBlackLead { get; set; } = "Valid Wild Encounter at location (Black Flute & Pressure/Hustle/Vital Spirit).";
public static string LEncConditionDexNav { get; set; } = "Valid Wild Encounter at location (DexNav).";
public static string LEncConditionLead { get; set; } = "Valid Wild Encounter at location (Pressure/Hustle/Vital Spirit).";
public static string LEncConditionWhite { get; set; } = "Valid Wild Encounter at location (White Flute).";
public static string LEncConditionWhiteLead { get; set; } = "Valid Wild Encounter at location (White Flute & Pressure/Hustle/Vital Spirit).";
public static string LEncGift { get; set; } = "Unable to match a gift Egg encounter from origin game.";
public static string LEncGiftEggEvent { get; set; } = "Unable to match an event Egg encounter from origin game.";
public static string LEncGiftIVMismatch { get; set; } = "IVs do not match Mystery Gift Data.";
public static string LEncGiftNicknamed { get; set; } = "Event gift has been nicknamed.";
public static string LEncGiftNotFound { get; set; } = "Unable to match to a Mystery Gift in the database.";
public static string LEncGiftPIDMismatch { get; set; } = "Mystery Gift fixed PID mismatch.";
public static string LEncGiftShinyMismatch { get; set; } = "Mystery Gift shiny mismatch.";
public static string LEncGiftVersionNotDistributed { get; set; } = "Mystery Gift cannot be received by this version.";
public static string LEncInvalid { get; set; } = "Unable to match an encounter from origin game.";
public static string LEncMasteryInitial { get; set; } = "Initial move mastery flags do not match the encounter's expected state.";
public static string LEncTradeChangedNickname { get; set; } = "In-game Trade Nickname has been altered.";
public static string LEncTradeChangedOT { get; set; } = "In-game Trade OT has been altered.";
public static string LEncTradeIndexBad { get; set; } = "In-game Trade invalid index?";
public static string LEncTradeMatch { get; set; } = "Valid In-game trade.";
public static string LEncTradeUnchanged { get; set; } = "In-game Trade OT and Nickname have not been altered.";
public static string LEncStaticMatch { get; set; } = "Valid gift/static encounter.";
public static string LEncStaticPIDShiny { get; set; } = "Static Encounter shiny mismatch.";
public static string LEncStaticRelearn { get; set; } = "Static encounter relearn move mismatch.";
public static string LEncTypeMatch { get; set; } = "Encounter Type matches encounter.";
public static string LEncTypeMismatch { get; set; } = "Encounter Type does not match encounter.";
public static string LEncUnreleased { get; set; } = "Unreleased event.";
public static string LEncUnreleasedEMewJP { get; set; } = "Non japanese Mew from Faraway Island. Unreleased event.";
public static string LEncUnreleasedHoOArceus { get; set; } = "Arceus from Hall of Origin. Unreleased event.";
public static string LEncUnreleasedPtDarkrai { get; set; } = "Non Platinum Darkrai from Newmoon Island. Unreleased event.";
public static string LEncUnreleasedPtShaymin { get; set; } = "Non Platinum Shaymin from Flower Paradise. Unreleased event.";
public static string LEReaderAmerica { get; set; } = "American E-Reader Berry in Japanese save file.";
public static string LEReaderInvalid { get; set; } = "Invalid E-Reader Berry.";
public static string LEReaderJapan { get; set; } = "Japanese E-Reader Berry in international save file.";
public static string LEffort2Remaining { get; set; } = "2 EVs remaining.";
public static string LEffortAbove252 { get; set; } = "EVs cannot go above 252.";
public static string LEffortAbove510 { get; set; } = "EV total cannot be above 510.";
public static string LEffortAllEqual { get; set; } = "EVs are all equal.";
public static string LEffortCap100 { get; set; } = "Individual EV for a level 100 encounter in Generation 4 cannot be greater than 100.";
public static string LEffortEgg { get; set; } = "Eggs cannot receive EVs.";
public static string LEffortShouldBeZero { get; set; } = "Cannot receive EVs.";
public static string LEffortEXPIncreased { get; set; } = "All EVs are zero, but leveled above Met Level.";
public static string LEffortUntrainedCap { get; set; } = "Individual EV without changing EXP cannot be greater than {0}.";
public static string LEvoInvalid { get; set; } = "Evolution not valid (or level/trade evolution unsatisfied).";
public static string LEvoTradeReq { get; set; } = "In-game trade {0} should have evolved into {1}.";
public static string LEvoTradeReqOutsider { get; set; } = "Outsider {0} should have evolved into {1}.";
public static string LEvoTradeRequired { get; set; } = "Version Specific evolution requires a trade to opposite version. A Handling Trainer is required.";
public static string LFateful { get; set; } = "Special In-game Fateful Encounter.";
public static string LFatefulGiftMissing { get; set; } = "Fateful Encounter with no matching Encounter. Has the Mystery Gift data been contributed?";
public static string LFatefulInvalid { get; set; } = "Fateful Encounter should not be checked.";
public static string LFatefulMissing { get; set; } = "Special In-game Fateful Encounter flag missing.";
public static string LFatefulMystery { get; set; } = "Mystery Gift Fateful Encounter.";
public static string LFatefulMysteryMissing { get; set; } = "Mystery Gift Fateful Encounter flag missing.";
public static string LFavoriteMarkingUnavailable { get; set; } = "Favorite Marking is not available.";
public static string LFormArgumentHigh { get; set; } = "Form argument is too high for current form.";
public static string LFormArgumentLow { get; set; } = "Form argument is too low for current form.";
public static string LFormArgumentNotAllowed { get; set; } = "Form argument is not allowed for this encounter.";
public static string LFormArgumentValid { get; set; } = "Form argument is valid.";
public static string LFormArgumentInvalid { get; set; } = "Form argument is not valid.";
public static string LFormBattle { get; set; } = "Form cannot exist outside of a battle.";
public static string LFormEternal { get; set; } = "Valid Eternal Flower encounter.";
public static string LFormEternalInvalid { get; set; } = "Invalid Eternal Flower encounter.";
public static string LFormInvalidGame { get; set; } = "Form cannot be obtained in origin game.";
public static string LFormInvalidNature { get; set; } = "Form cannot have this nature.";
public static string LFormInvalidRange { get; set; } = "Form Count is out of range. Expected <= {0}, got {1}.";
public static string LFormItem { get; set; } = "Held item matches Form.";
public static string LFormItemInvalid { get; set; } = "Held item does not match Form.";
public static string LFormParty { get; set; } = "Form cannot exist outside of Party.";
public static string LFormPikachuCosplay { get; set; } = "Only Cosplay Pikachu can have this form.";
public static string LFormPikachuCosplayInvalid { get; set; } = "Cosplay Pikachu cannot have the default form.";
public static string LFormPikachuEventInvalid { get; set; } = "Event Pikachu cannot have the default form.";
public static string LFormInvalidExpect_0 { get; set; } = "Form is invalid, expected form index {0}.";
public static string LFormValid { get; set; } = "Form is Valid.";
public static string LFormVivillon { get; set; } = "Valid Vivillon pattern.";
public static string LFormVivillonEventPre { get; set; } = "Event Vivillon pattern on pre-evolution.";
public static string LFormVivillonInvalid { get; set; } = "Invalid Vivillon pattern.";
public static string LFormVivillonNonNative { get; set; } = "Non-native Vivillon pattern.";
public static string LG1CatchRateChain { get; set; } = "Catch rate does not match any species from Pokémon evolution chain.";
public static string LG1CatchRateEvo { get; set; } = "Catch rate match species without encounters. Expected a preevolution catch rate.";
public static string LG1CatchRateItem { get; set; } = "Catch rate does not match a valid held item from Generation 2.";
public static string LG1CatchRateMatchPrevious { get; set; } = "Catch Rate matches a species from Pokémon evolution chain.";
public static string LG1CatchRateMatchTradeback { get; set; } = "Catch rate matches a valid held item from Generation 2.";
public static string LG1CatchRateNone { get; set; } = "Catch rate does not match any species from Pokémon evolution chain or any Generation 2 held items.";
public static string LG1CharNick { get; set; } = "Nickname from Generation 1/2 uses unavailable characters.";
public static string LG1CharOT { get; set; } = "OT from Generation 1/2 uses unavailable characters.";
public static string LG1GBEncounter { get; set; } = "Can't obtain Special encounter in Virtual Console games.";
public static string LG1MoveExclusive { get; set; } = "Generation 1 exclusive move. Incompatible with Non-tradeback moves.";
public static string LG1MoveLearnSameLevel { get; set; } = "Incompatible moves. Learned at the same level in Red/Blue and Yellow.";
public static string LG1MoveTradeback { get; set; } = "Non-tradeback Egg move. Incompatible with Generation 1 exclusive moves.";
public static string LG1OTEvent { get; set; } = "Incorrect RBY event OT Name.";
public static string LG1OTGender { get; set; } = "Female OT from Generation 1/2 is invalid.";
public static string LG1Stadium { get; set; } = "Incorrect Stadium OT.";
public static string LG1StadiumInternational { get; set; } = "Valid International Stadium OT.";
public static string LG1StadiumJapanese { get; set; } = "Valid Japanese Stadium OT.";
public static string LG1TradebackPreEvoMove { get; set; } = "Non-tradeback pre evolution move. Incompatible with Generation 1 exclusive moves.";
public static string LG1Type1Fail { get; set; } = "Invalid Type A, does not match species type.";
public static string LG1Type2Fail { get; set; } = "Invalid Type B, does not match species type.";
public static string LG1TypeMatch1 { get; set; } = "Valid Type A, matches species type.";
public static string LG1TypeMatch2 { get; set; } = "Valid Type B, matches species type.";
public static string LG1TypeMatchPorygon { get; set; } = "Porygon with valid Type A and B values.";
public static string LG1TypePorygonFail { get; set; } = "Porygon with invalid Type A and B values. Does not a match a valid type combination.";
public static string LG1TypePorygonFail1 { get; set; } = "Porygon with invalid Type A value.";
public static string LG1TypePorygonFail2 { get; set; } = "Porygon with invalid Type B value.";
public static string LG2InvalidTilePark { get; set; } = "National Park fishing encounter. Unreachable Water tiles.";
public static string LG2InvalidTileR14 { get; set; } = "Kanto Route 14 fishing encounter. Unreachable Water tiles.";
public static string LG2InvalidTileSafari { get; set; } = "Generation 2 Safari Zone fishing encounter. Unreachable zone.";
public static string LG2InvalidTileTreeID { get; set; } = "Found an unreachable tree for Crystal headbutt encounter that matches OTID.";
public static string LG2InvalidTileTreeNotFound { get; set; } = "Could not find a tree for Crystal headbutt encounter that matches OTID.";
public static string LG2TreeID { get; set; } = "Found a tree for Crystal headbutt encounter that matches OTID.";
public static string LG2OTGender { get; set; } = "OT from Virtual Console games other than Crystal cannot be female.";
public static string LG3EReader { get; set; } = "Non Japanese Shadow E-reader Pokémon. Unreleased encounter.";
public static string LG3OTGender { get; set; } = "OT from Colosseum/XD cannot be female.";
public static string LG4InvalidTileR45Surf { get; set; } = "Johto Route 45 surfing encounter. Unreachable Water tiles.";
public static string LG5ID_N { get; set; } = "The Name/TID16/SID16 of N is incorrect.";
public static string LG5IVAll30 { get; set; } = "All IVs of N's Pokémon should be 30.";
public static string LG5OTGenderN { get; set; } = "N's Pokémon must have a male OT gender.";
public static string LG5PIDShinyGrotto { get; set; } = "Hidden Grotto captures cannot be shiny.";
public static string LG5PIDShinyN { get; set; } = "N's Pokémon cannot be shiny.";
public static string LG5SparkleInvalid { get; set; } = "Special In-game N's Sparkle flag should not be checked.";
public static string LG5SparkleRequired { get; set; } = "Special In-game N's Sparkle flag missing.";
public static string LGanbaruStatTooHigh { get; set; } = "One or more Ganbaru Value is above the natural limit of (10 - IV bonus).";
public static string LGenderInvalidNone { get; set; } = "Genderless Pokémon should not have a gender.";
public static string LGeoBadOrder { get; set; } = "GeoLocation Memory: Gap/Blank present.";
public static string LGeoHardwareInvalid { get; set; } = "Geolocation: Country is not in 3DS region.";
public static string LGeoHardwareRange { get; set; } = "Invalid Console Region.";
public static string LGeoHardwareValid { get; set; } = "Geolocation: Country is in 3DS region.";
public static string LGeoMemoryMissing { get; set; } = "GeoLocation Memory: Memories should be present.";
public static string LGeoNoCountryHT { get; set; } = "GeoLocation Memory: HT Name present but has no previous Country.";
public static string LGeoNoRegion { get; set; } = "GeoLocation Memory: Region without Country.";
public static string LHyperTooLow_0 { get; set; } = "Can't Hyper Train a Pokémon that isn't level {0}.";
public static string LHyperPerfectAll { get; set; } = "Can't Hyper Train a Pokémon with perfect IVs.";
public static string LHyperPerfectOne { get; set; } = "Can't Hyper Train a perfect IV.";
public static string LHyperPerfectUnavailable { get; set; } = "Can't Hyper Train any IV(s).";
public static string LItemEgg { get; set; } = "Eggs cannot hold items.";
public static string LItemUnreleased { get; set; } = "Held item is unreleased.";
public static string LIVAllEqual_0 { get; set; } = "All IVs are {0}.";
public static string LIVNotCorrect { get; set; } = "IVs do not match encounter requirements.";
public static string LIVF_COUNT0_31 { get; set; } = "Should have at least {0} IVs = 31.";
public static string LLevelEXPThreshold { get; set; } = "Current experience matches level threshold.";
public static string LLevelEXPTooHigh { get; set; } = "Current experience exceeds maximum amount for level 100.";
public static string LLevelMetBelow { get; set; } = "Current level is below met level.";
public static string LLevelMetGift { get; set; } = "Met Level does not match Mystery Gift level.";
public static string LLevelMetGiftFail { get; set; } = "Current Level below Mystery Gift level.";
public static string LLevelMetSane { get; set; } = "Current level is not below met level.";
public static string LMarkValueOutOfRange_0 { get; set; } = "Individual marking at index {0} is not within the allowed value range.";
public static string LMarkValueShouldBeZero { get; set; } = "Marking flags cannot be set.";
public static string LMarkValueUnusedBitsPresent { get; set; } = "Marking flags uses bits beyond the accessible range.";
public static string LMemoryArgBadCatch { get; set; } = "{0} Memory: {0} did not catch this.";
public static string LMemoryArgBadHatch { get; set; } = "{0} Memory: {0} did not hatch this.";
public static string LMemoryArgBadHT { get; set; } = "Memory: Can't have Handling Trainer Memory as Egg.";
public static string LMemoryArgBadID { get; set; } = "{0} Memory: Can't obtain Memory on {0} Version.";
public static string LMemoryArgBadItem { get; set; } = "{0} Memory: Species can't hold this item.";
public static string LMemoryArgBadLocation { get; set; } = "{0} Memory: Can't obtain Location on {0} Version.";
public static string LMemoryArgBadMove { get; set; } = "{0} Memory: Species can't learn this move.";
public static string LMemoryArgBadOTEgg { get; set; } = "{0} Memory: Link Trade is not a valid first memory.";
public static string LMemoryArgBadSpecies { get; set; } = "{0} Memory: Can't capture species in game.";
public static string LMemoryArgSpecies { get; set; } = "{0} Memory: Species can be captured in game.";
public static string LMemoryCleared { get; set; } = "Memory: Not cleared properly.";
public static string LMemoryF_0_Valid { get; set; } = "{0} Memory is valid.";
public static string LMemoryFeelInvalid { get; set; } = "{0} Memory: Invalid Feeling.";
public static string LMemoryHTFlagInvalid { get; set; } = "Untraded: Current handler should not be the Handling Trainer.";
public static string LMemoryHTGender { get; set; } = "HT Gender invalid: {0}";
public static string LMemoryHTLanguage { get; set; } = "HT Language is missing.";
public static string LMemoryIndexArgHT { get; set; } = "Should have a HT Memory TextVar value (somewhere).";
public static string LMemoryIndexFeel { get; set; } = "{0} Memory: Feeling should be index {1}.";
public static string LMemoryIndexFeelHT09 { get; set; } = "Should have a HT Memory Feeling value 0-9.";
public static string LMemoryIndexID { get; set; } = "{0} Memory: Should be index {1}.";
public static string LMemoryIndexIntensity { get; set; } = "{0} Memory: Intensity should be index {1}.";
public static string LMemoryIndexIntensityHT1 { get; set; } = "Should have a HT Memory Intensity value (1st).";
public static string LMemoryIndexIntensityMin { get; set; } = "{0} Memory: Intensity should be at least {1}.";
public static string LMemoryIndexLinkHT { get; set; } = "Should have a Link Trade HT Memory.";
public static string LMemoryIndexVar { get; set; } = "{0} Memory: TextVar should be index {1}.";
public static string LMemoryMissingHT { get; set; } = "Memory: Handling Trainer Memory missing.";
public static string LMemoryMissingOT { get; set; } = "Memory: Original Trainer Memory missing.";
public static string LMemorySocialZero { get; set; } = "Social Stat should be zero.";
public static string LMemorySocialTooHigh_0 { get; set; } = "Social Stat should be <= {0}";
public static string LMemoryStatAffectionHT0 { get; set; } = "Untraded: Handling Trainer Affection should be 0.";
public static string LMemoryStatAffectionOT0 { get; set; } = "OT Affection should be 0.";
public static string LMemoryStatFriendshipHT0 { get; set; } = "Untraded: Handling Trainer Friendship should be 0.";
public static string LMemoryStatFriendshipOTBaseEvent { get; set; } = "Event OT Friendship does not match base friendship.";
public static string LMetDetailTimeOfDay { get; set; } = "Met Time of Day value is not within the expected range.";
public static string LMemoryStatFullness { get; set; } = "Fullness should be {0}.";
public static string LMemoryStatEnjoyment { get; set; } = "Enjoyment should be {0}.";
public static string LMoveEggFIncompatible0_1 { get; set; } = "{0} Inherited Move. Incompatible with {1} inherited moves.";
public static string LMoveEggIncompatible { get; set; } = "Egg Move. Incompatible with event Egg moves.";
public static string LMoveEggIncompatibleEvent { get; set; } = "Event Egg Move. Incompatible with normal Egg moves.";
public static string LMoveEggInherited { get; set; } = "Inherited Egg move.";
public static string LMoveEggInheritedTutor { get; set; } = "Inherited tutor move.";
public static string LMoveEggInvalid { get; set; } = "Not an expected Egg move.";
public static string LMoveEggInvalidEvent { get; set; } = "Egg Move. Not expected in an event Egg.";
public static string LMoveEggInvalidEventLevelUp { get; set; } = "Inherited move learned by Level-up. Not expected in an event Egg.";
public static string LMoveEggInvalidEventLevelUpGift { get; set; } = "Inherited move learned by Level-up. Not expected in a gift Egg.";
public static string LMoveEggInvalidEventTMHM { get; set; } = "Inherited TM/HM move. Not expected in an event Egg.";
public static string LMoveEggInvalidEventTutor { get; set; } = "Inherited tutor move. Not expected in an event Egg.";
public static string LMoveEggLevelUp { get; set; } = "Inherited move learned by Level-up.";
public static string LMoveEggMissing { get; set; } = "Event Egg move missing.";
public static string LMoveEggMoveGift { get; set; } = "Egg Move. Not expected in a gift Egg.";
public static string LMoveEggTMHM { get; set; } = "Inherited TM/HM move.";
public static string LMoveEventEggLevelUp { get; set; } = "Inherited move learned by Level-up. Incompatible with event Egg moves.";
public static string LMoveEvoFCombination_0 { get; set; } = "Moves combinations is not compatible with {0} evolution.";
public static string LMoveEvoFHigher { get; set; } = "Incompatible evolution moves. {1} Move learned at a higher level than other {0} moves.";
public static string LMoveEvoFLower { get; set; } = "Incompatible evolution moves. {0} Move learned at a lower level than other {1} moves.";
public static string LMoveFDefault_0 { get; set; } = "Default move in Generation {0}.";
public static string LMoveFExpect_0 { get; set; } = "Expected the following Moves: {0}";
public static string LMoveFExpectSingle_0 { get; set; } = "Expected: {0}";
public static string LMoveFLevelUp_0 { get; set; } = "Learned by Level-up in Generation {0}.";
public static string LMoveFTMHM_0 { get; set; } = "Learned by TM/HM in Generation {0}.";
public static string LMoveFTutor_0 { get; set; } = "Learned by Move Tutor in Generation {0}.";
public static string LMoveKeldeoMismatch { get; set; } = "Keldeo Move/Form mismatch.";
public static string LMoveNincada { get; set; } = "Only one Ninjask move allowed.";
public static string LMoveNincadaEvo { get; set; } = "Learned by evolving Nincada into Ninjask.";
public static string LMoveNincadaEvoF_0 { get; set; } = "Learned by evolving Nincada into Ninjask in Generation {0}.";
public static string LMovePPExpectHealed_0 { get; set; } = "Move {0} PP is below the amount expected.";
public static string LMovePPTooHigh_0 { get; set; } = "Move {0} PP is above the amount allowed.";
public static string LMovePPUpsTooHigh_0 { get; set; } = "Move {0} PP Ups is above the amount allowed.";
public static string LMoveSourceShared { get; set; } = "Shared Non-Relearn Move.";
public static string LMoveSourceSharedF { get; set; } = "Shared Non-Relearn Move in Generation {0}.";
public static string LMoveRelearnDexNav { get; set; } = "Not an expected DexNav move.";
public static string LMoveRelearnUnderground { get; set; } = "Not an expected Underground egg move.";
public static string LMoveRelearnEgg { get; set; } = "Base Egg move.";
public static string LMoveRelearnEggMissing { get; set; } = "Base Egg move missing.";
public static string LMoveRelearnFExpect_0 { get; set; } = "Expected the following Relearn Moves: {0} ({1})";
public static string LMoveRelearnFMiss_0 { get; set; } = "Relearn Moves missing: {0}";
public static string LMoveRelearnInvalid { get; set; } = "Not an expected Relearnable move.";
public static string LMoveRelearnNone { get; set; } = "Expected no Relearn Move in slot.";
public static string LMoveShopAlphaMoveShouldBeMastered { get; set; } = "Alpha Move should be marked as mastered.";
public static string LMoveShopAlphaMoveShouldBeOther { get; set; } = "Alpha encounter cannot be found with this Alpha Move.";
public static string LMoveShopAlphaMoveShouldBeZero { get; set; } = "Only Alphas may have an Alpha Move set.";
public static string LMoveShopMasterInvalid_0 { get; set; } = "Cannot manually master {0}: not permitted to master.";
public static string LMoveShopMasterNotLearned_0 { get; set; } = "Cannot manually master {0}: not in possible learned level up moves.";
public static string LMoveShopPurchaseInvalid_0 { get; set; } = "Cannot purchase {0} from the move shop.";
public static string LMoveSourceDefault { get; set; } = "Default move.";
public static string LMoveSourceDuplicate { get; set; } = "Duplicate Move.";
public static string LMoveSourceEgg { get; set; } = "Egg Move.";
public static string LMoveSourceEggEvent { get; set; } = "Event Egg Move.";
public static string LMoveSourceEmpty { get; set; } = "Empty Move.";
public static string LMoveSourceInvalid { get; set; } = "Invalid Move.";
public static string LMoveSourceInvalidSketch { get; set; } = "Invalid Move (Sketch).";
public static string LMoveSourceLevelUp { get; set; } = "Learned by Level-up.";
public static string LMoveSourceRelearn { get; set; } = "Relearnable Move.";
public static string LMoveSourceSpecial { get; set; } = "Special Non-Relearn Move.";
public static string LMoveSourceTMHM { get; set; } = "Learned by TM/HM.";
public static string LMoveSourceTutor { get; set; } = "Learned by Move Tutor.";
public static string LMoveSourceTR { get; set; } = "Unexpected Technical Record Learned flag: {0}";
public static string LNickFlagEggNo { get; set; } = "Egg must be not nicknamed.";
public static string LNickFlagEggYes { get; set; } = "Egg must be nicknamed.";
public static string LNickInvalidChar { get; set; } = "Cannot be given this Nickname.";
public static string LNickLengthLong { get; set; } = "Nickname too long.";
public static string LNickLengthShort { get; set; } = "Nickname is empty.";
public static string LNickMatchLanguage { get; set; } = "Nickname matches species name.";
public static string LNickMatchLanguageEgg { get; set; } = "Egg matches language Egg name.";
public static string LNickMatchLanguageEggFail { get; set; } = "Egg name does not match language Egg name.";
public static string LNickMatchLanguageFail { get; set; } = "Nickname does not match species name.";
public static string LNickMatchLanguageFlag { get; set; } = "Nickname flagged, matches species name.";
public static string LNickMatchNoOthers { get; set; } = "Nickname does not match another species name.";
public static string LNickMatchNoOthersFail { get; set; } = "Nickname matches another species name (+language).";
public static string LOTLanguage { get; set; } = "Language ID should be {0}, not {1}.";
public static string LOTLong { get; set; } = "OT Name too long.";
public static string LOTShort { get; set; } = "OT Name too short.";
public static string LOTSuspicious { get; set; } = "Suspicious Original Trainer details.";
public static string LOT_IDEqual { get; set; } = "TID16 and SID16 are equal.";
public static string LOT_IDs0 { get; set; } = "TID16 and SID16 are 0.";
public static string LOT_SID0 { get; set; } = "SID16 is zero.";
public static string LOT_SID0Invalid { get; set; } = "SID16 should be 0.";
public static string LOT_TID0 { get; set; } = "TID16 is zero.";
public static string LOT_IDInvalid { get; set; } = "TID16 and SID16 combination is not possible.";
public static string LPIDEncryptWurmple { get; set; } = "Wurmple evolution Encryption Constant mismatch.";
public static string LPIDEncryptZero { get; set; } = "Encryption Constant is not set.";
public static string LPIDEqualsEC { get; set; } = "Encryption Constant matches PID.";
public static string LPIDGenderMatch { get; set; } = "Gender matches PID.";
public static string LPIDGenderMismatch { get; set; } = "PID-Gender mismatch.";
public static string LPIDNatureMatch { get; set; } = "Nature matches PID.";
public static string LPIDNatureMismatch { get; set; } = "PID-Nature mismatch.";
public static string LPIDTypeMismatch { get; set; } = "PID+ correlation does not match what was expected for the Encounter's type.";
public static string LPIDZero { get; set; } = "PID is not set.";
public static string LPokerusDaysTooHigh_0 { get; set; } = "Pokérus Days Remaining value is too high; expected <= {0}.";
public static string LPokerusStrainUnobtainable_0 { get; set; } = "Pokérus Strain {0} cannot be obtained.";
public static string LRibbonAllValid { get; set; } = "All ribbons accounted for.";
public static string LRibbonEgg { get; set; } = "Can't receive Ribbon(s) as an Egg.";
public static string LRibbonFInvalid_0 { get; set; } = "Invalid Ribbons: ";
public static string LRibbonFMissing_0 { get; set; } = "Missing Ribbons: ";
public static string LRibbonMarkingFInvalid_0 { get; set; } = "Invalid Marking: {0}";
public static string LRibbonMarkingAffixedF_0 { get; set; } = "Invalid Affixed Ribbon/Marking: {0}";
public static string LStatDynamaxInvalid { get; set; } = "Dynamax Level is not within the expected range.";
public static string LStatIncorrectHeight { get; set; } = "Calculated Height does not match stored value.";
public static string LStatIncorrectHeightCopy { get; set; } = "Copy Height does not match the original value.";
public static string LStatIncorrectHeightValue { get; set; } = "Height does not match the expected value.";
public static string LStatIncorrectWeight { get; set; } = "Calculated Weight does not match stored value.";
public static string LStatIncorrectWeightValue { get; set; } = "Weight does not match the expected value.";
public static string LStatInvalidHeightWeight { get; set; } = "Height / Weight values are statistically improbable.";
public static string LStatIncorrectCP { get; set; } = "Calculated CP does not match stored value.";
public static string LStatGigantamaxInvalid { get; set; } = "Gigantamax Flag mismatch.";
public static string LStatGigantamaxValid { get; set; } = "Gigantamax Flag was changed via Max Soup.";
public static string LStatNatureInvalid { get; set; } = "Stat Nature is not within the expected range.";
public static string LStatBattleVersionInvalid { get; set; } = "Battle Version is not within the expected range.";
public static string LStatNobleInvalid { get; set; } = "Noble Flag mismatch.";
public static string LStatAlphaInvalid { get; set; } = "Alpha Flag mismatch.";
public static string LStoredSourceEgg { get; set; } = "Egg must be in Box or Party.";
public static string LStoredSourceInvalid_0 { get; set; } = "Invalid Stored Source: {0}";
public static string LSuperComplete { get; set; } = "Super Training complete flag mismatch.";
public static string LSuperDistro { get; set; } = "Distribution Super Training missions are not released.";
public static string LSuperEgg { get; set; } = "Can't Super Train an Egg.";
public static string LSuperNoComplete { get; set; } = "Can't have active Super Training complete flag for origins.";
public static string LSuperNoUnlocked { get; set; } = "Can't have active Super Training unlocked flag for origins.";
public static string LSuperUnavailable { get; set; } = "Super Training missions are not available in games visited.";
public static string LSuperUnused { get; set; } = "Unused Super Training Flag is flagged.";
public static string LTeraTypeIncorrect { get; set; } = "Tera Type does not match the expected value.";
public static string LTeraTypeMismatch { get; set; } = "Tera Type does not match either of the default types.";
public static string LTradeNotAvailable { get; set; } = "Encounter cannot be traded to the active trainer.";
public static string LTrainerIDNoSeed { get; set; } = "Trainer ID is not obtainable from any RNG seed.";
public static string LTransferBad { get; set; } = "Incorrectly transferred from previous generation.";
public static string LTransferCurrentHandlerInvalid { get; set; } = "Invalid Current handler value, trainer details for save file expected another value.";
public static string LTransferEgg { get; set; } = "Can't transfer Eggs between Generations.";
public static string LTransferEggLocationTransporter { get; set; } = "Invalid Met Location, expected Poké Transfer.";
public static string LTransferEggMetLevel { get; set; } = "Invalid Met Level for transfer.";
public static string LTransferEggVersion { get; set; } = "Can't transfer Eggs to this game.";
public static string LTransferFlagIllegal { get; set; } = "Flagged as illegal by the game (glitch abuse).";
public static string LTransferHTFlagRequired { get; set; } = "Current handler cannot be the OT.";
public static string LTransferHTMismatchName { get; set; } = "Handling trainer does not match the expected trainer name.";
public static string LTransferHTMismatchGender { get; set; } = "Handling trainer does not match the expected trainer gender.";
public static string LTransferHTMismatchLanguage { get; set; } = "Handling trainer does not match the expected trainer language.";
public static string LTransferMet { get; set; } = "Invalid Met Location, expected Poké Transfer or Crown.";
public static string LTransferNotPossible { get; set; } = "Unable to transfer into current format from origin format.";
public static string LTransferMetLocation { get; set; } = "Invalid Transfer Met Location.";
public static string LTransferMove { get; set; } = "Incompatible transfer move.";
public static string LTransferMoveG4HM { get; set; } = "Defog and Whirlpool. One of the two moves should have been removed before transferred to Generation 5.";
public static string LTransferMoveHM { get; set; } = "Generation {0} HM. Should have been removed before transferred to Generation {1}.";
public static string LTransferNature { get; set; } = "Invalid Nature for transfer Experience.";
public static string LTransferObedienceLevel { get; set; } = "Invalid Obedience Level.";
public static string LTransferOriginFInvalid0_1 { get; set; } = "{0} origin cannot exist in the currently loaded ({1}) save file.";
public static string LTransferPIDECBitFlip { get; set; } = "PID should be equal to EC [with top bit flipped]!";
public static string LTransferPIDECEquals { get; set; } = "PID should be equal to EC!";
public static string LTransferPIDECXor { get; set; } = "Encryption Constant matches shinyxored PID.";
public static string LTransferTrackerMissing { get; set; } = "Pokémon HOME Transfer Tracker is missing.";
public static string LTransferTrackerShouldBeZero { get; set; } = "Pokémon HOME Transfer Tracker should be 0.";
public static string LTrashBytesExpected_0 { get; set; } = "Expected Trash Bytes: {0}";
public static string LTrashBytesExpected { get; set; } = "Expected Trash Bytes.";
public static string LTrashBytesMismatchInitial { get; set; } = "Expected initial trash bytes to match the encounter.";
public static string LTrashBytesMissingTerminator { get; set; } = "Final terminator missing.";
public static string LTrashBytesShouldBeEmpty { get; set; } = "Trash Bytes should be cleared.";
public static string LTrashBytesUnexpected { get; set; } = "Unexpected Trash Bytes.";
#endregion
}

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core;
@@ -11,59 +10,70 @@ namespace PKHeX.Core;
/// </summary>
public static class LegalityFormatting
{
public static ILegalityFormatter Formatter { private get; set; } = new BaseLegalityFormatter();
/// <summary>
/// Creates a report message with optional verbosity for in-depth analysis.
/// </summary>
/// <param name="la">Legality result to format</param>
/// <param name="verbose">Include all details in the parse, including valid check messages.</param>
/// <returns>Single line string</returns>
public static string Report(this LegalityAnalysis la, bool verbose = false) => verbose ? GetVerboseLegalityReport(la) : GetLegalityReport(la);
public static ILegalityFormatter Formatter { private get; set; } = new BaseLegalityFormatter();
public static string GetLegalityReport(LegalityAnalysis la) => Formatter.GetReport(la);
public static string GetVerboseLegalityReport(LegalityAnalysis la) => Formatter.GetReportVerbose(la);
public static void AddSecondaryChecksValid(IEnumerable<CheckResult> results, List<string> lines)
public static string Report(this LegalityAnalysis la, bool verbose = false)
{
var outputLines = results
.Where(chk => chk.Valid && chk.Comment != L_AValid)
.OrderBy(chk => chk.Judgement) // Fishy sorted to top
.Select(chk => chk.Format(L_F0_1));
lines.AddRange(outputLines);
var localizer = LegalityLocalizationContext.Create(la);
return Report(localizer, verbose);
}
public static void AddSecondaryChecksInvalid(IReadOnlyList<CheckResult> results, List<string> lines)
/// <inheritdoc cref="Report(LegalityAnalysis, bool)"/>
public static string Report(this LegalityLocalizationContext localizer, bool verbose) => verbose ? GetVerboseLegalityReport(localizer) : GetLegalityReport(localizer);
/// <inheritdoc cref="Report(LegalityAnalysis, bool)"/>
public static string Report(this LegalityAnalysis la, string language, bool verbose = false)
{
var localizer = LegalityLocalizationContext.Create(la, language);
return localizer.Report(verbose);
}
public static string GetLegalityReport(LegalityLocalizationContext la) => Formatter.GetReport(la);
public static string GetVerboseLegalityReport(LegalityLocalizationContext la) => Formatter.GetReportVerbose(la);
public static void AddSecondaryChecksValid(LegalityLocalizationContext la, IEnumerable<CheckResult> results, List<string> lines)
{
var outputLines = results
.Where(chk => chk.Valid && chk.IsNotGeneric())
.OrderBy(chk => chk.Judgement); // Fishy sorted to top
foreach (var chk in outputLines)
lines.Add(la.Humanize(chk));
}
public static void AddSecondaryChecksInvalid(LegalityLocalizationContext la, IReadOnlyList<CheckResult> results, List<string> lines)
{
foreach (var chk in results)
{
if (chk.Valid)
continue;
lines.Add(chk.Format(L_F0_1));
lines.Add(la.Humanize(chk));
}
}
public static void AddRelearn(ReadOnlySpan<MoveResult> relearn, List<string> lines, bool state, PKM pk, EvolutionHistory history)
public static void AddRelearn(LegalityLocalizationContext la, ReadOnlySpan<MoveResult> relearn, List<string> lines, bool state)
{
for (int i = 0; i < relearn.Length; i++)
{
var move = relearn[i];
if (move.Valid == state)
lines.Add(move.Format(L_F0_RM_1_2, i + 1, pk, history));
lines.Add(la.FormatRelearn(move, i + 1));
}
}
public static void AddMoves(ReadOnlySpan<MoveResult> moves, List<string> lines, in int currentFormat, bool state, PKM pk, EvolutionHistory history)
public static void AddMoves(LegalityLocalizationContext la, ReadOnlySpan<MoveResult> moves, List<string> lines, in byte currentFormat, bool state)
{
for (int i = 0; i < moves.Length; i++)
{
var move = moves[i];
if (move.Valid != state)
continue;
var msg = move.Format(L_F0_M_1_2, i + 1, pk, history);
var gen = move.Generation;
if (currentFormat != gen && gen != 0)
msg += $" [Gen{gen}]";
var msg = la.FormatMove(move, i + 1, currentFormat);
lines.Add(msg);
}
}
@@ -71,41 +81,44 @@ public static void AddMoves(ReadOnlySpan<MoveResult> moves, List<string> lines,
/// <summary>
/// Adds information about the <see cref="LegalityAnalysis.EncounterMatch"/> to the <see cref="lines"/>.
/// </summary>
public static void AddEncounterInfo(LegalityAnalysis la, List<string> lines)
public static void AddEncounterInfo(LegalityLocalizationContext l, List<string> lines)
{
var la = l.Analysis;
var enc = la.EncounterOriginal;
var display = l.Settings.Encounter;
// Name
lines.Add(string.Format(L_FEncounterType_0, enc.GetEncounterName()));
lines.Add(string.Format(display.Format, display.EncounterType, enc.GetEncounterName(l.Strings.specieslist)));
if (enc is MysteryGift g)
lines.Add(g.CardHeader);
// Location
var loc = enc.GetEncounterLocation();
if (!string.IsNullOrEmpty(loc))
lines.Add(string.Format(L_F0_1, L_XLocation, loc));
lines.Add(string.Format(display.Format, display.Location, loc));
// Version
if (enc.Generation <= 2)
lines.Add(string.Format(L_F0_1, nameof(GameVersion), enc.Version));
lines.Add(string.Format(display.Format, display.Version, enc.Version));
// PID/IV
AddEncounterInfoPIDIV(la, lines);
AddEncounterInfoPIDIV(l, lines);
}
public static void AddEncounterInfoPIDIV(LegalityAnalysis la, List<string> lines)
public static void AddEncounterInfoPIDIV(LegalityLocalizationContext l, List<string> lines)
{
var strings = l.Settings;
var la = l.Analysis;
var info = la.Info;
if (!info.PIDParsed)
info.PIDIV = MethodFinder.Analyze(la.Entity);
AddEncounterInfoPIDIV(lines, info);
AddEncounterInfoPIDIV(strings, lines, info);
}
private static void AddEncounterInfoPIDIV(List<string> lines, LegalInfo info)
private static void AddEncounterInfoPIDIV(LegalityLocalizationSet strings, List<string> lines, LegalInfo info)
{
var pidiv = info.PIDIV;
var type = pidiv.Type;
var msgType = string.Format(L_FPIDType_0, type);
var msgType = string.Format(strings.Encounter.Format, strings.Encounter.PIDType, type);
var enc = info.EncounterOriginal;
if (enc is IRandomCorrelationEvent3 r3)
{
@@ -128,7 +141,7 @@ private static void AddEncounterInfoPIDIV(List<string> lines, LegalInfo info)
{
if (type is not PIDType.Pokewalker)
return;
var line = GetLinePokewalkerSeed(info);
var line = GetLinePokewalkerSeed(info, strings);
lines.Add(line);
}
else if (enc is PCD pcd)
@@ -137,7 +150,7 @@ private static void AddEncounterInfoPIDIV(List<string> lines, LegalInfo info)
if (gift is { HasPID: false }) // tick rand
{
var ticks = ARNG.Prev(info.Entity.EncryptionConstant);
var line = string.Format(L_FOriginSeed_0, ticks.ToString("X8"));
var line = string.Format(strings.Encounter.Format, strings.Encounter.OriginSeed, ticks.ToString("X8"));
line += $" [{ticks / 524_288f:F2}]"; // seconds?
lines.Add(line);
}
@@ -151,7 +164,7 @@ private static void AddEncounterInfoPIDIV(List<string> lines, LegalInfo info)
var initial = ClassicEraRNG.SeekInitialSeedForIVs(ivs, (uint)date.Year, (uint)date.Month, (uint)date.Day, out var origin);
var components = ClassicEraRNG.DecomposeSeed(initial, (uint)date.Year, (uint)date.Month, (uint)date.Day);
AppendInitialDateTime4(lines, initial, origin, components);
AppendInitialDateTime4(lines, initial, origin, components, strings.Encounter);
if (components.IsInvalid())
lines.Add("INVALID");
}
@@ -160,13 +173,13 @@ private static void AddEncounterInfoPIDIV(List<string> lines, LegalInfo info)
{
if (Daycare3.TryGetOriginSeed(info.Entity, out var day3))
{
var line = string.Format(L_FOriginSeed_0, day3.Origin.ToString("X8"));
var line = string.Format(strings.Encounter.Format, strings.Encounter.OriginSeed, day3.Origin.ToString("X8"));
lines.Add(line);
lines.Add($"Initial: 0x{day3.Initial:X8}, Frame: {day3.Advances + 1}"); // frames are 1-indexed
lines.Add(string.Format(strings.Encounter.FrameInitial, day3.Initial.ToString("X8"), day3.Advances + 1)); // frames are 1-indexed
var sb = new StringBuilder();
AppendFrameTimeStamp3(day3.Advances, sb);
lines.Add($"Time: {sb}");
AppendFrameTimeStamp3(day3.Advances, sb, strings.Encounter);
lines.Add(string.Format(strings.Encounter.Format, strings.Encounter.Time, sb));
}
}
return;
@@ -174,43 +187,43 @@ private static void AddEncounterInfoPIDIV(List<string> lines, LegalInfo info)
if (pidiv.IsSeed64())
{
var line = string.Format(L_FOriginSeed_0, pidiv.Seed64.ToString("X16"));
var line = string.Format(strings.Encounter.Format, strings.Encounter.OriginSeed, pidiv.Seed64.ToString("X16"));
lines.Add(line);
return;
}
if (enc is IEncounterSlot34 s)
{
var line = GetLineSlot34(info, pidiv, s);
var line = GetLineSlot34(info, strings, pidiv, s);
lines.Add(line);
}
else
{
var seed = pidiv.OriginSeed;
var line = string.Format(L_FOriginSeed_0, seed.ToString("X8"));
var line = string.Format(strings.Encounter.Format, strings.Encounter.OriginSeed, seed.ToString("X8"));
if (pidiv.Mutated is not 0 && pidiv.OriginSeed != pidiv.EncounterSeed)
line += $" [{pidiv.EncounterSeed:X8}]";
lines.Add(line);
}
if (enc is EncounterSlot3 or EncounterStatic3)
AppendDetailsFrame3(info, lines);
AppendDetailsFrame3(info, lines, strings.Encounter);
else if (enc is EncounterSlot4 or EncounterStatic4)
AppendDetailsDate4(info, lines);
AppendDetailsDate4(info, lines, strings.Encounter);
}
private static string GetLinePokewalkerSeed(LegalInfo info)
private static string GetLinePokewalkerSeed(LegalInfo info, LegalityLocalizationSet strings)
{
var pk = info.Entity;
var result = PokewalkerRNG.GetLeastEffortSeed((uint)pk.IV_HP, (uint)pk.IV_ATK, (uint)pk.IV_DEF, (uint)pk.IV_SPA, (uint)pk.IV_SPD, (uint)pk.IV_SPE);
var line = string.Format(L_FOriginSeed_0, result.Seed.ToString("X8"));
var line = string.Format(strings.Encounter.Format, strings.Encounter.OriginSeed, result.Seed.ToString("X8"));
line += $" [{result.Type} @ {result.PriorPoke}]";
return line;
}
private static string GetLineSlot34(LegalInfo info, PIDIV pidiv, IEncounterSlot34 s)
private static string GetLineSlot34(LegalInfo info, LegalityLocalizationSet strings, PIDIV pidiv, IEncounterSlot34 s)
{
var lead = pidiv.Lead;
var seed = !info.FrameMatches || lead == LeadRequired.Invalid ? pidiv.OriginSeed : pidiv.EncounterSeed;
var line = string.Format(L_FOriginSeed_0, seed.ToString("X8"));
var line = string.Format(strings.Encounter.Format, strings.Encounter.OriginSeed, seed.ToString("X8"));
if (lead != LeadRequired.None)
{
if (lead is LeadRequired.Static)
@@ -220,7 +233,7 @@ private static string GetLineSlot34(LegalInfo info, PIDIV pidiv, IEncounterSlot3
else
line += $" [{s.SlotNumber}]";
line += $" ({lead.Localize()})";
line += $" ({lead.Localize(strings.Lines)})";
}
else
{
@@ -230,7 +243,7 @@ private static string GetLineSlot34(LegalInfo info, PIDIV pidiv, IEncounterSlot3
return line;
}
private static void AppendDetailsDate4(LegalInfo info, List<string> lines)
private static void AppendDetailsDate4(LegalInfo info, List<string> lines, EncounterDisplayLocalization loc)
{
var pidiv = info.PIDIV;
if (pidiv.Type is not (PIDType.Method_1 or PIDType.ChainShiny))
@@ -244,34 +257,34 @@ private static void AppendDetailsDate4(LegalInfo info, List<string> lines)
var entity = info.Entity;
var date = entity.MetDate ?? new DateOnly(2000, 1, 1);
var initialSeed = ClassicEraRNG.SeekInitialSeed((uint)date.Year, (uint)date.Month, (uint)date.Day, seed);
AppendInitialDateTime4(lines, initialSeed, seed, date);
AppendInitialDateTime4(lines, initialSeed, seed, date, loc);
}
private static void AppendInitialDateTime4(List<string> lines, uint initialSeed, uint origin, DateOnly date)
private static void AppendInitialDateTime4(List<string> lines, uint initialSeed, uint origin, DateOnly date, EncounterDisplayLocalization loc)
{
var decompose = ClassicEraRNG.DecomposeSeed(initialSeed, (uint)date.Year, (uint)date.Month, (uint)date.Day);
AppendInitialDateTime4(lines, initialSeed, origin, decompose);
AppendInitialDateTime4(lines, initialSeed, origin, decompose, loc);
}
private static void AppendInitialDateTime4(List<string> lines, uint initialSeed, uint origin, InitialSeedComponents4 decompose)
private static void AppendInitialDateTime4(List<string> lines, uint initialSeed, uint origin, InitialSeedComponents4 decompose, EncounterDisplayLocalization loc)
{
var advances = LCRNG.GetDistance(initialSeed, origin);
lines.Add($"{decompose.Year+2000:0000}-{decompose.Month:00}-{decompose.Day:00} @ {decompose.Hour:00}:{decompose.Minute:00}:{decompose.Second:00} - {decompose.Delay}");
lines.Add($"Initial: 0x{initialSeed:X8}, Frame: {advances + 1}"); // frames are 1-indexed
lines.Add(string.Format(loc.FrameInitial, initialSeed.ToString("X8"), advances + 1)); // frames are 1-indexed
}
private static void AppendDetailsFrame3(LegalInfo info, List<string> lines)
private static void AppendDetailsFrame3(LegalInfo info, List<string> lines, EncounterDisplayLocalization loc)
{
var pidiv = info.PIDIV;
var pk = info.Entity;
var enc = info.EncounterOriginal;
var seed = enc is EncounterSlot3 && info.FrameMatches ? pidiv.EncounterSeed : pidiv.OriginSeed;
var (initialSeed, advances) = GetInitialSeed3(seed, pk.Version);
lines.Add($"Initial: 0x{initialSeed:X8}, Frame: {advances + 1}"); // frames are 1-indexed
lines.Add(string.Format(loc.FrameInitial, initialSeed.ToString("X8"), advances + 1)); // frames are 1-indexed
var sb = new StringBuilder();
AppendFrameTimeStamp3(advances, sb);
lines.Add($"Time: {sb}");
AppendFrameTimeStamp3(advances, sb, loc);
lines.Add(string.Format(loc.Format, loc.Time, sb));
// Try appending the TID frame if it originates from Emerald.
if (pk.Version is not GameVersion.E)
@@ -281,10 +294,10 @@ private static void AppendDetailsFrame3(LegalInfo info, List<string> lines)
var tidAdvances = LCRNG.GetDistance(tidSeed, seed);
if (tidAdvances >= advances)
return; // only show if it makes sense to
lines.Add($"New Game: 0x{tidSeed:X8}, Frame: {tidAdvances + 1}"); // frames are 1-indexed
lines.Add(string.Format(loc.FrameNewGame, tidSeed.ToString("X8"), tidAdvances + 1)); // frames are 1-indexed
sb.Clear();
AppendFrameTimeStamp3(tidAdvances, sb);
lines.Add($"Time: {sb}");
AppendFrameTimeStamp3(tidAdvances, sb, loc);
lines.Add(string.Format(loc.Format, loc.Time, sb));
}
/// <summary>
@@ -292,7 +305,8 @@ private static void AppendDetailsFrame3(LegalInfo info, List<string> lines)
/// </summary>
/// <param name="frame">Frames elapsed since the initial seed.</param>
/// <param name="sb">StringBuilder to append the timestamp to.</param>
private static void AppendFrameTimeStamp3(uint frame, StringBuilder sb)
/// <param name="loc">Localization strings for formatting.</param>
private static void AppendFrameTimeStamp3(uint frame, StringBuilder sb, EncounterDisplayLocalization loc)
{
var time = TimeSpan.FromSeconds((double)frame / 60);
if (time.TotalHours >= 1)
@@ -303,7 +317,7 @@ private static void AppendFrameTimeStamp3(uint frame, StringBuilder sb)
sb.Append($"{time.Milliseconds / 10:00}");
if (time.TotalDays >= 1)
sb.Append($" (days: {(int)time.TotalDays})");
sb.AppendFormat(loc.SuffixDays, (int)time.TotalDays);
}
private static (uint Seed, uint Advances) GetInitialSeed3(uint seed, GameVersion game)
@@ -329,25 +343,24 @@ private static (uint Seed, uint Advances) GetInitialSeed3(uint seed, GameVersion
return (nearest16, ctr);
}
private static string Localize(this LeadRequired lead)
private static string Localize(this LeadRequired lead, LegalityCheckLocalization localization)
{
if (lead is LeadRequired.Invalid)
return "❌";
var (ability, isFail, condition) = lead.GetDisplayAbility();
var abilities = GameInfo.Strings.Ability;
var name = abilities[(int)ability];
var result = isFail ? string.Format(L_F0_1, name, "❌") : name;
var result = isFail ? string.Format(localization.F0_1, name, "❌") : name;
if (condition != EncounterTriggerCondition.None)
result += $"-{condition}";
return result;
}
public static string GetEncounterName(this IEncounterable enc)
public static string GetEncounterName(this IEncounterable enc, ReadOnlySpan<string> speciesNames)
{
var str = ParseSettings.SpeciesStrings;
// Shouldn't ever be out of range, but just in case.
var species = enc.Species;
var name = (uint)species < str.Count ? str[species] : species.ToString();
var name = (uint)species < speciesNames.Length ? speciesNames[species] : species.ToString();
return $"{enc.LongName} ({name})";
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Text;
using static PKHeX.Core.LearnMethod;
using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core;
@@ -14,9 +13,9 @@ namespace PKHeX.Core;
public readonly record struct MoveLearnInfo(LearnMethod Method, LearnEnvironment Environment, byte Argument = 0)
{
/// <inheritdoc cref="Summarize(StringBuilder, ReadOnlySpan{char})"/>
public void Summarize(StringBuilder sb)
public void Summarize(StringBuilder sb, MoveSourceLocalization strings)
{
var localized = GetLocalizedMethod();
var localized = GetLocalizedMethod(strings);
Summarize(sb, localized);
}
@@ -34,31 +33,31 @@ private void Summarize(StringBuilder sb, ReadOnlySpan<char> localizedMethod)
sb.Append($" @ lv{Argument}");
}
private string GetLocalizedMethod() => Method switch
private string GetLocalizedMethod(MoveSourceLocalization strings) => Method switch
{
Empty => LMoveSourceEmpty,
Relearn => LMoveSourceRelearn,
Initial => LMoveSourceDefault,
LevelUp => LMoveSourceLevelUp,
TMHM => LMoveSourceTMHM,
Tutor => LMoveSourceTutor,
Sketch => LMoveSourceShared,
EggMove => LMoveRelearnEgg,
InheritLevelUp => LMoveEggInherited,
Empty => strings.SourceEmpty,
Relearn => strings.SourceRelearn,
Initial => strings.SourceDefault,
LevelUp => strings.SourceLevelUp,
TMHM => strings.SourceTMHM,
Tutor => strings.SourceTutor,
Sketch => strings.SourceShared,
EggMove => strings.RelearnEgg,
InheritLevelUp => strings.EggInherited,
HOME => LMoveSourceSpecial,
Evolution => LMoveSourceSpecial,
Encounter => LMoveSourceSpecial,
SpecialEgg => LMoveSourceSpecial,
ShedinjaEvo => LMoveSourceSpecial,
HOME => strings.SourceSpecial,
Evolution => strings.SourceSpecial,
Encounter => strings.SourceSpecial,
SpecialEgg => strings.SourceSpecial,
ShedinjaEvo => strings.SourceSpecial,
Shared => LMoveSourceShared,
Shared => strings.SourceShared,
// Invalid
None => LMoveSourceInvalid,
Unobtainable or UnobtainableExpect => LMoveSourceInvalid,
Duplicate => LMoveSourceDuplicate,
EmptyInvalid => LMoveSourceEmpty,
None => strings.SourceInvalid,
Unobtainable or UnobtainableExpect => strings.SourceInvalid,
Duplicate => strings.SourceDuplicate,
EmptyInvalid => strings.SourceEmpty,
_ => throw new ArgumentOutOfRangeException(nameof(Method), Method, null),
};

View File

@@ -18,18 +18,22 @@ namespace PKHeX.Core;
internal MoveResult(LearnMethod method, LearnEnvironment game) : this(new MoveLearnInfo(method, game), Generation: game.GetGeneration()) { }
private MoveResult(LearnMethod method) : this(new MoveLearnInfo(method, LearnEnvironment.None)) { }
public string Summary(ISpeciesForm current, EvolutionHistory history)
public string Summary(in LegalityLocalizationContext ctx)
{
var sb = new StringBuilder(48);
Info.Summarize(sb);
Info.Summarize(sb, ctx.Settings.Moves);
if (Info.Method.HasExpectedMove())
{
var name = ParseSettings.MoveStrings[Expect];
var str = LegalityCheckStrings.LMoveFExpectSingle_0;
var str = ctx.Settings.Lines.MoveFExpectSingle_0;
sb.Append(' ').AppendFormat(str, name);
return sb.ToString();
}
var la = ctx.Analysis;
var history = la.Info.EvoChainsAllGens;
var current = la.Entity;
var detail = GetDetail(history);
if (detail.Species == 0)
return sb.ToString();
@@ -57,9 +61,6 @@ private EvoCriteria GetDetail(EvolutionHistory history)
public bool IsRelearn => Info.Method.IsRelearn();
public Severity Judgement => Valid ? Severity.Valid : Severity.Invalid;
public string Rating => Judgement.Description();
public string Format(string format, int index, PKM pk, EvolutionHistory history) => string.Format(format, Rating, index, Summary(pk, history));
public static MoveResult Initial(LearnEnvironment game) => new(LearnMethod.Initial, game);
public static readonly MoveResult Relearn = new(LearnMethod.Relearn);

View File

@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using static PKHeX.Core.LegalityAnalyzers;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -99,7 +99,7 @@ public LegalityAnalysis(PKM pk, IPersonalInfo pi, StorageSlotType source = Ignor
{
EncounterFinder.FindVerifiedEncounter(pk, Info);
if (!pk.IsOriginValid)
AddLine(Severity.Invalid, LEncConditionBadSpecies, CheckIdentifier.GameOrigin);
AddLine(Severity.Invalid, EncConditionBadSpecies, CheckIdentifier.GameOrigin);
GetParseMethod()();
Valid = Parse.TrueForAll(chk => chk.Valid)
@@ -107,7 +107,7 @@ public LegalityAnalysis(PKM pk, IPersonalInfo pi, StorageSlotType source = Ignor
&& MoveResult.AllValid(Info.Relearn);
if (!Valid && IsPotentiallyMysteryGift(Info, pk))
AddLine(Severity.Invalid, LFatefulGiftMissing, CheckIdentifier.Fateful);
AddLine(Severity.Invalid, FatefulGiftMissing, CheckIdentifier.Fateful);
Parsed = true;
}
#if SUPPRESS
@@ -130,7 +130,7 @@ public LegalityAnalysis(PKM pk, IPersonalInfo pi, StorageSlotType source = Ignor
p = MoveResult.Unobtainable();
}
AddLine(Severity.Invalid, L_AError, CheckIdentifier.Misc);
AddLine(Severity.Invalid, Error, CheckIdentifier.Misc);
}
#endif
}
@@ -261,7 +261,7 @@ private void ParsePK9()
/// <param name="s">Check severity</param>
/// <param name="c">Check comment</param>
/// <param name="i">Check type</param>
internal void AddLine(Severity s, string c, CheckIdentifier i) => AddLine(new CheckResult(s, i, c));
internal void AddLine(Severity s, LegalityCheckResultCode c, CheckIdentifier i) => AddLine(CheckResult.Get(s, i, c));
/// <summary>
/// Adds a new Check parse value.

View File

@@ -0,0 +1,33 @@
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
using System.Text.Json.Serialization;
namespace PKHeX.Core;
/// <summary>
/// Localization strings for encounter display information.
/// </summary>
public sealed record EncounterDisplayLocalization
{
private static readonly EncounterDisplayLocalizationContext Context = new(LocalizationStorage<EncounterDisplayLocalization>.Options);
public static readonly LocalizationStorage<EncounterDisplayLocalization> Cache = new("encounter", Context.EncounterDisplayLocalization);
public static EncounterDisplayLocalization Get(string language = GameLanguage.DefaultLanguage) => Cache.Get(language);
public static EncounterDisplayLocalization Get(LanguageID language) => Cache.Get(language.GetLanguageCode());
public required string Format { get; set; } = "{0}: {1}";
public required string FormatLevelRange { get; set; } = "{0}: {1}-{2}";
public required string EncounterType { get; set; } = "Encounter Type";
public required string Version { get; set; } = "Version";
public required string Level { get; set; } = "Level";
public required string LevelRange { get; set; } = "Level Range";
public required string Location { get; set; } = "Location";
public required string OriginSeed { get; set; } = "Origin Seed";
public required string PIDType { get; set; } = "PID Type";
public required string Time { get; set; } = "Time";
public required string FrameNewGame { get; set; } = "New Game: 0x{0}, Frame: {1}";
public required string FrameInitial { get; set; } = "Initial: 0x{0}, Frame: {1}";
public required string SuffixDays { get; set; } = " (days: {0})";
}
[JsonSerializable(typeof(EncounterDisplayLocalization))]
public sealed partial class EncounterDisplayLocalizationContext : JsonSerializerContext;

View File

@@ -0,0 +1,27 @@
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
using System.Text.Json.Serialization;
namespace PKHeX.Core;
/// <summary>
/// Localization strings for general display information.
/// </summary>
public sealed record GeneralLocalization
{
private static readonly GeneralLocalizationContext Context = new(LocalizationStorage<GeneralLocalization>.Options);
public static readonly LocalizationStorage<GeneralLocalization> Cache = new("general", Context.GeneralLocalization);
public static GeneralLocalization Get(string language = GameLanguage.DefaultLanguage) => Cache.Get(language);
public static GeneralLocalization Get(LanguageID language) => Cache.Get(language.GetLanguageCode());
public required string[] StatNames { get; init; }
public required string OriginalTrainer { get; init; } = "Original Trainer";
public required string HandlingTrainer { get; init; } = "Handling Trainer";
public required string GenderMale { get; init; } = "Male";
public required string GenderFemale { get; init; } = "Female";
public required string GenderGenderless { get; init; } = "Genderless";
}
[JsonSerializable(typeof(GeneralLocalization))]
public sealed partial class GeneralLocalizationContext : JsonSerializerContext;

View File

@@ -0,0 +1,441 @@
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
using System.Text.Json.Serialization;
namespace PKHeX.Core;
/// <summary>
/// Legality Check Message Strings to indicate why certain <see cref="PKM"/> <see cref="LegalInfo"/> values are flagged.
/// </summary>
public sealed class LegalityCheckLocalization
{
private static readonly LegalityCheckLocalizationContext Context = new(LocalizationStorage<LegalityCheckLocalization>.Options);
public static readonly LocalizationStorage<LegalityCheckLocalization> Cache = new("legality", Context.LegalityCheckLocalization);
public static LegalityCheckLocalization Get(string language = GameLanguage.DefaultLanguage) => Cache.Get(language);
public static LegalityCheckLocalization Get(LanguageID language) => Cache.Get(language.GetLanguageCode());
// Message String Name format: L/F[Category][Summary]
#region General Strings
/// <summary>Default text for indicating validity.</summary>
public string Valid { get; set; } = "Valid.";
/// <summary>Default text for indicating legality.</summary>
public string Legal { get; set; } = "Legal!";
/// <summary>Default text for indicating an error has occurred.</summary>
public string Error { get; set; } = "Internal error.";
/// <summary>Analysis not available for the <see cref="PKM"/></summary>
public string AnalysisUnavailable { get; set; } = "Analysis not available for this Pokémon.";
/// <summary>Format text for exporting a legality check result.</summary>
public string F0_1 { get; set; } = "{0}: {1}";
/// <summary>Severity string for <see cref="Severity.Invalid"/></summary>
public string SInvalid { get; set; } = "Invalid";
/// <summary>Severity string for <see cref="Severity.Fishy"/></summary>
public string SFishy { get; set; } = "Fishy";
/// <summary>Severity string for <see cref="Severity.Valid"/></summary>
public string SValid { get; set; } = "Valid";
/// <summary>Severity string for anything not implemented.</summary>
public string NotImplemented { get; set; } = "Not Implemented";
public string AbilityCapsuleUsed { get; set; } = "Ability available with Ability Capsule.";
public string AbilityPatchUsed { get; set; } = "Ability available with Ability Patch.";
public string AbilityPatchRevertUsed { get; set; } = "Ability available with Ability Patch Revert.";
public string AbilityFlag { get; set; } = "Ability matches ability number.";
public string AbilityHiddenFail { get; set; } = "Hidden Ability mismatch for encounter type.";
public string AbilityHiddenUnavailable { get; set; } = "Hidden Ability not available.";
public string AbilityMismatch { get; set; } = "Ability mismatch for encounter.";
public string AbilityMismatch3 { get; set; } = "Ability does not match Generation 3 species ability.";
public string AbilityMismatchFlag { get; set; } = "Ability does not match ability number.";
public string AbilityMismatchGift { get; set; } = "Ability does not match Mystery Gift.";
public string AbilityMismatchPID { get; set; } = "Ability does not match PID.";
public string AbilityUnexpected { get; set; } = "Ability is not valid for species/form.";
public string AwakenedCap { get; set; } = "Individual AV cannot be greater than {0}.";
public string AwakenedShouldBeValue { get; set; } = "{1} AV should be greater than {0}.";
public string BallAbility { get; set; } = "Can't obtain Hidden Ability with Ball.";
public string BallEggCherish { get; set; } = "Can't have Cherish Ball for regular Egg.";
public string BallEggMaster { get; set; } = "Can't have Master Ball for regular Egg.";
public string BallEnc { get; set; } = "Correct ball for encounter type.";
public string BallEncMismatch { get; set; } = "Can't have ball for encounter type.";
public string BallHeavy { get; set; } = "Can't have Heavy Ball for light, low-catch rate species (Gen VII).";
public string BallSpecies { get; set; } = "Can't obtain species in Ball.";
public string BallSpeciesPass { get; set; } = "Ball possible for species.";
public string BallUnavailable { get; set; } = "Ball unobtainable in origin Generation.";
public string ContestZero { get; set; } = "Contest Stats should be 0.";
public string ContestZeroSheen { get; set; } = "Contest Stat Sheen should be 0.";
public string ContestSheenGEQ_0 { get; set; } = "Contest Stat Sheen should be >= {0}.";
public string ContestSheenLEQ_0 { get; set; } = "Contest Stat Sheen should be <= {0}.";
public string DateOutsideConsoleWindow { get; set; } = "Local Date is outside of console's local time window.";
public string DateTimeClockInvalid { get; set; } = "Local Time is not a valid timestamp.";
public string DateOutsideDistributionWindow { get; set; } = "Met Date is outside of distribution window.";
public string EggContest { get; set; } = "Cannot increase Contest Stats of an Egg.";
public string EggEXP { get; set; } = "Eggs cannot receive experience.";
public string EggFMetLevel_0 { get; set; } = "Invalid Met Level, expected {0}.";
public string EggHatchCycles { get; set; } = "Invalid Egg hatch cycles.";
public string EggLocation { get; set; } = "Able to hatch an Egg at Met Location.";
public string EggLocationInvalid { get; set; } = "Can't hatch an Egg at Met Location.";
public string EggLocationNone { get; set; } = "Invalid Egg Location, expected none.";
public string EggLocationPalPark { get; set; } = "Invalid Met Location, expected Pal Park.";
public string EggLocationTrade { get; set; } = "Able to hatch a traded Egg at Met Location.";
public string EggLocationTradeFail { get; set; } = "Invalid Egg Location, shouldn't be 'traded' while an Egg.";
public string EggMetLocationFail { get; set; } = "Can't obtain Egg from Egg Location.";
public string EggNature { get; set; } = "Eggs cannot have their Stat Nature changed.";
public string EggPokeathlon { get; set; } = "Eggs cannot have Pokéathlon stats.";
public string EggPP { get; set; } = "Eggs cannot have modified move PP counts.";
public string EggPPUp { get; set; } = "Cannot apply PP Ups to an Egg.";
public string EggRelearnFlags { get; set; } = "Expected no Relearn Move Flags.";
public string EggShinyLeaf { get; set; } = "Eggs cannot have Shiny Leaf/Crown.";
public string EggShinyPokeStar { get; set; } = "Eggs cannot be a Pokéstar Studios star.";
public string EggSpecies { get; set; } = "Can't obtain Egg for this species.";
public string EggUnhatched { get; set; } = "Valid un-hatched Egg.";
public string EncCondition { get; set; } = "Valid Wild Encounter at location.";
public string EncConditionBadRNGFrame { get; set; } = "Unable to match encounter conditions to a possible RNG frame.";
public string EncConditionBadSpecies { get; set; } = "Species does not exist in origin game.";
public string EncGift { get; set; } = "Unable to match a gift Egg encounter from origin game.";
public string EncGiftEggEvent { get; set; } = "Unable to match an event Egg encounter from origin game.";
public string EncGiftIVMismatch { get; set; } = "IVs do not match Mystery Gift Data.";
public string EncGiftNicknamed { get; set; } = "Event gift has been nicknamed.";
public string EncGiftNotFound { get; set; } = "Unable to match to a Mystery Gift in the database.";
public string EncGiftPIDMismatch { get; set; } = "Mystery Gift fixed PID mismatch.";
public string EncGiftShinyMismatch { get; set; } = "Mystery Gift shiny mismatch.";
public string EncGiftVersionNotDistributed { get; set; } = "Mystery Gift cannot be received by this version.";
public string EncInvalid { get; set; } = "Unable to match an encounter from origin game.";
public string EncMasteryInitial { get; set; } = "Initial move mastery flags do not match the encounter's expected state.";
public string EncTradeChangedNickname { get; set; } = "In-game Trade Nickname has been altered.";
public string EncTradeChangedOT { get; set; } = "In-game Trade OT has been altered.";
public string EncTradeIndexBad { get; set; } = "In-game Trade invalid index?";
public string EncTradeMatch { get; set; } = "Valid In-game trade.";
public string EncTradeUnchanged { get; set; } = "In-game Trade OT and Nickname have not been altered.";
public string EncStaticPIDShiny { get; set; } = "Encounter shiny mismatch.";
public string EncTypeMatch { get; set; } = "Encounter Type matches encounter.";
public string EncTypeMismatch { get; set; } = "Encounter Type does not match encounter.";
public string EncUnreleased { get; set; } = "Unreleased event.";
public string EncUnreleasedEMewJP { get; set; } = "Non japanese Mew from Faraway Island. Unreleased event.";
public string EReaderAmerica { get; set; } = "American E-Reader Berry in Japanese save file.";
public string EReaderInvalid { get; set; } = "Invalid E-Reader Berry.";
public string EReaderJapan { get; set; } = "Japanese E-Reader Berry in international save file.";
public string Effort2Remaining { get; set; } = "2 EVs remaining.";
public string EffortAbove252 { get; set; } = "EVs cannot go above 252.";
public string EffortAbove510 { get; set; } = "EV total cannot be above 510.";
public string EffortAllEqual { get; set; } = "EVs are all equal.";
public string EffortCap100 { get; set; } = "Individual EV for a level 100 encounter in Generation 4 cannot be greater than 100.";
public string EffortEgg { get; set; } = "Eggs cannot receive EVs.";
public string EffortShouldBeZero { get; set; } = "Cannot receive EVs.";
public string EffortEXPIncreased { get; set; } = "All EVs are zero, but leveled above Met Level.";
public string EffortUntrainedCap { get; set; } = "Individual EV without changing EXP cannot be greater than {0}.";
public string EvoInvalid { get; set; } = "Evolution not valid (or level/trade evolution unsatisfied).";
public string EvoTradeReqOutsider { get; set; } = "Outsider {0} should have evolved into {1}.";
public string EvoTradeRequired { get; set; } = "Version Specific evolution requires a trade to opposite version. A Handling Trainer is required.";
public string FatefulGiftMissing { get; set; } = "Fateful Encounter with no matching Encounter. Has the Mystery Gift data been contributed?";
public string FatefulInvalid { get; set; } = "Fateful Encounter should not be checked.";
public string FatefulMissing { get; set; } = "Special In-game Fateful Encounter flag missing.";
public string FatefulMystery { get; set; } = "Mystery Gift Fateful Encounter.";
public string FatefulMysteryMissing { get; set; } = "Mystery Gift Fateful Encounter flag missing.";
public string FavoriteMarkingUnavailable { get; set; } = "Favorite Marking is not available.";
public string FormArgumentLEQ_0 { get; set; } = "Form argument is too high for current form.";
public string FormArgumentGEQ_0 { get; set; } = "Form argument is too low for current form.";
public string FormArgumentNotAllowed { get; set; } = "Form argument is not allowed for this encounter.";
public string FormArgumentValid { get; set; } = "Form argument is valid.";
public string FormArgumentInvalid { get; set; } = "Form argument is not valid.";
public string FormBattle { get; set; } = "Form cannot exist outside of a battle.";
public string FormEternal { get; set; } = "Valid Eternal Flower encounter.";
public string FormEternalInvalid { get; set; } = "Invalid Eternal Flower encounter.";
public string FormInvalidGame { get; set; } = "Form cannot be obtained in origin game.";
public string FormInvalidNature { get; set; } = "Form cannot have this nature.";
public string FormInvalidRange { get; set; } = "Form Count is out of range. Expected <= {0}, got {1}.";
public string FormItem { get; set; } = "Held item matches Form.";
public string FormItemInvalid { get; set; } = "Held item does not match Form.";
public string FormParty { get; set; } = "Form cannot exist outside of Party.";
public string FormPikachuCosplay { get; set; } = "Only Cosplay Pikachu can have this form.";
public string FormPikachuCosplayInvalid { get; set; } = "Cosplay Pikachu cannot have the default form.";
public string FormPikachuEventInvalid { get; set; } = "Event Pikachu cannot have the default form.";
public string FormInvalidExpect_0 { get; set; } = "Form is invalid, expected form index {0}.";
public string FormValid { get; set; } = "Form is Valid.";
public string FormVivillon { get; set; } = "Valid Vivillon pattern.";
public string FormVivillonEventPre { get; set; } = "Event Vivillon pattern on pre-evolution.";
public string FormVivillonInvalid { get; set; } = "Invalid Vivillon pattern.";
public string FormVivillonNonNative { get; set; } = "Non-native Vivillon pattern.";
public string G1CatchRateChain { get; set; } = "Catch rate does not match any species from Pokémon evolution chain.";
public string G1CatchRateEvo { get; set; } = "Catch rate match species without encounters. Expected a preevolution catch rate.";
public string G1CatchRateItem { get; set; } = "Catch rate does not match a valid held item from Generation 2.";
public string G1CatchRateMatchPrevious { get; set; } = "Catch Rate matches a species from Pokémon evolution chain.";
public string G1CatchRateMatchTradeback { get; set; } = "Catch rate matches a valid held item from Generation 2.";
public string G1CatchRateNone { get; set; } = "Catch rate does not match any species from Pokémon evolution chain or any Generation 2 held items.";
public string G1CharNick { get; set; } = "Nickname from Generation 1/2 uses unavailable characters.";
public string G1CharOT { get; set; } = "OT from Generation 1/2 uses unavailable characters.";
public string G1OTGender { get; set; } = "Female OT from Generation 1/2 is invalid.";
public string G1Stadium { get; set; } = "Incorrect Stadium OT.";
public string G1Type1Fail { get; set; } = "Invalid Type A, does not match species type.";
public string G1Type2Fail { get; set; } = "Invalid Type B, does not match species type.";
public string G1TypeMatch1 { get; set; } = "Valid Type A, matches species type.";
public string G1TypeMatch2 { get; set; } = "Valid Type B, matches species type.";
public string G1TypeMatchPorygon { get; set; } = "Porygon with valid Type A and B values.";
public string G1TypePorygonFail { get; set; } = "Porygon with invalid Type A and B values. Does not a match a valid type combination.";
public string G1TypePorygonFail1 { get; set; } = "Porygon with invalid Type A value.";
public string G1TypePorygonFail2 { get; set; } = "Porygon with invalid Type B value.";
public string G2InvalidTileTreeNotFound { get; set; } = "Could not find a tree for Crystal headbutt encounter that matches OTID.";
public string G2TreeID { get; set; } = "Found a tree for Crystal headbutt encounter that matches OTID.";
public string G2OTGender { get; set; } = "OT from Virtual Console games other than Crystal cannot be female.";
public string G3EReader { get; set; } = "Non Japanese Shadow E-reader Pokémon. Unreleased encounter.";
public string G3OTGender { get; set; } = "OT from Colosseum/XD cannot be female.";
public string G4InvalidTileR45Surf { get; set; } = "Johto Route 45 surfing encounter. Unreachable Water tiles.";
public string G5IVAll30 { get; set; } = "All IVs of N's Pokémon should be 30.";
public string G5PIDShinyGrotto { get; set; } = "Hidden Grotto captures cannot be shiny.";
public string G5SparkleInvalid { get; set; } = "Special In-game N's Sparkle flag should not be checked.";
public string G5SparkleRequired { get; set; } = "Special In-game N's Sparkle flag missing.";
public string GanbaruStatTooHigh { get; set; } = "One or more Ganbaru Value is above the natural limit of (10 - IV bonus).";
public string GenderInvalidNone { get; set; } = "Genderless Pokémon should not have a gender.";
public string GeoBadOrder { get; set; } = "GeoLocation Memory: Gap/Blank present.";
public string GeoHardwareInvalid { get; set; } = "Geolocation: Country is not in 3DS region.";
public string GeoHardwareRange { get; set; } = "Invalid Console Region.";
public string GeoHardwareValid { get; set; } = "Geolocation: Country is in 3DS region.";
public string GeoMemoryMissing { get; set; } = "GeoLocation Memory: Memories should be present.";
public string GeoNoCountryHT { get; set; } = "GeoLocation Memory: HT Name present but has no previous Country.";
public string GeoNoRegion { get; set; } = "GeoLocation Memory: Region without Country.";
public string HyperTrainLevelGEQ_0 { get; set; } = "Can't Hyper Train a Pokémon that isn't level {0}.";
public string HyperPerfectAll { get; set; } = "Can't Hyper Train a Pokémon with perfect IVs.";
public string HyperPerfectOne { get; set; } = "Can't Hyper Train a perfect IV.";
public string HyperPerfectUnavailable { get; set; } = "Can't Hyper Train any IV(s).";
public string ItemEgg { get; set; } = "Eggs cannot hold items.";
public string ItemUnreleased { get; set; } = "Held item is unreleased.";
public string IVAllEqual_0 { get; set; } = "All IVs are {0}.";
public string IVNotCorrect { get; set; } = "IVs do not match encounter requirements.";
public string IVFlawlessCountGEQ_0 { get; set; } = "Should have at least {0} IVs = 31.";
public string LevelEXPThreshold { get; set; } = "Current experience matches level threshold.";
public string LevelEXPTooHigh { get; set; } = "Current experience exceeds maximum amount for level 100.";
public string LevelMetBelow { get; set; } = "Current level is below met level.";
public string LevelMetGift { get; set; } = "Met Level does not match Mystery Gift level.";
public string LevelMetGiftFail { get; set; } = "Current Level below Mystery Gift level.";
public string LevelMetSane { get; set; } = "Current level is not below met level.";
public string MarkValueOutOfRange_0 { get; set; } = "Individual marking at index {0} is not within the allowed value range.";
public string MarkValueShouldBeZero { get; set; } = "Marking flags cannot be set.";
public string MarkValueUnusedBitsPresent { get; set; } = "Marking flags uses bits beyond the accessible range.";
public string MemoryArgBadCatch_H { get; set; } = "{0} Memory: {0} did not catch this.";
public string MemoryArgBadHatch_H { get; set; } = "{0} Memory: {0} did not hatch this.";
public string MemoryArgBadHT { get; set; } = "Memory: Can't have Handling Trainer Memory as Egg.";
public string MemoryArgBadID_H { get; set; } = "{0} Memory: Can't obtain Memory on {0} Version.";
public string MemoryArgBadItem_H1 { get; set; } = "{0} Memory: Species can't hold this item.";
public string MemoryArgBadLocation_H { get; set; } = "{0} Memory: Can't obtain Location on {0} Version.";
public string MemoryArgBadMove_H1 { get; set; } = "{0} Memory: Species can't learn {1}.";
public string MemoryArgBadOTEgg_H { get; set; } = "{0} Memory: Link Trade is not a valid first memory.";
public string MemoryArgBadSpecies_H1 { get; set; } = "{0} Memory: Can't capture species in game.";
public string MemoryArgSpecies_H { get; set; } = "{0} Memory: Species can be captured in game.";
public string MemoryCleared_H { get; set; } = "Memory: Not cleared properly.";
public string MemoryValid_H { get; set; } = "{0} Memory is valid.";
public string MemoryFeelInvalid_H { get; set; } = "{0} Memory: Invalid Feeling.";
public string MemoryHTFlagInvalid { get; set; } = "Untraded: Current handler should not be the Handling Trainer.";
public string MemoryHTGender_0 { get; set; } = "HT Gender invalid: {0}";
public string MemoryHTLanguage { get; set; } = "HT Language is missing.";
public string MemoryIndexArgHT { get; set; } = "Should have a HT Memory TextVar value (somewhere).";
public string MemoryIndexFeel_H1 { get; set; } = "{0} Memory: Feeling should be index {1}.";
public string MemoryIndexFeelHTLEQ9 { get; set; } = "Should have a HT Memory Feeling value 0-9.";
public string MemoryIndexID_H1 { get; set; } = "{0} Memory: Should be index {1}.";
public string MemoryIndexIntensity_H1 { get; set; } = "{0} Memory: Intensity should be index {1}.";
public string MemoryIndexIntensityHT1 { get; set; } = "Should have a HT Memory Intensity value (1st).";
public string MemoryIndexIntensityMin_H1 { get; set; } = "{0} Memory: Intensity should be at least {1}.";
public string MemoryIndexLinkHT { get; set; } = "Should have a Link Trade HT Memory.";
public string MemoryIndexVar { get; set; } = "{0} Memory: TextVar should be index {1}.";
public string MemoryMissingHT { get; set; } = "Memory: Handling Trainer Memory missing.";
public string MemoryMissingOT { get; set; } = "Memory: Original Trainer Memory missing.";
public string MemorySocialZero { get; set; } = "Social Stat should be zero.";
public string MemoryStatSocialLEQ_0 { get; set; } = "Social Stat should be <= {0}";
public string MemoryStatAffectionHT0 { get; set; } = "Untraded: Handling Trainer Affection should be 0.";
public string MemoryStatAffectionOT0 { get; set; } = "OT Affection should be 0.";
public string MemoryStatFriendshipHT0 { get; set; } = "Untraded: Handling Trainer Friendship should be 0.";
public string MemoryStatFriendshipOTBaseEvent_0 { get; set; } = "Event OT Friendship does not match base friendship ({0}).";
public string MetDetailTimeOfDay { get; set; } = "Met Time of Day value is not within the expected range.";
public string MoveEvoFCombination_0 { get; set; } = "Moves combinations is not compatible with {0} evolution.";
public string MoveFExpectSingle_0 { get; set; } = "Expected: {0}";
public string MoveKeldeoMismatch { get; set; } = "Keldeo Move/Form mismatch.";
public string MovePPExpectHealed_0 { get; set; } = "Move {0} PP is below the amount expected.";
public string MovePPTooHigh_0 { get; set; } = "Move {0} PP is above the amount allowed.";
public string MovePPUpsTooHigh_0 { get; set; } = "Move {0} PP Ups is above the amount allowed.";
public string MoveShopAlphaMoveShouldBeMastered_0 { get; set; } = "Alpha Move should be marked as mastered.";
public string MoveShopAlphaMoveShouldBeOther { get; set; } = "Alpha encounter cannot be found with this Alpha Move.";
public string MoveShopAlphaMoveShouldBeZero { get; set; } = "Only Alphas may have an Alpha Move set.";
public string MoveShopMasterInvalid_0 { get; set; } = "Cannot manually master {0}: not permitted to master.";
public string MoveShopMasterNotLearned_0 { get; set; } = "Cannot manually master {0}: not in possible learned level up moves.";
public string MoveShopPurchaseInvalid_0 { get; set; } = "Cannot purchase {0} from the move shop.";
public string MoveTechRecordFlagMissing_0 { get; set; } = "Unexpected Technical Record Learned flag: {0}";
public string NickFlagEggNo { get; set; } = "Egg must be not nicknamed.";
public string NickFlagEggYes { get; set; } = "Egg must be nicknamed.";
public string NickInvalidChar { get; set; } = "Cannot be given this Nickname.";
public string NickLengthLong { get; set; } = "Nickname too long.";
public string NickLengthShort { get; set; } = "Nickname is empty.";
public string NickMatchLanguage { get; set; } = "Nickname matches species name.";
public string NickMatchLanguageEgg { get; set; } = "Egg matches language Egg name.";
public string NickMatchLanguageEggFail { get; set; } = "Egg name does not match language Egg name.";
public string NickMatchLanguageFail { get; set; } = "Nickname does not match species name.";
public string NickMatchLanguageFlag { get; set; } = "Nickname flagged, matches species name.";
public string NickMatchNoOthers { get; set; } = "Nickname does not match another species name.";
public string NickMatchNoOthersFail { get; set; } = "Nickname matches another species name (+language).";
public string OTLanguage { get; set; } = "Language ID should be {0}, not {1}.";
public string OTLong { get; set; } = "OT Name too long.";
public string OTShort { get; set; } = "OT Name too short.";
public string OTSuspicious { get; set; } = "Suspicious Original Trainer details.";
public string OT_IDEqual { get; set; } = "TID16 and SID16 are equal.";
public string OT_IDs0 { get; set; } = "TID16 and SID16 are 0.";
public string OT_SID0 { get; set; } = "SID16 is zero.";
public string OT_SID0Invalid { get; set; } = "SID16 should be 0.";
public string OT_TID0 { get; set; } = "TID16 is zero.";
public string OT_IDInvalid { get; set; } = "TID16 and SID16 combination is not possible.";
public string PIDEncryptWurmple { get; set; } = "Wurmple evolution Encryption Constant mismatch.";
public string PIDEncryptZero { get; set; } = "Encryption Constant is not set.";
public string PIDEqualsEC { get; set; } = "Encryption Constant matches PID.";
public string PIDGenderMatch { get; set; } = "Gender matches PID.";
public string PIDGenderMismatch { get; set; } = "PID-Gender mismatch.";
public string PIDNatureMatch { get; set; } = "Nature matches PID.";
public string PIDNatureMismatch { get; set; } = "PID-Nature mismatch.";
public string PIDTypeMismatch { get; set; } = "PID+ correlation does not match what was expected for the Encounter's type.";
public string PIDZero { get; set; } = "PID is not set.";
public string PokerusDaysTooHigh_0 { get; set; } = "Pokérus Days Remaining value is too high; expected <= {0}.";
public string PokerusStrainUnobtainable_0 { get; set; } = "Pokérus Strain {0} cannot be obtained.";
public string RibbonAllValid { get; set; } = "All ribbons accounted for.";
public string RibbonEgg { get; set; } = "Can't receive Ribbon(s) as an Egg.";
public string RibbonFInvalid_0 { get; set; } = "Invalid Ribbons: {0}";
public string RibbonMissing_0 { get; set; } = "Missing Ribbons: {0}";
public string RibbonMarkingInvalid_0 { get; set; } = "Invalid Marking: {0}";
public string RibbonMarkingAffixed_0 { get; set; } = "Invalid Affixed Ribbon/Marking: {0}";
public string StatDynamaxInvalid { get; set; } = "Dynamax Level is not within the expected range.";
public string StatIncorrectHeight { get; set; } = "Calculated Height does not match stored value.";
public string StatIncorrectHeightCopy { get; set; } = "Copy Height does not match the original value.";
public string StatIncorrectHeightValue { get; set; } = "Height does not match the expected value.";
public string StatIncorrectWeight { get; set; } = "Calculated Weight does not match stored value.";
public string StatIncorrectWeightValue { get; set; } = "Weight does not match the expected value.";
public string StatInvalidHeightWeight { get; set; } = "Height / Weight values are statistically improbable.";
public string StatIncorrectCP { get; set; } = "Calculated CP does not match stored value.";
public string StatGigantamaxInvalid { get; set; } = "Gigantamax Flag mismatch.";
public string StatGigantamaxValid { get; set; } = "Gigantamax Flag was changed via Max Soup.";
public string StatNatureInvalid { get; set; } = "Stat Nature is not within the expected range.";
public string StatBattleVersionInvalid { get; set; } = "Battle Version is not within the expected range.";
public string StatNobleInvalid { get; set; } = "Noble Flag mismatch.";
public string StatAlphaInvalid { get; set; } = "Alpha Flag mismatch.";
public string StoredSourceEgg { get; set; } = "Egg must be in Box or Party.";
public string StoredSlotSourceInvalid_0 { get; set; } = "Invalid Stored Source: {0}";
public string SuperComplete { get; set; } = "Super Training complete flag mismatch.";
public string SuperDistro { get; set; } = "Distribution Super Training missions are not released.";
public string SuperEgg { get; set; } = "Can't Super Train an Egg.";
public string SuperNoComplete { get; set; } = "Can't have active Super Training complete flag for origins.";
public string SuperNoUnlocked { get; set; } = "Can't have active Super Training unlocked flag for origins.";
public string SuperUnavailable { get; set; } = "Super Training missions are not available in games visited.";
public string SuperUnused { get; set; } = "Unused Super Training Flag is flagged.";
public string TeraTypeIncorrect { get; set; } = "Tera Type does not match the expected value.";
public string TeraTypeMismatch { get; set; } = "Tera Type does not match either of the default types.";
public string TradeNotAvailable { get; set; } = "Encounter cannot be traded to the active trainer.";
public string TrainerIDNoSeed { get; set; } = "Trainer ID is not obtainable from any RNG seed.";
public string TransferBad { get; set; } = "Incorrectly transferred from previous generation.";
public string TransferCurrentHandlerInvalid { get; set; } = "Invalid Current handler value, trainer details for save file expected another value.";
public string TransferEgg { get; set; } = "Can't transfer Eggs between Generations.";
public string TransferEggLocationTransporter { get; set; } = "Invalid Met Location, expected Poké Transfer.";
public string TransferEggMetLevel { get; set; } = "Invalid Met Level for transfer.";
public string TransferEggVersion { get; set; } = "Can't transfer Eggs to this game.";
public string TransferFlagIllegal { get; set; } = "Flagged as illegal by the game (glitch abuse).";
public string TransferHTFlagRequired { get; set; } = "Current handler cannot be the OT.";
public string TransferHTMismatchName { get; set; } = "Handling trainer does not match the expected trainer name.";
public string TransferHTMismatchGender { get; set; } = "Handling trainer does not match the expected trainer gender.";
public string TransferHTMismatchLanguage { get; set; } = "Handling trainer does not match the expected trainer language.";
public string TransferKoreanGen4 { get; set; } = "Korean Generation 4 games cannot interact with International Generation 4 games.";
public string TransferMet { get; set; } = "Invalid Met Location, expected Poké Transfer or Crown.";
public string TransferNotPossible { get; set; } = "Unable to transfer into current format from origin format.";
public string TransferMetLocation { get; set; } = "Invalid Transfer Met Location.";
public string TransferNature { get; set; } = "Invalid Nature for transfer Experience.";
public string TransferObedienceLevel { get; set; } = "Invalid Obedience Level.";
public string TransferPIDECBitFlip { get; set; } = "PID should be equal to EC [with top bit flipped]!";
public string TransferPIDECEquals { get; set; } = "PID should be equal to EC!";
public string TransferPIDECXor { get; set; } = "Encryption Constant matches shinyxored PID.";
public string TransferTrackerMissing { get; set; } = "Pokémon HOME Transfer Tracker is missing.";
public string TransferTrackerShouldBeZero { get; set; } = "Pokémon HOME Transfer Tracker should be 0.";
public string TrashBytesExpected { get; set; } = "Expected Trash Bytes.";
public string TrashBytesMismatchInitial { get; set; } = "Expected initial trash bytes to match the encounter.";
public string TrashBytesMissingTerminator { get; set; } = "Final terminator missing.";
public string TrashBytesShouldBeEmpty { get; set; } = "Trash Bytes should be cleared.";
#endregion
public string EncTradeShouldHaveEvolvedToSpecies_0 { get; set; } = "Trade Encounter should have evolved to species: {0}.";
public string EncGiftLanguageNotDistributed { get; set; } = "Gift Encounter was never distributed with this language.";
public string EncGiftRegionNotDistributed { get; set; } = "Gift Encounter was never distributed to this Console Region.";
public string FormInvalidRangeLEQ_0 { get; set; } = "Form Count is out of range. Expected <= {0}, got {1}.";
public string MovesShouldMatchRelearnMoves { get; set; } = "Moves should exactly match Relearn Moves.";
public string MemoryStatEnjoyment_0 { get; set; } = "Enjoyment should be {0}.";
public string MemoryStatFullness_0 { get; set; } = "Fullness should be {0}.";
public string MemoryStatFullnessLEQ_0 { get; set; } = "Fullness should be <= {0}.";
public string OTLanguageShouldBe_0 { get; set; } = "Language ID should be {0}, not {1}.";
public string OTLanguageShouldBe_0or1 { get; set; } = "Language ID should be {0} or {1}, not {2}.";
public string OTLanguageShouldBeLeq_0 { get; set; } = "Language ID should be <= {0}, not {1}.";
public string OTLanguageCannotPlayOnVersion_0 { get; set; } = "Language ID {0} cannot be played on this version.";
public string OTLanguageCannotTransferToConsoleRegion_0 { get; set; } = "Language ID {0} cannot be transferred to this Console Region.";
public string WordFilterInvalidCharacter_0 { get; set; } = "Word Filter: Invalid character '{0}' (0x{1}).";
public string WordFilterFlaggedPattern_01 { get; set; } = "Word Filter ({1}): Flagged pattern '{0}'.";
public string WordFilterTooManyNumbers_0 { get; set; } = "Word Filter: Too many numbers (>{0}).";
public string BulkCloneDetectedDetails { get; set; } = "Clone detected (Details).";
public string BulkCloneDetectedTracker { get; set; } = "Clone detected (Duplicate Tracker).";
public string HintEvolvesToSpecies_0 { get; set; } = "Evolves to species: {0}.";
public string HintEvolvesToRareForm_0 { get; set; } = "Evolves to rare form: {0}.";
public string BulkSharingEncryptionConstantGenerationDifferent { get; set; } = "Detected sharing of Encryption Constant across generations.";
public string BulkSharingEncryptionConstantGenerationSame { get; set; } = "Detected sharing of Encryption Constant.";
public string BulkSharingEncryptionConstantRNGType { get; set; } = "Detected sharing of Encryption Constant sharing for different RNG encounters.";
public string BulkSharingPIDGenerationDifferent { get; set; } = "Detected sharing of PID across generations.";
public string BulkSharingPIDGenerationSame { get; set; } = "Detected sharing of PID.";
public string BulkSharingPIDRNGType { get; set; } = "Detected sharing of PID for different RNG encounters.";
public string BulkDuplicateMysteryGiftEggReceived { get; set; } = "Detected multiple redemptions of the same non-repeatable Mystery Gift Egg.";
public string BulkSharingTrainerID { get; set; } = "Detected sharing of Trainer ID across multiple trainer names.";
public string BulkSharingTrainerVersion { get; set; } = "Detected sharing of Trainer ID across multiple versions.";
}
[JsonSerializable(typeof(LegalityCheckLocalization))]
internal sealed partial class LegalityCheckLocalizationContext : JsonSerializerContext;

View File

@@ -0,0 +1,407 @@
using System;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
/// <summary>
/// Provides extension methods for <see cref="LegalityCheckResultCode"/> to convert to human-readable strings.
/// </summary>
public static class LegalityCheckResultCodeExtensions
{
public static bool IsArgument(this LegalityCheckResultCode code) => code is < FirstWithMove and >= FirstWithArgument;
public static bool IsMove(this LegalityCheckResultCode code) => code is < FirstWithLanguage and >= FirstWithMove;
public static bool IsLanguage(this LegalityCheckResultCode code) => code is < FirstWithMemory and >= FirstWithLanguage;
public static bool IsMemory(this LegalityCheckResultCode code) => code is < FirstComplex and >= FirstWithMemory;
/// <summary>
/// Returns the template string for the given result code.
/// </summary>
public static string GetTemplate(this LegalityCheckResultCode code, LegalityCheckLocalization localization) => code switch
{
// General Strings
Valid => localization.Valid,
Error => localization.Error,
// Ability
AbilityCapsuleUsed => localization.AbilityCapsuleUsed,
AbilityPatchUsed => localization.AbilityPatchUsed,
AbilityPatchRevertUsed => localization.AbilityPatchRevertUsed,
AbilityFlag => localization.AbilityFlag,
AbilityHiddenFail => localization.AbilityHiddenFail,
AbilityHiddenUnavailable => localization.AbilityHiddenUnavailable,
AbilityMismatch => localization.AbilityMismatch,
AbilityMismatch3 => localization.AbilityMismatch3,
AbilityMismatchFlag => localization.AbilityMismatchFlag,
AbilityMismatchGift => localization.AbilityMismatchGift,
AbilityMismatchPID => localization.AbilityMismatchPID,
AbilityUnexpected => localization.AbilityUnexpected,
// Awakened Values
AwakenedCap => localization.AwakenedCap,
AwakenedStatGEQ_01 => localization.AwakenedShouldBeValue,
// Ball
BallAbility => localization.BallAbility,
BallEggCherish => localization.BallEggCherish,
BallEggMaster => localization.BallEggMaster,
BallEnc => localization.BallEnc,
BallEncMismatch => localization.BallEncMismatch,
BallHeavy => localization.BallHeavy,
BallSpecies => localization.BallSpecies,
BallSpeciesPass => localization.BallSpeciesPass,
BallUnavailable => localization.BallUnavailable,
// Contest
ContestZero => localization.ContestZero,
ContestZeroSheen => localization.ContestZeroSheen,
ContestSheenGEQ_0 => localization.ContestSheenGEQ_0,
ContestSheenLEQ_0 => localization.ContestSheenLEQ_0,
// Date & Timestamps
DateOutsideConsoleWindow => localization.DateOutsideConsoleWindow,
DateTimeClockInvalid => localization.DateTimeClockInvalid,
DateOutsideDistributionWindow => localization.DateOutsideDistributionWindow,
// Egg
EggContest => localization.EggContest,
EggEXP => localization.EggEXP,
EggFMetLevel_0 => localization.EggFMetLevel_0,
EggHatchCycles => localization.EggHatchCycles,
EggLocation => localization.EggLocation,
EggLocationInvalid => localization.EggLocationInvalid,
EggLocationNone => localization.EggLocationNone,
EggLocationPalPark => localization.EggLocationPalPark,
EggLocationTrade => localization.EggLocationTrade,
EggLocationTradeFail => localization.EggLocationTradeFail,
EggMetLocationFail => localization.EggMetLocationFail,
EggNature => localization.EggNature,
EggPokeathlon => localization.EggPokeathlon,
EggPP => localization.EggPP,
EggPPUp => localization.EggPPUp,
EggRelearnFlags => localization.EggRelearnFlags,
EggShinyLeaf => localization.EggShinyLeaf,
EggShinyPokeStar => localization.EggShinyPokeStar,
EggSpecies => localization.EggSpecies,
EggUnhatched => localization.EggUnhatched,
// Encounter
EncCondition => localization.EncCondition,
EncConditionBadRNGFrame => localization.EncConditionBadRNGFrame,
EncConditionBadSpecies => localization.EncConditionBadSpecies,
EncGift => localization.EncGift,
EncGiftEggEvent => localization.EncGiftEggEvent,
EncGiftIVMismatch => localization.EncGiftIVMismatch,
EncGiftNicknamed => localization.EncGiftNicknamed,
EncGiftNotFound => localization.EncGiftNotFound,
EncGiftPIDMismatch => localization.EncGiftPIDMismatch,
EncGiftShinyMismatch => localization.EncGiftShinyMismatch,
EncGiftVersionNotDistributed => localization.EncGiftVersionNotDistributed,
EncInvalid => localization.EncInvalid,
EncMasteryInitial => localization.EncMasteryInitial,
EncTradeChangedNickname => localization.EncTradeChangedNickname,
EncTradeChangedOT => localization.EncTradeChangedOT,
EncTradeIndexBad => localization.EncTradeIndexBad,
EncTradeMatch => localization.EncTradeMatch,
EncTradeUnchanged => localization.EncTradeUnchanged,
EncStaticPIDShiny => localization.EncStaticPIDShiny,
EncTypeMatch => localization.EncTypeMatch,
EncTypeMismatch => localization.EncTypeMismatch,
EncUnreleased => localization.EncUnreleased,
EncUnreleasedEMewJP => localization.EncUnreleasedEMewJP,
// E-Reader
EReaderAmerica => localization.EReaderAmerica,
EReaderInvalid => localization.EReaderInvalid,
EReaderJapan => localization.EReaderJapan,
// Effort Values
Effort2Remaining => localization.Effort2Remaining,
EffortAbove252 => localization.EffortAbove252,
EffortAbove510 => localization.EffortAbove510,
EffortAllEqual => localization.EffortAllEqual,
EffortCap100 => localization.EffortCap100,
EffortEgg => localization.EffortEgg,
EffortShouldBeZero => localization.EffortShouldBeZero,
EffortEXPIncreased => localization.EffortEXPIncreased,
EffortUntrainedCap_0 => localization.EffortUntrainedCap,
// Evolution
EvoInvalid => localization.EvoInvalid,
EvoTradeReqOutsider_0 => localization.EvoTradeReqOutsider,
EvoTradeRequired => localization.EvoTradeRequired,
// Form
FormArgumentLEQ_0 => localization.FormArgumentLEQ_0,
FormArgumentGEQ_0 => localization.FormArgumentGEQ_0,
FormArgumentNotAllowed => localization.FormArgumentNotAllowed,
FormArgumentValid => localization.FormArgumentValid,
FormArgumentInvalid => localization.FormArgumentInvalid,
FormBattle => localization.FormBattle,
FormEternal => localization.FormEternal,
FormEternalInvalid => localization.FormEternalInvalid,
FormInvalidGame => localization.FormInvalidGame,
FormInvalidNature => localization.FormInvalidNature,
FormInvalidRange_0 => localization.FormInvalidRange,
FormItemMatches => localization.FormItem,
FormItemInvalid => localization.FormItemInvalid,
FormParty => localization.FormParty,
FormPikachuCosplay => localization.FormPikachuCosplay,
FormPikachuCosplayInvalid => localization.FormPikachuCosplayInvalid,
FormPikachuEventInvalid => localization.FormPikachuEventInvalid,
FormInvalidExpect_0 => localization.FormInvalidExpect_0,
FormValid => localization.FormValid,
FormVivillon => localization.FormVivillon,
FormVivillonEventPre => localization.FormVivillonEventPre,
FormVivillonInvalid => localization.FormVivillonInvalid,
FormVivillonNonNative => localization.FormVivillonNonNative,
// Hyper Training
HyperTrainLevelGEQ_0 => localization.HyperTrainLevelGEQ_0,
HyperPerfectAll => localization.HyperPerfectAll,
HyperPerfectOne => localization.HyperPerfectOne,
HyperPerfectUnavailable => localization.HyperPerfectUnavailable,
// IVs
IVAllEqual_0 => localization.IVAllEqual_0,
IVNotCorrect => localization.IVNotCorrect,
IVFlawlessCountGEQ_0 => localization.IVFlawlessCountGEQ_0,
// Markings
MarkValueOutOfRange_0 => localization.MarkValueOutOfRange_0,
MarkValueShouldBeZero => localization.MarkValueShouldBeZero,
MarkValueUnusedBitsPresent => localization.MarkValueUnusedBitsPresent,
// Moves
MoveEvoFCombination_0 => localization.MoveEvoFCombination_0,
MovePPExpectHealed_0 => localization.MovePPExpectHealed_0,
MovePPTooHigh_0 => localization.MovePPTooHigh_0,
MovePPUpsTooHigh_0 => localization.MovePPUpsTooHigh_0,
MoveShopMasterInvalid_0 => localization.MoveShopMasterInvalid_0,
MoveShopMasterNotLearned_0 => localization.MoveShopMasterNotLearned_0,
MoveShopPurchaseInvalid_0 => localization.MoveShopPurchaseInvalid_0,
MoveTechRecordFlagMissing_0 => localization.MoveTechRecordFlagMissing_0,
// Memory
MemoryStatSocialLEQ_0 => localization.MemoryStatSocialLEQ_0,
// Pokerus
PokerusDaysLEQ_0 => localization.PokerusDaysTooHigh_0,
PokerusStrainUnobtainable_0 => localization.PokerusStrainUnobtainable_0,
// Ribbons
RibbonFInvalid_0 => localization.RibbonFInvalid_0,
RibbonMissing_0 => localization.RibbonMissing_0,
RibbonMarkingInvalid_0 => localization.RibbonMarkingInvalid_0,
RibbonMarkingAffixed_0 => localization.RibbonMarkingAffixed_0,
// Storage
StoredSlotSourceInvalid_0 => localization.StoredSlotSourceInvalid_0,
EncGiftLanguageNotDistributed_0 => localization.EncGiftLanguageNotDistributed,
EncGiftRegionNotDistributed => localization.EncGiftRegionNotDistributed,
EncTradeShouldHaveEvolvedToSpecies_0 => localization.EncTradeShouldHaveEvolvedToSpecies_0,
FatefulGiftMissing => localization.FatefulGiftMissing,
FatefulInvalid => localization.FatefulInvalid,
FatefulMissing => localization.FatefulMissing,
FatefulMystery => localization.FatefulMystery,
FatefulMysteryMissing => localization.FatefulMysteryMissing,
FavoriteMarkingUnavailable => localization.FavoriteMarkingUnavailable,
FormInvalidRangeLEQ_0 => localization.FormInvalidRangeLEQ_0,
G1CatchRateChain => localization.G1CatchRateChain,
G1CatchRateEvo => localization.G1CatchRateEvo,
G1CatchRateItem => localization.G1CatchRateItem,
G1CatchRateMatchPrevious => localization.G1CatchRateMatchPrevious,
G1CatchRateMatchTradeback => localization.G1CatchRateMatchTradeback,
G1CatchRateNone => localization.G1CatchRateNone,
G1CharNick => localization.G1CharNick,
G1CharOT => localization.G1CharOT,
G1OTGender => localization.G1OTGender,
G1Stadium => localization.G1Stadium,
G1Type1Fail => localization.G1Type1Fail,
G1Type2Fail => localization.G1Type2Fail,
G1TypeMatch1 => localization.G1TypeMatch1,
G1TypeMatch2 => localization.G1TypeMatch2,
G1TypeMatchPorygon => localization.G1TypeMatchPorygon,
G1TypePorygonFail => localization.G1TypePorygonFail,
G1TypePorygonFail1 => localization.G1TypePorygonFail1,
G1TypePorygonFail2 => localization.G1TypePorygonFail2,
G2InvalidTileTreeNotFound => localization.G2InvalidTileTreeNotFound,
G2TreeID => localization.G2TreeID,
G2OTGender => localization.G2OTGender,
G3EReader => localization.G3EReader,
G3OTGender => localization.G3OTGender,
G4InvalidTileR45Surf => localization.G4InvalidTileR45Surf,
G5IVAll30 => localization.G5IVAll30,
G5PIDShinyGrotto => localization.G5PIDShinyGrotto,
G5SparkleInvalid => localization.G5SparkleInvalid,
G5SparkleRequired => localization.G5SparkleRequired,
GanbaruStatLEQ_01 => localization.GanbaruStatTooHigh,
GenderInvalidNone => localization.GenderInvalidNone,
GeoBadOrder => localization.GeoBadOrder,
GeoHardwareInvalid => localization.GeoHardwareInvalid,
GeoHardwareRange => localization.GeoHardwareRange,
GeoHardwareValid => localization.GeoHardwareValid,
GeoMemoryMissing => localization.GeoMemoryMissing,
GeoNoCountryHT => localization.GeoNoCountryHT,
GeoNoRegion => localization.GeoNoRegion,
HintEvolvesToSpecies_0 => localization.HintEvolvesToSpecies_0,
HintEvolvesToRareForm_0 => localization.HintEvolvesToRareForm_0,
ItemEgg => localization.ItemEgg,
ItemUnreleased => localization.ItemUnreleased,
LevelEXPThreshold => localization.LevelEXPThreshold,
LevelEXPTooHigh => localization.LevelEXPTooHigh,
LevelMetBelow => localization.LevelMetBelow,
LevelMetGift => localization.LevelMetGift,
LevelMetGiftFail => localization.LevelMetGiftFail,
LevelMetSane => localization.LevelMetSane,
MemoryArgBadCatch_H => localization.MemoryArgBadCatch_H,
MemoryArgBadHatch_H => localization.MemoryArgBadHatch_H,
MemoryArgBadHT => localization.MemoryArgBadHT,
MemoryArgBadID_H => localization.MemoryArgBadID_H,
MemoryArgBadItem_H1 => localization.MemoryArgBadItem_H1,
MemoryArgBadLocation_H => localization.MemoryArgBadLocation_H,
MemoryArgBadMove_H1 => localization.MemoryArgBadMove_H1,
MemoryArgBadOTEgg_H => localization.MemoryArgBadOTEgg_H,
MemoryArgBadSpecies_H1 => localization.MemoryArgBadSpecies_H1,
MemoryArgSpecies_H => localization.MemoryArgSpecies_H,
MemoryCleared_H => localization.MemoryCleared_H,
MemoryValid_H => localization.MemoryValid_H,
MemoryFeelInvalid_H => localization.MemoryFeelInvalid_H,
MemoryHTFlagInvalid => localization.MemoryHTFlagInvalid,
MemoryHTGender_0 => localization.MemoryHTGender_0,
MemoryHTLanguage => localization.MemoryHTLanguage,
MemoryIndexArgHT => localization.MemoryIndexArgHT,
MemoryIndexFeel_H1 => localization.MemoryIndexFeel_H1,
MemoryIndexFeelHTLEQ9 => localization.MemoryIndexFeelHTLEQ9,
MemoryIndexID_H1 => localization.MemoryIndexID_H1,
MemoryIndexIntensity_H1 => localization.MemoryIndexIntensity_H1,
MemoryIndexIntensityHT1 => localization.MemoryIndexIntensityHT1,
MemoryIndexIntensityMin_H1 => localization.MemoryIndexIntensityMin_H1,
MemoryIndexLinkHT => localization.MemoryIndexLinkHT,
MemoryIndexVar_H1 => localization.MemoryIndexVar,
MemoryMissingHT => localization.MemoryMissingHT,
MemoryMissingOT => localization.MemoryMissingOT,
MemorySocialZero => localization.MemorySocialZero,
MemoryStatAffectionHT0 => localization.MemoryStatAffectionHT0,
MemoryStatAffectionOT0 => localization.MemoryStatAffectionOT0,
MemoryStatFriendshipHT0 => localization.MemoryStatFriendshipHT0,
MemoryStatFriendshipOTBaseEvent_0 => localization.MemoryStatFriendshipOTBaseEvent_0,
MemoryStatFullness_0 => localization.MemoryStatFullness_0,
MemoryStatFullnessLEQ_0 => localization.MemoryStatFullnessLEQ_0,
MemoryStatEnjoyment_0 => localization.MemoryStatEnjoyment_0,
MetDetailTimeOfDay => localization.MetDetailTimeOfDay,
MoveKeldeoMismatch => localization.MoveKeldeoMismatch,
MovesShouldMatchRelearnMoves => localization.MovesShouldMatchRelearnMoves,
MoveShopAlphaMoveShouldBeMastered_0 => localization.MoveShopAlphaMoveShouldBeMastered_0,
MoveShopAlphaMoveShouldBeOther => localization.MoveShopAlphaMoveShouldBeOther,
MoveShopAlphaMoveShouldBeZero => localization.MoveShopAlphaMoveShouldBeZero,
NickFlagEggNo => localization.NickFlagEggNo,
NickFlagEggYes => localization.NickFlagEggYes,
NickInvalidChar => localization.NickInvalidChar,
NickLengthLong => localization.NickLengthLong,
NickLengthShort => localization.NickLengthShort,
NickMatchLanguage => localization.NickMatchLanguage,
NickMatchLanguageEgg => localization.NickMatchLanguageEgg,
NickMatchLanguageEggFail => localization.NickMatchLanguageEggFail,
NickMatchLanguageFail => localization.NickMatchLanguageFail,
NickMatchLanguageFlag => localization.NickMatchLanguageFlag,
NickMatchNoOthers => localization.NickMatchNoOthers,
NickMatchNoOthersFail => localization.NickMatchNoOthersFail,
OTLanguage => localization.OTLanguage,
OTLanguageShouldBe_0 => localization.OTLanguageShouldBe_0,
OTLanguageShouldBe_0or1 => localization.OTLanguageShouldBe_0or1,
OTLanguageShouldBeLeq_0 => localization.OTLanguageShouldBeLeq_0,
OTLanguageCannotPlayOnVersion_0 => localization.OTLanguageCannotPlayOnVersion_0,
OTLanguageCannotTransferToConsoleRegion_0 => localization.OTLanguageCannotTransferToConsoleRegion_0,
OTLong => localization.OTLong,
OTShort => localization.OTShort,
OTSuspicious => localization.OTSuspicious,
OT_IDEqual => localization.OT_IDEqual,
OT_IDs0 => localization.OT_IDs0,
OT_SID0 => localization.OT_SID0,
OT_SID0Invalid => localization.OT_SID0Invalid,
OT_TID0 => localization.OT_TID0,
OT_IDInvalid => localization.OT_IDInvalid,
PIDEncryptWurmple => localization.PIDEncryptWurmple,
PIDEncryptZero => localization.PIDEncryptZero,
PIDEqualsEC => localization.PIDEqualsEC,
PIDGenderMatch => localization.PIDGenderMatch,
PIDGenderMismatch => localization.PIDGenderMismatch,
PIDNatureMatch => localization.PIDNatureMatch,
PIDNatureMismatch => localization.PIDNatureMismatch,
PIDTypeMismatch => localization.PIDTypeMismatch,
PIDZero => localization.PIDZero,
RibbonAllValid => localization.RibbonAllValid,
RibbonEgg => localization.RibbonEgg,
StatDynamaxInvalid => localization.StatDynamaxInvalid,
StatIncorrectHeight => localization.StatIncorrectHeight,
StatIncorrectHeightCopy => localization.StatIncorrectHeightCopy,
StatIncorrectHeightValue => localization.StatIncorrectHeightValue,
StatIncorrectWeight => localization.StatIncorrectWeight,
StatIncorrectWeightValue => localization.StatIncorrectWeightValue,
StatInvalidHeightWeight => localization.StatInvalidHeightWeight,
StatIncorrectCP_0 => localization.StatIncorrectCP,
StatGigantamaxInvalid => localization.StatGigantamaxInvalid,
StatGigantamaxValid => localization.StatGigantamaxValid,
StatNatureInvalid => localization.StatNatureInvalid,
StatBattleVersionInvalid => localization.StatBattleVersionInvalid,
StatNobleInvalid => localization.StatNobleInvalid,
StatAlphaInvalid => localization.StatAlphaInvalid,
StoredSourceEgg => localization.StoredSourceEgg,
SuperComplete => localization.SuperComplete,
SuperDistro => localization.SuperDistro,
SuperEgg => localization.SuperEgg,
SuperNoComplete => localization.SuperNoComplete,
SuperNoUnlocked => localization.SuperNoUnlocked,
SuperUnavailable => localization.SuperUnavailable,
SuperUnused => localization.SuperUnused,
TeraTypeIncorrect => localization.TeraTypeIncorrect,
TeraTypeMismatch => localization.TeraTypeMismatch,
TradeNotAvailable => localization.TradeNotAvailable,
TrainerIDNoSeed => localization.TrainerIDNoSeed,
TransferBad => localization.TransferBad,
TransferCurrentHandlerInvalid => localization.TransferCurrentHandlerInvalid,
TransferEgg => localization.TransferEgg,
TransferEggLocationTransporter => localization.TransferEggLocationTransporter,
TransferEggMetLevel => localization.TransferEggMetLevel,
TransferEggVersion => localization.TransferEggVersion,
TransferFlagIllegal => localization.TransferFlagIllegal,
TransferHandlerFlagRequired => localization.TransferHTFlagRequired,
TransferHandlerMismatchName => localization.TransferHTMismatchName,
TransferHandlerMismatchGender => localization.TransferHTMismatchGender,
TransferHandlerMismatchLanguage => localization.TransferHTMismatchLanguage,
TransferMet => localization.TransferMet,
TransferNotPossible => localization.TransferNotPossible,
TransferMetLocation => localization.TransferMetLocation,
TransferNature => localization.TransferNature,
TransferObedienceLevel => localization.TransferObedienceLevel,
TransferKoreanGen4 => localization.TransferKoreanGen4,
TransferEncryptGen6BitFlip => localization.TransferPIDECBitFlip,
TransferEncryptGen6Equals => localization.TransferPIDECEquals,
TransferEncryptGen6Xor => localization.TransferPIDECXor,
TransferTrackerMissing => localization.TransferTrackerMissing,
TransferTrackerShouldBeZero => localization.TransferTrackerShouldBeZero,
TrashBytesExpected => localization.TrashBytesExpected,
TrashBytesMismatchInitial => localization.TrashBytesMismatchInitial,
TrashBytesMissingTerminator => localization.TrashBytesMissingTerminator,
TrashBytesShouldBeEmpty => localization.TrashBytesShouldBeEmpty,
WordFilterInvalidCharacter_0 => localization.WordFilterInvalidCharacter_0,
WordFilterFlaggedPattern_01 => localization.WordFilterFlaggedPattern_01,
WordFilterTooManyNumbers_0 => localization.WordFilterTooManyNumbers_0,
BulkCloneDetectedDetails => localization.BulkCloneDetectedDetails,
BulkCloneDetectedTracker => localization.BulkCloneDetectedTracker,
BulkSharingEncryptionConstantGenerationSame => localization.BulkSharingEncryptionConstantGenerationSame,
BulkSharingEncryptionConstantGenerationDifferent => localization.BulkSharingEncryptionConstantGenerationDifferent,
BulkSharingEncryptionConstantEncounterType => localization.BulkSharingEncryptionConstantRNGType,
BulkSharingPIDGenerationDifferent => localization.BulkSharingPIDGenerationDifferent,
BulkSharingPIDGenerationSame => localization.BulkSharingPIDGenerationSame,
BulkSharingPIDEncounterType => localization.BulkSharingPIDRNGType,
BulkDuplicateMysteryGiftEggReceived => localization.BulkDuplicateMysteryGiftEggReceived,
BulkSharingTrainerIDs => localization.BulkSharingTrainerID,
BulkSharingTrainerVersion => localization.BulkSharingTrainerVersion,
>= MAX => throw new ArgumentOutOfRangeException(nameof(code), code, null),
};
}

View File

@@ -0,0 +1,211 @@
using System;
using System.Collections.Generic;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
public sealed class LegalityLocalizationSet
{
private static readonly Dictionary<string, LegalityLocalizationSet> Cache = new();
public required LegalityCheckLocalization Lines { get; init; }
public required GameStrings Strings { get; init; }
public required EncounterDisplayLocalization Encounter { get; init; }
public required MoveSourceLocalization Moves { get; init; }
public required GeneralLocalization General { get; init; }
public static LegalityLocalizationSet GetLocalization(LanguageID language) => GetLocalization(language.GetLanguageCode());
/// <summary>
/// Gets the localization for the requested language.
/// </summary>
/// <param name="language">Language code</param>
public static LegalityLocalizationSet GetLocalization(string language)
{
if (Cache.TryGetValue(language, out var result))
return result;
result = new LegalityLocalizationSet
{
Strings = GameInfo.GetStrings(language),
Lines = LegalityCheckLocalization.Get(language),
Encounter = EncounterDisplayLocalization.Get(language),
Moves = MoveSourceLocalization.Get(language),
General = GeneralLocalization.Get(language),
};
Cache[language] = result;
return result;
}
/// <summary>
/// Force loads all localizations.
/// </summary>
public static bool ForceLoadAll()
{
bool anyLoaded = false;
foreach (var lang in GameLanguage.AllSupportedLanguages)
{
if (Cache.ContainsKey(lang))
continue;
_ = GetLocalization(lang);
anyLoaded = true;
}
return anyLoaded;
}
/// <summary>
/// Gets all localizations.
/// </summary>
public static IReadOnlyDictionary<string, LegalityLocalizationSet> GetAll()
{
_ = ForceLoadAll();
return Cache;
}
}
public readonly ref struct LegalityLocalizationContext
{
public required LegalityAnalysis Analysis { get; init; }
public required LegalityLocalizationSet Settings { get; init; }
public GameStrings Strings => Settings.Strings;
/// <summary>
/// Creates a complete <see cref="LegalityLocalizationContext"/> with proper localization initialization.
/// </summary>
/// <param name="la">Legality analysis</param>
/// <param name="settings">Export settings</param>
/// <returns>Fully initialized localization context</returns>
public static LegalityLocalizationContext Create(LegalityAnalysis la, LegalityLocalizationSet settings) => new()
{
Analysis = la,
Settings = settings,
};
/// <summary>
/// Creates a complete <see cref="LegalityLocalizationContext"/> using the specified language.
/// </summary>
/// <param name="la">Legality analysis</param>
/// <param name="language">Language code</param>
/// <returns>Fully initialized localization context</returns>
public static LegalityLocalizationContext Create(LegalityAnalysis la, string language = GameLanguage.DefaultLanguage)
=> Create(la, LegalityLocalizationSet.GetLocalization(language));
public string GetRibbonMessage() => RibbonVerifier.GetMessage(Analysis, Strings.Ribbons, Settings.Lines);
public string GetStatName(int displayIndex) => GetSafe(Settings.General.StatNames, displayIndex);
public string GetMoveName(ushort move) => GetSafe(Strings.movelist, move);
public string GetSpeciesName(ushort species) => GetSafe(Strings.specieslist, species);
public string GetConsoleRegion3DS(int index) => GetSafe(Strings.console3ds, index);
public string GetRibbonName(int index) => GetSafe(Strings.ribbons, index);
public string GetLanguageName(int index) => GetSafe(Strings.languageNames, index);
private static string GetSafe(ReadOnlySpan<string> arr, int index)
{
if ((uint)index >= arr.Length)
return string.Empty;
return arr[index];
}
public string GetTrainer(int index) => index switch
{
0 => Settings.General.OriginalTrainer,
1 => Settings.General.HandlingTrainer,
_ => throw new ArgumentOutOfRangeException(nameof(index), $"Invalid Trainer argument: {index}"),
};
/// <summary>
/// Converts a <see cref="LegalityCheckResultCode"/> to its corresponding localized string,
/// applying formatting with the provided argument if needed.
/// </summary>
/// <param name="chk">Raw check value for formatting the string.</param>
/// <param name="verbose">Include Identifier</param>
/// <returns>The localized string from <see cref="LegalityCheckLocalization"/>, with formatting applied if needed</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the enum value doesn't have a corresponding string</exception>
public string Humanize(in CheckResult chk, bool verbose = false)
{
var str = GetInternalString(chk);
var format = Settings.Lines.F0_1;
if (verbose)
str = string.Format(format, chk.Identifier.ToString(), str);
return string.Format(format, Description(chk.Judgement), str);
}
public string Description(Severity s) => s switch
{
Severity.Invalid => Settings.Lines.SInvalid,
Severity.Fishy => Settings.Lines.SFishy,
Severity.Valid => Settings.Lines.SValid,
_ => Settings.Lines.NotImplemented,
};
private string GetInternalString(CheckResult chk)
{
var code = chk.Result;
var template = code.GetTemplate(Settings.Lines);
if (code < FirstWithArgument)
return template;
if (code.IsArgument())
return string.Format(template, chk.Argument);
if (code.IsMove())
return string.Format(template, GetMoveName(chk.Argument));
if (code.IsLanguage())
return string.Format(template, GetLanguageName(chk.Argument), GetLanguageName(Analysis.Entity.Language));
if (code.IsMemory())
return GetMemory(chk, template, code);
// Complex codes may require additional context or arguments.
return GetComplex(chk, template, code);
}
private string GetMemory(CheckResult chk, string template, LegalityCheckResultCode code)
{
if (code is < FirstMemoryWithValue)
return string.Format(template, chk.Value);
if (code is MemoryArgBadItem_H1)
return string.Format(template, GetTrainer(chk.Argument), GetSpeciesName(chk.Argument2));
if (code is MemoryArgBadMove_H1)
return string.Format(template, GetTrainer(chk.Argument), GetMoveName(chk.Argument2));
if (code is MemoryArgBadSpecies_H1)
return string.Format(template, GetTrainer(chk.Argument), GetSpeciesName(chk.Argument2));
return string.Format(template, GetTrainer(chk.Argument), chk.Argument2);
}
private string GetComplex(CheckResult chk, string format, LegalityCheckResultCode code) => code switch
{
< FirstComplex => format, // why are you even here?
RibbonFInvalid_0 => string.Format(format, GetRibbonMessage()),
WordFilterFlaggedPattern_01 => string.Format(format, (WordFilterType)chk.Argument, WordFilter.GetPattern((WordFilterType)chk.Argument, chk.Argument2)),
WordFilterInvalidCharacter_0 => string.Format(format, chk.Argument.ToString("X4")),
AwakenedStatGEQ_01 => string.Format(format, chk.Argument, GetStatName(chk.Argument2)),
GanbaruStatLEQ_01 => string.Format(format, chk.Argument, GetStatName(chk.Argument2)),
OTLanguageCannotTransferToConsoleRegion_0 => string.Format(format, GetConsoleRegion3DS(chk.Argument)),
EncTradeShouldHaveEvolvedToSpecies_0 => string.Format(format, GetSpeciesName(chk.Argument)),
MoveEvoFCombination_0 => string.Format(format, GetSpeciesName(chk.Argument)),
HintEvolvesToSpecies_0 => string.Format(format, GetSpeciesName(chk.Argument)),
RibbonMarkingInvalid_0 => string.Format(format, GetRibbonName(chk.Argument)),
RibbonMarkingAffixed_0 => string.Format(format, GetRibbonName(chk.Argument)),
RibbonMissing_0 => string.Format(format, GetRibbonName(chk.Argument)),
StoredSlotSourceInvalid_0 => string.Format(format, (StorageSlotSource)chk.Argument),
HintEvolvesToRareForm_0 => string.Format(format, chk.Argument == 1),
OTLanguageShouldBe_0or1 => string.Format(format, GetLanguageName(chk.Argument), GetLanguageName(chk.Argument2), GetLanguageName(Analysis.Entity.Language)),
>= MAX => throw new ArgumentOutOfRangeException(nameof(code), code, null),
};
public string FormatMove(in MoveResult move, int index, byte currentFormat)
{
var result = Format(move, index, Settings.Moves.FormatMove);
var gen = move.Generation;
if (currentFormat != gen && gen != 0)
result += $" [Gen{gen}]";
return result;
}
public string FormatRelearn(in MoveResult move, int index) => Format(move, index, Settings.Moves.FormatRelearn);
private string Format(in MoveResult move, int index, string format) => string.Format(format, Description(move.Judgement), index, move.Summary(this));
}

View File

@@ -0,0 +1,45 @@
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
using System.Text.Json.Serialization;
namespace PKHeX.Core;
/// <summary>
/// Localization strings for move learning source information.
/// </summary>
public sealed class MoveSourceLocalization
{
private static readonly MoveSourceLocalizationContext Context = new(LocalizationStorage<MoveSourceLocalization>.Options);
public static readonly LocalizationStorage<MoveSourceLocalization> Cache = new("movesource", Context.MoveSourceLocalization);
public static MoveSourceLocalization Get(string language = GameLanguage.DefaultLanguage) => Cache.Get(language);
public static MoveSourceLocalization Get(LanguageID language) => Cache.Get(language.GetLanguageCode());
/// <summary>Format text for exporting a legality check result for a Move.</summary>
public required string FormatMove { get; init; } = "{0} Move {1}: {2}";
/// <summary>Format text for exporting a legality check result for a Relearn Move.</summary>
public required string FormatRelearn { get; init; } = "{0} Relearn Move {1}: {2}";
// Basic source types
public required string SourceDefault { get; init; } = "Default move.";
public required string SourceDuplicate { get; init; } = "Duplicate Move.";
public required string SourceEmpty { get; init; } = "Empty Move.";
public required string SourceInvalid { get; init; } = "Invalid Move.";
public required string SourceLevelUp { get; init; } = "Learned by Level-up.";
public required string SourceRelearn { get; init; } = "Relearnable Move.";
public required string SourceSpecial { get; init; } = "Special Non-Relearn Move.";
public required string SourceTMHM { get; init; } = "Learned by TM/HM.";
public required string SourceTutor { get; init; } = "Learned by Move Tutor.";
public required string SourceShared { get; init; } = "Shared Non-Relearn Move.";
// Egg-related sources
public required string RelearnEgg { get; init; } = "Base Egg move.";
public required string EggInherited { get; init; } = "Inherited Egg move.";
public required string EggTMHM { get; init; } = "Inherited TM/HM move.";
public required string EggInheritedTutor { get; init; } = "Inherited tutor move.";
public required string EggInvalid { get; init; } = "Not an expected Egg move.";
public required string EggLevelUp { get; init; } = "Inherited move learned by Level-up.";
}
[JsonSerializable(typeof(MoveSourceLocalization))]
public sealed partial class MoveSourceLocalizationContext : JsonSerializerContext;

View File

@@ -1,6 +1,6 @@
using System;
using static PKHeX.Core.LearnMethod;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckLocalization;
namespace PKHeX.Core;
@@ -94,62 +94,62 @@ public static class EggSourceUtil
/// <summary>
/// Unboxes the parse result and returns a user-friendly string for the move result.
/// </summary>
public static string GetSourceString(Array parse, byte generation, int index)
public static string GetSourceString(Array parse, byte generation, int index, MoveSourceLocalization loc)
{
if (index >= parse.Length)
return LMoveSourceEmpty;
return loc.SourceEmpty;
return generation switch
{
2 => ((EggSource2[])parse)[index].GetSourceString(),
3 or 4 => ((EggSource34[])parse)[index].GetSourceString(),
5 => ((EggSource5[])parse)[index].GetSourceString(),
>= 6 => ((EggSource6[])parse)[index].GetSourceString(),
_ => LMoveSourceEmpty,
2 => ((EggSource2[])parse)[index].GetSourceString(loc),
3 or 4 => ((EggSource34[])parse)[index].GetSourceString(loc),
5 => ((EggSource5[])parse)[index].GetSourceString(loc),
>= 6 => ((EggSource6[])parse)[index].GetSourceString(loc),
_ => loc.SourceEmpty,
};
}
private static string GetSourceString(this EggSource2 source) => source switch
private static string GetSourceString(this EggSource2 source, MoveSourceLocalization loc) => source switch
{
EggSource2.Base => LMoveRelearnEgg,
EggSource2.FatherEgg => LMoveEggInherited,
EggSource2.FatherTM => LMoveEggTMHM,
EggSource2.ParentLevelUp => LMoveEggLevelUp,
EggSource2.Tutor => LMoveEggInheritedTutor,
EggSource2.Base => loc.RelearnEgg,
EggSource2.FatherEgg => loc.EggInherited,
EggSource2.FatherTM => loc.EggTMHM,
EggSource2.ParentLevelUp => loc.EggLevelUp,
EggSource2.Tutor => loc.EggInheritedTutor,
EggSource2.Max => "Any",
_ => LMoveEggInvalid,
_ => loc.EggInvalid,
};
private static string GetSourceString(this EggSource34 source) => source switch
private static string GetSourceString(this EggSource34 source, MoveSourceLocalization loc) => source switch
{
EggSource34.Base => LMoveRelearnEgg,
EggSource34.FatherEgg => LMoveEggInherited,
EggSource34.FatherTM => LMoveEggTMHM,
EggSource34.ParentLevelUp => LMoveEggLevelUp,
EggSource34.Base => loc.RelearnEgg,
EggSource34.FatherEgg => loc.EggInherited,
EggSource34.FatherTM => loc.EggTMHM,
EggSource34.ParentLevelUp => loc.EggLevelUp,
EggSource34.Max => "Any",
EggSource34.VoltTackle => LMoveSourceSpecial,
_ => LMoveEggInvalid,
EggSource34.VoltTackle => loc.SourceSpecial,
_ => loc.EggInvalid,
};
private static string GetSourceString(this EggSource5 source) => source switch
private static string GetSourceString(this EggSource5 source, MoveSourceLocalization loc) => source switch
{
EggSource5.Base => LMoveRelearnEgg,
EggSource5.FatherEgg => LMoveEggInherited,
EggSource5.ParentLevelUp => LMoveEggLevelUp,
EggSource5.FatherTM => LMoveEggTMHM,
EggSource5.Base => loc.RelearnEgg,
EggSource5.FatherEgg => loc.EggInherited,
EggSource5.ParentLevelUp => loc.EggLevelUp,
EggSource5.FatherTM => loc.EggTMHM,
EggSource5.Max => "Any",
EggSource5.VoltTackle => LMoveSourceSpecial,
_ => LMoveEggInvalid,
EggSource5.VoltTackle => loc.SourceSpecial,
_ => loc.EggInvalid,
};
private static string GetSourceString(this EggSource6 source) => source switch
private static string GetSourceString(this EggSource6 source, MoveSourceLocalization loc) => source switch
{
EggSource6.Base => LMoveRelearnEgg,
EggSource6.ParentLevelUp => LMoveEggLevelUp,
EggSource6.ParentEgg => LMoveEggInherited,
EggSource6.Base => loc.RelearnEgg,
EggSource6.ParentLevelUp => loc.EggLevelUp,
EggSource6.ParentEgg => loc.EggInherited,
EggSource6.Max => "Any",
EggSource6.VoltTackle => LMoveSourceSpecial,
_ => LMoveEggInvalid,
EggSource6.VoltTackle => loc.SourceSpecial,
_ => loc.EggInvalid,
};
/// <summary>

View File

@@ -36,6 +36,8 @@ internal static class EvolutionRestrictions
_ => NONE,
};
public static bool IsEvolvedSpeciesFormRare(uint encryptionConstant) => encryptionConstant % 100 is 0;
/// <summary>
/// Gets the species-form that it will evolve into.
/// </summary>

View File

@@ -1,3 +1,4 @@
using System;
using static PKHeX.Core.LanguageID;
namespace PKHeX.Core;
@@ -8,6 +9,14 @@ namespace PKHeX.Core;
/// <remarks>These values were specific to the 3DS games (Generations 6 and 7, excluding LGP/E)</remarks>
public static class Locale3DS
{
/// <summary>
/// List of defined Console Regions.
/// </summary>
/// <remarks>
/// 3 was reserved for AUS, but was later merged into Europe, so it is not a valid locale code.
/// </remarks>
public static ReadOnlySpan<byte> DefinedLocales => [0, 1, 2, /*3,*/ 4, 5, 6];
/// <summary>
/// Compares the <see cref="IRegionOrigin.ConsoleRegion"/> and <see cref="IRegionOrigin.Country"/> to determine if the country is available within that region.
/// </summary>

View File

@@ -25,5 +25,5 @@ public abstract class MemoryContext
public abstract bool CanHaveIntensity(byte memory, byte intensity);
public abstract bool CanHaveFeeling(byte memory, byte feeling, ushort argument);
public abstract int GetMinimumIntensity(byte memory);
public abstract byte GetMinimumIntensity(byte memory);
}

View File

@@ -154,5 +154,5 @@ public static byte GetMinimumIntensity6(int memory)
public override bool CanHaveIntensity(byte memory, byte intensity) => CanHaveIntensity6(memory, intensity);
public override bool CanHaveFeeling(byte memory, byte feeling, ushort argument) => CanHaveFeeling6(memory, feeling, argument);
public override int GetMinimumIntensity(byte memory) => GetMinimumIntensity6(memory);
public override byte GetMinimumIntensity(byte memory) => GetMinimumIntensity6(memory);
}

View File

@@ -238,5 +238,5 @@ public static byte GetMinimumIntensity8(int memory)
public override bool CanHaveIntensity(byte memory, byte intensity) => CanHaveIntensity8(memory, intensity);
public override bool CanHaveFeeling(byte memory, byte feeling, ushort argument) => CanHaveFeeling8(memory, feeling, argument);
public override int GetMinimumIntensity(byte memory) => GetMinimumIntensity8(memory);
public override byte GetMinimumIntensity(byte memory) => GetMinimumIntensity8(memory);
}

View File

@@ -1,5 +1,3 @@
using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core;
/// <summary>
@@ -10,12 +8,12 @@ namespace PKHeX.Core;
/// <param name="Variable">Argument for the memory</param>
/// <param name="Intensity">How strongly they remember the memory</param>
/// <param name="Feeling">How they feel about the memory</param>
public readonly record struct MemoryVariableSet(string Handler, byte MemoryID, ushort Variable, byte Intensity, byte Feeling)
public readonly record struct MemoryVariableSet(byte Handler, byte MemoryID, ushort Variable, byte Intensity, byte Feeling)
{
public static MemoryVariableSet Read(ITrainerMemories pk, int handler) => handler switch
{
0 => new(L_XOT, pk.OriginalTrainerMemory, pk.OriginalTrainerMemoryVariable, pk.OriginalTrainerMemoryIntensity, pk.OriginalTrainerMemoryFeeling), // OT
1 => new(L_XHT, pk.HandlingTrainerMemory, pk.HandlingTrainerMemoryVariable, pk.HandlingTrainerMemoryIntensity, pk.HandlingTrainerMemoryFeeling), // HT
_ => new(L_XOT, 0, 0, 0, 0),
0 => new(0, pk.OriginalTrainerMemory, pk.OriginalTrainerMemoryVariable, pk.OriginalTrainerMemoryIntensity, pk.OriginalTrainerMemoryFeeling), // OT
1 => new(1, pk.HandlingTrainerMemory, pk.HandlingTrainerMemoryVariable, pk.HandlingTrainerMemoryIntensity, pk.HandlingTrainerMemoryFeeling), // HT
_ => new(0, 0, 0, 0, 0),
};
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
namespace PKHeX.Core;
@@ -33,7 +32,7 @@ internal static Regex[] LoadPatterns(ReadOnlySpan<char> patterns)
/// <param name="regexes">Console regex set to check against.</param>
/// <param name="regMatch">Matching regex that filters the phrase.</param>
/// <returns>Boolean result if the message is filtered or not.</returns>
internal static bool TryMatch(ReadOnlySpan<char> message, ReadOnlySpan<Regex> regexes, [NotNullWhen(true)] out string? regMatch)
internal static bool TryMatch(ReadOnlySpan<char> message, ReadOnlySpan<Regex> regexes, out int regMatch)
{
// Clean the string
Span<char> clean = stackalloc char[message.Length];
@@ -41,76 +40,114 @@ internal static bool TryMatch(ReadOnlySpan<char> message, ReadOnlySpan<Regex> re
if (ctr != clean.Length)
clean = clean[..ctr];
foreach (var regex in regexes)
for (var i = 0; i < regexes.Length; i++)
{
var regex = regexes[i];
foreach (var _ in regex.EnumerateMatches(clean))
{
regMatch = regex.ToString();
regMatch = i;
return true;
}
}
regMatch = null;
regMatch = -1;
return false;
}
/// <inheritdoc cref="IsFiltered(ReadOnlySpan{char}, out string?, EntityContext, EntityContext)"/>
public static bool IsFiltered(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch,
EntityContext current)
=> IsFiltered(message, out regMatch, current, current);
/// <inheritdoc cref="IsFiltered(ReadOnlySpan{char}, EntityContext, EntityContext, out WordFilterType, out int)"/>
public static bool IsFiltered(ReadOnlySpan<char> message, EntityContext current, out WordFilterType type, out int regMatch)
=> IsFiltered(message, current, current, out type, out regMatch);
/// <summary>
/// Checks to see if a phrase contains filtered content.
/// </summary>
/// <param name="message">Phrase to check for</param>
/// <param name="regMatch">Matching regex that filters the phrase.</param>
/// <param name="current">Current context to check.</param>
/// <param name="original">Earliest context to check.</param>
/// <param name="type">Word filter set that matched the phrase.</param>
/// <param name="regMatch">Matching regex that filters the phrase.</param>
/// <returns>Boolean result if the message is filtered or not.</returns>
public static bool IsFiltered(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch,
EntityContext current, EntityContext original)
public static bool IsFiltered(ReadOnlySpan<char> message, EntityContext current, EntityContext original, out WordFilterType type, out int regMatch)
{
regMatch = null;
regMatch = -1;
if (message.IsWhiteSpace() || message.Length <= 1)
{
type = WordFilterType.None;
return false;
}
// Only check against the single filter if requested
if (ParseSettings.Settings.WordFilter.DisableWordFilterPastGen)
return IsFilteredCurrentOnly(message, ref regMatch, current, original);
{
if (IsFilteredCurrentOnly(message, current, original, out regMatch))
{
type = WordFilterTypeExtensions.GetName(current);
return true;
}
type = WordFilterType.None;
return false;
}
return IsFilteredLookBack(message, out regMatch, current, original);
return IsFilteredLookBack(message, current, original, out type, out regMatch);
}
private static bool IsFilteredCurrentOnly(ReadOnlySpan<char> message, [NotNullWhen(true)] ref string? regMatch,
EntityContext current, EntityContext original) => current switch
private static bool IsFilteredCurrentOnly(ReadOnlySpan<char> message, EntityContext current, EntityContext original, out int regMatch)
{
EntityContext.Gen5 => WordFilter5.IsFiltered(message, out regMatch),
EntityContext.Gen6 => WordFilter3DS.IsFilteredGen6(message, out regMatch),
EntityContext.Gen7 when original is EntityContext.Gen6
=> WordFilter3DS.IsFilteredGen6(message, out regMatch),
EntityContext.Gen7 => WordFilter3DS.IsFilteredGen7(message, out regMatch),
_ => current.GetConsole() switch
regMatch = 0;
return current switch
{
GameConsole.NX => WordFilterNX.IsFiltered(message, out regMatch, original),
_ => false,
},
};
EntityContext.Gen5 => WordFilter5.IsFiltered(message, out regMatch),
private static bool IsFilteredLookBack(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch,
EntityContext current, EntityContext original)
EntityContext.Gen6 => WordFilter3DS.IsFilteredGen6(message, out regMatch),
EntityContext.Gen7 when original is EntityContext.Gen6
=> WordFilter3DS.IsFilteredGen6(message, out regMatch),
EntityContext.Gen7 => WordFilter3DS.IsFilteredGen7(message, out regMatch),
_ => current.GetConsole() switch
{
GameConsole.NX => WordFilterNX.IsFiltered(message, out regMatch, original),
_ => false,
},
};
}
private static bool IsFilteredLookBack(ReadOnlySpan<char> message, EntityContext current, EntityContext original, out WordFilterType type, out int regMatch)
{
// Switch 2 backwards transfer? Won't know for another couple years.
if (WordFilterNX.IsFiltered(message, out regMatch, original))
{
type = WordFilterType.NintendoSwitch;
return true;
}
var generation = original.Generation();
if (generation > 7 || original is EntityContext.Gen7b)
{
type = WordFilterType.None;
return false;
if (WordFilter3DS.IsFiltered(message, out regMatch, original))
return true;
}
return generation == 5 && WordFilter5.IsFiltered(message, out regMatch);
if (WordFilter3DS.IsFiltered(message, out regMatch, original))
{
type = WordFilterType.Nintendo3DS;
return true;
}
if (generation == 5 && WordFilter5.IsFiltered(message, out regMatch))
{
type = WordFilterType.Gen5;
return true;
}
// no other word filters (none in Gen3 or Gen4)
type = WordFilterType.None;
return false;
}
public static string GetPattern(WordFilterType chkArgument, int index) => chkArgument switch
{
WordFilterType.Gen5 => WordFilter5.GetPattern(index),
WordFilterType.Nintendo3DS => WordFilter3DS.GetPattern(index),
WordFilterType.NintendoSwitch => WordFilterNX.GetPattern(index),
_ => string.Empty,
};
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
namespace PKHeX.Core;
@@ -12,24 +11,25 @@ public static class WordFilter3DS
{
private static readonly Regex[] Regexes = WordFilter.LoadPatterns(Util.GetStringResource("badwords_3ds"));
public static string GetPattern(int index) => Regexes[index].ToString();
/// <summary>
/// Regex patterns to check against
/// </summary>
/// <remarks>No need to keep the original pattern strings around; the <see cref="Regex"/> object retrieves this via <see cref="Regex.ToString()"/></remarks>
private static readonly ConcurrentDictionary<string, string?>.AlternateLookup<ReadOnlySpan<char>> Lookup =
new ConcurrentDictionary<string, string?>().GetAlternateLookup<ReadOnlySpan<char>>();
private static readonly ConcurrentDictionary<string, int>.AlternateLookup<ReadOnlySpan<char>> PreviouslyChecked =
new ConcurrentDictionary<string, int>().GetAlternateLookup<ReadOnlySpan<char>>();
private const int MAX_COUNT = (1 << 17) - 1; // arbitrary cap for max dictionary size
private const int NoMatch = -1;
/// <inheritdoc cref="IsFiltered"/>
/// <remarks>Generation 6 is case-sensitive.</remarks>
public static bool IsFilteredGen6(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch)
=> IsFiltered(message, out regMatch, EntityContext.Gen6);
public static bool IsFilteredGen6(ReadOnlySpan<char> message, out int regMatch) => IsFiltered(message, out regMatch, EntityContext.Gen6);
/// <inheritdoc cref="IsFiltered"/>
/// <remarks>Generation 7 is case-insensitive.</remarks>
public static bool IsFilteredGen7(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch)
=> IsFiltered(message, out regMatch, EntityContext.Gen7);
public static bool IsFilteredGen7(ReadOnlySpan<char> message, out int regMatch) => IsFiltered(message, out regMatch, EntityContext.Gen7);
/// <summary>
/// Checks to see if a phrase contains filtered content.
@@ -38,27 +38,27 @@ public static bool IsFilteredGen7(ReadOnlySpan<char> message, [NotNullWhen(true)
/// <param name="regMatch">Matching regex that filters the phrase.</param>
/// <param name="original">Earliest context to check.</param>
/// <returns>Boolean result if the message is filtered or not.</returns>
public static bool IsFiltered(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch, EntityContext original)
public static bool IsFiltered(ReadOnlySpan<char> message, out int regMatch, EntityContext original)
{
regMatch = null;
regMatch = NoMatch;
if (IsSpeciesName(message, original))
return false;
// Check dictionary
if (Lookup.TryGetValue(message, out regMatch))
return regMatch is not null;
if (PreviouslyChecked.TryGetValue(message, out regMatch))
return regMatch is not NoMatch;
// not in dictionary, check patterns
if (WordFilter.TryMatch(message, Regexes, out regMatch))
{
Lookup.TryAdd(message, regMatch);
PreviouslyChecked.TryAdd(message, regMatch);
return true;
}
// didn't match any pattern, cache result
if ((Lookup.Dictionary.Count & ~MAX_COUNT) != 0)
Lookup.Dictionary.Clear(); // reset
Lookup.TryAdd(message, regMatch = null);
if ((PreviouslyChecked.Dictionary.Count & ~MAX_COUNT) != 0)
PreviouslyChecked.Dictionary.Clear(); // reset
PreviouslyChecked.TryAdd(message, regMatch = NoMatch);
return false;
}

View File

@@ -1,23 +1,32 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace PKHeX.Core;
public static class WordFilter5
{
private static readonly HashSet<string>.AlternateLookup<ReadOnlySpan<char>> Words =
new HashSet<string>(Util.GetStringList("badwords_gen5"))
private static readonly string[] BadWords = Util.GetStringList("badwords_gen5");
private static readonly Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>> Words = GetDictionary(BadWords)
.GetAlternateLookup<ReadOnlySpan<char>>();
public static string GetPattern(int index) => BadWords[index];
private static Dictionary<string, int> GetDictionary(ReadOnlySpan<string> input)
{
var result = new Dictionary<string, int>(input.Length);
for (int i = 0; i < input.Length; i++)
result[input[i]] = i;
return result;
}
/// <summary>
/// Checks to see if a phrase contains filtered content.
/// </summary>
/// <param name="message">Phrase to check</param>
/// <param name="match">Blocked word that filters the phrase.</param>
/// <returns>Boolean result if the message is filtered or not.</returns>
public static bool IsFiltered(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? match)
public static bool IsFiltered(ReadOnlySpan<char> message, out int match)
{
Span<char> clean = stackalloc char[message.Length];
Normalize(message, clean);

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
namespace PKHeX.Core;
@@ -16,13 +15,16 @@ public static class WordFilterNX
/// <remarks>No need to keep the original pattern strings around; the <see cref="Regex"/> object retrieves this via <see cref="Regex.ToString()"/></remarks>
private static readonly Regex[] Regexes = WordFilter.LoadPatterns(Util.GetStringResource("badwords_switch"));
public static string GetPattern(int index) => Regexes[index].ToString();
/// <summary>
/// Due to some messages repeating (Trainer names), keep a list of repeated values for faster lookup.
/// </summary>
private static readonly ConcurrentDictionary<string, string?>.AlternateLookup<ReadOnlySpan<char>> Lookup =
new ConcurrentDictionary<string, string?>().GetAlternateLookup<ReadOnlySpan<char>>();
private static readonly ConcurrentDictionary<string, int>.AlternateLookup<ReadOnlySpan<char>> Lookup =
new ConcurrentDictionary<string, int>().GetAlternateLookup<ReadOnlySpan<char>>();
private const int MAX_COUNT = (1 << 17) - 1; // arbitrary cap for max dictionary size
private const int NoMatch = -1;
/// <summary>
/// Checks to see if a phrase contains filtered content.
@@ -31,15 +33,15 @@ public static class WordFilterNX
/// <param name="regMatch">Matching regex that filters the phrase.</param>
/// <param name="original">Earliest context to check.</param>
/// <returns>Boolean result if the message is filtered or not.</returns>
public static bool IsFiltered(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch, EntityContext original)
public static bool IsFiltered(ReadOnlySpan<char> message, out int regMatch, EntityContext original)
{
regMatch = null;
regMatch = NoMatch;
if (IsSpeciesName(message, original))
return false;
// Check dictionary
if (Lookup.TryGetValue(message, out regMatch))
return regMatch is not null;
return regMatch is not NoMatch;
// not in dictionary, check patterns
if (WordFilter.TryMatch(message, Regexes, out regMatch))
@@ -51,7 +53,7 @@ public static bool IsFiltered(ReadOnlySpan<char> message, [NotNullWhen(true)] ou
// didn't match any pattern, cache result
if ((Lookup.Dictionary.Count & ~MAX_COUNT) != 0)
Lookup.Dictionary.Clear(); // reset
Lookup.TryAdd(message, regMatch = null);
Lookup.TryAdd(message, regMatch = NoMatch);
return false;
}

View File

@@ -0,0 +1,60 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Word filter contexts used by different <see cref="EntityContext"/>.
/// </summary>
public enum WordFilterType
{
/// <summary>
/// No strict filtering is applied.
/// </summary>
None,
/// <summary>
/// Generation 5 word filter, used for games like Black/White and Black 2/White 2.
/// </summary>
/// <remarks>
/// <see cref="WordFilter5"/>
/// </remarks>
Gen5,
/// <summary>
/// Generation 6 and 7 word filter, used for games like X/Y, Sun/Moon, Ultra Sun/Ultra Moon, and Sword/Shield.
/// </summary>
/// <remarks>
/// See <see cref="WordFilter3DS"/>
/// </remarks>
Nintendo3DS,
/// <summary>
/// Generation 8+ word filter.
/// </summary>
/// <remarks>
/// See <see cref="WordFilterNX"/>
/// </remarks>
NintendoSwitch,
}
public static class WordFilterTypeExtensions
{
public static WordFilterType GetName(EntityContext type) => type.GetConsole() switch
{
GameConsole.NX => WordFilterType.NintendoSwitch,
_ => type.Generation() switch
{
5 => WordFilterType.Gen5,
6 or 7 => WordFilterType.Nintendo3DS,
_ => WordFilterType.None,
},
};
public static Type GetType(WordFilterType type) => type switch
{
WordFilterType.Gen5 => typeof(WordFilter5),
WordFilterType.Nintendo3DS => typeof(WordFilter3DS),
WordFilterType.NintendoSwitch => typeof(WordFilterNX),
_ => throw new ArgumentOutOfRangeException(nameof(type), type, $"Invalid {nameof(WordFilterType)} value."),
};
}

View File

@@ -47,7 +47,6 @@ public static void Initialize(LegalitySettings settings)
public static IReadOnlyList<string> MoveStrings { get; private set; } = Util.GetMovesList(GameLanguage.DefaultLanguage);
public static IReadOnlyList<string> SpeciesStrings { get; private set; } = Util.GetSpeciesList(GameLanguage.DefaultLanguage);
public static string GetMoveName(ushort move) => move >= MoveStrings.Count ? LegalityCheckStrings.L_AError : MoveStrings[move];
public static void ChangeLocalizationStrings(IReadOnlyList<string> moves, IReadOnlyList<string> species)
{

View File

@@ -168,4 +168,9 @@ public enum CheckIdentifier : byte
/// The <see cref="CheckResult"/> pertains to the <see cref="PKM"/> <see cref="StorageSlotType"/>.
/// </summary>
SlotType,
/// <summary>
/// The <see cref="CheckResult"/> pertains to the Current Handler (not OT) of the <see cref="PKM"/> data.
/// </summary>
Handler,
}

View File

@@ -1,16 +1,51 @@
using System.Runtime.InteropServices;
namespace PKHeX.Core;
/// <summary>
/// Result of a Legality Check
/// </summary>
[System.Diagnostics.DebuggerDisplay($"{{{nameof(Identifier)}}}: {{{nameof(Comment)}}}")]
// ReSharper disable once NotAccessedPositionalProperty.Global
public readonly record struct CheckResult(Severity Judgement, CheckIdentifier Identifier, string Comment)
[System.Diagnostics.DebuggerDisplay($"{{{nameof(Identifier)}}}: {{{nameof(Result)}}}")]
[StructLayout(LayoutKind.Explicit, Size = 8)]
public readonly record struct CheckResult
{
/// <summary> Indicates whether the result is valid. </summary>
public bool Valid => Judgement != Severity.Invalid;
public string Rating => Judgement.Description();
internal CheckResult(CheckIdentifier i) : this(Severity.Valid, i, LegalityCheckStrings.L_AValid) { }
/// <summary> Indicates if the result isn't a generic "Valid" result, and might be worth displaying to the user. </summary>
public bool IsNotGeneric() => Result != LegalityCheckResultCode.Valid;
public string Format(string format) => string.Format(format, Rating, Comment);
/// <summary> Indicates the severity of the result. </summary>
[field: FieldOffset(0)] public required Severity Judgement { get; init; }
/// <summary> Identifier for the check group that produced this result. </summary>
[field: FieldOffset(1)] public required CheckIdentifier Identifier { get; init; }
/// <summary> Result code for the check, indicating the analysis performed/flagged to arrive at the <see cref="Judgement"/>. </summary>
[field: FieldOffset(2)] public required LegalityCheckResultCode Result { get; init; }
#region Hint Parameters used for Human-readable messages
/// <summary> Raw value used for hints, or storing a 32-bit number hint. </summary>
[field: FieldOffset(4)] public uint Value { get; init; }
/// <summary> First argument used for hints, or storing a 16-bit number hint. </summary>
[field: FieldOffset(4)] public ushort Argument { get; init; }
/// <summary> Second argument used for hints, or storing a 16-bit number hint. </summary>
[field: FieldOffset(6)] public ushort Argument2 { get; init; }
#endregion
/// <summary>
/// Simple method to create a valid result with the given identifier.
/// </summary>
public static CheckResult GetValid(CheckIdentifier ident) => Get(Severity.Valid, ident, LegalityCheckResultCode.Valid);
/// <summary>
/// Simple method to create a result with the given parameters.
/// </summary>
public static CheckResult Get(Severity judge, CheckIdentifier ident, LegalityCheckResultCode code, uint value = 0) => new()
{
Judgement = judge,
Identifier = ident,
Result = code,
Value = value,
};
}

View File

@@ -0,0 +1,458 @@
namespace PKHeX.Core;
/// <summary>
/// Represents the specific type of legality result that was generated during a legality check.
/// </summary>
/// <remarks>
/// When a result is generated, instead of storing the string directly, an instance of this enum is stored instead.
/// The string is only fetched from <see cref="LegalityCheckLocalization"/> when needed for display.
/// </remarks>
public enum LegalityCheckResultCode : ushort
{
// General Strings
/// <summary>Default text for indicating validity.</summary>
Valid,
/// <summary>Default text for indicating an error has occurred.</summary>
Error,
// The order of the enum is important only for humanization; keep sorted by handling, with groups by category and functionality.
// Ability
AbilityCapsuleUsed,
AbilityPatchUsed,
AbilityPatchRevertUsed,
AbilityFlag,
AbilityHiddenFail,
AbilityHiddenUnavailable,
AbilityMismatch,
AbilityMismatch3,
AbilityMismatchFlag,
AbilityMismatchGift,
AbilityMismatchPID,
AbilityUnexpected,
// Awakened Values
AwakenedCap,
// Ball
BallAbility,
BallEggCherish,
BallEggMaster,
BallEnc,
BallEncMismatch,
BallHeavy,
BallSpecies,
BallSpeciesPass,
BallUnavailable,
// Contest
ContestZero,
ContestZeroSheen,
// Date & Timestamps
DateOutsideConsoleWindow,
DateTimeClockInvalid,
DateOutsideDistributionWindow,
// Egg
EggContest,
EggEXP,
EggHatchCycles,
EggLocation,
EggLocationInvalid,
EggLocationNone,
EggLocationPalPark,
EggLocationTrade,
EggLocationTradeFail,
EggMetLocationFail,
EggNature,
EggPokeathlon,
EggPP,
EggPPUp,
EggRelearnFlags,
EggShinyLeaf,
EggShinyPokeStar,
EggSpecies,
EggUnhatched,
// Encounter
EncCondition,
EncConditionBadRNGFrame,
EncConditionBadSpecies,
EncGift,
EncGiftEggEvent,
EncGiftIVMismatch,
EncGiftNicknamed,
EncGiftNotFound,
EncGiftPIDMismatch,
EncGiftShinyMismatch,
EncGiftVersionNotDistributed,
EncGiftRegionNotDistributed,
EncInvalid,
EncMasteryInitial,
EncTradeChangedNickname,
EncTradeChangedOT,
EncTradeIndexBad,
EncTradeMatch,
EncTradeUnchanged,
EncStaticPIDShiny,
EncTypeMatch,
EncTypeMismatch,
EncUnreleased,
EncUnreleasedEMewJP,
// E-Reader
EReaderAmerica,
EReaderInvalid,
EReaderJapan,
// Effort Values
Effort2Remaining,
EffortAbove252,
EffortAbove510,
EffortAllEqual,
EffortCap100,
EffortEgg,
EffortShouldBeZero,
EffortEXPIncreased,
// Evolution
EvoInvalid,
EvoTradeRequired,
// Fateful
FatefulGiftMissing,
FatefulInvalid,
FatefulMissing,
FatefulMystery,
FatefulMysteryMissing,
// Favorite Marking
FavoriteMarkingUnavailable,
// Form
FormArgumentNotAllowed,
FormArgumentValid,
FormArgumentInvalid,
FormBattle,
FormEternal,
FormEternalInvalid,
FormInvalidGame,
FormInvalidNature,
FormItemMatches,
FormItemInvalid,
FormParty,
FormPikachuCosplay,
FormPikachuCosplayInvalid,
FormPikachuEventInvalid,
FormInvalidExpect_0,
FormValid,
FormVivillon,
FormVivillonEventPre,
FormVivillonInvalid,
FormVivillonNonNative,
// Generation 1 & 2
G1CatchRateChain,
G1CatchRateEvo,
G1CatchRateItem,
G1CatchRateMatchPrevious,
G1CatchRateMatchTradeback,
G1CatchRateNone,
G1CharNick,
G1CharOT,
G1OTGender,
G1Stadium,
G1Type1Fail,
G1Type2Fail,
G1TypeMatch1,
G1TypeMatch2,
G1TypeMatchPorygon,
G1TypePorygonFail,
G1TypePorygonFail1,
G1TypePorygonFail2,
G2InvalidTileTreeNotFound,
G2TreeID,
G2OTGender,
// Generation 3+
G3EReader,
G3OTGender,
G4InvalidTileR45Surf,
G5IVAll30,
G5PIDShinyGrotto,
G5SparkleInvalid,
G5SparkleRequired,
// Gender
GenderInvalidNone,
// Geography
GeoBadOrder,
GeoHardwareInvalid,
GeoHardwareRange,
GeoHardwareValid,
GeoMemoryMissing,
GeoNoCountryHT,
GeoNoRegion,
// Hints
// Hyper Training
HyperPerfectAll,
HyperPerfectOne,
HyperPerfectUnavailable,
// Item
ItemEgg,
ItemUnreleased,
// IVs
IVNotCorrect,
// Level
LevelEXPThreshold,
LevelEXPTooHigh,
LevelMetBelow,
LevelMetGift,
LevelMetGiftFail,
LevelMetSane,
// Markings
MarkValueShouldBeZero,
MarkValueUnusedBitsPresent,
// Memory
MemoryArgBadHT,
MemoryHTFlagInvalid,
MemoryHTLanguage,
MemoryIndexArgHT,
MemoryIndexFeelHTLEQ9,
MemoryIndexIntensityHT1,
MemoryIndexLinkHT,
MemoryIndexVar_H1,
MemoryMissingHT,
MemoryMissingOT,
MemorySocialZero,
MemoryStatAffectionHT0,
MemoryStatAffectionOT0,
MemoryStatFriendshipHT0,
// Met Detail
MetDetailTimeOfDay,
// Moves - General
MoveKeldeoMismatch,
MovesShouldMatchRelearnMoves,
// Moves - Shop & Alpha
MoveShopAlphaMoveShouldBeOther,
MoveShopAlphaMoveShouldBeZero,
// Nickname
NickFlagEggNo,
NickFlagEggYes,
NickInvalidChar,
NickLengthLong,
NickLengthShort,
NickMatchLanguage,
NickMatchLanguageEgg,
NickMatchLanguageEggFail,
NickMatchLanguageFail,
NickMatchLanguageFlag,
NickMatchNoOthers,
NickMatchNoOthersFail,
// Original Trainer
OTLanguage,
OTLong,
OTShort,
OTSuspicious,
OT_IDEqual,
OT_IDs0,
OT_SID0,
OT_SID0Invalid,
OT_TID0,
OT_IDInvalid,
// PID & Encryption Constant
PIDEncryptWurmple,
PIDEncryptZero,
PIDEqualsEC,
PIDGenderMatch,
PIDGenderMismatch,
PIDNatureMatch,
PIDNatureMismatch,
PIDTypeMismatch,
PIDZero,
// Ribbons
RibbonAllValid,
RibbonEgg,
// Stats
StatDynamaxInvalid,
StatIncorrectHeight,
StatIncorrectHeightCopy,
StatIncorrectHeightValue,
StatIncorrectWeight,
StatIncorrectWeightValue,
StatInvalidHeightWeight,
StatGigantamaxInvalid,
StatGigantamaxValid,
StatNatureInvalid,
StatBattleVersionInvalid,
StatNobleInvalid,
StatAlphaInvalid,
// Storage
StoredSourceEgg,
// Super Training
SuperComplete,
SuperDistro,
SuperEgg,
SuperNoComplete,
SuperNoUnlocked,
SuperUnavailable,
SuperUnused,
// Tera Type
TeraTypeIncorrect,
TeraTypeMismatch,
// Trading
TradeNotAvailable,
// Trainer IDs
TrainerIDNoSeed,
// Transfer
TransferBad,
TransferCurrentHandlerInvalid,
TransferEgg,
TransferEggLocationTransporter,
TransferEggMetLevel,
TransferEggVersion,
TransferFlagIllegal,
TransferHandlerFlagRequired,
TransferHandlerMismatchName,
TransferHandlerMismatchGender,
TransferHandlerMismatchLanguage,
TransferMet,
TransferNotPossible,
TransferMetLocation,
TransferNature,
TransferObedienceLevel,
TransferKoreanGen4,
TransferEncryptGen6BitFlip,
TransferEncryptGen6Equals,
TransferEncryptGen6Xor,
TransferTrackerMissing,
TransferTrackerShouldBeZero,
TrashBytesExpected,
TrashBytesMismatchInitial,
TrashBytesMissingTerminator,
TrashBytesShouldBeEmpty,
// Bulk Cross-Comparison
BulkCloneDetectedDetails,
BulkCloneDetectedTracker,
BulkSharingEncryptionConstantGenerationSame,
BulkSharingEncryptionConstantGenerationDifferent,
BulkSharingEncryptionConstantEncounterType,
BulkSharingPIDGenerationDifferent,
BulkSharingPIDGenerationSame,
BulkSharingPIDEncounterType,
BulkDuplicateMysteryGiftEggReceived,
BulkSharingTrainerIDs,
BulkSharingTrainerVersion,
// Formattable Argument Present: 1 Number
FirstWithArgument,
ContestSheenGEQ_0 = FirstWithArgument,
MemoryStatFriendshipOTBaseEvent_0,
ContestSheenLEQ_0,
EggFMetLevel_0,
EffortUntrainedCap_0,
EvoTradeReqOutsider_0,
FormArgumentLEQ_0,
FormArgumentGEQ_0,
FormInvalidRange_0,
FormInvalidRangeLEQ_0,
HyperTrainLevelGEQ_0, // level
IVAllEqual_0,
IVFlawlessCountGEQ_0, // count
MarkValueOutOfRange_0, // unknown value
MemoryStatSocialLEQ_0,
MemoryStatFullness_0,
MemoryStatFullnessLEQ_0,
MemoryStatEnjoyment_0,
StatIncorrectCP_0, // value
WordFilterTooManyNumbers_0, // count
PokerusDaysLEQ_0, // days
PokerusStrainUnobtainable_0, // strain
MovePPExpectHealed_0, // move slot
MovePPTooHigh_0, // move slot
MovePPUpsTooHigh_0, // move slot
MemoryHTGender_0, // gender value
// Single Argument: Move ID
FirstWithMove,
MoveTechRecordFlagMissing_0 = FirstWithMove, // move ID
MoveShopAlphaMoveShouldBeMastered_0, // move
MoveShopMasterInvalid_0, // move ID
MoveShopMasterNotLearned_0, // move ID
MoveShopPurchaseInvalid_0, // move ID
// One Argument: Language
FirstWithLanguage,
OTLanguageShouldBe_0 = FirstWithLanguage, // language
OTLanguageShouldBeLeq_0, // language
EncGiftLanguageNotDistributed_0, // language
OTLanguageCannotPlayOnVersion_0, // language
// Multiple Arguments: Memories
FirstWithMemory,
MemoryValid_H = FirstWithMemory,
MemoryArgBadCatch_H,
MemoryArgBadHatch_H,
MemoryArgBadID_H,
MemoryArgBadLocation_H,
MemoryArgBadOTEgg_H,
MemoryArgSpecies_H,
MemoryCleared_H,
MemoryFeelInvalid_H,
FirstMemoryWithValue,
MemoryArgBadSpecies_H1 = FirstMemoryWithValue,
MemoryArgBadMove_H1,
MemoryArgBadItem_H1,
MemoryIndexID_H1,
MemoryIndexFeel_H1,
MemoryIndexIntensity_H1,
MemoryIndexIntensityMin_H1,
// One/Two Arguments: Special
FirstComplex,
RibbonFInvalid_0 = FirstComplex, // generated string
WordFilterFlaggedPattern_01, // filter, pattern
WordFilterInvalidCharacter_0, // filter, pattern
AwakenedStatGEQ_01,// value, statName
GanbaruStatLEQ_01, // value, statName
OTLanguageCannotTransferToConsoleRegion_0, // ConsoleRegion
EncTradeShouldHaveEvolvedToSpecies_0, // species
MoveEvoFCombination_0, // species
HintEvolvesToSpecies_0, // species
RibbonMarkingInvalid_0, // ribbon
RibbonMarkingAffixed_0, // ribbon
RibbonMissing_0, // ribbon
StoredSlotSourceInvalid_0, // StorageSlotType
HintEvolvesToRareForm_0, // bool
OTLanguageShouldBe_0or1, // language,language
MAX,
}

View File

@@ -1,5 +1,3 @@
using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core;
/// <summary> Severity indication of the associated <see cref="CheckResult"/> </summary>
@@ -25,19 +23,3 @@ public enum Severity : sbyte
/// </summary>
Valid = 1,
}
public static partial class Extensions
{
/// <summary>
/// Converts a Check result Severity determination (Valid/Invalid/etc.) to the localized string.
/// </summary>
/// <param name="s"><see cref="Severity"/> value to convert to string.</param>
/// <returns>Localized <see cref="string"/>.</returns>
public static string Description(this Severity s) => s switch
{
Severity.Invalid => L_SInvalid,
Severity.Fishy => L_SFishy,
Severity.Valid => L_SValid,
_ => L_SNotImplemented,
};
}

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -15,8 +15,8 @@ public override void Verify(LegalityAnalysis data)
data.AddLine(result);
}
private CheckResult VALID => GetValid(LAbilityFlag);
private CheckResult INVALID => GetInvalid(LAbilityMismatch);
private CheckResult VALID => GetValid(AbilityFlag);
private CheckResult INVALID => GetInvalid(AbilityMismatch);
private enum AbilityState : byte
{
@@ -33,7 +33,7 @@ private CheckResult VerifyAbility(LegalityAnalysis data)
int ability = pk.Ability;
int abilIndex = abilities.GetIndexOfAbility(ability);
if (abilIndex < 0)
return GetInvalid(LAbilityUnexpected);
return GetInvalid(AbilityUnexpected);
byte format = pk.Format;
if (format >= 6)
@@ -59,7 +59,7 @@ private CheckResult VerifyAbility(LegalityAnalysis data)
var evos = data.Info.EvoChainsAllGens;
if (!AbilityChangeRules.IsAbilityCapsulePossible(evos))
return INVALID;
return GetValid(LAbilityCapsuleUsed);
return GetValid(AbilityCapsuleUsed);
}
}
}
@@ -73,12 +73,12 @@ private CheckResult VerifyAbility(LegalityAnalysis data)
if (pk.AbilityNumber == 4)
{
if (AbilityChangeRules.IsAbilityPatchPossible(data.Info.EvoChainsAllGens))
return GetValid(LAbilityPatchUsed);
return GetValid(AbilityPatchUsed);
}
else if (enc.Ability == AbilityPermission.OnlyHidden)
{
if (AbilityChangeRules.IsAbilityPatchRevertPossible(data.Info.EvoChainsAllGens, pk.AbilityNumber))
return GetValid(LAbilityPatchRevertUsed);
return GetValid(AbilityPatchRevertUsed);
}
}
@@ -100,7 +100,7 @@ private CheckResult VerifyAbility(LegalityAnalysis data, IPersonalAbility12 abil
if (eabil >= 0)
{
if ((data.Entity.AbilityNumber == 4) != (eabil == AbilityPermission.OnlyHidden))
return GetInvalid(LAbilityHiddenFail);
return GetInvalid(AbilityHiddenFail);
if (eabil > 0)
return VerifyFixedAbility(data, abilities, AbilityState.CanMismatch, eabil, abilIndex);
}
@@ -129,7 +129,7 @@ private CheckResult VerifyAbility345(LegalityAnalysis data, IEncounterTemplate e
if (encounterAbility >= 0)
{
if ((pk.AbilityNumber == 4) != (encounterAbility == AbilityPermission.OnlyHidden))
return GetInvalid(LAbilityHiddenFail);
return GetInvalid(AbilityHiddenFail);
if (encounterAbility > 0)
return VerifyFixedAbility(data, abilities, state, encounterAbility, abilIndex);
}
@@ -148,14 +148,14 @@ private CheckResult VerifyFixedAbility(LegalityAnalysis data, IPersonalAbility12
if (enc.Generation >= 6)
{
if (IsAbilityCapsuleModified(pk, encounterAbility, data.Info.EvoChainsAllGens, enc.Context))
return GetValid(LAbilityCapsuleUsed);
return GetValid(AbilityCapsuleUsed);
if (pk.AbilityNumber != 1 << encounterAbility.GetSingleValue())
return INVALID;
return VALID;
}
if ((pk.AbilityNumber == 4) != (encounterAbility == AbilityPermission.OnlyHidden))
return GetInvalid(LAbilityHiddenFail);
return GetInvalid(AbilityHiddenFail);
bool hasEvolved = enc.Species != pk.Species;
if (hasEvolved && state != AbilityState.CanMismatch)
@@ -187,7 +187,7 @@ private CheckResult VerifyFixedAbility(LegalityAnalysis data, IPersonalAbility12
return CheckMatch(pk, abilities, enc.Generation, AbilityState.MustMatch, enc);
if (IsAbilityCapsuleModified(pk, encounterAbility, data.Info.EvoChainsAllGens, enc.Context))
return GetValid(LAbilityCapsuleUsed);
return GetValid(AbilityCapsuleUsed);
return INVALID;
}
@@ -248,7 +248,7 @@ private AbilityState VerifyAbilityGen3Transfer(LegalityAnalysis data, IPersonalA
// If we reach here, it has not evolved in Gen4/5 games or has an invalid ability.
// The ability does not need to match the PIDAbility, but only Gen3 ability is allowed.
if (pk.Ability != pers.Ability1) // Not evolved in Gen4/5, but doesn't have Gen3 only ability
data.AddLine(GetInvalid(LAbilityMismatch3)); // probably bad to do this here
data.AddLine(GetInvalid(AbilityMismatch3)); // probably bad to do this here
return AbilityState.CanMismatch;
}
@@ -260,14 +260,14 @@ private CheckResult VerifyAbilityMG(LegalityAnalysis data, MysteryGift g, IPerso
var pk = data.Entity;
if (g is PGT) // Ranger Manaphy
return (pk.Format >= 6 ? (pk.AbilityNumber == 1) : (pk.AbilityNumber < 4)) ? VALID : GetInvalid(LAbilityMismatchGift);
return (pk.Format >= 6 ? (pk.AbilityNumber == 1) : (pk.AbilityNumber < 4)) ? VALID : GetInvalid(AbilityMismatchGift);
var permit = g.Ability;
if (permit == AbilityPermission.Any12H)
return VALID;
int abilNumber = pk.AbilityNumber;
if (permit == AbilityPermission.Any12)
return abilNumber == 4 ? GetInvalid(LAbilityMismatchGift) : VALID;
return abilNumber == 4 ? GetInvalid(AbilityMismatchGift) : VALID;
// Only remaining matches are fixed index abilities
int cardAbilIndex = (int)permit;
@@ -277,7 +277,7 @@ private CheckResult VerifyAbilityMG(LegalityAnalysis data, MysteryGift g, IPerso
// Can still match if the ability was changed via ability capsule...
// However, it can't change to/from Hidden Abilities.
if (abilNumber == 4 || permit == AbilityPermission.OnlyHidden)
return GetInvalid(LAbilityHiddenFail);
return GetInvalid(AbilityHiddenFail);
// Ability can be flipped 0/1 if Ability Capsule is available, is not Hidden Ability, and Abilities are different.
if (pk.Format >= 6)
@@ -285,10 +285,10 @@ private CheckResult VerifyAbilityMG(LegalityAnalysis data, MysteryGift g, IPerso
// Maybe was evolved after using ability capsule.
var evos = data.Info.EvoChainsAllGens;
if (AbilityChangeRules.IsAbilityCapsulePossible(evos))
return GetValid(LAbilityCapsuleUsed);
return GetValid(AbilityCapsuleUsed);
}
return pk.Format < 6 ? GetInvalid(LAbilityMismatchPID) : INVALID;
return pk.Format < 6 ? GetInvalid(AbilityMismatchPID) : INVALID;
}
private CheckResult VerifyAbilityPCD(LegalityAnalysis data, IPersonalAbility12 abilities, PCD pcd)
@@ -305,7 +305,7 @@ private CheckResult VerifyAbilityPCD(LegalityAnalysis data, IPersonalAbility12 a
return CheckMatch(pk, abilities, 4, AbilityState.MustMatch, pcd); // evolved, must match
}
if (pk.AbilityNumber < 4) // Ability Capsule can change between 1/2
return GetValid(LAbilityCapsuleUsed);
return GetValid(AbilityCapsuleUsed);
}
if (pcd.Species != pk.Species)
@@ -322,7 +322,7 @@ private CheckResult VerifyAbility5(LegalityAnalysis data, IEncounterTemplate enc
// Eggs and Encounter Slots have not yet checked for Hidden Ability potential.
return enc switch
{
EncounterEgg5 egg when pk.AbilityNumber == 4 && !egg.Ability.CanBeHidden() => GetInvalid(LAbilityHiddenUnavailable),
EncounterEgg5 egg when pk.AbilityNumber == 4 && !egg.Ability.CanBeHidden() => GetInvalid(AbilityHiddenUnavailable),
_ => CheckMatch(data.Entity, abilities, 5, pk.Format == 5 ? AbilityState.MustMatch : AbilityState.CanMismatch, enc),
};
}
@@ -335,7 +335,7 @@ private CheckResult VerifyAbility6(LegalityAnalysis data, IEncounterTemplate enc
return enc switch
{
EncounterEgg6 egg when !egg.Ability.CanBeHidden() => GetInvalid(LAbilityHiddenUnavailable),
EncounterEgg6 egg when !egg.Ability.CanBeHidden() => GetInvalid(AbilityHiddenUnavailable),
_ => VALID,
};
}
@@ -348,7 +348,7 @@ private CheckResult VerifyAbility7(LegalityAnalysis data, IEncounterTemplate enc
return enc switch
{
EncounterEgg7 egg when !egg.Ability.CanBeHidden() => GetInvalid(LAbilityHiddenUnavailable),
EncounterEgg7 egg when !egg.Ability.CanBeHidden() => GetInvalid(AbilityHiddenUnavailable),
_ => VALID,
};
}
@@ -361,7 +361,7 @@ private CheckResult VerifyAbility8BDSP(LegalityAnalysis data, IEncounterTemplate
return enc switch
{
EncounterEgg8b egg when !egg.Ability.CanBeHidden() => GetInvalid(LAbilityHiddenUnavailable),
EncounterEgg8b egg when !egg.Ability.CanBeHidden() => GetInvalid(AbilityHiddenUnavailable),
_ => VALID,
};
}
@@ -377,7 +377,7 @@ private CheckResult VerifyAbility8BDSP(LegalityAnalysis data, IEncounterTemplate
private CheckResult CheckMatch(PKM pk, IPersonalAbility12 abilities, byte generation, AbilityState state, IEncounterTemplate enc)
{
if (generation is (3 or 4) && pk.AbilityNumber == 4)
return GetInvalid(LAbilityHiddenUnavailable);
return GetInvalid(AbilityHiddenUnavailable);
// other cases of hidden ability already flagged, all that is left is 1/2 mismatching
if (state != AbilityState.MustMatch)
@@ -394,7 +394,7 @@ private CheckResult CheckMatch(PKM pk, IPersonalAbility12 abilities, byte genera
// Must not have the Ability bit flag set.
// Shadow encounters set a random ability index; don't bother checking if it's a re-battle for ability bit flipping.
if (abit && enc is not IShadow3)
return GetInvalid(LAbilityMismatchFlag, CheckIdentifier.PID);
return GetInvalid(CheckIdentifier.PID, AbilityMismatchFlag);
}
else
{
@@ -402,7 +402,7 @@ private CheckResult CheckMatch(PKM pk, IPersonalAbility12 abilities, byte genera
// Version value check isn't factually correct, but there are no C/XD gifts with (Version!=15) that have two abilities.
// Pikachu, Celebi, Ho-Oh
if (pk.Version != GameVersion.CXD && abit != ((pk.EncryptionConstant & 1) == 1))
return GetInvalid(LAbilityMismatchPID, CheckIdentifier.PID);
return GetInvalid(CheckIdentifier.PID, AbilityMismatchPID);
}
}
else if (pk.Format >= 6)
@@ -421,7 +421,7 @@ private CheckResult GetPIDAbilityMatch(PKM pk, IPersonalAbility abilities)
var index = pk.AbilityNumber >> 1;
var abil = abilities.GetAbilityAtIndex(index);
if (abil != pk.Ability)
return GetInvalid(LAbilityMismatchPID);
return GetInvalid(AbilityMismatchPID);
return VALID;
}

View File

@@ -1,5 +1,5 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -25,11 +25,11 @@ public override void Verify(LegalityAnalysis data)
// Can't obtain EVs in the game; only AVs.
int sum = pb7.EVTotal;
if (sum != 0)
data.AddLine(GetInvalid(LEffortShouldBeZero));
data.AddLine(GetInvalid(EffortShouldBeZero));
// Check that all AVs are within the allowed cap
if (!pb7.AwakeningAllValid())
data.AddLine(GetInvalid(LAwakenedCap));
data.AddLine(GetInvalid(AwakenedCap));
// Gather all AVs. When leveling up, AVs are "randomly" granted, so a mon must be at or above.
Span<byte> required = stackalloc byte[6];
@@ -39,16 +39,16 @@ public override void Verify(LegalityAnalysis data)
// For each stat, ensure the current AV is at least the required minimum
if (current[0] < required[0])
data.AddLine(GetInvalid(string.Format(LAwakenedShouldBeValue, required[0], nameof(IAwakened.AV_HP))));
data.AddLine(GetInvalid(Identifier, AwakenedStatGEQ_01, required[0], 0)); // HP
if (current[1] < required[1])
data.AddLine(GetInvalid(string.Format(LAwakenedShouldBeValue, required[1], nameof(IAwakened.AV_ATK))));
data.AddLine(GetInvalid(Identifier, AwakenedStatGEQ_01, required[1], 1)); // Atk
if (current[2] < required[2])
data.AddLine(GetInvalid(string.Format(LAwakenedShouldBeValue, required[2], nameof(IAwakened.AV_DEF))));
data.AddLine(GetInvalid(Identifier, AwakenedStatGEQ_01, required[2], 2)); // Def
if (current[3] < required[3])
data.AddLine(GetInvalid(string.Format(LAwakenedShouldBeValue, required[3], nameof(IAwakened.AV_SPA))));
data.AddLine(GetInvalid(Identifier, AwakenedStatGEQ_01, required[3], 4)); // SpA
if (current[4] < required[4])
data.AddLine(GetInvalid(string.Format(LAwakenedShouldBeValue, required[4], nameof(IAwakened.AV_SPD))));
data.AddLine(GetInvalid(Identifier, AwakenedStatGEQ_01, required[4], 5)); // SpD
if (current[5] < required[5])
data.AddLine(GetInvalid(string.Format(LAwakenedShouldBeValue, required[5], nameof(IAwakened.AV_SPE))));
data.AddLine(GetInvalid(Identifier, AwakenedStatGEQ_01, required[5], 3)); // Speed
}
}

View File

@@ -1,5 +1,5 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.Ball;
using static PKHeX.Core.BallVerificationResult;
@@ -180,8 +180,8 @@ private static BallVerificationResult VerifyBallEggGen9(EncounterEgg9 enc, Ball
private CheckResult Localize(BallVerificationResult value)
{
bool valid = value.IsValid();
string msg = value.GetMessage();
return Get(msg, valid ? Severity.Valid : Severity.Invalid);
var msg = value.GetMessage();
return Get(valid ? Severity.Valid : Severity.Invalid, msg);
}
}
@@ -210,17 +210,17 @@ public static class BallVerificationResultExtensions
_ => false,
};
public static string GetMessage(this BallVerificationResult value) => value switch
public static LegalityCheckResultCode GetMessage(this BallVerificationResult value) => value switch
{
ValidEncounter => LBallEnc,
ValidInheritedSpecies => LBallSpeciesPass,
BadEncounter => LBallEncMismatch,
BadCaptureHeavy => LBallHeavy,
BadInheritAbility => LBallAbility,
BadInheritSpecies => LBallSpecies,
BadInheritCherish => LBallEggCherish,
BadInheritMaster => LBallEggMaster,
BadOutOfRange => LBallUnavailable,
ValidEncounter => BallEnc,
ValidInheritedSpecies => BallSpeciesPass,
BadEncounter => BallEncMismatch,
BadCaptureHeavy => BallHeavy,
BadInheritAbility => BallAbility,
BadInheritSpecies => BallSpecies,
BadInheritCherish => BallEggCherish,
BadInheritMaster => BallEggMaster,
BadOutOfRange => BallUnavailable,
_ => throw new ArgumentOutOfRangeException(nameof(value), value, null),
};
}

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -17,7 +17,7 @@ public override void Verify(LegalityAnalysis data)
// Colo starters are already hard-verified. No need to check them here.
if (pk.OriginalTrainerGender == 1)
data.AddLine(GetInvalid(LG3OTGender, CheckIdentifier.Trainer));
data.AddLine(GetInvalid(CheckIdentifier.Trainer, G3OTGender));
// Trainer ID is checked in another verifier. Don't duplicate it here.
}
@@ -34,7 +34,7 @@ private static void VerifyStarterXD(LegalityAnalysis data)
bool valid = MethodCXD.TryGetSeedStarterXD(pk, out var seed);
if (!valid)
data.AddLine(GetInvalid(LEncConditionBadRNGFrame, CheckIdentifier.PID));
data.AddLine(GetInvalid(CheckIdentifier.PID, EncConditionBadRNGFrame));
else
data.Info.PIDIV = new PIDIV(PIDType.CXD, seed);
}

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -24,7 +24,7 @@ private CheckResult VerifyConsoleRegion(IRegionOrigin pk)
{
var consoleRegion = pk.ConsoleRegion;
if (consoleRegion >= 7)
return GetInvalid(LGeoHardwareRange);
return GetInvalid(GeoHardwareRange);
return Verify3DSDataPresent(pk, consoleRegion);
}
@@ -32,7 +32,7 @@ private CheckResult VerifyConsoleRegion(IRegionOrigin pk)
private CheckResult Verify3DSDataPresent(IRegionOrigin pk, byte consoleRegion)
{
if (!Locale3DS.IsConsoleRegionCountryValid(consoleRegion, pk.Country))
return GetInvalid(LGeoHardwareInvalid);
return GetValid(LGeoHardwareValid);
return GetInvalid(GeoHardwareInvalid);
return GetValid(GeoHardwareValid);
}
}

View File

@@ -1,6 +1,6 @@
using static PKHeX.Core.ContestStatGranting;
using static PKHeX.Core.ContestStatInfo;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -29,14 +29,14 @@ public override void Verify(LegalityAnalysis data)
if (correlation == None)
{
// We're only here because we have contest stat values. We aren't permitted to have any, so flag it.
data.AddLine(GetInvalid(LContestZero));
data.AddLine(GetInvalid(ContestZero));
}
else if (correlation == NoSheen)
{
// We can get contest stat values, but we can't get any for Sheen.
// Any combination of non-sheen is ok, but nonzero sheen is illegal.
if (s.ContestSheen != 0)
data.AddLine(GetInvalid(LContestZeroSheen));
data.AddLine(GetInvalid(ContestZeroSheen));
}
else if (correlation == CorrelateSheen)
{
@@ -50,12 +50,12 @@ public override void Verify(LegalityAnalysis data)
var minSheen = CalculateMinimumSheen(s, initial, pk, method);
if (s.ContestSheen < minSheen)
data.AddLine(GetInvalid(string.Format(LContestSheenTooLow_0, minSheen)));
data.AddLine(GetInvalid(ContestSheenGEQ_0, minSheen));
// Check for sheen values that are too high.
var maxSheen = CalculateMaximumSheen(s, pk.Nature, initial, gen3);
if (s.ContestSheen > maxSheen)
data.AddLine(GetInvalid(string.Format(LContestSheenTooHigh_0, maxSheen)));
data.AddLine(GetInvalid(ContestSheenLEQ_0, maxSheen));
}
else if (correlation == Mixed)
{
@@ -65,7 +65,7 @@ public override void Verify(LegalityAnalysis data)
var initial = GetReferenceTemplate(data.Info.EncounterMatch);
var maxSheen = CalculateMaximumSheen(s, pk.Nature, initial, gen3);
if (s.ContestSheen > maxSheen)
data.AddLine(GetInvalid(string.Format(LContestSheenTooHigh_0, maxSheen)));
data.AddLine(GetInvalid(ContestSheenLEQ_0, maxSheen));
}
}
}

View File

@@ -1,5 +1,5 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -16,7 +16,7 @@ public override void Verify(LegalityAnalysis data)
if (pk.IsEgg)
{
if (pk.EVTotal is not 0)
data.AddLine(GetInvalid(LEffortEgg));
data.AddLine(GetInvalid(EffortEgg));
return;
}
@@ -28,24 +28,24 @@ public override void Verify(LegalityAnalysis data)
int sum = pk.EVTotal;
if (sum > EffortValues.Max510) // format >= 3
data.AddLine(GetInvalid(LEffortAbove510));
data.AddLine(GetInvalid(EffortAbove510));
var enc = data.EncounterMatch;
Span<int> evs = stackalloc int[6];
pk.GetEVs(evs);
if (format >= 6 && IsAnyAboveHardLimit6(evs))
data.AddLine(GetInvalid(LEffortAbove252));
data.AddLine(GetInvalid(EffortAbove252));
else if (format < 5) // 3/4
VerifyGainedEVs34(data, enc, evs, pk);
// Only one of the following can be true: 0, 508, and x%6!=0
if (sum == 0 && !enc.IsWithinEncounterRange(pk))
data.AddLine(Get(LEffortEXPIncreased, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, EffortEXPIncreased));
else if (sum == EffortValues.MaxEffective)
data.AddLine(Get(LEffort2Remaining, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, Effort2Remaining));
else if (evs[0] != 0 && !evs.ContainsAnyExcept(evs[0]))
data.AddLine(Get(LEffortAllEqual, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, EffortAllEqual));
}
private void VerifyGainedEVs34(LegalityAnalysis data, IEncounterTemplate enc, ReadOnlySpan<int> evs, PKM pk)
@@ -58,14 +58,14 @@ private void VerifyGainedEVs34(LegalityAnalysis data, IEncounterTemplate enc, Re
{
// Cannot EV train at level 100 -- Certain events are distributed at level 100.
// EVs can only be increased by vitamins to a max of 100.
data.AddLine(GetInvalid(LEffortCap100));
data.AddLine(GetInvalid(EffortCap100));
}
else // Check for gained EVs without gaining EXP -- don't check Gen5+ which have wings to boost above 100.
{
var growth = PersonalTable.HGSS[enc.Species].EXPGrowth;
var baseEXP = Experience.GetEXP(enc.LevelMin, growth);
if (baseEXP == pk.EXP)
data.AddLine(GetInvalid(string.Format(LEffortUntrainedCap, EffortValues.MaxVitamins34)));
data.AddLine(GetInvalid(EffortUntrainedCap_0, EffortValues.MaxVitamins34));
}
}

View File

@@ -1,5 +1,6 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using System.Diagnostics.CodeAnalysis;
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.Species;
namespace PKHeX.Core;
@@ -26,79 +27,79 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f)
var unusedMask = pk.Format == 6 ? 0xFFFF_FF00 : 0xFF00_0000;
if ((arg & unusedMask) != 0)
return GetInvalid(LFormArgumentHigh);
return GetInvalid(FormArgumentLEQ_0);
return (Species)pk.Species switch
{
// Transfer Edge Cases -- Bank wipes the form but keeps old FormArgument value.
Furfrou when pk is { Context: EntityContext.Gen7, Form: 0 } &&
((enc.Generation == 6 && f.FormArgument <= byte.MaxValue) || IsFormArgumentDayCounterValid(f, 5, true))
=> GetValid(LFormArgumentValid),
=> GetValid(FormArgumentValid),
Furfrou when pk.Form != 0 => !IsFormArgumentDayCounterValid(f, 5, true) ? GetInvalid(LFormArgumentInvalid) : GetValid(LFormArgumentValid),
Furfrou when pk.Form != 0 => !IsFormArgumentDayCounterValid(f, 5, true) ? GetInvalid(FormArgumentInvalid) : GetValid(FormArgumentValid),
Hoopa when pk.Form == 1 => data.Info.EvoChainsAllGens switch
{
{ HasVisitedGen9: true } when arg == 0 => GetValid(LFormArgumentValid), // Value not applied on form change, and reset when reverted.
{ HasVisitedGen6: true } when IsFormArgumentDayCounterValid(f, 3) => GetValid(LFormArgumentValid), // 0-3 via OR/AS
{ HasVisitedGen7: true } when IsFormArgumentDayCounterValid(f, 3) && f.FormArgumentRemain != 0 => GetValid(LFormArgumentValid), // 1-3 via Gen7
_ => GetInvalid(LFormArgumentInvalid),
{ HasVisitedGen9: true } when arg == 0 => GetValid(FormArgumentValid), // Value not applied on form change, and reset when reverted.
{ HasVisitedGen6: true } when IsFormArgumentDayCounterValid(f, 3) => GetValid(FormArgumentValid), // 0-3 via OR/AS
{ HasVisitedGen7: true } when IsFormArgumentDayCounterValid(f, 3) && f.FormArgumentRemain != 0 => GetValid(FormArgumentValid), // 1-3 via Gen7
_ => GetInvalid(FormArgumentInvalid),
},
Yamask when pk.Form == 1 => arg switch
{
not 0 when pk.IsEgg => GetInvalid(LFormArgumentNotAllowed),
> 9_999 => GetInvalid(LFormArgumentHigh),
_ => GetValid(LFormArgumentValid),
not 0 when pk.IsEgg => GetInvalid(FormArgumentNotAllowed),
> 9_999 => GetInvalid(FormArgumentLEQ_0, 9999),
_ => GetValid(FormArgumentValid),
},
Basculin when pk.Form is 2 => arg switch
{
not 0 when pk.IsEgg => GetInvalid(LFormArgumentNotAllowed),
> 9_999 => GetInvalid(LFormArgumentHigh),
_ => GetValid(LFormArgumentValid),
not 0 when pk.IsEgg => GetInvalid(FormArgumentNotAllowed),
> 9_999 => GetInvalid(FormArgumentLEQ_0, 9999),
_ => GetValid(FormArgumentValid),
},
Qwilfish when pk.Form is 1 => arg switch
{
not 0 when pk.IsEgg => GetInvalid(LFormArgumentNotAllowed),
not 0 when pk.CurrentLevel < 25 => GetInvalid(LFormArgumentHigh), // Can't get requisite move
> 9_999 => GetInvalid(LFormArgumentHigh),
_ => GetValid(LFormArgumentValid),
not 0 when pk.IsEgg => GetInvalid(FormArgumentNotAllowed),
not 0 when pk.CurrentLevel < 25 => GetInvalid(FormArgumentLEQ_0, 0), // Can't get requisite move
> 9_999 => GetInvalid(FormArgumentLEQ_0, 9999),
_ => GetValid(FormArgumentValid),
},
Overqwil => arg switch
{
> 9_999 => GetInvalid(LFormArgumentHigh),
0 when enc.Species == (ushort)Overqwil => GetValid(LFormArgumentValid),
< 20 when !data.Info.EvoChainsAllGens.HasVisitedGen9 || pk.CurrentLevel < (pk is IHomeTrack { HasTracker: true } ? 15 : 28) => GetInvalid(LFormArgumentLow),
>= 20 when !data.Info.EvoChainsAllGens.HasVisitedPLA || pk.CurrentLevel < 25 => GetInvalid(LFormArgumentLow),
_ when pk is IHomeTrack { HasTracker: false } and PA8 { CurrentLevel: < 25 } => GetInvalid(LEvoInvalid),
_ => GetValid(LFormArgumentValid),
> 9_999 => GetInvalid(FormArgumentLEQ_0, 9999),
0 when enc.Species == (ushort)Overqwil => GetValid(FormArgumentValid),
< 20 when !data.Info.EvoChainsAllGens.HasVisitedGen9 || pk.CurrentLevel < (pk is IHomeTrack { HasTracker: true } ? 15 : 28) => GetInvalid(FormArgumentGEQ_0, 20),
>= 20 when !data.Info.EvoChainsAllGens.HasVisitedPLA || pk.CurrentLevel < 25 => GetInvalid(FormArgumentLEQ_0, 0),
_ when pk is IHomeTrack { HasTracker: false } and PA8 { CurrentLevel: < 25 } => GetInvalid(EvoInvalid),
_ => GetValid(FormArgumentValid),
},
Stantler => arg switch
{
not 0 when pk.IsEgg => GetInvalid(LFormArgumentNotAllowed),
not 0 when pk.CurrentLevel < 31 => GetInvalid(LFormArgumentHigh),
> 9_999 => GetInvalid(LFormArgumentHigh),
_ => arg == 0 || HasVisitedPLA(data, Stantler) ? GetValid(LFormArgumentValid) : GetInvalid(LFormArgumentNotAllowed),
not 0 when pk.IsEgg => GetInvalid(FormArgumentNotAllowed),
not 0 when pk.CurrentLevel < 31 => GetInvalid(FormArgumentLEQ_0, 0),
> 9_999 => GetInvalid(FormArgumentLEQ_0, 9999),
_ => arg == 0 || HasVisitedPLA(data, Stantler) ? GetValid(FormArgumentValid) : GetInvalid(FormArgumentNotAllowed),
},
Primeape => arg switch
{
> 9_999 => GetInvalid(LFormArgumentHigh),
_ => arg == 0 || HasVisitedSV(data, Primeape) ? GetValid(LFormArgumentValid) : GetInvalid(LFormArgumentNotAllowed),
> 9_999 => GetInvalid(FormArgumentLEQ_0, 9999),
_ => arg == 0 || HasVisitedSV(data, Primeape) ? GetValid(FormArgumentValid) : GetInvalid(FormArgumentNotAllowed),
},
Bisharp => arg switch
{
> 9_999 => GetInvalid(LFormArgumentHigh),
_ => arg == 0 || HasVisitedSV(data, Bisharp) ? GetValid(LFormArgumentValid) : GetInvalid(LFormArgumentNotAllowed),
> 9_999 => GetInvalid(FormArgumentLEQ_0, 9999),
_ => arg == 0 || HasVisitedSV(data, Bisharp) ? GetValid(FormArgumentValid) : GetInvalid(FormArgumentNotAllowed),
},
Gimmighoul => arg switch
{
// When leveled up, the game copies the save file's current coin count to the arg (clamped to <=999). If >=999, evolution is triggered.
// Without being leveled up at least once, it cannot have a form arg value.
>= 999 => GetInvalid(LFormArgumentHigh),
0 => GetValid(LFormArgumentValid),
_ => pk.CurrentLevel != pk.MetLevel ? GetValid(LFormArgumentValid) : GetInvalid(LFormArgumentNotAllowed),
>= 999 => GetInvalid(FormArgumentLEQ_0, 999),
0 => GetValid(FormArgumentValid),
_ => pk.CurrentLevel != pk.MetLevel ? GetValid(FormArgumentValid) : GetInvalid(FormArgumentNotAllowed),
},
Runerigus => VerifyFormArgumentRange(enc.Species, Runerigus, arg, 49, 9999),
Alcremie => VerifyFormArgumentRange(enc.Species, Alcremie, arg, 0, (uint)AlcremieDecoration.Ribbon),
Wyrdeer when enc.Species != (int)Wyrdeer && pk.CurrentLevel < 31 => GetInvalid(LEvoInvalid),
Alcremie => VerifyFormArgumentRange(enc.Species, Alcremie, arg, 0, (ushort)AlcremieDecoration.Ribbon),
Wyrdeer when enc.Species != (int)Wyrdeer && pk.CurrentLevel < 31 => GetInvalid(EvoInvalid),
Wyrdeer => VerifyFormArgumentRange(enc.Species, Wyrdeer, arg, 20, 9999),
Basculegion => VerifyFormArgumentRange(enc.Species, Basculegion, arg, 294, 9999),
Annihilape => VerifyFormArgumentRange(enc.Species, Annihilape, arg, 20, 9999),
@@ -108,17 +109,17 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f)
{
// Starter Legend has '1' when present in party, to differentiate.
// Cannot be traded to other games.
EncounterStatic9 { StarterBoxLegend: true } x when ParseSettings.ActiveTrainer is { } tr && (tr is not SAV9SV sv || sv.Version != x.Version) => GetInvalid(LTradeNotAvailable),
EncounterStatic9 { StarterBoxLegend: true } x when ParseSettings.ActiveTrainer is { } tr && (tr is not SAV9SV sv || sv.Version != x.Version) => GetInvalid(TradeNotAvailable),
EncounterStatic9 { StarterBoxLegend: true } => arg switch
{
< EncounterStatic9.RideLegendFormArg => GetInvalid(LFormArgumentLow),
EncounterStatic9.RideLegendFormArg => !data.IsStoredSlot(StorageSlotType.Ride) ? GetInvalid(LFormParty) : GetValid(LFormArgumentValid),
> EncounterStatic9.RideLegendFormArg => GetInvalid(LFormArgumentHigh),
< EncounterStatic9.RideLegendFormArg => GetInvalid(FormArgumentGEQ_0, EncounterStatic9.RideLegendFormArg),
EncounterStatic9.RideLegendFormArg => !data.IsStoredSlot(StorageSlotType.Ride) ? GetInvalid(FormParty) : GetValid(FormArgumentValid),
> EncounterStatic9.RideLegendFormArg => GetInvalid(FormArgumentLEQ_0, EncounterStatic9.RideLegendFormArg),
},
_ => arg switch
{
not 0 => GetInvalid(LFormArgumentNotAllowed),
_ => GetValid(LFormArgumentValid),
not 0 => GetInvalid(FormArgumentNotAllowed),
_ => GetValid(FormArgumentValid),
},
},
_ => VerifyFormArgumentNone(pk, f),
@@ -137,22 +138,22 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f)
/// <param name="value">Current Form Argument value</param>
/// <param name="min">Minimum value allowed</param>
/// <param name="max">Maximum value allowed</param>
private CheckResult VerifyFormArgumentRange(ushort encSpecies, Species check, uint value, uint min, uint max)
private CheckResult VerifyFormArgumentRange(ushort encSpecies, Species check, uint value, [ConstantExpected] ushort min, [ConstantExpected] ushort max)
{
// If was never the Form Argument accruing species (never evolved from it), then it must be zero.
if (encSpecies == (ushort)check)
{
if (value == 0)
return GetValid(LFormArgumentValid);
return GetInvalid(LFormArgumentNotAllowed);
return GetValid(FormArgumentValid);
return GetInvalid(FormArgumentNotAllowed);
}
// Evolved, must be within the range.
if (value < min)
return GetInvalid(LFormArgumentLow);
return GetInvalid(FormArgumentGEQ_0, min);
if (value > max)
return GetInvalid(LFormArgumentHigh);
return GetValid(LFormArgumentValid);
return GetInvalid(FormArgumentLEQ_0, max);
return GetValid(FormArgumentValid);
}
private CheckResult VerifyFormArgumentNone(PKM pk, IFormArgument f)
@@ -162,26 +163,26 @@ private CheckResult VerifyFormArgumentNone(PKM pk, IFormArgument f)
if (f.FormArgument != 0)
{
if (pk is { Species: (int)Furfrou, Form: 0 } && (f.FormArgument & ~0xFF_00_00u) == 0)
return GetValid(LFormArgumentValid);
return GetInvalid(LFormArgumentNotAllowed);
return GetValid(FormArgumentValid);
return GetInvalid(FormArgumentNotAllowed);
}
return GetValid(LFormArgumentValid);
return GetValid(FormArgumentValid);
}
if (f.FormArgument != 0)
{
if (pk is { Species: (int)Furfrou, Form: 0 } && (f.FormArgument & ~0xFFu) == 0)
return GetValid(LFormArgumentValid);
return GetInvalid(LFormArgumentNotAllowed);
return GetValid(FormArgumentValid);
return GetInvalid(FormArgumentNotAllowed);
}
// Stored separately from main form argument value
if (pk6.FormArgumentRemain != 0)
return GetInvalid(LFormArgumentNotAllowed);
return GetInvalid(FormArgumentNotAllowed);
if (pk6.FormArgumentElapsed != 0)
return GetInvalid(LFormArgumentNotAllowed);
return GetInvalid(FormArgumentNotAllowed);
return GetValid(LFormArgumentValid);
return GetValid(FormArgumentValid);
}
private static bool IsFormArgumentDayCounterValid(IFormArgument f, uint maxSeed, bool canRefresh = false)

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.Species;
namespace PKHeX.Core;
@@ -22,7 +22,7 @@ public override void Verify(LegalityAnalysis data)
FormArg.Verify(data);
}
private CheckResult VALID => GetValid(LFormValid);
private CheckResult VALID => GetValid(FormValid);
private CheckResult VerifyForm(LegalityAnalysis data)
{
@@ -36,10 +36,9 @@ private CheckResult VerifyForm(LegalityAnalysis data)
var species = pk.Species;
var enc = data.EncounterMatch;
var Info = data.Info;
if (!pi.IsFormWithinRange(form) && !FormInfo.IsValidOutOfBoundsForm(species, form, enc.Generation))
return GetInvalid(string.Format(LFormInvalidRange, count - 1, form));
return GetInvalid(FormInvalidRange_0, (ushort)(count - 1));
switch ((Species)species)
{
@@ -48,44 +47,48 @@ private CheckResult VerifyForm(LegalityAnalysis data)
{
if (form == 0)
break; // Regular Pikachu, OK.
return GetInvalid(LFormPikachuCosplay);
return GetInvalid(FormPikachuCosplay);
}
if (form != s6.Form)
return GetInvalid(LFormPikachuCosplayInvalid);
return GetInvalid(FormPikachuCosplayInvalid);
if (pk.Format != 6)
return GetInvalid(LTransferBad); // Can't transfer.
return GetInvalid(TransferBad); // Can't transfer.
break;
// LGP/E: Can't get the other game's Starter form.
case Pikachu when form is not 0 && ParseSettings.ActiveTrainer is SAV7b {Version:GameVersion.GE}:
case Eevee when form is not 0 && ParseSettings.ActiveTrainer is SAV7b {Version:GameVersion.GP}:
return GetInvalid(LFormBattle);
return GetInvalid(FormBattle);
case Pikachu when enc.Generation >= 7: // Cap
var expectForm = enc is EncounterInvalid or IEncounterEgg ? 0 : enc.Form;
if (form != expectForm)
{
bool gift = enc is MysteryGift g && g.Form != form;
var msg = gift ? LFormPikachuEventInvalid : LFormInvalidGame;
var msg = gift ? FormPikachuEventInvalid : FormInvalidGame;
return GetInvalid(msg);
}
break;
case Unown when enc.Generation == 2 && form >= 26:
return GetInvalid(string.Format(LFormInvalidRange, "Z", form == 26 ? "!" : "?"));
case Unown when enc.Generation == 3 && form != EntityPID.GetUnownForm3(pk.EncryptionConstant):
return GetInvalid(string.Format(LFormInvalidExpect_0, EntityPID.GetUnownForm3(pk.EncryptionConstant)));
case Dialga or Palkia or Giratina or Arceus when form > 0 && pk is PA8: // can change forms with key items
return GetInvalid(FormInvalidRangeLEQ_0, 25);
case Unown when enc.Generation == 3:
var expectUnown = EntityPID.GetUnownForm3(pk.EncryptionConstant);
if (expectUnown != form)
return GetInvalid(FormInvalidExpect_0, expectUnown);
break;
case Dialga or Palkia or Giratina or Arceus when form > 0 && pk is PA8: // can change forms with key items
break;
case Dialga when pk.Format >= 9 && ((form == 1) != (pk.HeldItem == 1777)): // Origin Forme Dialga with Adamant Crystal
case Palkia when pk.Format >= 9 && ((form == 1) != (pk.HeldItem == 1778)): // Origin Forme Palkia with Lustrous Globe
case Giratina when pk.Format >= 9 && ((form == 1) != (pk.HeldItem == 1779)): // Origin Forme Giratina with Griseous Core
case Giratina when pk.Format <= 8 && ((form == 1) != (pk.HeldItem == 0112)): // Origin Forme Giratina with Griseous Orb
return GetInvalid(LFormItemInvalid);
return GetInvalid(FormItemInvalid);
case Arceus:
var arceus = FormItem.GetFormArceus(pk.HeldItem, pk.Format);
return arceus != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem);
return arceus != form ? GetInvalid(FormItemInvalid) : GetValid(FormItemMatches);
case Keldeo when enc.Generation != 5 || pk.Format >= 8:
// can mismatch in Gen5 via B/W tutor and transfer up
// can mismatch in Gen8+ as the form activates in battle when knowing the move; outside of battle can be either state.
@@ -93,93 +96,93 @@ private CheckResult VerifyForm(LegalityAnalysis data)
bool hasSword = pk.HasMove((int) Move.SecretSword);
bool isSword = pk.Form == 1;
if (isSword != hasSword)
return GetInvalid(LMoveKeldeoMismatch);
return GetInvalid(MoveKeldeoMismatch);
break;
case Genesect:
var genesect = FormItem.GetFormGenesect(pk.HeldItem);
return genesect != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem);
return genesect != form ? GetInvalid(FormItemInvalid) : GetValid(FormItemMatches);
case Greninja:
if (form > 1) // Ash Battle Bond active
return GetInvalid(LFormBattle);
return GetInvalid(FormBattle);
if (form != 0 && enc is not MysteryGift) // Form can not be bred for, MysteryGift already checked
return GetInvalid(string.Format(LFormInvalidRange, 0, form));
return GetInvalid(FormInvalidRange_0, 0);
break;
case Scatterbug or Spewpa or Vivillon when enc.Context is EntityContext.Gen9:
if (form > 18 && enc.Form != form) // Pokéball
return GetInvalid(LFormVivillonEventPre);
return GetInvalid(FormVivillonEventPre);
if (form != 18 && enc is IEncounterEgg) // Fancy
return GetInvalid(LFormVivillonNonNative);
return GetInvalid(FormVivillonNonNative);
break;
case Scatterbug or Spewpa:
if (form > Vivillon3DS.MaxWildFormID) // Fancy & Pokéball
return GetInvalid(LFormVivillonEventPre);
return GetInvalid(FormVivillonEventPre);
if (pk is not IRegionOrigin tr)
break;
if (!Vivillon3DS.IsPatternValid(form, tr.ConsoleRegion))
return GetInvalid(LFormVivillonInvalid);
return GetInvalid(FormVivillonInvalid);
if (!Vivillon3DS.IsPatternNative(form, tr.Country, tr.Region))
data.AddLine(Get(LFormVivillonNonNative, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, FormVivillonNonNative));
break;
case Vivillon:
if (form > Vivillon3DS.MaxWildFormID) // Fancy & Pokéball
{
if (enc is not MysteryGift)
return GetInvalid(LFormVivillonInvalid);
return GetValid(LFormVivillon);
return GetInvalid(FormVivillonInvalid);
return GetValid(FormVivillon);
}
if (pk is not IRegionOrigin trv)
break;
if (!Vivillon3DS.IsPatternValid(form, trv.ConsoleRegion))
return GetInvalid(LFormVivillonInvalid);
return GetInvalid(FormVivillonInvalid);
if (!Vivillon3DS.IsPatternNative(form, trv.Country, trv.Region))
data.AddLine(Get(LFormVivillonNonNative, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, FormVivillonNonNative));
break;
case Floette when form == 5: // Floette Eternal Flower -- Never Released
if (enc is not MysteryGift)
return GetInvalid(LFormEternalInvalid);
return GetValid(LFormEternal);
return GetInvalid(FormEternalInvalid);
return GetValid(FormEternal);
case Meowstic when form != pk.Gender:
return GetInvalid(LGenderInvalidNone);
return GetInvalid(GenderInvalidNone);
case Silvally:
var silvally = FormItem.GetFormSilvally(pk.HeldItem);
return silvally != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem);
return silvally != form ? GetInvalid(FormItemInvalid) : GetValid(FormItemMatches);
// Form doesn't exist in SM; cannot originate from that game.
case Rockruff when enc.Generation == 7 && form == 1 && pk.SM:
case Lycanroc when enc.Generation == 7 && form == 2 && pk.SM:
return GetInvalid(LFormInvalidGame);
return GetInvalid(FormInvalidGame);
// Toxel encounters have already been checked for the nature-specific evolution criteria.
case Toxtricity when enc.Species == (int)Toxtricity:
// The game enforces the Nature for Toxtricity encounters too!
if (pk.Form != ToxtricityUtil.GetAmpLowKeyResult(pk.Nature))
return GetInvalid(LFormInvalidNature);
return GetInvalid(FormInvalidNature);
break;
// Ogerpon's form changes depending on its held mask
case Ogerpon when (form & 3) != FormItem.GetFormOgerpon(pk.HeldItem):
return GetInvalid(LFormItemInvalid);
return GetInvalid(FormItemInvalid);
// Impossible Egg forms
case Rotom when pk.IsEgg && form != 0:
case Furfrou when pk.IsEgg && form != 0:
return GetInvalid(LEggSpecies);
return GetInvalid(EggSpecies);
// Party Only Forms
case Shaymin:
case Furfrou:
case Hoopa:
if (form != 0 && !data.IsStoredSlot(StorageSlotType.Party) && pk.Format <= 6) // has form but stored in box
return GetInvalid(LFormParty);
return GetInvalid(FormParty);
break;
}
var format = pk.Format;
if (FormInfo.IsBattleOnlyForm(species, form, format))
return GetInvalid(LFormBattle);
return GetInvalid(FormBattle);
return VALID;
}

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -19,7 +19,7 @@ public override void Verify(LegalityAnalysis data)
// D/P/Pt & HG/SS Shedinja glitch -- only generation 4 spawns
bool ignore = pk is { Format: 4, Species: (int)Species.Shedinja } && pk.MetLevel != pk.CurrentLevel;
if (!ignore)
data.AddLine(GetInvalid(LGenderInvalidNone));
data.AddLine(GetInvalid(GenderInvalidNone));
return;
}
@@ -28,7 +28,7 @@ public override void Verify(LegalityAnalysis data)
if (gen is 3 or 4 or 5)
{
// Gender-PID & Nature-PID relationship check
var result = IsValidGenderPID(data) ? GetValid(LPIDGenderMatch) : GetInvalid(LPIDGenderMismatch);
var result = IsValidGenderPID(data) ? GetValid(PIDGenderMatch) : GetInvalid(PIDGenderMismatch);
data.AddLine(result);
if (gen != 5)
@@ -38,15 +38,15 @@ public override void Verify(LegalityAnalysis data)
// Check fixed gender cases
if ((pi.OnlyFemale && gender != 1) || (pi.OnlyMale && gender != 0))
data.AddLine(GetInvalid(LGenderInvalidNone));
data.AddLine(GetInvalid(GenderInvalidNone));
}
private static void VerifyNaturePID(LegalityAnalysis data)
{
var pk = data.Entity;
var result = GetExpectedNature(pk) == pk.Nature
? GetValid(LPIDNatureMatch, CheckIdentifier.Nature)
: GetInvalid(LPIDNatureMismatch, CheckIdentifier.Nature);
? GetValid(CheckIdentifier.Nature, PIDNatureMatch)
: GetInvalid(CheckIdentifier.Nature, PIDNatureMismatch);
data.AddLine(result);
}

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -18,7 +18,7 @@ public override void Verify(LegalityAnalysis data)
return;
var enc = data.EncounterMatch;
bool valid = IsGroundTileValid(enc, e);
var result = !valid ? GetInvalid(LEncTypeMismatch) : GetValid(LEncTypeMatch);
var result = !valid ? GetInvalid(EncTypeMismatch) : GetValid(EncTypeMatch);
data.AddLine(result);
}

View File

@@ -1,5 +1,5 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -68,22 +68,22 @@ private void VerifyTradeState(LegalityAnalysis data)
VerifyGeoLocationData(data, t, data.Entity);
if (pk.VC && pk is PK7 {Geo1_Country: 0}) // VC transfers set Geo1 Country
data.AddLine(GetInvalid(LGeoMemoryMissing));
data.AddLine(GetInvalid(GeoMemoryMissing));
if (!pk.IsUntraded)
{
// Can't have HT details even as a Link Trade egg, except in some games.
if (pk.IsEgg && !EggStateLegality.IsValidHTEgg(pk))
data.AddLine(GetInvalid(LMemoryArgBadHT));
data.AddLine(GetInvalid(MemoryArgBadHT));
return;
}
if (pk.CurrentHandler != 0) // Badly edited; PKHeX doesn't trip this.
data.AddLine(GetInvalid(LMemoryHTFlagInvalid));
data.AddLine(GetInvalid(MemoryHTFlagInvalid));
else if (pk.HandlingTrainerFriendship != 0)
data.AddLine(GetInvalid(LMemoryStatFriendshipHT0));
data.AddLine(GetInvalid(MemoryStatFriendshipHT0));
else if (pk is IAffection {HandlingTrainerAffection: not 0})
data.AddLine(GetInvalid(LMemoryStatAffectionHT0));
data.AddLine(GetInvalid(MemoryStatAffectionHT0));
// Don't check trade evolutions if Untraded. The Evolution Chain already checks for trade evolutions.
}
@@ -106,7 +106,7 @@ private void VerifyHandlerState(LegalityAnalysis data, bool neverOT)
{
// generally disable this check if it's being edited inside a blank save file's environment.
if (tr is not SaveFile { State.Exportable: false })
data.AddLine(GetInvalid(LTransferCurrentHandlerInvalid));
data.AddLine(GetInvalid(TransferCurrentHandlerInvalid));
// if there's no HT data yet specified, don't bother checking further.
// blank save exports will be injected and fixed later, and not-blanks will have been flagged by the above.
if (pk.IsUntraded)
@@ -118,9 +118,9 @@ private void VerifyHandlerState(LegalityAnalysis data, bool neverOT)
}
if (current != 1 && (enc.Context != pk.Context || neverOT))
data.AddLine(GetInvalid(LTransferHTFlagRequired));
data.AddLine(GetInvalid(TransferHandlerFlagRequired));
if (!pk.IsUntraded && IsUntradeableEncounter(enc)) // Starter, untradeable
data.AddLine(GetInvalid(LTransferCurrentHandlerInvalid));
data.AddLine(GetInvalid(TransferCurrentHandlerInvalid));
}
public static bool IsHandlerStateCorrect(IEncounterTemplate enc, PKM pk, byte current, byte expect)
@@ -141,15 +141,15 @@ private void CheckHandlingTrainerEquals(LegalityAnalysis data, PKM pk, ITrainerI
ht = ht[..len];
if (!ht.SequenceEqual(tr.OT))
data.AddLine(GetInvalid(LTransferHTMismatchName));
data.AddLine(GetInvalid(TransferHandlerMismatchName));
if (pk.HandlingTrainerGender != tr.Gender)
data.AddLine(GetInvalid(LTransferHTMismatchGender));
data.AddLine(GetInvalid(TransferHandlerMismatchGender));
// If the format exposes a language, check if it matches.
// Can be mismatched as the game only checks OT/Gender equivalence -- if it matches, don't update everything else.
// Statistically unlikely that players will play in different languages, but it's technically possible.
if (pk is IHandlerLanguage h && h.HandlingTrainerLanguage != tr.Language)
data.AddLine(Get(LTransferHTMismatchLanguage, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, TransferHandlerMismatchLanguage));
}
private static bool IsUntradeableEncounter(IEncounterTemplate enc) => enc switch
@@ -188,8 +188,9 @@ private void VerifyOTFriendship(LegalityAnalysis data, bool neverOT, byte genera
// If none match, then it is not a valid OT friendship.
var fs = pk.OriginalTrainerFriendship;
var enc = data.Info.EncounterMatch;
if (GetBaseFriendship(enc) != fs)
data.AddLine(GetInvalid(LMemoryStatFriendshipOTBaseEvent));
var expect = GetBaseFriendship(enc);
if (fs != expect)
data.AddLine(GetInvalid(MemoryStatFriendshipOTBaseEvent_0, expect));
}
}
@@ -199,20 +200,22 @@ private void VerifyOTFriendshipVC12(LegalityAnalysis data, PKM pk)
// Since some evolutions have different base friendship values, check all possible evolutions for a match.
// If none match, then it is not a valid OT friendship.
// VC transfers use S/M personal info
var any = IsMatchFriendship(data.Info.EvoChainsAllGens.Gen7, pk.OriginalTrainerFriendship);
var any = IsMatchFriendship(data.Info.EvoChainsAllGens.Gen7, pk.OriginalTrainerFriendship, out var hint);
if (!any)
data.AddLine(GetInvalid(LMemoryStatFriendshipOTBaseEvent));
data.AddLine(GetInvalid(MemoryStatFriendshipOTBaseEvent_0, hint));
}
private static bool IsMatchFriendship(EvoCriteria[] evos, int fs)
private static bool IsMatchFriendship(ReadOnlySpan<EvoCriteria> evos, byte current, out byte expect)
{
expect = 0; // will be overridden on the first loop
var pt = PersonalTable.USUM;
foreach (var z in evos)
{
if (!pt.IsPresentInGame(z.Species, z.Form))
continue;
var entry = pt.GetFormEntry(z.Species, z.Form);
if (entry.BaseFriendship == fs)
expect = entry.BaseFriendship;
if (expect == current)
return true;
}
return false;
@@ -228,7 +231,7 @@ private void VerifyOTAffection(LegalityAnalysis data, bool neverOT, int origin,
// Can gain affection in Gen6 via the Contest glitch applying affection to OT rather than HT.
// VC encounters cannot obtain OT affection since they can't visit Gen6.
if ((origin <= 2 && a.OriginalTrainerAffection != 0) || IsInvalidContestAffection(a))
data.AddLine(GetInvalid(LMemoryStatAffectionOT0));
data.AddLine(GetInvalid(MemoryStatAffectionOT0));
}
else if (neverOT)
{
@@ -237,17 +240,17 @@ private void VerifyOTAffection(LegalityAnalysis data, bool neverOT, int origin,
if (pk is { IsUntraded: true, XY: true })
{
if (a.OriginalTrainerAffection != 0)
data.AddLine(GetInvalid(LMemoryStatAffectionOT0));
data.AddLine(GetInvalid(MemoryStatAffectionOT0));
}
else if (IsInvalidContestAffection(a))
{
data.AddLine(GetInvalid(LMemoryStatAffectionOT0));
data.AddLine(GetInvalid(MemoryStatAffectionOT0));
}
}
else
{
if (a.OriginalTrainerAffection != 0)
data.AddLine(GetInvalid(LMemoryStatAffectionOT0));
data.AddLine(GetInvalid(MemoryStatAffectionOT0));
}
}
}
@@ -260,18 +263,18 @@ private void VerifyHTMisc(LegalityAnalysis data)
var pk = data.Entity;
var htGender = pk.HandlingTrainerGender;
if (htGender > 1 || (pk.IsUntraded && htGender != 0))
data.AddLine(GetInvalid(string.Format(LMemoryHTGender, htGender)));
data.AddLine(GetInvalid(MemoryHTGender_0, htGender));
}
private void VerifyGeoLocationData(LegalityAnalysis data, IGeoTrack t, PKM pk)
{
var valid = t.GetValidity();
if (valid == GeoValid.CountryAfterPreviousEmpty)
data.AddLine(GetInvalid(LGeoBadOrder));
data.AddLine(GetInvalid(GeoBadOrder));
else if (valid == GeoValid.RegionWithoutCountry)
data.AddLine(GetInvalid(LGeoNoRegion));
data.AddLine(GetInvalid(GeoNoRegion));
if (t.Geo1_Country != 0 && pk.IsUntraded) // traded
data.AddLine(GetInvalid(LGeoNoCountryHT));
data.AddLine(GetInvalid(GeoNoCountryHT));
}
// OR/AS contests mistakenly apply 20 affection to the OT instead of the current handler's value
@@ -314,13 +317,13 @@ public static bool GetCanOTHandle(IEncounterTemplate enc, PKM pk, byte generatio
_ => false,
};
private static int GetBaseFriendship(IEncounterTemplate enc) => enc switch
private static byte GetBaseFriendship(IEncounterTemplate enc) => enc switch
{
IFixedOTFriendship f => f.OriginalTrainerFriendship,
_ => GetBaseFriendship(enc.Context, enc.Species, enc.Form),
};
private static int GetBaseFriendship(EntityContext context, ushort species, byte form) => context switch
private static byte GetBaseFriendship(EntityContext context, ushort species, byte form) => context switch
{
EntityContext.Gen6 => PersonalTable.AO[species].BaseFriendship,
EntityContext.Gen7 => PersonalTable.USUM[species].BaseFriendship,

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -20,21 +20,21 @@ public override void Verify(LegalityAnalysis data)
if (!t.IsHyperTrainingAvailable())
{
data.AddLine(GetInvalid(LHyperPerfectUnavailable));
data.AddLine(GetInvalid(HyperPerfectUnavailable));
return;
}
var minLevel = t.GetHyperTrainMinLevel(data.Info.EvoChainsAllGens, pk.Context);
if (pk.CurrentLevel < minLevel)
{
data.AddLine(GetInvalid(string.Format(LHyperTooLow_0, minLevel)));
data.AddLine(GetInvalid(HyperTrainLevelGEQ_0, (ushort)minLevel));
return;
}
int max = pk.MaxIV;
if (pk.IVTotal == max * 6)
{
data.AddLine(GetInvalid(LHyperPerfectAll));
data.AddLine(GetInvalid(HyperPerfectAll));
return;
}
@@ -53,7 +53,7 @@ public override void Verify(LegalityAnalysis data)
}
if (IsFlawlessHyperTrained(pk, t, max))
data.AddLine(GetInvalid(LHyperPerfectOne));
data.AddLine(GetInvalid(HyperPerfectOne));
}
public static bool IsFlawlessHyperTrained(PKM pk, IHyperTrain t, int max)

View File

@@ -1,5 +1,5 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -30,7 +30,7 @@ public override void Verify(LegalityAnalysis data)
var pk = data.Entity;
var hp = pk.IV_HP;
if (hp < 30 && AllIVsEqual(pk, hp))
data.AddLine(Get(string.Format(LIVAllEqual_0, hp), Severity.Fishy));
data.AddLine(Get(Severity.Fishy, IVAllEqual_0, (ushort)hp));
}
private static bool AllIVsEqual(PKM pk, int hp) => pk.IV_ATK == hp
@@ -51,7 +51,7 @@ private void VerifyIVsMystery(LegalityAnalysis data, MysteryGift g)
{
bool valid = Legal.GetIsFixedIVSequenceValidSkipRand(IVs, data.Entity);
if (!valid)
data.AddLine(GetInvalid(LEncGiftIVMismatch));
data.AddLine(GetInvalid(EncGiftIVMismatch));
}
else
{
@@ -80,12 +80,12 @@ private void VerifyIVsFlawless(LegalityAnalysis data, IFlawlessIVCount s)
private void VerifyIVsFlawless(LegalityAnalysis data, int count)
{
if (data.Entity.FlawlessIVCount < count)
data.AddLine(GetInvalid(string.Format(LIVF_COUNT0_31, count)));
data.AddLine(GetInvalid(IVFlawlessCountGEQ_0, (ushort)count));
}
private void VerifyIVsGoTransfer(LegalityAnalysis data, IPogoSlot g)
{
if (!g.GetIVsValid(data.Entity))
data.AddLine(GetInvalid(LIVNotCorrect));
data.AddLine(GetInvalid(IVNotCorrect));
}
}

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -14,10 +14,10 @@ public override void Verify(LegalityAnalysis data)
var pk = data.Entity;
var item = pk.HeldItem;
if (pk.IsEgg && item != 0)
data.AddLine(GetInvalid(LItemEgg));
data.AddLine(GetInvalid(ItemEgg));
if (!ItemRestrictions.IsHeldItemAllowed(item, context: pk.Context))
data.AddLine(GetInvalid(LItemUnreleased));
data.AddLine(GetInvalid(ItemUnreleased));
else if (item == 175 && pk is G3PKM g3) // Enigma Berry
VerifyEnigmaGen3(data, g3);
}
@@ -27,7 +27,7 @@ private void VerifyEnigmaGen3(LegalityAnalysis data, G3PKM g3)
// A Pokémon holding this Berry cannot be traded to Pokémon Colosseum or Pokémon XD: Gale of Darkness,
// nor can it be stored in Pokémon Box Ruby & Sapphire.
if (g3 is CK3 or XK3 || ParseSettings.ActiveTrainer is SAV3RSBox)
data.AddLine(GetInvalid(LItemUnreleased));
data.AddLine(GetInvalid(ItemUnreleased));
else
VerifyEReaderBerry(data);
}
@@ -42,10 +42,10 @@ private void VerifyEReaderBerry(LegalityAnalysis data)
private CheckResult GetEReaderCheckResult(EReaderBerryMatch status) => status switch
{
EReaderBerryMatch.NoMatch => GetInvalid(LEReaderInvalid),
EReaderBerryMatch.NoData => GetInvalid(LItemUnreleased),
EReaderBerryMatch.InvalidUSA => GetInvalid(LEReaderAmerica),
EReaderBerryMatch.InvalidJPN => GetInvalid(LEReaderJapan),
EReaderBerryMatch.NoMatch => GetInvalid(EReaderInvalid),
EReaderBerryMatch.NoData => GetInvalid(ItemUnreleased),
EReaderBerryMatch.InvalidUSA => GetInvalid(EReaderAmerica),
EReaderBerryMatch.InvalidJPN => GetInvalid(EReaderJapan),
_ => default,
};
}

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.GameVersion;
using static PKHeX.Core.LanguageID;
@@ -20,7 +20,7 @@ public override void Verify(LegalityAnalysis data)
var enc = data.EncounterMatch;
if (!IsValidLanguageID(currentLanguage, maxLanguageID, pk, enc))
{
data.AddLine(GetInvalid(string.Format(LOTLanguage, $"<={maxLanguageID}", currentLanguage)));
data.AddLine(GetInvalid(Identifier, OTLanguageShouldBeLeq_0, (byte)maxLanguageID, (byte)currentLanguage));
return;
}
@@ -30,9 +30,7 @@ public override void Verify(LegalityAnalysis data)
)
{
bool kor = currentLanguage == Korean;
var msgpkm = kor ? L_XKorean : L_XKoreanNon;
var msgsav = kor ? L_XKoreanNon : L_XKorean;
data.AddLine(GetInvalid(string.Format(LTransferOriginFInvalid0_1, msgpkm, msgsav)));
data.AddLine(GetInvalid(TransferKoreanGen4));
return;
}
@@ -40,11 +38,11 @@ public override void Verify(LegalityAnalysis data)
{
// Korean Crystal does not exist, neither do Korean VC1
if (pk is { Korean: true, Version: not (GD or SI) })
data.AddLine(GetInvalid(string.Format(LOTLanguage, $"!={currentLanguage}", currentLanguage)));
data.AddLine(GetInvalid(OTLanguageCannotPlayOnVersion_0, (byte)pk.Version));
// Japanese VC is language locked; cannot obtain Japanese-Blue version as other languages.
if (pk is { Japanese: false, Version: BU })
data.AddLine(GetInvalid(string.Format(LOTLanguage, nameof(Japanese), currentLanguage)));
data.AddLine(GetInvalid(OTLanguageCannotPlayOnVersion_0, (byte)pk.Version));
}
}

View File

@@ -1,5 +1,5 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -16,9 +16,9 @@ public override void Verify(LegalityAnalysis data)
return;
if (pa.IsNoble)
data.AddLine(GetInvalid(LStatNobleInvalid));
data.AddLine(GetInvalid(StatNobleInvalid));
if (pa.IsAlpha != data.EncounterMatch is IAlphaReadOnly { IsAlpha: true })
data.AddLine(GetInvalid(LStatAlphaInvalid));
data.AddLine(GetInvalid(StatAlphaInvalid));
CheckScalars(data, pa);
CheckGanbaru(data, pa);
@@ -36,7 +36,7 @@ private static void CheckGanbaru(LegalityAnalysis data, PA8 pa)
if (gv <= max)
continue;
data.AddLine(GetInvalid(LGanbaruStatTooHigh, CheckIdentifier.GVs));
data.AddLine(GetInvalid(CheckIdentifier.GVs, GanbaruStatLEQ_01, max, (ushort)i));
return;
}
}
@@ -47,14 +47,14 @@ private void CheckScalars(LegalityAnalysis data, PA8 pa)
if (pa.IsAlpha && data.EncounterMatch is EncounterSlot8a)
{
if (pa.HeightScalar != 255)
data.AddLine(GetInvalid(LStatIncorrectHeightValue));
data.AddLine(GetInvalid(StatIncorrectHeightValue, 255));
if (pa.WeightScalar != 255)
data.AddLine(GetInvalid(LStatIncorrectWeightValue));
data.AddLine(GetInvalid(StatIncorrectWeightValue, 255));
}
// No way to mutate the display height scalar value. Must match!
if (pa.HeightScalar != pa.Scale)
data.AddLine(GetInvalid(LStatIncorrectHeightCopy, CheckIdentifier.Encounter));
data.AddLine(GetInvalid(CheckIdentifier.Encounter, StatIncorrectHeightCopy));
}
private static void CheckLearnset(LegalityAnalysis data, PA8 pa)
@@ -188,7 +188,7 @@ private void VerifyTutorMoveIndex(LegalityAnalysis data, PA8 pa, int i, IPermitR
if (permit.IsRecordPermitted(i))
return; // If it has been legally purchased, then any mastery state is legal.
data.AddLine(GetInvalid(string.Format(LMoveShopPurchaseInvalid_0, ParseSettings.MoveStrings[permit.RecordPermitIndexes[i]])));
data.AddLine(GetInvalid(MoveShopPurchaseInvalid_0, permit.RecordPermitIndexes[i]));
return;
}
@@ -204,9 +204,9 @@ private void VerifyTutorMoveIndex(LegalityAnalysis data, PA8 pa, int i, IPermitR
if (data.EncounterMatch is (IMoveset m and IMasteryInitialMoveShop8) && m.Moves.Contains(move))
return; // Previously checked.
if (!permit.IsRecordPermitted(i))
data.AddLine(GetInvalid(string.Format(LMoveShopMasterInvalid_0, ParseSettings.MoveStrings[move])));
data.AddLine(GetInvalid(MoveShopMasterInvalid_0, move));
else if (!CanLearnMoveByLevelUp(data, pa, i, moves))
data.AddLine(GetInvalid(string.Format(LMoveShopMasterNotLearned_0, ParseSettings.MoveStrings[move])));
data.AddLine(GetInvalid(MoveShopMasterNotLearned_0, move));
}
private static bool CanLearnMoveByLevelUp(LegalityAnalysis data, PA8 pa, int i, ReadOnlySpan<ushort> moves)
@@ -229,12 +229,12 @@ private void VerifyAlphaMove(LegalityAnalysis data, PA8 pa, ushort alphaMove, IP
{
if (!pa.IsAlpha || data.EncounterMatch is EncounterSlot8a { Type: SlotType8a.Landmark })
{
data.AddLine(GetInvalid(LMoveShopAlphaMoveShouldBeZero));
data.AddLine(GetInvalid(MoveShopAlphaMoveShouldBeZero));
return;
}
if (!CanMasterMoveFromMoveShop(alphaMove, permit))
{
data.AddLine(GetInvalid(LMoveShopAlphaMoveShouldBeOther));
data.AddLine(GetInvalid(MoveShopAlphaMoveShouldBeOther));
return;
}
@@ -242,7 +242,7 @@ private void VerifyAlphaMove(LegalityAnalysis data, PA8 pa, ushort alphaMove, IP
var masteredIndex = permit.RecordPermitIndexes.IndexOf(alphaMove);
// Index is already >= 0, implicitly via the above call not returning false.
if (!pa.GetMasteredRecordFlag(masteredIndex))
data.AddLine(GetInvalid(LMoveShopAlphaMoveShouldBeMastered));
data.AddLine(GetInvalid(MoveShopAlphaMoveShouldBeMastered_0, alphaMove));
}
private void VerifyAlphaMoveZero(LegalityAnalysis data)
@@ -256,7 +256,7 @@ private void VerifyAlphaMoveZero(LegalityAnalysis data)
var pi = PersonalTable.LA.GetFormEntry(enc.Species, enc.Form);
if (!pi.HasMoveShop) // must have had a tutor flag
data.AddLine(GetInvalid(LMoveShopAlphaMoveShouldBeOther));
data.AddLine(GetInvalid(MoveShopAlphaMoveShouldBeOther));
}
private static bool CanMasterMoveFromMoveShop(ushort move, IPermitRecord permit)

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -17,12 +17,12 @@ public override void Verify(LegalityAnalysis data)
{
if (!IsMetLevelMatchEncounter(gift, pk))
{
data.AddLine(GetInvalid(LLevelMetGift));
data.AddLine(GetInvalid(LevelMetGift));
return;
}
if (gift.Level > pk.CurrentLevel)
{
data.AddLine(GetInvalid(LLevelMetGiftFail));
data.AddLine(GetInvalid(LevelMetGiftFail, gift.Level));
return;
}
}
@@ -31,7 +31,7 @@ public override void Verify(LegalityAnalysis data)
{
if (pk.CurrentLevel != enc.LevelMin)
{
data.AddLine(GetInvalid(string.Format(LEggFMetLevel_0, enc.LevelMin)));
data.AddLine(GetInvalid(EggFMetLevel_0, enc.LevelMin));
return;
}
@@ -39,7 +39,7 @@ public override void Verify(LegalityAnalysis data)
? 125 // Gen2 Dizzy Punch gifts always have 125 EXP, even if it's more than the Lv5 exp required.
: Experience.GetEXP(enc.LevelMin, data.PersonalInfo.EXPGrowth);
if (reqEXP != pk.EXP)
data.AddLine(GetInvalid(LEggEXP));
data.AddLine(GetInvalid(EggEXP, reqEXP));
return;
}
@@ -48,15 +48,15 @@ public override void Verify(LegalityAnalysis data)
{
var expect = Experience.GetEXP(100, data.PersonalInfo.EXPGrowth);
if (pk.EXP != expect)
data.AddLine(GetInvalid(LLevelEXPTooHigh));
data.AddLine(GetInvalid(Identifier, LevelEXPTooHigh, expect));
}
if (lvl < pk.MetLevel)
data.AddLine(GetInvalid(LLevelMetBelow));
data.AddLine(GetInvalid(LevelMetBelow, lvl));
else if (!enc.IsWithinEncounterRange(pk) && lvl != 100 && pk.EXP == Experience.GetEXP(lvl, data.PersonalInfo.EXPGrowth))
data.AddLine(Get(LLevelEXPThreshold, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, LevelEXPThreshold));
else
data.AddLine(GetValid(LLevelMetSane));
data.AddLine(GetValid(LevelMetSane));
}
private static bool IsMetLevelMatchEncounter(MysteryGift gift, PKM pk)
@@ -81,25 +81,21 @@ public void VerifyG1(LegalityAnalysis data)
if (pk.IsEgg)
{
if (pk.CurrentLevel != EncounterEgg2.Level)
data.AddLine(GetInvalid(string.Format(LEggFMetLevel_0, EncounterEgg2.Level)));
data.AddLine(GetInvalid(EggFMetLevel_0, EncounterEgg2.Level));
return;
}
if (pk.MetLocation != 0) // crystal
{
var lvl = pk.CurrentLevel;
if (lvl < pk.MetLevel)
data.AddLine(GetInvalid(LLevelMetBelow));
data.AddLine(GetInvalid(LevelMetBelow, lvl));
}
if (IsTradeEvolutionRequired(data, enc))
{
// Pokémon has been traded illegally between games without evolving.
// Trade evolution species IDs for Gen1 are sequential dex numbers.
var names = ParseSettings.SpeciesStrings;
var species = enc.Species;
var evolved = names[species + 1];
var unevolved = names[species];
data.AddLine(GetInvalid(string.Format(LEvoTradeReqOutsider, unevolved, evolved)));
data.AddLine(GetInvalid(EvoTradeReqOutsider_0, enc.Species + 1u));
}
}

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.RibbonIndex;
namespace PKHeX.Core;
@@ -25,13 +25,12 @@ public override void Verify(LegalityAnalysis data)
if (pk.IsEgg && pk is IRibbonSetAffixed a && a.AffixedRibbon != -1)
{
// Disallow affixed values on eggs.
var affix = (RibbonIndex)a.AffixedRibbon;
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe(affix))));
data.AddLine(GetInvalid(RibbonMarkingAffixed_0, (ushort)(RibbonIndex)a.AffixedRibbon));
}
// Some encounters come with a fixed Mark, and we've not yet checked if it's missing.
if (data.EncounterMatch is IEncounterMarkExtra extra && extra.IsMissingExtraMark(pk, out var missing))
data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, GetRibbonNameSafe(missing))));
data.AddLine(GetInvalid(RibbonMissing_0, (ushort)missing));
}
private void VerifyNoMarksPresent(LegalityAnalysis data, IRibbonIndex m)
@@ -39,7 +38,7 @@ private void VerifyNoMarksPresent(LegalityAnalysis data, IRibbonIndex m)
for (var mark = MarkLunchtime; mark <= MarkSlump; mark++)
{
if (m.GetRibbon((int)mark))
data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, GetRibbonNameSafe(mark))));
data.AddLine(GetInvalid(RibbonMarkingInvalid_0, (ushort)mark));
}
}
@@ -54,14 +53,14 @@ private void VerifyMarksPresent(LegalityAnalysis data, IRibbonIndex m)
if (hasOne)
{
data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, GetRibbonNameSafe(mark))));
data.AddLine(GetInvalid(RibbonMarkingInvalid_0, (ushort)mark));
return;
}
bool result = MarkRules.IsEncounterMarkValid(mark, data.Entity, data.EncounterMatch);
if (!result)
{
data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, GetRibbonNameSafe(mark))));
data.AddLine(GetInvalid(RibbonMarkingInvalid_0, (ushort)mark));
return;
}
@@ -69,14 +68,6 @@ private void VerifyMarksPresent(LegalityAnalysis data, IRibbonIndex m)
}
}
private static string GetRibbonNameSafe(RibbonIndex index)
{
if (index >= MAX_COUNT)
return index.ToString();
var expect = $"Ribbon{index}";
return RibbonStrings.GetName(expect);
}
private void VerifyAffixedRibbonMark(LegalityAnalysis data, IRibbonIndex m)
{
if (m is not IRibbonSetAffixed a)
@@ -90,7 +81,7 @@ private void VerifyAffixedRibbonMark(LegalityAnalysis data, IRibbonIndex m)
var max = MarkRules.GetMaxAffixValue(data.Info.EvoChainsAllGens);
if ((sbyte)max == -1 || affix > max)
{
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe(affix))));
data.AddLine(GetInvalid(RibbonMarkingAffixed_0, (ushort)affix));
return;
}
@@ -114,7 +105,7 @@ private void VerifyShedinjaAffixed(LegalityAnalysis data, RibbonIndex affix, PKM
if (affix.IsEncounterMark8())
{
if (!MarkRules.IsEncounterMarkValid(affix, pk, enc))
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe(affix))));
data.AddLine(GetInvalid(RibbonMarkingAffixed_0, (ushort)affix));
return;
}
@@ -129,10 +120,9 @@ private void VerifyShedinjaAffixed(LegalityAnalysis data, RibbonIndex affix, PKM
clone.Species = (int) Species.Nincada;
var args = new RibbonVerifierArguments(clone, enc, data.Info.EvoChainsAllGens);
affix.Fix(args, true);
var name = GetRibbonNameSafe(affix);
bool invalid = RibbonVerifier.IsValidExtra(affix, args);
var severity = invalid ? Severity.Invalid : Severity.Fishy;
data.AddLine(Get(string.Format(LRibbonMarkingAffixedF_0, name), severity));
data.AddLine(Get(severity, RibbonMarkingAffixed_0, (ushort)affix));
}
private static bool IsMoveSetEvolvedShedinja(PKM pk)
@@ -153,6 +143,6 @@ private void EnsureHasRibbon(LegalityAnalysis data, IRibbonIndex m, RibbonIndex
{
var hasRibbon = m.GetRibbonIndex(affix);
if (!hasRibbon)
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe(affix))));
data.AddLine(GetInvalid(RibbonMarkingAffixed_0, (ushort)affix));
}
}

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.CheckIdentifier;
namespace PKHeX.Core;
@@ -21,7 +21,7 @@ private void VerifyFavoriteMark(LegalityAnalysis data, PKM pk)
{
// Can only be toggled on in LGP/E, and is retained via transfer to HOME and into other games.
if (pk is IFavorite { IsFavorite: true } && !data.Info.EvoChainsAllGens.HasVisitedLGPE)
data.AddLine(GetInvalid(LFavoriteMarkingUnavailable));
data.AddLine(GetInvalid(FavoriteMarkingUnavailable));
}
private void VerifyMarkValue(LegalityAnalysis data, PKM pk)
@@ -47,14 +47,14 @@ private void VerifyMarkValueDual(LegalityAnalysis data, IAppliedMarkings7 pk, us
if (mv == 0)
return;
if (mv > Dual6)
data.AddLine(GetInvalid(LMarkValueUnusedBitsPresent));
data.AddLine(GetInvalid(MarkValueUnusedBitsPresent));
var count = pk.MarkingCount;
for (int i = 0; i < count; i++)
{
var value = pk.GetMarking(i);
if (value is not (0 or MarkingColor.Blue or MarkingColor.Pink))
data.AddLine(GetInvalid(string.Format(LMarkValueOutOfRange_0, i)));
data.AddLine(GetInvalid(MarkValueOutOfRange_0, (ushort)i));
}
}
@@ -63,7 +63,7 @@ private void VerifyMarkValueSingle(LegalityAnalysis data, IAppliedMarkings3 pk,
if (mv == 0)
return;
if (!IsMarkValueValid3456(pk, mv))
data.AddLine(GetInvalid(LMarkValueUnusedBitsPresent));
data.AddLine(GetInvalid(MarkValueUnusedBitsPresent));
}
private static bool IsMarkValueValid3456(IAppliedMarkings3 pk, int value)

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -22,18 +22,18 @@ private void VerifyMedalsRegular(LegalityAnalysis data)
var Info = data.Info;
uint value = train.SuperTrainBitFlags;
if ((value & 3) != 0) // 2 unused flags
data.AddLine(GetInvalid(LSuperUnused));
data.AddLine(GetInvalid(SuperUnused));
int TrainCount = train.SuperTrainingMedalCount();
if (pk.IsEgg)
{
// Can't have any super training data as an egg.
if (TrainCount > 0)
data.AddLine(GetInvalid(LSuperEgg));
data.AddLine(GetInvalid(SuperEgg));
if (train.SecretSuperTrainingUnlocked)
data.AddLine(GetInvalid(LSuperNoUnlocked));
data.AddLine(GetInvalid(SuperNoUnlocked));
if (train.SecretSuperTrainingComplete)
data.AddLine(GetInvalid(LSuperNoComplete));
data.AddLine(GetInvalid(SuperNoComplete));
return;
}
@@ -41,11 +41,11 @@ private void VerifyMedalsRegular(LegalityAnalysis data)
{
// Can't have any super training data if it never visited Gen6.
if (TrainCount > 0)
data.AddLine(GetInvalid(LSuperUnavailable));
data.AddLine(GetInvalid(SuperUnavailable));
if (train.SecretSuperTrainingUnlocked)
data.AddLine(GetInvalid(LSuperNoUnlocked));
data.AddLine(GetInvalid(SuperNoUnlocked));
if (train.SecretSuperTrainingComplete)
data.AddLine(GetInvalid(LSuperNoComplete));
data.AddLine(GetInvalid(SuperNoComplete));
return;
}
@@ -53,15 +53,15 @@ private void VerifyMedalsRegular(LegalityAnalysis data)
{
// Gen6->Gen7 transfer wipes the two Secret flags.
if (train.SecretSuperTrainingUnlocked)
data.AddLine(GetInvalid(LSuperNoUnlocked));
data.AddLine(GetInvalid(SuperNoUnlocked));
if (train.SecretSuperTrainingComplete)
data.AddLine(GetInvalid(LSuperNoComplete));
data.AddLine(GetInvalid(SuperNoComplete));
return;
}
// Only reach here if Format==6.
if (TrainCount == 30 ^ train.SecretSuperTrainingComplete)
data.AddLine(GetInvalid(LSuperComplete));
data.AddLine(GetInvalid(SuperComplete));
}
private void VerifyMedalsEvent(LegalityAnalysis data)
@@ -69,6 +69,6 @@ private void VerifyMedalsEvent(LegalityAnalysis data)
var pk = data.Entity;
byte value = pk.Data[0x3A];
if (value != 0)
data.AddLine(GetInvalid(LSuperDistro));
data.AddLine(GetInvalid(SuperDistro));
}
}

View File

@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.MemoryPermissions;
using static PKHeX.Core.EntityContext;
@@ -85,7 +85,7 @@ private void VerifyHTLanguage(LegalityAnalysis data, MemorySource source)
if (pk is not IHandlerLanguage h)
return;
if (!GetIsHTLanguageValid(data.EncounterMatch, pk, h.HandlingTrainerLanguage, source))
data.AddLine(GetInvalid(LMemoryHTLanguage));
data.AddLine(GetInvalid(MemoryHTLanguage));
}
private static bool GetIsHTLanguageValid(IEncounterTemplate enc, PKM pk, byte language, MemorySource source)
@@ -142,16 +142,16 @@ private CheckResult VerifyCommonMemory(PKM pk, int handler, LegalInfo info, Memo
return VerifyMemoryHM6(info, mem, memory, hmIndex);
if (mem.IsInvalidGeneralLocationMemoryValue(memory.MemoryID, memory.Variable, info.EncounterMatch, pk))
return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler));
return GetInvalid(MemoryArgBadLocation_H, memory.Handler);
if (mem.IsInvalidMiscMemory(memory.MemoryID, memory.Variable, (Species)pk.Species, pk.Version, handler))
return GetInvalid(string.Format(LMemoryArgBadID, memory.Handler));
return GetInvalid(MemoryArgBadID_H, memory.Handler);
switch (memory.MemoryID)
{
case 19 when pk.Species is (int)Species.Urshifu && memory.Variable is not 34: // tall building is the only location for evolving Urshifu
case 19 when pk.Species is (int)Species.Runerigus && memory.Variable is not 72: // vast field is the only location for evolving Runerigus
return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler));
return GetInvalid(MemoryArgBadLocation_H, memory.Handler);
// {0} saw {2} carrying {1} on its back. {4} that {3}.
case 21 when mem.Context != Gen6 || !PersonalTable.AO.GetFormEntry(memory.Variable, 0).GetIsLearnHM(2): // Fly
@@ -170,7 +170,7 @@ private CheckResult VerifyCommonMemory(PKM pk, int handler, LegalInfo info, Memo
case 71 when !GetCanDynamaxTrainer(memory.Variable, 8, handler == 0 ? pk.Version : GameVersion.Any):
// {0} battled {2} and Dynamaxed upon {1}s instruction. {4} that {3}.
case 72 when !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable):
return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler));
return GetInvalid(MemoryArgBadSpecies_H1, memory.Handler, memory.Variable);
// Move
// {0} studied about how to use {2} in a Box, thinking about {1}. {4} that {3}.
@@ -181,23 +181,23 @@ private CheckResult VerifyCommonMemory(PKM pk, int handler, LegalInfo info, Memo
// Species
// With {1}, {0} went fishing, and they caught {2}. {4} that {3}.
case 7 when !GetCanFishSpecies(memory.Variable, mem.Context, handler == 0 ? pk.Version : GameVersion.Any):
return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler));
return GetInvalid(MemoryArgBadSpecies_H1, memory.Handler, memory.Variable);
// {0} saw {1} paying attention to {2}. {4} that {3}.
// {0} fought hard until it had to use Struggle when it battled at {1}s side against {2}. {4} that {3}.
// {0} was taken to a Pokémon Nursery by {1} and left with {2}. {4} that {3}.
case 9 or 60 or 75 when mem.Context == Gen8 && !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable):
return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler));
return GetInvalid(MemoryArgBadSpecies_H1, memory.Handler, memory.Variable);
// {0} had a great chat about {1} with the {2} that it was in a Box with. {4} that {3}.
// {0} became good friends with the {2} in a Box, practiced moves with it, and talked about the day that {0} would be praised by {1}. {4} that {3}.
// {0} got in a fight with the {2} that it was in a Box with about {1}. {4} that {3}.
case 82 or 83 or 87 when !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable):
return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler));
return GetInvalid(MemoryArgBadSpecies_H1, memory.Handler, memory.Variable);
// {0} had a very hard training session with {1}. {4} that {3}.
case 53 when mem.Context == Gen8 && pk is IHyperTrain t && !t.IsHyperTrained():
return GetInvalid(string.Format(LMemoryArgBadID, memory.Handler));
return GetInvalid(MemoryArgBadID_H, memory.Handler);
// Item
// {0} went to a Pokémon Center with {1} to buy {2}. {4} that {3}.
@@ -215,7 +215,7 @@ private CheckResult VerifyCommonMemory(PKM pk, int handler, LegalInfo info, Memo
// {0} was worried if {1} was looking for the {2} that it was holding in a Box. {4} that {3}.
// When {0} was in a Box, it thought about the reason why {1} had it hold the {2}. {4} that {3}.
case 84 or 88 when !Legal.HeldItems_SWSH.Contains(memory.Variable) || pk.IsEgg:
return GetInvalid(string.Format(LMemoryArgBadItem, memory.Handler));
return GetInvalid(Identifier, MemoryArgBadItem_H1, memory.Handler, memory.Variable);
}
return VerifyCommonMemoryEtc(memory, mem);
@@ -244,22 +244,25 @@ private CheckResult VerifyMemoryHM6(LegalInfo info, MemoryContext mem, MemoryVar
return BadSpeciesMove(memory.Handler);
}
private CheckResult BadSpeciesMove(string handler) => GetInvalid(string.Format(LMemoryArgBadMove, handler));
private CheckResult BadSpeciesMove(byte handler) => GetInvalid(MemoryArgBadMove_H1, handler);
private CheckResult VerifyCommonMemoryEtc(MemoryVariableSet memory, MemoryContext context)
{
if (!context.CanHaveIntensity(memory.MemoryID, memory.Intensity))
{
var min = context.GetMinimumIntensity(memory.MemoryID);
return GetInvalid(string.Format(LMemoryIndexIntensityMin, memory.Handler, min));
return GetInvalid(Identifier, MemoryIndexIntensityMin_H1, memory.Handler, min);
}
if (!context.CanHaveFeeling(memory.MemoryID, memory.Feeling, memory.Variable))
return GetInvalid(string.Format(LMemoryFeelInvalid, memory.Handler));
return GetInvalid(MemoryFeelInvalid_H, memory.Handler);
return GetValid(string.Format(LMemoryF_0_Valid, memory.Handler));
return GetValid(MemoryValid_H, memory.Handler);
}
private const ushort L_XOT = 0; // Original Trainer Memory
private const ushort L_XHT = 1; // Handling Trainer Memory
/// <summary>
/// Used for enforcing a fixed memory detail.
/// </summary>
@@ -272,19 +275,19 @@ private void VerifyOTMemoryIs(LegalityAnalysis data, byte m, byte i, ushort t, b
{
var pk = (ITrainerMemories)data.Entity;
if (pk.OriginalTrainerMemory != m)
data.AddLine(GetInvalid(string.Format(LMemoryIndexID, L_XOT, m)));
data.AddLine(GetInvalid(MemoryIndexID_H1, L_XOT, m));
if (pk.OriginalTrainerMemoryIntensity != i)
data.AddLine(GetInvalid(string.Format(LMemoryIndexIntensity, L_XOT, i)));
data.AddLine(GetInvalid(MemoryIndexIntensity_H1, L_XOT, i));
if (pk.OriginalTrainerMemoryVariable != t)
data.AddLine(GetInvalid(string.Format(LMemoryIndexVar, L_XOT, t)));
data.AddLine(GetInvalid(MemoryIndexVar_H1, L_XOT, t));
if (pk.OriginalTrainerMemoryFeeling != f)
data.AddLine(GetInvalid(string.Format(LMemoryIndexFeel, L_XOT, f)));
data.AddLine(GetInvalid(MemoryIndexFeel_H1, L_XOT, f));
}
private void VerifyHTMemoryNone(LegalityAnalysis data, ITrainerMemories pk)
{
if (pk.HandlingTrainerMemory != 0 || pk.HandlingTrainerMemoryVariable != 0 || pk.HandlingTrainerMemoryIntensity != 0 || pk.HandlingTrainerMemoryFeeling != 0)
data.AddLine(GetInvalid(string.Format(LMemoryCleared, L_XHT)));
data.AddLine(GetInvalid(MemoryCleared_H, L_XHT));
}
private void VerifyOTMemory(LegalityAnalysis data)
@@ -339,37 +342,37 @@ private void VerifyOTMemory(LegalityAnalysis data)
// Bounds checking
var mc = Memories.GetContext(context);
if (!mc.CanObtainMemoryOT(pk.Version, memory))
data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XOT)));
data.AddLine(GetInvalid(MemoryArgBadID_H, L_XOT));
// Verify memory if specific to OT
switch (memory)
{
// No Memory
case 0: // SW/SH trades don't set HT memories immediately, which is hilarious.
data.AddLine(Get(LMemoryMissingOT, mc.Context == Gen8 ? Severity.Fishy : Severity.Invalid));
data.AddLine(Get(mc.Context == Gen8 ? Severity.Fishy : Severity.Invalid, MemoryMissingOT));
VerifyOTMemoryIs(data, 0, 0, 0, 0);
return;
// {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}.
case 2 when !enc.IsEgg:
data.AddLine(GetInvalid(string.Format(LMemoryArgBadHatch, L_XOT)));
data.AddLine(GetInvalid(MemoryArgBadHatch_H, L_XOT));
break;
// {0} became {1}s friend when it arrived via Link Trade at... {2}. {4} that {3}.
case 4 when mc.Context == Gen6: // Gen8 applies this memory erroneously
data.AddLine(GetInvalid(string.Format(LMemoryArgBadOTEgg, L_XOT)));
data.AddLine(GetInvalid(MemoryArgBadOTEgg_H, L_XOT));
return;
// {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}.
case 6 when !mc.HasPokeCenter(pk.Version, mem.OriginalTrainerMemoryVariable):
data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XOT)));
data.AddLine(GetInvalid(MemoryArgBadLocation_H, L_XOT));
return;
// {0} was with {1} when {1} caught {2}. {4} that {3}.
case 14:
var result = GetCanBeCaptured(mem.OriginalTrainerMemoryVariable, mc.Context, pk.Version) // Any Game in the Handling Trainer's generation
? GetValid(string.Format(LMemoryArgSpecies, L_XOT))
: GetInvalid(string.Format(LMemoryArgBadSpecies, L_XOT));
? GetValid(MemoryArgSpecies_H, L_XOT)
: GetInvalid(MemoryArgBadSpecies_H1, L_XOT, mem.OriginalTrainerMemoryVariable);
data.AddLine(result);
return;
}
@@ -426,7 +429,7 @@ private void VerifyHTMemory(LegalityAnalysis data, EntityContext memoryGen)
// Bounds checking
var mc = Memories.GetContext(memoryGen);
if (!mc.CanObtainMemoryHT(pk.Version, memory))
data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XHT)));
data.AddLine(GetInvalid(MemoryArgBadID_H, L_XHT));
// Verify memory if specific to HT
switch (memory)
@@ -440,30 +443,30 @@ private void VerifyHTMemory(LegalityAnalysis data, EntityContext memoryGen)
_ => Severity.Invalid,
};
if (severity != Severity.Valid)
data.AddLine(Get(LMemoryMissingHT, severity));
data.AddLine(Get(severity, MemoryMissingHT));
VerifyHTMemoryNone(data, mem);
return;
// {0} met {1} at... {2}. {1} threw a Poké Ball at it, and they started to travel together. {4} that {3}.
case 1:
data.AddLine(GetInvalid(string.Format(LMemoryArgBadCatch, L_XHT)));
data.AddLine(GetInvalid(MemoryArgBadCatch_H, L_XHT));
return;
// {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}.
case 2:
data.AddLine(GetInvalid(string.Format(LMemoryArgBadHatch, L_XHT)));
data.AddLine(GetInvalid(MemoryArgBadHatch_H, L_XHT));
return;
// {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}.
case 6 when !mc.HasPokeCenter(GameVersion.Any, mem.HandlingTrainerMemoryVariable):
data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XHT)));
data.AddLine(GetInvalid(MemoryArgBadLocation_H, L_XHT));
return;
// {0} was with {1} when {1} caught {2}. {4} that {3}.
case 14:
var result = GetCanBeCaptured(mem.HandlingTrainerMemoryVariable, mc.Context, GameVersion.Any) // Any Game in the Handling Trainer's generation
? GetValid(string.Format(LMemoryArgSpecies, L_XHT))
: GetInvalid(string.Format(LMemoryArgBadSpecies, L_XHT));
? GetValid(MemoryArgSpecies_H, L_XHT)
: GetInvalid(MemoryArgBadSpecies_H1, L_XHT, mem.HandlingTrainerMemoryVariable);
data.AddLine(result);
return;
}
@@ -492,12 +495,12 @@ private void VerifyHTMemoryTransferTo7(LegalityAnalysis data, PKM pk, LegalInfo
return;
if (mem.HandlingTrainerMemory != 4)
data.AddLine(Severity.Invalid, LMemoryIndexLinkHT, CheckIdentifier.Memory);
data.AddLine(GetInvalid(MemoryIndexLinkHT, L_XHT, 4));
if (mem.HandlingTrainerMemoryVariable != 0)
data.AddLine(Severity.Invalid, LMemoryIndexArgHT, CheckIdentifier.Memory);
data.AddLine(GetInvalid(MemoryIndexArgHT, L_XHT, 0));
if (mem.HandlingTrainerMemoryIntensity != 1)
data.AddLine(Severity.Invalid, LMemoryIndexIntensityHT1, CheckIdentifier.Memory);
if (mem.HandlingTrainerMemoryFeeling > 10)
data.AddLine(Severity.Invalid, LMemoryIndexFeelHT09, CheckIdentifier.Memory);
data.AddLine(GetInvalid(MemoryIndexIntensityHT1, L_XHT, 1));
if (mem.HandlingTrainerMemoryFeeling >= 10)
data.AddLine(GetInvalid(MemoryIndexFeelHTLEQ9, L_XHT));
}
}

View File

@@ -67,7 +67,7 @@ public static void SetMaxContestStats(this PKM pk, IEncounterTemplate enc, Evolu
_ => h.HasVisitedBDSP ? CorrelateSheen : None, // BD/SP Contests
};
public static int CalculateMaximumSheen(IContestStatsReadOnly s, Nature nature, IContestStatsReadOnly initial, bool pokeBlock3)
public static byte CalculateMaximumSheen(IContestStatsReadOnly s, Nature nature, IContestStatsReadOnly initial, bool pokeBlock3)
{
if (s.IsAnyContestStatMax())
return MaxContestStat;
@@ -86,10 +86,10 @@ public static int CalculateMaximumSheen(IContestStatsReadOnly s, Nature nature,
return 59;
// Can get trash poffins by burning and spilling on purpose.
return Math.Min(MaxContestStat, avg * HighestFeelPoffin8b);
return (byte)Math.Min(MaxContestStat, avg * HighestFeelPoffin8b);
}
public static int CalculateMinimumSheen(IContestStatsReadOnly s, IContestStatsReadOnly initial, INature pk, ContestStatGrantingSheen method) => method switch
public static byte CalculateMinimumSheen(IContestStatsReadOnly s, IContestStatsReadOnly initial, INature pk, ContestStatGrantingSheen method) => method switch
{
ContestStatGrantingSheen.Gen8b => CalculateMinimumSheen8b(s, pk.Nature, initial),
ContestStatGrantingSheen.Gen3 => CalculateMinimumSheen3(s, pk.Nature, initial),
@@ -98,7 +98,7 @@ public static int CalculateMaximumSheen(IContestStatsReadOnly s, Nature nature,
};
// BD/SP has a slightly better stat:sheen ratio than Gen4; prefer if it has visited.
public static int CalculateMinimumSheen8b(IContestStatsReadOnly s, Nature nature, IContestStatsReadOnly initial)
public static byte CalculateMinimumSheen8b(IContestStatsReadOnly s, Nature nature, IContestStatsReadOnly initial)
{
if (s.IsContestEqual(initial))
return initial.ContestSheen;
@@ -111,10 +111,10 @@ public static int CalculateMinimumSheen8b(IContestStatsReadOnly s, Nature nature
avg = Math.Min(rawAvg, avg); // be generous
avg = (BestSheenStat8b * avg) / MaxContestStat;
return Math.Clamp(avg, LowestFeelPoffin8b, BestSheenStat8b);
return (byte)Math.Clamp(avg, LowestFeelPoffin8b, BestSheenStat8b);
}
public static int CalculateMinimumSheen3(IContestStatsReadOnly s, Nature nature, IContestStatsReadOnly initial)
public static byte CalculateMinimumSheen3(IContestStatsReadOnly s, Nature nature, IContestStatsReadOnly initial)
{
if (s.IsContestEqual(initial))
return initial.ContestSheen;
@@ -127,10 +127,10 @@ public static int CalculateMinimumSheen3(IContestStatsReadOnly s, Nature nature,
avg = Math.Min(rawAvg, avg); // be generous
avg = (BestSheenStat3 * avg) / MaxContestStat;
return Math.Clamp(avg, LowestFeelBlock3, BestSheenStat3);
return (byte)Math.Clamp(avg, LowestFeelBlock3, BestSheenStat3);
}
public static int CalculateMinimumSheen4(IContestStatsReadOnly s, Nature nature, IContestStatsReadOnly initial)
public static byte CalculateMinimumSheen4(IContestStatsReadOnly s, Nature nature, IContestStatsReadOnly initial)
{
if (s.IsContestEqual(initial))
return initial.ContestSheen;
@@ -142,10 +142,10 @@ public static int CalculateMinimumSheen4(IContestStatsReadOnly s, Nature nature,
var avg = Math.Max(1, (byte)nature % 6 == 0 ? rawAvg : GetAverageFeel(s, nature, initial));
avg = Math.Min(rawAvg, avg); // be generous
return Math.Clamp(avg, LowestFeelPoffin4, MaxContestStat);
return (byte)Math.Clamp(avg, LowestFeelPoffin4, MaxContestStat);
}
private static int CalculateMaximumSheen3(IContestStatsReadOnly s, Nature nature, IContestStatsReadOnly initial)
private static byte CalculateMaximumSheen3(IContestStatsReadOnly s, Nature nature, IContestStatsReadOnly initial)
{
// By using Enigma and Lansat and a 25 +1/-1, can get a +9/+19s at minimum RPM
// By using Strib, Chilan, Niniku, or Topo, can get a black +2/2/2 & 83 block (6:83) at minimum RPM.
@@ -164,7 +164,7 @@ private static int CalculateMaximumSheen3(IContestStatsReadOnly s, Nature nature
// Prefer the bad-black-block correlation if more than 3 stats have gains >= 2.
var permit = has3 ? (sum * 83 / 6) : (sum * 19 / 9);
return Math.Clamp(permit, LowestFeelBlock3, MaxContestStat);
return (byte)Math.Clamp(permit, LowestFeelBlock3, MaxContestStat);
}
private static int GetAverageFeel(IContestStatsReadOnly s, Nature nature, IContestStatsReadOnly initial)

View File

@@ -1,6 +1,5 @@
using System;
using System.Text;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.CheckIdentifier;
namespace PKHeX.Core;
@@ -23,36 +22,36 @@ public override void Verify(LegalityAnalysis data)
// No egg have contest stats from the encounter.
if (pk is IContestStatsReadOnly s && s.HasContestStats())
data.AddLine(GetInvalid(LEggContest, Egg));
data.AddLine(GetInvalid(Egg, EggContest));
// Cannot transfer eggs across contexts (must be hatched).
var e = data.EncounterOriginal;
if (e.Context != pk.Context)
data.AddLine(GetInvalid(LTransferEggVersion, Egg));
data.AddLine(GetInvalid(Egg, TransferEggVersion));
switch (pk)
{
// Side Game: No Eggs
case SK2 or CK3 or XK3 or BK4 or RK4 when e.Context == pk.Context:
data.AddLine(GetInvalid(LTransferEggVersion, Egg));
data.AddLine(GetInvalid(Egg, TransferEggVersion));
break;
// All Eggs are Japanese and flagged specially for localized string
case PK3 when pk.Language != 1:
data.AddLine(GetInvalid(string.Format(LOTLanguage, LanguageID.Japanese, (LanguageID)pk.Language), Egg));
data.AddLine(GetInvalid(Egg, OTLanguageShouldBe_0, (byte)LanguageID.Japanese));
break;
// Cannot obtain Shiny Leaf or Pokeathlon Stats as Egg
case PK4 pk4:
if (pk4.ShinyLeaf != 0)
data.AddLine(GetInvalid(LEggShinyLeaf, Egg));
data.AddLine(GetInvalid(Egg, EggShinyLeaf));
if (pk4.PokeathlonStat != 0)
data.AddLine(GetInvalid(LEggPokeathlon, Egg));
data.AddLine(GetInvalid(Egg, EggPokeathlon));
break;
}
if (pk is IHomeTrack { HasTracker: true })
data.AddLine(GetInvalid(LTransferTrackerShouldBeZero));
data.AddLine(GetInvalid(TransferTrackerShouldBeZero));
}
switch (pk)
@@ -88,7 +87,7 @@ public override void Verify(LegalityAnalysis data)
if (s64.TryGetSeed(pk, out var seed))
data.Info.PIDIV = new PIDIV(PIDType.Xoroshiro, seed);
if (enc is IMasteryInitialMoveShop8 m && !m.IsForcedMasteryCorrect(pk))
data.AddLine(GetInvalid(LEncMasteryInitial));
data.AddLine(GetInvalid(EncMasteryInitial));
}
VerifyMiscFatefulEncounter(data);
@@ -111,7 +110,7 @@ private void VerifyCorrelation8b(LegalityAnalysis data, IStaticCorrelation8b s8b
};
if (!valid)
data.AddLine(GetInvalid(LPIDTypeMismatch));
data.AddLine(GetInvalid(PIDTypeMismatch));
}
private void VerifyCorrelation8(LegalityAnalysis data, IOverworldCorrelation8 z, PKM pk)
@@ -132,7 +131,7 @@ private void VerifyCorrelation8(LegalityAnalysis data, IOverworldCorrelation8 z,
};
if (!valid)
data.AddLine(GetInvalid(LPIDTypeMismatch));
data.AddLine(GetInvalid(PIDTypeMismatch));
}
private void VerifyServerDate2000(LegalityAnalysis data, PKM pk, IEncounterable enc, IEncounterServerDate date)
@@ -144,18 +143,18 @@ private void VerifyServerDate2000(LegalityAnalysis data, PKM pk, IEncounterable
if (enc is WB8 { IsDateLockJapanese: true } or WA8 { IsDateLockJapanese: true })
{
if (actualDay < new DateOnly(2022, 5, 20) && pk.Language != (int)LanguageID.Japanese)
data.AddLine(GetInvalid(LDateOutsideDistributionWindow));
data.AddLine(GetInvalid(DateOutsideDistributionWindow));
}
var result = date.IsWithinDistributionWindow(actualDay);
if (result == EncounterServerDateCheck.Invalid)
data.AddLine(GetInvalid(LDateOutsideDistributionWindow));
data.AddLine(GetInvalid(DateOutsideDistributionWindow));
}
private void VerifyStats7(LegalityAnalysis data, PK7 pk7)
{
if (pk7.ResortEventStatus >= ResortEventState.MAX)
data.AddLine(GetInvalid(LTransferBad));
data.AddLine(GetInvalid(TransferBad));
}
private void VerifyMiscScaleValues(LegalityAnalysis data, PKM pk, IEncounterTemplate enc)
@@ -168,20 +167,20 @@ private void VerifyMiscScaleValues(LegalityAnalysis data, PKM pk, IEncounterTemp
{
// Gen1-7 can have 0-0 if kept in PLA before HOME 3.0
if (s2 is { HeightScalar: 0, WeightScalar: 0 } && !data.Info.EvoChainsAllGens.HasVisitedPLA && enc is not IPogoSlot)
data.AddLine(Get(LStatInvalidHeightWeight, Severity.Invalid, Encounter));
data.AddLine(Get(Encounter, Severity.Invalid, StatInvalidHeightWeight));
}
else if (CheckHeightWeightOdds(data.EncounterMatch))
{
if (s2 is { HeightScalar: 0, WeightScalar: 0 })
{
if (ParseSettings.Settings.HOMETransfer.ZeroHeightWeight != Severity.Valid)
data.AddLine(Get(LStatInvalidHeightWeight, ParseSettings.Settings.HOMETransfer.ZeroHeightWeight, Encounter));
data.AddLine(Get(Encounter, ParseSettings.Settings.HOMETransfer.ZeroHeightWeight, StatInvalidHeightWeight));
}
}
// Check for Scale
if (pk is IScaledSize3 s3 && IsHeightScaleMatchRequired(pk) && s2.HeightScalar != s3.Scale)
data.AddLine(GetInvalid(LStatIncorrectHeightValue));
data.AddLine(GetInvalid(StatIncorrectHeightValue));
}
private void VerifyIsMovesetAllowed(LegalityAnalysis data, SK2 sk2)
@@ -220,18 +219,18 @@ private static void VerifyStats5(LegalityAnalysis data, PK5 pk5)
// Cannot participate in Pokestar Studios as Egg
if (pk5.IsEgg && pk5.PokeStarFame != 0)
data.AddLine(GetInvalid(LEggShinyPokeStar, Egg));
data.AddLine(GetInvalid(Egg, EggShinyPokeStar));
// Ensure NSparkle is only present on N's encounters.
if (enc is EncounterStatic5N)
{
if (!pk5.NSparkle)
data.AddLine(GetInvalid(LG5SparkleRequired, Fateful));
data.AddLine(GetInvalid(Fateful, G5SparkleRequired));
}
else
{
if (pk5.NSparkle)
data.AddLine(GetInvalid(LG5SparkleInvalid, Fateful));
data.AddLine(GetInvalid(Fateful, G5SparkleInvalid));
}
}
@@ -248,35 +247,35 @@ private void VerifyStats9(LegalityAnalysis data, PK9 pk9)
VerifyTechRecordSV(data, pk9);
if (!pk9.IsBattleVersionValid(data.Info.EvoChainsAllGens))
data.AddLine(GetInvalid(LStatBattleVersionInvalid));
data.AddLine(GetInvalid(StatBattleVersionInvalid));
if (!IsObedienceLevelValid(pk9, pk9.ObedienceLevel, pk9.MetLevel))
data.AddLine(GetInvalid(LTransferObedienceLevel));
data.AddLine(GetInvalid(TransferObedienceLevel));
if (pk9.IsEgg)
{
if (pk9.TeraTypeOverride != (MoveType)TeraTypeUtil.OverrideNone)
data.AddLine(GetInvalid(LTeraTypeIncorrect));
data.AddLine(GetInvalid(TeraTypeIncorrect));
}
else if (pk9.Species == (int)Species.Terapagos)
{
if (!TeraTypeUtil.IsValidTerapagos((byte)pk9.TeraTypeOverride))
data.AddLine(GetInvalid(LTeraTypeIncorrect));
data.AddLine(GetInvalid(TeraTypeIncorrect));
}
else if (pk9.Species == (int)Species.Ogerpon)
{
if (!TeraTypeUtil.IsValidOgerpon((byte)pk9.TeraTypeOverride, pk9.Form))
data.AddLine(GetInvalid(LTeraTypeIncorrect));
data.AddLine(GetInvalid(TeraTypeIncorrect));
}
else
{
if (!TeraTypeUtil.IsValid((byte)pk9.TeraTypeOriginal))
data.AddLine(GetInvalid(LTeraTypeIncorrect));
data.AddLine(GetInvalid(TeraTypeIncorrect));
}
var enc = data.EncounterOriginal;
if (enc is EncounterEgg9 g)
{
if (!Tera9RNG.IsMatchTeraTypePersonalEgg(g.Species, g.Form, (byte)pk9.TeraTypeOriginal))
data.AddLine(GetInvalid(LTeraTypeMismatch));
data.AddLine(GetInvalid(TeraTypeMismatch));
}
else if (enc is ITeraRaid9)
{
@@ -286,15 +285,15 @@ private void VerifyStats9(LegalityAnalysis data, PK9 pk9)
else if (enc is not { Context: EntityContext.Gen9 } || pk9 is { GO_HOME: true })
{
if (pk9.TeraTypeOverride == (MoveType)TeraTypeUtil.OverrideNone)
data.AddLine(GetInvalid(LTeraTypeIncorrect));
data.AddLine(GetInvalid(TeraTypeIncorrect));
else if (GetTeraImportMatch(data.Info.EvoChainsAllGens.Gen9, pk9.TeraTypeOriginal, enc) == -1)
data.AddLine(GetInvalid(LTeraTypeIncorrect));
data.AddLine(GetInvalid(TeraTypeIncorrect));
}
else if (enc is EncounterStatic9 { StarterBoxLegend: true })
{
// Ride legends cannot be traded or transferred.
if (pk9.CurrentHandler != 0 || pk9.Tracker != 0 || !pk9.IsUntraded)
data.AddLine(GetInvalid(LTransferBad));
data.AddLine(GetInvalid(TransferBad));
}
if (!Locations9.IsAccessiblePreDLC(pk9.MetLocation))
@@ -309,7 +308,7 @@ private void VerifyStats9(LegalityAnalysis data, PK9 pk9)
// Safari and Sport are not obtainable in the base game.
// For the learnset restricted cases, we need to check if the ball is available too.
if (((BallUseLegality.WildPokeballs9PreDLC2 >> pk9.Ball) & 1) != 1)
data.AddLine(GetInvalid(LBallUnavailable));
data.AddLine(GetInvalid(BallUnavailable));
}
}
@@ -372,11 +371,11 @@ private void VerifyMiscPokerus(LegalityAnalysis data)
var days = pk.PokerusDays;
bool strainValid = Pokerus.IsStrainValid(pk, data.Info.EncounterMatch, strain, days);
if (!strainValid)
data.AddLine(GetInvalid(string.Format(LPokerusStrainUnobtainable_0, strain)));
data.AddLine(GetInvalid(PokerusStrainUnobtainable_0, (ushort)strain));
bool daysValid = Pokerus.IsDurationValid(strain, days, out var max);
if (!daysValid)
data.AddLine(GetInvalid(string.Format(LPokerusDaysTooHigh_0, max)));
data.AddLine(GetInvalid(PokerusDaysLEQ_0, (ushort)max));
}
public void VerifyMiscG1(LegalityAnalysis data)
@@ -397,7 +396,7 @@ public void VerifyMiscG1(LegalityAnalysis data)
_ => time is 1 or 2 or 3,
};
if (!valid)
data.AddLine(new CheckResult(Severity.Invalid, Encounter, LMetDetailTimeOfDay));
data.AddLine(GetInvalid(Encounter, MetDetailTimeOfDay));
}
return;
}
@@ -414,16 +413,16 @@ private void VerifyMiscG1Types(LegalityAnalysis data, PK1 pk1)
// Can have any type combination of any species by using Conversion.
if (!PersonalTable1.TypeIDExists(pk1.Type1))
{
data.AddLine(GetInvalid(LG1TypePorygonFail1));
data.AddLine(GetInvalid(G1TypePorygonFail1));
}
if (!PersonalTable1.TypeIDExists(pk1.Type2))
{
data.AddLine(GetInvalid(LG1TypePorygonFail2));
data.AddLine(GetInvalid(G1TypePorygonFail2));
}
else // Both types exist, ensure a Gen1 species has this combination
{
var matchSpecies = PersonalTable.RB.IsValidTypeCombination(pk1);
var result = matchSpecies != -1 ? GetValid(LG1TypeMatchPorygon) : GetInvalid(LG1TypePorygonFail);
var result = matchSpecies != -1 ? GetValid(G1TypeMatchPorygon) : GetInvalid(G1TypePorygonFail);
data.AddLine(result);
}
}
@@ -434,8 +433,8 @@ private void VerifyMiscG1Types(LegalityAnalysis data, PK1 pk1)
if (!match2 && ParseSettings.AllowGBStadium2)
match2 = (species is (int)Species.Magnemite or (int)Species.Magneton) && pk1.Type2 == 9; // Steel Magnemite via Stadium2
var first = match1 ? GetValid(LG1TypeMatch1) : GetInvalid(LG1Type1Fail);
var second = match2 ? GetValid(LG1TypeMatch2) : GetInvalid(LG1Type2Fail);
var first = match1 ? GetValid(G1TypeMatch1) : GetInvalid(G1Type1Fail);
var second = match2 ? GetValid(G1TypeMatch2) : GetInvalid(G1Type2Fail);
data.AddLine(first);
data.AddLine(second);
}
@@ -454,7 +453,7 @@ private CheckResult GetWasTradeback(LegalityAnalysis data, PK1 pk1, TimeCapsuleE
{
var rate = pk1.CatchRate;
if (PK1.IsCatchRateHeldItem(rate))
return GetValid(LG1CatchRateMatchTradeback);
return GetValid(G1CatchRateMatchTradeback);
return GetWasNotTradeback(data, pk1, eval);
}
@@ -462,20 +461,20 @@ private CheckResult GetWasNotTradeback(LegalityAnalysis data, PK1 pk1, TimeCapsu
{
var rate = pk1.CatchRate;
if (MoveInfo.IsAnyFromGeneration(2, data.Info.Moves))
return GetInvalid(LG1CatchRateItem);
return GetInvalid(G1CatchRateItem);
var e = data.EncounterMatch;
if (e is EncounterGift1 { Version: GameVersion.Stadium } or EncounterTrade1)
return GetValid(LG1CatchRateMatchPrevious); // Encounters detected by the catch rate, cant be invalid if match this encounters
return GetValid(G1CatchRateMatchPrevious); // Encounters detected by the catch rate, cant be invalid if match this encounters
ushort species = pk1.Species;
if (GBRestrictions.IsSpeciesNotAvailableCatchRate((byte)species) && rate == PersonalTable.RB[species].CatchRate)
{
if (species != (int)Species.Dragonite || rate != 45 || !(e.Version == GameVersion.BU || e.Version.Contains(GameVersion.YW)))
return GetInvalid(LG1CatchRateEvo);
return GetInvalid(G1CatchRateEvo);
}
if (!GBRestrictions.RateMatchesEncounter(e.Species, e.Version, rate))
return GetInvalid(eval == TimeCapsuleEvaluation.Transferred12 ? LG1CatchRateChain : LG1CatchRateNone);
return GetValid(LG1CatchRateMatchPrevious);
return GetInvalid(eval == TimeCapsuleEvaluation.Transferred12 ? G1CatchRateChain : G1CatchRateNone);
return GetValid(G1CatchRateMatchPrevious);
}
private static void VerifyMiscFatefulEncounter(LegalityAnalysis data)
@@ -509,7 +508,7 @@ private static void VerifyMiscFatefulEncounter(LegalityAnalysis data)
return;
}
if (pk.FatefulEncounter)
data.AddLine(GetInvalid(LFatefulInvalid, Fateful));
data.AddLine(GetInvalid(Fateful, FatefulInvalid));
}
private static void VerifyMiscEggCommon(LegalityAnalysis data)
@@ -518,30 +517,17 @@ private static void VerifyMiscEggCommon(LegalityAnalysis data)
var enc = data.EncounterMatch;
if (!EggStateLegality.GetIsEggHatchCyclesValid(pk, enc))
data.AddLine(GetInvalid(LEggHatchCycles, Egg));
data.AddLine(GetInvalid(Egg, EggHatchCycles));
if (pk.Format >= 6 && enc is IEncounterEgg && !MovesMatchRelearn(pk))
{
const int moveCount = 4;
var sb = new StringBuilder(64);
for (int i = 0; i < moveCount; i++)
{
var move = pk.GetRelearnMove(i);
var name = ParseSettings.GetMoveName(move);
sb.Append(name);
if (i != moveCount - 1)
sb.Append(", ");
}
var msg = string.Format(LMoveFExpect_0, sb);
data.AddLine(GetInvalid(msg, Egg));
}
data.AddLine(GetInvalid(Egg, MovesShouldMatchRelearnMoves));
if (pk is ITechRecord record)
{
if (record.GetMoveRecordFlagAny())
data.AddLine(GetInvalid(LEggRelearnFlags, Egg));
data.AddLine(GetInvalid(Egg, EggRelearnFlags));
if (pk.StatNature != pk.Nature)
data.AddLine(GetInvalid(LEggNature, Egg));
data.AddLine(GetInvalid(Egg, EggNature));
}
}
@@ -569,14 +555,14 @@ private static void VerifyFatefulMysteryGift(LegalityAnalysis data, MysteryGift
{
var locToCheck = pk.IsEgg ? pk.MetLocation : pk.EggLocation;
if (locToCheck is not (Locations.LinkTrade5 or Locations.LinkTrade5NPC))
data.AddLine(GetInvalid(LPIDTypeMismatch, PID));
data.AddLine(GetInvalid(PID, PIDTypeMismatch));
}
}
bool shouldHave = g.FatefulEncounter;
var result = pk.FatefulEncounter == shouldHave
? GetValid(LFatefulMystery, Fateful)
: GetInvalid(LFatefulMysteryMissing, Fateful);
? GetValid(Fateful, FatefulMystery)
: GetInvalid(Fateful, FatefulMysteryMissing);
data.AddLine(result);
}
@@ -585,21 +571,22 @@ private static void VerifyReceivability(LegalityAnalysis data, MysteryGift g)
var pk = data.Entity;
switch (g)
{
case PCD pcd when !pcd.CanBeReceivedByVersion(pk.Version) && pcd.Gift.PK.Version == 0:
case WC6 wc6 when !wc6.CanBeReceivedByVersion(pk.Version) && !pk.WasTradedEgg:
case WC7 wc7 when !wc7.CanBeReceivedByVersion(pk.Version) && !pk.WasTradedEgg:
case WC8 wc8 when !wc8.CanBeReceivedByVersion(pk.Version):
case WB8 wb8 when !wb8.CanBeReceivedByVersion(pk.Version, pk):
case WA8 wa8 when !wa8.CanBeReceivedByVersion(pk.Version, pk):
data.AddLine(GetInvalid(LEncGiftVersionNotDistributed, GameOrigin));
data.AddLine(GetInvalid(GameOrigin, EncGiftVersionNotDistributed));
return;
case PGF pgf when pgf.RestrictLanguage != 0 && pk.Language != pgf.RestrictLanguage:
data.AddLine(GetInvalid(string.Format(LOTLanguage, pgf.RestrictLanguage, pk.Language), CheckIdentifier.Language));
data.AddLine(GetInvalid(CheckIdentifier.Language, EncGiftLanguageNotDistributed_0, (ushort)pgf.RestrictLanguage));
return;
case WC6 wc6 when wc6.RestrictLanguage != 0 && pk.Language != wc6.RestrictLanguage:
data.AddLine(GetInvalid(string.Format(LOTLanguage, wc6.RestrictLanguage, pk.Language), CheckIdentifier.Language));
data.AddLine(GetInvalid(CheckIdentifier.Language, EncGiftLanguageNotDistributed_0, (ushort)wc6.RestrictLanguage));
return;
case WC7 wc7 when wc7.RestrictLanguage != 0 && pk.Language != wc7.RestrictLanguage:
data.AddLine(GetInvalid(string.Format(LOTLanguage, wc7.RestrictLanguage, pk.Language), CheckIdentifier.Language));
data.AddLine(GetInvalid(CheckIdentifier.Language, EncGiftLanguageNotDistributed_0, (ushort)wc7.RestrictLanguage));
return;
}
}
@@ -608,15 +595,15 @@ private static void VerifyGift3Shiny(LegalityAnalysis data, EncounterGift3 g3)
{
// check for shiny locked gifts
if (!g3.Shiny.IsValid(data.Entity))
data.AddLine(GetInvalid(LEncGiftShinyMismatch, Fateful));
data.AddLine(GetInvalid(Fateful, EncGiftShinyMismatch));
}
private static void VerifyFatefulIngameActive(LegalityAnalysis data)
{
var pk = data.Entity;
var result = pk.FatefulEncounter
? GetValid(LFateful, Fateful)
: GetInvalid(LFatefulMissing, Fateful);
? GetValid(Fateful, Valid)
: GetInvalid(Fateful, FatefulMissing);
data.AddLine(result);
}
@@ -635,7 +622,7 @@ public void VerifyVersionEvolution(LegalityAnalysis data)
bool Sun() => ((uint)pk.Version & 1) == 0;
bool Moon() => ((uint)pk.Version & 1) == 1;
if (pk.IsUntraded)
data.AddLine(GetInvalid(LEvoTradeRequired, Evolution));
data.AddLine(GetInvalid(Evolution, EvoTradeRequired));
break;
}
}
@@ -645,21 +632,21 @@ private static void VerifyFullness(LegalityAnalysis data, PKM pk, IFullnessEnjoy
if (pk.IsEgg)
{
if (fe.Fullness != 0)
data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter));
data.AddLine(GetInvalid(Encounter, MemoryStatFullness_0, 0));
if (fe.Enjoyment != 0)
data.AddLine(GetInvalid(string.Format(LMemoryStatEnjoyment, "0"), Encounter));
data.AddLine(GetInvalid(Encounter, MemoryStatEnjoyment_0, 0));
return;
}
if (pk.Format >= 8)
{
if (fe.Fullness > 245) // Exiting camp is -10, so a 255=>245 is max.
data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "<=245"), Encounter));
data.AddLine(GetInvalid(Encounter, MemoryStatFullnessLEQ_0, 245));
else if (fe.Fullness is not 0 && pk is not PK8) // BD/SP and PLA do not set this field, even via HOME.
data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter));
data.AddLine(GetInvalid(Encounter, MemoryStatFullness_0, 0));
if (fe.Enjoyment != 0)
data.AddLine(GetInvalid(string.Format(LMemoryStatEnjoyment, "0"), Encounter));
data.AddLine(GetInvalid(Encounter, MemoryStatEnjoyment_0, 0));
return;
}
@@ -673,7 +660,7 @@ private static void VerifyFullness(LegalityAnalysis data, PKM pk, IFullnessEnjoy
return; // evolved
if (IsUnfeedable(pk.Species))
data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter));
data.AddLine(GetInvalid(Encounter, MemoryStatFullness_0, 0));
}
public static bool IsUnfeedable(ushort species) => species is
@@ -688,16 +675,17 @@ private static void VerifyFullness(LegalityAnalysis data, PKM pk, IFullnessEnjoy
private static void VerifyStats7b(LegalityAnalysis data, PB7 pb7)
{
VerifyAbsoluteSizes(data, pb7);
var calc = pb7.CalcCP;
if (pb7.Stat_CP != pb7.CalcCP && !IsStarterLGPE(pb7))
data.AddLine(GetInvalid(LStatIncorrectCP, Encounter));
data.AddLine(GetInvalid(Encounter, StatIncorrectCP_0, (uint)calc));
if (pb7.ReceivedTime is null)
data.AddLine(GetInvalid(LDateTimeClockInvalid, Misc));
data.AddLine(GetInvalid(Misc, DateTimeClockInvalid));
// HOME moving in and out will retain received date. ensure it matches if no HT data present.
// Go Park captures will have different dates, as the GO met date is retained as Met Date.
if (pb7.ReceivedDate is not { } date || !EncounterDate.IsValidDateSwitch(date) || (pb7.IsUntraded && data.EncounterOriginal is not EncounterSlot7GO && date != pb7.MetDate))
data.AddLine(GetInvalid(LDateOutsideConsoleWindow, Misc));
data.AddLine(GetInvalid(Misc, DateOutsideConsoleWindow));
}
private static void VerifyAbsoluteSizes<T>(LegalityAnalysis data, T obj) where T : IScaledSizeValue
@@ -715,10 +703,13 @@ private static void VerifyStats7b(LegalityAnalysis data, PB7 pb7)
{
// Unlike PLA, there is no way to force it to recalculate in-game.
// The only encounter this applies to is Meltan, which cannot reach PLA for recalculation.
if (obj.HeightAbsolute != enc.GetHomeHeightAbsolute())
data.AddLine(GetInvalid(LStatIncorrectHeight, Encounter));
if (obj.WeightAbsolute != enc.GetHomeWeightAbsolute())
data.AddLine(GetInvalid(LStatIncorrectWeight, Encounter));
var expectHeight = enc.GetHomeHeightAbsolute();
if (obj.HeightAbsolute != expectHeight)
data.AddLine(GetInvalid(Encounter, StatIncorrectHeight, BitConverter.SingleToUInt32Bits(expectHeight)));
var expectWeight = enc.GetHomeWeightAbsolute();
if (obj.WeightAbsolute != expectWeight)
data.AddLine(GetInvalid(Encounter, StatIncorrectWeight, BitConverter.SingleToUInt32Bits(expectWeight)));
}
private static void VerifyFixedSizeMidAlpha(LegalityAnalysis data, PA8 pk)
@@ -742,10 +733,13 @@ private static void VerifyFixedSizeMidAlpha(LegalityAnalysis data, PA8 pk)
private static void VerifyCalculatedSizes<T>(LegalityAnalysis data, T obj) where T : IScaledSizeValue
{
if (obj.HeightAbsolute != obj.CalcHeightAbsolute)
data.AddLine(GetInvalid(LStatIncorrectHeight, Encounter));
var expectHeight = obj.CalcHeightAbsolute;
if (obj.HeightAbsolute != expectHeight)
data.AddLine(GetInvalid(Encounter, StatIncorrectHeight, BitConverter.SingleToUInt32Bits(expectHeight)));
var expectWeight = obj.CalcWeightAbsolute;
if (obj.WeightAbsolute != obj.CalcWeightAbsolute)
data.AddLine(GetInvalid(LStatIncorrectWeight, Encounter));
data.AddLine(GetInvalid(Encounter, StatIncorrectWeight, BitConverter.SingleToUInt32Bits(expectWeight)));
}
// ReSharper restore CompareOfFloatsByEqualityOperator
@@ -762,31 +756,31 @@ private void VerifyStats8(LegalityAnalysis data, PK8 pk8)
if (pk8.IsEgg)
{
if (social != 0)
data.AddLine(GetInvalid(LMemorySocialZero, Encounter));
data.AddLine(GetInvalid(Encounter, MemorySocialZero));
}
else if (social > byte.MaxValue)
{
data.AddLine(GetInvalid(string.Format(LMemorySocialTooHigh_0, byte.MaxValue), Encounter));
data.AddLine(GetInvalid(Encounter, MemoryStatSocialLEQ_0, 0));
}
VerifyStatNature(data, pk8);
if (!pk8.IsBattleVersionValid(data.Info.EvoChainsAllGens))
data.AddLine(GetInvalid(LStatBattleVersionInvalid));
data.AddLine(GetInvalid(StatBattleVersionInvalid));
var enc = data.EncounterMatch;
bool originGMax = enc is IGigantamaxReadOnly {CanGigantamax: true};
if (originGMax != pk8.CanGigantamax)
{
bool ok = !pk8.IsEgg && Gigantamax.CanToggle(pk8.Species, pk8.Form, enc.Species, enc.Form);
var chk = ok ? GetValid(LStatGigantamaxValid) : GetInvalid(LStatGigantamaxInvalid);
var chk = ok ? GetValid(StatGigantamaxValid) : GetInvalid(StatGigantamaxInvalid);
data.AddLine(chk);
}
if (pk8.DynamaxLevel != 0)
{
if (!pk8.CanHaveDynamaxLevel(pk8) || pk8.DynamaxLevel > 10)
data.AddLine(GetInvalid(LStatDynamaxInvalid));
data.AddLine(GetInvalid(StatDynamaxInvalid));
}
VerifyTechRecordSWSH(data, pk8);
@@ -799,21 +793,21 @@ private void VerifyStats8a(LegalityAnalysis data, PA8 pa8)
var social = pa8.Sociability;
if (social != 0)
data.AddLine(GetInvalid(LMemorySocialZero, Encounter));
data.AddLine(GetInvalid(Encounter, MemorySocialZero));
VerifyStatNature(data, pa8);
if (!pa8.IsBattleVersionValid(data.Info.EvoChainsAllGens))
data.AddLine(GetInvalid(LStatBattleVersionInvalid));
data.AddLine(GetInvalid(StatBattleVersionInvalid));
if (pa8.CanGigantamax)
data.AddLine(GetInvalid(LStatGigantamaxInvalid));
data.AddLine(GetInvalid(StatGigantamaxInvalid));
if (pa8.DynamaxLevel != 0)
data.AddLine(GetInvalid(LStatDynamaxInvalid));
data.AddLine(GetInvalid(StatDynamaxInvalid));
if (pa8.GetMoveRecordFlagAny() && !pa8.IsEgg) // already checked for eggs
data.AddLine(GetInvalid(LEggRelearnFlags));
data.AddLine(GetInvalid(EggRelearnFlags));
VerifyTechRecordSWSH(data, pa8);
}
@@ -822,28 +816,28 @@ private void VerifyStats8b(LegalityAnalysis data, PB8 pb8)
{
var social = pb8.Sociability;
if (social != 0)
data.AddLine(GetInvalid(LMemorySocialZero, Encounter));
data.AddLine(GetInvalid(Encounter, MemorySocialZero));
if (pb8.IsDprIllegal)
data.AddLine(GetInvalid(LTransferFlagIllegal));
data.AddLine(GetInvalid(TransferFlagIllegal));
if (pb8.Species is (int)Species.Spinda or (int)Species.Nincada && !pb8.BDSP)
data.AddLine(GetInvalid(LTransferNotPossible));
data.AddLine(GetInvalid(TransferNotPossible));
if (pb8.Species is (int)Species.Spinda && pb8.Tracker != 0)
data.AddLine(GetInvalid(LTransferTrackerShouldBeZero));
data.AddLine(GetInvalid(TransferTrackerShouldBeZero));
VerifyStatNature(data, pb8);
if (!pb8.IsBattleVersionValid(data.Info.EvoChainsAllGens))
data.AddLine(GetInvalid(LStatBattleVersionInvalid));
data.AddLine(GetInvalid(StatBattleVersionInvalid));
if (pb8.CanGigantamax)
data.AddLine(GetInvalid(LStatGigantamaxInvalid));
data.AddLine(GetInvalid(StatGigantamaxInvalid));
if (pb8.DynamaxLevel != 0)
data.AddLine(GetInvalid(LStatDynamaxInvalid));
data.AddLine(GetInvalid(StatDynamaxInvalid));
if (pb8.GetMoveRecordFlagAny() && !pb8.IsEgg) // already checked for eggs
data.AddLine(GetInvalid(LEggRelearnFlags));
data.AddLine(GetInvalid(EggRelearnFlags));
VerifyTechRecordSWSH(data, pb8);
}
@@ -867,10 +861,10 @@ private static bool CheckHeightWeightOdds(IEncounterTemplate enc)
if (statNature == pk.Nature)
return;
if (!statNature.IsMint())
data.AddLine(GetInvalid(LStatNatureInvalid));
data.AddLine(GetInvalid(StatNatureInvalid));
}
private static string GetMoveName<T>(T pk, int index) where T : PKM, ITechRecord => ParseSettings.MoveStrings[pk.Permit.RecordPermitIndexes[index]];
private static ushort GetMoveIndex<T>(T pk, int index) where T : PKM, ITechRecord => pk.Permit.RecordPermitIndexes[index];
private void VerifyTechRecordSWSH<T>(LegalityAnalysis data, T pk) where T : PKM, ITechRecord
{
@@ -882,7 +876,7 @@ private static bool CheckHeightWeightOdds(IEncounterTemplate enc)
{
if (!pk.GetMoveRecordFlag(i))
continue;
data.AddLine(GetInvalid(string.Format(LMoveSourceTR, GetMoveName(pk, i))));
data.AddLine(GetInvalid(MoveTechRecordFlagMissing_0, GetMoveIndex(pk, i)));
}
}
else
@@ -908,7 +902,7 @@ private static bool CheckHeightWeightOdds(IEncounterTemplate enc)
continue;
}
data.AddLine(GetInvalid(string.Format(LMoveSourceTR, GetMoveName(pk, i))));
data.AddLine(GetInvalid(MoveTechRecordFlagMissing_0, GetMoveIndex(pk, i)));
}
}
}
@@ -929,7 +923,7 @@ private void VerifyTechRecordSV(LegalityAnalysis data, PK9 pk)
{
if (!pk.GetMoveRecordFlag(i))
continue;
data.AddLine(GetInvalid(string.Format(LMoveSourceTR, GetMoveName(pk, i))));
data.AddLine(GetInvalid(MoveTechRecordFlagMissing_0, GetMoveIndex(pk, i)));
}
}
else
@@ -978,7 +972,7 @@ private void VerifyTechRecordSV(LegalityAnalysis data, PK9 pk)
if (preEvoHas)
continue;
}
data.AddLine(GetInvalid(string.Format(LMoveSourceTR, GetMoveName(pk, i))));
data.AddLine(GetInvalid(MoveTechRecordFlagMissing_0, GetMoveIndex(pk, i)));
}
}
}

View File

@@ -1,5 +1,5 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -22,9 +22,9 @@ private void VerifyEgg(LegalityAnalysis data)
{
var pk = data.Entity;
if (pk.Move1_PPUps != 0 || pk.Move2_PPUps != 0 || pk.Move3_PPUps != 0 || pk.Move4_PPUps != 0)
data.AddLine(GetInvalid(LEggPPUp, CheckIdentifier.Egg));
data.AddLine(GetInvalid(CheckIdentifier.Egg, EggPPUp));
if (!IsZeroMovePP(pk))
data.AddLine(GetInvalid(LEggPP, CheckIdentifier.Egg));
data.AddLine(GetInvalid(CheckIdentifier.Egg, EggPP));
}
private static bool IsZeroMovePP(PKM pk)
@@ -58,7 +58,7 @@ private void VerifyEntity(LegalityAnalysis data)
for (int i = 0; i < ups.Length; i++)
{
if (ups[i] != 0)
data.AddLine(GetInvalid(string.Format(LMovePPUpsTooHigh_0, i + 1)));
data.AddLine(GetInvalid(MovePPUpsTooHigh_0, (ushort)(i + 1)));
}
}
else // Check specific move indexes
@@ -66,7 +66,7 @@ private void VerifyEntity(LegalityAnalysis data)
for (int i = 0; i < ups.Length; i++)
{
if (!Legal.IsPPUpAvailable(moves[i]) && ups[i] != 0)
data.AddLine(GetInvalid(string.Format(LMovePPUpsTooHigh_0, i + 1)));
data.AddLine(GetInvalid(MovePPUpsTooHigh_0, (ushort)(i + 1)));
}
}
@@ -74,9 +74,9 @@ private void VerifyEntity(LegalityAnalysis data)
{
var expect = pk.GetMovePP(moves[i], ups[i]);
if (pp[i] > expect)
data.AddLine(GetInvalid(string.Format(LMovePPTooHigh_0, i + 1)));
data.AddLine(GetInvalid(MovePPTooHigh_0, (ushort)(i + 1)));
else if (expectHeal && pp[i] != expect)
data.AddLine(GetInvalid(string.Format(LMovePPExpectHealed_0, i + 1)));
data.AddLine(GetInvalid(MovePPExpectHealed_0, (ushort)(i + 1)));
}
}

View File

@@ -1,5 +1,5 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.LanguageID;
namespace PKHeX.Core;
@@ -20,13 +20,13 @@ public override void Verify(LegalityAnalysis data)
int len = pk.LoadString(pk.NicknameTrash, nickname);
if (len == 0)
{
data.AddLine(GetInvalid(LNickLengthShort));
data.AddLine(GetInvalid(NickLengthShort));
return;
}
nickname = nickname[..len];
if (nickname.Contains('\uffff') && pk is { Format: 4 })
{
data.AddLine(GetInvalid(LNickInvalidChar));
data.AddLine(GetInvalid(NickInvalidChar, ushort.MaxValue));
return;
}
@@ -45,7 +45,7 @@ public override void Verify(LegalityAnalysis data)
if (pk.VC)
VerifyG1NicknameWithinBounds(data, nickname);
else if (IsMysteryGiftNoNickname(enc))
data.AddLine(Get(LEncGiftNicknamed, ParseSettings.Settings.Nickname.NicknamedMysteryGift(enc.Context)));
data.AddLine(Get(ParseSettings.Settings.Nickname.NicknamedMysteryGift(enc.Context), EncGiftNicknamed));
}
if (enc is IFixedTrainer t)
@@ -68,10 +68,10 @@ public override void Verify(LegalityAnalysis data)
if (ParseSettings.Settings.WordFilter.IsEnabled(pk.Format) && pk.IsNicknamed)
{
var mostRecentNicknameContext = pk.Format >= 8 ? pk.Context : enc.Context;
if (WordFilter.IsFiltered(nickname, out var badPattern, pk.Context, mostRecentNicknameContext))
data.AddLine(GetInvalid($"Word Filter: {badPattern}"));
if (WordFilter.IsFiltered(nickname, pk.Context, mostRecentNicknameContext, out var type, out var badPattern))
data.AddLine(GetInvalid(CheckIdentifier.Nickname, WordFilterFlaggedPattern_01, (ushort)type, (ushort)badPattern));
if (TrainerNameVerifier.ContainsTooManyNumbers(nickname, enc.Generation))
data.AddLine(GetInvalid("Word Filter: Too many numbers."));
data.AddLine(GetInvalid(CheckIdentifier.Nickname, WordFilterTooManyNumbers_0, (ushort)TrainerNameVerifier.GetMaxNumberCount(enc.Generation)));
}
}
@@ -106,7 +106,7 @@ private void VerifyFixedNicknameEncounter(LegalityAnalysis data, ILangNicknamedT
return;
if (pk.IsNicknamed)
data.AddLine(Get(LEncGiftNicknamed, Severity.Invalid));
data.AddLine(Get(Severity.Invalid, EncGiftNicknamed));
return;
}
@@ -122,14 +122,14 @@ private void VerifyFixedNicknameEncounter(LegalityAnalysis data, ILangNicknamedT
}
// Should have a nickname present.
data.AddLine(GetInvalid(LNickMatchLanguageFail));
data.AddLine(GetInvalid(NickMatchLanguageFail));
return;
}
// Encounter has a nickname, and PKM should have it.
bool matches = nickname.SequenceEqual(encounterNickname);
var severity = !matches || !pk.IsNicknamed ? Severity.Invalid : Severity.Valid;
data.AddLine(Get(LEncGiftNicknamed, severity));
data.AddLine(Get(severity, EncGiftNicknamed));
}
private void VerifyHomeGiftNickname(LegalityAnalysis data, IEncounterTemplate enc, ILangNick pk, ReadOnlySpan<char> nickname)
@@ -141,14 +141,14 @@ private void VerifyHomeGiftNickname(LegalityAnalysis data, IEncounterTemplate en
// Can't nickname everything.
if (enc.Species == (int) Species.Melmetal)
{
data.AddLine(GetInvalid(LEncGiftNicknamed));
data.AddLine(GetInvalid(EncGiftNicknamed));
return;
}
// Ensure the nickname does not match species name
var orig = SpeciesName.GetSpeciesNameGeneration(enc.Species, pk.Language, enc.Generation);
if (nickname.SequenceEqual(orig))
data.AddLine(GetInvalid(LNickMatchLanguageFlag));
data.AddLine(GetInvalid(NickMatchLanguageFlag));
}
private bool VerifyUnNicknamedEncounter(LegalityAnalysis data, PKM pk, ReadOnlySpan<char> nickname, IEncounterTemplate enc)
@@ -161,29 +161,29 @@ private bool VerifyUnNicknamedEncounter(LegalityAnalysis data, PKM pk, ReadOnlyS
// Setting the nickname to the same as the species name does not set the Nickname flag (equals unmodified, no flag)
if (!SpeciesName.IsNicknamed(pk.Species, nickname, pk.Language, pk.Format))
{
data.AddLine(Get(LNickMatchLanguageFlag, Severity.Invalid));
data.AddLine(Get(Severity.Invalid, NickMatchLanguageFlag));
return true;
}
}
if (SpeciesName.TryGetSpeciesAnyLanguage(nickname, out var species, pk.Format))
{
var msg = species == pk.Species ? LNickMatchLanguageFlag : LNickMatchNoOthersFail;
data.AddLine(Get(msg, ParseSettings.Settings.Nickname.NicknamedAnotherSpecies));
var msg = species == pk.Species ? NickMatchLanguageFlag : NickMatchNoOthersFail;
data.AddLine(Get(ParseSettings.Settings.Nickname.NicknamedAnotherSpecies, msg));
return true;
}
if (pk.Format <= 7 && StringConverter.HasEastAsianScriptCharacters(nickname) && pk is not PB7) // East Asian Scripts
{
data.AddLine(GetInvalid(LNickInvalidChar));
data.AddLine(GetInvalid(NickInvalidChar));
return true;
}
if (nickname.Length > Legal.GetMaxLengthNickname(enc.Generation, (LanguageID)pk.Language))
{
int length = GetForeignNicknameLength(pk, enc, enc.Generation);
var severe = (length != 0 && nickname.Length <= length) ? Severity.Fishy : Severity.Invalid;
data.AddLine(Get(LNickLengthLong, severe));
data.AddLine(Get(severe, NickLengthLong));
return true;
}
data.AddLine(GetValid(LNickMatchNoOthers));
data.AddLine(GetValid(NickMatchNoOthers));
}
else
{
@@ -198,13 +198,13 @@ private void VerifyUnNicknamed(LegalityAnalysis data, PKM pk, ReadOnlySpan<char>
if (pk.Format < 3)
{
// pk1/pk2 IsNicknamed getter checks for match, logic should only reach here if matches.
data.AddLine(GetValid(LNickMatchLanguage));
data.AddLine(GetValid(NickMatchLanguage));
}
else
{
var enc = data.EncounterOriginal;
bool valid = IsNicknameValid(pk, enc, nickname);
var result = valid ? GetValid(LNickMatchLanguage) : GetInvalid(LNickMatchLanguageFail);
var result = valid ? GetValid(NickMatchLanguage) : GetInvalid(NickMatchLanguageFail);
data.AddLine(result);
}
}
@@ -318,18 +318,18 @@ private static void VerifyNicknameEgg(LegalityAnalysis data)
bool flagState = EggStateLegality.IsNicknameFlagSet(enc, pk);
if (pk.IsNicknamed != flagState)
data.AddLine(GetInvalid(flagState ? LNickFlagEggYes : LNickFlagEggNo, CheckIdentifier.Egg));
data.AddLine(GetInvalid(CheckIdentifier.Egg, flagState ? NickFlagEggYes : NickFlagEggNo));
Span<char> nickname = stackalloc char[pk.TrashCharCountNickname];
int len = pk.LoadString(pk.NicknameTrash, nickname);
nickname = nickname[..len];
if (pk.Format == 2 && !SpeciesName.IsNicknamedAnyLanguage(0, nickname, 2))
data.AddLine(GetValid(LNickMatchLanguageEgg, CheckIdentifier.Egg));
data.AddLine(GetValid(CheckIdentifier.Egg, NickMatchLanguageEgg));
else if (!nickname.SequenceEqual(SpeciesName.GetEggName(pk.Language, enc.Generation)))
data.AddLine(GetInvalid(LNickMatchLanguageEggFail, CheckIdentifier.Egg));
data.AddLine(GetInvalid(CheckIdentifier.Egg, NickMatchLanguageEggFail));
else
data.AddLine(GetValid(LNickMatchLanguageEgg, CheckIdentifier.Egg));
data.AddLine(GetValid(CheckIdentifier.Egg, NickMatchLanguageEgg));
}
private static void VerifyNicknameTrade(LegalityAnalysis data, IEncounterTemplate t)
@@ -351,21 +351,21 @@ private void VerifyG1NicknameWithinBounds(LegalityAnalysis data, ReadOnlySpan<ch
if (StringConverter1.GetIsEnglish(str))
{
if (str.Length > 10)
data.AddLine(GetInvalid(LNickLengthLong));
data.AddLine(GetInvalid(NickLengthLong, 10));
}
else if (StringConverter1.GetIsJapanese(str))
{
if (str.Length > 5)
data.AddLine(GetInvalid(LNickLengthLong));
data.AddLine(GetInvalid(NickLengthLong, 5));
}
else if (pk.Korean && StringConverter2KOR.GetIsKorean(str))
{
if (str.Length > 5)
data.AddLine(GetInvalid(LNickLengthLong));
data.AddLine(GetInvalid(NickLengthLong, 5));
}
else
{
data.AddLine(GetInvalid(LG1CharNick));
data.AddLine(GetInvalid(G1CharNick));
}
}
@@ -373,7 +373,7 @@ private static void VerifyTrade4(LegalityAnalysis data, EncounterTrade4PID t)
{
var pk = data.Entity;
if (t.IsIncorrectEnglish(pk))
data.AddLine(GetInvalid(string.Format(LOTLanguage, Japanese, English), CheckIdentifier.Language));
data.AddLine(GetInvalid(CheckIdentifier.Language, OTLanguageShouldBeLeq_0, (byte)Japanese));
var lang = t.DetectOriginalLanguage(pk);
VerifyTrade(data, t, lang);
}
@@ -401,7 +401,7 @@ private static void VerifyTrade8b(LegalityAnalysis data, EncounterTrade8b t)
lang = t.DetectMeisterMagikarpLanguage(nickname, trainer, lang);
if (lang == -1) // err
data.AddLine(GetInvalid(string.Format(LOTLanguage, $"{Japanese}/{German}", $"{(LanguageID)pk.Language}"), CheckIdentifier.Language));
data.AddLine(GetInvalid(CheckIdentifier.Language, OTLanguageShouldBe_0or1, (byte)Japanese, (byte)German));
}
if (t.IsPijako(pk))
@@ -418,7 +418,7 @@ private static void VerifyEncounterTrade5(LegalityAnalysis data, EncounterTrade5
var pk = data.Entity;
var lang = pk.Language;
if (pk.Format == 5 && lang == (int)Japanese)
data.AddLine(GetInvalid(string.Format(LOTLanguage, 0, Japanese), CheckIdentifier.Language));
data.AddLine(GetInvalid(CheckIdentifier.Language, OTLanguageShouldBe_0, (byte)0));
lang = Math.Max(lang, 1);
VerifyTrade(data, t, lang);
@@ -434,7 +434,7 @@ private static CheckResult CheckTradeOTOnly(LegalityAnalysis data, IFixedTrainer
{
var pk = data.Entity;
if (pk.IsNicknamed && (pk.Format < 8 || pk.FatefulEncounter))
return GetInvalid(LEncTradeChangedNickname, CheckIdentifier.Nickname);
return GetInvalid(CheckIdentifier.Nickname, EncTradeChangedNickname);
int lang = pk.Language;
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
@@ -442,8 +442,8 @@ private static CheckResult CheckTradeOTOnly(LegalityAnalysis data, IFixedTrainer
trainer = trainer[..len];
if (!t.IsTrainerMatch(pk, trainer, lang))
return GetInvalid(LEncTradeIndexBad, CheckIdentifier.Trainer);
return GetValid(LEncTradeUnchanged, CheckIdentifier.Nickname);
return GetInvalid(CheckIdentifier.Trainer, EncTradeIndexBad);
return GetValid(CheckIdentifier.Nickname, EncTradeUnchanged);
}
private static void VerifyTrade(LegalityAnalysis data, IEncounterTemplate t, int language)
@@ -458,8 +458,8 @@ private static void VerifyNickname(LegalityAnalysis data, IFixedNickname fn, int
{
var pk = data.Entity;
var result = fn.IsNicknameMatch(pk, pk.Nickname, language)
? GetValid(LEncTradeUnchanged, CheckIdentifier.Nickname)
: Get(LEncTradeChangedNickname, ParseSettings.Settings.Nickname.NicknamedTrade(data.EncounterOriginal.Context), CheckIdentifier.Nickname);
? GetValid(CheckIdentifier.Nickname, EncTradeUnchanged)
: Get(CheckIdentifier.Nickname, ParseSettings.Settings.Nickname.NicknamedTrade(data.EncounterOriginal.Context), EncTradeChangedNickname);
data.AddLine(result);
}
@@ -471,6 +471,6 @@ private static void VerifyTrainerName(LegalityAnalysis data, IFixedTrainer ft, i
trainer = trainer[..len];
if (!ft.IsTrainerMatch(pk, trainer, language))
data.AddLine(GetInvalid(LEncTradeChangedOT, CheckIdentifier.Trainer));
data.AddLine(GetInvalid(CheckIdentifier.Trainer, EncTradeChangedOT));
}
}

View File

@@ -1,5 +1,5 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -23,9 +23,9 @@ public override void Verify(LegalityAnalysis data)
VerifyEC100(data, enc.Species);
if (pk.PID == 0)
data.AddLine(Get(LPIDZero, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, PIDZero));
if (!pk.Nature.IsFixed()) // out of range
data.AddLine(GetInvalid(LPIDNatureMismatch));
data.AddLine(GetInvalid(PIDNatureMismatch));
if (data.Info.EncounterMatch is IEncounterEgg egg)
VerifyEggPID(data, pk, egg);
@@ -39,7 +39,7 @@ private static void VerifyEggPID(LegalityAnalysis data, PKM pk, IEncounterEgg eg
// Gen5 eggs use rand(0xFFFFFFFF), which never yields 0xFFFFFFFF (max 0xFFFFFFFE).
// Masuda Method does the same as the original PID roll. PID is never re-rolled a different way.
if (pk.EncryptionConstant == uint.MaxValue)
data.AddLine(Get(LPIDEncryptZero, Severity.Invalid, CheckIdentifier.EC));
data.AddLine(Get(CheckIdentifier.EC, Severity.Invalid, PIDEncryptZero));
}
else if (egg is EncounterEgg4)
{
@@ -50,7 +50,7 @@ private static void VerifyEggPID(LegalityAnalysis data, PKM pk, IEncounterEgg eg
// None of the un-rolled states share the same shiny-xor as PID=0, you can re-roll into an all-zero PID.
// Flag it as fishy, because more often than not, it is hacked rather than a legitimately obtained egg.
if (pk.EncryptionConstant == 0)
data.AddLine(Get(LPIDEncryptZero, Severity.Fishy, CheckIdentifier.EC));
data.AddLine(Get(CheckIdentifier.EC, Severity.Fishy, PIDEncryptZero));
if (Breeding.IsGenderSpeciesDetermination(egg.Species))
VerifyEggGender8000(data, pk);
@@ -58,7 +58,7 @@ private static void VerifyEggPID(LegalityAnalysis data, PKM pk, IEncounterEgg eg
else if (egg is EncounterEgg3)
{
if (!Daycare3.IsValidProcPID(pk.EncryptionConstant, egg.Version))
data.AddLine(Get(LPIDEncryptZero, Severity.Invalid, CheckIdentifier.EC));
data.AddLine(Get(CheckIdentifier.EC, Severity.Invalid, PIDEncryptZero));
if (Breeding.IsGenderSpeciesDetermination(egg.Species))
VerifyEggGender8000(data, pk);
@@ -72,7 +72,7 @@ private static void VerifyEggGender8000(LegalityAnalysis data, PKM pk)
if (Breeding.IsValidSpeciesBit34(pk.EncryptionConstant, gender))
return; // 50/50 chance!
if (gender == 1 || IsEggBitRequiredMale34(data.Info.Moves))
data.AddLine(GetInvalid(LPIDGenderMismatch, CheckIdentifier.EC));
data.AddLine(GetInvalid(CheckIdentifier.EC, PIDGenderMismatch));
}
private void VerifyShiny(LegalityAnalysis data)
@@ -81,7 +81,7 @@ private void VerifyShiny(LegalityAnalysis data)
var enc = data.EncounterMatch;
if (!enc.Shiny.IsValid(pk))
data.AddLine(GetInvalid(LEncStaticPIDShiny, CheckIdentifier.Shiny));
data.AddLine(GetInvalid(CheckIdentifier.Shiny, EncStaticPIDShiny));
switch (enc)
{
@@ -91,7 +91,7 @@ private void VerifyShiny(LegalityAnalysis data)
VerifyG5PID_IDCorrelation(data);
break;
case EncounterSlot5 {IsHiddenGrotto: true} when pk.IsShiny:
data.AddLine(GetInvalid(LG5PIDShinyGrotto, CheckIdentifier.Shiny));
data.AddLine(GetInvalid(CheckIdentifier.Shiny, G5PIDShinyGrotto));
break;
case EncounterSlot5 {IsHiddenGrotto: false}:
VerifyG5PID_IDCorrelation(data);
@@ -99,16 +99,16 @@ private void VerifyShiny(LegalityAnalysis data)
case PCD d: // fixed PID
if (d.IsFixedPID() && pk.EncryptionConstant != d.Gift.PK.PID)
data.AddLine(GetInvalid(LEncGiftPIDMismatch, CheckIdentifier.Shiny));
data.AddLine(GetInvalid(CheckIdentifier.Shiny, EncGiftPIDMismatch));
break;
case WC7 { IsAshGreninja: true } when pk.IsShiny:
data.AddLine(GetInvalid(LEncGiftShinyMismatch, CheckIdentifier.Shiny));
data.AddLine(GetInvalid(CheckIdentifier.Shiny, EncGiftShinyMismatch));
break;
// Underground Raids are originally anti-shiny on encounter.
// When selecting a prize at the end, the game rolls and force-shiny is applied to be XOR=1.
case EncounterStatic8U u when !u.IsShinyXorValid(pk.ShinyXor):
data.AddLine(GetInvalid(LEncStaticPIDShiny, CheckIdentifier.Shiny));
data.AddLine(GetInvalid(CheckIdentifier.Shiny, EncStaticPIDShiny));
break;
}
}
@@ -118,7 +118,7 @@ private void VerifyG5PID_IDCorrelation(LegalityAnalysis data)
var pk = data.Entity;
var result = MonochromeRNG.GetBitXor(pk, pk.EncryptionConstant);
if (result != 0)
data.AddLine(GetInvalid(LPIDTypeMismatch));
data.AddLine(GetInvalid(PIDTypeMismatch));
}
private static void VerifyECPIDWurmple(LegalityAnalysis data)
@@ -129,14 +129,12 @@ private static void VerifyECPIDWurmple(LegalityAnalysis data)
{
// Indicate what it will evolve into
var evoVal = WurmpleUtil.GetWurmpleEvoVal(pk.EncryptionConstant);
var evolvesTo = evoVal == WurmpleEvolution.Silcoon ? (int)Species.Beautifly : (int)Species.Dustox;
var species = ParseSettings.SpeciesStrings[evolvesTo];
var msg = string.Format(L_XWurmpleEvo_0, species);
data.AddLine(GetValid(msg, CheckIdentifier.EC));
var evolvesTo = evoVal == WurmpleEvolution.Silcoon ? (ushort)Species.Beautifly : (ushort)Species.Dustox;
data.AddLine(GetValid(CheckIdentifier.EC, HintEvolvesToSpecies_0, evolvesTo));
}
else if (!WurmpleUtil.IsWurmpleEvoValid(pk))
{
data.AddLine(GetInvalid(LPIDEncryptWurmple, CheckIdentifier.EC));
data.AddLine(GetInvalid(CheckIdentifier.EC, PIDEncryptWurmple));
}
}
@@ -147,14 +145,9 @@ private static void VerifyEC100(LegalityAnalysis data, ushort encSpecies)
return; // Evolved, don't need to calculate the final evolution for the verbose report.
// Indicate the evolution for the user.
const EntityContext mostRecent = Latest.Context; // latest ec100 form here
uint evoVal = pk.EncryptionConstant % 100;
bool rare = evoVal == 0;
var (species, form) = EvolutionRestrictions.GetEvolvedSpeciesFormEC100(encSpecies, rare);
var str = GameInfo.Strings;
var forms = FormConverter.GetFormList(species, str.Types, str.forms, GameInfo.GenderSymbolASCII, mostRecent);
var msg = string.Format(L_XRareFormEvo_0_1, forms[form], rare);
data.AddLine(GetValid(msg, CheckIdentifier.EC));
var rare = EvolutionRestrictions.IsEvolvedSpeciesFormRare(pk.EncryptionConstant);
var hint = rare ? (byte)1 : (byte)0;
data.AddLine(GetValid(CheckIdentifier.EC, HintEvolvesToRareForm_0, hint));
}
private static void VerifyEC(LegalityAnalysis data)
@@ -166,7 +159,7 @@ private static void VerifyEC(LegalityAnalysis data)
{
if (Info.EncounterMatch is WC8 {IsHOMEGift: true})
return; // HOME Gifts
data.AddLine(Get(LPIDEncryptZero, Severity.Fishy, CheckIdentifier.EC));
data.AddLine(Get(CheckIdentifier.EC, Severity.Fishy, PIDEncryptZero));
}
// Gen3-5 => Gen6 have PID==EC with an edge case exception.
@@ -186,7 +179,7 @@ private static void VerifyEC(LegalityAnalysis data)
if (enc is WB8 {IsEquivalentFixedECPID: true})
return;
data.AddLine(GetInvalid(LPIDEqualsEC, CheckIdentifier.EC)); // better to flag than 1:2^32 odds since RNG is not feasible to yield match
data.AddLine(GetInvalid(CheckIdentifier.EC, PIDEqualsEC)); // better to flag than 1:2^32 odds since RNG is not feasible to yield match
return;
}
@@ -195,7 +188,7 @@ private static void VerifyEC(LegalityAnalysis data)
{
var xor = pk.ShinyXor;
if (xor >> 3 == 1) // 8 <= x <= 15
data.AddLine(Get(LTransferPIDECXor, Severity.Fishy, CheckIdentifier.EC));
data.AddLine(Get(CheckIdentifier.EC, Severity.Fishy, TransferEncryptGen6Xor));
}
}
@@ -238,8 +231,8 @@ private static void VerifyTransferEC(LegalityAnalysis data)
if (pk.PID == expect)
return;
var msg = bitFlipProc ? LTransferPIDECBitFlip : LTransferPIDECEquals;
data.AddLine(GetInvalid(msg, CheckIdentifier.EC));
var msg = bitFlipProc ? TransferEncryptGen6BitFlip : TransferEncryptGen6Equals;
data.AddLine(GetInvalid(CheckIdentifier.EC, msg));
}
private static bool IsEggBitRequiredMale34(ReadOnlySpan<MoveResult> moves)

View File

@@ -7,15 +7,15 @@ namespace PKHeX.Core;
/// <summary>
/// String Translation Utility
/// </summary>
public static class RibbonStrings
public class RibbonStrings
{
private static readonly Dictionary<string, string> RibbonNames = [];
private readonly Dictionary<string, string> RibbonNames = [];
/// <summary>
/// Resets the Ribbon Dictionary to use the supplied set of Ribbon (Property) Names.
/// </summary>
/// <param name="lines">Array of strings that are tab separated with Property Name, \t, and Display Name.</param>
public static void ResetDictionary(ReadOnlySpan<string> lines)
public RibbonStrings(ReadOnlySpan<string> lines)
{
RibbonNames.EnsureCapacity(lines.Length);
@@ -38,11 +38,11 @@ public static void ResetDictionary(ReadOnlySpan<string> lines)
/// <param name="propertyName">Ribbon property name</param>
/// <param name="result">Ribbon localized name</param>
/// <returns>True if exists</returns>
public static bool GetNameSafe(string propertyName, [NotNullWhen(true)] out string? result) => RibbonNames.TryGetValue(propertyName, out result);
public bool GetNameSafe(string propertyName, [NotNullWhen(true)] out string? result) => RibbonNames.TryGetValue(propertyName, out result);
/// <returns>Ribbon display name</returns>
/// <inheritdoc cref="GetNameSafe"/>
public static string GetName(string propertyName)
public string GetName(string propertyName)
{
// Throw an exception with the requested property name as the message, rather than an ambiguous "key not present" message.
// We should ALWAYS have the key present as the input arguments are not user-defined, rather, they are from PKM property names.

View File

@@ -1,6 +1,6 @@
using System;
using System.Text;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -23,6 +23,25 @@ public sealed class RibbonVerifier : Verifier
public const int MaxRibbonCount = (int)RibbonIndex.MAX_COUNT;
public override void Verify(LegalityAnalysis data)
{
Span<RibbonResult> result = stackalloc RibbonResult[MaxRibbonCount];
var count = Parse(result, data);
if (count == 0)
data.AddLine(GetValid(RibbonAllValid));
else // defer hint string creation unless we request the string. We'll re-do work, but this saves hot path allocation where the string is never needed to be humanized.
data.AddLine(GetInvalid(RibbonFInvalid_0, (ushort)count));
}
public static string GetMessage(LegalityAnalysis data, RibbonStrings str, LegalityCheckLocalization localize)
{
// Calling this method assumes that one or more ribbons are invalid or missing.
// The work was already done but forgotten, so we need to parse again.
Span<RibbonResult> result = stackalloc RibbonResult[MaxRibbonCount];
int count = Parse(result, data);
return GetMessage(result[..count], str, localize);
}
private static int Parse(Span<RibbonResult> result, LegalityAnalysis data)
{
// Flag VC (Gen1/2) ribbons using Gen7 origin rules.
var enc = data.EncounterMatch;
@@ -30,16 +49,7 @@ public override void Verify(LegalityAnalysis data)
// Check Unobtainable Ribbons
var args = new RibbonVerifierArguments(pk, enc, data.Info.EvoChainsAllGens);
Span<RibbonResult> result = stackalloc RibbonResult[MaxRibbonCount];
int count = GetRibbonResults(args, result);
if (count == 0)
{
data.AddLine(GetValid(LRibbonAllValid));
return;
}
var msg = GetMessage(result[..count]);
data.AddLine(GetInvalid(msg));
return GetRibbonResults(args, result);
}
/// <summary>
@@ -86,19 +96,19 @@ private static int GetRibbonResults(in RibbonVerifierArguments args, ref RibbonR
return list.Count;
}
private static string GetMessage(ReadOnlySpan<RibbonResult> result)
private static string GetMessage(ReadOnlySpan<RibbonResult> result, RibbonStrings str , LegalityCheckLocalization localize)
{
var total = result.Length;
int missing = GetCountMissing(result);
int invalid = total - missing;
var sb = new StringBuilder(total * 20);
if (missing != 0)
AppendAll(result, sb, LRibbonFMissing_0, true);
AppendAll(result, sb, str, localize.RibbonMissing_0, true);
if (invalid != 0)
{
if (missing != 0) // need to visually separate the message
sb.Append(Environment.NewLine);
AppendAll(result, sb, LRibbonFInvalid_0, false);
AppendAll(result, sb, str, localize.RibbonFInvalid_0, false);
}
return sb.ToString();
}
@@ -116,7 +126,7 @@ private static int GetCountMissing(ReadOnlySpan<RibbonResult> result)
private const string MessageSplitNextRibbon = ", ";
private static void AppendAll(ReadOnlySpan<RibbonResult> result, StringBuilder sb, string startText, bool stateMissing)
private static void AppendAll(ReadOnlySpan<RibbonResult> result, StringBuilder sb, RibbonStrings str, string startText, bool stateMissing)
{
int added = 0;
sb.Append(startText);
@@ -126,7 +136,7 @@ private static void AppendAll(ReadOnlySpan<RibbonResult> result, StringBuilder s
continue;
if (added++ != 0)
sb.Append(MessageSplitNextRibbon);
var localized = RibbonStrings.GetName(x.PropertyName);
var localized = str.GetName(x.PropertyName);
sb.Append(localized);
}
}

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.StorageSlotType;
using static PKHeX.Core.Species;
@@ -18,12 +18,12 @@ public override void Verify(LegalityAnalysis data)
if (pk.IsEgg)
{
if (!IsSourceValidEgg(pk, source))
data.AddLine(GetInvalid(LStoredSourceEgg));
data.AddLine(GetInvalid(StoredSourceEgg));
}
else
{
if (!IsSourceValid(pk, source))
data.AddLine(GetInvalid(string.Format(LStoredSourceInvalid_0, source)));
data.AddLine(GetInvalid(StoredSlotSourceInvalid_0, (ushort)source));
}
}

View File

@@ -1,4 +1,4 @@
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -23,7 +23,7 @@ public override void Verify(LegalityAnalysis data)
var id32 = pk.ID32;
if (id32 is 0 or int.MaxValue)
{
data.AddLine(GetInvalid(LOT_IDInvalid));
data.AddLine(GetInvalid(OT_IDInvalid));
return;
}
}
@@ -45,29 +45,29 @@ public override void Verify(LegalityAnalysis data)
{
// Only TID is used for Gen 1/2 VC
if (pk.SID16 != 0)
data.AddLine(GetInvalid(LOT_SID0Invalid));
data.AddLine(GetInvalid(OT_SID0Invalid));
if (pk.TID16 == 0)
data.AddLine(Get(LOT_TID0, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, OT_TID0));
return;
}
else if (pk.Format <= 2)
{
// Only TID is used for Gen 1/2
if (pk.TID16 == 0)
data.AddLine(Get(LOT_TID0, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, OT_TID0));
return;
}
if (pk is { ID32: 0 })
data.AddLine(Get(LOT_IDs0, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, OT_IDs0));
else if (pk.TID16 == pk.SID16)
data.AddLine(Get(LOT_IDEqual, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, OT_IDEqual));
else if (pk.TID16 == 0)
data.AddLine(Get(LOT_TID0, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, OT_TID0));
else if (pk.SID16 == 0)
data.AddLine(Get(LOT_SID0, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, OT_SID0));
else if (IsOTIDSuspicious(pk.TID16, pk.SID16))
data.AddLine(Get(LOTSuspicious, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, OTSuspicious));
}
public static bool TryGetShinySID(uint pid, ushort tid, GameVersion version, out ushort sid)
@@ -97,13 +97,13 @@ public static bool TryGetShinySID(uint pid, ushort tid, GameVersion version, out
// japanese will stay Invalid
}
if (!MethodCXD.TryGetSeedTrainerID(tr.TID16, tr.SID16, out _))
data.AddLine(Get(LTrainerIDNoSeed, severity, CheckIdentifier.Trainer));
data.AddLine(Get(CheckIdentifier.Trainer, severity, TrainerIDNoSeed));
}
private static void VerifyTrainerID_RS<T>(LegalityAnalysis data, T tr, Severity severity = Severity.Invalid) where T : ITrainerID32ReadOnly
{
if (!MethodH.TryGetSeedTrainerID(tr.TID16, tr.SID16, out _))
data.AddLine(Get(LTrainerIDNoSeed, severity, CheckIdentifier.Trainer));
data.AddLine(Get(CheckIdentifier.Trainer, severity, TrainerIDNoSeed));
}
public static bool IsOTIDSuspicious(ushort tid16, ushort sid16) => (tid16, sid16) switch

View File

@@ -1,5 +1,5 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -27,19 +27,19 @@ public override void Verify(LegalityAnalysis data)
int len = pk.LoadString(pk.OriginalTrainerTrash, trainer);
if (len == 0)
{
data.AddLine(GetInvalid(LOTShort));
data.AddLine(GetInvalid(OTShort));
return;
}
trainer = trainer[..len];
if (trainer.Contains('\uffff') && pk is { Format: 4 })
{
data.AddLine(GetInvalid("Trainer Name: Unknown Character"));
data.AddLine(GetInvalid(CheckIdentifier.Trainer, WordFilterInvalidCharacter_0, 0xFFFF));
return;
}
if (IsOTNameSuspicious(trainer))
{
data.AddLine(Get(LOTSuspicious, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, OTSuspicious));
}
if (pk.VC)
@@ -49,20 +49,20 @@ public override void Verify(LegalityAnalysis data)
else if (trainer.Length > Legal.GetMaxLengthOT(enc.Generation, (LanguageID)pk.Language))
{
if (!IsEdgeCaseLength(pk, enc, trainer))
data.AddLine(Get(LOTLong, Severity.Invalid));
data.AddLine(Get(Severity.Invalid, OTLong));
}
if (ParseSettings.Settings.WordFilter.IsEnabled(pk.Format))
{
if (WordFilter.IsFiltered(trainer, out var badPattern, pk.Context, enc.Context))
data.AddLine(GetInvalid($"Word Filter: {badPattern}"));
if (WordFilter.IsFiltered(trainer, pk.Context, enc.Context, out var type, out var badPattern))
data.AddLine(GetInvalid(CheckIdentifier.Trainer, WordFilterFlaggedPattern_01, (ushort)type, (ushort)badPattern));
if (ContainsTooManyNumbers(trainer, enc.Generation))
data.AddLine(GetInvalid("Word Filter: Too many numbers."));
data.AddLine(GetInvalid(CheckIdentifier.Trainer, WordFilterTooManyNumbers_0, (ushort)GetMaxNumberCount(enc.Generation)));
Span<char> ht = stackalloc char[pk.TrashCharCountTrainer];
int nameLen = pk.LoadString(pk.HandlingTrainerTrash, ht);
if (WordFilter.IsFiltered(ht[..nameLen], out badPattern, pk.Context)) // HT context is always the current context
data.AddLine(GetInvalid($"Word Filter: {badPattern}"));
if (WordFilter.IsFiltered(ht[..nameLen], pk.Context, out type, out badPattern)) // HT context is always the current context
data.AddLine(GetInvalid(CheckIdentifier.Handler, WordFilterFlaggedPattern_01, (ushort)type, (ushort)badPattern));
}
}
@@ -104,7 +104,7 @@ public void VerifyOTGB(LegalityAnalysis data)
// Transferring from RBY->Gen7 won't have OT Gender in PK1, nor will PK1 originated encounters.
// GSC Trades already checked for OT Gender matching.
if (pk is { Format: > 2, VC1: true } || enc is { Generation: 1 } or EncounterGift2 { IsEgg: false })
data.AddLine(GetInvalid(LG1OTGender));
data.AddLine(GetInvalid(G1OTGender));
}
if (enc is IFixedTrainer { IsFixedTrainer: true })
@@ -118,11 +118,11 @@ public void VerifyOTGB(LegalityAnalysis data)
{
if (pk is SK2 {TID16: 0, IsRental: true})
{
data.AddLine(Get(LOTShort, Severity.Fishy));
data.AddLine(Get(Severity.Fishy, OTShort));
}
else
{
data.AddLine(GetInvalid(LOTShort));
data.AddLine(GetInvalid(OTShort));
return;
}
}
@@ -145,23 +145,23 @@ private void VerifyGBOTWithinBounds(LegalityAnalysis data, ReadOnlySpan<char> st
if (pk.Japanese)
{
if (str.Length > 5)
data.AddLine(GetInvalid(LOTLong));
data.AddLine(GetInvalid(OTLong, 5));
if (!StringConverter1.GetIsJapanese(str))
data.AddLine(GetInvalid(LG1CharOT));
data.AddLine(GetInvalid(G1CharOT));
}
else if (pk.Korean)
{
if (str.Length > 5)
data.AddLine(GetInvalid(LOTLong));
data.AddLine(GetInvalid(OTLong, 5));
if (!StringConverter2KOR.GetIsKorean(str))
data.AddLine(GetInvalid(LG1CharOT));
data.AddLine(GetInvalid(G1CharOT));
}
else
{
if (str.Length > 7)
data.AddLine(GetInvalid(LOTLong));
data.AddLine(GetInvalid(OTLong, 7));
if (!StringConverter1.GetIsEnglish(str))
data.AddLine(GetInvalid(LG1CharOT));
data.AddLine(GetInvalid(G1CharOT));
}
}
@@ -179,13 +179,15 @@ public static bool ContainsTooManyNumbers(ReadOnlySpan<char> str, byte originalG
{
if (originalGeneration <= 3)
return false; // no limit from these generations
int max = originalGeneration < 6 ? 4 : 5;
int max = GetMaxNumberCount(originalGeneration);
if (str.Length <= max)
return false;
int count = GetNumberCount(str);
return count > max;
}
public static int GetMaxNumberCount(byte originalGeneration) => originalGeneration < 6 ? 4 : 5;
private static int GetNumberCount(ReadOnlySpan<char> str)
{
int ctr = 0;

View File

@@ -1,5 +1,5 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
@@ -27,7 +27,7 @@ private void VerifyVCOTGender(LegalityAnalysis data)
{
var pk = data.Entity;
if (pk.OriginalTrainerGender == 1 && pk.Version != GameVersion.C)
data.AddLine(GetInvalid(LG2OTGender));
data.AddLine(GetInvalid(G2OTGender));
}
private void VerifyVCNatureEXP(LegalityAnalysis data)
@@ -39,7 +39,7 @@ private void VerifyVCNatureEXP(LegalityAnalysis data)
{
var nature = Experience.GetNatureVC(pk.EXP);
if (nature != pk.Nature)
data.AddLine(GetInvalid(LTransferNature));
data.AddLine(GetInvalid(TransferNature, (uint)nature));
return;
}
if (met <= 2) // Not enough EXP to have every nature -- check for exclusions!
@@ -49,7 +49,7 @@ private void VerifyVCNatureEXP(LegalityAnalysis data)
var nature = pk.Nature;
bool valid = VerifyVCNature(growth, nature);
if (!valid)
data.AddLine(GetInvalid(LTransferNature));
data.AddLine(GetInvalid(TransferNature));
}
}
@@ -68,7 +68,7 @@ private static void VerifyVCShinyXorIfShiny(LegalityAnalysis data)
// (15:65536, ~1:4096) odds on a given shiny transfer!
var xor = data.Entity.ShinyXor;
if (xor is <= 15 and not 0)
data.AddLine(Get(LEncStaticPIDShiny, ParseSettings.Settings.Game.Gen7.Gen7TransferStarPID, CheckIdentifier.PID));
data.AddLine(Get(CheckIdentifier.PID, ParseSettings.Settings.Game.Gen7.Gen7TransferStarPID, EncStaticPIDShiny));
}
private static void VerifyVCGeolocation(LegalityAnalysis data)
@@ -79,7 +79,7 @@ private static void VerifyVCGeolocation(LegalityAnalysis data)
// VC Games were region locked to the Console, meaning not all language games are available.
var within = Locale3DS.IsRegionLockedLanguageValidVC(pk7.ConsoleRegion, pk7.Language);
if (!within)
data.AddLine(GetInvalid(string.Format(LOTLanguage, $"!={(LanguageID)pk7.Language}", ((LanguageID)pk7.Language).ToString()), CheckIdentifier.Language));
data.AddLine(GetInvalid(CheckIdentifier.Language, OTLanguageCannotTransferToConsoleRegion_0, pk7.ConsoleRegion));
}
public void VerifyTransferLegalityG3(LegalityAnalysis data)
@@ -88,12 +88,12 @@ public void VerifyTransferLegalityG3(LegalityAnalysis data)
if (pk.Format == 4) // Pal Park (3->4)
{
if (pk.MetLocation != Locations.Transfer3)
data.AddLine(GetInvalid(LEggLocationPalPark));
data.AddLine(GetInvalid(EggLocationPalPark, Locations.Transfer3));
}
else // Transporter (4->5)
{
if (pk.MetLocation != Locations.Transfer4)
data.AddLine(GetInvalid(LTransferEggLocationTransporter));
data.AddLine(GetInvalid(TransferEggLocationTransporter, Locations.Transfer4));
}
}
@@ -109,14 +109,14 @@ public void VerifyTransferLegalityG4(LegalityAnalysis data)
{
case (int)Species.Celebi:
if (loc is not (Locations.Transfer4_CelebiUnused or Locations.Transfer4_CelebiUsed))
data.AddLine(GetInvalid(LTransferMet));
data.AddLine(GetInvalid(TransferMet));
break;
case (int)Species.Raikou or (int)Species.Entei or (int)Species.Suicune:
if (loc is not (Locations.Transfer4_CrownUnused or Locations.Transfer4_CrownUsed))
data.AddLine(GetInvalid(LTransferMet));
data.AddLine(GetInvalid(TransferMet));
break;
default:
data.AddLine(GetInvalid(LTransferEggLocationTransporter));
data.AddLine(GetInvalid(TransferEggLocationTransporter));
break;
}
}
@@ -137,7 +137,7 @@ public void VerifyTransferLegalityG8(LegalityAnalysis data)
{
if (s.IsTotemNoTransfer || pk.Form != s.GetTotemBaseForm())
{
data.AddLine(GetInvalid(LTransferBad));
data.AddLine(GetInvalid(TransferBad));
return;
}
}
@@ -152,7 +152,7 @@ public void VerifyTransferLegalityG8(LegalityAnalysis data)
_ => PersonalTable.SWSH,
};
if (!pt.IsPresentInGame(pk.Species, pk.Form))
data.AddLine(GetInvalid(LTransferBad));
data.AddLine(GetInvalid(TransferBad));
}
private void VerifyHOMETransfer(LegalityAnalysis data, PKM pk)
@@ -168,7 +168,7 @@ private void VerifyHOMETransfer(LegalityAnalysis data, PKM pk)
if (pk.Context is not (EntityContext.Gen8 or EntityContext.Gen8a or EntityContext.Gen8b))
{
if (s is { HeightScalar: 0, WeightScalar: 0 } && !data.Info.EvoChainsAllGens.HasVisitedPLA)
data.AddLine(GetInvalid(LTransferBad));
data.AddLine(GetInvalid(TransferBad));
}
}
@@ -178,7 +178,7 @@ private void VerifyHOMETracker(LegalityAnalysis data, PKM pk)
// Can't validate the actual values (we aren't the server), so we can only check against zero.
if (pk is IHomeTrack { HasTracker: false })
{
data.AddLine(Get(LTransferTrackerMissing, ParseSettings.Settings.HOMETransfer.HOMETransferTrackerNotPresent));
data.AddLine(Get(ParseSettings.Settings.HOMETransfer.HOMETransferTrackerNotPresent, TransferTrackerMissing));
// To the reader: It seems like the best course of action for setting a tracker is:
// - Transfer a 0-Tracker pk to HOME to get assigned a valid Tracker via the game it originated from.
// - Don't make one up.
@@ -188,11 +188,11 @@ private void VerifyHOMETracker(LegalityAnalysis data, PKM pk)
public void VerifyVCEncounter(PKM pk, IEncounterTemplate original, EncounterTransfer7 transfer, LegalityAnalysis data)
{
if (pk.MetLocation != transfer.Location)
data.AddLine(GetInvalid(LTransferMetLocation));
data.AddLine(GetInvalid(TransferMetLocation));
var expectEgg = pk is PB8 ? Locations.Default8bNone : transfer.EggLocation;
if (pk.EggLocation != expectEgg)
data.AddLine(GetInvalid(LEggLocationNone));
data.AddLine(GetInvalid(EggLocationNone));
// Flag Moves that cannot be transferred
if (original is EncounterStatic2 { DizzyPunchEgg: true}) // Dizzy Punch Gifts
@@ -207,12 +207,12 @@ public void VerifyVCEncounter(PKM pk, IEncounterTemplate original, EncounterTran
var enc = data.EncounterOriginal;
var pi = PersonalTable.USUM[enc.Species];
if (pi.Gender == 31 && pk.IsShiny) // impossible gender-shiny
data.AddLine(GetInvalid(LEncStaticPIDShiny, CheckIdentifier.PID));
data.AddLine(GetInvalid(CheckIdentifier.PID, EncStaticPIDShiny));
}
else if (pk.Species == (int)Species.Unown)
{
if (pk.Form is not (8 or 21) && pk.IsShiny) // impossibly form-shiny (not I or V)
data.AddLine(GetInvalid(LEncStaticPIDShiny, CheckIdentifier.PID));
data.AddLine(GetInvalid(CheckIdentifier.PID, EncStaticPIDShiny));
}
}

View File

@@ -1,5 +1,5 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.StringSource;
namespace PKHeX.Core;
@@ -11,16 +11,6 @@ public sealed class TrashByteVerifier : Verifier
{
protected override CheckIdentifier Identifier => CheckIdentifier.TrashBytes;
private static string Format(StringSource s) => s switch
{
Nickname => L_XNickname,
OriginalTrainer => L_XOT,
HandlingTrainer => L_XHT,
_ => throw new ArgumentOutOfRangeException(nameof(s)),
};
private static string Format(StringSource s, string msg) => string.Format(L_F0_1, Format(s), msg);
public override void Verify(LegalityAnalysis data)
{
var pk = data.Entity;
@@ -38,19 +28,19 @@ public override void Verify(LegalityAnalysis data)
}
}
private void VerifyTrashBytesPalPark(LegalityAnalysis data, PKM pk)
private static void VerifyTrashBytesPalPark(LegalityAnalysis data, PKM pk)
{
if (pk.Japanese)
{
// Trash bytes should be zero.
if (!TrashBytesUTF16.IsTrashEmpty(pk.OriginalTrainerTrash))
data.AddLine(GetInvalid(Format(Nickname, LTrashBytesShouldBeEmpty)));
data.AddLine(GetInvalid(CheckIdentifier.Trainer, TrashBytesShouldBeEmpty));
}
else
{
// Should have trash bytes from the transfer process.
if (TrashBytesUTF16.IsTrashEmpty(pk.OriginalTrainerTrash))
data.AddLine(GetInvalid(Format(Nickname, LTrashBytesExpected)));
data.AddLine(GetInvalid(CheckIdentifier.Trainer, TrashBytesExpected));
}
}
@@ -59,24 +49,24 @@ private void VerifyTrashBytesPCD(LegalityAnalysis data, PKM pk, PCD pcd)
var enc = pcd.Gift.PK;
var ot = enc.OriginalTrainerTrash;
if (!ot.SequenceEqual(pk.OriginalTrainerTrash))
data.AddLine(GetInvalid(Format(OriginalTrainer, LTrashBytesMismatchInitial)));
data.AddLine(GetInvalid(CheckIdentifier.Trainer, TrashBytesMismatchInitial));
if (pcd.Species != pk.Species)
return; // Evolved, trash bytes are rewritten.
var nick = enc.NicknameTrash;
if (!nick.SequenceEqual(pk.NicknameTrash))
data.AddLine(GetInvalid(Format(Nickname, LTrashBytesMismatchInitial)));
data.AddLine(GetInvalid(CheckIdentifier.Nickname, TrashBytesMismatchInitial));
}
private void VerifyTrashBytesHOME(LegalityAnalysis data, PKM pk)
{
if (!TrashBytesUTF16.IsFinalTerminatorPresent(pk.NicknameTrash))
data.AddLine(GetInvalid(Format(Nickname, LTrashBytesMissingTerminator)));
data.AddLine(GetInvalid(CheckIdentifier.Nickname, TrashBytesMissingTerminator));
if (!TrashBytesUTF16.IsFinalTerminatorPresent(pk.OriginalTrainerTrash))
data.AddLine(GetInvalid(Format(OriginalTrainer, LTrashBytesMissingTerminator)));
data.AddLine(GetInvalid(CheckIdentifier.Trainer, TrashBytesMissingTerminator));
if (!TrashBytesUTF16.IsFinalTerminatorPresent(pk.HandlingTrainerTrash))
data.AddLine(GetInvalid(Format(HandlingTrainer, LTrashBytesMissingTerminator)));
data.AddLine(GetInvalid(CheckIdentifier.Handler, TrashBytesMissingTerminator));
if (pk.IsEgg)
{
@@ -143,15 +133,14 @@ private void VerifyTrashSingle(LegalityAnalysis data, ReadOnlySpan<byte> span, S
{
var result = TrashBytesUTF16.IsTrashSingleOrNone(span);
if (result.IsInvalid())
data.AddLine(GetInvalid(Format(s, LTrashBytesShouldBeEmpty)));
data.AddLine(GetInvalid(GetIdentifier(s), TrashBytesShouldBeEmpty));
}
private void VerifyTrashSpecific(LegalityAnalysis data, ReadOnlySpan<byte> span, ReadOnlySpan<char> under, StringSource s,
Severity severity = Severity.Invalid)
private void VerifyTrashSpecific(LegalityAnalysis data, ReadOnlySpan<byte> span, ReadOnlySpan<char> under, StringSource s, Severity severity = Severity.Invalid)
{
var result = TrashBytesUTF16.IsTrashSpecific(span, under);
if (result.IsInvalid())
data.AddLine(Get(Format(s, string.Format(LTrashBytesExpected_0, under.ToString())), severity));
data.AddLine(Get(GetIdentifier(s), severity, TrashBytesExpected));
}
private void VerifyTrashNone(LegalityAnalysis data, ReadOnlySpan<byte> span, StringSource s,
@@ -159,20 +148,28 @@ private void VerifyTrashSingle(LegalityAnalysis data, ReadOnlySpan<byte> span, S
{
var result = TrashBytesUTF16.IsTrashNone(span);
if (result.IsInvalid())
data.AddLine(Get(Format(s, LTrashBytesShouldBeEmpty), severity));
data.AddLine(Get(GetIdentifier(s), severity, TrashBytesShouldBeEmpty));
}
private void VerifyTrashNotEmpty(LegalityAnalysis data, ReadOnlySpan<byte> span, StringSource s)
{
if (!TrashBytesUTF16.IsTrashNotEmpty(span))
data.AddLine(GetInvalid(Format(s, LTrashBytesExpected)));
data.AddLine(GetInvalid(GetIdentifier(s), TrashBytesExpected));
}
private void VerifyTrashEmpty(LegalityAnalysis data, ReadOnlySpan<byte> span, StringSource s)
{
if (!TrashBytesUTF16.IsTrashEmpty(span))
data.AddLine(GetInvalid(Format(s, LTrashBytesShouldBeEmpty)));
data.AddLine(GetInvalid(GetIdentifier(s), TrashBytesShouldBeEmpty));
}
private static CheckIdentifier GetIdentifier(StringSource s) => s switch
{
Nickname => CheckIdentifier.Nickname,
OriginalTrainer => CheckIdentifier.Trainer,
HandlingTrainer => CheckIdentifier.Handler,
_ => throw new ArgumentOutOfRangeException(nameof(s)),
};
}
public enum StringSource : byte { Nickname, OriginalTrainer, HandlingTrainer }

View File

@@ -16,11 +16,19 @@ public abstract class Verifier
/// <param name="data">Analysis data to process</param>
public abstract void Verify(LegalityAnalysis data);
protected CheckResult GetInvalid(string msg) => Get(msg, Severity.Invalid);
protected CheckResult GetValid(string msg) => Get(msg, Severity.Valid);
protected CheckResult Get(string msg, Severity s) => new(s, Identifier, msg);
// Standard methods for creating CheckResults
protected CheckResult GetValid(LegalityCheckResultCode msg) => Get(Severity.Valid, msg);
protected CheckResult GetInvalid(LegalityCheckResultCode msg) => Get(Severity.Invalid, msg);
protected CheckResult Get(Severity s, LegalityCheckResultCode msg) => CheckResult.Get(s, Identifier, msg);
protected static CheckResult GetInvalid(string msg, CheckIdentifier c) => Get(msg, Severity.Invalid, c);
protected static CheckResult GetValid(string msg, CheckIdentifier c) => Get(msg, Severity.Valid, c);
protected static CheckResult Get(string msg, Severity s, CheckIdentifier c) => new(s, c, msg);
protected CheckResult GetValid(LegalityCheckResultCode msg, uint argument) => Get(Severity.Valid, msg, argument);
protected CheckResult GetInvalid(LegalityCheckResultCode msg, uint argument) => Get(Severity.Invalid, msg, argument);
protected CheckResult GetInvalid(LegalityCheckResultCode msg, ushort arg0, ushort arg1) => GetInvalid(Identifier, msg, arg0, arg1);
protected CheckResult Get(Severity s, LegalityCheckResultCode msg, uint argument) => CheckResult.Get(s, Identifier, msg, argument);
protected static CheckResult GetValid(CheckIdentifier c, LegalityCheckResultCode msg, uint argument = 0) => Get(c, Severity.Valid, msg, argument);
protected static CheckResult Get(CheckIdentifier c, Severity s, LegalityCheckResultCode msg, uint argument = 0) => CheckResult.Get(s, c, msg, argument);
protected static CheckResult GetInvalid(CheckIdentifier c, LegalityCheckResultCode msg, uint value = 0) => CheckResult.Get(Severity.Invalid, c, msg, value);
protected static CheckResult GetInvalid(CheckIdentifier c, LegalityCheckResultCode msg, ushort arg0, ushort arg1 = 0) => GetInvalid(c, msg, arg0 | (uint)arg1 << 16);
}

View File

@@ -53,7 +53,7 @@ public PK4 PK
{
_pk = value;
var data = value.Data;
bool zero = Array.TrueForAll(data, static z => z == 0); // all zero
bool zero = !data.ContainsAnyExcept<byte>(0); // all zero
if (!zero)
data = PokeCrypto.EncryptArray45(data);
data.CopyTo(Data[8..]);

View File

@@ -33,7 +33,7 @@ public static BK4 ReadUnshuffle(ReadOnlySpan<byte> data)
return result;
}
public BK4(byte[] data) : base(data)
public BK4(Memory<byte> data) : base(data)
{
Sanity = 0x4000;
ResetPartyStats();
@@ -41,24 +41,24 @@ public BK4(byte[] data) : base(data)
public BK4() : this(new byte[PokeCrypto.SIZE_4STORED]) { }
public override BK4 Clone() => new((byte[])Data.Clone());
public override BK4 Clone() => new(Data.ToArray());
// Structure
public override uint PID { get => ReadUInt32BigEndian(Data.AsSpan(0x00)); set => WriteUInt32BigEndian(Data.AsSpan(0x00), value); }
public override ushort Sanity { get => ReadUInt16BigEndian(Data.AsSpan(0x04)); set => WriteUInt16BigEndian(Data.AsSpan(0x04), value); }
public override ushort Checksum { get => ReadUInt16BigEndian(Data.AsSpan(0x06)); set => WriteUInt16BigEndian(Data.AsSpan(0x06), value); }
public override uint PID { get => ReadUInt32BigEndian(Data); set => WriteUInt32BigEndian(Data, value); }
public override ushort Sanity { get => ReadUInt16BigEndian(Data[0x04..]); set => WriteUInt16BigEndian(Data[0x04..], value); }
public override ushort Checksum { get => ReadUInt16BigEndian(Data[0x06..]); set => WriteUInt16BigEndian(Data[0x06..], value); }
#region Block A
public override ushort Species { get => ReadUInt16BigEndian(Data.AsSpan(0x08)); set => WriteUInt16BigEndian(Data.AsSpan(0x08), value); }
public override int HeldItem { get => ReadUInt16BigEndian(Data.AsSpan(0x0A)); set => WriteUInt16BigEndian(Data.AsSpan(0x0A), (ushort)value); }
public override uint ID32 { get => ReadUInt32BigEndian(Data.AsSpan(0x0C)); set => WriteUInt32BigEndian(Data.AsSpan(0x0C), value); }
public override ushort SID16 { get => ReadUInt16BigEndian(Data.AsSpan(0x0C)); set => WriteUInt16BigEndian(Data.AsSpan(0x0C), value); }
public override ushort TID16 { get => ReadUInt16BigEndian(Data.AsSpan(0x0E)); set => WriteUInt16BigEndian(Data.AsSpan(0x0E), value); }
public override ushort Species { get => ReadUInt16BigEndian(Data[0x08..]); set => WriteUInt16BigEndian(Data[0x08..], value); }
public override int HeldItem { get => ReadUInt16BigEndian(Data[0x0A..]); set => WriteUInt16BigEndian(Data[0x0A..], (ushort)value); }
public override uint ID32 { get => ReadUInt32BigEndian(Data[0x0C..]); set => WriteUInt32BigEndian(Data[0x0C..], value); }
public override ushort SID16 { get => ReadUInt16BigEndian(Data[0x0C..]); set => WriteUInt16BigEndian(Data[0x0C..], value); }
public override ushort TID16 { get => ReadUInt16BigEndian(Data[0x0E..]); set => WriteUInt16BigEndian(Data[0x0E..], value); }
public override uint EXP
{
get => ReadUInt32BigEndian(Data.AsSpan(0x10));
set => WriteUInt32BigEndian(Data.AsSpan(0x10), value);
get => ReadUInt32BigEndian(Data[0x10..]);
set => WriteUInt32BigEndian(Data[0x10..], value);
}
public override byte OriginalTrainerFriendship { get => Data[0x14]; set => Data[0x14] = value; }
@@ -115,16 +115,16 @@ public override uint EXP
public override bool RIB3_6 { get => (RIB3 & (1 << 6)) == 1 << 6; set => RIB3 = (byte)((RIB3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused
public override bool RIB3_7 { get => (RIB3 & (1 << 7)) == 1 << 7; set => RIB3 = (byte)((RIB3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused
public override int RibbonCount => BitOperations.PopCount(ReadUInt32LittleEndian(Data.AsSpan(0x24)) & 0b00001111_11111111__11111111_11111111)
+ BitOperations.PopCount(ReadUInt32LittleEndian(Data.AsSpan(0x3C)))
+ BitOperations.PopCount(ReadUInt32LittleEndian(Data.AsSpan(0x60)) & 0b00000000_00001111__11111111_11111111);
public override int RibbonCount => BitOperations.PopCount(ReadUInt32LittleEndian(Data[0x24..]) & 0b00001111_11111111__11111111_11111111)
+ BitOperations.PopCount(ReadUInt32LittleEndian(Data[0x3C..]))
+ BitOperations.PopCount(ReadUInt32LittleEndian(Data[0x60..]) & 0b00000000_00001111__11111111_11111111);
#endregion
#region Block B
public override ushort Move1 { get => ReadUInt16BigEndian(Data.AsSpan(0x28)); set => WriteUInt16BigEndian(Data.AsSpan(0x28), value); }
public override ushort Move2 { get => ReadUInt16BigEndian(Data.AsSpan(0x2A)); set => WriteUInt16BigEndian(Data.AsSpan(0x2A), value); }
public override ushort Move3 { get => ReadUInt16BigEndian(Data.AsSpan(0x2C)); set => WriteUInt16BigEndian(Data.AsSpan(0x2C), value); }
public override ushort Move4 { get => ReadUInt16BigEndian(Data.AsSpan(0x2E)); set => WriteUInt16BigEndian(Data.AsSpan(0x2E), value); }
public override ushort Move1 { get => ReadUInt16BigEndian(Data[0x28..]); set => WriteUInt16BigEndian(Data[0x28..], value); }
public override ushort Move2 { get => ReadUInt16BigEndian(Data[0x2A..]); set => WriteUInt16BigEndian(Data[0x2A..], value); }
public override ushort Move3 { get => ReadUInt16BigEndian(Data[0x2C..]); set => WriteUInt16BigEndian(Data[0x2C..], value); }
public override ushort Move4 { get => ReadUInt16BigEndian(Data[0x2E..]); set => WriteUInt16BigEndian(Data[0x2E..], value); }
public override int Move1_PP { get => Data[0x30]; set => Data[0x30] = (byte)value; }
public override int Move2_PP { get => Data[0x31]; set => Data[0x31] = (byte)value; }
public override int Move3_PP { get => Data[0x32]; set => Data[0x32] = (byte)value; }
@@ -133,7 +133,7 @@ public override uint EXP
public override int Move2_PPUps { get => Data[0x35]; set => Data[0x35] = (byte)value; }
public override int Move3_PPUps { get => Data[0x36]; set => Data[0x36] = (byte)value; }
public override int Move4_PPUps { get => Data[0x37]; set => Data[0x37] = (byte)value; }
public override uint IV32 { get => ReadUInt32BigEndian(Data.AsSpan(0x38)); set => WriteUInt32BigEndian(Data.AsSpan(0x38), value); }
public override uint IV32 { get => ReadUInt32BigEndian(Data[0x38..]); set => WriteUInt32BigEndian(Data[0x38..], value); }
public override int IV_SPD { get => (int)(IV32 >> 02) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 02)) | ((value > 31 ? 31u : (uint)value) << 02); }
public override int IV_SPA { get => (int)(IV32 >> 07) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 07)) | ((value > 31 ? 31u : (uint)value) << 07); }
public override int IV_SPE { get => (int)(IV32 >> 12) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 12)) | ((value > 31 ? 31u : (uint)value) << 12); }
@@ -188,14 +188,14 @@ public override uint EXP
// 0x42-0x43 Unused
public override ushort EggLocationExtended
{
get => ReadUInt16BigEndian(Data.AsSpan(0x44));
set => WriteUInt16BigEndian(Data.AsSpan(0x44), value);
get => ReadUInt16BigEndian(Data[0x44..]);
set => WriteUInt16BigEndian(Data[0x44..], value);
}
public override ushort MetLocationExtended
{
get => ReadUInt16BigEndian(Data.AsSpan(0x46));
set => WriteUInt16BigEndian(Data.AsSpan(0x46), value);
get => ReadUInt16BigEndian(Data[0x46..]);
set => WriteUInt16BigEndian(Data[0x46..], value);
}
#endregion
@@ -264,13 +264,13 @@ public override string Nickname
public override ushort EggLocationDP
{
get => ReadUInt16BigEndian(Data.AsSpan(0x7E));
set => WriteUInt16BigEndian(Data.AsSpan(0x7E), value);
get => ReadUInt16BigEndian(Data[0x7E..]);
set => WriteUInt16BigEndian(Data[0x7E..], value);
}
public override ushort MetLocationDP
{
get => ReadUInt16BigEndian(Data.AsSpan(0x80));
set => WriteUInt16BigEndian(Data.AsSpan(0x80), value);
get => ReadUInt16BigEndian(Data[0x80..]);
set => WriteUInt16BigEndian(Data[0x80..], value);
}
public override byte PokerusState { get => Data[0x82]; set => Data[0x82] = value; }
@@ -298,7 +298,7 @@ public override ushort MetLocationDP
public override int Characteristic => EntityCharacteristic.GetCharacteristicInvertFields(PID, IV32);
// Methods
protected override ushort CalculateChecksum() => Checksums.Add16BigEndian(Data.AsSpan()[8..PokeCrypto.SIZE_4STORED]);
protected override ushort CalculateChecksum() => Checksums.Add16BigEndian(Data[8..PokeCrypto.SIZE_4STORED]);
protected override byte[] Encrypt()
{

View File

@@ -4,7 +4,7 @@
namespace PKHeX.Core;
/// <summary> Generation 3 <see cref="PKM"/> format, exclusively for Pokémon Colosseum. </summary>
public sealed class CK3(byte[] Data) : G3PKM(Data), IShadowCapture, ISeparateIVs, IGCRegion
public sealed class CK3(Memory<byte> Raw) : G3PKM(Raw), IShadowCapture, ISeparateIVs, IGCRegion
{
public CK3() : this(new byte[PokeCrypto.SIZE_3CSTORED]) { }
@@ -22,31 +22,31 @@ public sealed class CK3(byte[] Data) : G3PKM(Data), IShadowCapture, ISeparateIVs
public override int SIZE_STORED => PokeCrypto.SIZE_3CSTORED;
public override EntityContext Context => EntityContext.Gen3;
public override PersonalInfo3 PersonalInfo => PersonalTable.RS[Species];
public override CK3 Clone() => new((byte[])Data.Clone());
public override CK3 Clone() => new(Data.ToArray());
// Trash Bytes
public override Span<byte> OriginalTrainerTrash => Data.AsSpan(0x18, 22);
public Span<byte> NicknameDisplayTrash => Data.AsSpan(0x2E, 22);
public override Span<byte> NicknameTrash => Data.AsSpan(0x44, 22);
public override Span<byte> OriginalTrainerTrash => Data.Slice(0x18, 22);
public Span<byte> NicknameDisplayTrash => Data.Slice(0x2E, 22);
public override Span<byte> NicknameTrash => Data.Slice(0x44, 22);
public override int TrashCharCountTrainer => 11;
public override int TrashCharCountNickname => 11;
// Future Attributes
public override ushort SpeciesInternal { get => ReadUInt16BigEndian(Data.AsSpan(0x00)); set => WriteUInt16BigEndian(Data.AsSpan(0x00), value); } // raw access
public override ushort SpeciesInternal { get => ReadUInt16BigEndian(Data); set => WriteUInt16BigEndian(Data, value); } // raw access
public override ushort Species { get => SpeciesConverter.GetNational3(SpeciesInternal); set => SpeciesInternal = SpeciesConverter.GetInternal3(value); }
// 02-04 unused
public override uint PID { get => ReadUInt32BigEndian(Data.AsSpan(0x04)); set => WriteUInt32BigEndian(Data.AsSpan(0x04), value); }
public override uint PID { get => ReadUInt32BigEndian(Data[0x04..]); set => WriteUInt32BigEndian(Data[0x04..], value); }
public override GameVersion Version { get => GetGBAVersionID((GCVersion)Data[0x08]); set => Data[0x08] = (byte)GetGCVersionID(value); }
public GCRegion CurrentRegion { get => (GCRegion)Data[0x09]; set => Data[0x09] = (byte)value; }
public GCRegion OriginalRegion { get => (GCRegion)Data[0x0A]; set => Data[0x0A] = (byte)value; }
public override int Language { get => Core.Language.GetMainLangIDfromGC(Data[0x0B]); set => Data[0x0B] = Core.Language.GetGCLangIDfromMain((byte)value); }
public override ushort MetLocation { get => ReadUInt16BigEndian(Data.AsSpan(0x0C)); set => WriteUInt16BigEndian(Data.AsSpan(0x0C), value); }
public override ushort MetLocation { get => ReadUInt16BigEndian(Data[0x0C..]); set => WriteUInt16BigEndian(Data[0x0C..], value); }
public override byte MetLevel { get => Data[0x0E]; set => Data[0x0E] = value; }
public override byte Ball { get => Data[0x0F]; set => Data[0x0F] = value; }
public override byte OriginalTrainerGender { get => Data[0x10]; set => Data[0x10] = value; }
public override uint ID32 { get => ReadUInt32BigEndian(Data.AsSpan(0x14)); set => WriteUInt32BigEndian(Data.AsSpan(0x14), value); }
public override ushort SID16 { get => ReadUInt16BigEndian(Data.AsSpan(0x14)); set => WriteUInt16BigEndian(Data.AsSpan(0x14), value); }
public override ushort TID16 { get => ReadUInt16BigEndian(Data.AsSpan(0x16)); set => WriteUInt16BigEndian(Data.AsSpan(0x16), value); }
public override uint ID32 { get => ReadUInt32BigEndian(Data[0x14..]); set => WriteUInt32BigEndian(Data[0x14..], value); }
public override ushort SID16 { get => ReadUInt16BigEndian(Data[0x14..]); set => WriteUInt16BigEndian(Data[0x14..], value); }
public override ushort TID16 { get => ReadUInt16BigEndian(Data[0x16..]); set => WriteUInt16BigEndian(Data[0x16..], value); }
public override string OriginalTrainerName { get => GetString(OriginalTrainerTrash); set => SetString(OriginalTrainerTrash, value, 10, StringConverterOption.None); }
public string NicknameDisplay { get => GetString(NicknameDisplayTrash); set => SetString(NicknameDisplayTrash, value, 10, StringConverterOption.None); }
public override string Nickname { get => GetString(NicknameTrash); set { SetString(NicknameTrash, value, 10, StringConverterOption.None); ResetNicknameDisplay(); } }
@@ -59,7 +59,7 @@ public void ResetNicknameDisplay()
current[10..].Clear(); // clamp to 5 chars at most
}
public override uint EXP { get => ReadUInt32BigEndian(Data.AsSpan(0x5C)); set => WriteUInt32BigEndian(Data.AsSpan(0x5C), value); }
public override uint EXP { get => ReadUInt32BigEndian(Data[0x5C..]); set => WriteUInt32BigEndian(Data[0x5C..], value); }
public override byte Stat_Level { get => Data[0x60]; set => Data[0x60] = value; }
// 0x64-0x77 are battle/status related
@@ -67,55 +67,55 @@ public void ResetNicknameDisplay()
// Not that the program cares
// Moves
public override ushort Move1 { get => ReadUInt16BigEndian(Data.AsSpan(0x78)); set => WriteUInt16BigEndian(Data.AsSpan(0x78), value); }
public override ushort Move1 { get => ReadUInt16BigEndian(Data[0x78..]); set => WriteUInt16BigEndian(Data[0x78..], value); }
public override int Move1_PP { get => Data[0x7A]; set => Data[0x7A] = (byte)value; }
public override int Move1_PPUps { get => Data[0x7B]; set => Data[0x7B] = (byte)value; }
public override ushort Move2 { get => ReadUInt16BigEndian(Data.AsSpan(0x7C)); set => WriteUInt16BigEndian(Data.AsSpan(0x7C), value); }
public override ushort Move2 { get => ReadUInt16BigEndian(Data[0x7C..]); set => WriteUInt16BigEndian(Data[0x7C..], value); }
public override int Move2_PP { get => Data[0x7E]; set => Data[0x7E] = (byte)value; }
public override int Move2_PPUps { get => Data[0x7F]; set => Data[0x7F] = (byte)value; }
public override ushort Move3 { get => ReadUInt16BigEndian(Data.AsSpan(0x80)); set => WriteUInt16BigEndian(Data.AsSpan(0x80), value); }
public override ushort Move3 { get => ReadUInt16BigEndian(Data[0x80..]); set => WriteUInt16BigEndian(Data[0x80..], value); }
public override int Move3_PP { get => Data[0x82]; set => Data[0x82] = (byte)value; }
public override int Move3_PPUps { get => Data[0x83]; set => Data[0x83] = (byte)value; }
public override ushort Move4 { get => ReadUInt16BigEndian(Data.AsSpan(0x84)); set => WriteUInt16BigEndian(Data.AsSpan(0x84), value); }
public override ushort Move4 { get => ReadUInt16BigEndian(Data[0x84..]); set => WriteUInt16BigEndian(Data[0x84..], value); }
public override int Move4_PP { get => Data[0x86]; set => Data[0x86] = (byte)value; }
public override int Move4_PPUps { get => Data[0x87]; set => Data[0x87] = (byte)value; }
public override int SpriteItem => ItemConverter.GetItemFuture3((ushort)HeldItem);
public override int HeldItem { get => ReadUInt16BigEndian(Data.AsSpan(0x88)); set => WriteUInt16BigEndian(Data.AsSpan(0x88), (ushort)value); }
public override int HeldItem { get => ReadUInt16BigEndian(Data[0x88..]); set => WriteUInt16BigEndian(Data[0x88..], (ushort)value); }
// More party stats
public override int Stat_HPCurrent { get => ReadUInt16BigEndian(Data.AsSpan(0x8A)); set => WriteUInt16BigEndian(Data.AsSpan(0x8A), (ushort)value); }
public override int Stat_HPMax { get => ReadUInt16BigEndian(Data.AsSpan(0x8C)); set => WriteUInt16BigEndian(Data.AsSpan(0x8C), (ushort)value); }
public override int Stat_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x8E)); set => WriteUInt16BigEndian(Data.AsSpan(0x8E), (ushort)value); }
public override int Stat_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x90)); set => WriteUInt16BigEndian(Data.AsSpan(0x90), (ushort)value); }
public override int Stat_SPA { get => ReadUInt16BigEndian(Data.AsSpan(0x92)); set => WriteUInt16BigEndian(Data.AsSpan(0x92), (ushort)value); }
public override int Stat_SPD { get => ReadUInt16BigEndian(Data.AsSpan(0x94)); set => WriteUInt16BigEndian(Data.AsSpan(0x94), (ushort)value); }
public override int Stat_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x96)); set => WriteUInt16BigEndian(Data.AsSpan(0x96), (ushort)value); }
public override int Stat_HPCurrent { get => ReadUInt16BigEndian(Data[0x8A..]); set => WriteUInt16BigEndian(Data[0x8A..], (ushort)value); }
public override int Stat_HPMax { get => ReadUInt16BigEndian(Data[0x8C..]); set => WriteUInt16BigEndian(Data[0x8C..], (ushort)value); }
public override int Stat_ATK { get => ReadUInt16BigEndian(Data[0x8E..]); set => WriteUInt16BigEndian(Data[0x8E..], (ushort)value); }
public override int Stat_DEF { get => ReadUInt16BigEndian(Data[0x90..]); set => WriteUInt16BigEndian(Data[0x90..], (ushort)value); }
public override int Stat_SPA { get => ReadUInt16BigEndian(Data[0x92..]); set => WriteUInt16BigEndian(Data[0x92..], (ushort)value); }
public override int Stat_SPD { get => ReadUInt16BigEndian(Data[0x94..]); set => WriteUInt16BigEndian(Data[0x94..], (ushort)value); }
public override int Stat_SPE { get => ReadUInt16BigEndian(Data[0x96..]); set => WriteUInt16BigEndian(Data[0x96..], (ushort)value); }
// EVs
public override int EV_HP {
get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x98)));
set => WriteUInt16BigEndian(Data.AsSpan(0x98), (ushort)(value & 0xFF)); }
get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data[0x98..]));
set => WriteUInt16BigEndian(Data[0x98..], (ushort)(value & 0xFF)); }
public override int EV_ATK {
get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x9A)));
set => WriteUInt16BigEndian(Data.AsSpan(0x9A), (ushort)(value & 0xFF)); }
get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data[0x9A..]));
set => WriteUInt16BigEndian(Data[0x9A..], (ushort)(value & 0xFF)); }
public override int EV_DEF {
get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x9C)));
set => WriteUInt16BigEndian(Data.AsSpan(0x9C), (ushort)(value & 0xFF)); }
get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data[0x9C..]));
set => WriteUInt16BigEndian(Data[0x9C..], (ushort)(value & 0xFF)); }
public override int EV_SPA {
get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x9E)));
set => WriteUInt16BigEndian(Data.AsSpan(0x9E), (ushort)(value & 0xFF)); }
get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data[0x9E..]));
set => WriteUInt16BigEndian(Data[0x9E..], (ushort)(value & 0xFF)); }
public override int EV_SPD {
get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0xA0)));
set => WriteUInt16BigEndian(Data.AsSpan(0xA0), (ushort)(value & 0xFF)); }
get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data[0xA0..]));
set => WriteUInt16BigEndian(Data[0xA0..], (ushort)(value & 0xFF)); }
public override int EV_SPE {
get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0xA2)));
set => WriteUInt16BigEndian(Data.AsSpan(0xA2), (ushort)(value & 0xFF)); }
get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data[0xA2..]));
set => WriteUInt16BigEndian(Data[0xA2..], (ushort)(value & 0xFF)); }
// IVs
// Big Endian! Shift offset by 1 when interacting with a single byte.
@@ -127,32 +127,32 @@ public void ResetNicknameDisplay()
byte ISeparateIVs.IV_SPE { get => Data[0xAE + 1]; set => Data[0xAE + 1] = value; }
public override int IV_HP {
get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xA4)));
set => WriteUInt16BigEndian(Data.AsSpan(0xA4), (ushort)(value & 0x1F)); }
get => Math.Min((ushort)31, ReadUInt16BigEndian(Data[0xA4..]));
set => WriteUInt16BigEndian(Data[0xA4..], (ushort)(value & 0x1F)); }
public override int IV_ATK {
get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xA6)));
set => WriteUInt16BigEndian(Data.AsSpan(0xA6), (ushort)(value & 0x1F)); }
get => Math.Min((ushort)31, ReadUInt16BigEndian(Data[0xA6..]));
set => WriteUInt16BigEndian(Data[0xA6..], (ushort)(value & 0x1F)); }
public override int IV_DEF {
get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xA8)));
set => WriteUInt16BigEndian(Data.AsSpan(0xA8), (ushort)(value & 0x1F)); }
get => Math.Min((ushort)31, ReadUInt16BigEndian(Data[0xA8..]));
set => WriteUInt16BigEndian(Data[0xA8..], (ushort)(value & 0x1F)); }
public override int IV_SPA {
get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xAA)));
set => WriteUInt16BigEndian(Data.AsSpan(0xAA), (ushort)(value & 0x1F)); }
get => Math.Min((ushort)31, ReadUInt16BigEndian(Data[0xAA..]));
set => WriteUInt16BigEndian(Data[0xAA..], (ushort)(value & 0x1F)); }
public override int IV_SPD {
get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xAC)));
set => WriteUInt16BigEndian(Data.AsSpan(0xAC), (ushort)(value & 0x1F)); }
get => Math.Min((ushort)31, ReadUInt16BigEndian(Data[0xAC..]));
set => WriteUInt16BigEndian(Data[0xAC..], (ushort)(value & 0x1F)); }
public override int IV_SPE {
get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xAE)));
set => WriteUInt16BigEndian(Data.AsSpan(0xAE), (ushort)(value & 0x1F)); }
get => Math.Min((ushort)31, ReadUInt16BigEndian(Data[0xAE..]));
set => WriteUInt16BigEndian(Data[0xAE..], (ushort)(value & 0x1F)); }
public override byte OriginalTrainerFriendship {
get => (byte)Math.Min((ushort)0xFF, ReadUInt16BigEndian(Data.AsSpan(0xB0)));
set => WriteUInt16BigEndian(Data.AsSpan(0xB0), (ushort)(value & 0xFF));
get => (byte)Math.Min((ushort)0xFF, ReadUInt16BigEndian(Data[0xB0..]));
set => WriteUInt16BigEndian(Data[0xB0..], (ushort)(value & 0xFF));
}
// Contest
@@ -186,7 +186,7 @@ public void ResetNicknameDisplay()
public override bool Unused3 { get => ((Data[0xC9] >> 2) & 1) == 1; set => Data[0xC9] = (byte)((Data[0xC9] & ~4) | (value ? 4 : 0)); }
public override bool Unused4 { get => ((Data[0xC9] >> 3) & 1) == 1; set => Data[0xC9] = (byte)((Data[0xC9] & ~8) | (value ? 8 : 0)); }
private bool FatefulEncounterJPN { get => ((Data[0xC9] >> 4) & 1) == 1; set => Data[0xC9] = (byte)((Data[0xC9] &~16) | (value ?16 : 0)); }
public override int RibbonCount => Data.AsSpan(0xBD, 12).Count<byte>(1) + RibbonCountG3Cool + RibbonCountG3Beauty + RibbonCountG3Cute + RibbonCountG3Smart + RibbonCountG3Tough;
public override int RibbonCount => Data.Slice(0xBD, 12).Count<byte>(1) + RibbonCountG3Cool + RibbonCountG3Beauty + RibbonCountG3Cute + RibbonCountG3Smart + RibbonCountG3Tough;
public override int PokerusStrain { get => Data[0xCA] & 0xF; set => Data[0xCA] = (byte)(value & 0xF); }
public override bool IsEgg { get => Data[0xCB] == 1; set => Data[0xCB] = value ? (byte)1 : (byte)0; }
@@ -197,9 +197,9 @@ public void ResetNicknameDisplay()
public override int PokerusDays { get => Math.Max((sbyte)Data[0xD0], (sbyte)0); set => Data[0xD0] = (byte)(value == 0 ? 0xFF : value & 0xF); }
public int PartySlot { get => Data[0xD7]; set => Data[0xD7] = (byte)value; } // or not; only really used while in party?
public ushort ShadowID { get => ReadUInt16BigEndian(Data.AsSpan(0xD8)); set => WriteUInt16BigEndian(Data.AsSpan(0xD8), value); }
public int Purification { get => ReadInt32BigEndian(Data.AsSpan(0xDC)); set => WriteInt32BigEndian(Data.AsSpan(0xDC), value); }
public uint EXP_Shadow { get => ReadUInt32BigEndian(Data.AsSpan(0xC0)); set => WriteUInt32BigEndian(Data.AsSpan(0xC0), value); }
public ushort ShadowID { get => ReadUInt16BigEndian(Data[0xD8..]); set => WriteUInt16BigEndian(Data[0xD8..], value); }
public int Purification { get => ReadInt32BigEndian(Data[0xDC..]); set => WriteInt32BigEndian(Data[0xDC..], value); }
public uint EXP_Shadow { get => ReadUInt32BigEndian(Data[0xC0..]); set => WriteUInt32BigEndian(Data[0xC0..], value); }
private bool FatefulEncounterINT { get => ((Data[0xFB] >> 0) & 1) == 1; set => Data[0xFB] = (byte)((Data[0xFB] & ~1) | (value ? 1 : 0)); }
@@ -238,7 +238,7 @@ public bool IsFatefulValid(bool japanese)
public const int Purified = -100;
public bool IsShadow => ShadowID != 0 && Purification != Purified;
protected override byte[] Encrypt() => (byte[])Data.Clone();
protected override byte[] Encrypt() => Data.ToArray();
public PK3 ConvertToPK3()
{

View File

@@ -102,9 +102,9 @@ private static void Crypt(ReadOnlySpan<byte> data, Span<byte> result, byte[] key
/// Decrypts the input <see cref="data"/> data into a new array if it is encrypted, and updates the reference.
/// </summary>
/// <remarks>Format encryption check</remarks>
public static void DecryptIfEncrypted(ref byte[] data)
public static void DecryptIfEncrypted(ref Memory<byte> data)
{
var span = data.AsSpan();
var span = data.Span;
var format = ReadUInt16LittleEndian(span);
if (IsKnownVersion(format))
{

View File

@@ -17,9 +17,9 @@ public sealed class PKH : PKM, IHandlerLanguage, IFormArgument, IHomeTrack, IBat
public override EntityContext Context => EntityContext.None;
public PKH(byte[] data) : base(DecryptHome(data))
public PKH(Memory<byte> data) : base(DecryptHome(data))
{
var mem = Data.AsMemory(HomeCrypto.SIZE_1HEADER + 2);
var mem = Raw[(HomeCrypto.SIZE_1HEADER + 2)..];
var core = mem[..CoreDataSize];
var side = mem.Slice(core.Length + 2, GameDataSize);
@@ -31,7 +31,7 @@ public PKH() : base(HomeCrypto.SIZE_STORED)
{
CoreDataSize = HomeCrypto.SIZE_CORE;
var mem = Data.AsMemory(HomeCrypto.SIZE_1HEADER + 2);
var mem = Raw[(HomeCrypto.SIZE_1HEADER + 2)..];
var core = mem[..CoreDataSize];
Core = new GameDataCore(core) { AffixedRibbon = PKHeX.Core.AffixedRibbon.None };
}
@@ -62,19 +62,19 @@ private void ReadGameData1(Memory<byte> data)
_ => throw new ArgumentException($"Unknown {nameof(HomeGameDataFormat)} {format}"),
};
private static byte[] DecryptHome(byte[] data)
private static Memory<byte> DecryptHome(Memory<byte> data)
{
HomeCrypto.DecryptIfEncrypted(ref data);
//Array.Resize(ref data, HomeCrypto.SIZE_1STORED);
return data;
}
public ushort DataVersion { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), value); }
public ulong EncryptionSeed { get => ReadUInt64LittleEndian(Data.AsSpan(0x02)); set => WriteUInt64LittleEndian(Data.AsSpan(0x02), value); }
public uint Checksum { get => ReadUInt32LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt32LittleEndian(Data.AsSpan(0x0A), value); }
public ushort EncodedDataSize { get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), value); }
public ushort CoreDataSize { get => ReadUInt16LittleEndian(Data.AsSpan(0x10)); set => WriteUInt16LittleEndian(Data.AsSpan(0x10), value); }
public ushort GameDataSize { get => ReadUInt16LittleEndian(Data.AsSpan(0x12 + CoreDataSize)); set => WriteUInt16LittleEndian(Data.AsSpan(0x12 + CoreDataSize), value); }
public ushort DataVersion { get => ReadUInt16LittleEndian(Data); set => WriteUInt16LittleEndian(Data, value); }
public ulong EncryptionSeed { get => ReadUInt64LittleEndian(Data[0x02..]); set => WriteUInt64LittleEndian(Data[0x02..], value); }
public uint Checksum { get => ReadUInt32LittleEndian(Data[0x0A..]); set => WriteUInt32LittleEndian(Data[0x0A..], value); }
public ushort EncodedDataSize { get => ReadUInt16LittleEndian(Data[0x0E..]); set => WriteUInt16LittleEndian(Data[0x0E..], value); }
public ushort CoreDataSize { get => ReadUInt16LittleEndian(Data[0x10..]); set => WriteUInt16LittleEndian(Data[0x10..], value); }
public ushort GameDataSize { get => ReadUInt16LittleEndian(Data[(0x12 + CoreDataSize)..]); set => WriteUInt16LittleEndian(Data[(0x12 + CoreDataSize)..], value); }
private const int GameDataStart = HomeCrypto.SIZE_1HEADER + 2 + HomeCrypto.SIZE_CORE + 2;
@@ -288,7 +288,7 @@ public byte[] Rebuild()
DataVersion = HomeCrypto.VersionLatest;
EncodedDataSize = (ushort)(result.Length - HomeCrypto.SIZE_1HEADER);
CoreDataSize = (ushort)Core.SerializedSize;
Data.AsSpan(0, HomeCrypto.SIZE_1HEADER + 2).CopyTo(span); // Copy updated header & CoreData length.
Data[..(HomeCrypto.SIZE_1HEADER + 2)].CopyTo(span); // Copy updated header & CoreData length.
return result;
}
@@ -307,7 +307,7 @@ private int WriteLength
}
}
public override PKH Clone() => new((byte[])Data.Clone())
public override PKH Clone() => new(Data.ToArray())
{
DataPK9 = DataPK9?.Clone(),
DataPK8 = DataPK8?.Clone(),

View File

@@ -34,21 +34,25 @@ public sealed class PA8 : PKM, ISanityChecksum,
public override EntityContext Context => EntityContext.Gen8a;
public PA8() : base(PokeCrypto.SIZE_8APARTY) => AffixedRibbon = Core.AffixedRibbon.None;
public PA8(byte[] data) : base(DecryptParty(data)) { }
public PA8(Memory<byte> data) : base(DecryptParty(data)) { }
public override int SIZE_PARTY => PokeCrypto.SIZE_8APARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_8ASTORED;
public override bool ChecksumValid => CalculateChecksum() == Checksum;
public override PA8 Clone() => new((byte[])Data.Clone());
public override PA8 Clone() => new(Data.ToArray());
private static byte[] DecryptParty(byte[] data)
private static Memory<byte> DecryptParty(Memory<byte> data)
{
PokeCrypto.DecryptIfEncrypted8A(ref data);
Array.Resize(ref data, PokeCrypto.SIZE_8APARTY);
return data;
if (data.Length >= PokeCrypto.SIZE_8APARTY)
return data;
var result = new byte[PokeCrypto.SIZE_8APARTY];
data.Span.CopyTo(result);
return result;
}
private ushort CalculateChecksum() => Checksums.Add16(Data.AsSpan()[8..PokeCrypto.SIZE_8ASTORED]);
private ushort CalculateChecksum() => Checksums.Add16(Data[8..PokeCrypto.SIZE_8ASTORED]);
// Simple Generated Attributes
@@ -62,9 +66,9 @@ public override byte CurrentFriendship
public override bool Valid { get => Sanity == 0 && ChecksumValid; set { if (!value) return; Sanity = 0; RefreshChecksum(); } }
// Trash Bytes
public override Span<byte> NicknameTrash => Data.AsSpan(0x60, 26);
public override Span<byte> HandlingTrainerTrash => Data.AsSpan(0xB8, 26);
public override Span<byte> OriginalTrainerTrash => Data.AsSpan(0x110, 26);
public override Span<byte> NicknameTrash => Data.Slice(0x60, 26);
public override Span<byte> HandlingTrainerTrash => Data.Slice(0xB8, 26);
public override Span<byte> OriginalTrainerTrash => Data.Slice(0x110, 26);
public override int TrashCharCountTrainer => 13;
public override int TrashCharCountNickname => 13;
@@ -113,29 +117,29 @@ public void FixRelearn()
}
}
public override uint EncryptionConstant { get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); }
public ushort Sanity { get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); }
public ushort Checksum { get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); }
public override uint EncryptionConstant { get => ReadUInt32LittleEndian(Data); set => WriteUInt32LittleEndian(Data, value); }
public ushort Sanity { get => ReadUInt16LittleEndian(Data[0x04..]); set => WriteUInt16LittleEndian(Data[0x04..], value); }
public ushort Checksum { get => ReadUInt16LittleEndian(Data[0x06..]); set => WriteUInt16LittleEndian(Data[0x06..], value); }
// Structure
#region Block A
public override ushort Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); set => WriteUInt16LittleEndian(Data.AsSpan(0x08), value); }
public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); }
public override uint ID32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x0C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x0C), value); }
public override ushort TID16 { get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), value); }
public override ushort SID16 { get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), value); }
public override uint EXP { get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); }
public override int Ability { get => ReadUInt16LittleEndian(Data.AsSpan(0x14)); set => WriteUInt16LittleEndian(Data.AsSpan(0x14), (ushort)value); }
public override ushort Species { get => ReadUInt16LittleEndian(Data[0x08..]); set => WriteUInt16LittleEndian(Data[0x08..], value); }
public override int HeldItem { get => ReadUInt16LittleEndian(Data[0x0A..]); set => WriteUInt16LittleEndian(Data[0x0A..], (ushort)value); }
public override uint ID32 { get => ReadUInt32LittleEndian(Data[0x0C..]); set => WriteUInt32LittleEndian(Data[0x0C..], value); }
public override ushort TID16 { get => ReadUInt16LittleEndian(Data[0x0C..]); set => WriteUInt16LittleEndian(Data[0x0C..], value); }
public override ushort SID16 { get => ReadUInt16LittleEndian(Data[0x0E..]); set => WriteUInt16LittleEndian(Data[0x0E..], value); }
public override uint EXP { get => ReadUInt32LittleEndian(Data[0x10..]); set => WriteUInt32LittleEndian(Data[0x10..], value); }
public override int Ability { get => ReadUInt16LittleEndian(Data[0x14..]); set => WriteUInt16LittleEndian(Data[0x14..], (ushort)value); }
public override int AbilityNumber { get => Data[0x16] & 7; set => Data[0x16] = (byte)((Data[0x16] & ~7) | (value & 7)); }
public bool IsFavorite { get => (Data[0x16] & 8) != 0; set => Data[0x16] = (byte)((Data[0x16] & ~8) | ((value ? 1 : 0) << 3)); } // unused, was in LGPE but not in SWSH
public bool CanGigantamax { get => (Data[0x16] & 16) != 0; set => Data[0x16] = (byte)((Data[0x16] & ~16) | (value ? 16 : 0)); }
public bool IsAlpha { get => (Data[0x16] & 32) != 0; set => Data[0x16] = (byte)((Data[0x16] & ~32) | ((value ? 1 : 0) << 5)); }
public bool IsNoble { get => (Data[0x16] & 64) != 0; set => Data[0x16] = (byte)((Data[0x16] & ~64) | ((value ? 1 : 0) << 6)); }
// 0x17 alignment unused
public ushort MarkingValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(0x18), value); }
public ushort MarkingValue { get => ReadUInt16LittleEndian(Data[0x18..]); set => WriteUInt16LittleEndian(Data[0x18..], value); }
// 0x1A alignment unused
// 0x1B alignment unused
public override uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x1C), value); }
public override uint PID { get => ReadUInt32LittleEndian(Data[0x1C..]); set => WriteUInt32LittleEndian(Data[0x1C..], value); }
public override Nature Nature { get => (Nature)Data[0x20]; set => Data[0x20] = (byte)value; }
public override Nature StatNature { get => (Nature)Data[0x21]; set => Data[0x21] = (byte)value; }
public override bool FatefulEncounter { get => (Data[0x22] & 1) == 1; set => Data[0x22] = (byte)((Data[0x22] & ~0x01) | (value ? 1 : 0)); }
@@ -143,7 +147,7 @@ public void FixRelearn()
public override byte Gender { get => (byte)((Data[0x22] >> 2) & 0x3); set => Data[0x22] = (byte)((Data[0x22] & 0xF3) | (value << 2)); }
// 0x23 alignment unused
public override byte Form { get => Data[0x24]; set => WriteUInt16LittleEndian(Data.AsSpan(0x24), value); }
public override byte Form { get => Data[0x24]; set => WriteUInt16LittleEndian(Data[0x24..], value); }
public override int EV_HP { get => Data[0x26]; set => Data[0x26] = (byte)value; }
public override int EV_ATK { get => Data[0x27]; set => Data[0x27] = (byte)value; }
public override int EV_DEF { get => Data[0x28]; set => Data[0x28] = (byte)value; }
@@ -237,7 +241,7 @@ public void FixRelearn()
public byte RibbonCountMemoryContest { get => Data[0x3C]; set => HasContestMemoryRibbon = (Data[0x3C] = value) != 0; }
public byte RibbonCountMemoryBattle { get => Data[0x3D]; set => HasBattleMemoryRibbon = (Data[0x3D] = value) != 0; }
public ushort AlphaMove { get => ReadUInt16LittleEndian(Data.AsSpan(0x3E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3E), value); }
public ushort AlphaMove { get => ReadUInt16LittleEndian(Data[0x3E..]); set => WriteUInt16LittleEndian(Data[0x3E..], value); }
// 0x40 Ribbon 1
public bool RibbonMarkMisty { get => FlagUtil.GetFlag(Data, 0x40, 0); set => FlagUtil.SetFlag(Data, 0x40, 0, value); }
@@ -313,18 +317,18 @@ public void FixRelearn()
public bool RIB47_6 { get => FlagUtil.GetFlag(Data, 0x47, 6); set => FlagUtil.SetFlag(Data, 0x47, 6, value); }
public bool RIB47_7 { get => FlagUtil.GetFlag(Data, 0x47, 7); set => FlagUtil.SetFlag(Data, 0x47, 7, value); }
public int RibbonCount => BitOperations.PopCount(ReadUInt64LittleEndian(Data.AsSpan(0x34)) & 0b00000000_00011111__11111111_11111111__11111111_11111111__11111111_11111111)
+ BitOperations.PopCount(ReadUInt64LittleEndian(Data.AsSpan(0x40)) & 0b00000000_00000000__00000100_00011100__00000000_00000000__00000000_00000000);
public int MarkCount => BitOperations.PopCount(ReadUInt64LittleEndian(Data.AsSpan(0x34)) & 0b11111111_11100000__00000000_00000000__00000000_00000000__00000000_00000000)
+ BitOperations.PopCount(ReadUInt64LittleEndian(Data.AsSpan(0x40)) & 0b00000000_00000000__00111011_11100011__11111111_11111111__11111111_11111111);
public int RibbonMarkCount => BitOperations.PopCount(ReadUInt64LittleEndian(Data.AsSpan(0x34)) & 0b11111111_11111111__11111111_11111111__11111111_11111111__11111111_11111111)
+ BitOperations.PopCount(ReadUInt64LittleEndian(Data.AsSpan(0x40)) & 0b00000000_00000000__00111111_11111111__11111111_11111111__11111111_11111111);
public int RibbonCount => BitOperations.PopCount(ReadUInt64LittleEndian(Data[0x34..]) & 0b00000000_00011111__11111111_11111111__11111111_11111111__11111111_11111111)
+ BitOperations.PopCount(ReadUInt64LittleEndian(Data[0x40..]) & 0b00000000_00000000__00000100_00011100__00000000_00000000__00000000_00000000);
public int MarkCount => BitOperations.PopCount(ReadUInt64LittleEndian(Data[0x34..]) & 0b11111111_11100000__00000000_00000000__00000000_00000000__00000000_00000000)
+ BitOperations.PopCount(ReadUInt64LittleEndian(Data[0x40..]) & 0b00000000_00000000__00111011_11100011__11111111_11111111__11111111_11111111);
public int RibbonMarkCount => BitOperations.PopCount(ReadUInt64LittleEndian(Data[0x34..]) & 0b11111111_11111111__11111111_11111111__11111111_11111111__11111111_11111111)
+ BitOperations.PopCount(ReadUInt64LittleEndian(Data[0x40..]) & 0b00000000_00000000__00111111_11111111__11111111_11111111__11111111_11111111);
public bool HasMarkEncounter8 => BitOperations.PopCount(ReadUInt64LittleEndian(Data.AsSpan(0x34)) & 0b11111111_11100000__00000000_00000000__00000000_00000000__00000000_00000000)
+ BitOperations.PopCount(ReadUInt64LittleEndian(Data.AsSpan(0x40)) & 0b00000000_00000000__00000000_00000011__11111111_11111111__11111111_11111111) != 0;
public bool HasMarkEncounter8 => BitOperations.PopCount(ReadUInt64LittleEndian(Data[0x34..]) & 0b11111111_11100000__00000000_00000000__00000000_00000000__00000000_00000000)
+ BitOperations.PopCount(ReadUInt64LittleEndian(Data[0x40..]) & 0b00000000_00000000__00000000_00000011__11111111_11111111__11111111_11111111) != 0;
public bool HasMarkEncounter9 => (Data[0x45] & 0b00111000) != 0;
public uint Sociability { get => ReadUInt32LittleEndian(Data.AsSpan(0x48)); set => WriteUInt32LittleEndian(Data.AsSpan(0x48), value); }
public uint Sociability { get => ReadUInt32LittleEndian(Data[0x48..]); set => WriteUInt32LittleEndian(Data[0x48..], value); }
// 0x4C-0x4F unused
@@ -334,10 +338,10 @@ public void FixRelearn()
// 0x53 unused
public override ushort Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x54)); set => WriteUInt16LittleEndian(Data.AsSpan(0x54), value); }
public override ushort Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x56)); set => WriteUInt16LittleEndian(Data.AsSpan(0x56), value); }
public override ushort Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x58)); set => WriteUInt16LittleEndian(Data.AsSpan(0x58), value); }
public override ushort Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x5A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5A), value); }
public override ushort Move1 { get => ReadUInt16LittleEndian(Data[0x54..]); set => WriteUInt16LittleEndian(Data[0x54..], value); }
public override ushort Move2 { get => ReadUInt16LittleEndian(Data[0x56..]); set => WriteUInt16LittleEndian(Data[0x56..], value); }
public override ushort Move3 { get => ReadUInt16LittleEndian(Data[0x58..]); set => WriteUInt16LittleEndian(Data[0x58..], value); }
public override ushort Move4 { get => ReadUInt16LittleEndian(Data[0x5A..]); set => WriteUInt16LittleEndian(Data[0x5A..], value); }
public override int Move1_PP { get => Data[0x5C]; set => Data[0x5C] = (byte)value; }
public override int Move2_PP { get => Data[0x5D]; set => Data[0x5D] = (byte)value; }
@@ -358,13 +362,13 @@ public override string Nickname
public override int Move3_PPUps { get => Data[0x88]; set => Data[0x88] = (byte)value; }
public override int Move4_PPUps { get => Data[0x89]; set => Data[0x89] = (byte)value; }
public override ushort RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x8A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x8A), value); }
public override ushort RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x8C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x8C), value); }
public override ushort RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x8E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x8E), value); }
public override ushort RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x90)); set => WriteUInt16LittleEndian(Data.AsSpan(0x90), value); }
public override ushort RelearnMove1 { get => ReadUInt16LittleEndian(Data[0x8A..]); set => WriteUInt16LittleEndian(Data[0x8A..], value); }
public override ushort RelearnMove2 { get => ReadUInt16LittleEndian(Data[0x8C..]); set => WriteUInt16LittleEndian(Data[0x8C..], value); }
public override ushort RelearnMove3 { get => ReadUInt16LittleEndian(Data[0x8E..]); set => WriteUInt16LittleEndian(Data[0x8E..], value); }
public override ushort RelearnMove4 { get => ReadUInt16LittleEndian(Data[0x90..]); set => WriteUInt16LittleEndian(Data[0x90..], value); }
public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0x92)); set => WriteUInt16LittleEndian(Data.AsSpan(0x92), (ushort)value); }
public uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x94)); set => WriteUInt32LittleEndian(Data.AsSpan(0x94), value); }
public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data[0x92..]); set => WriteUInt16LittleEndian(Data[0x92..], (ushort)value); }
public uint IV32 { get => ReadUInt32LittleEndian(Data[0x94..]); set => WriteUInt32LittleEndian(Data[0x94..], value); }
public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); }
public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); }
public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); }
@@ -376,8 +380,8 @@ public override string Nickname
public byte DynamaxLevel { get => Data[0x98]; set => Data[0x98] = value; }
public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0x9C)); set => WriteInt32LittleEndian(Data.AsSpan(0x9C), value); }
public int UnkA0 { get => ReadInt32LittleEndian(Data.AsSpan(0xA0)); set => WriteInt32LittleEndian(Data.AsSpan(0xA0), value); }
public override int Status_Condition { get => ReadInt32LittleEndian(Data[0x9C..]); set => WriteInt32LittleEndian(Data[0x9C..], value); }
public int UnkA0 { get => ReadInt32LittleEndian(Data[0xA0..]); set => WriteInt32LittleEndian(Data[0xA0..], value); }
public byte GV_HP { get => Data[0xA4]; set => Data[0xA4] = value; }
public byte GV_ATK { get => Data[0xA5]; set => Data[0xA5] = value; }
public byte GV_DEF { get => Data[0xA6]; set => Data[0xA6] = value; }
@@ -387,8 +391,8 @@ public override string Nickname
// 0xAA-0xAB unused
public float HeightAbsolute { get => ReadSingleLittleEndian(Data.AsSpan(0xAC)); set => WriteSingleLittleEndian(Data.AsSpan(0xAC), value); }
public float WeightAbsolute { get => ReadSingleLittleEndian(Data.AsSpan(0xB0)); set => WriteSingleLittleEndian(Data.AsSpan(0xB0), value); }
public float HeightAbsolute { get => ReadSingleLittleEndian(Data[0xAC..]); set => WriteSingleLittleEndian(Data[0xAC..], value); }
public float WeightAbsolute { get => ReadSingleLittleEndian(Data[0xB0..]); set => WriteSingleLittleEndian(Data[0xB0..], value); }
// 0xB4-0xB7 unused
@@ -404,12 +408,12 @@ public override string HandlingTrainerName
public byte HandlingTrainerLanguage { get => Data[0xD3]; set => Data[0xD3] = value; }
public override byte CurrentHandler { get => Data[0xD4]; set => Data[0xD4] = value; }
// 0xD5 unused (alignment)
public ushort HandlingTrainerID { get => ReadUInt16LittleEndian(Data.AsSpan(0xD6)); set => WriteUInt16LittleEndian(Data.AsSpan(0xD6), value); } // unused?
public ushort HandlingTrainerID { get => ReadUInt16LittleEndian(Data[0xD6..]); set => WriteUInt16LittleEndian(Data[0xD6..], value); } // unused?
public override byte HandlingTrainerFriendship { get => Data[0xD8]; set => Data[0xD8] = value; }
public byte HandlingTrainerMemoryIntensity { get => Data[0xD9]; set => Data[0xD9] = value; }
public byte HandlingTrainerMemory { get => Data[0xDA]; set => Data[0xDA] = value; }
public byte HandlingTrainerMemoryFeeling { get => Data[0xDB]; set => Data[0xDB] = value; }
public ushort HandlingTrainerMemoryVariable { get => ReadUInt16LittleEndian(Data.AsSpan(0xDC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDC), value); }
public ushort HandlingTrainerMemoryVariable { get => ReadUInt16LittleEndian(Data[0xDC..]); set => WriteUInt16LittleEndian(Data[0xDC..], value); }
// 0xDE-0xEB unused
@@ -421,7 +425,7 @@ public override string HandlingTrainerName
// public override byte ConsoleRegion { get => Data[0xF1]; set => Data[0xF1] = (byte)value; }
public override int Language { get => Data[0xF2]; set => Data[0xF2] = (byte)value; }
public int UnkF3 { get => Data[0xF3]; set => Data[0xF3] = (byte)value; }
public uint FormArgument { get => ReadUInt32LittleEndian(Data.AsSpan(0xF4)); set => WriteUInt32LittleEndian(Data.AsSpan(0xF4), value); }
public uint FormArgument { get => ReadUInt32LittleEndian(Data[0xF4..]); set => WriteUInt32LittleEndian(Data[0xF4..], value); }
public byte FormArgumentRemain { get => (byte)FormArgument; set => FormArgument = (FormArgument & ~0xFFu) | value; }
public byte FormArgumentElapsed { get => (byte)(FormArgument >> 8); set => FormArgument = (FormArgument & ~0xFF00u) | (uint)(value << 8); }
public byte FormArgumentMaximum { get => (byte)(FormArgument >> 16); set => FormArgument = (FormArgument & ~0xFF0000u) | (uint)(value << 16); }
@@ -439,7 +443,7 @@ public override string OriginalTrainerName
public byte OriginalTrainerMemoryIntensity { get => Data[0x12B]; set => Data[0x12B] = value; }
public byte OriginalTrainerMemory { get => Data[0x12C]; set => Data[0x12C] = value; }
// 0x12D unused align
public ushort OriginalTrainerMemoryVariable { get => ReadUInt16LittleEndian(Data.AsSpan(0x12E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x12E), value); }
public ushort OriginalTrainerMemoryVariable { get => ReadUInt16LittleEndian(Data[0x12E..]); set => WriteUInt16LittleEndian(Data[0x12E..], value); }
public byte OriginalTrainerMemoryFeeling { get => Data[0x130]; set => Data[0x130] = value; }
public override byte EggYear { get => Data[0x131]; set => Data[0x131] = value; }
public override byte EggMonth { get => Data[0x132]; set => Data[0x132] = value; }
@@ -448,8 +452,8 @@ public override string OriginalTrainerName
public override byte MetMonth { get => Data[0x135]; set => Data[0x135] = value; }
public override byte MetDay { get => Data[0x136]; set => Data[0x136] = value; }
public override byte Ball { get => Data[0x137]; set => Data[0x137] = value; }
public override ushort EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x138)); set => WriteUInt16LittleEndian(Data.AsSpan(0x138), value); }
public override ushort MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x13A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x13A), value); }
public override ushort EggLocation { get => ReadUInt16LittleEndian(Data[0x138..]); set => WriteUInt16LittleEndian(Data[0x138..], value); }
public override ushort MetLocation { get => ReadUInt16LittleEndian(Data[0x13A..]); set => WriteUInt16LittleEndian(Data[0x13A..], value); }
// 0x13C unused align
public override byte MetLevel { get => (byte)(Data[0x13D] & ~0x80); set => Data[0x13D] = (byte)((Data[0x13D] & 0x80) | value); }
public override byte OriginalTrainerGender { get => (byte)(Data[0x13D] >> 7); set => Data[0x13D] = (byte)((Data[0x13D] & ~0x80) | (value << 7)); }
@@ -477,23 +481,23 @@ public void SetMoveRecordFlag(int index, bool value = true)
FlagUtil.SetFlag(Data, 0x13F + ofs, index & 7, value);
}
public Span<byte> MoveRecordFlags => Data.AsSpan(0x13F, 14);
public Span<byte> MoveRecordFlags => Data.Slice(0x13F, 14);
public bool GetMoveRecordFlagAny() => MoveRecordFlags.ContainsAnyExcept<byte>(0);
public void ClearMoveRecordFlags() => MoveRecordFlags.Clear();
public ulong Tracker
{
get => ReadUInt64LittleEndian(Data.AsSpan(0x14D));
set => WriteUInt64LittleEndian(Data.AsSpan(0x14D), value);
get => ReadUInt64LittleEndian(Data[0x14D..]);
set => WriteUInt64LittleEndian(Data[0x14D..], value);
}
public Span<byte> PurchasedRecord => Data.AsSpan(0x155, 8);
public Span<byte> PurchasedRecord => Data.Slice(0x155, 8);
public bool GetPurchasedRecordFlag(int index) => FlagUtil.GetFlag(PurchasedRecord, index >> 3, index & 7);
public void SetPurchasedRecordFlag(int index, bool value) => FlagUtil.SetFlag(PurchasedRecord, index >> 3, index & 7, value);
public bool GetPurchasedRecordFlagAny() => PurchasedRecord.ContainsAnyExcept<byte>(0);
public int GetPurchasedCount() => BitOperations.PopCount(ReadUInt64LittleEndian(PurchasedRecord));
public Span<byte> MasteredRecord => Data.AsSpan(0x15D, 8);
public Span<byte> MasteredRecord => Data.Slice(0x15D, 8);
public bool GetMasteredRecordFlag(int index) => FlagUtil.GetFlag(MasteredRecord, index >> 3, index & 7);
public void SetMasteredRecordFlag(int index, bool value) => FlagUtil.SetFlag(MasteredRecord, index >> 3, index & 7, value);
public bool GetMasteredRecordFlagAny() => MasteredRecord.ContainsAnyExcept<byte>(0);
@@ -502,12 +506,12 @@ public ulong Tracker
#region Battle Stats
public override byte Stat_Level { get => Data[0x168]; set => Data[0x168] = value; }
// 0x149 unused alignment
public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0x16A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x16A), (ushort)value); }
public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0x16C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x16C), (ushort)value); }
public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0x16E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x16E), (ushort)value); }
public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0x170)); set => WriteUInt16LittleEndian(Data.AsSpan(0x170), (ushort)value); }
public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0x172)); set => WriteUInt16LittleEndian(Data.AsSpan(0x172), (ushort)value); }
public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0x174)); set => WriteUInt16LittleEndian(Data.AsSpan(0x174), (ushort)value); }
public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data[0x16A..]); set => WriteUInt16LittleEndian(Data[0x16A..], (ushort)value); }
public override int Stat_ATK { get => ReadUInt16LittleEndian(Data[0x16C..]); set => WriteUInt16LittleEndian(Data[0x16C..], (ushort)value); }
public override int Stat_DEF { get => ReadUInt16LittleEndian(Data[0x16E..]); set => WriteUInt16LittleEndian(Data[0x16E..], (ushort)value); }
public override int Stat_SPE { get => ReadUInt16LittleEndian(Data[0x170..]); set => WriteUInt16LittleEndian(Data[0x170..], (ushort)value); }
public override int Stat_SPA { get => ReadUInt16LittleEndian(Data[0x172..]); set => WriteUInt16LittleEndian(Data[0x172..], (ushort)value); }
public override int Stat_SPD { get => ReadUInt16LittleEndian(Data[0x174..]); set => WriteUInt16LittleEndian(Data[0x174..], (ushort)value); }
#endregion
public override void LoadStats(IBaseStat p, Span<ushort> stats)

View File

@@ -30,83 +30,86 @@ public sealed class PB7 : G6PKM, IHyperTrain, IAwakened, IScaledSizeValue, IComb
public override PersonalInfo7GG PersonalInfo => PersonalTable.GG.GetFormEntry(Species, Form);
public PB7() : base(SIZE) { }
public PB7(byte[] data) : base(DecryptParty(data)) { }
public PB7(Memory<byte> data) : base(DecryptParty(data)) { }
private static byte[] DecryptParty(byte[] data)
private static Memory<byte> DecryptParty(Memory<byte> data)
{
PokeCrypto.DecryptIfEncrypted67(ref data);
if (data.Length != SIZE)
Array.Resize(ref data, SIZE);
return data;
if (data.Length >= SIZE)
return data;
var result = new byte[SIZE];
data.Span.CopyTo(result);
return result;
}
public override PB7 Clone() => new((byte[])Data.Clone());
public override PB7 Clone() => new(Data.ToArray());
// Structure
#region Block A
public override uint EncryptionConstant
{
get => ReadUInt32LittleEndian(Data.AsSpan(0x00));
set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value);
get => ReadUInt32LittleEndian(Data);
set => WriteUInt32LittleEndian(Data, value);
}
public override ushort Sanity
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x04));
set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value);
get => ReadUInt16LittleEndian(Data[0x04..]);
set => WriteUInt16LittleEndian(Data[0x04..], value);
}
public override ushort Checksum
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x06));
set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value);
get => ReadUInt16LittleEndian(Data[0x06..]);
set => WriteUInt16LittleEndian(Data[0x06..], value);
}
public override ushort Species
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x08));
set => WriteUInt16LittleEndian(Data.AsSpan(0x08), value);
get => ReadUInt16LittleEndian(Data[0x08..]);
set => WriteUInt16LittleEndian(Data[0x08..], value);
}
public override int HeldItem
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x0A));
set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value);
get => ReadUInt16LittleEndian(Data[0x0A..]);
set => WriteUInt16LittleEndian(Data[0x0A..], (ushort)value);
}
public override uint ID32
{
get => ReadUInt32LittleEndian(Data.AsSpan(0x0C));
set => WriteUInt32LittleEndian(Data.AsSpan(0x0C), value);
get => ReadUInt32LittleEndian(Data[0x0C..]);
set => WriteUInt32LittleEndian(Data[0x0C..], value);
}
public override ushort TID16
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x0C));
set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), value);
get => ReadUInt16LittleEndian(Data[0x0C..]);
set => WriteUInt16LittleEndian(Data[0x0C..], value);
}
public override ushort SID16
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x0E));
set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), value);
get => ReadUInt16LittleEndian(Data[0x0E..]);
set => WriteUInt16LittleEndian(Data[0x0E..], value);
}
public override uint EXP
{
get => ReadUInt32LittleEndian(Data.AsSpan(0x10));
set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value);
get => ReadUInt32LittleEndian(Data[0x10..]);
set => WriteUInt32LittleEndian(Data[0x10..], value);
}
public override int Ability { get => Data[0x14]; set => Data[0x14] = (byte)value; }
public override int AbilityNumber { get => Data[0x15] & 7; set => Data[0x15] = (byte)((Data[0x15] & ~7) | (value & 7)); }
public bool IsFavorite { get => (Data[0x15] & 8) != 0; set => Data[0x15] = (byte)((Data[0x15] & ~8) | ((value ? 1 : 0) << 3)); }
public ushort MarkingValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x16)); set => WriteUInt16LittleEndian(Data.AsSpan(0x16), value); }
public ushort MarkingValue { get => ReadUInt16LittleEndian(Data[0x16..]); set => WriteUInt16LittleEndian(Data[0x16..], value); }
public override uint PID
{
get => ReadUInt32LittleEndian(Data.AsSpan(0x18));
set => WriteUInt32LittleEndian(Data.AsSpan(0x18), value);
get => ReadUInt32LittleEndian(Data[0x18..]);
set => WriteUInt32LittleEndian(Data[0x18..], value);
}
public override Nature Nature { get => (Nature)Data[0x1C]; set => Data[0x1C] = (byte)value; }
@@ -129,7 +132,7 @@ public override uint PID
public byte PokerusState { get => Data[0x2B]; set => Data[0x2B] = value; }
public override int PokerusDays { get => PokerusState & 0xF; set => PokerusState = (byte)((PokerusState & ~0xF) | value); }
public override int PokerusStrain { get => PokerusState >> 4; set => PokerusState = (byte)((PokerusState & 0xF) | (value << 4)); }
public float HeightAbsolute { get => ReadSingleLittleEndian(Data.AsSpan(0x2C)); set => WriteSingleLittleEndian(Data.AsSpan(0x2C), value); }
public float HeightAbsolute { get => ReadSingleLittleEndian(Data[0x2C..]); set => WriteSingleLittleEndian(Data[0x2C..], value); }
private byte RIB0 { get => Data[0x30]; set => Data[0x30] = value; } // Ribbons are read as uints, but let's keep them per byte.
private byte RIB1 { get => Data[0x31]; set => Data[0x31] = value; }
private byte RIB2 { get => Data[0x32]; set => Data[0x32] = value; }
@@ -201,12 +204,12 @@ public override uint PID
// 0x39 Unused
public byte HeightScalar { get => Data[0x3A]; set => Data[0x3A] = value; }
public byte WeightScalar { get => Data[0x3B]; set => Data[0x3B] = value; }
public uint FormArgument { get => ReadUInt32LittleEndian(Data.AsSpan(0x3C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x3C), value); }
public uint FormArgument { get => ReadUInt32LittleEndian(Data[0x3C..]); set => WriteUInt32LittleEndian(Data[0x3C..], value); }
public byte FormArgumentRemain { get => (byte)FormArgument; set => FormArgument = (FormArgument & ~0xFFu) | value; }
public byte FormArgumentElapsed { get => (byte)(FormArgument >> 8); set => FormArgument = (FormArgument & ~0xFF00u) | (uint)(value << 8); }
public byte FormArgumentMaximum { get => (byte)(FormArgument >> 16); set => FormArgument = (FormArgument & ~0xFF0000u) | (uint)(value << 16); }
public int RibbonCount => BitOperations.PopCount(ReadUInt64LittleEndian(Data.AsSpan(0x30)) & 0b00000000_00000011__11111111_11111111__11111111_11111111__11111111_11111111);
public int RibbonCount => BitOperations.PopCount(ReadUInt64LittleEndian(Data[0x30..]) & 0b00000000_00000011__11111111_11111111__11111111_11111111__11111111_11111111);
#endregion
#region Block B
@@ -218,26 +221,26 @@ public override string Nickname
public override ushort Move1
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x5A));
set => WriteUInt16LittleEndian(Data.AsSpan(0x5A), value);
get => ReadUInt16LittleEndian(Data[0x5A..]);
set => WriteUInt16LittleEndian(Data[0x5A..], value);
}
public override ushort Move2
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x5C));
set => WriteUInt16LittleEndian(Data.AsSpan(0x5C), value);
get => ReadUInt16LittleEndian(Data[0x5C..]);
set => WriteUInt16LittleEndian(Data[0x5C..], value);
}
public override ushort Move3
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x5E));
set => WriteUInt16LittleEndian(Data.AsSpan(0x5E), value);
get => ReadUInt16LittleEndian(Data[0x5E..]);
set => WriteUInt16LittleEndian(Data[0x5E..], value);
}
public override ushort Move4
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x60));
set => WriteUInt16LittleEndian(Data.AsSpan(0x60), value);
get => ReadUInt16LittleEndian(Data[0x60..]);
set => WriteUInt16LittleEndian(Data[0x60..], value);
}
public override int Move1_PP { get => Data[0x62]; set => Data[0x62] = (byte)value; }
@@ -251,31 +254,31 @@ public override ushort Move4
public override ushort RelearnMove1
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x6A));
set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), value);
get => ReadUInt16LittleEndian(Data[0x6A..]);
set => WriteUInt16LittleEndian(Data[0x6A..], value);
}
public override ushort RelearnMove2
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x6C));
set => WriteUInt16LittleEndian(Data.AsSpan(0x6C), value);
get => ReadUInt16LittleEndian(Data[0x6C..]);
set => WriteUInt16LittleEndian(Data[0x6C..], value);
}
public override ushort RelearnMove3
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x6E));
set => WriteUInt16LittleEndian(Data.AsSpan(0x6E), value);
get => ReadUInt16LittleEndian(Data[0x6E..]);
set => WriteUInt16LittleEndian(Data[0x6E..], value);
}
public override ushort RelearnMove4
{
get => ReadUInt16LittleEndian(Data.AsSpan(0x70));
set => WriteUInt16LittleEndian(Data.AsSpan(0x70), value);
get => ReadUInt16LittleEndian(Data[0x70..]);
set => WriteUInt16LittleEndian(Data[0x70..], value);
}
// 0x72 Unused
// 0x73 Unused
public override uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x74)); set => WriteUInt32LittleEndian(Data.AsSpan(0x74), value); }
public override uint IV32 { get => ReadUInt32LittleEndian(Data[0x74..]); set => WriteUInt32LittleEndian(Data[0x74..], value); }
public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); }
public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); }
public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); }
@@ -314,7 +317,7 @@ public override string HandlingTrainerName
public byte HT_Memory { get => Data[0xA5]; set => Data[0xA5] = value; }
public byte HT_Feeling { get => Data[0xA6]; set => Data[0xA6] = value; }
// 0xA7 Unused
public ushort HT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xA8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA8), value); }
public ushort HT_TextVar { get => ReadUInt16LittleEndian(Data[0xA8..]); set => WriteUInt16LittleEndian(Data[0xA8..], value); }
// 0xAA Unused
// 0xAB Unused
public byte FieldEventFatigue1 { get => Data[0xAC]; set => Data[0xAC] = value; }
@@ -401,8 +404,8 @@ public override string OriginalTrainerName
public override byte MetMonth { get => Data[0xD5]; set => Data[0xD5] = value; }
public override byte MetDay { get => Data[0xD6]; set => Data[0xD6] = value; }
public int Rank { get => Data[0xD7]; set => Data[0xD7] = (byte)value; } // unused but fetched for stat calcs, and set for trpoke data?
public override ushort EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0xD8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xD8), value); }
public override ushort MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0xDA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDA), value); }
public override ushort EggLocation { get => ReadUInt16LittleEndian(Data[0xD8..]); set => WriteUInt16LittleEndian(Data[0xD8..], value); }
public override ushort MetLocation { get => ReadUInt16LittleEndian(Data[0xDA..]); set => WriteUInt16LittleEndian(Data[0xDA..], value); }
public override byte Ball { get => Data[0xDC]; set => Data[0xDC] = value; }
public override byte MetLevel { get => (byte)(Data[0xDD] & ~0x80); set => Data[0xDD] = (byte)((Data[0xDD] & 0x80) | value); }
public override byte OriginalTrainerGender { get => (byte)(Data[0xDD] >> 7); set => Data[0xDD] = (byte)((Data[0xDD] & ~0x80) | (value << 7)); }
@@ -418,22 +421,22 @@ public override string OriginalTrainerName
// 0xE1 Unused
// 0xE2 Unused
public override int Language { get => Data[0xE3]; set => Data[0xE3] = (byte)value; }
public float WeightAbsolute { get => ReadSingleLittleEndian(Data.AsSpan(0xE4)); set => WriteSingleLittleEndian(Data.AsSpan(0xE4), value); }
public float WeightAbsolute { get => ReadSingleLittleEndian(Data[0xE4..]); set => WriteSingleLittleEndian(Data[0xE4..], value); }
#endregion
#region Battle Stats
public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0xE8)); set => WriteInt32LittleEndian(Data.AsSpan(0xE8), value); }
public override int Status_Condition { get => ReadInt32LittleEndian(Data[0xE8..]); set => WriteInt32LittleEndian(Data[0xE8..], value); }
public override byte Stat_Level { get => Data[0xEC]; set => Data[0xEC] = value; }
public byte DirtType { get => Data[0xED]; set => Data[0xED] = value; }
public byte DirtLocation { get => Data[0xEE]; set => Data[0xEE] = value; }
// 0xEF unused
public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0xF0)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF0), (ushort)value); }
public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0xF2)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF2), (ushort)value); }
public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0xF4)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF4), (ushort)value); }
public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0xF6)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF6), (ushort)value); }
public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0xF8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF8), (ushort)value); }
public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0xFA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFA), (ushort)value); }
public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0xFC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFC), (ushort)value); }
public int Stat_CP { get => ReadUInt16LittleEndian(Data.AsSpan(0xFE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFE), (ushort)value); }
public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data[0xF0..]); set => WriteUInt16LittleEndian(Data[0xF0..], (ushort)value); }
public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data[0xF2..]); set => WriteUInt16LittleEndian(Data[0xF2..], (ushort)value); }
public override int Stat_ATK { get => ReadUInt16LittleEndian(Data[0xF4..]); set => WriteUInt16LittleEndian(Data[0xF4..], (ushort)value); }
public override int Stat_DEF { get => ReadUInt16LittleEndian(Data[0xF6..]); set => WriteUInt16LittleEndian(Data[0xF6..], (ushort)value); }
public override int Stat_SPE { get => ReadUInt16LittleEndian(Data[0xF8..]); set => WriteUInt16LittleEndian(Data[0xF8..], (ushort)value); }
public override int Stat_SPA { get => ReadUInt16LittleEndian(Data[0xFA..]); set => WriteUInt16LittleEndian(Data[0xFA..], (ushort)value); }
public override int Stat_SPD { get => ReadUInt16LittleEndian(Data[0xFC..]); set => WriteUInt16LittleEndian(Data[0xFC..], (ushort)value); }
public int Stat_CP { get => ReadUInt16LittleEndian(Data[0xFE..]); set => WriteUInt16LittleEndian(Data[0xFE..], (ushort)value); }
public bool Stat_Mega { get => Data[0x100] != 0; set => Data[0x100] = value ? (byte)1 : (byte)0; }
public int Stat_MegaForm { get => Data[0x101]; set => Data[0x101] = (byte)value; }
// 102/103 unused

View File

@@ -35,8 +35,8 @@ public PB8()
AffixedRibbon = Core.AffixedRibbon.None;
}
public PB8(byte[] data) : base(data) { }
public override PB8 Clone() => new((byte[])Data.Clone());
public PB8(Memory<byte> data) : base(data) { }
public override PB8 Clone() => new(Data.ToArray());
public bool IsDprIllegal
{

View File

@@ -36,7 +36,7 @@ private static byte[] EnsurePartySize(byte[] data)
public override PK1 Clone()
{
PK1 clone = new((byte[])Data.Clone(), Japanese);
PK1 clone = new(Data.ToArray(), Japanese);
OriginalTrainerTrash.CopyTo(clone.OriginalTrainerTrash);
NicknameTrash.CopyTo(clone.NicknameTrash);
return clone;
@@ -47,7 +47,7 @@ public override PK1 Clone()
#region Stored Attributes
public byte SpeciesInternal { get => Data[0]; set => Data[0] = value; } // raw access
public override ushort Species { get => SpeciesConverter.GetNational1(SpeciesInternal); set => SetSpeciesValues(value); }
public override int Stat_HPCurrent { get => ReadUInt16BigEndian(Data.AsSpan(0x1)); set => WriteUInt16BigEndian(Data.AsSpan(0x1), (ushort)value); }
public override int Stat_HPCurrent { get => ReadUInt16BigEndian(Data[0x1..]); set => WriteUInt16BigEndian(Data[0x1..], (ushort)value); }
public int Stat_LevelBox { get => Data[3]; set => Data[3] = (byte)value; }
public override int Status_Condition { get => Data[4]; set => Data[4] = (byte)value; }
public byte Type1 { get => Data[5]; set => Data[5] = value; }
@@ -57,14 +57,14 @@ public override PK1 Clone()
public override ushort Move2 { get => Data[9]; set => Data[9] = (byte)value; }
public override ushort Move3 { get => Data[10]; set => Data[10] = (byte)value; }
public override ushort Move4 { get => Data[11]; set => Data[11] = (byte)value; }
public override ushort TID16 { get => ReadUInt16BigEndian(Data.AsSpan(0xC)); set => WriteUInt16BigEndian(Data.AsSpan(0xC), value); }
public override uint EXP { get => ReadUInt32BigEndian(Data.AsSpan(0xE)) >> 8; set => WriteUInt32BigEndian(Data.AsSpan(0xE), (value << 8) | Data[0x11]); }
public override int EV_HP { get => ReadUInt16BigEndian(Data.AsSpan(0x11)); set => WriteUInt16BigEndian(Data.AsSpan(0x11), (ushort)value); }
public override int EV_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x13)); set => WriteUInt16BigEndian(Data.AsSpan(0x13), (ushort)value); }
public override int EV_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x15)); set => WriteUInt16BigEndian(Data.AsSpan(0x15), (ushort)value); }
public override int EV_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x17)); set => WriteUInt16BigEndian(Data.AsSpan(0x17), (ushort)value); }
public override int EV_SPC { get => ReadUInt16BigEndian(Data.AsSpan(0x19)); set => WriteUInt16BigEndian(Data.AsSpan(0x19), (ushort)value); }
public override ushort DV16 { get => ReadUInt16BigEndian(Data.AsSpan(0x1B)); set => WriteUInt16BigEndian(Data.AsSpan(0x1B), value); }
public override ushort TID16 { get => ReadUInt16BigEndian(Data[0xC..]); set => WriteUInt16BigEndian(Data[0xC..], value); }
public override uint EXP { get => ReadUInt32BigEndian(Data[0xE..]) >> 8; set => WriteUInt32BigEndian(Data[0xE..], (value << 8) | Data[0x11]); }
public override int EV_HP { get => ReadUInt16BigEndian(Data[0x11..]); set => WriteUInt16BigEndian(Data[0x11..], (ushort)value); }
public override int EV_ATK { get => ReadUInt16BigEndian(Data[0x13..]); set => WriteUInt16BigEndian(Data[0x13..], (ushort)value); }
public override int EV_DEF { get => ReadUInt16BigEndian(Data[0x15..]); set => WriteUInt16BigEndian(Data[0x15..], (ushort)value); }
public override int EV_SPE { get => ReadUInt16BigEndian(Data[0x17..]); set => WriteUInt16BigEndian(Data[0x17..], (ushort)value); }
public override int EV_SPC { get => ReadUInt16BigEndian(Data[0x19..]); set => WriteUInt16BigEndian(Data[0x19..], (ushort)value); }
public override ushort DV16 { get => ReadUInt16BigEndian(Data[0x1B..]); set => WriteUInt16BigEndian(Data[0x1B..], value); }
public override int Move1_PP { get => Data[0x1D] & 0x3F; set => Data[0x1D] = (byte)((Data[0x1D] & 0xC0) | Math.Min(63, value)); }
public override int Move2_PP { get => Data[0x1E] & 0x3F; set => Data[0x1E] = (byte)((Data[0x1E] & 0xC0) | Math.Min(63, value)); }
public override int Move3_PP { get => Data[0x1F] & 0x3F; set => Data[0x1F] = (byte)((Data[0x1F] & 0xC0) | Math.Min(63, value)); }
@@ -77,11 +77,11 @@ public override PK1 Clone()
#region Party Attributes
public override byte Stat_Level { get => Data[0x21]; set => Stat_LevelBox = Data[0x21] = value; }
public override int Stat_HPMax { get => ReadUInt16BigEndian(Data.AsSpan(0x22)); set => WriteUInt16BigEndian(Data.AsSpan(0x22), (ushort)value); }
public override int Stat_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x24)); set => WriteUInt16BigEndian(Data.AsSpan(0x24), (ushort)value); }
public override int Stat_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x26)); set => WriteUInt16BigEndian(Data.AsSpan(0x26), (ushort)value); }
public override int Stat_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x28)); set => WriteUInt16BigEndian(Data.AsSpan(0x28), (ushort)value); }
public int Stat_SPC { get => ReadUInt16BigEndian(Data.AsSpan(0x2A)); set => WriteUInt16BigEndian(Data.AsSpan(0x2A), (ushort)value); }
public override int Stat_HPMax { get => ReadUInt16BigEndian(Data[0x22..]); set => WriteUInt16BigEndian(Data[0x22..], (ushort)value); }
public override int Stat_ATK { get => ReadUInt16BigEndian(Data[0x24..]); set => WriteUInt16BigEndian(Data[0x24..], (ushort)value); }
public override int Stat_DEF { get => ReadUInt16BigEndian(Data[0x26..]); set => WriteUInt16BigEndian(Data[0x26..], (ushort)value); }
public override int Stat_SPE { get => ReadUInt16BigEndian(Data[0x28..]); set => WriteUInt16BigEndian(Data[0x28..], (ushort)value); }
public int Stat_SPC { get => ReadUInt16BigEndian(Data[0x2A..]); set => WriteUInt16BigEndian(Data[0x2A..], (ushort)value); }
// Leave SPA and SPD as alias for SPC
public override int Stat_SPA { get => Stat_SPC; set => Stat_SPC = value; }
public override int Stat_SPD { get => Stat_SPC; set { } }
@@ -108,8 +108,7 @@ private void SetSpeciesValues(ushort species)
SpeciesInternal = internalID;
var pi = PersonalTable.RB[species];
Type1 = pi.Type1;
Type2 = pi.Type2;
SetTypes(pi);
// Before updating catch rate, check if non-standard
if (IsValidCatchRateAnyPreEvo((byte)species, CatchRate))
@@ -119,6 +118,12 @@ private void SetSpeciesValues(ushort species)
CatchRate = pi.CatchRate;
}
public void SetTypes<T>(T pi) where T : IPersonalType
{
Type1 = pi.Type1;
Type2 = pi.Type2;
}
private static bool IsValidCatchRateAnyPreEvo(byte species, byte rate)
{
if (IsCatchRateHeldItem(rate))
@@ -158,7 +163,7 @@ private static bool IsValidCatchRateAnyPreEvo(byte species, byte rate)
public PK2 ConvertToPK2()
{
PK2 pk2 = new(Japanese) {Species = Species};
Data.AsSpan(7, 0x1A).CopyTo(pk2.Data.AsSpan(1));
Data.Slice(7, 0x1A).CopyTo(pk2.Data[1..]);
OriginalTrainerTrash.CopyTo(pk2.OriginalTrainerTrash);
NicknameTrash.CopyTo(pk2.NicknameTrash);

View File

@@ -36,7 +36,7 @@ private static byte[] EnsurePartySize(byte[] data)
public override PK2 Clone()
{
PK2 clone = new((byte[])Data.Clone(), Japanese) { IsEgg = IsEgg };
PK2 clone = new(Data.ToArray(), Japanese) { IsEgg = IsEgg };
OriginalTrainerTrash.CopyTo(clone.OriginalTrainerTrash);
NicknameTrash.CopyTo(clone.NicknameTrash);
return clone;
@@ -48,19 +48,19 @@ public override PK2 Clone()
public override ushort Species { get => Data[0]; set => Data[0] = (byte)value; }
public byte SpeciesInternal { get => Data[0]; set => Data[0] = value; } // Alias with a different type.
public override int SpriteItem => ItemConverter.GetItemFuture2((byte)HeldItem);
public override int HeldItem { get => Data[0x1]; set => Data[0x1] = (byte)value; }
public override int HeldItem { get => Data[1]; set => Data[1] = (byte)value; }
public override ushort Move1 { get => Data[2]; set => Data[2] = (byte)value; }
public override ushort Move2 { get => Data[3]; set => Data[3] = (byte)value; }
public override ushort Move3 { get => Data[4]; set => Data[4] = (byte)value; }
public override ushort Move4 { get => Data[5]; set => Data[5] = (byte)value; }
public override ushort TID16 { get => ReadUInt16BigEndian(Data.AsSpan(6)); set => WriteUInt16BigEndian(Data.AsSpan(6), value); }
public override uint EXP { get => ReadUInt32BigEndian(Data.AsSpan(0x08)) >> 8; set => WriteUInt32BigEndian(Data.AsSpan(8), (value << 8) | Data[0xB]); }
public override int EV_HP { get => ReadUInt16BigEndian(Data.AsSpan(0x0B)); set => WriteUInt16BigEndian(Data.AsSpan(0xB), (ushort)value); }
public override int EV_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x0D)); set => WriteUInt16BigEndian(Data.AsSpan(0xD), (ushort)value); }
public override int EV_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x0F)); set => WriteUInt16BigEndian(Data.AsSpan(0xF), (ushort)value); }
public override int EV_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x11)); set => WriteUInt16BigEndian(Data.AsSpan(0x11), (ushort)value); }
public override int EV_SPC { get => ReadUInt16BigEndian(Data.AsSpan(0x13)); set => WriteUInt16BigEndian(Data.AsSpan(0x13), (ushort)value); }
public override ushort DV16 { get => ReadUInt16BigEndian(Data.AsSpan(0x15)); set => WriteUInt16BigEndian(Data.AsSpan(0x15), value); }
public override ushort TID16 { get => ReadUInt16BigEndian(Data[6..]); set => WriteUInt16BigEndian(Data[6..], value); }
public override uint EXP { get => ReadUInt32BigEndian(Data[0x08..]) >> 8; set => WriteUInt32BigEndian(Data[8..], (value << 8) | Data[0xB]); }
public override int EV_HP { get => ReadUInt16BigEndian(Data[0x0B..]); set => WriteUInt16BigEndian(Data[0xB..], (ushort)value); }
public override int EV_ATK { get => ReadUInt16BigEndian(Data[0x0D..]); set => WriteUInt16BigEndian(Data[0xD..], (ushort)value); }
public override int EV_DEF { get => ReadUInt16BigEndian(Data[0x0F..]); set => WriteUInt16BigEndian(Data[0xF..], (ushort)value); }
public override int EV_SPE { get => ReadUInt16BigEndian(Data[0x11..]); set => WriteUInt16BigEndian(Data[0x11..], (ushort)value); }
public override int EV_SPC { get => ReadUInt16BigEndian(Data[0x13..]); set => WriteUInt16BigEndian(Data[0x13..], (ushort)value); }
public override ushort DV16 { get => ReadUInt16BigEndian(Data[0x15..]); set => WriteUInt16BigEndian(Data[0x15..], value); }
public override int Move1_PP { get => Data[0x17] & 0x3F; set => Data[0x17] = (byte)((Data[0x17] & 0xC0) | Math.Min(63, value)); }
public override int Move2_PP { get => Data[0x18] & 0x3F; set => Data[0x18] = (byte)((Data[0x18] & 0xC0) | Math.Min(63, value)); }
public override int Move3_PP { get => Data[0x19] & 0x3F; set => Data[0x19] = (byte)((Data[0x19] & 0xC0) | Math.Min(63, value)); }
@@ -74,7 +74,7 @@ public override PK2 Clone()
public override int PokerusDays { get => PokerusState & 0xF; set => PokerusState = (byte)((PokerusState & ~0xF) | value); }
public override int PokerusStrain { get => PokerusState >> 4; set => PokerusState = (byte)((PokerusState & 0xF) | (value << 4)); }
// Crystal only Caught Data
public ushort CaughtData { get => ReadUInt16BigEndian(Data.AsSpan(0x1D)); set => WriteUInt16BigEndian(Data.AsSpan(0x1D), value); }
public ushort CaughtData { get => ReadUInt16BigEndian(Data[0x1D..]); set => WriteUInt16BigEndian(Data[0x1D..], value); }
public int MetTimeOfDay { get => (CaughtData >> 14) & 0x3; set => CaughtData = (ushort)((CaughtData & 0x3FFF) | ((value & 0x3) << 14)); }
public override byte MetLevel { get => (byte)((CaughtData >> 8) & 0x3F); set => CaughtData = (ushort)((CaughtData & 0xC0FF) | ((value & 0x3F) << 8)); }
public override byte OriginalTrainerGender { get => (byte)((CaughtData >> 7) & 1); set => CaughtData = (ushort)((CaughtData & 0xFF7F) | ((value & 1) << 7)); }
@@ -91,13 +91,13 @@ public override byte Stat_Level
#region Party Attributes
public override int Status_Condition { get => Data[0x20]; set => Data[0x20] = (byte)value; }
public override int Stat_HPCurrent { get => ReadUInt16BigEndian(Data.AsSpan(0x22)); set => WriteUInt16BigEndian(Data.AsSpan(0x22), (ushort)value); }
public override int Stat_HPMax { get => ReadUInt16BigEndian(Data.AsSpan(0x24)); set => WriteUInt16BigEndian(Data.AsSpan(0x24), (ushort)value); }
public override int Stat_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x26)); set => WriteUInt16BigEndian(Data.AsSpan(0x26), (ushort)value); }
public override int Stat_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x28)); set => WriteUInt16BigEndian(Data.AsSpan(0x28), (ushort)value); }
public override int Stat_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x2A)); set => WriteUInt16BigEndian(Data.AsSpan(0x2A), (ushort)value); }
public override int Stat_SPA { get => ReadUInt16BigEndian(Data.AsSpan(0x2C)); set => WriteUInt16BigEndian(Data.AsSpan(0x2C), (ushort)value); }
public override int Stat_SPD { get => ReadUInt16BigEndian(Data.AsSpan(0x2E)); set => WriteUInt16BigEndian(Data.AsSpan(0x2E), (ushort)value); }
public override int Stat_HPCurrent { get => ReadUInt16BigEndian(Data[0x22..]); set => WriteUInt16BigEndian(Data[0x22..], (ushort)value); }
public override int Stat_HPMax { get => ReadUInt16BigEndian(Data[0x24..]); set => WriteUInt16BigEndian(Data[0x24..], (ushort)value); }
public override int Stat_ATK { get => ReadUInt16BigEndian(Data[0x26..]); set => WriteUInt16BigEndian(Data[0x26..], (ushort)value); }
public override int Stat_DEF { get => ReadUInt16BigEndian(Data[0x28..]); set => WriteUInt16BigEndian(Data[0x28..], (ushort)value); }
public override int Stat_SPE { get => ReadUInt16BigEndian(Data[0x2A..]); set => WriteUInt16BigEndian(Data[0x2A..], (ushort)value); }
public override int Stat_SPA { get => ReadUInt16BigEndian(Data[0x2C..]); set => WriteUInt16BigEndian(Data[0x2C..], (ushort)value); }
public override int Stat_SPD { get => ReadUInt16BigEndian(Data[0x2E..]); set => WriteUInt16BigEndian(Data[0x2E..], (ushort)value); }
#endregion
public override bool IsEgg { get; set; }
@@ -114,7 +114,8 @@ public override byte Stat_Level
public PK1 ConvertToPK1()
{
PK1 pk1 = new(Japanese);
Array.Copy(Data, 0x1, pk1.Data, 0x7, 0x1A);
var dest = pk1.Data.Slice(0x7, 0x1A);
Data[1..].CopyTo(dest);
pk1.Species = Species; // This will take care of Typing :)
var lvl = Stat_Level;

View File

@@ -15,13 +15,17 @@ public sealed class PK3 : G3PKM, ISanityChecksum
public override PersonalInfo3 PersonalInfo => PersonalTable.RS[Species];
public PK3() : base(PokeCrypto.SIZE_3PARTY) { }
public PK3(byte[] data) : base(DecryptParty(data)) { }
public PK3(Memory<byte> data) : base(DecryptParty(data)) { }
private static byte[] DecryptParty(byte[] data)
private static Memory<byte> DecryptParty(Memory<byte> data)
{
PokeCrypto.DecryptIfEncrypted3(ref data);
Array.Resize(ref data, PokeCrypto.SIZE_3PARTY);
return data;
if (data.Length >= PokeCrypto.SIZE_3PARTY)
return data;
var result = new byte[PokeCrypto.SIZE_3PARTY];
data.Span.CopyTo(result);
return result;
}
public override PK3 Clone()
@@ -29,25 +33,25 @@ public override PK3 Clone()
// Don't use the byte[] constructor, the DecryptIfEncrypted call is based on checksum.
// An invalid checksum will shuffle the data; we already know it's un-shuffled. Set up manually.
PK3 pk = new();
Data.CopyTo(pk.Data, 0);
Data.CopyTo(pk.Data);
return pk;
}
private const string EggNameJapanese = "タマゴ";
// Trash Bytes
public override Span<byte> NicknameTrash => Data.AsSpan(0x08, 10); // no inaccessible terminator
public override Span<byte> OriginalTrainerTrash => Data.AsSpan(0x14, 7); // no inaccessible terminator
public override Span<byte> NicknameTrash => Data.Slice(0x08, 10); // no inaccessible terminator
public override Span<byte> OriginalTrainerTrash => Data.Slice(0x14, 7); // no inaccessible terminator
public override int TrashCharCountTrainer => 7;
public override int TrashCharCountNickname => 10;
// At top for System.Reflection execution order hack
// 0x20 Intro
public override uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); }
public override uint ID32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x04)); set => WriteUInt32LittleEndian(Data.AsSpan(0x04), value); }
public override ushort TID16 { get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); }
public override ushort SID16 { get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); }
public override uint PID { get => ReadUInt32LittleEndian(Data); set => WriteUInt32LittleEndian(Data, value); }
public override uint ID32 { get => ReadUInt32LittleEndian(Data[0x04..]); set => WriteUInt32LittleEndian(Data[0x04..], value); }
public override ushort TID16 { get => ReadUInt16LittleEndian(Data[0x04..]); set => WriteUInt16LittleEndian(Data[0x04..], value); }
public override ushort SID16 { get => ReadUInt16LittleEndian(Data[0x06..]); set => WriteUInt16LittleEndian(Data[0x06..], value); }
public override string Nickname
{
get => StringConverter3.GetString(NicknameTrash, Language);
@@ -63,11 +67,11 @@ public override string OriginalTrainerName
set => StringConverter3.SetString(OriginalTrainerTrash, value, 7, Language, StringConverterOption.None);
}
public override byte MarkingValue { get => (byte)SwapBits(Data[0x1B], 1, 2); set => Data[0x1B] = (byte)SwapBits(value, 1, 2); }
public ushort Checksum { get => ReadUInt16LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1C), value); }
public ushort Sanity { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), value); }
public ushort Checksum { get => ReadUInt16LittleEndian(Data[0x1C..]); set => WriteUInt16LittleEndian(Data[0x1C..], value); }
public ushort Sanity { get => ReadUInt16LittleEndian(Data[0x1E..]); set => WriteUInt16LittleEndian(Data[0x1E..], value); }
#region Block A
public override ushort SpeciesInternal { get => ReadUInt16LittleEndian(Data.AsSpan(0x20)); set => WriteUInt16LittleEndian(Data.AsSpan(0x20), value); } // raw access
public override ushort SpeciesInternal { get => ReadUInt16LittleEndian(Data[0x20..]); set => WriteUInt16LittleEndian(Data[0x20..], value); } // raw access
public override ushort Species
{
@@ -80,9 +84,9 @@ public override ushort Species
}
public override int SpriteItem => ItemConverter.GetItemFuture3((ushort)HeldItem);
public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); }
public override int HeldItem { get => ReadUInt16LittleEndian(Data[0x22..]); set => WriteUInt16LittleEndian(Data[0x22..], (ushort)value); }
public override uint EXP { get => ReadUInt32LittleEndian(Data.AsSpan(0x24)); set => WriteUInt32LittleEndian(Data.AsSpan(0x24), value); }
public override uint EXP { get => ReadUInt32LittleEndian(Data[0x24..]); set => WriteUInt32LittleEndian(Data[0x24..], value); }
private byte PPUps { get => Data[0x28]; set => Data[0x28] = value; }
public override int Move1_PPUps { get => (PPUps >> 0) & 3; set => PPUps = (byte)((PPUps & ~(3 << 0)) | (value << 0)); }
public override int Move2_PPUps { get => (PPUps >> 2) & 3; set => PPUps = (byte)((PPUps & ~(3 << 2)) | (value << 2)); }
@@ -93,10 +97,10 @@ public override ushort Species
#endregion
#region Block B
public override ushort Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2C), value); }
public override ushort Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2E), value); }
public override ushort Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x30)); set => WriteUInt16LittleEndian(Data.AsSpan(0x30), value); }
public override ushort Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x32)); set => WriteUInt16LittleEndian(Data.AsSpan(0x32), value); }
public override ushort Move1 { get => ReadUInt16LittleEndian(Data[0x2C..]); set => WriteUInt16LittleEndian(Data[0x2C..], value); }
public override ushort Move2 { get => ReadUInt16LittleEndian(Data[0x2E..]); set => WriteUInt16LittleEndian(Data[0x2E..], value); }
public override ushort Move3 { get => ReadUInt16LittleEndian(Data[0x30..]); set => WriteUInt16LittleEndian(Data[0x30..], value); }
public override ushort Move4 { get => ReadUInt16LittleEndian(Data[0x32..]); set => WriteUInt16LittleEndian(Data[0x32..], value); }
public override int Move1_PP { get => Data[0x34]; set => Data[0x34] = (byte)value; }
public override int Move2_PP { get => Data[0x35]; set => Data[0x35] = (byte)value; }
public override int Move3_PP { get => Data[0x36]; set => Data[0x36] = (byte)value; }
@@ -124,13 +128,13 @@ public override ushort Species
public override int PokerusStrain { get => PokerusState >> 4; set => PokerusState = (byte)((PokerusState & 0xF) | (value << 4)); }
public override ushort MetLocation { get => Data[0x45]; set => Data[0x45] = (byte)value; }
// Origins
private ushort Origins { get => ReadUInt16LittleEndian(Data.AsSpan(0x46)); set => WriteUInt16LittleEndian(Data.AsSpan(0x46), value); }
private ushort Origins { get => ReadUInt16LittleEndian(Data[0x46..]); set => WriteUInt16LittleEndian(Data[0x46..], value); }
public override byte MetLevel { get => (byte)(Origins & 0x7F); set => Origins = (ushort)((Origins & ~0x7F) | value); }
public override GameVersion Version { get => (GameVersion)((Origins >> 7) & 0xF); set => Origins = (ushort)((Origins & ~0x780) | (((byte)value & 0xF) << 7)); }
public override byte Ball { get => (byte)((Origins >> 11) & 0xF); set => Origins = (ushort)((Origins & ~0x7800) | ((value & 0xF) << 11)); }
public override byte OriginalTrainerGender { get => (byte)((Origins >> 15) & 1); set => Origins = (ushort)((Origins & ~(1 << 15)) | ((value & 1) << 15)); }
public uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x48)); set => WriteUInt32LittleEndian(Data.AsSpan(0x48), value); }
public uint IV32 { get => ReadUInt32LittleEndian(Data[0x48..]); set => WriteUInt32LittleEndian(Data[0x48..], value); }
public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); }
public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); }
public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); }
@@ -155,7 +159,7 @@ public override bool IsEgg
public override bool AbilityBit { get => IV32 >> 31 == 1; set => IV32 = (IV32 & 0x7FFFFFFF) | (value ? 1u << 31 : 0u); }
private uint RIB0 { get => ReadUInt32LittleEndian(Data.AsSpan(0x4C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x4C), value); }
private uint RIB0 { get => ReadUInt32LittleEndian(Data[0x4C..]); set => WriteUInt32LittleEndian(Data[0x4C..], value); }
public override byte RibbonCountG3Cool { get => (byte)((RIB0 >> 00) & 7); set => RIB0 = ((RIB0 & ~(7u << 00)) | ((uint)(value & 7) << 00)); }
public override byte RibbonCountG3Beauty { get => (byte)((RIB0 >> 03) & 7); set => RIB0 = ((RIB0 & ~(7u << 03)) | ((uint)(value & 7) << 03)); }
public override byte RibbonCountG3Cute { get => (byte)((RIB0 >> 06) & 7); set => RIB0 = ((RIB0 & ~(7u << 06)) | ((uint)(value & 7) << 06)); }
@@ -183,16 +187,16 @@ public override bool IsEgg
#endregion
#region Battle Stats
public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0x50)); set => WriteInt32LittleEndian(Data.AsSpan(0x50), value); }
public override int Status_Condition { get => ReadInt32LittleEndian(Data[0x50..]); set => WriteInt32LittleEndian(Data[0x50..], value); }
public override byte Stat_Level { get => Data[0x54]; set => Data[0x54] = value; }
public sbyte HeldMailID { get => (sbyte)Data[0x55]; set => Data[0x55] = (byte)value; }
public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0x56)); set => WriteUInt16LittleEndian(Data.AsSpan(0x56), (ushort)value); }
public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0x58)); set => WriteUInt16LittleEndian(Data.AsSpan(0x58), (ushort)value); }
public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0x5A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5A), (ushort)value); }
public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0x5C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5C), (ushort)value); }
public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0x5E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5E), (ushort)value); }
public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0x60)); set => WriteUInt16LittleEndian(Data.AsSpan(0x60), (ushort)value); }
public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0x62)); set => WriteUInt16LittleEndian(Data.AsSpan(0x62), (ushort)value); }
public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data[0x56..]); set => WriteUInt16LittleEndian(Data[0x56..], (ushort)value); }
public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data[0x58..]); set => WriteUInt16LittleEndian(Data[0x58..], (ushort)value); }
public override int Stat_ATK { get => ReadUInt16LittleEndian(Data[0x5A..]); set => WriteUInt16LittleEndian(Data[0x5A..], (ushort)value); }
public override int Stat_DEF { get => ReadUInt16LittleEndian(Data[0x5C..]); set => WriteUInt16LittleEndian(Data[0x5C..], (ushort)value); }
public override int Stat_SPE { get => ReadUInt16LittleEndian(Data[0x5E..]); set => WriteUInt16LittleEndian(Data[0x5E..], (ushort)value); }
public override int Stat_SPA { get => ReadUInt16LittleEndian(Data[0x60..]); set => WriteUInt16LittleEndian(Data[0x60..], (ushort)value); }
public override int Stat_SPD { get => ReadUInt16LittleEndian(Data[0x62..]); set => WriteUInt16LittleEndian(Data[0x62..], (ushort)value); }
#endregion
protected override byte[] Encrypt()
@@ -201,7 +205,7 @@ protected override byte[] Encrypt()
return PokeCrypto.EncryptArray3(Data);
}
private ushort CalculateChecksum() => Checksums.Add16(Data.AsSpan()[0x20..PokeCrypto.SIZE_3STORED]);
private ushort CalculateChecksum() => Checksums.Add16(Data[0x20..PokeCrypto.SIZE_3STORED]);
public override void RefreshChecksum()
{

Some files were not shown because too many files have changed in this diff Show More