From c73264d4f33efefa3f68c1adcb45337e507a2522 Mon Sep 17 00:00:00 2001 From: Kurt Date: Sat, 15 Apr 2023 01:58:37 -0700 Subject: [PATCH] Minor minor perf Small changes to reduce some allocations --- .../Editing/Applicators/BallApplicator.cs | 4 +- PKHeX.Core/Editing/CommonEdits.cs | 4 +- .../Saves/Editors/EventWork/EventWork.cs | 51 ++++++++++++--- PKHeX.Core/Legality/Areas/EncounterArea.cs | 9 +-- PKHeX.Core/Legality/Areas/EncounterArea1.cs | 2 - PKHeX.Core/Legality/Areas/EncounterArea2.cs | 2 - PKHeX.Core/Legality/Areas/EncounterArea3.cs | 2 - PKHeX.Core/Legality/Areas/EncounterArea3XD.cs | 2 - PKHeX.Core/Legality/Areas/EncounterArea4.cs | 2 - PKHeX.Core/Legality/Areas/EncounterArea5.cs | 2 - PKHeX.Core/Legality/Areas/EncounterArea6AO.cs | 14 +++- PKHeX.Core/Legality/Areas/EncounterArea6XY.cs | 14 +++- PKHeX.Core/Legality/Areas/EncounterArea7.cs | 2 - PKHeX.Core/Legality/Areas/EncounterArea7b.cs | 2 - PKHeX.Core/Legality/Areas/EncounterArea7g.cs | 2 - PKHeX.Core/Legality/Areas/EncounterArea8.cs | 13 +++- PKHeX.Core/Legality/Areas/EncounterArea8a.cs | 2 - PKHeX.Core/Legality/Areas/EncounterArea8b.cs | 2 - PKHeX.Core/Legality/Areas/EncounterArea8g.cs | 2 - PKHeX.Core/Legality/Areas/EncounterArea9.cs | 2 - PKHeX.Core/Legality/Bulk/BulkAnalysis.cs | 3 +- .../Encounters/Data/Gen3/EncountersWC3.cs | 65 ++++++++++--------- .../EncounterStatic/EncounterStatic5N.cs | 5 +- .../Encounters/Generator/EncounterFinder.cs | 2 +- .../Moveset/EncounterMovesetGenerator.cs | 9 ++- .../Encounters/Information/EncounterLearn.cs | 2 +- .../Information/EncounterSuggestion.cs | 20 ++++-- .../Information/EncounterSummary.cs | 2 +- .../Encounters/Information/EncounterText.cs | 58 +++++++++++++++++ .../Memories/MemoryPermissions.cs | 19 ++++-- .../Verifiers/HyperTrainingVerifier.cs | 38 ++++++++--- .../Legality/Verifiers/LevelVerifier.cs | 5 +- .../Legality/Verifiers/MemoryVerifier.cs | 48 +++++++++----- PKHeX.Core/Legality/Verifiers/MiscVerifier.cs | 2 +- PKHeX.Core/Moves/MoveInfo.cs | 10 +++ .../PersonalInfo/Table/PersonalTable2.cs | 2 +- .../PersonalInfo/Table/PersonalTable3.cs | 2 +- PKHeX.Core/Saves/Access/SaveBlockMetadata.cs | 2 +- PKHeX.Core/Saves/SAV5.cs | 5 +- .../Substructures/Gen5/CGearBackground.cs | 28 ++++---- .../Substructures/Gen7/LGPE/GoParkStorage.cs | 17 +++-- .../Controls/Slots/SummaryPreviewer.cs | 47 +------------- PKHeX.WinForms/MainWindow/Main.cs | 2 +- PKHeX.WinForms/Subforms/SAV_Encounters.cs | 2 +- PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs | 2 +- .../Subforms/Save Editors/Gen3/SAV_Misc3.cs | 8 ++- .../Save Editors/Gen5/SAV_CGearSkin.cs | 3 +- .../Save Editors/Gen6/SAV_PokeBlockORAS.cs | 8 ++- .../Subforms/Save Editors/SAV_Wondercard.cs | 2 +- .../PKHeX.Core.Tests/Legality/ShadowTests.cs | 4 +- 50 files changed, 338 insertions(+), 219 deletions(-) create mode 100644 PKHeX.Core/Legality/Encounters/Information/EncounterText.cs diff --git a/PKHeX.Core/Editing/Applicators/BallApplicator.cs b/PKHeX.Core/Editing/Applicators/BallApplicator.cs index 0189c04ae..20bc901bb 100644 --- a/PKHeX.Core/Editing/Applicators/BallApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/BallApplicator.cs @@ -120,8 +120,8 @@ private static int GetCircularOnce(T[] items, T current, Span result) static BallApplicator() { - Span exclude = stackalloc Ball[] {None, Poke}; - Span end = stackalloc Ball[] {Poke}; + ReadOnlySpan exclude = stackalloc Ball[] {None, Poke}; + ReadOnlySpan end = stackalloc Ball[] {Poke}; Span all = stackalloc Ball[BallList.Length - exclude.Length]; all = all[..FillExcept(all, exclude, BallList)]; diff --git a/PKHeX.Core/Editing/CommonEdits.cs b/PKHeX.Core/Editing/CommonEdits.cs index 29a35bf38..305aa631e 100644 --- a/PKHeX.Core/Editing/CommonEdits.cs +++ b/PKHeX.Core/Editing/CommonEdits.cs @@ -174,13 +174,13 @@ public static void ApplySetDetails(this PKM pk, IBattleTemplate Set) // Under this scenario, just force the Hidden Power type. if (Array.IndexOf(Set.Moves, (ushort)Move.HiddenPower) != -1 && pk.HPType != Set.HiddenPowerType) { - if (Array.Exists(Set.IVs, static iv => iv >= 30)) + if (Set.IVs.AsSpan().IndexOfAny(30, 31) >= 0) pk.SetHiddenPower(Set.HiddenPowerType); } // In Generation 1/2 Format sets, when EVs are not specified at all, it implies maximum EVs instead! // Under this scenario, just apply maximum EVs (65535). - if (Array.TrueForAll(Set.EVs, static ev => ev == 0)) + if (Set.EVs.AsSpan().IndexOfAnyExcept(0) == -1) gb.MaxEVs(); else pk.SetEVs(Set.EVs); diff --git a/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWork.cs b/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWork.cs index 6e7289604..7eef03518 100644 --- a/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWork.cs +++ b/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWork.cs @@ -1,5 +1,5 @@ +using System; using System.Collections.Generic; -using System.Linq; namespace PKHeX.Core; @@ -10,22 +10,53 @@ namespace PKHeX.Core; public sealed class EventWork : EventVar where T : struct { public T Value; + + /// + /// Values with known behavior. They are labeled with a humanized string. + /// public readonly IList Options = new List { new() }; public EventWork(int index, EventVarType t, IReadOnlyList pieces) : base(index, t, pieces[1]) { - if (pieces.Count < 3) - return; + if (pieces.Count >= 3) + AddLabeledValueOptions(pieces[2], Options); + } - var items = pieces[2] - .Split(',') - .Select(z => z.Split(':')) - .Where(z => z.Length == 2); + private const char OptionSeparator = ','; // KVP,KVP + private const char OptionValueSeparator = ':'; // Name:Value - foreach (var s in items) + /// + /// Adds the key-value pair options to the . + /// + /// Text line containing key-value pairs. + /// Object that stores the results + /// + /// Decodes in the format of Value:Text,Value:Text. + /// Key-value pairs are separated by . + /// Key and value are separated by . + /// + private static void AddLabeledValueOptions(ReadOnlySpan text, ICollection list) + { + int i = 0; + while (i < text.Length) { - if (int.TryParse(s[0], out var value)) - Options.Add(new EventWorkVal(s[1], value)); + // Scan for the length of this key-value pair. + int commaIndex = text[i..].IndexOf(OptionSeparator); + if (commaIndex == -1) + commaIndex = text.Length - i; // end of string + + // Scan for the length of the key. + var pair = text.Slice(i, commaIndex); + i += commaIndex + 1; // skip to next kvp for next iteration + int colonIndex = pair.IndexOf(OptionValueSeparator); + if (colonIndex == -1) + continue; // invalid kvp + + // Split to get the key and value. + var valueText = pair[..colonIndex]; + var display = pair[(colonIndex + 1)..]; + if (int.TryParse(valueText, out int value)) + list.Add(new EventWorkVal(display.ToString(), value)); } } } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea.cs b/PKHeX.Core/Legality/Areas/EncounterArea.cs index 4d0ec459e..dd31359d4 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; namespace PKHeX.Core; @@ -10,7 +9,6 @@ public abstract record EncounterArea(GameVersion Version) : IVersion { public int Location { get; protected init; } public SlotType Type { get; protected init; } - protected abstract IReadOnlyList Raw { get; } /// /// Gets the slots contained in the area that match the provided data. @@ -26,6 +24,9 @@ public abstract record EncounterArea(GameVersion Version) : IVersion /// Met Location ID /// True if possibly originated from this area, false otherwise. public virtual bool IsMatchLocation(int location) => Location == location; - - public bool HasSpecies(ushort species) => Raw.Any(z => z.Species == species); +} + +internal interface IMemorySpeciesArea +{ + bool HasSpecies(ushort species); } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea1.cs b/PKHeX.Core/Legality/Areas/EncounterArea1.cs index 6bf725e1d..4245787ed 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea1.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea1.cs @@ -12,8 +12,6 @@ public sealed record EncounterArea1 : EncounterArea public readonly int Rate; public readonly EncounterSlot1[] Slots; - protected override IReadOnlyList Raw => Slots; - public static EncounterArea1[] GetAreas(BinLinkerAccessor input, GameVersion game) { var result = new EncounterArea1[input.Length]; diff --git a/PKHeX.Core/Legality/Areas/EncounterArea2.cs b/PKHeX.Core/Legality/Areas/EncounterArea2.cs index 54389267e..bc15f2981 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea2.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea2.cs @@ -18,8 +18,6 @@ public sealed record EncounterArea2 : EncounterArea public readonly byte[]? Rates; public readonly EncounterSlot2[] Slots; - protected override IReadOnlyList Raw => Slots; - public static EncounterArea2[] GetAreas(BinLinkerAccessor input, GameVersion game) { var result = new EncounterArea2[input.Length]; diff --git a/PKHeX.Core/Legality/Areas/EncounterArea3.cs b/PKHeX.Core/Legality/Areas/EncounterArea3.cs index 5ca3baab8..f84b5568c 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea3.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea3.cs @@ -13,8 +13,6 @@ public sealed record EncounterArea3 : EncounterArea public readonly int Rate; public readonly EncounterSlot3[] Slots; - protected override IReadOnlyList Raw => Slots; - public static EncounterArea3[] GetAreas(BinLinkerAccessor input, GameVersion game) { var result = new EncounterArea3[input.Length]; diff --git a/PKHeX.Core/Legality/Areas/EncounterArea3XD.cs b/PKHeX.Core/Legality/Areas/EncounterArea3XD.cs index e0b8f7058..49c53e28a 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea3XD.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea3XD.cs @@ -11,8 +11,6 @@ public sealed record EncounterArea3XD : EncounterArea { public readonly EncounterSlot3PokeSpot[] Slots; - protected override IReadOnlyList Raw => Slots; - public EncounterArea3XD(int loc, ushort s0, byte l0, ushort s1, byte l1, ushort s2, byte l2) : base(GameVersion.XD) { Location = loc; diff --git a/PKHeX.Core/Legality/Areas/EncounterArea4.cs b/PKHeX.Core/Legality/Areas/EncounterArea4.cs index 3465ec5d3..a22aee1ed 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea4.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea4.cs @@ -14,8 +14,6 @@ public sealed record EncounterArea4 : EncounterArea public readonly GroundTileAllowed GroundTile; public readonly EncounterSlot4[] Slots; - protected override IReadOnlyList Raw => Slots; - public static EncounterArea4[] GetAreas(BinLinkerAccessor input, GameVersion game) { var result = new EncounterArea4[input.Length]; diff --git a/PKHeX.Core/Legality/Areas/EncounterArea5.cs b/PKHeX.Core/Legality/Areas/EncounterArea5.cs index bd6e9b227..31c0a668d 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea5.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea5.cs @@ -12,8 +12,6 @@ public sealed record EncounterArea5 : EncounterArea { public readonly EncounterSlot5[] Slots; - protected override IReadOnlyList Raw => Slots; - public static EncounterArea5[] GetAreas(BinLinkerAccessor input, GameVersion game) { var result = new EncounterArea5[input.Length]; diff --git a/PKHeX.Core/Legality/Areas/EncounterArea6AO.cs b/PKHeX.Core/Legality/Areas/EncounterArea6AO.cs index 58074b04f..d43f00b57 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea6AO.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea6AO.cs @@ -8,12 +8,10 @@ namespace PKHeX.Core; /// /// encounter area /// -public sealed record EncounterArea6AO : EncounterArea +public sealed record EncounterArea6AO : EncounterArea, IMemorySpeciesArea { public readonly EncounterSlot6AO[] Slots; - protected override IReadOnlyList Raw => Slots; - public static EncounterArea6AO[] GetAreas(BinLinkerAccessor input, GameVersion game) { var result = new EncounterArea6AO[input.Length]; @@ -88,4 +86,14 @@ public override IEnumerable GetMatchingSlots(PKM pk, EvoCriter } } } + + public bool HasSpecies(ushort species) + { + foreach (var slot in Slots) + { + if (slot.Species == species) + return true; + } + return false; + } } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea6XY.cs b/PKHeX.Core/Legality/Areas/EncounterArea6XY.cs index 2ac7fac3b..5fc749577 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea6XY.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea6XY.cs @@ -8,12 +8,10 @@ namespace PKHeX.Core; /// /// encounter area /// -public sealed record EncounterArea6XY : EncounterArea +public sealed record EncounterArea6XY : EncounterArea, IMemorySpeciesArea { public readonly EncounterSlot6XY[] Slots; - protected override IReadOnlyList Raw => Slots; - public static EncounterArea6XY[] GetAreas(BinLinkerAccessor input, GameVersion game, EncounterArea6XY safari) { int count = input.Length; @@ -155,4 +153,14 @@ private bool ExistsPressureSlot(EvoCriteria evo, ref byte level) } return existsForm; } + + public bool HasSpecies(ushort species) + { + foreach (var slot in Slots) + { + if (slot.Species == species) + return true; + } + return false; + } } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea7.cs b/PKHeX.Core/Legality/Areas/EncounterArea7.cs index d29660315..469307a1a 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea7.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea7.cs @@ -12,8 +12,6 @@ public sealed record EncounterArea7 : EncounterArea { public readonly EncounterSlot7[] Slots; - protected override IReadOnlyList Raw => Slots; - public static EncounterArea7[] GetAreas(BinLinkerAccessor input, GameVersion game) { var result = new EncounterArea7[input.Length]; diff --git a/PKHeX.Core/Legality/Areas/EncounterArea7b.cs b/PKHeX.Core/Legality/Areas/EncounterArea7b.cs index ea672455a..59d62336e 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea7b.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea7b.cs @@ -12,8 +12,6 @@ public sealed record EncounterArea7b : EncounterArea { public readonly EncounterSlot7b[] Slots; - protected override IReadOnlyList Raw => Slots; - public static EncounterArea7b[] GetAreas(BinLinkerAccessor input, GameVersion game) { var result = new EncounterArea7b[input.Length]; diff --git a/PKHeX.Core/Legality/Areas/EncounterArea7g.cs b/PKHeX.Core/Legality/Areas/EncounterArea7g.cs index 09c72c6ad..1a82342f9 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea7g.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea7g.cs @@ -17,8 +17,6 @@ public sealed record EncounterArea7g : EncounterArea, ISpeciesForm public byte Form { get; } public readonly EncounterSlot7GO[] Slots; - protected override IReadOnlyList Raw => Slots; - private EncounterArea7g(ushort species, byte form, EncounterSlot7GO[] slots) : base(GameVersion.GO) { Species = species; diff --git a/PKHeX.Core/Legality/Areas/EncounterArea8.cs b/PKHeX.Core/Legality/Areas/EncounterArea8.cs index 060d32f35..e6b53e8e3 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea8.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea8.cs @@ -10,11 +10,10 @@ namespace PKHeX.Core; /// /// encounter area /// -public sealed record EncounterArea8 : EncounterArea +public sealed record EncounterArea8 : EncounterArea, IMemorySpeciesArea { public readonly EncounterSlot8[] Slots; - protected override IReadOnlyList Raw => Slots; /// /// Slots from this area can cross over to another area, resulting in a different met location. /// @@ -403,6 +402,16 @@ private EncounterSlot8[] ReadSlots(ReadOnlySpan areaData, byte slotCount) return slots; } + + public bool HasSpecies(ushort species) + { + foreach (var slot in Slots) + { + if (slot.Species == species) + return true; + } + return false; + } } /// diff --git a/PKHeX.Core/Legality/Areas/EncounterArea8a.cs b/PKHeX.Core/Legality/Areas/EncounterArea8a.cs index 3801a1bf3..bdb291864 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea8a.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea8a.cs @@ -13,8 +13,6 @@ public sealed record EncounterArea8a : EncounterArea public readonly EncounterSlot8a[] Slots; private readonly byte[] Locations; - protected override IReadOnlyList Raw => Slots; - public override bool IsMatchLocation(int location) { return Array.IndexOf(Locations, (byte)location) != -1; diff --git a/PKHeX.Core/Legality/Areas/EncounterArea8b.cs b/PKHeX.Core/Legality/Areas/EncounterArea8b.cs index d7d94c472..ffd0046c3 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea8b.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea8b.cs @@ -12,8 +12,6 @@ public sealed record EncounterArea8b : EncounterArea { public readonly EncounterSlot8b[] Slots; - protected override IReadOnlyList Raw => Slots; - public static EncounterArea8b[] GetAreas(BinLinkerAccessor input, GameVersion game) { var result = new EncounterArea8b[input.Length]; diff --git a/PKHeX.Core/Legality/Areas/EncounterArea8g.cs b/PKHeX.Core/Legality/Areas/EncounterArea8g.cs index b23a833b0..33bded4a4 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea8g.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea8g.cs @@ -17,8 +17,6 @@ public sealed record EncounterArea8g : EncounterArea, ISpeciesForm public byte Form { get; } public readonly EncounterSlot8GO[] Slots; - protected override IReadOnlyList Raw => Slots; - private EncounterArea8g(ushort species, byte form, EncounterSlot8GO[] slots) : base(GameVersion.GO) { Species = species; diff --git a/PKHeX.Core/Legality/Areas/EncounterArea9.cs b/PKHeX.Core/Legality/Areas/EncounterArea9.cs index 0e6ac8fb9..f13a8ebc9 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea9.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea9.cs @@ -14,8 +14,6 @@ public sealed record EncounterArea9 : EncounterArea public ushort CrossFrom { get; } - protected override IReadOnlyList Raw => Slots; - public static EncounterArea9[] GetAreas(BinLinkerAccessor input, GameVersion game) { var result = new EncounterArea9[input.Length]; diff --git a/PKHeX.Core/Legality/Bulk/BulkAnalysis.cs b/PKHeX.Core/Legality/Bulk/BulkAnalysis.cs index 6ac115932..4f6d847fc 100644 --- a/PKHeX.Core/Legality/Bulk/BulkAnalysis.cs +++ b/PKHeX.Core/Legality/Bulk/BulkAnalysis.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; namespace PKHeX.Core.Bulk; @@ -41,7 +40,7 @@ public BulkAnalysis(SaveFile sav, IBulkAnalysisSettings settings) CloneFlags = new bool[AllData.Count]; ScanAll(); - Valid = Parse.Count == 0 || Parse.All(z => z.Valid); + Valid = Parse.Count == 0 || Parse.TrueForAll(static z => z.Valid); } // Remove things that aren't actual stored data, or already flagged by legality checks. diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen3/EncountersWC3.cs b/PKHeX.Core/Legality/Encounters/Data/Gen3/EncountersWC3.cs index 1829fb699..bd6124364 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen3/EncountersWC3.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen3/EncountersWC3.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Linq; +using System; namespace PKHeX.Core; @@ -17,54 +16,58 @@ internal static class EncountersWC3 new() { Species = 385, Level = 05, ID32 = 20043, OT_Gender = 0, Version = GameVersion.R, Method = PIDType.BACD_R, OT_Name = "WISHMKR", CardTitle = "Wishmaker Jirachi", Language = (int)LanguageID.English }, }; - private static IEnumerable GetIngameCXDData() + private static WC3[] GetIngameCXDData() { - var langs = new[]{LanguageID.Japanese, LanguageID.English, LanguageID.French, LanguageID.Italian, LanguageID.German, LanguageID.Spanish}; + var langs = LanguageCXD; string[] h = {string.Empty, "ダニー", "HORDEL", "VOLKER", "ODINO", "HORAZ", string.Empty, "HORDEL"}; string[] d = {string.Empty, "ギンザル", "DUKING", "DOKING", "RODRIGO", "GRAND", string.Empty, "GERMÁN"}; string[] z = {string.Empty, "コンセント", "ZAPRONG", "ZAPRONG", "ZAPRONG", "ZAPRONG", string.Empty, "ZAPRONG"}; - return langs.SelectMany(l => GetIngame((int)l)); - IEnumerable GetIngame(int l) + const int count = 5; + var result = new WC3[count * langs.Length]; + for (int i = 0, ofs = 0; i < langs.Length; i++) { - var id = (LanguageID) l; - return new WC3[] - { - // Colosseum - new() { Species = 311, Level = 13, Language = l, Location = 254, ID32 = 37149, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.COLO, CardTitle = $"Special Gift ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, Moves = new(045, 086, 098, 270) }, // Plusle @ Ingame Trade - // XD - new(true) { Species = 239, Level = 20, Language = l, Location = 164, TID16 = 41400, OT_Gender = 0, OT_Name = h[l], Version = GameVersion.XD, CardTitle = $"Trade Togepi ({id})", Method = PIDType.CXD, Moves = new(008, 007, 009, 238), Nickname = z[l] }, // Elekid @ Snagem Hideout - new(true) { Species = 307, Level = 20, Language = l, Location = 116, TID16 = 37149, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Trapinch ({id})", Method = PIDType.CXD, Moves = new(223, 093, 247, 197) }, // Meditite @ Pyrite Town - new(true) { Species = 213, Level = 20, Language = l, Location = 116, TID16 = 37149, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Surskit ({id})", Method = PIDType.CXD, Moves = new(092, 164, 188, 227) }, // Shuckle @ Pyrite Town - new(true) { Species = 246, Level = 20, Language = l, Location = 116, TID16 = 37149, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Wooper ({id})", Method = PIDType.CXD, Moves = new(201, 349, 044, 200) }, // Larvitar @ Pyrite Town - }; + var id = langs[i]; + var l = (int)id; + + // Colosseum + result[ofs++] = new() { Species = 311, Level = 13, Language = l, Location = 254, ID32 = 37149, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.COLO, CardTitle = $"Special Gift ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, Moves = new(045, 086, 098, 270) }; // Plusle @ Ingame Trade + // XD + result[ofs++] = new(true) { Species = 239, Level = 20, Language = l, Location = 164, TID16 = 41400, OT_Gender = 0, OT_Name = h[l], Version = GameVersion.XD, CardTitle = $"Trade Togepi ({id})", Method = PIDType.CXD, Moves = new(008, 007, 009, 238), Nickname = z[l] }; // Elekid @ Snagem Hideout + result[ofs++] = new(true) { Species = 307, Level = 20, Language = l, Location = 116, TID16 = 37149, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Trapinch ({id})", Method = PIDType.CXD, Moves = new(223, 093, 247, 197) }; // Meditite @ Pyrite Town + result[ofs++] = new(true) { Species = 213, Level = 20, Language = l, Location = 116, TID16 = 37149, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Surskit ({id})", Method = PIDType.CXD, Moves = new(092, 164, 188, 227) }; // Shuckle @ Pyrite Town + result[ofs++] = new(true) { Species = 246, Level = 20, Language = l, Location = 116, TID16 = 37149, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Wooper ({id})", Method = PIDType.CXD, Moves = new(201, 349, 044, 200) }; // Larvitar @ Pyrite Town } + return result; } - private static IEnumerable GetIngameCXDDataMainline() + private static ReadOnlySpan LanguageCXD => new[] { LanguageID.Japanese, LanguageID.English, LanguageID.French, LanguageID.Italian, LanguageID.German, LanguageID.Spanish }; + + private static WC3[] GetIngameCXDDataMainline() { - var langs = new[] { LanguageID.Japanese, LanguageID.English, LanguageID.French, LanguageID.Italian, LanguageID.German, LanguageID.Spanish }; + var langs = LanguageCXD; string[] p = { string.Empty, "コロシアム", "COLOS", "COLOSSEUM", "ARENA", "COLOSSEUM", string.Empty, "CLAUDIO" }; string[] c = { string.Empty, "アゲト", "AGATE", "SAMARAGD", "SOFO", "EMERITAE", string.Empty, "ÁGATA" }; string[] m = { string.Empty, "バトルやま", "MATTLE", "MT BATAILL", "MONTE LOTT", "DUELLBERG", string.Empty, "ERNESTO" }; // truncated on ck3->pk3 transfer - return langs.SelectMany(l => GetIngame((int)l)); - IEnumerable GetIngame(int l) + const int count = 3; + var result = new WC3[count * langs.Length]; + for (int i = 0, ofs = 0; i < langs.Length; i++) { - var id = (LanguageID)l; + var id = langs[i]; + var l = (int)id; var nd = id != LanguageID.Japanese; - return new WC3[] - { - // Colosseum - new() { Species = 025, Level = 10, Language = l, Location = 255, ID32 = 31121, OT_Gender = 0, OT_Name = p[l], Version = GameVersion.R, CardTitle = $"Colosseum Pikachu ({id})",Method = PIDType.CXD, Shiny = Shiny.Never, NotDistributed = nd }, // Colosseum Pikachu bonus gift - new() { Species = 251, Level = 10, Language = l, Location = 255, ID32 = 31121, OT_Gender = 1, OT_Name = c[l], Version = GameVersion.R, CardTitle = $"Agate Celebi ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, NotDistributed = nd }, // Ageto Celebi bonus gift - new() { Species = 250, Level = 70, Language = l, Location = 255, ID32 = 10048, OT_Gender = 0, OT_Name = m[l], Version = GameVersion.S, CardTitle = $"Mt. Battle Ho-Oh ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, Moves = new(105, 126, 241, 129) }, // Ho-oh @ Mt. Battle - }; + + // Colosseum + result[ofs++] = new() { Species = 025, Level = 10, Language = l, Location = 255, ID32 = 31121, OT_Gender = 0, OT_Name = p[l], Version = GameVersion.R, CardTitle = $"Colosseum Pikachu ({id})",Method = PIDType.CXD, Shiny = Shiny.Never, NotDistributed = nd }; // Colosseum Pikachu bonus gift + result[ofs++] = new() { Species = 251, Level = 10, Language = l, Location = 255, ID32 = 31121, OT_Gender = 1, OT_Name = c[l], Version = GameVersion.R, CardTitle = $"Agate Celebi ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, NotDistributed = nd }; // Ageto Celebi bonus gift + result[ofs++] = new() { Species = 250, Level = 70, Language = l, Location = 255, ID32 = 10048, OT_Gender = 0, OT_Name = m[l], Version = GameVersion.S, CardTitle = $"Mt. Battle Ho-Oh ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, Moves = new(105, 126, 241, 129) }; // Ho-oh @ Mt. Battle } + return result; } - internal static readonly WC3[] Encounter_WC3CXD = GetIngameCXDData().ToArray(); - internal static readonly WC3[] Encounter_WC3CXDMain = GetIngameCXDDataMainline().ToArray(); + internal static readonly WC3[] Encounter_WC3CXD = GetIngameCXDData(); + internal static readonly WC3[] Encounter_WC3CXDMain = GetIngameCXDDataMainline(); internal static readonly WC3[] Encounter_Event3 = ArrayUtil.ConcatAll(Encounter_Event3_Special, Encounter_WC3CXD, Encounter_WC3CXDMain); diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5N.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5N.cs index 34c75dff6..5502af5f3 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5N.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5N.cs @@ -1,4 +1,4 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; /// /// Generation 5 Static Encounter from N @@ -47,8 +47,7 @@ private static void SetNPokemonData(PK5 pk5, int lang) pk5.IV_HP = pk5.IV_ATK = pk5.IV_DEF = pk5.IV_SPA = pk5.IV_SPD = pk5.IV_SPE = 30; pk5.NSparkle = NSparkle; pk5.OT_Name = GetOT(lang); - pk5.TID16 = 00002; - pk5.SID16 = 00000; + pk5.ID32 = 00002; } public static string GetOT(int lang) => lang == (int)LanguageID.Japanese ? "N" : "N"; diff --git a/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs b/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs index 79663e956..0534ef3af 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs @@ -130,7 +130,7 @@ private static bool VerifySecondaryChecks(PKM pk, LegalInfo info, PeekEnumerator } else if (pk is PK1 pk1) { - var hasGen2 = Array.Exists(info.Moves, z => z.Generation is 2); + var hasGen2 = MoveInfo.IsAnyFromGeneration(2, info.Moves); if (hasGen2) { if (!ParseSettings.AllowGen1Tradeback) diff --git a/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterMovesetGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterMovesetGenerator.cs index 0a9d332e9..f9d93a5cb 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterMovesetGenerator.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterMovesetGenerator.cs @@ -2,7 +2,6 @@ using System; using System.Buffers; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using static PKHeX.Core.EncounterTypeGroup; @@ -172,7 +171,7 @@ public static IEnumerable GenerateEncounters(PKM pk, ushort[] mo /// Moves that the resulting must be able to learn. /// Specific version to iterate for. /// A consumable list of possible encounters. - private static IEnumerable GenerateVersionEncounters(PKM pk, ReadOnlySpan moves, GameVersion version) + private static IEnumerable GenerateVersionEncounters(PKM pk, ushort[] moves, GameVersion version) { pk.Version = (int)version; @@ -185,7 +184,11 @@ private static IEnumerable GenerateVersionEncounters(PKM pk, Rea var needs = GetNeededMoves(pk, moves, version); var generator = EncounterGenerator.GetGenerator(version); - return PriorityList.SelectMany(type => GetPossibleOfType(pk, needs, version, type, chain, generator)); + foreach (var type in PriorityList) + { + foreach (var enc in GetPossibleOfType(pk, needs, version, type, chain, generator)) + yield return enc; + } } private static bool IsSane(PKM pk, ReadOnlySpan moves) diff --git a/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs b/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs index 2df30e90a..7722d51c4 100644 --- a/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs +++ b/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs @@ -75,7 +75,7 @@ public static IEnumerable GetLearn(ushort species, ushort[] move { if (species == 0) return Array.Empty(); - if (Array.Exists(moves, z => z == 0)) + if (moves.AsSpan().Contains(0)) return Array.Empty(); var vers = GameUtil.GameVersions; diff --git a/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestion.cs b/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestion.cs index ccb0f7f4b..764a5a0e6 100644 --- a/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestion.cs +++ b/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestion.cs @@ -29,10 +29,22 @@ public static class EncounterSuggestion if (s is null) return GetSuggestedEncounter(pk, w, loc); - bool isDefinitelySlot = Array.Exists(chain, z => z.Species == w.Species && z.Form == w.Form); - bool isDefinitelyStatic = Array.Exists(chain, z => z.Species == s.Species && z.Form == s.Form); - IEncounterable obj = (isDefinitelySlot || !isDefinitelyStatic) ? w : s; - return GetSuggestedEncounter(pk, obj, loc); + // Prefer the wild slot; fall back to wild slot if none are exact match. + if (IsSpeciesFormMatch(chain, w)) + return GetSuggestedEncounter(pk, w, loc); + if (IsSpeciesFormMatch(chain, s)) + return GetSuggestedEncounter(pk, s, loc); + return GetSuggestedEncounter(pk, w, loc); + } + + private static bool IsSpeciesFormMatch(ReadOnlySpan evos, ISpeciesForm encounter) + { + foreach (var evo in evos) + { + if (evo.Species == encounter.Species && evo.Form == encounter.Form) + return true; + } + return false; } private static EncounterSuggestionData GetSuggestedEncounterEgg(PKM pk, int loc = -1) diff --git a/PKHeX.Core/Legality/Encounters/Information/EncounterSummary.cs b/PKHeX.Core/Legality/Encounters/Information/EncounterSummary.cs index 2f6bd2b63..01c77a246 100644 --- a/PKHeX.Core/Legality/Encounters/Information/EncounterSummary.cs +++ b/PKHeX.Core/Legality/Encounters/Information/EncounterSummary.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace PKHeX.Core; diff --git a/PKHeX.Core/Legality/Encounters/Information/EncounterText.cs b/PKHeX.Core/Legality/Encounters/Information/EncounterText.cs new file mode 100644 index 000000000..9a144aa8f --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Information/EncounterText.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using static PKHeX.Core.LegalityCheckStrings; + +namespace PKHeX.Core; + +/// +/// Logic for creating a formatted text summary of an encounter. +/// +public static class EncounterText +{ + public static IReadOnlyList GetTextLines(this IEncounterInfo enc) => GetTextLines(enc, GameInfo.Strings); + + public static IReadOnlyList GetTextLines(this IEncounterInfo enc, GameStrings strings) + { + var lines = new List(); + 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)); + if (enc is MysteryGift mg) + { + lines.AddRange(mg.GetDescription()); + } + else if (enc is IMoveset m) + { + var moves = m.Moves; + if (moves.HasMoves) + { + string result = moves.GetMovesetLine(strings.movelist); + lines.Add(result); + } + } + + var el = enc as ILocation; + var loc = el?.GetEncounterLocation(enc.Generation, (int)enc.Version); + if (!string.IsNullOrEmpty(loc)) + lines.Add(string.Format(L_F0_1, "Location", loc)); + + 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}"); + +#if DEBUG + // Record types! Can get a nice summary. + // Won't work neatly for Mystery Gift types since those aren't record types. + if (enc is not MysteryGift) + { + // ReSharper disable once ConstantNullCoalescingCondition + var raw = enc.ToString() ?? throw new ArgumentNullException(nameof(enc)); + lines.AddRange(raw.Split(',', '}', '{')); + } +#endif + return lines; + } +} diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs index e4e56af16..8be9af0b6 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using static PKHeX.Core.Encounters6XY; using static PKHeX.Core.Encounters6AO; @@ -184,7 +183,7 @@ private static bool GetCanKnowMove(PKM pk, ushort move, EntityContext context, E }, EntityContext.Gen8 => version switch { - GameVersion.Any => GetCanBeCaptured(species, SlotsSW.Concat(SlotsSH), StaticSW.Concat(StaticSH)), + GameVersion.Any => GetCanBeCaptured(species, SlotsSW, StaticSW) || GetCanBeCaptured(species, SlotsSH, StaticSH), GameVersion.SW => GetCanBeCaptured(species, SlotsSW, StaticSW), GameVersion.SH => GetCanBeCaptured(species, SlotsSH, StaticSH), _ => false, @@ -192,12 +191,18 @@ private static bool GetCanKnowMove(PKM pk, ushort move, EntityContext context, E _ => false, }; - private static bool GetCanBeCaptured(ushort species, IEnumerable area, IEnumerable statics) + private static bool GetCanBeCaptured(ushort species, TArea[] areas, TStatic[] statics) where TArea : IMemorySpeciesArea where TStatic : IEncounterTemplate { - if (area.Any(loc => loc.HasSpecies(species))) - return true; - if (statics.Any(enc => enc.Species == species && !enc.Gift)) - return true; + foreach (var area in areas) + { + if (area.HasSpecies(species)) + return true; + } + foreach (var s in statics) + { + if (s.Species == species) + return true; + } return false; } diff --git a/PKHeX.Core/Legality/Verifiers/HyperTrainingVerifier.cs b/PKHeX.Core/Legality/Verifiers/HyperTrainingVerifier.cs index 387d4f260..801ebb1f3 100644 --- a/PKHeX.Core/Legality/Verifiers/HyperTrainingVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/HyperTrainingVerifier.cs @@ -42,21 +42,39 @@ public override void Verify(LegalityAnalysis data) // already checked for 6IV, therefore we're flawed on at least one IV if (t.IsHyperTrainedAll()) { - // SV gold bottle cap applies to all IVs regardless - // LGPE gold bottle cap applies to all IVs regardless - var evos = data.Info.EvoChainsAllGens; - if (evos.HasVisitedGen9 && Array.Exists(evos.Gen9, x => x.LevelMax >= 50)) - return; - if (evos.HasVisitedLGPE && Array.Exists(evos.Gen7b, x => x.LevelMax >= 100)) + if (HasVisitedGoldBottleFlawless(data.Info.EvoChainsAllGens)) return; + // Otherwise, could not have hyper trained a flawless IV. Flag a flawless IV with the usual logic. } + if (IsFlawlessHyperTrained(pk, t, max)) + data.AddLine(GetInvalid(LHyperPerfectOne)); + } + + private static bool HasVisitedGoldBottleFlawless(EvolutionHistory evos) + { + // S/V gold bottle cap applies to all IVs regardless + // LGP/E gold bottle cap applies to all IVs regardless + foreach (ref var x in evos.Gen9.AsSpan()) + { + if (x.LevelMax >= 50) + return true; + } + foreach (ref var x in evos.Gen7b.AsSpan()) + { + if (x.LevelMax >= 100) + return true; + } + return false; + } + + public static bool IsFlawlessHyperTrained(PKM pk, IHyperTrain t, int max) + { for (int i = 0; i < 6; i++) // Check individual IVs { - if (pk.GetIV(i) != max || !t.IsHyperTrained(i)) - continue; - data.AddLine(GetInvalid(LHyperPerfectOne)); - break; + if (pk.GetIV(i) == max && t.IsHyperTrained(i)) + return true; } + return false; } } diff --git a/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs b/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs index 43fbb54cb..2ac7e6d52 100644 --- a/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs @@ -1,4 +1,3 @@ -using System; using static PKHeX.Core.LegalityCheckStrings; namespace PKHeX.Core; @@ -131,9 +130,9 @@ private static bool IsTradeEvolutionRequired(LegalityAnalysis data, IEncounterTe var moves = data.Info.Moves; // Gen2 stuff can be traded between Gen2 games holding an Everstone, assuming it hasn't been transferred to Gen1 for special moves. if (enc.Generation == 2) - return Array.Exists(moves, z => z.Generation == 1); + return MoveInfo.IsAnyFromGeneration(1, moves); // Gen1 stuff can only be un-evolved if it was never traded from the OT. - if (Array.Exists(moves, z => z.Generation == 2)) + if (MoveInfo.IsAnyFromGeneration(2, moves)) return true; // traded to Gen2 for special moves if (pk.Format != 1) return true; // traded to Gen2 (current state) diff --git a/PKHeX.Core/Legality/Verifiers/MemoryVerifier.cs b/PKHeX.Core/Legality/Verifiers/MemoryVerifier.cs index 404c5fe95..fa5955925 100644 --- a/PKHeX.Core/Legality/Verifiers/MemoryVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/MemoryVerifier.cs @@ -42,20 +42,7 @@ private CheckResult VerifyCommonMemory(PKM pk, int handler, EntityContext contex // Actionable HM moves int hmIndex = MemoryContext6.MoveSpecificMemoryHM.IndexOf(memory.MemoryID); if (hmIndex != -1) - { - if (context != Gen6) // Gen8 has no HMs, so this memory can never exist. - return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); - - if (pk.Species != (int)Species.Smeargle) - { - // All AO hidden machine permissions are super-sets of Gen 3-5 games. - // Don't need to check the move history -- a learned HM in a prior game can still be learned in Gen6. - var evos = info.EvoChainsAllGens.Gen6; - var exists = Array.Exists(evos, z => PersonalTable.AO.GetFormEntry(z.Species, 0).GetIsLearnHM(hmIndex)); - if (!exists) - return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); - } - } + return VerifyMemoryHM6(context, info, mem, memory, hmIndex); if (mem.IsInvalidGeneralLocationMemoryValue(memory.MemoryID, memory.Variable, info.EncounterMatch, pk)) return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler)); @@ -71,15 +58,15 @@ private CheckResult VerifyCommonMemory(PKM pk, int handler, EntityContext contex // {0} saw {2} carrying {1} on its back. {4} that {3}. case 21 when context != Gen6 || !PersonalTable.AO.GetFormEntry(memory.Variable, 0).GetIsLearnHM(2): // Fly - return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); + return BadSpeciesMove(memory.Handler); // {0} used {2} at {1}’s instruction, but it had no effect. {4} that {3}. // The Move Deleter that {0} met through {1} made it forget {2}. {4} that {3}. case 16 or 48 when !CanKnowMove(pk, memory, context, info, memory.MemoryID == 16): - return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); + return BadSpeciesMove(memory.Handler); case 49 when memory.Variable == 0 || !GetCanRelearnMove(pk, memory.Variable, context, info.EvoChainsAllGens, info.EncounterOriginal): - return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); + return BadSpeciesMove(memory.Handler); // Dynamaxing // {0} battled at {1}’s side against {2} that Dynamaxed. {4} that {3}. @@ -92,7 +79,7 @@ private CheckResult VerifyCommonMemory(PKM pk, int handler, EntityContext contex // {0} studied about how to use {2} in a Box, thinking about {1}. {4} that {3}. // {0} practiced its cool pose for the move {2} in a Box, wishing to be praised by {1}. {4} that {3}. case 80 or 81 when !CanKnowMove(pk, memory, context, info): - return Get(string.Format(LMemoryArgBadMove, memory.Handler), Severity.Invalid); + return BadSpeciesMove(memory.Handler); // Species // With {1}, {0} went fishing, and they caught {2}. {4} that {3}. @@ -137,6 +124,31 @@ private CheckResult VerifyCommonMemory(PKM pk, int handler, EntityContext contex return VerifyCommonMemoryEtc(memory, mem); } + private CheckResult VerifyMemoryHM6(EntityContext context, LegalInfo info, MemoryContext mem, MemoryVariableSet memory, int hmIndex) + { + if (context != Gen6) // Gen8 has no HMs, so this memory can never exist. + return BadSpeciesMove(memory.Handler); + + if (info.EncounterMatch.Species == (int)Species.Smeargle) + return VerifyCommonMemoryEtc(memory, mem); + + // All AO hidden machine permissions are super-sets of Gen 3-5 games. + // Don't need to check the move history -- a learned HM in a prior game can still be learned in Gen6. + var pt = PersonalTable.AO; + foreach (ref var evo in info.EvoChainsAllGens.Gen6.AsSpan()) + { + var entry = pt[evo.Species]; + var canLearn = entry.GetIsLearnHM(hmIndex); + if (canLearn) + return VerifyCommonMemoryEtc(memory, mem); + break; + } + + return BadSpeciesMove(memory.Handler); + } + + private CheckResult BadSpeciesMove(string handler) => GetInvalid(string.Format(LMemoryArgBadMove, handler)); + private CheckResult VerifyCommonMemoryEtc(MemoryVariableSet memory, MemoryContext context) { if (!context.CanHaveIntensity(memory.MemoryID, memory.Intensity)) diff --git a/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs b/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs index 7ec4802f1..cc930d90f 100644 --- a/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs @@ -312,7 +312,7 @@ CheckResult GetWasTradeback(TimeCapsuleEvaluation timeCapsuleEvalution) CheckResult GetWasNotTradeback(TimeCapsuleEvaluation timeCapsuleEvalution) { - if (Array.Exists(data.Info.Moves, z => z.Generation == 2)) + if (MoveInfo.IsAnyFromGeneration(2, data.Info.Moves)) return GetInvalid(LG1CatchRateItem); var e = data.EncounterMatch; if (e is EncounterStatic1E {Version: GameVersion.Stadium} or EncounterTrade1) diff --git a/PKHeX.Core/Moves/MoveInfo.cs b/PKHeX.Core/Moves/MoveInfo.cs index 047779af8..ca6c35994 100644 --- a/PKHeX.Core/Moves/MoveInfo.cs +++ b/PKHeX.Core/Moves/MoveInfo.cs @@ -179,4 +179,14 @@ private static byte GetType(ushort move, ReadOnlySpan types) return 0; return types[move]; } + + public static bool IsAnyFromGeneration(int generation, ReadOnlySpan moves) + { + foreach (var move in moves) + { + if (move.Generation == generation) + return true; + } + return false; + } } diff --git a/PKHeX.Core/PersonalInfo/Table/PersonalTable2.cs b/PKHeX.Core/PersonalInfo/Table/PersonalTable2.cs index 7b80db2d4..951e826f1 100644 --- a/PKHeX.Core/PersonalInfo/Table/PersonalTable2.cs +++ b/PKHeX.Core/PersonalInfo/Table/PersonalTable2.cs @@ -7,7 +7,7 @@ namespace PKHeX.Core; /// public sealed class PersonalTable2 : IPersonalTable, IPersonalTable { - private readonly PersonalInfo2[] Table; // internal to share with Gen1 tables + private readonly PersonalInfo2[] Table; private const int SIZE = PersonalInfo2.SIZE; private const int MaxSpecies = Legal.MaxSpeciesID_2; public int MaxSpeciesID => MaxSpecies; diff --git a/PKHeX.Core/PersonalInfo/Table/PersonalTable3.cs b/PKHeX.Core/PersonalInfo/Table/PersonalTable3.cs index 598c87f2f..87e9507f9 100644 --- a/PKHeX.Core/PersonalInfo/Table/PersonalTable3.cs +++ b/PKHeX.Core/PersonalInfo/Table/PersonalTable3.cs @@ -7,7 +7,7 @@ namespace PKHeX.Core; /// public sealed class PersonalTable3 : IPersonalTable, IPersonalTable { - private readonly PersonalInfo3[] Table; // internal to share with Gen1 tables + private readonly PersonalInfo3[] Table; private const int SIZE = PersonalInfo3.SIZE; private const int MaxSpecies = Legal.MaxSpeciesID_3; public int MaxSpeciesID => MaxSpecies; diff --git a/PKHeX.Core/Saves/Access/SaveBlockMetadata.cs b/PKHeX.Core/Saves/Access/SaveBlockMetadata.cs index ad3b3c7ba..483aa8e53 100644 --- a/PKHeX.Core/Saves/Access/SaveBlockMetadata.cs +++ b/PKHeX.Core/Saves/Access/SaveBlockMetadata.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace PKHeX.Core; diff --git a/PKHeX.Core/Saves/SAV5.cs b/PKHeX.Core/Saves/SAV5.cs index 527339731..4b69154c2 100644 --- a/PKHeX.Core/Saves/SAV5.cs +++ b/PKHeX.Core/Saves/SAV5.cs @@ -174,6 +174,8 @@ private bool CGearSkinPresent set => Data[CGearSkinInfoOffset + 2] = Data[PlayerData.Offset + (this is SAV5B2W2 ? 0x6C : 0x54)] = value ? (byte)1 : (byte)0; } + private static ReadOnlySpan DLCFooter => new byte[] { 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x27, 0x00, 0x00, 0x27, 0x35, 0x05, 0x31, 0x00, 0x00 }; + public byte[] CGearSkinData { get @@ -193,8 +195,7 @@ public byte[] CGearSkinData WriteUInt16LittleEndian(footer[2..], chk); // checksum WriteUInt16LittleEndian(footer[0x100..], chk); // second checksum - Span dlcfooter = stackalloc byte[] { 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x27, 0x00, 0x00, 0x27, 0x35, 0x05, 0x31, 0x00, 0x00 }; - dlcfooter.CopyTo(footer[0x102..]); + DLCFooter.CopyTo(footer[0x102..]); ushort skinchkval = Checksums.CRC16_CCITT(footer[0x100..0x104]); WriteUInt16LittleEndian(footer[0x112..], skinchkval); diff --git a/PKHeX.Core/Saves/Substructures/Gen5/CGearBackground.cs b/PKHeX.Core/Saves/Substructures/Gen5/CGearBackground.cs index 797172ac3..2566b0661 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/CGearBackground.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/CGearBackground.cs @@ -245,27 +245,31 @@ public static CGearBackground GetBackground(ReadOnlySpan data) if (Width * Height * bpp != data.Length) throw new ArgumentException("Invalid image data size."); - var pixels = MemoryMarshal.Cast(data); - var colors = GetColorData(pixels); + var colors = GetColorData(data); + var palette = colors.Distinct().ToArray(); + if (palette.Length > ColorCount) + throw new ArgumentException($"Too many unique colors. Expected <= 16, got {palette.Length}"); - var Palette = colors.Distinct().ToArray(); - if (Palette.Length > ColorCount) - throw new ArgumentException($"Too many unique colors. Expected <= 16, got {Palette.Length}"); - - var tiles = GetTiles(colors, Palette); + var tiles = GetTiles(colors, palette); GetTileList(tiles, out List tilelist, out TileMap tm); if (tilelist.Count >= 0xFF) throw new ArgumentException($"Too many unique tiles. Expected < 256, received {tilelist.Count}."); // Finished! - return new CGearBackground(Palette, tilelist.ToArray(), tm); + return new CGearBackground(palette, tilelist.ToArray(), tm); } - private static int[] GetColorData(ReadOnlySpan pixels) + private static int[] GetColorData(ReadOnlySpan data) { + var pixels = MemoryMarshal.Cast(data); int[] colors = new int[pixels.Length]; for (int i = 0; i < pixels.Length; i++) - colors[i] = GetRGB555_32(pixels[i]); + { + var pixel = pixels[i]; + if (!BitConverter.IsLittleEndian) + pixel = ReverseEndianness(pixel); + colors[i] = GetRGB555_32(pixel); + } return colors; } @@ -327,10 +331,10 @@ private static void FindPossibleRotatedTile(Tile t, IList tilelist, TileMa tm.Rotations[tileIndex] = 0; } - private CGearBackground(int[] Palette, Tile[] tilelist, TileMap tm) + private CGearBackground(int[] palette, Tile[] tilelist, TileMap tm) { Map = tm; - ColorPalette = Palette; + ColorPalette = palette; Tiles = tilelist; } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GoParkStorage.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GoParkStorage.cs index 3efc786dc..087763a5a 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GoParkStorage.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GoParkStorage.cs @@ -1,7 +1,6 @@ -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; namespace PKHeX.Core; @@ -45,10 +44,16 @@ public void SetAllEntities(IReadOnlyList value) this[i] = value[i]; } - public IEnumerable DumpAll(IReadOnlyList speciesNames) => GetAllEntities() - .Select((z, i) => (Entry: z, Index: i)) - .Where(z => z.Entry.Species > 0) - .Select(z => z.Entry.Dump(speciesNames, z.Index)); + public IEnumerable DumpAll(IReadOnlyList speciesNames) + { + var arr = GetAllEntities(); + for (int i = 0; i < arr.Length; i++) + { + var entry = arr[i]; + if (entry.Species > 0) + yield return entry.Dump(speciesNames, i); + } + } public IEnumerator GetEnumerator() => (IEnumerator)GetAllEntities().GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetAllEntities().GetEnumerator(); diff --git a/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs b/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs index cc6e5ce11..dfceb858a 100644 --- a/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs +++ b/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs @@ -3,8 +3,6 @@ using System.Windows.Forms; using PKHeX.Core; -using static PKHeX.Core.LegalityCheckStrings; - namespace PKHeX.WinForms.Controls; public sealed class SummaryPreviewer @@ -43,7 +41,7 @@ public void Show(Control pb, IEncounterInfo enc) if (Main.Settings.Hover.HoverSlotShowText) { - var lines = GetTextLines(enc); + var lines = enc.GetTextLines(GameInfo.Strings); var text = string.Join(Environment.NewLine, lines); ShowSet.SetToolTip(pb, text); } @@ -52,49 +50,6 @@ public void Show(Control pb, IEncounterInfo enc) Cry.PlayCry(enc, enc.Context); } - public static IEnumerable GetTextLines(IEncounterInfo enc) - { - var lines = new List(); - var str = GameInfo.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)); - if (enc is MysteryGift mg) - { - lines.AddRange(mg.GetDescription()); - } - else if (enc is IMoveset m) - { - var moves = m.Moves; - if (moves.HasMoves) - { - string result = moves.GetMovesetLine(GameInfo.Strings.movelist); - lines.Add(result); - } - } - - var el = enc as ILocation; - var loc = el?.GetEncounterLocation(enc.Generation, (int) enc.Version); - if (!string.IsNullOrEmpty(loc)) - lines.Add(string.Format(L_F0_1, "Location", loc)); - lines.Add(string.Format(L_F0_1, nameof(GameVersion), enc.Version)); - lines.Add(enc.LevelMin == enc.LevelMax - ? $"Level: {enc.LevelMin}" - : $"Level: {enc.LevelMin}-{enc.LevelMax}"); - -#if DEBUG - // Record types! Can get a nice summary. - // Won't work neatly for Mystery Gift types since those aren't record types. - if (enc is not MysteryGift) - { - // ReSharper disable once ConstantNullCoalescingCondition - var raw = enc.ToString() ?? throw new ArgumentNullException(nameof(enc)); - lines.AddRange(raw.Split(',', '}', '{')); - } -#endif - return lines; - } - public void Clear() { ShowSet.RemoveAll(); diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs index 5831b3558..a8796e7d8 100644 --- a/PKHeX.WinForms/MainWindow/Main.cs +++ b/PKHeX.WinForms/MainWindow/Main.cs @@ -1063,7 +1063,7 @@ private static void DisplayLegalityReport(LegalityAnalysis la) if (dr != DialogResult.Yes) return; #if DEBUG - var enc = SummaryPreviewer.GetTextLines(la.EncounterOriginal); + var enc = la.EncounterOriginal.GetTextLines(); report += Environment.NewLine + Environment.NewLine + string.Join(Environment.NewLine, enc); #endif WinFormsUtil.SetClipboardText(report); diff --git a/PKHeX.WinForms/Subforms/SAV_Encounters.cs b/PKHeX.WinForms/Subforms/SAV_Encounters.cs index d29bb6be5..6059ec28a 100644 --- a/PKHeX.WinForms/Subforms/SAV_Encounters.cs +++ b/PKHeX.WinForms/Subforms/SAV_Encounters.cs @@ -80,7 +80,7 @@ public SAV_Encounters(PKMEditor f1, TrainerDatabase db) return; var enc = Results[index]; - pb.AccessibleDescription = string.Join(Environment.NewLine, SummaryPreviewer.GetTextLines(enc)); + pb.AccessibleDescription = string.Join(Environment.NewLine, enc.GetTextLines()); }; slot.ContextMenuStrip = mnu; if (Main.Settings.Hover.HoverSlotShowText) diff --git a/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs b/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs index 8ab95a8dc..983f56b61 100644 --- a/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs +++ b/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs @@ -83,7 +83,7 @@ public SAV_MysteryGiftDB(PKMEditor tabs, SAVEditor sav) return; var enc = Results[index]; - pb.AccessibleDescription = string.Join(Environment.NewLine, SummaryPreviewer.GetTextLines(enc)); + pb.AccessibleDescription = string.Join(Environment.NewLine, enc.GetTextLines()); }; } diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen3/SAV_Misc3.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen3/SAV_Misc3.cs index 1e2beb7b1..0f33fa1e3 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/Gen3/SAV_Misc3.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen3/SAV_Misc3.cs @@ -112,6 +112,9 @@ private void SaveJoyful(IGen3Joyful j) } #endregion + private const ushort ItemIDOldSeaMap = 0x178; + private static ReadOnlySpan TicketItemIDs => new ushort[] { 0x109, 0x113, 0x172, 0x173, ItemIDOldSeaMap }; // item IDs + #region Ferry private void B_GetTickets_Click(object sender, EventArgs e) { @@ -123,9 +126,8 @@ private void B_GetTickets_Click(object sender, EventArgs e) itemlist[i] = $"(Item #{i:000})"; } - const ushort oldsea = 0x178; - Span tickets = stackalloc ushort[] {0x109, 0x113, 0x172, 0x173, oldsea }; // item IDs - if (!SAV.Japanese && DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, $"Non Japanese save file. Add {itemlist[oldsea]} (unreleased)?")) + var tickets = TicketItemIDs; + if (!SAV.Japanese && DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, $"Non Japanese save file. Add {itemlist[ItemIDOldSeaMap]} (unreleased)?")) tickets = tickets[..^1]; // remove old sea map var p = Pouches.FirstOrDefault(z => z.Type == InventoryType.KeyItems); diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_CGearSkin.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_CGearSkin.cs index f1f7ed6bd..557fd5431 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_CGearSkin.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_CGearSkin.cs @@ -2,7 +2,6 @@ using System.Drawing; using System.Drawing.Imaging; using System.IO; -using System.Linq; using System.Windows.Forms; using PKHeX.Core; @@ -105,7 +104,7 @@ private void B_ExportCGB_Click(object sender, EventArgs e) private void B_Save_Click(object sender, EventArgs e) { byte[] bgdata = bg.GetSkin(SAV is SAV5B2W2); - if (bgdata.Any(z => z != 0)) + if (bgdata.AsSpan().IndexOfAnyExcept(0) != -1) { SAV.CGearSkinData = bgdata; Origin.CopyChangesFrom(SAV); diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen6/SAV_PokeBlockORAS.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen6/SAV_PokeBlockORAS.cs index df887a2cc..a25d9c847 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/Gen6/SAV_PokeBlockORAS.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen6/SAV_PokeBlockORAS.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Windows.Forms; using PKHeX.Core; using static System.Buffers.Binary.BinaryPrimitives; @@ -42,13 +42,15 @@ private void B_Save_Click(object sender, EventArgs e) Close(); } - private void B_RandomizeBerries_Click(object sender, EventArgs e) + private static ReadOnlySpan DefaultBerryTree => new byte[] { 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x80, 0x40, 0x01, 0x00, 0x00, 0x00 }; + +private void B_RandomizeBerries_Click(object sender, EventArgs e) { if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Repopulate all berry plots with random berries?")) return; // Randomize the trees. - Span tree = stackalloc byte[] { 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x80, 0x40, 0x01, 0x00, 0x00, 0x00 }; + ReadOnlySpan tree = DefaultBerryTree; var plantable = Legal.Pouch_Berry_XY; // 0 index is None, skip with rand var rnd = Util.Rand; diff --git a/PKHeX.WinForms/Subforms/Save Editors/SAV_Wondercard.cs b/PKHeX.WinForms/Subforms/Save Editors/SAV_Wondercard.cs index 668248a95..9f10002cf 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/SAV_Wondercard.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/SAV_Wondercard.cs @@ -48,7 +48,7 @@ public SAV_Wondercard(SaveFile sav, DataMysteryGift? g = null) return; var enc = mga.Gifts[index]; - pb.AccessibleDescription = string.Join(Environment.NewLine, SummaryPreviewer.GetTextLines(enc)); + pb.AccessibleDescription = string.Join(Environment.NewLine, enc.GetTextLines()); }; } diff --git a/Tests/PKHeX.Core.Tests/Legality/ShadowTests.cs b/Tests/PKHeX.Core.Tests/Legality/ShadowTests.cs index f64e9ce42..ef413d3ca 100644 --- a/Tests/PKHeX.Core.Tests/Legality/ShadowTests.cs +++ b/Tests/PKHeX.Core.Tests/Legality/ShadowTests.cs @@ -185,10 +185,12 @@ public static void VerifyResults(IReadOnlyList results, TeamLock[] team) 0x049F2F05, // Mawile }; + private static ReadOnlySpan MawileIVs => new[] {31, 30, 29, 31, 23, 27}; + [Fact] public static void VerifyMawileAntishiny() { - VerifyResultsAntiShiny(MawileTeamPIDs, Mawile, 12345, 51882, stackalloc[] {31, 30, 29, 31, 23, 27}); + VerifyResultsAntiShiny(MawileTeamPIDs, Mawile, 12345, 51882, MawileIVs); } private static void VerifyResultsAntiShiny(ReadOnlySpan resultPIDs, TeamLock[] team, ushort tid, ushort sid, ReadOnlySpan ivs)