diff --git a/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs b/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs
index 7c19e5af5..a64fcf226 100644
--- a/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs
+++ b/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs
@@ -185,6 +185,18 @@ private static IEnumerable FilterNatureSync(IEnumerable seeds,
foreach (var seed in seeds)
{
var s = seed.Seed;
+
+ if (info.Safari3)
+ {
+ // successful pokeblock activation
+ bool result = IsValidPokeBlockNature(s, info.Nature, out uint blockSeed);
+ if (result)
+ yield return info.GetFrame(blockSeed, seed.Charm3 ? LeadRequired.CuteCharm : LeadRequired.None);
+
+ // if no pokeblocks present (or failed call), fall out of the safari specific code and generate via the other scenarios
+ s = info.RNG.Prev(s); // wasted RNG call
+ }
+
var rand = s >> 16;
bool sync = info.AllowLeads && !seed.Charm3 && (info.DPPt ? rand >> 15 : rand & 1) == 0;
bool reg = (info.DPPt ? rand / 0xA3E : rand % 25) == info.Nature;
@@ -209,6 +221,60 @@ private static IEnumerable FilterNatureSync(IEnumerable seeds,
}
}
}
+ private static bool IsValidPokeBlockNature(uint seed, uint nature, out uint natureOrigin)
+ {
+ if (nature % 6 == 0) // neutral
+ {
+ natureOrigin = 0;
+ return false;
+ }
+
+ // unroll the RNG to a stack of seeds
+ var stack = new Stack();
+ for (uint i = 0; i < 25; i++)
+ for (uint j = 1 + i; j < 25; j++)
+ stack.Push(seed = RNG.LCRNG.Prev(seed));
+
+ natureOrigin = RNG.LCRNG.Prev(stack.Peek());
+ if (natureOrigin >> 16 % 100 >= 80) // failed proc
+ return false;
+
+ // init natures
+ uint[] natures = new uint[25];
+ for (uint i = 0; i < 25; i++)
+ natures[i] = i;
+
+ // shuffle nature list
+ for (uint i = 0; i < 25; i++)
+ for (uint j = 1 + i; j < 25; j++)
+ {
+ var s = stack.Pop();
+ if ((s >> 16 & 1) == 0)
+ continue; // only swap if 1
+
+ var temp = natures[i];
+ natures[i] = natures[j];
+ natures[j] = temp;
+ }
+
+ var likes = Pokeblock.GetLikedBlockFlavor(nature);
+ // best case scenario is a perfect flavored pokeblock for the nature.
+ // has liked flavor, and all other non-disliked flavors are present.
+ // is it possible to skip this step?
+ for (int i = 0; i < 25; i++)
+ {
+ var n = natures[i];
+ if (n == nature)
+ break;
+
+ var nl = Pokeblock.GetLikedBlockFlavor(natures[i]);
+ if (nl == likes) // next random nature likes the same block as the desired nature
+ return false; // current nature is chosen instead, fail!
+ }
+ // unroll once more to hit the level calc (origin with respect for beginning the nature calcs)
+ natureOrigin = RNG.LCRNG.Prev(natureOrigin);
+ return true;
+ }
///
/// Filters the input according to a Cute Charm frame generation pattern.
diff --git a/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs b/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs
index 9dc565f5d..91cadbce2 100644
--- a/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs
+++ b/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs
@@ -10,6 +10,8 @@ public class FrameGenerator
public readonly bool AllowLeads;
public readonly FrameType FrameType = FrameType.None;
public readonly RNG RNG;
+ public readonly bool Safari3;
+
public Frame GetFrame(uint seed, LeadRequired lead) => new Frame(seed, FrameType, RNG, lead);
public Frame GetFrame(uint seed, LeadRequired lead, uint esv) => new Frame(seed, FrameType, RNG, lead) {ESV = esv};
@@ -33,6 +35,7 @@ public FrameGenerator(PIDIV pidiv, PKM pk)
DPPt = false;
FrameType = FrameType.MethodH;
RNG = pidiv.RNG;
+ Safari3 = pk.Ball == 5;
if (ver != GameVersion.E)
return;
diff --git a/PKHeX.Core/Legality/RNG/Frame/Pokeblock.cs b/PKHeX.Core/Legality/RNG/Frame/Pokeblock.cs
new file mode 100644
index 000000000..8f685ade1
--- /dev/null
+++ b/PKHeX.Core/Legality/RNG/Frame/Pokeblock.cs
@@ -0,0 +1,16 @@
+namespace PKHeX.Core
+{
+ public static class Pokeblock
+ {
+ public static Flavor GetLikedBlockFlavor(uint nature) => (Flavor)(nature/5);
+ public static Flavor GetDislikedBlockFlavor(uint nature) => (Flavor)(nature%5);
+ public enum Flavor
+ {
+ Spicy,
+ Sour,
+ Sweet,
+ Dry,
+ Bitter,
+ }
+ }
+}
\ No newline at end of file