diff --git a/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs b/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs
index 48788258b..996f5292d 100644
--- a/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs
+++ b/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs
@@ -6,7 +6,7 @@ namespace PKHeX.Core;
///
/// Object that can be fed to a converter to ensure that the resulting meets rough specifications.
///
-public sealed record EncounterCriteria : IFixedNature, IFixedAbilityNumber, IShinyPotential
+public sealed record EncounterCriteria : IFixedNature, IFixedAbilityNumber, IShinyPotential, ILevelRange
{
///
/// Default criteria with no restrictions (random) for all fields.
@@ -36,10 +36,8 @@ public sealed record EncounterCriteria : IFixedNature, IFixedAbilityNumber, IShi
public sbyte IV_SPD { get; init; } = RandomIV;
public sbyte IV_SPE { get; init; } = RandomIV;
- ///
- /// If the Encounter yields variable level ranges (e.g. RNG correlation), force the minimum level instead of yielding first match.
- ///
- public bool ForceMinLevelRange { get; set; }
+ public byte LevelMin { get; set; }
+ public byte LevelMax { get; set; }
public sbyte TeraType { get; init; } = -1;
@@ -49,6 +47,7 @@ public sealed record EncounterCriteria : IFixedNature, IFixedAbilityNumber, IShi
private const int RandomIV = -1;
public bool IsSpecifiedNature() => Nature != Nature.Random;
+ public bool IsSpecifiedLevelRange() => LevelMin != 0;
public bool IsSpecifiedTeraType() => TeraType != -1;
public bool IsSpecifiedIVs() => IV_HP != RandomIV
@@ -58,6 +57,8 @@ public sealed record EncounterCriteria : IFixedNature, IFixedAbilityNumber, IShi
&& IV_SPD != RandomIV
&& IV_SPE != RandomIV;
+ public bool IsLevelRangeSatisfied(byte level) => LevelMin <= level && level <= LevelMax;
+
///
/// Checks if the IVs are compatible with the encounter's defined IV restrictions.
///
diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterArea3.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterArea3.cs
index 782397889..70d3a56f6 100644
--- a/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterArea3.cs
+++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterArea3.cs
@@ -134,15 +134,26 @@ public byte GetPressureMax(ushort species, byte levelMax)
}
}
+///
+/// Wild Encounter data Type
+///
public enum SlotType3 : byte
{
+ /// Grass tiles
Grass = 0,
+ /// Water tiles
Surf = 1,
+ /// Fishing with Old Rod
Old_Rod = 2,
+ /// Fishing with Good Rod
Good_Rod = 3,
+ /// Fishing with Super Rod
Super_Rod = 4,
+ /// Using Rock Smash move
Rock_Smash = 5,
+ /// Swarm in grass tiles
SwarmGrass50 = 6,
+ /// Swarm in water tiles
SwarmFish50 = 7,
}
diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterArea4.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterArea4.cs
index 5a0ae1703..255432ab6 100644
--- a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterArea4.cs
+++ b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterArea4.cs
@@ -128,11 +128,14 @@ private static bool IsMunchlaxTree(ReadOnlySpan trees, ushort location)
58, // 20 Floaroma Meadow
];
+ ///
+ /// Checks if the Unown form is valid for the given .
+ ///
public static bool IsUnownFormValid(PKM pk, byte form)
{
return pk.HGSS
- ? RuinsOfAlph4.IsUnownFormValid(pk, form)
- : SolaceonRuins4.IsUnownFormValid(pk, form);
+ ? RuinsOfAlph4.IsFormValid(pk, form)
+ : SolaceonRuins4.IsFormValid(pk, form);
}
}
@@ -143,28 +146,54 @@ public static bool IsUnownFormValid(PKM pk, byte form)
/// Different from , this corresponds to the method that the may be encountered.
public enum SlotType4 : byte
{
+ /// Grass tiles
Grass = 0,
+ /// Water tiles
Surf = 1,
+ /// Fishing with Old Rod
Old_Rod = 2,
+ /// Fishing with Good Rod
Good_Rod = 3,
+ /// Fishing with Super Rod
Super_Rod = 4,
+ /// Using Rock Smash move
Rock_Smash = 5,
+ /// Using Headbutt on capable trees
Headbutt = 6,
+ /// Using Headbutt on special trees
HeadbuttSpecial = 7,
+ /// Grass tiles during a Bug Catching Contest
BugContest = 8,
+ /// Shaking Honey Trees
HoneyTree = 9,
+ /// Grass tiles in the Safari Zone
Safari_Grass = 10,
+ /// Water tiles in the Safari Zone
Safari_Surf = 11,
+ /// Fishing with Old Rod in the Safari Zone
Safari_Old_Rod = 12,
+ /// Fishing with Good Rod in the Safari Zone
Safari_Good_Rod = 13,
+ /// Fishing with Super Rod in the Safari Zone
Safari_Super_Rod = 14,
}
public static class SlotType4Extensions
{
+ ///
+ /// Checks if the is an encounter within the Safari Zone.
+ ///
public static bool IsSafari(this SlotType4 type) => type >= SlotType4.Safari_Grass;
+
+ ///
+ /// Checks if the has a level range that is random. For D/P/Pt; this is all types except Grass.
+ ///
public static bool IsLevelRandDPPt(this SlotType4 type) => type != SlotType4.Grass;
+
+ ///
+ /// Checks if the has a level range that is random. For HG/SS; this is all types except Grass and Safari.
+ ///
public static bool IsLevelRandHGSS(this SlotType4 type) => type != SlotType4.Grass && !type.IsSafari();
}
diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterSlot8a.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterSlot8a.cs
index eca90ee15..37e384bcb 100644
--- a/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterSlot8a.cs
+++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterSlot8a.cs
@@ -75,7 +75,7 @@ private void SetPINGA(PA8 pk, EncounterCriteria criteria, PersonalInfo8LA pi)
{
// Give a random level according to the RNG correlation.
var lvl = Overworld8aRNG.GetRandomLevel(slotSeed, LevelMin, LevelMax);
- if (criteria.ForceMinLevelRange && lvl != LevelMin)
+ if (criteria.IsSpecifiedLevelRange() && !criteria.IsLevelRangeSatisfied(lvl))
continue;
pk.MetLevel = pk.CurrentLevel = lvl;
}
diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/GenerateMethodH.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/GenerateMethodH.cs
index 64411fbf2..bca0d7d55 100644
--- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/GenerateMethodH.cs
+++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/GenerateMethodH.cs
@@ -120,7 +120,9 @@ public static bool SetFromIVs(this T enc, PK3 pk, EncounterCriteria criteria)
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (!criteria.IsGenderSatisfied(gender))
continue;
- var lead = MethodH.GetSeed(enc, seed, pk.E, gender, criteria.ForceMinLevelRange);
+ var lead = criteria.IsSpecifiedLevelRange()
+ ? MethodH.GetSeed(enc, seed, pk.E, gender, criteria)
+ : MethodH.GetSeed(enc, seed, pk.E, gender);
if (!lead.IsValid()) // Verifies the slot, (min) level, and nature loop; if it passes, apply the details.
continue;
@@ -155,7 +157,9 @@ public static bool SetFromIVs(this T enc, PK3 pk, EncounterCriteria criteria)
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (!criteria.IsGenderSatisfied(gender))
continue;
- var lead = MethodH.GetSeed(enc, seed, pk.E, gender, criteria.ForceMinLevelRange);
+ var lead = criteria.IsSpecifiedLevelRange()
+ ? MethodH.GetSeed(enc, seed, pk.E, gender, criteria)
+ : MethodH.GetSeed(enc, seed, pk.E, gender);
if (!lead.IsValid()) // Verifies the slot and nature loop; if it passes, apply the details.
continue;
@@ -203,7 +207,7 @@ public static bool SetFromIVsUnown(this T enc, PK3 pk, EncounterCriteria crit
continue;
seed = LCRNG.Prev(seed);
}
- var lead = MethodH.GetSeed(enc, seed, false, 2, criteria.ForceMinLevelRange);
+ var lead = MethodH.GetSeed(enc, seed, false, 2);
if (!lead.IsValid()) // Verifies the slot and form loop; if it passes, apply the details.
continue;
@@ -229,7 +233,7 @@ public static bool SetFromIVsUnown(this T enc, PK3 pk, EncounterCriteria crit
var form = EntityPID.GetUnownForm3(pid);
if (form != enc.Form)
continue;
- var lead = MethodH.GetSeed(enc, seed, false, 2, criteria.ForceMinLevelRange);
+ var lead = MethodH.GetSeed(enc, seed, false, 2);
if (!lead.IsValid()) // Verifies the slot and form loop; if it passes, apply the details.
continue;
diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/MethodH.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/MethodH.cs
index bc0c07920..f8e8aced1 100644
--- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/MethodH.cs
+++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/MethodH.cs
@@ -33,9 +33,17 @@ public static class MethodH
}
/// Used when generating or ignoring level ranges.
- ///
- 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);
+ ///
+ public static LeadSeed GetSeed(TEnc enc, uint seed, bool emerald, byte gender)
+ where TEnc : IEncounterSlot3
+ => GetSeed(enc, seed, enc, emerald, gender, 0);
+
+ /// Used when generating with specific level ranges.
+ ///
+ public static LeadSeed GetSeed(TEnc enc, uint seed, bool emerald, byte gender, TEvo evo)
+ where TEnc : IEncounterSlot3
+ where TEvo : ILevelRange
+ => GetSeed(enc, seed, evo, emerald, gender, 3);
// Summary of Random Determinations:
// Nature: rand() % 25 == nature
diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodJ.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodJ.cs
index 5c2163572..2c250f07c 100644
--- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodJ.cs
+++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodJ.cs
@@ -102,7 +102,9 @@ public static bool SetFromIVsJ(this T enc, PK4 pk, PersonalInfo4 pi, Encounte
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (!criteria.IsGenderSatisfied(gender))
continue;
- var lead = MethodJ.GetSeed(enc, seed, criteria.ForceMinLevelRange);
+ var lead = criteria.IsSpecifiedLevelRange()
+ ? MethodJ.GetSeed(enc, seed, criteria)
+ : MethodJ.GetSeed(enc, seed);
if (!lead.IsValid()) // Verifies the slot, (min) level, and nature loop; if it passes, apply the details.
continue;
diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodK.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodK.cs
index 3942fb480..ff3e9d8c6 100644
--- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodK.cs
+++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodK.cs
@@ -106,7 +106,9 @@ public static bool SetFromIVsK(this T enc, PK4 pk, PersonalInfo4 pi, Encounte
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (!criteria.IsGenderSatisfied(gender))
continue;
- var lead = MethodK.GetSeed(enc, seed, criteria.ForceMinLevelRange);
+ var lead = criteria.IsSpecifiedLevelRange()
+ ? MethodK.GetSeed(enc, seed, criteria)
+ : MethodK.GetSeed(enc, seed);
if (!lead.IsValid()) // Verifies the slot, (min) level, and nature loop; if it passes, apply the details.
continue;
diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodJ.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodJ.cs
index 740ed45cc..917a36778 100644
--- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodJ.cs
+++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodJ.cs
@@ -30,9 +30,16 @@ public static class MethodJ
}
/// 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) where TEnc : IEncounterSlot4
+ => GetSeed(enc, seed, enc, 0);
+
+ /// Used when generating with specific level ranges.
+ ///
+ public static LeadSeed GetSeed(TEnc enc, uint seed, TEvo evo)
+ where TEnc : IEncounterSlot4
+ where TEvo : ILevelRange
+ => GetSeed(enc, seed, evo, 4);
// Summary of Random Determinations:
// For constant-value rand choices, the games avoid using modulo via:
diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs
index 52edb7119..f8b0ff30e 100644
--- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs
+++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs
@@ -23,9 +23,16 @@ public static class MethodK
=> 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) where TEnc : IEncounterSlot4
+ => GetSeed(enc, seed, enc, 0);
+
+ /// Used when generating with specific level ranges.
+ ///
+ public static LeadSeed GetSeed(TEnc enc, uint seed, TEvo evo)
+ where TEnc : IEncounterSlot4
+ where TEvo : ILevelRange
+ => GetSeed(enc, seed, evo, 4);
public static LeadSeed GetSeed(TEnc enc, uint seed, byte levelMin, byte levelMax, byte format = Format, int depth = 0)
where TEnc : IEncounterSlot4
diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/RuinsOfAlph4.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/RuinsOfAlph4.cs
index b17de82db..c6d169767 100644
--- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/RuinsOfAlph4.cs
+++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/RuinsOfAlph4.cs
@@ -5,25 +5,35 @@ namespace PKHeX.Core;
public static class RuinsOfAlph4
{
///
- /// Checks if the requested is valid for the given seed.
+ /// Checks if the requested is valid for the given result.
///
- public static bool IsUnownFormValid(PKM pk, byte form)
+ public static bool IsFormValid(PKM pk, byte form)
{
if (!MethodFinder.GetLCRNGMethod1Match(pk, out var seed))
return true; // invalid anyway, don't care.
-
- // ABCD|E(Item)|F(Form) determination
- var f = LCRNG.Next6(seed);
- return IsFormValid(form, f);
+ return IsFormValid(seed, form);
}
///
/// Checks if the requested is valid for the given seed.
///
+ /// Seed that originated the PID/IV.
/// Form to validate
+ /// True if the form is valid
+ public static bool IsFormValid(uint seed, byte form)
+ {
+ // ABCD|E(Item)|F(Form) determination
+ var f = LCRNG.Next6(seed);
+ return IsFormValidFrame(f, form);
+ }
+
+ ///
+ /// Checks if the requested is valid for the given frame seed that calculates form.
+ ///
/// RNG state that determines the form
- ///
- public static bool IsFormValid(byte form, uint seed)
+ /// Form to validate
+ /// True if the form is valid
+ public static bool IsFormValidFrame(uint seed, byte form)
{
if (form >= 26) // Entrance
return form == GetEntranceForm(seed);
diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/SolaceonRuins4.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/SolaceonRuins4.cs
index 9dea535aa..f77d6abad 100644
--- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/SolaceonRuins4.cs
+++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/SolaceonRuins4.cs
@@ -7,7 +7,7 @@ public static class SolaceonRuins4
///
/// Checks if the requested is valid for the given seed.
///
- public static bool IsUnownFormValid(PKM pk, byte form)
+ public static bool IsFormValid(PKM pk, byte form)
{
if (IsSingleFormRoomUnown(form))
return true; // FRIEND: Specific rooms with only one form.
@@ -17,6 +17,7 @@ public static bool IsUnownFormValid(PKM pk, byte form)
if (!MethodFinder.GetLCRNGMethod1Match(pk, out var seed))
return true; // invalid anyway, don't care.
+ // ABCD|E(Item)|F(Form) determination
var f = LCRNG.Next6(seed) >> 16;
var expect = GetUnownForm(f, form);
return expect == form;
diff --git a/PKHeX.Core/Legality/RNG/Methods/Gen9/Encounter9RNG.cs b/PKHeX.Core/Legality/RNG/Methods/Gen9/Encounter9RNG.cs
index 6e032851b..4a67b1776 100644
--- a/PKHeX.Core/Legality/RNG/Methods/Gen9/Encounter9RNG.cs
+++ b/PKHeX.Core/Legality/RNG/Methods/Gen9/Encounter9RNG.cs
@@ -121,10 +121,17 @@ public static bool GenerateData(PK9 pk, in GenerateParam9 enc, EncounterCriteria
var nature = enc.Nature != Nature.Random ? enc.Nature : enc.Species == (int)Species.Toxtricity
? ToxtricityUtil.GetRandomNature(ref rand, pk.Form)
: (Nature)rand.NextInt(25);
- if (criteria.Nature != Nature.Random && nature != criteria.Nature)
- return false;
pk.Nature = pk.StatNature = nature;
+ // Compromise on Nature -- some are fixed, some are random. If the request wants a specific nature, just mint it.
+ var requestNature = criteria.Nature;
+ if (criteria.Nature != Nature.Random && nature != requestNature)
+ {
+ if (!requestNature.IsMint())
+ return false;
+ pk.StatNature = requestNature;
+ }
+
pk.HeightScalar = enc.Height != 0 ? enc.Height : (byte)(rand.NextInt(0x81) + rand.NextInt(0x80));
pk.WeightScalar = enc.Weight != 0 ? enc.Weight : (byte)(rand.NextInt(0x81) + rand.NextInt(0x80));
pk.Scale = enc.ScaleType.GetSizeValue(enc.Scale, ref rand);