Add pokespot reverse/forward methods

Also adds partial IV specifying for cxd generating
This commit is contained in:
Kurt 2025-03-09 20:09:17 -05:00
parent 2fbe2feb52
commit 5bf9865f3f
9 changed files with 296 additions and 88 deletions

View File

@ -48,10 +48,10 @@ private static int GetLegalBallsEvolvedShedinja(Span<Ball> result, PKM pk, IEnco
{
switch (enc)
{
case EncounterSlot4 when IsNincadaEvolveInOrigin(pk, enc):
case EncounterSlot4 s4 when IsNincadaEvolveInOrigin(pk, s4):
ShedinjaEvolve4.CopyTo(result);
return ShedinjaEvolve4.Length;
case EncounterSlot3 when IsNincadaEvolveInOrigin(pk, enc):
case EncounterSlot3 s3 when IsNincadaEvolveInOrigin(pk, s3):
return LoadLegalBalls(result, pk, enc);
}
result[0] = Poke;

View File

@ -31,9 +31,11 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, Le
{
if (z is EncounterSlot3XD w)
{
var pidiv = MethodFinder.GetPokeSpotSeedFirst(pk, w.SlotNumber);
if (pidiv.Type == PIDType.PokeSpot)
info.PIDIV = pidiv;
if (MethodPokeSpot.TryGetOriginSeeds(pk, w, out var pid, out var ivs))
{
const PIDType type = PIDType.PokeSpot;
info.PIDIV = new PIDIV(type, pid).AsMutated(type, ivs);
}
}
else if (z is IShadow3 s)
{

View File

@ -59,10 +59,9 @@ public XK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
private void SetPINGA(XK3 pk, EncounterCriteria criteria, PersonalInfo3 pi)
{
var gender = criteria.GetGender(pi);
var nature = criteria.GetNature();
int ability = criteria.GetAbilityFromNumber(Ability);
PIDGenerator.SetRandomPokeSpotPID(pk, nature, gender, ability, SlotNumber);
MethodPokeSpot.SetRandomPID(pk, criteria, pi.Gender, SlotNumber);
if (criteria.IsSpecifiedIVsAll() && !MethodPokeSpot.TrySetIVs(pk, criteria, LevelMin, LevelMax))
MethodPokeSpot.SetRandomIVs(pk, criteria, LevelMin, LevelMax);
}
#endregion

View File

@ -154,7 +154,7 @@ private static void AddEncounterInfoPIDIV(List<string> lines, LegalInfo info)
{
var seed = pidiv.OriginSeed;
var line = string.Format(L_FOriginSeed_0, seed.ToString("X8"));
if (info is { EncounterMatch: IRandomCorrelationEvent3 } && pidiv.Mutated is not 0 && pidiv.OriginSeed != pidiv.EncounterSeed)
if (pidiv.Mutated is not 0 && pidiv.OriginSeed != pidiv.EncounterSeed)
line += $" [{pidiv.EncounterSeed:X8}]";
lines.Add(line);
}

View File

@ -362,6 +362,7 @@ public static bool SetStarterFromIVs(XK3 pk, EncounterCriteria criteria)
public static void SetRandomStarter(XK3 pk, EncounterCriteria criteria)
{
var seed = Util.Rand32();
bool filterIVs = criteria.IsSpecifiedIVsAny(out var count) && count <= 2;
while (true)
{
@ -404,6 +405,7 @@ public static void SetRandom(CK3 pk, EncounterCriteria criteria, byte gender)
var id32 = pk.ID32;
var seed = Util.Rand32();
bool filterIVs = criteria.IsSpecifiedIVsAny(out var count) && count <= 2;
while (true)
{
// Get PID
@ -425,6 +427,8 @@ public static void SetRandom(CK3 pk, EncounterCriteria criteria, byte gender)
var iv32 = iv2 << 15 | iv1;
if (criteria.IsSpecifiedHiddenPower() && !criteria.IsSatisfiedHiddenPower(iv32))
continue;
if (filterIVs && !criteria.IsSatisfiedIVs(iv32))
continue;
var abit = XDRNG.Next16(ref seed);
pk.PID = pid;
@ -439,6 +443,7 @@ public static void SetRandom(XK3 pk, EncounterCriteria criteria, byte gender)
var id32 = pk.ID32;
var seed = Util.Rand32();
bool filterIVs = criteria.IsSpecifiedIVsAny(out var count) && count <= 2;
while (true)
{
// Get PID
@ -460,6 +465,8 @@ public static void SetRandom(XK3 pk, EncounterCriteria criteria, byte gender)
var iv32 = iv2 << 15 | iv1;
if (criteria.IsSpecifiedHiddenPower() && !criteria.IsSatisfiedHiddenPower(iv32))
continue;
if (filterIVs && !criteria.IsSatisfiedIVs(iv32))
continue;
var abit = XDRNG.Next16(ref seed);
pk.PID = pid;

View File

@ -0,0 +1,275 @@
using System;
namespace PKHeX.Core;
public static class MethodPokeSpot
{
/// <summary>
/// Checks if the PID generating seed is valid for the given slot.
/// </summary>
/// <param name="slot">Encounter slot index (0-2).</param>
/// <param name="seed">PID generating seed.</param>
/// <param name="origin">Origin seed.</param>
public static PokeSpotSetup IsValidActivation(byte slot, uint seed, out uint origin)
{
// Forward call structure: depends on if Bonsly is available.
// No Bonsly
// 0: Origin
// 1: rand(003) == 0 - Activation
// 2: rand(100) < 10 - if Munchlax available, spawn Munchlax & return.
// x: rand(100) encounter slot
// y: pid
// Bonsly
// 0: Origin
// 1: rand(003) == 0 - Activation
// 2: ???
// 3: rand(100) < 30 - Bonsly; < 40 - Munchlax
// x: rand(100) encounter slot
// y: pid
origin = seed;
// wild
var esv = (seed >> 16) % 100;
if (!IsSlotValid(slot, esv))
return PokeSpotSetup.Invalid;
// Assume only Munchlax available
var preSlot = XDRNG.Prev16(ref origin);
if (preSlot % 100 < 10)
return PokeSpotSetup.Invalid; // worst case: only Munchlax available
preSlot = XDRNG.Prev16(ref origin);
if (preSlot % 3 == 0)
return PokeSpotSetup.NoBonsly;
// Assume both available
if (preSlot % 100 < 30)
return PokeSpotSetup.Invalid;
preSlot = XDRNG.Prev16(ref origin);
if (preSlot % 3 == 0)
return PokeSpotSetup.Bonsly;
return PokeSpotSetup.Invalid;
}
public static bool TryGetOriginSeeds(PKM pk, EncounterSlot3XD slot, out uint pid, out uint ivs)
{
pid = 0;
ivs = 0;
if (!TryGetOriginSeedPID(pk.PID, slot.SlotNumber, out pid))
return false;
if (!TryGetOriginSeedIVs(pk, slot.LevelMin, slot.LevelMax, out ivs))
return false;
return true;
}
public static bool TryGetOriginSeedIVs(PKM pk, byte levelMin, byte levelMax, out uint origin)
{
var hp = (uint)pk.IV_HP;
var atk = (uint)pk.IV_ATK;
var def = (uint)pk.IV_DEF;
var spa = (uint)pk.IV_SPA;
var spd = (uint)pk.IV_SPD;
var spe = (uint)pk.IV_SPE;
var iv1 = hp | (atk << 5) | (def << 10);
var iv2 = spe | (spa << 5) | (spd << 10);
return TryGetOriginSeedIVs(iv1 << 16, iv2 << 16, levelMin, levelMax, pk.MetLevel, pk.Format == 3, out origin);
}
public static bool TryGetOriginSeedPID(uint pid, byte slot, out uint origin)
{
Span<uint> seeds = stackalloc uint[XDRNG.MaxCountSeedsPID];
var count = XDRNG.GetSeeds(seeds, pid);
if (count == 0)
{
origin = 0;
return false;
}
var reg = seeds[..count];
foreach (var seed in reg)
{
// check for valid encounter slot info
if (IsValidActivation(slot, seed, out origin) == PokeSpotSetup.Invalid)
continue;
return true;
}
origin = 0;
return false;
}
public static bool TryGetOriginSeedIVs(uint iv1, uint iv2, byte levelMin, byte levelMax, byte metLevel, bool hasOriginalMetLevel, out uint origin)
{
Span<uint> seeds = stackalloc uint[XDRNG.MaxCountSeedsIV];
var count = XDRNG.GetSeedsIVs(seeds, iv1, iv2);
if (count == 0)
{
origin = 0;
return false;
}
var levelDelta = 1u + levelMax - levelMin;
foreach (var preIV in seeds[..count])
{
// origin
// {u16 fakeID, u16 fakeID}
// ???
// levelRand
// {u16 fakePID, u16 fakePID} => you are here (origin)
// {u16 iv1, u16 iv2}
// ability
var lvl16 = XDRNG.Prev2(preIV) >> 16;
var lvlRnd = lvl16 % levelDelta;
var lvl = (byte)(levelMin + lvlRnd);
if (hasOriginalMetLevel ? (metLevel != lvl) : (metLevel < lvl))
continue;
var abil16 = XDRNG.Next3(preIV) >> 16;
var abit = abil16 & 1; // don't care about ability, might be reset on evolution
origin = XDRNG.Prev6(preIV);
return true;
}
origin = 0;
return false;
}
public static bool TrySetIVs(XK3 pk, EncounterCriteria criteria, byte levelMin, byte levelMax)
{
Span<uint> seeds = stackalloc uint[XDRNG.MaxCountSeedsIV];
criteria.GetCombinedIVs(out var iv1, out var iv2);
var count = XDRNG.GetSeedsIVs(seeds, iv1 << 16, iv2 << 16);
if (count == 0)
return false;
var levelDelta = 1u + levelMax - levelMin;
bool checkLevel = criteria.IsSpecifiedLevelRange() && criteria.IsLevelWithinRange(levelMin, levelMax);
foreach (var preIV in seeds[..count])
{
// origin
// {u16 fakeID, u16 fakeID}
// ???
// levelRand
// {u16 fakePID, u16 fakePID} => you are here (origin)
// {u16 iv1, u16 iv2}
// ability
var lvl16 = XDRNG.Prev2(preIV) >> 16;
var lvlRnd = lvl16 % levelDelta;
var lvl = (byte)(levelMin + lvlRnd);
if (checkLevel && !criteria.IsSatisfiedLevelRange(lvl))
continue;
var abil16 = XDRNG.Next3(preIV) >> 16;
var abit = abil16 & 1; // don't care about ability, might be reset on evolution
SetIVs(pk, iv1, iv2);
pk.RefreshAbility((int)(abit & 1));
pk.CurrentLevel = pk.MetLevel = lvl;
return true;
}
return false;
}
public static void SetRandomPID(XK3 pk, EncounterCriteria criteria, byte gender, byte slot)
=> pk.PID = GetRandomPID(pk.ID32, criteria, gender, slot, out _);
public static uint GetRandomPID(uint id32, EncounterCriteria criteria, byte gender, byte slot, out uint origin)
{
var seed = Util.Rand32();
while (true)
{
var result = IsValidActivation(slot, seed, out origin);
if (result == PokeSpotSetup.Invalid)
{
seed = XDRNG.Next(seed);
continue;
}
var pid = GetPIDRegular(ref seed);
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
continue;
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(EntityGender.GetFromPIDAndRatio(pid, gender)))
continue;
if (criteria.Shiny.IsShiny() != ShinyUtil.GetIsShiny(id32, pid, 8))
continue;
return pid;
}
}
public static void SetRandomIVs(XK3 pk, EncounterCriteria criteria, byte levelMin, byte levelMax)
{
var levelDelta = 1u + levelMax - levelMin;
var seed = Util.Rand32();
bool filterIVs = criteria.IsSpecifiedIVsAny(out var count) && count <= 2;
bool checkLevel = criteria.IsSpecifiedLevelRange() && criteria.IsLevelWithinRange(levelMin, levelMax);
while (true)
{
// origin
// {u16 fakeID, u16 fakeID}
// ???
// levelRand
// {u16 fakePID, u16 fakePID} => you are here (origin)
// {u16 iv1, u16 iv2}
// ability
var preIV = seed;
var iv1 = XDRNG.Next15(ref seed);
var iv2 = XDRNG.Next15(ref seed);
var iv32 = iv2 << 15 | iv1;
if (criteria.IsSpecifiedHiddenPower() && !criteria.IsSatisfiedHiddenPower(iv32))
continue;
if (filterIVs && !criteria.IsSatisfiedIVs(iv32))
continue;
var lvl16 = XDRNG.Prev2(preIV) >> 16;
var lvlRnd = lvl16 % levelDelta;
var lvl = (byte)(levelMin + lvlRnd);
if (checkLevel && !criteria.IsSatisfiedLevelRange(lvl))
continue;
var abil16 = XDRNG.Next3(preIV) >> 16;
var abit = abil16 & 1; // don't care about ability, might be reset on evolution
SetIVs(pk, iv1, iv2);
pk.RefreshAbility((int)(abit & 1));
pk.CurrentLevel = pk.MetLevel = lvl;
return;
}
}
private static void SetIVs(XK3 pk, uint iv1, uint iv2)
=> pk.SetIVs(iv2 << 15 | iv1);
private static uint GetPIDRegular(ref uint seed)
{
var a = XDRNG.Next16(ref seed);
var b = XDRNG.Next16(ref seed);
return GetPIDRegular(a, b);
}
private static uint GetPIDRegular(uint a, uint b) => a << 16 | b;
public static bool IsSlotValid(byte slot, uint esv) => GetSlot(esv) == slot;
public static byte GetSlot(uint esv) => esv switch
{
< 50 => 0,
< 85 => 1,
_ => 2,
};
}
public enum PokeSpotSetup : byte
{
Invalid,
NoBonsly,
Bonsly,
}

View File

@ -589,55 +589,4 @@ private static PIDIV AnalyzeGB(PKM _)
// not implemented; correlation between IVs and RNG hasn't been converted to code.
return PIDIV.None;
}
public static bool IsPokeSpotActivation(int slot, uint seed, out uint s)
{
s = seed;
var esv = (seed >> 16) % 100;
if (!IsPokeSpotSlotValid(slot, esv))
{
// todo
}
// check for valid activation
s = XDRNG.Prev(seed);
if ((s >> 16) % 3 != 0)
{
if ((s >> 16) % 100 < 10) // can't fail a Munchlax/Bonsly encounter check
{
// todo
}
s = XDRNG.Prev(s);
if ((s >> 16) % 3 != 0) // can't activate even if generous
{
// todo
}
}
return true;
}
private static bool IsPokeSpotSlotValid(int slot, uint esv) => slot switch
{
0 => esv < 50 , // [0,50)
1 => esv - 50 < 35, // [50,85)
2 => esv >= 85, // [85,100)
_ => false,
};
public static PIDIV GetPokeSpotSeedFirst(PKM pk, byte slot)
{
// Activate (rand % 3)
// Munchlax / Bonsly (10%/30%)
// Encounter Slot Value (ESV) = 50%/35%/15% rarity (0-49, 50-84, 85-99)
Span<uint> seeds = stackalloc uint[XDRNG.MaxCountSeedsPID];
int count = XDRNG.GetSeeds(seeds, pk.EncryptionConstant);
var reg = seeds[..count];
foreach (var seed in reg)
{
// check for valid encounter slot info
if (IsPokeSpotActivation(slot, seed, out uint s))
return new PIDIV(PokeSpot, s);
}
return default;
}
}

View File

@ -205,25 +205,6 @@ private static uint SetRandomPokeSpotPID(PKM pk, uint seed)
return E;
}
public static void SetRandomPokeSpotPID(PKM pk, Nature nature, byte gender, int ability, int slot)
{
var rnd = Util.Rand;
while (true)
{
var seed = rnd.Rand32();
if (!MethodFinder.IsPokeSpotActivation(slot, seed, out var newSeed))
continue;
SetRandomPokeSpotPID(pk, newSeed);
pk.SetRandomIVs();
if (!IsValidCriteria4(pk, nature, ability, gender))
continue;
return;
}
}
public static uint GetMG5ShinyPID(uint gval, uint av, ushort TID16, ushort SID16)
{
uint pid = ((gval ^ TID16 ^ SID16) << 16) | gval;

View File

@ -146,14 +146,9 @@ public void PIDIVMatchingTest5()
public void PIDIVPokeSpotTest()
{
// XD PokeSpots: Check all 3 Encounter Slots (examples are one for each location).
var pkPS0 = new PK3 { PID = 0x7B2D9DA7 }; // Zubat (Cave)
MethodFinder.GetPokeSpotSeedFirst(pkPS0, 0).Type.Should().Be(PIDType.PokeSpot); // PokeSpot encounter info mismatch (Common)
var pkPS1 = new PK3 { PID = 0x3EE9AF66 }; // Gligar (Rock)
MethodFinder.GetPokeSpotSeedFirst(pkPS1, 1).Type.Should().Be(PIDType.PokeSpot); // PokeSpot encounter info mismatch (Uncommon)
var pkPS2 = new PK3 { PID = 0x9B667F3C }; // Surskit (Oasis)
MethodFinder.GetPokeSpotSeedFirst(pkPS2, 2).Type.Should().Be(PIDType.PokeSpot); // PokeSpot encounter info mismatch (Rare)
MethodPokeSpot.TryGetOriginSeedPID(0x7B2D9DA7, 0, out _).Should().BeTrue(); // Zubat (Cave) (Common)
MethodPokeSpot.TryGetOriginSeedPID(0x3EE9AF66, 1, out _).Should().BeTrue(); // Gligar (Rock) (Uncommon)
MethodPokeSpot.TryGetOriginSeedPID(0x9B667F3C, 2, out _).Should().BeTrue(); // Surskit (Oasis) (Rare)
}
[Theory]