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);