diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionHistory.cs b/PKHeX.Core/Legality/Evolutions/EvolutionHistory.cs index d4bfb9e30..5b6224e20 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionHistory.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionHistory.cs @@ -99,4 +99,43 @@ public EvolutionHistory AsSingle(EntityContext context) single.Set(context, Get(context).ToArray()); return single; } + + public EvolutionHistory PruneKeepPreEvolutions(ushort species) => new() + { + Gen1 = PruneKeepPreEvolutions(Gen1, species), + Gen2 = PruneKeepPreEvolutions(Gen2, species), + Gen3 = PruneKeepPreEvolutions(Gen3, species), + Gen4 = PruneKeepPreEvolutions(Gen4, species), + Gen5 = PruneKeepPreEvolutions(Gen5, species), + Gen6 = PruneKeepPreEvolutions(Gen6, species), + Gen7 = PruneKeepPreEvolutions(Gen7, species), + Gen8 = PruneKeepPreEvolutions(Gen8, species), + Gen9 = PruneKeepPreEvolutions(Gen9, species), + Gen7b = PruneKeepPreEvolutions(Gen7b, species), + Gen8a = PruneKeepPreEvolutions(Gen8a, species), + Gen8b = PruneKeepPreEvolutions(Gen8b, species), + Gen9a = PruneKeepPreEvolutions(Gen9a, species), + }; + + private static EvoCriteria[] PruneKeepPreEvolutions(EvoCriteria[] src, ushort species) + { + // Most evolved species is at the lowest index. + // If `species` is at the current index, only keep indexes after. + var start = GetSpeciesIndex(src, species); + if (start == -1) + return src; + if (start == src.Length - 1) + return NONE; + return src[(start + 1)..]; + } + + private static int GetSpeciesIndex(ReadOnlySpan array, ushort species) + { + for (int i = 0; i < array.Length; i++) + { + if (array[i].Species == species) + return i; + } + return -1; + } } diff --git a/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs b/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs index d649d956d..7d0415ea7 100644 --- a/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs +++ b/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs @@ -68,6 +68,7 @@ public static bool IsFormArgEvolution(ushort species) [ (int)Charm, (int)BabyDollEyes, + (int)DisarmingVoice, // Z-A ]; /// @@ -90,34 +91,23 @@ public static bool IsValidEvolutionWithMove(PKM pk, LegalInfo info) var move = GetSpeciesEvolutionMove(species); if (move is NONE) return true; // not a move evolution - if (move is EEVEE) - return IsValidEvolutionWithMoveSylveon(pk, enc, info); if (!IsMoveSlotAvailable(info.Moves)) return false; - if (pk.HasMove(move)) - return true; - // Check the entire chain to see if it could have learnt it at any point. var head = LearnGroupUtil.GetCurrentGroup(pk); - return MemoryPermissions.GetCanKnowMove(enc, move, info.EvoChainsAllGens, pk, head); + var pruned = info.EvoChainsAllGens.PruneKeepPreEvolutions(species); + if (move is EEVEE) + return IsValidEvolutionWithMoveAny(enc, EeveeFairyMoves, pruned, pk, head); + + return MemoryPermissions.GetCanKnowMove(enc, move, pruned, pk, head); } - private static bool IsValidEvolutionWithMoveSylveon(PKM pk, IEncounterTemplate enc, LegalInfo info) + private static bool IsValidEvolutionWithMoveAny(IEncounterTemplate enc, ReadOnlySpan any, EvolutionHistory history, PKM pk, ILearnGroup head) { - if (!IsMoveSlotAvailable(info.Moves)) - return false; - - foreach (var move in EeveeFairyMoves) + foreach (var move in any) { - if (pk.HasMove(move)) - return true; - } - - var head = LearnGroupUtil.GetCurrentGroup(pk); - foreach (var move in EeveeFairyMoves) - { - if (MemoryPermissions.GetCanKnowMove(enc, move, info.EvoChainsAllGens, pk, head)) + if (MemoryPermissions.GetCanKnowMove(enc, move, history, pk, head)) return true; } return false; diff --git a/PKHeX.Core/Legality/Structures/LegalityCheckResultCode.cs b/PKHeX.Core/Legality/Structures/LegalityCheckResultCode.cs index 44a2970ea..b1912cac1 100644 --- a/PKHeX.Core/Legality/Structures/LegalityCheckResultCode.cs +++ b/PKHeX.Core/Legality/Structures/LegalityCheckResultCode.cs @@ -146,7 +146,6 @@ public enum LegalityCheckResultCode : ushort FormPikachuCosplay, FormPikachuCosplayInvalid, FormPikachuEventInvalid, - FormInvalidExpect_0, FormValid, FormVivillon, FormVivillonEventPre, @@ -398,6 +397,7 @@ public enum LegalityCheckResultCode : ushort EvoTradeReqOutsider_0, FormArgumentLEQ_0, FormArgumentGEQ_0, + FormInvalidExpect_0, HyperTrainLevelGEQ_0, // level IVAllEqual_0, IVFlawlessCountGEQ_0, // count diff --git a/PKHeX.Core/Legality/Tables/FormInfo.cs b/PKHeX.Core/Legality/Tables/FormInfo.cs index 7453393b7..2ecf9780a 100644 --- a/PKHeX.Core/Legality/Tables/FormInfo.cs +++ b/PKHeX.Core/Legality/Tables/FormInfo.cs @@ -92,6 +92,9 @@ public static bool IsMegaForm(ushort species, byte form) (ushort)Mimikyu => (byte)(form & 2), (ushort)Ogerpon => (byte)(form & 3), (ushort)Floette => 5, + (ushort)Tatsugiri => (byte)(form - 3), // Mega (form specific) + (ushort)Magearna => (byte)(form - 2), // Mega (form specific) + (ushort)Meowstic => (byte)(form - 2), // Mega (gendered) _ => 0, }; diff --git a/PKHeX.Core/Legality/Verifiers/FormArgumentVerifier.cs b/PKHeX.Core/Legality/Verifiers/FormArgumentVerifier.cs index 8b377173e..0094495ee 100644 --- a/PKHeX.Core/Legality/Verifiers/FormArgumentVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/FormArgumentVerifier.cs @@ -68,11 +68,7 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f) > 9_999 => GetInvalid(FormArgumentLEQ_0, 9999), _ => arg == 0 || HasVisitedPLA(data, Stantler) ? GetValid(FormArgumentValid) : GetInvalid(FormArgumentNotAllowed), }, - Primeape => arg switch - { - > 9_999 => GetInvalid(FormArgumentLEQ_0, 9999), - _ => arg == 0 || HasVisitedSV(data, Primeape) || HasVisitedZA(data, Primeape) ? GetValid(FormArgumentValid) : GetInvalid(FormArgumentNotAllowed), - }, + Primeape => CheckPrimeape(data, pk, arg, enc), Bisharp => arg switch { > 9_999 => GetInvalid(FormArgumentLEQ_0, 9999), @@ -123,6 +119,27 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f) }; } + private CheckResult CheckPrimeape(LegalityAnalysis data, PKM pk, uint arg, IEncounterable enc) + { + if (arg == 0) + return GetValid(FormArgumentValid); + if (arg > 9_999) + return GetInvalid(FormArgumentLEQ_0, 9999); + + if (HasVisitedSV(data, Primeape) || HasVisitedZA(data, Primeape)) + { + const ushort move = (ushort)Move.RageFist; + // Eager check + if (pk.HasMove(move)) + return GetValid(FormArgumentValid); + + var head = LearnGroupUtil.GetCurrentGroup(pk); + if (MemoryPermissions.GetCanKnowMove(enc, move, data.Info.EvoChainsAllGens, pk, head)) + return GetValid(FormArgumentValid); + } + return GetInvalid(FormArgumentLEQ_0, 0); // Can't increase from 0. + } + private CheckResult CheckFarfetchd(LegalityAnalysis data, PKM pk, uint arg) { if (arg == 0) diff --git a/PKHeX.Core/Legality/Verifiers/FormVerifier.cs b/PKHeX.Core/Legality/Verifiers/FormVerifier.cs index a698b70aa..0e41dca4f 100644 --- a/PKHeX.Core/Legality/Verifiers/FormVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/FormVerifier.cs @@ -143,7 +143,7 @@ private CheckResult VerifyForm(LegalityAnalysis data) if (enc is not EncounterGift9a) return GetInvalid(FormEternalInvalid); return GetValid(FormEternal); - case Meowstic when form != pk.Gender: + case Meowstic when (form & 1) != pk.Gender: return GetInvalid(GenderInvalidNone); case Silvally: @@ -197,7 +197,14 @@ private CheckResult VerifyBattleForms9a(LegalityAnalysis data, ushort species, b // Battle forms can exist in Party. if (!FormInfo.IsMegaForm(species, form) && !FormInfo.IsPrimalForm(species, form)) + { + // Morpeko-1 can exist in party, but only if it's at a level where it can know the move Aura Wheel; only learns starting at Lv. 57. + // You can use move menu to forget Aura Wheel while it's in Hangry Mode, but the form doesn't revert, kinda like Mega Rayquaza with Dragon Ascent + if (species is (int)Morpeko && !MemoryPermissions.GetCanKnowMove(data.EncounterMatch, (ushort)Move.AuraWheel, data.Info.EvoChainsAllGens, data.Entity, LearnGroupUtil.GetCurrentGroup(data.Entity))) + return GetInvalid(FormInvalidExpect_0, 0); + return VALID; + } var megaStone = ItemStorage9ZA.GetExpectedMegaStoneOrPrimalOrb(species, form); if (megaStone == 0 || data.Entity.HeldItem == megaStone) diff --git a/PKHeX.Core/PKM/Util/Conversion/ItemConverter.cs b/PKHeX.Core/PKM/Util/Conversion/ItemConverter.cs index fdbc03812..6c33bef8e 100644 --- a/PKHeX.Core/PKM/Util/Conversion/ItemConverter.cs +++ b/PKHeX.Core/PKM/Util/Conversion/ItemConverter.cs @@ -258,5 +258,12 @@ public static ushort GetTechnicalMachineIndex9a(ushort oldTM) -68, +06, -68, -07, -07, -64, -71, -07, +15, -07, -07, -07, -07, -07, -90, -07, -07, -07, -80, -07, -63, 0, -08, -08, -13, -08, -71, -08, -08, + 0, + +01, +02, +02, +03, +04, +06, +06, +06, +07, +08, + +09, +12, +12, +13, +15, +17, +17, +29, +29, -21, + -20, -19, -17, -16, -15, -15, -12, -11, -10, -09, + -09, -09, -07, -06, -06, -05, -05, -03, -03, -03, + -03, -03, -03, -03, -03, -03, -03, -03, -03, -01, + -01, -01, ]; } diff --git a/PKHeX.Core/Saves/Substructures/Gen9/ZA/TechnicalMachine9a.cs b/PKHeX.Core/Saves/Substructures/Gen9/ZA/TechnicalMachine9a.cs index d330ca6be..cf9bc9b0b 100644 --- a/PKHeX.Core/Saves/Substructures/Gen9/ZA/TechnicalMachine9a.cs +++ b/PKHeX.Core/Saves/Substructures/Gen9/ZA/TechnicalMachine9a.cs @@ -58,15 +58,24 @@ public static class TechnicalMachine9a public static int SetAllTechnicalMachines(SAV9ZA sav, bool collected = false) { int ctr = 0; + var field = sav.Blocks.FieldItems; + var inv = sav.Items; + var finalQuantity = collected ? 1 : 0; foreach (var item in TechnicalMachines) { var hash = FnvHash.HashFnv1a_64(item.FieldItem); - var index = sav.Blocks.FieldItems.GetIndex(hash); + var index = field.GetIndex(hash); if (index == -1) continue; // Shouldn't happen. All TMs should be populated in a save file, except if it's a DLC TM (not applicable). - sav.Blocks.FieldItems.SetValue(index, collected); - sav.Items.SetItemQuantity(item.ItemID, collected ? 1 : 0); + ctr++; + if (field.GetValue(index) != collected) + field.SetValue(index, collected); + else + ctr--; + + if (inv.GetItemQuantity(item.ItemID) != finalQuantity) + inv.SetItemQuantity(item.ItemID, finalQuantity); } return ctr; }