diff --git a/PKHeX.Core/Editing/Bulk/BatchMods.cs b/PKHeX.Core/Editing/Bulk/BatchMods.cs index af75b3f47..26c0c0fcd 100644 --- a/PKHeX.Core/Editing/Bulk/BatchMods.cs +++ b/PKHeX.Core/Editing/Bulk/BatchMods.cs @@ -15,8 +15,8 @@ public static class BatchMods new TypeSuggestion(nameof(IScaledSizeValue.WeightAbsolute), p => p.ResetWeight()), new TypeSuggestion(nameof(Extensions.HyperTrainClear), p => p.HyperTrainClear()), new TypeSuggestion(nameof(Extensions.ClearGeoLocationData), p => p.ClearGeoLocationData()), - new TypeSuggestion(nameof(Extensions.AwakeningClear), p => p.AwakeningClear()), - new TypeSuggestion(nameof(Extensions.AwakeningMax), p => p.AwakeningMax()), + new TypeSuggestion(nameof(AwakeningUtil.AwakeningClear), p => p.AwakeningClear()), + new TypeSuggestion(nameof(AwakeningUtil.AwakeningMax), p => p.AwakeningMax()), new TypeSuggestion(nameof(GanbaruExtensions.ClearGanbaruValues), p => p.ClearGanbaruValues()), new TypeSuggestion(nameof(GanbaruExtensions.SetSuggestedGanbaruValues), p => p.SetSuggestedGanbaruValues((PKM)p)), diff --git a/PKHeX.Core/Legality/Enums/CheckIdentifier.cs b/PKHeX.Core/Legality/Enums/CheckIdentifier.cs index ed5869e1d..8ae979402 100644 --- a/PKHeX.Core/Legality/Enums/CheckIdentifier.cs +++ b/PKHeX.Core/Legality/Enums/CheckIdentifier.cs @@ -153,4 +153,9 @@ public enum CheckIdentifier : byte /// The pertains to values. /// Marking, + + /// + /// The pertains to the values. + /// + AVs, } diff --git a/PKHeX.Core/Legality/LegalityAnalysis.cs b/PKHeX.Core/Legality/LegalityAnalysis.cs index ab507a594..daa33a893 100644 --- a/PKHeX.Core/Legality/LegalityAnalysis.cs +++ b/PKHeX.Core/Legality/LegalityAnalysis.cs @@ -1,4 +1,4 @@ -#define SUPPRESS +#define SUPPRESS using System; using System.Collections.Generic; @@ -229,6 +229,8 @@ private void ParsePK7() UpdateChecks(); if (Entity.Format >= 8) Transfer.VerifyTransferLegalityG8(this); + else if (Entity is PB7) + Awakening.Verify(this); } private void ParsePK8() diff --git a/PKHeX.Core/Legality/LegalityAnalyzers.cs b/PKHeX.Core/Legality/LegalityAnalyzers.cs index e579ed751..2769cb441 100644 --- a/PKHeX.Core/Legality/LegalityAnalyzers.cs +++ b/PKHeX.Core/Legality/LegalityAnalyzers.cs @@ -1,4 +1,4 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; /// /// Collection of analyzers that are used for parsing secondary details. @@ -34,4 +34,5 @@ internal static class LegalityAnalyzers public static readonly TransferVerifier Transfer = new(); public static readonly MarkVerifier Mark = new(); public static readonly LegendsArceusVerifier Arceus = new(); + public static readonly AwakenedValueVerifier Awakening = new(); } diff --git a/PKHeX.Core/Legality/Verifiers/AwakenedValueVerifier.cs b/PKHeX.Core/Legality/Verifiers/AwakenedValueVerifier.cs new file mode 100644 index 000000000..8d6ea45f2 --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/AwakenedValueVerifier.cs @@ -0,0 +1,42 @@ +using System; + +namespace PKHeX.Core; + +public sealed class AwakenedValueVerifier : Verifier +{ + protected override CheckIdentifier Identifier => CheckIdentifier.AVs; + + public override void Verify(LegalityAnalysis data) + { + if (data.Entity is not PB7 pb7) + return; + + int sum = pb7.EVTotal; + if (sum != 0) + data.AddLine(GetInvalid(LegalityCheckStrings.LEffortShouldBeZero)); + + if (!pb7.AwakeningAllValid()) + data.AddLine(GetInvalid(LegalityCheckStrings.LAwakenedCap)); + + Span required = stackalloc byte[6]; + AwakeningUtil.GetExpectedMinimumAVs(required, pb7); + ReadOnlySpan current = stackalloc byte[6] + { + pb7.GetAV(0), + pb7.GetAV(1), + pb7.GetAV(2), + pb7.GetAV(4), + pb7.GetAV(5), + pb7.GetAV(3), // Speed last! + }; + + for (int i = 0; i < required.Length; i++) + { + if (current[i] >= required[i]) + continue; + + data.AddLine(GetInvalid(string.Format(LegalityCheckStrings.LAwakenedShouldBeValue, required[i]))); + break; + } + } +} diff --git a/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs b/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs index 3d717ec36..1afb93a10 100644 --- a/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs @@ -13,11 +13,6 @@ public sealed class EffortValueVerifier : Verifier public override void Verify(LegalityAnalysis data) { var pk = data.Entity; - if (pk is IAwakened a) - { - VerifyAwakenedValues(data, a); - return; - } var enc = data.EncounterMatch; if (pk.IsEgg) { @@ -65,36 +60,4 @@ public override void Verify(LegalityAnalysis data) else if (evs[0] != 0 && evs.Count(evs[0]) == evs.Length) data.AddLine(Get(LEffortAllEqual, Severity.Fishy)); } - - private void VerifyAwakenedValues(LegalityAnalysis data, IAwakened awakened) - { - var pk = data.Entity; - int sum = pk.EVTotal; - if (sum != 0) - data.AddLine(GetInvalid(LEffortShouldBeZero)); - - if (!awakened.AwakeningAllValid()) - data.AddLine(GetInvalid(LAwakenedCap)); - - var enc = data.EncounterMatch; - - // go park transfers have 2 AVs for all stats. - if (enc is EncounterSlot7GO) - { - Span avs = stackalloc byte[6]; - awakened.GetAVs(avs); - foreach (var av in avs) - { - if (av >= 2) - continue; - - data.AddLine(GetInvalid(string.Format(LAwakenedShouldBeValue, 2))); - break; - } - return; - } - - if (awakened.AwakeningSum() == 0 && !enc.IsWithinEncounterRange(pk)) - data.AddLine(Get(LAwakenedEXPIncreased, Severity.Fishy)); - } } diff --git a/PKHeX.Core/PKM/Interfaces/IAwakened.cs b/PKHeX.Core/PKM/Interfaces/IAwakened.cs index 91270f658..712b060fd 100644 --- a/PKHeX.Core/PKM/Interfaces/IAwakened.cs +++ b/PKHeX.Core/PKM/Interfaces/IAwakened.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace PKHeX.Core; @@ -12,7 +12,7 @@ public interface IAwakened byte AV_SPD { get; set; } } -public static partial class Extensions +public static class AwakeningUtil { /// /// Sums all values. @@ -47,10 +47,26 @@ public static partial class Extensions /// Maximum value to set public static void AwakeningSetRandom(this IAwakened pk, byte min = 0, int max = Legal.AwakeningMax) { + if (pk is not PB7 pb7) + return; + Span result = stackalloc byte[6]; + GetExpectedMinimumAVs(result, pb7); + var rnd = Util.Rand; int randClamp = max + 1; - for (int index = 0; index < 6; index++) - pk.SetAV(index, (byte)rnd.Next(min, randClamp)); + for (int i = 0; i < 6; i++) + result[i] = (byte)rnd.Next(result[i], randClamp); + AwakeningSetVisual(pb7, result); + } + + public static void AwakeningSetVisual(IAwakened pk, Span result) + { + pk.AV_HP = result[0]; + pk.AV_ATK = result[1]; + pk.AV_DEF = result[2]; + pk.AV_SPA = result[3]; + pk.AV_SPD = result[4]; + pk.AV_SPE = result[5]; } /// @@ -96,7 +112,7 @@ public static bool AwakeningAllValid(this IAwakened pk) /// /// Pokémon to check. /// Index to get - public static int GetAV(this IAwakened pk, int index) => index switch + public static byte GetAV(this IAwakened pk, int index) => index switch { 0 => pk.AV_HP, 1 => pk.AV_ATK, @@ -129,12 +145,14 @@ public static void GetAVs(this IAwakened pk, Span value) /// Retriever for IVs public static void SetSuggestedAwakenedValues(this IAwakened a, PKM pk) { + Span result = stackalloc byte[6]; + GetExpectedMinimumAVs(result, (PB7)a); a.AV_HP = Legal.AwakeningMax; - a.AV_ATK = pk.IV_ATK == 0 ? (byte)0 : Legal.AwakeningMax; + a.AV_ATK = pk.IV_ATK == 0 ? result[1] : Legal.AwakeningMax; a.AV_DEF = Legal.AwakeningMax; - a.AV_SPE = pk.IV_SPE == 0 ? (byte)0 : Legal.AwakeningMax; a.AV_SPA = Legal.AwakeningMax; a.AV_SPD = Legal.AwakeningMax; + a.AV_SPE = pk.IV_SPE == 0 ? result[5] : Legal.AwakeningMax; } public static bool IsAwakeningBelow(this IAwakened current, IAwakened initial) => !current.IsAwakeningAboveOrEqual(initial); @@ -155,4 +173,26 @@ public static bool IsAwakeningAboveOrEqual(this IAwakened current, IAwakened ini return false; return true; } + + public static void GetExpectedMinimumAVs(Span result, PB7 pk) + { + // go park transfers have 2 AVs for all stats. + // leveling up in-game applies 1 AV to a "random" index. + if (pk.Version == (int)GameVersion.GO) + result.Fill(2); + + var nature = pk.Nature; + var character = pk.Characteristic; + var ec = pk.EncryptionConstant; + var start = pk.Met_Level; + var end = pk.CurrentLevel; + + for (int i = start + 1; i <= end; i++) + { + var lm10 = i % 10; + var bits = (ec >> (3 * lm10)) & 7; + var index = PB7.GetRandomIndex((int)bits, character, nature); + ++result[index]; + } + } } diff --git a/PKHeX.Core/PKM/PB7.cs b/PKHeX.Core/PKM/PB7.cs index f92836461..658da8a86 100644 --- a/PKHeX.Core/PKM/PB7.cs +++ b/PKHeX.Core/PKM/PB7.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using static System.Buffers.Binary.BinaryPrimitives; @@ -585,6 +585,31 @@ public static byte GetWeightScalar(float height, float weight, int avgHeight, in return (byte)Math.Min(255, unsigned); } + public static int GetRandomIndex(int bits, int characterIndex, int nature) + { + if (bits is 6 or 7) + return GetRandomIndex(characterIndex); + if (bits is 0) + return 0; + Span amps = NatureAmpTable.AsSpan(5 * nature, 5); + if (amps[bits - 1] != -1) // not a negative stat + return bits; + + // remap a negative stat to positive + return 1 + amps.IndexOf((sbyte)1); + } + + private static int GetRandomIndex(int characterIndex) => (characterIndex / 5) switch + { + 0 => 0, + 1 => 1, + 2 => 2, + 3 => 5, + 4 => 3, + 5 => 4, + _ => throw new ArgumentOutOfRangeException(nameof(characterIndex)), // never happens, characteristic is always 0-29 + }; + public PK8 ConvertToPK8() { var pk8 = new PK8