mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-10 06:34:19 +00:00
Extract pokewalker logic from template ctor
Actually searching (instead of brute-forcing) for a spread will forever be haunting. Add to Legality Check matching with vague partial match
This commit is contained in:
parent
176d0d670a
commit
abcaaa44cd
11 changed files with 358 additions and 195 deletions
|
@ -299,4 +299,23 @@ public sealed record EncounterCriteria : IFixedNature, IFixedGender, IFixedAbili
|
|||
return Util.Rand.Next(32);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCompatibleIVs(ReadOnlySpan<int> ivs)
|
||||
{
|
||||
if (ivs.Length != 6)
|
||||
return false;
|
||||
if (IV_HP != RandomIV && IV_HP != ivs[0])
|
||||
return false;
|
||||
if (IV_ATK != RandomIV && IV_ATK != ivs[1])
|
||||
return false;
|
||||
if (IV_DEF != RandomIV && IV_DEF != ivs[2])
|
||||
return false;
|
||||
if (IV_SPE != RandomIV && IV_SPE != ivs[3])
|
||||
return false;
|
||||
if (IV_SPA != RandomIV && IV_SPA != ivs[4])
|
||||
return false;
|
||||
if (IV_SPD != RandomIV && IV_SPD != ivs[5])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Provides a summary for an <see cref="IEncounterTemplate"/> object.
|
||||
/// </summary>
|
||||
public record EncounterSummary
|
||||
public sealed record EncounterSummary
|
||||
{
|
||||
private readonly GameVersion Version;
|
||||
private readonly string LocationName;
|
||||
|
|
|
@ -103,7 +103,8 @@ public sealed record EncounterStatic4(GameVersion Version)
|
|||
// Pichu is special -- use Pokewalker method
|
||||
if (Species == (int)Core.Species.Pichu)
|
||||
{
|
||||
PIDGenerator.SetRandomPIDPokewalker(pk, (byte)Nature, Gender);
|
||||
var pid = pk.PID = PokewalkerRNG.GetPID(pk.TID16, pk.SID16, (uint)Nature, pk.Gender = Gender, pi.Gender);
|
||||
pk.RefreshAbility((int)(pid & 1));
|
||||
criteria.SetRandomIVs(pk);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -46,16 +46,17 @@ public sealed record EncounterStatic4Pokewalker(PokewalkerCourse4 Course)
|
|||
|
||||
public static EncounterStatic4Pokewalker[] GetAll(ReadOnlySpan<byte> data)
|
||||
{
|
||||
const int size = 0xC;
|
||||
var count = data.Length / size;
|
||||
System.Diagnostics.Debug.Assert(count == 6 * (int)PokewalkerCourse4.MAX_COUNT);
|
||||
var result = new EncounterStatic4Pokewalker[count];
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
const int SlotSize = 0xC;
|
||||
const int SlotsPerCourse = PokewalkerRNG.SlotsPerCourse;
|
||||
const int SlotCount = SlotsPerCourse * (int)PokewalkerCourse4.MAX_COUNT;
|
||||
System.Diagnostics.Debug.Assert(data.Length == SlotCount * SlotSize);
|
||||
|
||||
PokewalkerCourse4 course = 0;
|
||||
var result = new EncounterStatic4Pokewalker[SlotCount];
|
||||
for (int i = 0, offset = 0; i < result.Length; course++)
|
||||
{
|
||||
var offset = i * size;
|
||||
var slice = data.Slice(offset, size);
|
||||
var course = (PokewalkerCourse4)(i / 6);
|
||||
result[i] = new(slice, course);
|
||||
for (int s = 0; s < SlotsPerCourse; s++, offset += SlotSize)
|
||||
result[i++] = new(data.Slice(offset, SlotSize), course);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -102,13 +103,14 @@ public sealed record EncounterStatic4Pokewalker(PokewalkerCourse4 Course)
|
|||
private void SetPINGA(PK4 pk, EncounterCriteria criteria, PersonalInfo4 pi)
|
||||
{
|
||||
int gender = criteria.GetGender(Gender, pi);
|
||||
int nature = (int)criteria.GetNature();
|
||||
var nature = (uint)criteria.GetNature();
|
||||
|
||||
var pid = pk.PID = PokewalkerRNG.GetPID(pk.TID16, pk.SID16, nature, pk.Gender = gender, pi.Gender);
|
||||
// Cannot force an ability; nature-gender-trainerID only yield fixed PIDs.
|
||||
// int ability = criteria.GetAbilityFromNumber(Ability, pi);
|
||||
|
||||
PIDGenerator.SetRandomPIDPokewalker(pk, nature, gender);
|
||||
criteria.SetRandomIVs(pk);
|
||||
pk.RefreshAbility((int)(pid & 1));
|
||||
Span<int> ivs = stackalloc int[6];
|
||||
PokewalkerRNG.SetRandomIVs(ivs, criteria);
|
||||
pk.SetIVs(ivs);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -129,6 +131,14 @@ public sealed record EncounterStatic4Pokewalker(PokewalkerCourse4 Course)
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool IsMatchSeed(PKM pk)
|
||||
{
|
||||
Span<int> ivs = stackalloc int[6];
|
||||
pk.GetIVs(ivs);
|
||||
var seed = PokewalkerRNG.GetFirstSeed(Species, Course, ivs);
|
||||
return seed.Type != PokewalkerSeedType.None;
|
||||
}
|
||||
|
||||
private bool IsMatchGender(PKM pk)
|
||||
{
|
||||
if (pk.Gender == Gender)
|
||||
|
@ -170,7 +180,7 @@ public sealed record EncounterStatic4Pokewalker(PokewalkerCourse4 Course)
|
|||
return EncounterMatchRating.Match;
|
||||
}
|
||||
|
||||
private static bool IsMatchPartial(PKM pk) => pk.Ball != (byte)Ball.Poke;
|
||||
private bool IsMatchPartial(PKM pk) => pk.Ball != (byte)Ball.Poke || !IsMatchSeed(pk);
|
||||
#endregion
|
||||
|
||||
public bool IsCompatible(PIDType val, PKM pk)
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace PKHeX.Core;
|
|||
/// <param name="FrameType"></param>
|
||||
/// <param name="Lead"></param>
|
||||
[DebuggerDisplay($"{{{nameof(FrameType)},nq}}[{{{nameof(Lead)},nq}}]")]
|
||||
public record Frame(uint Seed, FrameType FrameType, LeadRequired Lead)
|
||||
public sealed record Frame(uint Seed, FrameType FrameType, LeadRequired Lead)
|
||||
{
|
||||
/// <summary>
|
||||
/// Starting seed for the frame (to generate the frame).
|
||||
|
|
|
@ -511,17 +511,17 @@ public static class MethodFinder
|
|||
return GetNonMatch(out pidiv);
|
||||
}
|
||||
|
||||
private static bool GetPokewalkerMatch(PKM pk, uint oldpid, out PIDIV pidiv)
|
||||
private static bool GetPokewalkerMatch(PKM pk, uint pid, out PIDIV pidiv)
|
||||
{
|
||||
// check surface compatibility
|
||||
// Bits 8-24 must all be zero or all be one.
|
||||
const uint midMask = 0x00FFFF00;
|
||||
var mid = oldpid & midMask;
|
||||
var mid = pid & midMask;
|
||||
if (mid is not (0 or midMask))
|
||||
return GetNonMatch(out pidiv);
|
||||
|
||||
// Quirky Nature is not possible with the algorithm.
|
||||
var nature = oldpid % 25;
|
||||
var nature = pid % 25;
|
||||
if (nature == 24)
|
||||
return GetNonMatch(out pidiv);
|
||||
|
||||
|
@ -531,10 +531,10 @@ public static class MethodFinder
|
|||
var gr = pk.PersonalInfo.Gender;
|
||||
if (pk.Species == (int)Species.Froslass)
|
||||
gr = 0x7F; // Snorunt
|
||||
uint pid = PIDGenerator.GetPokeWalkerPID(pk.TID16, pk.SID16, nature, gender, gr);
|
||||
if (pid != oldpid)
|
||||
var expect = PokewalkerRNG.GetPID(pk.TID16, pk.SID16, nature, gender, gr);
|
||||
if (expect != pid)
|
||||
{
|
||||
if (!(gender == 0 && IsAzurillEdgeCaseM(pk, nature, oldpid)))
|
||||
if (!(gender == 0 && IsAzurillEdgeCaseM(pk, nature, pid)))
|
||||
return GetNonMatch(out pidiv);
|
||||
}
|
||||
pidiv = PIDIV.Pokewalker;
|
||||
|
@ -554,7 +554,7 @@ public static class MethodFinder
|
|||
if (gender != 1)
|
||||
return false;
|
||||
|
||||
var pid = PIDGenerator.GetPokeWalkerPID(pk.TID16, pk.SID16, nature, 1, AzurillGenderRatio);
|
||||
var pid = PokewalkerRNG.GetPID(pk.TID16, pk.SID16, nature, 1, AzurillGenderRatio);
|
||||
return pid == oldpid;
|
||||
}
|
||||
|
||||
|
@ -807,120 +807,3 @@ public static class MethodFinder
|
|||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MethodFinderPokewalker
|
||||
{
|
||||
public static (uint Seed, int Prior) GetSeedsPokewalkerIVs(ushort species, PokewalkerCourse4 course,
|
||||
Span<uint> tmpIVs, uint hp, uint atk, uint def, uint spa, uint spd, uint spe)
|
||||
{
|
||||
uint first = (hp | (atk << 5) | (def << 10)) << 16;
|
||||
uint second = (spe | (spa << 5) | (spd << 10)) << 16;
|
||||
return GetSeedsPokewalkerIVs(species, course, tmpIVs, first, second);
|
||||
}
|
||||
|
||||
public const int NoPokwalkerMatch = -1;
|
||||
|
||||
public static (uint Seed, int Prior) GetSeedsPokewalkerIVs(ushort species, PokewalkerCourse4 course,
|
||||
Span<uint> 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 does not begin a stroll, generate an initial seed based on seconds elapsed in the day (< 86400).
|
||||
// Otherwise, generate an initial seed based on the elapsed time and date (similar to Gen4 initial seeding).
|
||||
|
||||
// If the player begins a stroll, the game generates a set of 3 Pokémon to see, with results untraceable to the correlation.
|
||||
// Then, the game generates each Pokémon's IVs by calling rand() twice.
|
||||
// Since stroll causes 3 RNG advancements, an initial seed [stroll] can be advanced 3+(2*n) times, or [no-stroll] advanced 0+(2*n) times.
|
||||
// To determine the first valid initial seed, take advantage of the even-odd nature of the RNG frames (different initial seeding algorithm).
|
||||
|
||||
// seeding for [stroll]: 3600 * hour + 60 * minute + second
|
||||
// seeding for [no-stroll]: (((month*day + minute + second) & 0xff) << 24) | (hour << 16) | (year)
|
||||
// the top byte of no-stroll can be any value, so we can skip checking that byte.
|
||||
|
||||
int ctr = LCRNGReversal.GetSeedsIVs(result, first, second);
|
||||
if (ctr == 0)
|
||||
return (0, NoPokwalkerMatch);
|
||||
|
||||
result = result[..ctr];
|
||||
|
||||
const int boxCount = 18;
|
||||
const int boxSize = 30;
|
||||
const int boxCapacity = boxCount * boxSize;
|
||||
const int maxHours = 24;
|
||||
const int maxYears = 100;
|
||||
const int secondsPerDay = 60 * 60 * 24;
|
||||
for (int priorPoke = 0; priorPoke < boxCapacity; priorPoke++)
|
||||
{
|
||||
foreach (ref var seed in result)
|
||||
{
|
||||
var s = seed; // already unrolled once
|
||||
|
||||
// Check the [no-stroll] case.
|
||||
if ((byte)(s >> 16) < maxHours && (ushort)s < maxYears)
|
||||
return (s, priorPoke);
|
||||
s = seed = LCRNG.Prev(seed);
|
||||
|
||||
// Check the [stroll] case.
|
||||
if (priorPoke != 0 && s < secondsPerDay && IsValidStrollSeed(s, species, course)) // seed can't be hit due to the 3 advances from stroll
|
||||
return (s, priorPoke);
|
||||
seed = LCRNG.Prev(seed); // prep for next loop
|
||||
}
|
||||
}
|
||||
return (0, NoPokwalkerMatch);
|
||||
}
|
||||
|
||||
private const int SlotsPerCourse = 6;
|
||||
|
||||
public static bool IsValidStrollSeed(uint seed, ushort species, PokewalkerCourse4 course)
|
||||
{
|
||||
// initial seed
|
||||
// rand() & 1 => slot A
|
||||
// rand() & 1 => slot B
|
||||
// rand() & 1 => slot C
|
||||
// generate IVs
|
||||
var span = CourseSpecies.Slice((int)course * SlotsPerCourse, SlotsPerCourse);
|
||||
seed = LCRNG.Next(seed);
|
||||
var slotA = (seed >> 16) & 1;
|
||||
if (span[(int)slotA] == species)
|
||||
return true;
|
||||
seed = LCRNG.Next(seed);
|
||||
var slotB = (seed >> 16) & 1;
|
||||
if (span[(int)slotB + 2] == species)
|
||||
return true;
|
||||
seed = LCRNG.Next(seed);
|
||||
var slotC = (seed >> 16) & 1;
|
||||
if (span[(int)slotC + 4] == species)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<ushort> CourseSpecies => new ushort[]
|
||||
{
|
||||
115, 084, 029, 032, 016, 161, // 00 Refreshing Field
|
||||
202, 069, 048, 046, 043, 021, // 01 Noisy Forest
|
||||
240, 095, 066, 077, 163, 074, // 02 Rugged Road
|
||||
054, 120, 079, 060, 191, 194, // 03 Beautiful Beach
|
||||
239, 081, 081, 198, 163, 019, // 04 Suburban Area
|
||||
238, 092, 092, 095, 041, 066, // 05 Dim Cave
|
||||
147, 060, 098, 090, 118, 072, // 06 Blue Lake
|
||||
063, 100, 109, 088, 019, 162, // 07 Town Outskirts
|
||||
300, 264, 314, 313, 263, 265, // 08 Hoenn Field
|
||||
320, 298, 116, 318, 118, 129, // 09 Warm Beach
|
||||
218, 307, 228, 111, 077, 074, // 10 Volcano Path
|
||||
352, 351, 203, 234, 044, 070, // 11 Treehouse
|
||||
105, 128, 042, 177, 066, 092, // 12 Scary Cave
|
||||
439, 415, 403, 406, 399, 401, // 13 Sinnoh Field
|
||||
459, 361, 215, 436, 220, 179, // 14 Icy Mountain Road
|
||||
357, 438, 114, 400, 179, 102, // 15 Big Forest
|
||||
433, 200, 093, 418, 223, 170, // 16 White Lake
|
||||
456, 422, 129, 086, 054, 090, // 17 Stormy Beach
|
||||
417, 025, 039, 035, 183, 187, // 18 Resort
|
||||
442, 446, 433, 349, 164, 042, // 19 Quiet Cave
|
||||
120, 224, 116, 222, 223, 170, // 20 Beyond The Sea
|
||||
035, 039, 041, 163, 074, 095, // 21 Night Sky's Edge
|
||||
025, 025, 025, 025, 025, 025, // 22 Yellow Forest
|
||||
441, 302, 025, 453, 427, 417, // 23 Rally
|
||||
255, 133, 279, 061, 052, 025, // 24 Sightseeing
|
||||
446, 374, 116, 355, 129, 436, // 25 Winners Path
|
||||
239, 240, 238, 440, 174, 173, // 26 Amity Meadow
|
||||
};
|
||||
}
|
||||
|
|
290
PKHeX.Core/Legality/RNG/Methods/PokewalkerRNG.cs
Normal file
290
PKHeX.Core/Legality/RNG/Methods/PokewalkerRNG.cs
Normal file
|
@ -0,0 +1,290 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for Pokéwalker RNG.
|
||||
/// </summary>
|
||||
public static class PokewalkerRNG
|
||||
{
|
||||
private const int boxCount = 18;
|
||||
private const int boxSize = 30;
|
||||
private const int boxCapacity = boxCount * boxSize;
|
||||
private const int maxHours = 24;
|
||||
private const int maxYears = 100;
|
||||
private const int secondsPerDay = 60 * 60 * 24;
|
||||
|
||||
/// <summary>
|
||||
/// Get the 32-bit RNG seed for a stroll generation instance.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint GetStrollSeed(uint hour, uint minute, uint second) => (3600 * hour) + (60 * minute) + second;
|
||||
|
||||
/// <summary>
|
||||
/// Get the 32-bit RNG seed for a no-stroll generation instance.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint GetNoStrollSeed(uint year, uint month, uint day, uint hour, uint minute, uint second) => ((((month * day) + minute + second) & 0xff) << 24) | (hour << 16) | year;
|
||||
|
||||
/// <summary> Species slots per course. </summary>
|
||||
public const int SlotsPerCourse = 6;
|
||||
|
||||
/// <summary>
|
||||
/// All species for all Pokéwalker courses.
|
||||
/// </summary>
|
||||
/// <remarks>6 species per course; each course has 3 groups (A/B/C) of 2 species (0/1).</remarks>
|
||||
private static ReadOnlySpan<ushort> CourseSpecies => new ushort[]
|
||||
{
|
||||
115, 084, 029, 032, 016, 161, // 00 Refreshing Field
|
||||
202, 069, 048, 046, 043, 021, // 01 Noisy Forest
|
||||
240, 095, 066, 077, 163, 074, // 02 Rugged Road
|
||||
054, 120, 079, 060, 191, 194, // 03 Beautiful Beach
|
||||
239, 081, 081, 198, 163, 019, // 04 Suburban Area
|
||||
238, 092, 092, 095, 041, 066, // 05 Dim Cave
|
||||
147, 060, 098, 090, 118, 072, // 06 Blue Lake
|
||||
063, 100, 109, 088, 019, 162, // 07 Town Outskirts
|
||||
300, 264, 314, 313, 263, 265, // 08 Hoenn Field
|
||||
320, 298, 116, 318, 118, 129, // 09 Warm Beach
|
||||
218, 307, 228, 111, 077, 074, // 10 Volcano Path
|
||||
352, 351, 203, 234, 044, 070, // 11 Treehouse
|
||||
105, 128, 042, 177, 066, 092, // 12 Scary Cave
|
||||
439, 415, 403, 406, 399, 401, // 13 Sinnoh Field
|
||||
459, 361, 215, 436, 220, 179, // 14 Icy Mountain Road
|
||||
357, 438, 114, 400, 179, 102, // 15 Big Forest
|
||||
433, 200, 093, 418, 223, 170, // 16 White Lake
|
||||
456, 422, 129, 086, 054, 090, // 17 Stormy Beach
|
||||
417, 025, 039, 035, 183, 187, // 18 Resort
|
||||
442, 446, 433, 349, 164, 042, // 19 Quiet Cave
|
||||
120, 224, 116, 222, 223, 170, // 20 Beyond The Sea
|
||||
035, 039, 041, 163, 074, 095, // 21 Night Sky's Edge
|
||||
025, 025, 025, 025, 025, 025, // 22 Yellow Forest
|
||||
441, 302, 025, 453, 427, 417, // 23 Rally
|
||||
255, 133, 279, 061, 052, 025, // 24 Sightseeing
|
||||
446, 374, 116, 355, 129, 436, // 25 Winners Path
|
||||
239, 240, 238, 440, 174, 173, // 26 Amity Meadow
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first valid seed for the given Pokéwalker IVs.
|
||||
/// </summary>
|
||||
public static PokewalkerSeedResult GetFirstSeed(ushort species, PokewalkerCourse4 course, Span<int> ivs)
|
||||
{
|
||||
var tmp = MemoryMarshal.Cast<int, uint>(ivs);
|
||||
return GetFirstSeed(species, course, tmp, tmp[0], tmp[1], tmp[2], tmp[4], tmp[5], spe: tmp[3]);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetFirstSeed(ushort, PokewalkerCourse4, Span{int})"/>
|
||||
public static PokewalkerSeedResult GetFirstSeed(ushort species, PokewalkerCourse4 course,
|
||||
Span<uint> tmpIVs, uint hp, uint atk, uint def, uint spa, uint spd, uint spe)
|
||||
{
|
||||
uint first = (hp | (atk << 5) | (def << 10)) << 16;
|
||||
uint second = (spe | (spa << 5) | (spd << 10)) << 16;
|
||||
return GetFirstSeed(species, course, tmpIVs, first, second);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetFirstSeed(ushort, PokewalkerCourse4, Span{int})"/>
|
||||
public static PokewalkerSeedResult GetFirstSeed(ushort species, PokewalkerCourse4 course,
|
||||
Span<uint> 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 does not begin a stroll, generate an initial seed based on seconds elapsed in the day (< 86400).
|
||||
// Otherwise, generate an initial seed based on the elapsed time and date (similar to Gen4 initial seeding).
|
||||
|
||||
// If the player begins a stroll, the game generates a set of 3 Pokémon to see, with results untraceable to the correlation.
|
||||
// Then, the game generates each Pokémon's IVs by calling rand() twice.
|
||||
// Since stroll causes 3 RNG advancements, an initial seed [stroll] can be advanced 3+(2*n) times, or [no-stroll] advanced 0+(2*n) times.
|
||||
// To determine the first valid initial seed, take advantage of the even-odd nature of the RNG frames (different initial seeding algorithm).
|
||||
|
||||
// seeding for [stroll]: 3600 * hour + 60 * minute + second
|
||||
// seeding for [no-stroll]: (((month*day + minute + second) & 0xff) << 24) | (hour << 16) | (year)
|
||||
// the top byte of no-stroll can be any value, so we can skip checking that byte.
|
||||
|
||||
int ctr = LCRNGReversal.GetSeedsIVs(result, first, second);
|
||||
if (ctr == 0)
|
||||
return default;
|
||||
|
||||
result = result[..ctr];
|
||||
for (ushort priorPoke = 0; priorPoke < boxCapacity; priorPoke++)
|
||||
{
|
||||
foreach (ref var seed in result)
|
||||
{
|
||||
var s = seed; // already unrolled once
|
||||
|
||||
// Check the [no-stroll] case.
|
||||
if ((byte)(s >> 16) < maxHours && (ushort)s < maxYears)
|
||||
return new(s, priorPoke, PokewalkerSeedType.NoStroll);
|
||||
s = seed = LCRNG.Prev(seed);
|
||||
|
||||
// Check the [stroll] case.
|
||||
if (priorPoke != 0 && s < secondsPerDay && IsValidStrollSeed(s, species, course)) // seed can't be hit due to the 3 advances from stroll
|
||||
return new(s, priorPoke, PokewalkerSeedType.Stroll);
|
||||
seed = LCRNG.Prev(seed); // prep for next loop
|
||||
}
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the given seed is a valid Stroll seed for the given species on the given course.
|
||||
/// </summary>
|
||||
/// <param name="seed">Seed generated for a Pokéwalker Stroll.</param>
|
||||
/// <param name="species">Species expected to be encountered.</param>
|
||||
/// <param name="course">Course the Stroll is taking place on.</param>
|
||||
/// <returns>True if the seed is valid, false otherwise.</returns>
|
||||
public static bool IsValidStrollSeed(uint seed, ushort species, PokewalkerCourse4 course)
|
||||
{
|
||||
// initial seed
|
||||
// rand() & 1 => slot A
|
||||
// rand() & 1 => slot B
|
||||
// rand() & 1 => slot C
|
||||
// generate IVs
|
||||
var span = GetSpecies(course);
|
||||
seed = LCRNG.Next(seed);
|
||||
var slotA = (seed >> 16) & 1;
|
||||
if (span[(int)slotA] == species)
|
||||
return true;
|
||||
seed = LCRNG.Next(seed);
|
||||
var slotB = (seed >> 16) & 1;
|
||||
if (span[(int)slotB + 2] == species)
|
||||
return true;
|
||||
seed = LCRNG.Next(seed);
|
||||
var slotC = (seed >> 16) & 1;
|
||||
if (span[(int)slotC + 4] == species)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all 6 species for the given course.
|
||||
/// </summary>
|
||||
/// <param name="course">Course to get species for.</param>
|
||||
/// <returns>Span of all 6 species.</returns>
|
||||
public static ReadOnlySpan<ushort> GetSpecies(PokewalkerCourse4 course) => CourseSpecies.Slice((int)course * SlotsPerCourse, SlotsPerCourse);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the species for the given course, group, and rand bit.
|
||||
/// </summary>
|
||||
/// <param name="course">Course to get species for.</param>
|
||||
/// <param name="group">Group to get species for (A/B/C).</param>
|
||||
/// <param name="rare">Rand bit to get species for (0/1).</param>
|
||||
/// <returns>Species for the given course, group, and rand bit.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public static ushort GetSpecies(PokewalkerCourse4 course, int group, int rare)
|
||||
{
|
||||
if ((uint)group > 2)
|
||||
throw new ArgumentOutOfRangeException(nameof(group));
|
||||
if ((uint)rare > 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(rare));
|
||||
var span = GetSpecies(course);
|
||||
return span[(group * 2) + rare];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a Pokewalker PID based on the given parameters.
|
||||
/// </summary>
|
||||
/// <param name="TID16">16-bit Trainer ID.</param>
|
||||
/// <param name="SID16">16-bit Secret ID.</param>
|
||||
/// <param name="nature">Nature to set PID to.</param>
|
||||
/// <param name="gender">Gender to set PID to.</param>
|
||||
/// <param name="genderRatio">Gender ratio of the species.</param>
|
||||
/// <returns>PID for the given parameters.</returns>
|
||||
public static uint GetPID(ushort TID16, ushort SID16, uint nature, int gender, byte genderRatio)
|
||||
{
|
||||
if (nature >= 24)
|
||||
nature = 0;
|
||||
uint pid = ((((uint)TID16 ^ SID16) >> 8) ^ 0xFF) << 24; // the most significant byte of the PID is chosen so the Pokémon can never be shiny.
|
||||
// Ensure nature is set to required nature without affecting shininess
|
||||
pid += nature - (pid % 25);
|
||||
|
||||
if (genderRatio is 0 or >= 0xFE) // non-dual gender
|
||||
return pid;
|
||||
|
||||
// Ensure Gender is set to required gender without affecting other properties
|
||||
// If Gender is modified, modify the ability if appropriate
|
||||
|
||||
// either m/f
|
||||
var pidGender = (pid & 0xFF) < genderRatio ? 1 : 0;
|
||||
if (gender == pidGender)
|
||||
return pid;
|
||||
|
||||
if (gender == 0) // Male
|
||||
{
|
||||
pid += (((genderRatio - (pid & 0xFF)) / 25) + 1) * 25;
|
||||
if ((nature & 1) != (pid & 1))
|
||||
pid += 25;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid -= ((((pid & 0xFF) - genderRatio) / 25) + 1) * 25;
|
||||
if ((nature & 1) != (pid & 1))
|
||||
pid -= 25;
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the IVs to a valid Pokewalker IV spread.
|
||||
/// </summary>
|
||||
/// <param name="ivs">IVs to set.</param>
|
||||
/// <param name="criteria">Criteria to set IVs with.</param>
|
||||
public static bool SetRandomIVs(Span<int> ivs, EncounterCriteria criteria)
|
||||
{
|
||||
// Try to find a seed that works for the given criteria.
|
||||
// Don't waste too much time iterating, try around 100k.
|
||||
// 256 * 24 * 2 * 10 = 122,880
|
||||
for (uint year = 0; year < 2; year++)
|
||||
{
|
||||
uint seed = year;
|
||||
for (uint hour = 0; hour < maxHours; hour++)
|
||||
{
|
||||
for (uint i = 0; i <= 0xFF; i++)
|
||||
{
|
||||
var iterSeed = seed;
|
||||
for (int p = 0; p < 10; p++)
|
||||
{
|
||||
if (TryApply(ref iterSeed, ivs, criteria))
|
||||
return true;
|
||||
}
|
||||
seed += 0x01_000000;
|
||||
}
|
||||
seed += 0x01_0000;
|
||||
}
|
||||
}
|
||||
|
||||
var randByte = (uint)Util.Rand.Next(256) << 24;
|
||||
TryApply(ref randByte, ivs, EncounterCriteria.Unrestricted);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryApply(ref uint seed, Span<int> ivs, EncounterCriteria criteria)
|
||||
{
|
||||
// Act like a Non-Stroll encounter, generate IV rand() results immediately.
|
||||
uint iv1 = LCRNG.Next(seed);
|
||||
uint iv2 = seed = LCRNG.Next(iv1);
|
||||
MethodFinder.GetIVsInt32(ivs, iv1 >> 16, iv2 >> 16);
|
||||
return criteria.IsCompatibleIVs(ivs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for Pokewalker Seed Results
|
||||
/// </summary>
|
||||
/// <param name="Seed">32-bit seed</param>
|
||||
/// <param name="PriorPoke">Count of Pokémon generated prior to the checked Pokémon</param>
|
||||
/// <param name="Type">Type of seed</param>
|
||||
public readonly record struct PokewalkerSeedResult(uint Seed, ushort PriorPoke, PokewalkerSeedType Type);
|
||||
|
||||
/// <summary>
|
||||
/// Type of Pokewalker Seed
|
||||
/// </summary>
|
||||
public enum PokewalkerSeedType : byte
|
||||
{
|
||||
/// <summary> Invalid </summary>
|
||||
None = 0,
|
||||
/// <summary> Stroll Seed </summary>
|
||||
Stroll = 1,
|
||||
/// <summary> No Stroll Seed </summary>
|
||||
NoStroll = 2,
|
||||
}
|
|
@ -178,7 +178,11 @@ public static class PIDGenerator
|
|||
return SetValuesFromSeedMG5Shiny;
|
||||
|
||||
case PIDType.Pokewalker:
|
||||
return (pk, seed) => pk.PID = GetPokeWalkerPID(pk.TID16, pk.SID16, seed%24, pk.Gender, pk.PersonalInfo.Gender);
|
||||
return (pk, seed) =>
|
||||
{
|
||||
var pid = pk.PID = PokewalkerRNG.GetPID(pk.TID16, pk.SID16, seed % 24, pk.Gender, pk.PersonalInfo.Gender);
|
||||
pk.RefreshAbility((int)(pid & 1));
|
||||
};
|
||||
|
||||
// others: unimplemented
|
||||
case PIDType.CuteCharm:
|
||||
|
@ -245,40 +249,6 @@ public static class PIDGenerator
|
|||
return PID;
|
||||
}
|
||||
|
||||
public static uint GetPokeWalkerPID(ushort TID16, ushort SID16, uint nature, int gender, byte gr)
|
||||
{
|
||||
if (nature >= 24)
|
||||
nature = 0;
|
||||
uint pid = ((((uint)TID16 ^ SID16) >> 8) ^ 0xFF) << 24; // the most significant byte of the PID is chosen so the Pokémon can never be shiny.
|
||||
// Ensure nature is set to required nature without affecting shininess
|
||||
pid += nature - (pid % 25);
|
||||
|
||||
if (gr is 0 or >= 0xFE) // non-dual gender
|
||||
return pid;
|
||||
|
||||
// Ensure Gender is set to required gender without affecting other properties
|
||||
// If Gender is modified, modify the ability if appropriate
|
||||
|
||||
// either m/f
|
||||
var pidGender = (pid & 0xFF) < gr ? 1 : 0;
|
||||
if (gender == pidGender)
|
||||
return pid;
|
||||
|
||||
if (gender == 0) // Male
|
||||
{
|
||||
pid += (((gr - (pid & 0xFF)) / 25) + 1) * 25;
|
||||
if ((nature & 1) != (pid & 1))
|
||||
pid += 25;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid -= ((((pid & 0xFF) - gr) / 25) + 1) * 25;
|
||||
if ((nature & 1) != (pid & 1))
|
||||
pid -= 25;
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
public static void SetValuesFromSeedMG5Shiny(PKM pk, uint seed)
|
||||
{
|
||||
var gv = seed >> 24;
|
||||
|
@ -287,15 +257,6 @@ public static class PIDGenerator
|
|||
SetRandomIVs(pk);
|
||||
}
|
||||
|
||||
public static void SetRandomPIDPokewalker(PKM pk, int nature, int gender)
|
||||
{
|
||||
// Pokewalker PIDs cannot yield multiple abilities from the input nature-gender-trainerID. Disregard any ability request.
|
||||
var pi = pk.PersonalInfo.Gender;
|
||||
pk.Gender = gender;
|
||||
pk.PID = GetPokeWalkerPID(pk.TID16, pk.SID16, (uint)nature, gender, pi);
|
||||
pk.RefreshAbility((int) (pk.PID & 1));
|
||||
}
|
||||
|
||||
public static void SetRandomWildPID4(PKM pk, int nature, int ability, int gender, PIDType type)
|
||||
{
|
||||
pk.RefreshAbility(ability);
|
||||
|
|
|
@ -23,10 +23,9 @@ public static class WordFilter
|
|||
{
|
||||
var lineCount = 1 + patterns.Count('\n');
|
||||
var result = new Regex[lineCount];
|
||||
var enumerator = patterns.EnumerateLines();
|
||||
int i = 0;
|
||||
while (enumerator.MoveNext())
|
||||
result[i++] = new Regex(enumerator.Current.ToString(), Options);
|
||||
foreach (var line in patterns.EnumerateLines())
|
||||
result[i++] = new Regex(line.ToString(), Options);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -153,14 +153,14 @@ public class PIDIVTest
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(30, 31, 31, 14, 31, 31, 0x28070031, 24, (int)Species.Pikachu, PokewalkerCourse4.YellowForest)]
|
||||
public void PokewalkerIVTest(uint hp, uint atk, uint def, uint spA, uint spD, uint spE, uint seed, int expect, ushort species, PokewalkerCourse4 course)
|
||||
[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)
|
||||
{
|
||||
Span<uint> tmp = stackalloc uint[LCRNG.MaxCountSeedsIV];
|
||||
(uint actualSeed, int prior) = MethodFinderPokewalker.GetSeedsPokewalkerIVs(species, course, tmp, hp, atk, def, spA, spD, spE);
|
||||
prior.Should().NotBe(MethodFinderPokewalker.NoPokwalkerMatch);
|
||||
prior.Should().Be(expect);
|
||||
actualSeed.Should().Be(seed);
|
||||
var result = PokewalkerRNG.GetFirstSeed(species, course, tmp, hp, atk, def, spA, spD, spE);
|
||||
result.Type.Should().Be(type);
|
||||
result.PriorPoke.Should().Be(expect);
|
||||
result.Seed.Should().Be(seed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
Loading…
Reference in a new issue