From 9deafa851a1a05286389fbef3bf72fd53cfecfd2 Mon Sep 17 00:00:00 2001 From: Kurt Date: Fri, 6 May 2022 15:43:23 -0700 Subject: [PATCH] Create initial movesets for Alpha entities correctly, verify initial mastery (#3489) * Draft checks for encounter slot mastery * Check encounter mastery flags * Add moves for LA static encounters that don't follow learnset * Add moves on crossover LA static encounters * add alpha moveset population method Now generates and applies moves as the game does Updates some handling of other methods to use Span * Show better message for bad mastery init flags * Insert descending if candidates have same level Level 78 Yanmega: - [01] [10] Quick Attack - [06] [15] Gust - [11] [20] Silver Wind - [18] [28] Hypnosis - [25] [35] Air Slash - [34] [45] Ancient Power - [43] [54] Crunch - [43] [54] Bug Buzz Yields: AlphaMove Crunch* Bug Buzz* Ancient Power * Descending order due to iteration Co-authored-by: Lusamine <30205550+Lusamine@users.noreply.github.com> --- .../Editing/Applicators/MoveApplicator.cs | 11 +- PKHeX.Core/Legality/Core.cs | 1 + .../Legality/Encounters/Data/Encounters8a.cs | 52 +++++----- .../EncounterSlot/EncounterSlot3Swarm.cs | 2 +- .../EncounterSlot/EncounterSlot8a.cs | 79 +++++++++++--- .../EncounterStatic/EncounterStatic.cs | 4 +- .../EncounterStatic/EncounterStatic8a.cs | 97 ++++++++++-------- .../EncounterTrade/EncounterTrade.cs | 4 +- .../Encounters/IMasteryInitialMoveShop8.cs | 6 ++ .../Formatting/LegalityCheckStrings.cs | 1 + PKHeX.Core/Legality/Learnset/Learnset.cs | 44 +++++++- PKHeX.Core/Legality/Verifiers/MiscVerifier.cs | 5 + PKHeX.Core/MysteryGifts/PGT.cs | 3 +- PKHeX.Core/MysteryGifts/WC3.cs | 5 +- PKHeX.Core/PKM/Interfaces/IMoveShop8.cs | 49 +++++++++ PKHeX.Core/PKM/PKM.cs | 12 +-- .../Resources/legality/misc/mastery_la.pkl | Bin 0 -> 15612 bytes 17 files changed, 264 insertions(+), 111 deletions(-) create mode 100644 PKHeX.Core/Legality/Encounters/IMasteryInitialMoveShop8.cs create mode 100644 PKHeX.Core/Resources/legality/misc/mastery_la.pkl diff --git a/PKHeX.Core/Editing/Applicators/MoveApplicator.cs b/PKHeX.Core/Editing/Applicators/MoveApplicator.cs index f9677c4e2..2115b21b1 100644 --- a/PKHeX.Core/Editing/Applicators/MoveApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/MoveApplicator.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; namespace PKHeX.Core { @@ -56,12 +55,12 @@ public static void SetMoves(this PKM pk, int[] moves, bool maxPP = false) /// /// Pokémon to modify. /// to use (if already known). Will fetch the current if not provided. - public static void SetMaximumPPCurrent(this PKM pk, IReadOnlyList moves) + public static void SetMaximumPPCurrent(this PKM pk, ReadOnlySpan moves) { - pk.Move1_PP = moves.Count == 0 ? 0 : pk.GetMovePP(moves[0], pk.Move1_PPUps); - pk.Move2_PP = moves.Count <= 1 ? 0 : pk.GetMovePP(moves[1], pk.Move2_PPUps); - pk.Move3_PP = moves.Count <= 2 ? 0 : pk.GetMovePP(moves[2], pk.Move3_PPUps); - pk.Move4_PP = moves.Count <= 3 ? 0 : pk.GetMovePP(moves[3], pk.Move4_PPUps); + pk.Move1_PP = moves.Length == 0 ? 0 : pk.GetMovePP(moves[0], pk.Move1_PPUps); + pk.Move2_PP = moves.Length <= 1 ? 0 : pk.GetMovePP(moves[1], pk.Move2_PPUps); + pk.Move3_PP = moves.Length <= 2 ? 0 : pk.GetMovePP(moves[2], pk.Move3_PPUps); + pk.Move4_PP = moves.Length <= 3 ? 0 : pk.GetMovePP(moves[3], pk.Move4_PPUps); } /// diff --git a/PKHeX.Core/Legality/Core.cs b/PKHeX.Core/Legality/Core.cs index 5d715d34f..651bc75a1 100644 --- a/PKHeX.Core/Legality/Core.cs +++ b/PKHeX.Core/Legality/Core.cs @@ -54,6 +54,7 @@ public static partial class Legal internal static readonly EggMoves6[] EggMovesBDSP = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_bdsp.pkl"), "bs")); internal static readonly Learnset[] LevelUpBDSP = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_bdsp.pkl"), "bs")); internal static readonly Learnset[] LevelUpLA = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_la.pkl"), "la")); + internal static readonly Learnset[] MasteryLA = LearnsetReader.GetArray(Get(Util.GetBinaryResource("mastery_la.pkl"), "la")); public static IReadOnlyList GetPPTable(PKM pkm, int format) => format switch { diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters8a.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters8a.cs index 9b442b0e3..1c2f40246 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters8a.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters8a.cs @@ -25,16 +25,16 @@ internal static class Encounters8a new(493,000,75,M,M) { Location = 109, FlawlessIVCount = 3, Gift = true, Method = Fixed, Ball = (int)Ball.LAPoke, Fateful = true }, // Arceus // Static Encounters - Scripted Table Slots - new(480,000,70,M,M) { Location = 111, FlawlessIVCount = 3 }, // Uxie - new(481,000,70,M,M) { Location = 104, FlawlessIVCount = 3 }, // Mesprit - new(482,000,70,M,M) { Location = 105, FlawlessIVCount = 3 }, // Azelf - new(485,000,70,M,M) { Location = 068, FlawlessIVCount = 3 }, // Heatran - new(488,000,70,M,M) { Location = 082, FlawlessIVCount = 3 }, // Cresselia + new(480,000,70,M,M) { Location = 111, FlawlessIVCount = 3, Moves = new[] {129,326,832,095} }, // Uxie + new(481,000,70,M,M) { Location = 104, FlawlessIVCount = 3, Moves = new[] {129,326,832,105} }, // Mesprit + new(482,000,70,M,M) { Location = 105, FlawlessIVCount = 3, Moves = new[] {129,458,326,832} }, // Azelf + new(485,000,70,M,M) { Location = 068, FlawlessIVCount = 3, Moves = new[] {442,242,414,463} }, // Heatran + new(488,000,70,M,M) { Location = 082, FlawlessIVCount = 3, Moves = new[] {427,094,585,849} }, // Cresselia - new(641,000,70,M,M) { Location = 090, FlawlessIVCount = 3 }, // Tornadus - new(642,000,70,M,M) { Location = 009, FlawlessIVCount = 3 }, // Thundurus - new(645,000,70,M,M) { Location = 027, FlawlessIVCount = 3 }, // Landorus - new(905,000,70,M,M) { Location = 038, FlawlessIVCount = 3 }, // Enamorus + new(641,000,70,M,M) { Location = 090, FlawlessIVCount = 3, Moves = new[] {326,242,542,846} }, // Tornadus + new(642,000,70,M,M) { Location = 009, FlawlessIVCount = 3, Moves = new[] {326,242,087,847} }, // Thundurus + new(645,000,70,M,M) { Location = 027, FlawlessIVCount = 3, Moves = new[] {326,242,414,848} }, // Landorus + new(905,000,70,M,M) { Location = 038, FlawlessIVCount = 3, Moves = new[] {326,242,585,831} }, // Enamorus new(077,000,15 ) { Location = 014, Shiny = Always}, // Ponyta* new(442,000,60,M,M) { Location = 043, FlawlessIVCount = 3 }, // Spiritomb @@ -42,13 +42,13 @@ internal static class Encounters8a new(570,001,28 ) { Location = 027 }, // Zorua new(570,001,29 ) { Location = 027 }, // Zorua - new(489,000,33 ) { Location = 064, Fateful = true }, // Phione - new(489,000,34 ) { Location = 064, Fateful = true }, // Phione - new(489,000,35 ) { Location = 064, Fateful = true }, // Phione - new(489,000,36 ) { Location = 064, Fateful = true }, // Phione - new(490,000,50,M,M) { Location = 064, FlawlessIVCount = 3, Fateful = true }, // Manaphy - new(491,000,70,M,M) { Location = 010, FlawlessIVCount = 3, Fateful = true }, // Darkrai - new(492,000,70,M,M) { Location = 026, FlawlessIVCount = 3, Fateful = true }, // Shaymin + new(489,000,33 ) { Location = 064, Fateful = true, Moves = new[] {145,352,151,428} }, // Phione + new(489,000,34 ) { Location = 064, Fateful = true, Moves = new[] {145,352,151,428} }, // Phione + new(489,000,35 ) { Location = 064, Fateful = true, Moves = new[] {145,352,151,428} }, // Phione + new(489,000,36 ) { Location = 064, Fateful = true, Moves = new[] {145,352,151,428} }, // Phione + new(490,000,50,M,M) { Location = 064, FlawlessIVCount = 3, Fateful = true, Moves = new[] {352,428,585,145} }, // Manaphy + new(491,000,70,M,M) { Location = 010, FlawlessIVCount = 3, Fateful = true, Moves = new[] {506,399,094,464} }, // Darkrai + new(492,000,70,M,M) { Location = 026, FlawlessIVCount = 3, Fateful = true, Moves = new[] {403,412,414,465} }, // Shaymin // Unown Notes new(201,000,25,U) { Location = 040, Method = Fixed }, // Unown A @@ -88,10 +88,10 @@ internal static class Encounters8a new(201,024,25,U) { Location = 097, Method = Fixed }, // Unown Y new(201,006,25,U) { Location = 007, Method = Fixed }, // Unown G - new(642,000,70,M,M) { Location = 059, FlawlessIVCount = 3 }, // Thundurus (Lunker’s Lair) - new(642,000,70,M,M) { Location = 129, FlawlessIVCount = 3 }, // Thundurus (Sand’s Reach) - new(488,000,70,M,M) { Location = 010, FlawlessIVCount = 3 }, // Cresselia (Coronet Highlands) - new(491,000,70,M,M) { Location = 074, FlawlessIVCount = 3, Fateful = true }, // Darkrai (Lonely Spring) + new(642,000,70,M,M) { Location = 059, FlawlessIVCount = 3, Moves = new[] {326,242,087,847} }, // Thundurus (Lunker’s Lair) + new(642,000,70,M,M) { Location = 129, FlawlessIVCount = 3, Moves = new[] {326,242,087,847} }, // Thundurus (Sand’s Reach) + new(488,000,70,M,M) { Location = 010, FlawlessIVCount = 3, Moves = new[] {427,094,585,849} }, // Cresselia (Coronet Highlands) + new(491,000,70,M,M) { Location = 074, FlawlessIVCount = 3, Fateful = true, Moves = new[] {506,399,094,464} }, // Darkrai (Lonely Spring) // Static Encounters new(046,000,50,M,M) { Location = 019, Method = Fixed }, // paras01: Paras @@ -106,11 +106,11 @@ internal static class Encounters8a new(486,000,70,M,M) { Location = 095, Method = Fixed, FlawlessIVCount = 3 }, // regigigas01: Regigigas new(487,001,70,M,M) { Location = 067, Method = Fixed, FlawlessIVCount = 3 }, // giratina02: Giratina-1 - new(362,000,64,A,A) { Location = 011, Method = Fixed, IsAlpha = true, Moves = new[] {442,059,556,242}, Mastery = new[] {true,true,true, true } }, // onigohri01: Glalie - new(402,000,12,A,A) { Location = 007, Method = Fixed, IsAlpha = true, Gender = 0, Moves = new[] {206,071,033,332}, Mastery = new[] {true,true,false,false} }, // mev002: Kricketune - new(416,000,60,A,A) { Location = 022, Method = Fixed, IsAlpha = true, Gender = 1, FlawlessIVCount = 3, Moves = new[] {188,403,408,405}, Mastery = new[] {true,true,true ,true } }, // beequen01: Vespiquen - new(571,001,58,M,M) { Location = 111, Method = Fixed, IsAlpha = true, Moves = new[] {555,421,841,417}, Mastery = new[] {true,true,true ,true } }, // zoroark01: Zoroark-1 - new(706,001,58,M,M) { Location = 104, Method = Fixed, IsAlpha = true, Moves = new[] {231,406,842,056}, Mastery = new[] {true,true,true ,true } }, // numelgon01: Goodra-1 - new(904,000,58,M,M) { Location = 105, Method = Fixed, IsAlpha = true, Moves = new[] {301,398,401,038}, Mastery = new[] {true,true,true ,false} }, // harysen01: Overqwil + new(362,000,64,A,A) { Location = 011, Method = Fixed, IsAlpha = true, Moves = new[] {442,059,556,242} }, // onigohri01: Glalie + new(402,000,12,A,A) { Location = 007, Method = Fixed, IsAlpha = true, Gender = 0, Moves = new[] {206,071,033,332} }, // mev002: Kricketune + new(416,000,60,A,A) { Location = 022, Method = Fixed, IsAlpha = true, Gender = 1, FlawlessIVCount = 3, Moves = new[] {188,403,408,405} }, // beequen01: Vespiquen + new(571,001,58,M,M) { Location = 111, Method = Fixed, IsAlpha = true, Moves = new[] {555,421,841,417} }, // zoroark01: Zoroark-1 + new(706,001,58,M,M) { Location = 104, Method = Fixed, IsAlpha = true, Moves = new[] {231,406,842,056} }, // numelgon01: Goodra-1 + new(904,000,58,M,M) { Location = 105, Method = Fixed, IsAlpha = true, Moves = new[] {301,398,401,038} }, // harysen01: Overqwil }; } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3Swarm.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3Swarm.cs index ab1f1f95c..19d8bd941 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3Swarm.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3Swarm.cs @@ -18,7 +18,7 @@ internal sealed record EncounterSlot3Swarm : EncounterSlot3, IMoveset protected override void SetEncounterMoves(PKM pk, GameVersion version, int level) { - var moves = Moves; + var moves = (int[])Moves; pk.SetMoves(moves); pk.SetMaximumPPCurrent(moves); } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8a.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8a.cs index 6d2f4fd90..5ac2ed621 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8a.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8a.cs @@ -6,7 +6,7 @@ namespace PKHeX.Core; /// Encounter Slot found in . /// /// -public sealed record EncounterSlot8a : EncounterSlot, IAlpha +public sealed record EncounterSlot8a : EncounterSlot, IAlpha, IMasteryInitialMoveShop8 { public override int Generation => 8; public SlotType Type => Area.Type; @@ -25,9 +25,9 @@ public EncounterSlot8a(EncounterArea8a area, ushort species, byte form, byte min public bool HasAlphaMove => IsAlpha && Type is not SlotType.Landmark; - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + protected override void SetPINGA(PKM pk, EncounterCriteria criteria) { - base.ApplyDetails(sav, criteria, pk); + base.SetPINGA(pk, criteria); if (Gender != Gender.Random) pk.Gender = (int)Gender; @@ -35,20 +35,39 @@ protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteri var (_, slotSeed) = Overworld8aRNG.ApplyDetails(pk, criteria, para, HasAlphaMove); if (LevelMin != LevelMax) pk.CurrentLevel = pk.Met_Level = Overworld8aRNG.GetRandomLevel(slotSeed, LevelMin, LevelMax); + } + protected override void SetEncounterMoves(PKM pk, GameVersion version, int level) + { + var pa8 = (PA8)pk; + Span moves = stackalloc int[4]; + var index = PersonalTable.LA.GetFormIndex(Species, Form); + var mastery = Legal.MasteryLA[index]; + var learn = Legal.LevelUpLA[index]; + if (pa8.AlphaMove != 0) + { + moves[0] = pa8.AlphaMove; + learn.SetEncounterMovesBackwards(level, moves, 1); + } + else + { + learn.SetEncounterMoves(level, moves); + } + pk.SetMoves(moves); + pk.SetMaximumPPCurrent(moves); + foreach (var move in moves) + { + if (mastery.GetMoveLevel(move) <= level) + pa8.SetMasteryFlagMove(move); + } + } + + protected override void SetFormatSpecificData(PKM pk) + { + var pa8 = (PA8)pk; if (IsAlpha) - { - if (pk is IAlpha a) - a.IsAlpha = true; - if (pk is PA8 { AlphaMove: not 0 } pa) - pk.PushMove(pa.AlphaMove); - } - - if (pk is PA8 pa8) - { - pa8.HeightScalarCopy = pa8.HeightScalar; - pa8.SetMasteryFlags(); - } + pa8.IsAlpha = true; + pa8.HeightScalarCopy = pa8.HeightScalar; } protected override void ApplyDetailsBall(PKM pk) => pk.Ball = (int)Ball.LAPoke; @@ -71,8 +90,10 @@ private EncounterMatchRating GetMatchRatingInternal(PKM pkm) return EncounterMatchRating.DeferredErrors; if (IsFormArgMismatch(pkm)) return EncounterMatchRating.DeferredErrors; + if (!IsForcedMasteryCorrect(pkm)) + return EncounterMatchRating.DeferredErrors; - return GetAlphaMoveCompatibility(pkm); + return GetMoveCompatibility(pkm); } private bool IsFormArgMismatch(PKM pkm) => pkm.Species switch @@ -83,7 +104,7 @@ private EncounterMatchRating GetMatchRatingInternal(PKM pkm) _ => false, }; - private EncounterMatchRating GetAlphaMoveCompatibility(PKM pkm) + private EncounterMatchRating GetMoveCompatibility(PKM pkm) { // Check for Alpha move compatibility. if (pkm is not PA8 pa) @@ -115,6 +136,30 @@ private EncounterMatchRating GetAlphaMoveCompatibility(PKM pkm) return EncounterMatchRating.Match; } + public bool IsForcedMasteryCorrect(PKM pkm) + { + if (pkm is not IMoveShop8Mastery p) + return true; // Can't check. + + Span moves = stackalloc int[4]; + var level = pkm.Met_Level; + var index = PersonalTable.LA.GetFormIndex(Species, Form); + var mastery = Legal.MasteryLA[index]; + var learn = Legal.LevelUpLA[index]; + ushort alpha = 0; + if (pkm is PA8 { AlphaMove: not 0 } pa8) + { + moves[0] = alpha = pa8.AlphaMove; + learn.SetEncounterMovesBackwards(level, moves, 1); + } + else + { + learn.SetEncounterMoves(level, moves); + } + + return p.IsValidMasteredEncounter(moves, mastery, level, alpha); + } + private OverworldParam8a GetParams() { var pt = PersonalTable.LA; diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs index dd669e476..6ae33b759 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs @@ -156,9 +156,9 @@ protected virtual void SetMetData(PKM pk, int level, DateTime today) pk.MetDate = today; } - private void SetEncounterMoves(PKM pk, GameVersion version, int level) + protected virtual void SetEncounterMoves(PKM pk, GameVersion version, int level) { - var moves = Moves.Count > 0 ? Moves : MoveLevelUp.GetEncounterMoves(pk, level, version); + var moves = Moves.Count > 0 ? (int[])Moves : MoveLevelUp.GetEncounterMoves(pk, level, version); pk.SetMoves(moves); pk.SetMaximumPPCurrent(moves); } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs index 06729dc18..dd26210a4 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs @@ -6,9 +6,8 @@ namespace PKHeX.Core; /// Generation 8 Static Encounter /// /// -public sealed record EncounterStatic8a(GameVersion Version) : EncounterStatic(Version), IAlpha +public sealed record EncounterStatic8a(GameVersion Version) : EncounterStatic(Version), IAlpha, IMasteryInitialMoveShop8 { - public bool[]? Mastery; public override int Generation => 8; public byte HeightScalar { get; } @@ -33,31 +32,20 @@ public EncounterStatic8a(ushort species, ushort form, byte level, byte h = NoSca protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) { base.ApplyDetails(sav, criteria, pk); - if (pk is IScaledSize s) - { - if (HasFixedHeight) - s.HeightScalar = HeightScalar; - if (HasFixedWeight) - s.WeightScalar = WeightScalar; - if (pk is IScaledSizeValue v) - { - v.ResetHeight(); - v.ResetWeight(); - } - } - if (IsAlpha && pk is IAlpha a) - a.IsAlpha = true; + var pa = (PA8)pk; - if (pk is PA8 pa) - { - if (IsAlpha && Moves.Count != 0) - pa.AlphaMove = (ushort)Moves[0]; - if (pa.AlphaMove != 0) - pk.PushMove(pa.AlphaMove); - pa.SetMasteryFlags(); - pa.HeightScalarCopy = pa.HeightScalar; - } + if (IsAlpha) + pa.IsAlpha = true; + + if (HasFixedHeight) + pa.HeightScalar = HeightScalar; + if (HasFixedWeight) + pa.WeightScalar = WeightScalar; + pa.HeightScalarCopy = pa.HeightScalar; + + pa.ResetHeight(); + pa.ResetWeight(); } protected override void SetPINGA(PKM pk, EncounterCriteria criteria) @@ -97,9 +85,6 @@ public override bool IsMatchExact(PKM pkm, IDexLevel evo) public override EncounterMatchRating GetMatchRating(PKM pkm) { - if (!IsForcedMasteryCorrect(pkm)) - return EncounterMatchRating.PartialMatch; - var result = GetMatchRatingInternal(pkm); var orig = base.GetMatchRating(pkm); return result > orig ? result : orig; @@ -116,39 +101,61 @@ private EncounterMatchRating GetMatchRatingInternal(PKM pkm) if (orig is not EncounterMatchRating.Match) return orig; + if (!IsForcedMasteryCorrect(pkm)) + return EncounterMatchRating.DeferredErrors; + if (IsAlpha && pkm is PA8 { AlphaMove: 0 }) return EncounterMatchRating.Deferred; return EncounterMatchRating.Match; } - private bool IsForcedMasteryCorrect(PKM pkm) + public bool IsForcedMasteryCorrect(PKM pkm) { - if (Mastery is not { } m) - return true; - + ushort alpha = 0; if (IsAlpha && Moves.Count != 0) { - if (pkm is PA8 pa && pa.AlphaMove != Moves[0]) + if (pkm is PA8 pa && (alpha = pa.AlphaMove) != Moves[0]) return false; } if (pkm is not IMoveShop8Mastery p) return true; - for (int i = 0; i < m.Length; i++) - { - if (!m[i]) - continue; - var move = Moves[i]; - var index = p.MoveShopPermitIndexes.IndexOf((ushort)move); - if (index == -1) - continue; // manually mastered for encounter, not a tutor - if (!p.GetMasteredRecordFlag(index)) - return false; - } + Span m = stackalloc int[4]; + var level = pkm.Met_Level; + var index = PersonalTable.LA.GetFormIndex(Species, Form); + var mastery = Legal.MasteryLA[index]; + if (Moves.Count != 0) + m = (int[])Moves; + else + Legal.LevelUpLA[index].SetEncounterMoves(level, m); - return true; + return p.IsValidMasteredEncounter(m, mastery, level, alpha); + } + + protected override void SetEncounterMoves(PKM pk, GameVersion version, int level) + { + var pa8 = (PA8)pk; + Span moves = stackalloc int[4]; + var index = PersonalTable.LA.GetFormIndex(Species, Form); + var mastery = Legal.MasteryLA[index]; + var learn = Legal.LevelUpLA[index]; + if (IsAlpha && Moves.Count != 0) + { + moves = (int[])Moves; + } + else + { + learn.SetEncounterMoves(level, moves); + } + pk.SetMoves(moves); + pk.SetMaximumPPCurrent(moves); + foreach (var move in moves) + { + if (mastery.GetMoveLevel(move) <= level) + pa8.SetMasteryFlagMove(move); + } } private OverworldParam8a GetParams() diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs index b8d5cadb5..5404efb7d 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs @@ -161,8 +161,8 @@ private void SetMoves(PKM pk, GameVersion version, int level) var moves = Moves.Count != 0 ? Moves : MoveLevelUp.GetEncounterMoves(pk, level, version); if (pk.Format == 1 && moves.All(z => z == 0)) moves = ((PersonalInfoG1)PersonalTable.RB[Species]).Moves; - pk.SetMoves(moves); - pk.SetMaximumPPCurrent(moves); + pk.SetMoves((int[])moves); + pk.SetMaximumPPCurrent((int[])moves); } private void SetEggMetData(PKM pk, DateTime time) diff --git a/PKHeX.Core/Legality/Encounters/IMasteryInitialMoveShop8.cs b/PKHeX.Core/Legality/Encounters/IMasteryInitialMoveShop8.cs new file mode 100644 index 000000000..8760b08f5 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/IMasteryInitialMoveShop8.cs @@ -0,0 +1,6 @@ +namespace PKHeX.Core; + +public interface IMasteryInitialMoveShop8 +{ + bool IsForcedMasteryCorrect(PKM pkm); +} diff --git a/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs b/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs index ebb54bb0e..933210ba5 100644 --- a/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs +++ b/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs @@ -142,6 +142,7 @@ public static class LegalityCheckStrings public static string LEncGiftVersionNotDistributed { get; set; } = "Mystery Gift cannot be received by this version."; public static string LEncInvalid { get; set; } = "Unable to match an encounter from origin game."; + public static string LEncMasteryInitial { get; set; } = "Initial move mastery flags do not match the encounter's expected state."; public static string LEncTradeChangedNickname { get; set; } = "In-game Trade Nickname has been altered."; public static string LEncTradeChangedOT { get; set; } = "In-game Trade OT has been altered."; diff --git a/PKHeX.Core/Legality/Learnset/Learnset.cs b/PKHeX.Core/Legality/Learnset/Learnset.cs index cd1103a94..a575369c3 100644 --- a/PKHeX.Core/Legality/Learnset/Learnset.cs +++ b/PKHeX.Core/Legality/Learnset/Learnset.cs @@ -42,9 +42,11 @@ public int[] GetMoves(int maxLevel, int minLevel = 0) int end = Array.FindLastIndex(Levels, z => z <= maxLevel); if (end < 0) return Array.Empty(); - int[] result = new int[end - start + 1]; - Array.Copy(Moves, start, result, 0, result.Length); - return result; + + var length = end - start + 1; + if (length == Moves.Length) + return Moves; + return Moves.AsSpan(start, length).ToArray(); } /// @@ -121,6 +123,34 @@ public void SetEncounterMoves(int level, Span moves, int ctr = 0) } } + public void SetEncounterMovesBackwards(int level, Span moves, int ctr = 0) + { + int index = Array.FindLastIndex(Levels, z => z <= level); + + while (true) + { + if (index == -1) + return; // no moves to add? + + // In the event we have multiple moves at the same level, insert them in regular descending order. + int start = index; + while (start != 0 && Levels[start] == Levels[start - 1]) + start--; + + for (int i = start; i <= index; i++) + { + var move = Moves[i]; + if (moves.IndexOf(move) == -1) + moves[ctr++] = move; + + if (ctr == 4) + return; + } + + index = start - 1; + } + } + /// Adds the learned moves by level up to the specified level. public void SetLevelUpMoves(int startLevel, int endLevel, Span moves, int ctr = 0) { @@ -228,6 +258,14 @@ public int GetMinMoveLevel(int level) return Math.Max(end - 4, 1); } + public int GetMoveLevel(int move) + { + var index = Array.LastIndexOf(Moves, move); + if (index == -1) + return -1; + return Levels[index]; + } + private Dictionary? Learn; private Dictionary GetDictionary() diff --git a/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs b/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs index a4a273d57..b5cce04e7 100644 --- a/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs @@ -114,6 +114,11 @@ public override void Verify(LegalityAnalysis data) if (!valid) data.AddLine(GetInvalid(LPIDTypeMismatch)); } + else if (enc is IMasteryInitialMoveShop8 m) + { + if (!m.IsForcedMasteryCorrect(pkm)) + data.AddLine(GetInvalid(LEncMasteryInitial)); + } VerifyMiscFatefulEncounter(data); VerifyMiscPokerus(data); diff --git a/PKHeX.Core/MysteryGifts/PGT.cs b/PKHeX.Core/MysteryGifts/PGT.cs index a3316d012..5c0d9fee9 100644 --- a/PKHeX.Core/MysteryGifts/PGT.cs +++ b/PKHeX.Core/MysteryGifts/PGT.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core @@ -110,7 +111,7 @@ private void EncryptPK() public override bool IsPokémon { get => PGTGiftType is GiftType.Pokémon or GiftType.PokémonEgg or GiftType.ManaphyEgg; set { } } public override int Species { get => IsManaphyEgg ? 490 : PK.Species; set => PK.Species = value; } - public override IReadOnlyList Moves { get => PK.Moves; set => PK.SetMoves(value); } + public override IReadOnlyList Moves { get => PK.Moves; set => PK.SetMoves(value.ToArray()); } public override int HeldItem { get => PK.HeldItem; set => PK.HeldItem = value; } public override bool IsShiny => PK.IsShiny; public override int Gender { get => PK.Gender; set => PK.Gender = value; } diff --git a/PKHeX.Core/MysteryGifts/WC3.cs b/PKHeX.Core/MysteryGifts/WC3.cs index 1a662d85c..6b50694c1 100644 --- a/PKHeX.Core/MysteryGifts/WC3.cs +++ b/PKHeX.Core/MysteryGifts/WC3.cs @@ -168,8 +168,9 @@ private void SetMoves(PK3 pk) Moves = moves; } - pk.SetMoves(Moves); - pk.SetMaximumPPCurrent(Moves); + var m = (int[])Moves; + pk.SetMoves(m); + pk.SetMaximumPPCurrent(m); } private void SetPINGA(PK3 pk, EncounterCriteria _) diff --git a/PKHeX.Core/PKM/Interfaces/IMoveShop8.cs b/PKHeX.Core/PKM/Interfaces/IMoveShop8.cs index 14dc809fe..c0fb3250a 100644 --- a/PKHeX.Core/PKM/Interfaces/IMoveShop8.cs +++ b/PKHeX.Core/PKM/Interfaces/IMoveShop8.cs @@ -18,3 +18,52 @@ public interface IMoveShop8Mastery : IMoveShop8 void SetMasteredRecordFlag(int index, bool value); bool GetMasteredRecordFlagAny(); } + +public static class MoveShop8MasteryExtensions +{ + public static bool IsValidMasteredEncounter(this IMoveShop8Mastery shop, Span moves, Learnset mastery, int level, ushort alpha) + { + foreach (var move in moves) + { + if (move == 0) + continue; + var index = shop.MoveShopPermitIndexes.IndexOf((ushort)move); + if (index == -1) + continue; // manually mastered for encounter, not a tutor + + bool p = shop.GetPurchasedRecordFlag(index); + bool m = shop.GetMasteredRecordFlag(index); + + var masteryLevel = mastery.GetMoveLevel(move); + if (masteryLevel > level && move != alpha) // no master flag set + { + if (!p && m) + return false; + } + else + { + if (p || !m) + return false; + } + } + + return true; + } + + private static bool IsPurchasedEncounterMove(IMoveShop8 p, Span moves) + { + foreach (var move in moves) + { + if (move == 0) + continue; + var index = p.MoveShopPermitIndexes.IndexOf((ushort)move); + if (index == -1) + continue; // manually mastered for encounter, not a tutor + + if (p.GetPurchasedRecordFlag(index)) + return true; + } + + return false; + } +} diff --git a/PKHeX.Core/PKM/PKM.cs b/PKHeX.Core/PKM/PKM.cs index 644516479..59ce8920d 100644 --- a/PKHeX.Core/PKM/PKM.cs +++ b/PKHeX.Core/PKM/PKM.cs @@ -439,7 +439,7 @@ public int[] Stats public int[] Moves { get => new[] { Move1, Move2, Move3, Move4 }; - set => SetMoves(value); + set => SetMoves(value.AsSpan()); } public void PushMove(int move) @@ -464,12 +464,12 @@ public void GetMoves(Span value) value[0] = Move1; } - public void SetMoves(IReadOnlyList value) + public void SetMoves(ReadOnlySpan value) { - Move1 = value.Count > 0 ? value[0] : 0; - Move2 = value.Count > 1 ? value[1] : 0; - Move3 = value.Count > 2 ? value[2] : 0; - Move4 = value.Count > 3 ? value[3] : 0; + Move1 = value.Length > 0 ? value[0] : 0; + Move2 = value.Length > 1 ? value[1] : 0; + Move3 = value.Length > 2 ? value[2] : 0; + Move4 = value.Length > 3 ? value[3] : 0; } public int[] RelearnMoves diff --git a/PKHeX.Core/Resources/legality/misc/mastery_la.pkl b/PKHeX.Core/Resources/legality/misc/mastery_la.pkl new file mode 100644 index 0000000000000000000000000000000000000000..6771a1f95a7980c8416f9fa023e5708f1d2dc96c GIT binary patch literal 15612 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 P>Z}q~qXy?GQ%(6Fqxyr< literal 0 HcmV?d00001