diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4Pokewalker.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4Pokewalker.cs index 68deda2ba..712a89d15 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4Pokewalker.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4Pokewalker.cs @@ -116,7 +116,7 @@ private uint GetIV32(EncounterCriteria criteria) if (criteria.IsSpecifiedIVsAll()) // Don't trust that the requirements are valid { criteria.GetCombinedIVs(out var iv1, out var iv2); - var seed = PokewalkerRNG.GetFirstSeed(iv1, iv2); + var seed = PokewalkerRNG.GetLeastEffortSeed(iv1, iv2); if (seed.Type != PokewalkerSeedType.None) return criteria.GetCombinedIVs(); } @@ -142,11 +142,11 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo) return true; } - private bool IsMatchSeed(PKM pk) + private static bool IsMatchSeed(PKM pk) { Span ivs = stackalloc int[6]; pk.GetIVs(ivs); - var seed = PokewalkerRNG.GetFirstSeed(ivs); + var seed = PokewalkerRNG.GetLeastEffortSeed(ivs); return seed.Type != PokewalkerSeedType.None; } @@ -191,7 +191,7 @@ public EncounterMatchRating GetMatchRating(PKM pk) return EncounterMatchRating.Match; } - private bool IsMatchPartial(PKM pk) => pk.Ball != (byte)Ball.Poke || !IsMatchSeed(pk); + private static bool IsMatchPartial(PKM pk) => pk.Ball != (byte)Ball.Poke || !IsMatchSeed(pk); #endregion public bool IsCompatible(PIDType type, PKM pk) diff --git a/PKHeX.Core/Legality/Formatting/LegalityFormatting.cs b/PKHeX.Core/Legality/Formatting/LegalityFormatting.cs index e743e3744..02527e4be 100644 --- a/PKHeX.Core/Legality/Formatting/LegalityFormatting.cs +++ b/PKHeX.Core/Legality/Formatting/LegalityFormatting.cs @@ -117,7 +117,17 @@ private static void AddEncounterInfoPIDIV(List lines, LegalInfo info) } lines.Add(type); if (pidiv.NoSeed) + { + if (pidiv.Type is PIDType.Pokewalker) + { + var pk = info.Entity; + var result = PokewalkerRNG.GetLeastEffortSeed((uint)pk.IV_HP, (uint)pk.IV_ATK, (uint)pk.IV_DEF, (uint)pk.IV_SPA, (uint)pk.IV_SPD, (uint)pk.IV_SPE); + var line = string.Format(L_FOriginSeed_0, result.Seed.ToString("X8")); + line += $" [{result.Type} @ {result.PriorPoke}]"; + lines.Add(line); + } return; + } if (pidiv.IsSeed64()) { diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/PokewalkerRNG.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/PokewalkerRNG.cs index 37200c00e..9f6d93707 100644 --- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/PokewalkerRNG.cs +++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/PokewalkerRNG.cs @@ -99,29 +99,32 @@ public static bool IsSeedFormatNoStroll(uint seed) ]; /// - /// Gets the first valid seed for the given Pokéwalker IVs. + /// Finds an initial seed for the Pokéwalker IVs with the least amount of capture-IVs advances needed. /// - public static PokewalkerSeedResult GetFirstSeed(Span ivs) + /// IVs in order (speed last). + public static PokewalkerSeedResult GetLeastEffortSeed(Span ivs) { var tmp = MemoryMarshal.Cast(ivs); - return GetFirstSeed(tmp, tmp[0], tmp[1], tmp[2], tmp[4], tmp[5], spe: tmp[3]); + return GetLeastEffortSeed(tmp[0], tmp[1], tmp[2], tmp[4], tmp[5], spe: tmp[3]); } - /// - public static PokewalkerSeedResult GetFirstSeed(Span tmpIVs, - uint hp, uint atk, uint def, uint spa, uint spd, uint spe) + /// + public static PokewalkerSeedResult GetLeastEffortSeed(uint hp, uint atk, uint def, uint spa, uint spd, uint spe) { + Span result = stackalloc uint[LCRNG.MaxCountSeedsIV]; uint first = (hp | (atk << 5) | (def << 10)) << 16; uint second = (spe | (spa << 5) | (spd << 10)) << 16; - return GetFirstSeed(tmpIVs, first, second); + return GetLeastEffortSeed(result, first, second); } - /// - public static PokewalkerSeedResult GetFirstSeed(uint first, uint second) - => GetFirstSeed(stackalloc uint[LCRNG.MaxCountSeedsIV], first, second); + /// + /// + public static PokewalkerSeedResult GetLeastEffortSeed(uint first, uint second) + => GetLeastEffortSeed(stackalloc uint[LCRNG.MaxCountSeedsIV], first, second); - /// - public static PokewalkerSeedResult GetFirstSeed(Span result, uint first, uint second) + /// + /// + public static PokewalkerSeedResult GetLeastEffortSeed(Span result, uint first, uint second) { // When generating a set of Pokéwalker Pokémon (and their IVs), the game does the following logic: // If the player begins a stroll, generate an initial seed based on seconds elapsed in the day (< 86400) and 3 slots. @@ -146,15 +149,25 @@ public static PokewalkerSeedResult GetFirstSeed(Span result, uint first, u // Check the [no-stroll] case. if (IsSeedFormatNoStroll(s)) return new(s, priorPoke, PokewalkerSeedType.NoStroll); - s = seed = LCRNG.Prev(seed); + s = LCRNG.Prev(s); // Check the [stroll] case. // Due to this backtracking algorithm, the first time we check won't be a valid (needs 3 advancements) if (priorPoke != 0 && IsSeedFormatStroll(s)) // don't check species; can be disassociated from slots. return new(s, --priorPoke, PokewalkerSeedType.Stroll); // decrement priorPoke back to 0-indexed - seed = LCRNG.Prev(seed); // prep for next loop + seed = LCRNG.Prev(s); // prep for next loop } } + + // The above logic for Stroll checks for [0,n-1) due to the backtracking nature of the algorithm. + // Check the last-empty-slot for the Stroll case. + // That's catching 540 'mons... quite unlikely! But still possible, as not all spreads are obtainable. + foreach (ref var seed in result) + { + var s = LCRNG.Prev(seed); + if (IsSeedFormatStroll(s)) // don't check species; can be disassociated from slots. + return new(s, boxCapacity - 1, PokewalkerSeedType.Stroll); // decrement priorPoke back to 0-indexed + } return default; } diff --git a/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs b/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs index a06ccbc43..2e0467056 100644 --- a/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs +++ b/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs @@ -157,11 +157,11 @@ public void PIDIVPokeSpotTest() } [Theory] - [InlineData(30, 31, 31, 14, 31, 31, 0x28070031, 24, (int)Species.Pikachu, PokewalkerCourse4.YellowForest, PokewalkerSeedType.NoStroll)] - public void PokewalkerIVTest(uint hp, uint atk, uint def, uint spA, uint spD, uint spE, uint seed, ushort expect, ushort species, PokewalkerCourse4 course, PokewalkerSeedType type) + [InlineData(30, 31, 31, 14, 31, 31, 0x28070031, 024, PokewalkerSeedType.NoStroll)] + [InlineData(00, 00, 00, 05, 03, 09, 0x00011434, 539, PokewalkerSeedType.Stroll)] + public void PokewalkerIVTest(uint hp, uint atk, uint def, uint spA, uint spD, uint spE, uint seed, ushort expect, PokewalkerSeedType type) { - Span tmp = stackalloc uint[LCRNG.MaxCountSeedsIV]; - var result = PokewalkerRNG.GetFirstSeed(tmp, hp, atk, def, spA, spD, spE); + var result = PokewalkerRNG.GetLeastEffortSeed(hp, atk, def, spA, spD, spE); result.Type.Should().Be(type); result.PriorPoke.Should().Be(expect); result.Seed.Should().Be(seed);