From 52351bf0e7fbecaae333c853eea61395183a3a80 Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 18 Nov 2024 18:56:18 -0600 Subject: [PATCH] Generator: Gen3/4 slots w/ IVs & not-min level Previously would only generate with minimum levels when IVs are provided Now that it generates not-min levels, need to set the correct level. Might be possible in the future to have IV-spec also respect a Level request, but for now it's only forceMin which nobody currently uses (no usage of the setter). --- .../RNG/ClassicEra/Gen3/GenerateMethodH.cs | 28 +++++++++--- .../Legality/RNG/ClassicEra/Gen3/MethodH.cs | 44 ++++++++++++++----- .../RNG/ClassicEra/Gen4/GenerateMethodJ.cs | 17 ++++--- .../RNG/ClassicEra/Gen4/GenerateMethodK.cs | 12 ++++- .../Legality/RNG/ClassicEra/Gen4/MethodJ.cs | 35 ++++++++++++--- .../Legality/RNG/ClassicEra/Gen4/MethodK.cs | 36 ++++++++++++--- .../Legality/RNG/MethodJChecks.cs | 4 +- 7 files changed, 135 insertions(+), 41 deletions(-) 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); }