From 59dc7fb6948e4dffe4db02b483d3d6521d4ee59d Mon Sep 17 00:00:00 2001 From: Kurt Date: Sun, 5 Nov 2023 14:20:35 -0800 Subject: [PATCH] Misc tweaks --- PKHeX.Core/Legality/Core.cs | 1 - PKHeX.Core/Legality/Tables/FormInfo.cs | 61 +++---- .../Verifiers/Ability/AbilityBreedLegality.cs | 168 +++++------------- .../Verifiers/Ability/AbilityVerifier.cs | 9 +- PKHeX.Core/Legality/Verifiers/FormVerifier.cs | 27 ++- .../Verifiers/IndividualValueVerifier.cs | 9 +- PKHeX.Core/Legality/Verifiers/MiscVerifier.cs | 12 +- .../PKM/Util/Conversion/FormConverter.cs | 30 +++- PKHeX.Core/PKM/Util/EntityContext.cs | 34 ++++ PKHeX.Core/PKM/Util/GameConsole.cs | 5 +- PKHeX.Core/Saves/SAV3GCMemoryCard.cs | 9 +- .../Saves/Substructures/Gen7/LGPE/GP1.cs | 18 +- PKHeX.Core/Saves/Substructures/Gen7/QR7.cs | 62 ++++--- PKHeX.Core/Util/Util.cs | 40 ++++- PKHeX.Drawing.Misc/QR/QRImageUtil.cs | 12 +- .../Save Editors/Gen7/SAV_PokedexGG.cs | 2 +- .../Save Editors/Gen8/SAV_PokedexLA.cs | 4 +- .../Save Editors/Gen8/SAV_PokedexSWSH.cs | 2 +- .../Save Editors/Gen9/SAV_PokedexSV.cs | 2 +- .../Gen9/SAV_PokedexSVKitakami.cs | 2 +- Tests/PKHeX.Core.Tests/Legality/RaidTests.cs | 36 ++-- 21 files changed, 277 insertions(+), 268 deletions(-) diff --git a/PKHeX.Core/Legality/Core.cs b/PKHeX.Core/Legality/Core.cs index bac64a89a..79ecda0dd 100644 --- a/PKHeX.Core/Legality/Core.cs +++ b/PKHeX.Core/Legality/Core.cs @@ -17,7 +17,6 @@ public static class Legal internal const int MaxItemID_2 = 255; internal const int MaxAbilityID_2 = 0; - internal const int MaxSpeciesIndex_3 = 412; internal const int MaxSpeciesID_3 = 386; internal const int MaxMoveID_3 = 354; internal const int MaxItemID_3 = 374; diff --git a/PKHeX.Core/Legality/Tables/FormInfo.cs b/PKHeX.Core/Legality/Tables/FormInfo.cs index 941d6c9b2..60430be57 100644 --- a/PKHeX.Core/Legality/Tables/FormInfo.cs +++ b/PKHeX.Core/Legality/Tables/FormInfo.cs @@ -16,27 +16,19 @@ public static class FormInfo /// Entity form /// Current generation format /// True if it can only exist in a battle, false if it can exist outside of battle. - public static bool IsBattleOnlyForm(ushort species, byte form, int format) + public static bool IsBattleOnlyForm(ushort species, byte form, int format) => BattleOnly.Contains(species) && species switch { - if (!BattleOnly.Contains(species)) - return false; - + // Only continue checking if the species is in the list of Battle Only forms. // Some species have battle only forms as well as out-of-battle forms (other than base form). - switch (species) - { - case (int)Slowbro when form == 2 && format >= 8: // this one is OK, Galarian Slowbro (not a Mega) - case (int)Darmanitan when form == 2 && format >= 8: // this one is OK, Galarian non-Zen - case (int)Zygarde when form < 4: // Zygarde Complete - case (int)Mimikyu when form == 2: // Totem disguise Mimikyu - case (int)Necrozma when form < 3: // Only mark Ultra Necrozma as Battle Only - return false; - case (int)Minior: return form < 7; // Minior Shields-Down - case (int)Ogerpon: return form >= 4; // Embody Aspect - - default: - return form != 0; - } - } + (ushort)Slowbro => form == 1, // Mega + (ushort)Darmanitan => (form & 1) == 1, // Zen + (ushort)Zygarde => form == 4, // Zygarde Complete + (ushort)Minior => form < 7, // Minior Shields-Down + (ushort)Mimikyu => (form & 1) == 1, // Busted + (ushort)Necrozma => form == 3, // Ultra Necrozma + (ushort)Ogerpon => form >= 4, // Embody Aspect + _ => form != 0, + }; /// /// Reverts the Battle Form to the form it would have outside of Battle. @@ -48,10 +40,11 @@ public static bool IsBattleOnlyForm(ushort species, byte form, int format) /// Suggested alt form value. public static byte GetOutOfBattleForm(ushort species, byte form, int format) => species switch { - (int)Darmanitan => (byte)(form & 2), - (int)Zygarde when format > 6 => 3, - (int)Minior => (byte)(form + 7), - (int)Ogerpon => (byte)(form & 3), + (ushort)Darmanitan => (byte)(form & 2), + (ushort)Zygarde when format > 6 => 3, + (ushort)Minior => (byte)(form + 7), + (ushort)Mimikyu => (byte)(form & 2), + (ushort)Ogerpon => (byte)(form & 3), _ => 0, }; @@ -65,9 +58,9 @@ public static bool IsBattleOnlyForm(ushort species, byte form, int format) /// True if it trading should be disallowed. public static bool IsUntradable(ushort species, byte form, uint formArg, int format) => species switch { - (int)Koraidon or (int)Miraidon when formArg == 1 => true, // Ride-able Box Legend - (int)Pikachu when form == 8 && format == 7 => true, // Let's Go Pikachu Starter - (int)Eevee when form == 1 && format == 7 => true, // Let's Go Eevee Starter + (ushort)Koraidon or (int)Miraidon => formArg == 1, // Ride-able Box Legend + (ushort)Pikachu => format == 7 && form == 8, // Let's Go Pikachu Starter + (ushort)Eevee => format == 7 && form == 1, // Let's Go Eevee Starter _ => IsFusedForm(species, form, format), }; @@ -80,9 +73,9 @@ public static bool IsBattleOnlyForm(ushort species, byte form, int format) /// True if it is a fused species-form, false if it is not fused. public static bool IsFusedForm(ushort species, byte form, int format) => species switch { - (int)Kyurem when form != 0 && format >= 5 => true, - (int)Necrozma when form != 0 && format >= 7 => true, - (int)Calyrex when form != 0 && format >= 8 => true, + (ushort)Kyurem => form != 0 && format >= 5, + (ushort)Necrozma => form != 0 && format >= 7, + (ushort)Calyrex => form != 0 && format >= 8, _ => false, }; @@ -224,18 +217,16 @@ public static bool IsFormChangeable(ushort species, byte oldForm, byte newForm, (int)Necrozma, // Ultra Necrozma }; - /// - /// Species that have a primal form that cannot exist outside of battle. - /// - private static readonly HashSet BattlePrimals = new() { (int)Kyogre, (int)Groudon }; - private static readonly HashSet BattleOnly = GetBattleFormSet(); private static HashSet GetBattleFormSet() { var hs = new HashSet(BattleForms); hs.UnionWith(BattleMegas); - hs.UnionWith(BattlePrimals); + + // Primals + hs.Add((ushort)Kyogre); + hs.Add((ushort)Groudon); return hs; } diff --git a/PKHeX.Core/Legality/Verifiers/Ability/AbilityBreedLegality.cs b/PKHeX.Core/Legality/Verifiers/Ability/AbilityBreedLegality.cs index 6cf487ee8..e314380a4 100644 --- a/PKHeX.Core/Legality/Verifiers/Ability/AbilityBreedLegality.cs +++ b/PKHeX.Core/Legality/Verifiers/Ability/AbilityBreedLegality.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System; using static PKHeX.Core.Species; namespace PKHeX.Core; @@ -8,147 +8,73 @@ namespace PKHeX.Core; /// internal static class AbilityBreedLegality { + private static ReadOnlySpan BanHidden5 => new byte[] + { + 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x10, 0x10, 0x20, 0x00, 0x01, 0x11, 0x02, 0x00, 0x49, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x90, 0x04, + 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x86, 0x80, + 0x49, 0x00, 0x40, 0x00, 0x48, 0x02, 0x00, 0x00, 0x10, 0x00, 0x12, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x82, 0x24, 0x80, 0x0A, 0x00, + 0x00, 0x0C, 0x00, 0x00, 0x44, 0x44, 0x00, 0x00, 0xA0, 0x84, 0x80, + 0x40, 0x08, 0x12, + }; + /// /// Species that cannot be bred with a Hidden Ability originating in /// - internal static readonly HashSet BanHidden5 = new() + public static bool IsHiddenPossible5(ushort species) { - // Only males distributed; unable to pass to offspring - (int)Bulbasaur, (int)Charmander, (int)Squirtle, - (int)Tauros, - (int)Chikorita, (int)Cyndaquil, (int)Totodile, - (int)Tyrogue, - (int)Treecko, (int)Torchic, (int)Mudkip, - (int)Turtwig, (int)Chimchar, (int)Piplup, - (int)Pansage, (int)Pansear, (int)Panpour, - (int)Gothita, - - // Genderless; unable to pass to offspring - (int)Magnemite, - (int)Voltorb, - (int)Staryu, - (int)Ditto, - (int)Porygon, - (int)Beldum, - (int)Bronzor, - (int)Golett, - - // Not available at all - (int)Gastly, - (int)Koffing, - (int)Misdreavus, - (int)Unown, - (int)Slakoth, - (int)Plusle, - (int)Plusle, - (int)Lunatone, - (int)Solrock, - (int)Baltoy, - (int)Castform, - (int)Kecleon, - (int)Duskull, - (int)Chimecho, - (int)Cherubi, - (int)Chingling, - (int)Rotom, - (int)Phione, - (int)Snivy, (int)Tepig, (int)Oshawott, - (int)Throh, (int)Sawk, - (int)Yamask, - (int)Archen, - (int)Zorua, - (int)Ferroseed, - (int)Klink, - (int)Tynamo, - (int)Litwick, - (int)Cryogonal, - (int)Rufflet, - (int)Deino, - (int)Larvesta, - }; + var index = species >> 3; + var table = BanHidden5; + if (index >= table.Length) + return true; + return (BanHidden5[index] & (1 << (species & 7))) == 0; + } /// /// Species that cannot be bred with a Hidden Ability originating in /// - internal static readonly HashSet BanHidden6 = new() + public static bool IsHiddenPossible6(ushort species, byte form) => species switch { - // Not available at Friend Safari or Horde Encounter - (int)Flabébé + (2 << 11), // Orange - (int)Flabébé + (4 << 11), // White - - // Super Size can be obtained as a Pumpkaboo from event distributions - (int)Pumpkaboo + (1 << 11), // Small - (int)Pumpkaboo + (2 << 11), // Large - // Same abilities (1/2/H), not available as H - (int)Honedge, - (int)Carnivine, - (int)Cryogonal, - (int)Archen, - (int)Rotom, - (int)Rotom + (1 << 11), - (int)Rotom + (2 << 11), - (int)Rotom + (3 << 11), - (int)Rotom + (4 << 11), - (int)Rotom + (5 << 11), + (int)Castform => false, + (int)Carnivine => false, + (int)Rotom => false, + (int)Phione => false, + (int)Archen => false, + (int)Cryogonal => false, - (int)Castform, - (int)Furfrou, - (int)Furfrou + (1 << 11), - (int)Furfrou + (2 << 11), - (int)Furfrou + (3 << 11), - (int)Furfrou + (4 << 11), - (int)Furfrou + (5 << 11), - (int)Furfrou + (6 << 11), - (int)Furfrou + (7 << 11), - (int)Furfrou + (8 << 11), - (int)Furfrou + (9 << 11), + (int)Flabébé => form is not (2 or 4), // Orange or White - not available in Friend Safari or Horde + (int)Honedge => false, + (int)Furfrou => false, + (int)Pumpkaboo => form is not (1 or 2), // Previous-Gen: Size & Ability inherit from mother + + _ => true, }; /// /// Species that cannot be bred with a Hidden Ability originating in /// - internal static readonly HashSet BanHidden7 = new() + public static bool IsHiddenPossible7(ushort species, byte form) => species switch { - // SOS slots have 0 call rate - (int)Wimpod, - (int)Golisopod, - (int)Komala, - - // No Encounter - (int)Minior + (07 << 11), - (int)Minior + (08 << 11), - (int)Minior + (09 << 11), - (int)Minior + (10 << 11), - (int)Minior + (11 << 11), - (int)Minior + (12 << 11), - (int)Minior + (13 << 11), - - // Previous-Gen - (int)Pumpkaboo + (1 << 11), // Small - (int)Pumpkaboo + (2 << 11), // Large - // Same abilities (1/2/H), not available as H - (int)Honedge, - (int)Doublade, - (int)Aegislash, - (int)Carnivine, - (int)Cryogonal, - (int)Archen, - (int)Archeops, - (int)Rotom, - (int)Rotom + (1 << 11), - (int)Rotom + (2 << 11), - (int)Rotom + (3 << 11), - (int)Rotom + (4 << 11), - (int)Rotom + (5 << 11), + (int)Carnivine => false, + (int)Rotom => false, + (int)Phione => false, + (int)Archen => false, + (int)Cryogonal => false, + (int)Honedge => false, + (int)Pumpkaboo => form is not (1 or 2), // Previous-Gen: Size & Ability inherit from mother + + (int)Minior => false, // No SOS Encounter + (int)Wimpod => false, // SOS slots have 0 call rate + (int)Komala => false, // SOS slots have 0 call rate + _ => true, }; /// /// Species that cannot be bred with a Hidden Ability originating in /// - internal static readonly HashSet BanHidden8b = new() - { - (int)Phione, - }; + public static bool IsHiddenPossibleHOME(ushort eggSpecies) => eggSpecies is not (int)Phione; // Everything else can! } diff --git a/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs index a2ae5b3fd..21fa3dcd5 100644 --- a/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs @@ -341,7 +341,7 @@ private CheckResult VerifyAbility5(LegalityAnalysis data, IEncounterTemplate enc // Eggs and Encounter Slots are not yet checked for Hidden Ability potential. return enc switch { - EncounterEgg e when pk.AbilityNumber == 4 && AbilityBreedLegality.BanHidden5.Contains(e.Species) => GetInvalid(LAbilityHiddenUnavailable), + EncounterEgg e when pk.AbilityNumber == 4 && !AbilityBreedLegality.IsHiddenPossible5(e.Species) => GetInvalid(LAbilityHiddenUnavailable), _ => CheckMatch(data.Entity, abilities, 5, pk.Format == 5 ? AbilityState.MustMatch : AbilityState.CanMismatch, enc), }; } @@ -352,10 +352,9 @@ private CheckResult VerifyAbility6(LegalityAnalysis data, IEncounterTemplate enc if (pk.AbilityNumber != 4) return VALID; - // Eggs and Encounter Slots are not yet checked for Hidden Ability potential. return enc switch { - EncounterEgg egg when AbilityBreedLegality.BanHidden6.Contains((ushort)(egg.Species | (egg.Form << 11))) => GetInvalid(LAbilityHiddenUnavailable), + EncounterEgg egg when !AbilityBreedLegality.IsHiddenPossible6(egg.Species, egg.Form) => GetInvalid(LAbilityHiddenUnavailable), _ => VALID, }; } @@ -368,7 +367,7 @@ private CheckResult VerifyAbility7(LegalityAnalysis data, IEncounterTemplate enc return enc switch { - EncounterEgg egg when AbilityBreedLegality.BanHidden7.Contains((ushort)(egg.Species | (egg.Form << 11))) => GetInvalid(LAbilityHiddenUnavailable), + EncounterEgg egg when !AbilityBreedLegality.IsHiddenPossible7(egg.Species, egg.Form) => GetInvalid(LAbilityHiddenUnavailable), _ => VALID, }; } @@ -381,7 +380,7 @@ private CheckResult VerifyAbility8BDSP(LegalityAnalysis data, IEncounterable enc return enc switch { - EncounterEgg egg when AbilityBreedLegality.BanHidden8b.Contains((ushort)(egg.Species | (egg.Form << 11))) => GetInvalid(LAbilityHiddenUnavailable), + EncounterEgg egg when !AbilityBreedLegality.IsHiddenPossibleHOME(egg.Species) => GetInvalid(LAbilityHiddenUnavailable), _ => VALID, }; } diff --git a/PKHeX.Core/Legality/Verifiers/FormVerifier.cs b/PKHeX.Core/Legality/Verifiers/FormVerifier.cs index b90cd9c77..438dcfdd2 100644 --- a/PKHeX.Core/Legality/Verifiers/FormVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/FormVerifier.cs @@ -44,19 +44,26 @@ private CheckResult VerifyForm(LegalityAnalysis data) switch ((Species)species) { case Pikachu when Info.Generation == 6: // Cosplay - bool isStatic = enc is EncounterStatic6; - bool validCosplay = form == (isStatic ? enc.Form : 0); - if (!validCosplay) - return GetInvalid(isStatic ? LFormPikachuCosplayInvalid : LFormPikachuCosplay); + if (enc is not EncounterStatic6 s6) + { + if (form == 0) + break; // Regular Pikachu, OK. + return GetInvalid(LFormPikachuCosplay); + } + if (form != s6.Form) + return GetInvalid(LFormPikachuCosplayInvalid); + if (pk.Format != 6) + return GetInvalid(LTransferBad); // 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); case Pikachu when Info.Generation >= 7: // Cap - bool validCap = form == (enc is EncounterInvalid or EncounterEgg ? 0 : enc.Form); - if (!validCap) + var expectForm = enc is EncounterInvalid or EncounterEgg ? 0 : enc.Form; + if (form != expectForm) { bool gift = enc is MysteryGift g && g.Form != form; var msg = gift ? LFormPikachuEventInvalid : LFormInvalidGame; @@ -77,10 +84,8 @@ private CheckResult VerifyForm(LegalityAnalysis data) return GetInvalid(LFormItemInvalid); case Arceus: - { var arceus = FormItem.GetFormArceus(pk.HeldItem, pk.Format); return arceus != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem); - } case Keldeo when enc.Generation != 5 || pk.Format >= 8: // can mismatch in gen5 via BW 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. @@ -91,10 +96,8 @@ private CheckResult VerifyForm(LegalityAnalysis data) return GetInvalid(LMoveKeldeoMismatch); break; case Genesect: - { var genesect = FormItem.GetFormGenesect(pk.HeldItem); return genesect != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem); - } case Greninja: if (form > 1) // Ash Battle Bond active return GetInvalid(LFormBattle); @@ -141,10 +144,8 @@ private CheckResult VerifyForm(LegalityAnalysis data) return GetInvalid(LGenderInvalidNone); case Silvally: - { var silvally = FormItem.GetFormSilvally(pk.HeldItem); return silvally != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem); - } // Form doesn't exist in SM; cannot originate from that game. case Rockruff when enc.Generation == 7 && form == 1 && pk.SM: @@ -153,12 +154,10 @@ private CheckResult VerifyForm(LegalityAnalysis data) // 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); break; - } // Ogerpon's form changes depending on its held mask case Ogerpon when (form & 3) != FormItem.GetFormOgerpon(pk.HeldItem): diff --git a/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs b/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs index 2ef84fdf2..e7a7ee552 100644 --- a/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs @@ -14,9 +14,8 @@ public override void Verify(LegalityAnalysis data) { switch (data.EncounterMatch) { - case EncounterSlot7GO: - case EncounterSlot8GO: - VerifyIVsGoTransfer(data); + case IPogoSlot s: + VerifyIVsGoTransfer(data, s); break; case IFlawlessIVCount s: VerifyIVsFlawless(data, s); @@ -83,9 +82,9 @@ private void VerifyIVsFlawless(LegalityAnalysis data, int count) data.AddLine(GetInvalid(string.Format(LIVF_COUNT0_31, count))); } - private void VerifyIVsGoTransfer(LegalityAnalysis data) + private void VerifyIVsGoTransfer(LegalityAnalysis data, IPogoSlot g) { - if (data.EncounterMatch is IPogoSlot g && !g.GetIVsValid(data.Entity)) + if (!g.GetIVsValid(data.Entity)) data.AddLine(GetInvalid(LIVNotCorrect)); } } diff --git a/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs b/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs index eaa1d0d65..6f7213af6 100644 --- a/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs @@ -799,9 +799,10 @@ private void VerifyStatNature(LegalityAnalysis data, PKM pk) data.AddLine(GetInvalid(LStatNatureInvalid)); } + private static string GetMoveName(T pk, int index) where T : PKM, ITechRecord => ParseSettings.MoveStrings[pk.Permit.RecordPermitIndexes[index]]; + private void VerifyTechRecordSWSH(LegalityAnalysis data, T pk) where T : PKM, ITechRecord { - string GetMoveName(int index) => ParseSettings.MoveStrings[pk.Permit.RecordPermitIndexes[index]]; var evos = data.Info.EvoChainsAllGens.Gen8; if (evos.Length == 0) { @@ -810,7 +811,7 @@ private void VerifyStatNature(LegalityAnalysis data, PKM pk) { if (!pk.GetMoveRecordFlag(i)) continue; - data.AddLine(GetInvalid(string.Format(LMoveSourceTR, GetMoveName(i)))); + data.AddLine(GetInvalid(string.Format(LMoveSourceTR, GetMoveName(pk, i)))); } } else @@ -836,7 +837,7 @@ private void VerifyStatNature(LegalityAnalysis data, PKM pk) continue; } - data.AddLine(GetInvalid(string.Format(LMoveSourceTR, GetMoveName(i)))); + data.AddLine(GetInvalid(string.Format(LMoveSourceTR, GetMoveName(pk, i)))); } } } @@ -849,7 +850,6 @@ private static bool CanLearnTR(ushort species, byte form, int tr) private void VerifyTechRecordSV(LegalityAnalysis data, PK9 pk) { - string GetMoveName(int index) => ParseSettings.MoveStrings[pk.Permit.RecordPermitIndexes[index]]; var evos = data.Info.EvoChainsAllGens.Gen9; if (evos.Length == 0) { @@ -858,7 +858,7 @@ private void VerifyTechRecordSV(LegalityAnalysis data, PK9 pk) { if (!pk.GetMoveRecordFlag(i)) continue; - data.AddLine(GetInvalid(string.Format(LMoveSourceTR, GetMoveName(i)))); + data.AddLine(GetInvalid(string.Format(LMoveSourceTR, GetMoveName(pk, i)))); } } else @@ -884,7 +884,7 @@ private void VerifyTechRecordSV(LegalityAnalysis data, PK9 pk) break; } if (!preEvoHas) - data.AddLine(GetInvalid(string.Format(LMoveSourceTR, GetMoveName(i)))); + data.AddLine(GetInvalid(string.Format(LMoveSourceTR, GetMoveName(pk, i)))); } } } diff --git a/PKHeX.Core/PKM/Util/Conversion/FormConverter.cs b/PKHeX.Core/PKM/Util/Conversion/FormConverter.cs index 66c2afbe9..17272aaf2 100644 --- a/PKHeX.Core/PKM/Util/Conversion/FormConverter.cs +++ b/PKHeX.Core/PKM/Util/Conversion/FormConverter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using static PKHeX.Core.Species; using static PKHeX.Core.EntityContext; @@ -907,11 +908,26 @@ private static string[] GetFormsGalarSlowbro(IReadOnlyList types, IReadO public static string GetGigantamaxName(IReadOnlyList forms) => forms[Gigantamax]; + private const byte AlcremieCountDecoration = 7; + private const byte AlcremieCountForms = 9; + private const byte AlcremieCountDifferent = AlcremieCountDecoration * AlcremieCountForms; + + /// + /// Used to enumerate the possible combinations of Alcremie forms and decorations. + /// + /// Form names + /// + /// Used for Pokédex display listings. + /// > public static string[] GetAlcremieFormList(IReadOnlyList forms) { - const int deco = 7; - const byte fc = 9; - var result = new string[deco * fc]; // 63 + var result = new string[AlcremieCountDifferent]; // 63 + SetAlcremieFormList(forms, result); + return result; + } + + private static void SetAlcremieFormList(IReadOnlyList forms, Span result) + { SetDecorations(result, 0, forms[(int)Alcremie]); // Vanilla Cream SetDecorations(result, 1, forms[RubyCream]); SetDecorations(result, 2, forms[MatchaCream]); @@ -922,12 +938,12 @@ public static string[] GetAlcremieFormList(IReadOnlyList forms) SetDecorations(result, 7, forms[CaramelSwirl]); SetDecorations(result, 8, forms[RainbowSwirl]); - return result; + return; - static void SetDecorations(string[] result, int f, string baseName) + static void SetDecorations(Span result, [ConstantExpected] byte f, string baseName) { - int start = f * deco; - var slice = result.AsSpan(start, deco); + int start = f * AlcremieCountDecoration; + var slice = result.Slice(start, AlcremieCountDecoration); for (int i = 0; i < slice.Length; i++) slice[i] = $"{baseName} ({(AlcremieDecoration)i})"; } diff --git a/PKHeX.Core/PKM/Util/EntityContext.cs b/PKHeX.Core/PKM/Util/EntityContext.cs index 737206695..89e5842b1 100644 --- a/PKHeX.Core/PKM/Util/EntityContext.cs +++ b/PKHeX.Core/PKM/Util/EntityContext.cs @@ -13,6 +13,7 @@ namespace PKHeX.Core; /// public enum EntityContext : byte { + // Generation numerically so we can cast to and from int for most cases. None = 0, Gen1 = 1, Gen2 = 2, @@ -24,16 +25,28 @@ public enum EntityContext : byte Gen8 = 8, Gen9 = 9, + /// + /// Internal separator to pivot between adjacent contexts. + /// SplitInvalid, + /// Let's Go, Pikachu! & Let's Go, Eevee! Gen7b, + /// Legends: Arceus Gen8a, + /// Brilliant Diamond & Shining Pearl Gen8b, + /// + /// Internal separator to bounds check count. + /// MaxInvalid, } public static class EntityContextExtensions { + /// + /// Get the generation number of the context. + /// public static int Generation(this EntityContext value) => value < SplitInvalid ? (int)value : value switch { Gen7b => 7, @@ -42,8 +55,16 @@ public static class EntityContextExtensions _ => throw new ArgumentOutOfRangeException(nameof(value), value, null), }; + /// + /// Checks if the context is a defined value assigned to a valid context. + /// + /// True if the context is valid. public static bool IsValid(this EntityContext value) => value is not (0 or SplitInvalid) and < MaxInvalid; + /// + /// Get a pre-defined single game version associated with the context. + /// + /// Game ID choice here is the developer's choice; if multiple game sets exist for a context, one from the most recent was chosen. public static GameVersion GetSingleGameVersion(this EntityContext value) => value switch { Gen1 => GameVersion.RD, @@ -63,6 +84,9 @@ public static class EntityContextExtensions _ => throw new ArgumentOutOfRangeException(nameof(value), value, null), }; + /// + /// Get the game console associated with the context. + /// public static GameConsole GetConsole(this EntityContext value) => value switch { Gen1 or Gen2 => GameConsole.GB, @@ -74,8 +98,15 @@ public static class EntityContextExtensions _ => throw new ArgumentOutOfRangeException(nameof(value), value, null), }; + /// + /// Gets all values that fall within the context. + /// public static GameVersion[] GetVersionsWithin(this EntityContext value, GameVersion[] source) => value.GetVersionLump().GetVersionsWithinRange(source); + /// + /// Gets the corresponding lumped value for the context. + /// + /// Shouldn't really use this; see . public static GameVersion GetVersionLump(this EntityContext value) => value switch { Gen1 => GameVersion.Gen1, @@ -95,6 +126,9 @@ public static class EntityContextExtensions _ => throw new ArgumentOutOfRangeException(nameof(value), value, null), }; + /// + /// Gets the corresponding value for the . + /// public static EntityContext GetContext(this GameVersion version) => version switch { GameVersion.GP or GameVersion.GE or GameVersion.GO => Gen7b, diff --git a/PKHeX.Core/PKM/Util/GameConsole.cs b/PKHeX.Core/PKM/Util/GameConsole.cs index b8732a984..d7e1c21e0 100644 --- a/PKHeX.Core/PKM/Util/GameConsole.cs +++ b/PKHeX.Core/PKM/Util/GameConsole.cs @@ -3,7 +3,10 @@ namespace PKHeX.Core; /// /// Hardware console types. /// -/// Related to ; no need to specify side-game consoles like the N64 as they're tied to the mainline console. +/// +/// Related to ; no need to specify side-game consoles like the N64 as they're tied to the mainline console. +/// Console revisions (like GameBoy Color) or 3DS-XL are not included, again, only care about console limitations that run the games. +/// public enum GameConsole : byte { /// Invalid console type. diff --git a/PKHeX.Core/Saves/SAV3GCMemoryCard.cs b/PKHeX.Core/Saves/SAV3GCMemoryCard.cs index a17e43734..b3864aa36 100644 --- a/PKHeX.Core/Saves/SAV3GCMemoryCard.cs +++ b/PKHeX.Core/Saves/SAV3GCMemoryCard.cs @@ -321,9 +321,14 @@ private string GCISaveGameName() int offset = (DirectoryBlock_Used * BLOCK_SIZE) + (EntrySelected * DENTRY_SIZE); string GameCode = EncodingType.GetString(Data, offset, 4); string Makercode = EncodingType.GetString(Data, offset + 0x04, 2); - string FileName = EncodingType.GetString(Data, offset + 0x08, DENTRY_STRLEN); - return $"{Makercode}-{GameCode}-{Util.TrimFromZero(FileName)}.gci"; + Span FileName = stackalloc char[DENTRY_STRLEN]; + EncodingType.GetString(Data.AsSpan(offset + 0x08, DENTRY_STRLEN)); + var zero = FileName.IndexOf('\0'); + if (zero >= 0) + FileName = FileName[..zero]; + + return $"{Makercode}-{GameCode}-{FileName}.gci"; } public ReadOnlyMemory ReadSaveGameData() diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GP1.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GP1.cs index 1c2d606eb..7d1a033a9 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GP1.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GP1.cs @@ -51,8 +51,18 @@ private static GP1 FromData(ReadOnlySpan span) public static void InitializeBlank(Span data) => Blank20.CopyTo(data); - public string Username1 => Util.TrimFromZero(Encoding.ASCII.GetString(Data.AsSpan(0x00, 0x10))); - public string Username2 => Util.TrimFromZero(Encoding.ASCII.GetString(Data.AsSpan(0x10, 0x10))); + private static ReadOnlySpan GetLength(ReadOnlySpan buffer) + { + var length = buffer.IndexOf((byte)0); + if (length == -1) + return buffer; + return buffer[..length]; + } + + private static string GetString(ReadOnlySpan buffer) => Encoding.ASCII.GetString(GetLength(buffer)); + + public string Username1 => GetString(Data.AsSpan(0x00, 0x10)); + public string Username2 => GetString(Data.AsSpan(0x10, 0x10)); public ushort Species => ReadUInt16LittleEndian(Data.AsSpan(0x28)); // s32, just read as u16 public int CP => ReadInt32LittleEndian(Data.AsSpan(0x2C)); @@ -104,9 +114,9 @@ public byte WeightScalar public int Move1 => ReadInt32LittleEndian(Data.AsSpan(0x74)); // uses Go Indexes public int Move2 => ReadInt32LittleEndian(Data.AsSpan(0x78)); // uses Go Indexes - public string GeoCityName => Util.TrimFromZero(Encoding.ASCII.GetString(Data, 0x7C, 0x60)); // dunno length + public string GeoCityName => GetString(Data.AsSpan(0x7C, 0x60)); // dunno length - public string Nickname => Util.TrimFromZero(Encoding.ASCII.GetString(Data, 0x12D, 0x20)); // dunno length + public string Nickname => GetString(Data.AsSpan(0x12D, 0x20)); // dunno length public static readonly IReadOnlyList Genders = GameInfo.GenderSymbolASCII; public string GenderString => (uint) Gender >= Genders.Count ? string.Empty : Genders[Gender]; diff --git a/PKHeX.Core/Saves/Substructures/Gen7/QR7.cs b/PKHeX.Core/Saves/Substructures/Gen7/QR7.cs index 7b0a1f728..2b226012e 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/QR7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/QR7.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; @@ -20,20 +19,30 @@ namespace PKHeX.Core; /// public static class QR7 { - private static readonly HashSet GenderDifferences = new() + public const int SIZE = 0x1A2; + + private static ReadOnlySpan GenderDifferences => new byte[] { - 003, 012, 019, 020, 025, 026, 041, 042, 044, 045, - 064, 065, 084, 085, 097, 111, 112, 118, 119, 123, - 129, 130, 154, 165, 166, 178, 185, 186, 190, 194, - 195, 198, 202, 203, 207, 208, 212, 214, 215, 217, - 221, 224, 229, 232, 255, 256, 257, 267, 269, 272, - 274, 275, 307, 308, 315, 316, 317, 322, 323, 332, - 350, 369, 396, 397, 398, 399, 400, 401, 402, 403, - 404, 405, 407, 415, 417, 418, 419, 424, 443, 444, - 445, 449, 450, 453, 454, 456, 457, 459, 460, 461, - 464, 465, 473, 521, 592, 593, 668, 678, + 0x08, 0x10, 0x18, 0x06, 0x00, 0x36, 0x00, 0x00, 0x03, 0x00, + 0x30, 0x00, 0x02, 0x80, 0xC1, 0x08, 0x06, 0x00, 0x00, 0x04, + 0x60, 0x00, 0x04, 0x46, 0x4C, 0x8C, 0xD1, 0x22, 0x21, 0x01, + 0x00, 0x80, 0x03, 0x28, 0x0D, 0x00, 0x00, 0x00, 0x18, 0x38, + 0x0C, 0x10, 0x00, 0x40, 0x00, 0x00, 0x02, 0x00, 0x00, 0xF0, + 0xBF, 0x80, 0x0E, 0x01, 0x00, 0x38, 0x66, 0x3B, 0x03, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x40, }; + private static bool IsGenderDifferent(ushort species) + { + var index = species >> 3; + var table = GenderDifferences; + if (index >= table.Length) + return false; + return (table[index] & (1 << (species & 7))) != 0; + } + private static void GetRawQR(Span dest, ushort species, byte form, bool shiny, byte gender) { dest[..6].Fill(0xFF); @@ -48,7 +57,7 @@ private static void GetRawQR(Span dest, ushort species, byte form, bool sh else if (pi.Genderless) gender = 2; else - biGender = !GenderDifferences.Contains(species); + biGender = !IsGenderDifferent(species); dest[0x2A] = form; dest[0x2B] = gender; @@ -58,21 +67,21 @@ private static void GetRawQR(Span dest, ushort species, byte form, bool sh public static byte[] GenerateQRData(PK7 pk7, int box = 0, int slot = 0, int num_copies = 1) { - if (box > 31) - box = 31; - if (slot > 29) - slot = 29; - if (box < 0) - box = 0; - if (slot < 0) - slot = 0; - if (num_copies < 0) - num_copies = 1; + byte[] data = new byte[SIZE]; + SetQRData(pk7, data, box, slot, num_copies); + return data; + } + + public static void SetQRData(PK7 pk7, Span span, int box = 0, int slot = 0, int num_copies = 1) + { + box = Math.Clamp(box, 0, 31); + slot = Math.Clamp(slot, 0, 29); + num_copies = Math.Min(num_copies, 1); + if (span.Length < SIZE) + throw new ArgumentException($"Span must be at least {SIZE} bytes long.", nameof(span)); - byte[] data = new byte[0x1A2]; - var span = data.AsSpan(); WriteUInt32LittleEndian(span, 0x454B4F50); // POKE magic - data[0x4] = 0xFF; // QR Type + span[0x4] = 0xFF; // QR Type WriteInt32LittleEndian(span[0x08..], box); WriteInt32LittleEndian(span[0x0C..], slot); WriteInt32LittleEndian(span[0x10..], num_copies); // No need to check max num_copies, payload parser handles it on-console. @@ -82,6 +91,5 @@ public static byte[] GenerateQRData(PK7 pk7, int box = 0, int slot = 0, int num_ var chk = Checksums.CRC16Invert(span[..0x1A0]); WriteUInt16LittleEndian(span[0x1A0..], chk); - return data; } } diff --git a/PKHeX.Core/Util/Util.cs b/PKHeX.Core/Util/Util.cs index e972d615c..5c7925168 100644 --- a/PKHeX.Core/Util/Util.cs +++ b/PKHeX.Core/Util/Util.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; namespace PKHeX.Core; @@ -141,10 +140,22 @@ public static void GetBytesFromHexString(ReadOnlySpan input, Span re private const string HexChars = "0123456789ABCDEF"; + /// + /// Converts the byte array into a hex string (non-spaced, bytes in reverse order). + /// public static string GetHexStringFromBytes(ReadOnlySpan data) { System.Diagnostics.Debug.Assert(data.Length is (4 or 8 or 12 or 16)); Span result = stackalloc char[data.Length * 2]; + GetHexStringFromBytes(data, result); + return new string(result); + } + + /// + public static void GetHexStringFromBytes(ReadOnlySpan data, Span result) + { + if (result.Length != data.Length * 2) + throw new ArgumentException("Result buffer must be twice the size of the input buffer."); for (int i = 0; i < data.Length; i++) { // Write tuples from the opposite side of the result buffer. @@ -152,7 +163,6 @@ public static string GetHexStringFromBytes(ReadOnlySpan data) result[offset + 0] = HexChars[data[i] >> 4]; result[offset + 1] = HexChars[data[i] & 0xF]; } - return new string(result); } /// @@ -164,14 +174,21 @@ public static string GetOnlyHex(ReadOnlySpan str) if (str.IsWhiteSpace()) return string.Empty; - int ctr = 0; Span result = stackalloc char[str.Length]; + int ctr = GetOnlyHex(str, ref result); + return new string(result[..ctr]); + } + + /// + public static int GetOnlyHex(ReadOnlySpan str, ref Span result) + { + int ctr = 0; foreach (var c in str) { if (char.IsAsciiHexDigit(c)) result[ctr++] = c; } - return new string(result[..ctr]); + return ctr; } /// @@ -184,6 +201,13 @@ public static string ToTitleCase(ReadOnlySpan span) return string.Empty; Span result = stackalloc char[span.Length]; + ToTitleCase(span, result); + return new string(result); + } + + /// + public static void ToTitleCase(ReadOnlySpan span, Span result) + { // Add each word to the string builder. Continue from the first index that isn't a space. // Add the first character as uppercase, then add each successive character as lowercase. bool first = true; @@ -205,7 +229,6 @@ public static string ToTitleCase(ReadOnlySpan span) } result[i] = c; } - return new string(result); } /// @@ -213,12 +236,15 @@ public static string ToTitleCase(ReadOnlySpan span) /// /// String to trim. /// Trimmed string. - public static string TrimFromZero(string input) => TrimFromFirst(input, '\0'); + public static ReadOnlySpan TrimFromZero(ReadOnlySpan input) => TrimFromFirst(input, '\0'); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string TrimFromFirst(string input, char c) + private static ReadOnlySpan TrimFromFirst(ReadOnlySpan input, char c) { int index = input.IndexOf(c); return index < 0 ? input : input[..index]; } + + /// + public static string TrimFromZero(string input) => TrimFromZero(input.AsSpan()).ToString(); } diff --git a/PKHeX.Drawing.Misc/QR/QRImageUtil.cs b/PKHeX.Drawing.Misc/QR/QRImageUtil.cs index 2b0f857ae..286a1ee23 100644 --- a/PKHeX.Drawing.Misc/QR/QRImageUtil.cs +++ b/PKHeX.Drawing.Misc/QR/QRImageUtil.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Drawing; namespace PKHeX.Drawing.Misc; @@ -11,7 +11,7 @@ public static Bitmap GetQRImage(Image qr, Image preview) var foreground = new Bitmap(preview.Width + 4, preview.Height + 4); using (Graphics gfx = Graphics.FromImage(foreground)) { - gfx.FillRectangle(new SolidBrush(Color.White), 0, 0, foreground.Width, foreground.Height); + gfx.FillRectangle(Brushes.White, 0, 0, foreground.Width, foreground.Height); int x = (foreground.Width / 2) - (preview.Width / 2); int y = (foreground.Height / 2) - (preview.Height / 2); gfx.DrawImage(preview, x, y); @@ -25,17 +25,17 @@ public static Bitmap GetQRImage(Image qr, Image preview) } } - public static Bitmap GetQRImageExtended(Font font, Image qr, Image pk, int width, int height, string[] lines, string extraText) + public static Bitmap GetQRImageExtended(Font font, Image qr, Image pk, int width, int height, ReadOnlySpan lines, string extraText) { var pic = GetQRImage(qr, pk); return ExtendImage(font, qr, width, height, pic, lines, extraText); } - private static Bitmap ExtendImage(Font font, Image qr, int width, int height, Image pic, string[] lines, string extraText) + private static Bitmap ExtendImage(Font font, Image qr, int width, int height, Image pic, ReadOnlySpan lines, string extraText) { var newpic = new Bitmap(width, height); using Graphics g = Graphics.FromImage(newpic); - g.FillRectangle(new SolidBrush(Color.White), 0, 0, newpic.Width, newpic.Height); + g.FillRectangle(Brushes.White, 0, 0, newpic.Width, newpic.Height); g.DrawImage(pic, 0, 0); g.DrawString(GetLine(lines, 0), font, Brushes.Black, new PointF(18, qr.Height - 5)); @@ -46,5 +46,5 @@ private static Bitmap ExtendImage(Font font, Image qr, int width, int height, Im return newpic; } - private static string GetLine(string[] lines, int line) => lines.Length <= line ? string.Empty : lines[line]; + private static string GetLine(ReadOnlySpan lines, int line) => lines.Length <= line ? string.Empty : lines[line]; } diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen7/SAV_PokedexGG.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen7/SAV_PokedexGG.cs index d992c7797..29eee8e78 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/Gen7/SAV_PokedexGG.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen7/SAV_PokedexGG.cs @@ -134,7 +134,7 @@ private bool FillLBForms() return false; } - // sanity check forms -- SM does not have totem form dex bits + // sanity check forms -- GG does not have totem form dex bits int count = SAV.Personal[bspecies].FormCount; if (count < ds.Count) ds.RemoveAt(count); // remove last diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen8/SAV_PokedexLA.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen8/SAV_PokedexLA.cs index 1472e6c83..029a9cbc6 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/Gen8/SAV_PokedexLA.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen8/SAV_PokedexLA.cs @@ -167,8 +167,8 @@ private bool FillLBForms(int index) if (!hasForms) return false; - var ds = FormConverter.GetFormList(species, GameInfo.Strings.types, GameInfo.Strings.forms, Main.GenderSymbols, SAV.Context).ToList(); - if (ds.Count == 1 && string.IsNullOrEmpty(ds[0])) + var ds = FormConverter.GetFormList(species, GameInfo.Strings.types, GameInfo.Strings.forms, Main.GenderSymbols, SAV.Context); + if (ds.Length == 1 && string.IsNullOrEmpty(ds[0])) { // empty LB_Forms.Enabled = CB_DisplayForm.Enabled = false; diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen8/SAV_PokedexSWSH.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen8/SAV_PokedexSWSH.cs index 25a84a3b7..436058689 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/Gen8/SAV_PokedexSWSH.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen8/SAV_PokedexSWSH.cs @@ -143,7 +143,7 @@ private static string[] GetFormList(in ushort species) var s = GameInfo.Strings; if (species == (int)Species.Alcremie) return FormConverter.GetAlcremieFormList(s.forms); - return FormConverter.GetFormList(species, s.Types, s.forms, GameInfo.GenderSymbolASCII, EntityContext.Gen8).ToArray(); + return FormConverter.GetFormList(species, s.Types, s.forms, GameInfo.GenderSymbolASCII, EntityContext.Gen8); } private void SetEntry(int index) diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen9/SAV_PokedexSV.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen9/SAV_PokedexSV.cs index 1b28c6735..53864e2d0 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/Gen9/SAV_PokedexSV.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen9/SAV_PokedexSV.cs @@ -179,7 +179,7 @@ private static string[] GetFormList(in ushort species) var s = GameInfo.Strings; if (species == (int)Species.Alcremie) return FormConverter.GetAlcremieFormList(s.forms); - return FormConverter.GetFormList(species, s.Types, s.forms, GameInfo.GenderSymbolASCII, EntityContext.Gen9).ToArray(); + return FormConverter.GetFormList(species, s.Types, s.forms, GameInfo.GenderSymbolASCII, EntityContext.Gen9); } private void SetEntry(int index) diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen9/SAV_PokedexSVKitakami.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen9/SAV_PokedexSVKitakami.cs index b4bfff334..5cd5444c2 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/Gen9/SAV_PokedexSVKitakami.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen9/SAV_PokedexSVKitakami.cs @@ -239,7 +239,7 @@ private static string[] GetFormList(in ushort species) var s = GameInfo.Strings; if (species == (int)Species.Alcremie) return FormConverter.GetAlcremieFormList(s.forms); - return FormConverter.GetFormList(species, s.Types, s.forms, GameInfo.GenderSymbolASCII, EntityContext.Gen9).ToArray(); + return FormConverter.GetFormList(species, s.Types, s.forms, GameInfo.GenderSymbolASCII, EntityContext.Gen9); } private void SetEntry(int index) diff --git a/Tests/PKHeX.Core.Tests/Legality/RaidTests.cs b/Tests/PKHeX.Core.Tests/Legality/RaidTests.cs index 8debfe334..787013fe0 100644 --- a/Tests/PKHeX.Core.Tests/Legality/RaidTests.cs +++ b/Tests/PKHeX.Core.Tests/Legality/RaidTests.cs @@ -15,32 +15,26 @@ public void CheckMatch(string raw, ulong seed) byte[] data = raw.ToByteArray(); var pk8 = new PK8(data); - bool found = false; - var seeds = new XoroMachineSkip(pk8.EncryptionConstant, pk8.PID); - foreach (var s in seeds) - { - if (s != seed) - continue; - found = true; - break; - } + bool found = IsPotentialRaidSeed(pk8.EncryptionConstant, pk8.PID, seed); found.Should().BeTrue(); var la = new LegalityAnalysis(pk8); var enc = la.EncounterMatch; - - var compare = enc switch - { - EncounterStatic8N r => r.Verify(pk8, seed), - EncounterStatic8ND r => r.Verify(pk8, seed), - EncounterStatic8NC r => r.Verify(pk8, seed), - EncounterStatic8U r => r.Verify(pk8, seed), - _ => throw new ArgumentException(nameof(enc)), - }; - compare.Should().BeTrue(); - - var s64 = (ISeedCorrelation64)enc; + if (enc is not ISeedCorrelation64 s64) + throw new ArgumentException(nameof(enc)); s64.TryGetSeed(pk8, out ulong detected).Should().BeTrue(); detected.Should().Be(seed); } + + private static bool IsPotentialRaidSeed(uint ec, uint pid, ulong expect) + { + var seeds = new XoroMachineSkip(ec, pid); + foreach (var seed in seeds) + { + if (seed != expect) + continue; + return true; + } + return false; + } }