diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/GenerateMethodH.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/GenerateMethodH.cs
index d77389523..64411fbf2 100644
--- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/GenerateMethodH.cs
+++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/GenerateMethodH.cs
@@ -48,7 +48,7 @@ public static class GenerateMethodH
if (!criteria.IsGenderSatisfied(gender))
break; // try again
- pk.MetLevel = pk.CurrentLevel = (byte)((lv % (enc.LevelMax - enc.LevelMin + 1)) + enc.LevelMin);
+ pk.MetLevel = pk.CurrentLevel = (byte)MethodH.GetRandomLevel(enc, lv, LeadRequired.None);
SetPIDIVSequential(pk, pid, seed);
return;
}
@@ -120,10 +120,18 @@ public static class GenerateMethodH
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (!criteria.IsGenderSatisfied(gender))
continue;
- var lead = MethodH.GetSeed(enc, seed, enc, pk.E, gender, 3);
+ var lead = MethodH.GetSeed(enc, seed, pk.E, gender, criteria.ForceMinLevelRange);
if (!lead.IsValid()) // Verifies the slot, (min) level, and nature loop; if it passes, apply the details.
continue;
+ // always level rand
+ {
+ var lv = MethodH.SkipToLevelRand(enc, lead.Seed) >> 16;
+ var actual = MethodH.GetRandomLevel(enc, lv, lead.Lead);
+ if (pk.MetLevel != actual)
+ pk.MetLevel = pk.CurrentLevel = (byte)actual;
+ }
+
pk.PID = pid;
pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF);
pk.RefreshAbility((int)(pid & 1));
@@ -147,10 +155,18 @@ public static class GenerateMethodH
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (!criteria.IsGenderSatisfied(gender))
continue;
- var lead = MethodH.GetSeed(enc, seed, enc, pk.E, gender, 3);
+ var lead = MethodH.GetSeed(enc, seed, pk.E, gender, criteria.ForceMinLevelRange);
if (!lead.IsValid()) // Verifies the slot and nature loop; if it passes, apply the details.
continue;
+ // always level rand
+ {
+ var lv = MethodH.SkipToLevelRand(enc, lead.Seed) >> 16;
+ var actual = MethodH.GetRandomLevel(enc, lv, lead.Lead);
+ if (pk.MetLevel != actual)
+ pk.MetLevel = pk.CurrentLevel = (byte)actual;
+ }
+
pk.PID = pid;
pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF);
pk.RefreshAbility((int)(pid & 1));
@@ -187,10 +203,11 @@ public static class GenerateMethodH
continue;
seed = LCRNG.Prev(seed);
}
- var lead = MethodH.GetSeed(enc, seed, enc, false, 2, 3);
+ var lead = MethodH.GetSeed(enc, seed, false, 2, criteria.ForceMinLevelRange);
if (!lead.IsValid()) // Verifies the slot and form loop; if it passes, apply the details.
continue;
+ // Level is always 25, and no need to consider ability (always slot 0, not dual ability).
pk.PID = pid;
pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF);
return true;
@@ -212,10 +229,11 @@ public static class GenerateMethodH
var form = EntityPID.GetUnownForm3(pid);
if (form != enc.Form)
continue;
- var lead = MethodH.GetSeed(enc, seed, enc, false, 2, 3);
+ var lead = MethodH.GetSeed(enc, seed, false, 2, criteria.ForceMinLevelRange);
if (!lead.IsValid()) // Verifies the slot and form loop; if it passes, apply the details.
continue;
+ // Level is always 25, and no need to consider ability (always slot 0, not dual ability).
pk.PID = pid;
pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF);
return true;
diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/MethodH.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/MethodH.cs
index c80f52f95..bc0c07920 100644
--- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/MethodH.cs
+++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/MethodH.cs
@@ -18,7 +18,7 @@ public static class MethodH
/// Level range constraints for the capture, if known.
/// Version encountered in (either Emerald or not)
/// Gender encountered as
- /// Current format (different from 3)
+ /// Current format (different from 3 will use level range instead of exact)
public static LeadSeed GetSeed(TEnc enc, uint seed, TEvo evo, bool emerald, byte gender, byte format)
where TEnc : IEncounterSlot3
where TEvo : ILevelRange
@@ -32,9 +32,10 @@ public static class MethodH
return GetOriginSeed(info, enc, seed, nature, evo.LevelMin, evo.LevelMax, format);
}
+ /// Used when generating or ignoring level ranges.
///
- public static LeadSeed GetSeed(TEnc enc, uint seed, bool emerald, byte gender, byte format)
- where TEnc : IEncounterSlot3 => GetSeed(enc, seed, enc, emerald, gender, format);
+ public static LeadSeed GetSeed(TEnc enc, uint seed, bool emerald, byte gender, bool minLevel = false)
+ where TEnc : IEncounterSlot3 => GetSeed(enc, seed, enc, emerald, gender, minLevel ? (byte)3 : (byte)0);
// Summary of Random Determinations:
// Nature: rand() % 25 == nature
@@ -203,6 +204,18 @@ public static class MethodH
public static bool IsEncounterCheckApplicable(SlotType3 type) => type is Rock_Smash; // Fishing can use Sticky/Suction along with Friendship boost.
+ ///
+ public static uint SkipToLevelRand(T enc, uint seed)
+ where T : IEncounterSlot3
+ {
+ if (enc.Type is Rock_Smash)
+ return LCRNG.Next3(seed); // Proc, ESV, level.
+ if (enc.Type.IsFishingRodType())
+ return LCRNG.Next2(seed); // ESV, level.
+ // Can sweet scent trigger.
+ return LCRNG.Next2(seed); // ESV, level.
+ }
+
public static bool CheckEncounterActivation(T enc, ref LeadSeed result)
where T : IEncounterSlot3
{
@@ -570,7 +583,13 @@ public static class MethodH
private static bool IsLevelValid(T enc, byte min, byte max, byte format, uint u16LevelRand) where T : ILevelRange
{
- var level = GetExpectedLevel(enc, u16LevelRand);
+ var level = GetRandomLevel(enc, u16LevelRand);
+ return IsOriginalLevelValid(min, max, format, level);
+ }
+
+ private static bool IsLevelValidMinus1(T enc, byte min, byte max, byte format, uint u16LevelRand) where T : ILevelRange
+ {
+ var level = GetRandomLevelMinus1(enc, u16LevelRand);
return IsOriginalLevelValid(min, max, format, level);
}
@@ -581,20 +600,21 @@ public static class MethodH
return LevelRangeExtensions.IsLevelWithinRange((int)level, min, max);
}
- private static uint GetExpectedLevel(T enc, uint u16LevelRand) where T : ILevelRange
+ public static uint GetRandomLevel(T enc, uint u16LevelRand, LeadRequired lead) where T : ILevelRange => lead switch
+ {
+ PressureHustleSpiritFail => GetRandomLevelMinus1(enc, u16LevelRand),
+ PressureHustleSpirit => enc.LevelMax,
+ _ => GetRandomLevel(enc, u16LevelRand),
+ };
+
+ private static uint GetRandomLevel(T enc, uint u16LevelRand) where T : ILevelRange
{
var min = enc.LevelMin;
uint mod = 1u + enc.LevelMax - min;
return (u16LevelRand % mod) + min;
}
- private static bool IsLevelValidMinus1(T enc, byte min, byte max, byte format, uint u16LevelRand) where T : ILevelRange
- {
- var level = GetExpectedLevelMinus1(enc, u16LevelRand);
- return IsOriginalLevelValid(min, max, format, level);
- }
-
- private static uint GetExpectedLevelMinus1(T enc, uint u16LevelRand) where T : ILevelRange
+ private static uint GetRandomLevelMinus1(T enc, uint u16LevelRand) where T : ILevelRange
{
var min = enc.LevelMin;
uint mod = 1u + enc.LevelMax - min;
diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodJ.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodJ.cs
index 074a83143..5c2163572 100644
--- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodJ.cs
+++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodJ.cs
@@ -59,12 +59,7 @@ public static class GenerateMethodJ
break; // try again
if (randLevel)
- {
- var lvl = enc.Type is SlotType4.HoneyTree
- ? MethodJ.GetHoneyTreeLevel(lv)
- : ((lv % (enc.LevelMax - enc.LevelMin + 1)) + enc.LevelMin);
- pk.MetLevel = pk.CurrentLevel = (byte)lvl;
- }
+ pk.MetLevel = pk.CurrentLevel = (byte)MethodJ.GetRandomLevel(enc, lv, LeadRequired.None);
pk.PID = pid;
var iv1 = LCRNG.Next16(ref seed);
var iv2 = LCRNG.Next16(ref seed);
@@ -107,10 +102,18 @@ public static class GenerateMethodJ
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (!criteria.IsGenderSatisfied(gender))
continue;
- var lead = MethodJ.GetSeed(enc, seed, enc, 4);
+ var lead = MethodJ.GetSeed(enc, seed, criteria.ForceMinLevelRange);
if (!lead.IsValid()) // Verifies the slot, (min) level, and nature loop; if it passes, apply the details.
continue;
+ if (MethodJ.IsLevelRand(enc))
+ {
+ var lv = MethodJ.SkipToLevelRand(enc, lead.Seed) >> 16;
+ var actual = MethodJ.GetRandomLevel(enc, lv, lead.Lead);
+ if (pk.MetLevel != actual)
+ pk.MetLevel = pk.CurrentLevel = (byte)actual;
+ }
+
pk.PID = pid;
pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF);
pk.Gender = gender;
diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodK.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodK.cs
index 584ead0a5..3942fb480 100644
--- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodK.cs
+++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodK.cs
@@ -60,7 +60,7 @@ public static class GenerateMethodK
break; // try again
if (randLevel)
- pk.MetLevel = pk.CurrentLevel = (byte)((lv % (enc.LevelMax - enc.LevelMin + 1)) + enc.LevelMin);
+ pk.MetLevel = pk.CurrentLevel = (byte)MethodK.GetRandomLevel(enc, lv, LeadRequired.None);
pk.PID = pid;
var iv1 = LCRNG.Next16(ref seed);
var iv2 = LCRNG.Next16(ref seed);
@@ -106,10 +106,18 @@ public static class GenerateMethodK
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (!criteria.IsGenderSatisfied(gender))
continue;
- var lead = MethodK.GetSeed(enc, seed, enc, 4);
+ var lead = MethodK.GetSeed(enc, seed, criteria.ForceMinLevelRange);
if (!lead.IsValid()) // Verifies the slot, (min) level, and nature loop; if it passes, apply the details.
continue;
+ if (MethodK.IsLevelRand(enc))
+ {
+ var lv = MethodK.SkipToLevelRand(enc, lead.Seed, lead.Lead) >> 16;
+ var actual = MethodK.GetRandomLevel(enc, lv, lead.Lead);
+ if (pk.MetLevel != actual)
+ pk.MetLevel = pk.CurrentLevel = (byte)actual;
+ }
+
pk.PID = pid;
pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF);
pk.Gender = gender;
diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodJ.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodJ.cs
index 1a46510b5..740ed45cc 100644
--- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodJ.cs
+++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodJ.cs
@@ -17,7 +17,7 @@ public static class MethodJ
/// Encounter template.
/// Seed that immediately generates the PID.
/// Level range constraints for the capture, if known.
- /// Current format (different from 4)
+ /// Current format (different from 4 will use level range instead of exact)
public static LeadSeed GetSeed(TEnc enc, uint seed, TEvo evo, byte format)
where TEnc : IEncounterSlot4
where TEvo : ILevelRange
@@ -29,9 +29,10 @@ public static class MethodJ
return GetOriginSeed(enc, seed, nature, frames, evo.LevelMin, evo.LevelMax, format);
}
+ /// Used when generating or ignoring level ranges.
///
- public static LeadSeed GetSeed(TEnc enc, uint seed, byte format)
- where TEnc : IEncounterSlot4 => GetSeed(enc, seed, enc, format);
+ public static LeadSeed GetSeed(TEnc enc, uint seed, bool minLevel = false) where TEnc : IEncounterSlot4
+ => GetSeed(enc, seed, enc, minLevel ? (byte)4 : (byte)0);
// Summary of Random Determinations:
// For constant-value rand choices, the games avoid using modulo via:
@@ -95,6 +96,19 @@ public static class MethodJ
public static bool IsEncounterCheckApplicable(SlotType4 type) => type is HoneyTree || type.IsFishingRodType();
+ ///
+ public static uint SkipToLevelRand(T enc, uint seed)
+ where T : IEncounterSlot4
+ {
+ if (enc.Type is HoneyTree)
+ return LCRNG.Next(seed); // Honey Tree: No ESV, just the level.
+ if (!enc.Type.IsFishingRodType())
+ return LCRNG.Next2(seed); // ESV, level.
+ if (enc is EncounterSlot4 { Parent.IsCoronetFeebasArea: true })
+ return LCRNG.Next4(seed); // Proc, Tile, ESV, level.
+ return LCRNG.Next3(seed); // Proc, ESV, level.
+ }
+
public static bool CheckEncounterActivation(T enc, ref LeadSeed result)
where T : IEncounterSlot4
{
@@ -142,7 +156,7 @@ public static class MethodJ
return true;
}
- private static bool IsValidCoronetB1F(ISpeciesForm s4, ref uint result)
+ private static bool IsValidCoronetB1F(T s4, ref uint result) where T : ISpeciesForm
{
// The game rolls to check if it might need to replace the slots with Feebas.
// This occurs in Mt. Coronet B1F; if passed, check if the player is on a Feebas tile before replacing.
@@ -445,7 +459,7 @@ public static class MethodJ
private static bool IsLevelValid(T enc, byte min, byte max, byte format, uint u16LevelRand) where T : IEncounterSlot4
{
- var level = enc.Type is HoneyTree ? GetHoneyTreeLevel(u16LevelRand) : GetExpectedLevel(enc, u16LevelRand);
+ var level = GetRandomLevel(enc, u16LevelRand);
return IsOriginalLevelValid(min, max, format, level);
}
@@ -456,8 +470,17 @@ public static class MethodJ
return LevelRangeExtensions.IsLevelWithinRange((int)level, min, max);
}
- private static uint GetExpectedLevel(T enc, uint u16LevelRand) where T : ILevelRange
+ public static uint GetRandomLevel(T enc, uint seed, LeadRequired lead) where T : IEncounterSlot4
{
+ if (lead is PressureHustleSpirit)
+ return enc.PressureLevel;
+ return GetRandomLevel(enc, seed);
+ }
+
+ private static uint GetRandomLevel(T enc, uint u16LevelRand) where T : IEncounterSlot4
+ {
+ if (enc.Type is HoneyTree)
+ return GetHoneyTreeLevel(u16LevelRand);
var min = enc.LevelMin;
uint mod = 1u + enc.LevelMax - min;
return (u16LevelRand % mod) + min;
diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs
index 50542b3e2..52edb7119 100644
--- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs
+++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs
@@ -16,12 +16,17 @@ public static class MethodK
/// Encounter template.
/// Seed that immediately generates the PID.
/// Level range constraints for the capture, if known.
- /// Current format (different from 4)
+ /// Current format (different from 4 will use level range instead of exact)
public static LeadSeed GetSeed(TEnc enc, uint seed, TEvo evo, byte format = Format)
where TEnc : IEncounterSlot4
where TEvo : ILevelRange
=> GetSeed(enc, seed, evo.LevelMin, evo.LevelMax, format);
+ /// Used when generating or ignoring level ranges.
+ ///
+ public static LeadSeed GetSeed(TEnc enc, uint seed, bool minLevel = false) where TEnc : IEncounterSlot4
+ => GetSeed(enc, seed, enc, minLevel ? (byte)4 : (byte)0);
+
public static LeadSeed GetSeed(TEnc enc, uint seed, byte levelMin, byte levelMax, byte format = Format, int depth = 0)
where TEnc : IEncounterSlot4
{
@@ -32,10 +37,6 @@ public static class MethodK
return GetOriginSeed(enc, seed, nature, frames, levelMin, levelMax, format, depth);
}
- ///
- public static LeadSeed GetSeed(TEnc enc, uint seed)
- where TEnc : IEncounterSlot4 => GetSeed(enc, seed, enc);
-
///
/// Count of reverses allowed for no specific lead (not cute charm).
public static int GetReversalWindow(uint seed, byte nature) => MethodJ.GetReversalWindow(seed, nature);
@@ -92,6 +93,20 @@ public static class MethodK
public static bool IsEncounterCheckApplicable(SlotType4 type) => type is Rock_Smash or BugContest || type.IsFishingRodType();
+ ///
+ /// Advances the origin seed to the state that yields the level rand() result.
+ ///
+ /// If using the rand() result, be sure to >> 16.
+ public static uint SkipToLevelRand(T enc, uint seed, LeadRequired lead)
+ where T : IEncounterSlot4
+ {
+ if (enc.Type.IsFishingRodType() || enc.Type is Rock_Smash)
+ return LCRNG.Next3(seed); // Proc, ESV, level.
+ if (enc.Type is BugContest && !lead.IsAbleToSweetScent())
+ return LCRNG.Next4(seed); // ProcStep[2], ESV, level.
+ return LCRNG.Next2(seed); // ESV, level.
+ }
+
public static bool CheckEncounterActivation(T enc, ref LeadSeed result)
where T : IEncounterSlot4
{
@@ -471,7 +486,7 @@ public static class MethodK
private static bool IsLevelValid(T enc, byte min, byte max, byte format, uint u16LevelRand) where T : ILevelRange
{
- var level = GetExpectedLevel(enc, u16LevelRand);
+ var level = GetRandomLevel(enc, u16LevelRand);
return IsOriginalLevelValid(min, max, format, level);
}
@@ -482,7 +497,14 @@ public static class MethodK
return LevelRangeExtensions.IsLevelWithinRange((int)level, min, max);
}
- private static uint GetExpectedLevel(T enc, uint u16LevelRand) where T : ILevelRange
+ public static uint GetRandomLevel(T enc, uint seed, LeadRequired lead) where T : IEncounterSlot4
+ {
+ if (lead is PressureHustleSpirit)
+ return enc.PressureLevel;
+ return GetRandomLevel(enc, seed);
+ }
+
+ private static uint GetRandomLevel(T enc, uint u16LevelRand) where T : ILevelRange
{
var min = enc.LevelMin;
uint mod = 1u + enc.LevelMax - min;
diff --git a/Tests/PKHeX.Core.Tests/Legality/RNG/MethodJChecks.cs b/Tests/PKHeX.Core.Tests/Legality/RNG/MethodJChecks.cs
index e4b9b2a2e..fdf7eaff6 100644
--- a/Tests/PKHeX.Core.Tests/Legality/RNG/MethodJChecks.cs
+++ b/Tests/PKHeX.Core.Tests/Legality/RNG/MethodJChecks.cs
@@ -46,7 +46,7 @@ public static class MethodJChecks
LeadRequired expectLead = LeadRequired.Synchronize)
{
var fake = new MockSlot4(slotIndex);
- var (seed, lead) = MethodJ.GetSeed(fake, prePID, 4);
+ var (seed, lead) = MethodJ.GetSeed(fake, prePID);
lead.Should().Be(expectLead);
seed.Should().BeInRange(0, uint.MaxValue);
}
@@ -62,7 +62,7 @@ public static class MethodJChecks
public static void CheckImpossibleJ(uint prePID, byte slotIndex)
{
var fake = new MockSlot4(slotIndex);
- var lead = MethodJ.GetSeed(fake, prePID, 4);
+ var lead = MethodJ.GetSeed(fake, prePID);
lead.IsValid().Should().BeFalse();
lead.Seed.Should().BeInRange(0, uint.MaxValue);
}