mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-03-21 17:48:28 -05:00
Add pokespot reverse/forward methods
Also adds partial IV specifying for cxd generating
This commit is contained in:
parent
2fbe2feb52
commit
5bf9865f3f
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
275
PKHeX.Core/Legality/RNG/CXD/MethodPokeSpot.cs
Normal file
275
PKHeX.Core/Legality/RNG/CXD/MethodPokeSpot.cs
Normal 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,
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user