From bc7118b4935025ec996335c0aeeabc7956baec59 Mon Sep 17 00:00:00 2001 From: Kurt Date: Sun, 17 Mar 2024 13:54:36 -0500 Subject: [PATCH] Add specialized PIDIV generator for gen4 slots extract gen3's to a static class --- .../Templates/Gen3/EncounterSlot3.cs | 91 +------ .../Templates/Gen4/EncounterSlot4.cs | 25 +- .../RNG/ClassicEra/Gen3/GenerateMethodH.cs | 232 ++++++++++++++++++ .../RNG/ClassicEra/Gen4/GenerateMethodJ.cs | 129 ++++++++++ .../RNG/ClassicEra/Gen4/GenerateMethodK.cs | 127 ++++++++++ .../Legality/RNG/ClassicEra/Gen4/MethodJ.cs | 2 +- .../Legality/RNG/ClassicEra/Gen4/MethodK.cs | 8 +- PKHeX.WinForms/Subforms/SAV_Encounters.cs | 2 +- .../Subforms/Save Editors/Gen5/SAV_Misc5.cs | 4 +- Tests/PKHeX.Core.Tests/Legality/BreedTests.cs | 2 +- 10 files changed, 517 insertions(+), 105 deletions(-) create mode 100644 PKHeX.Core/Legality/RNG/ClassicEra/Gen3/GenerateMethodH.cs create mode 100644 PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodJ.cs create mode 100644 PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodK.cs diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterSlot3.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterSlot3.cs index 757dc1175..d650fbbdd 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterSlot3.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterSlot3.cs @@ -1,4 +1,3 @@ -using System; using static PKHeX.Core.PIDType; using static PKHeX.Core.SlotType3; @@ -69,98 +68,20 @@ public record EncounterSlot3(EncounterArea3 Parent, ushort Species, byte Form, b private void SetPINGA(PK3 pk, EncounterCriteria criteria, PersonalInfo3 pi) { - var gender = criteria.GetGender(pi); - var nature = criteria.GetNature(); - var ability = criteria.GetAbilityFromNumber(Ability); - var lvl = new SingleLevelRange(LevelMin); - int ctr = 0; - - if (Species == (int)Core.Species.Unown) + if (Species != (int)Core.Species.Unown) { - if (criteria.IsSpecifiedIVs() && SetUnownFromIVs(pk, criteria)) + if (criteria.IsSpecifiedIVs() && this.SetFromIVs(pk, criteria)) return; - - // Generate a random Unown with the correct form and desired nature. - SetUnownRandom(pk, criteria); - return; + this.SetRandom(pk, pi, criteria, Util.Rand32()); } - - do + else { - var seed = PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, Method_1); - var result = MethodH.GetSeed(this, seed, lvl, pk.E, pk.Gender, 3); - if (result.IsValid()) + if (criteria.IsSpecifiedIVs() && this.SetFromIVsUnown(pk, criteria)) return; - } while (ctr++ < 10_000); - } - - private void SetUnownRandom(PK3 pk, EncounterCriteria criteria) - { - //bool checkForm = forms.Contains(criteria.Form); // not a property :( - var (min, max) = SlotMethodH.GetRangeGrass(SlotNumber); - var seed = Util.Rand32() & 0x7FFF_FFFF; - // Can't game the seed with % 100 increments as Unown's form calculation is based on the PID. - - while (true) - { - var esv = LCRNG.Next16(ref seed) % 100; - if (esv < min || esv > max) - continue; - // Skip the level roll, always results in the same level value. - var rand = LCRNG.Next2(seed); - if (criteria.Nature != Nature.Random && (rand >> 16) % 25 != (byte)criteria.Nature) - continue; - - while (true) - { - var a = LCRNG.Next16(ref rand); - var b = LCRNG.Next16(ref rand); - var pid = a << 16 | b; - var form = EntityPID.GetUnownForm3(pid); - if (form != Form) - continue; - - pk.PID = pid; - var iv1 = LCRNG.Next16(ref rand); - var iv2 = LCRNG.Next16(ref rand); - pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF); - return; - } + this.SetRandomUnown(pk, criteria); } } - private bool SetUnownFromIVs(PK3 pk, EncounterCriteria criteria) - { - Span seeds = stackalloc uint[LCRNG.MaxCountSeedsIV]; - var count = LCRNGReversal.GetSeedsIVs(seeds, (byte)criteria.IV_HP, (byte)criteria.IV_ATK, (byte)criteria.IV_DEF, (byte)criteria.IV_SPA, (byte)criteria.IV_SPD, (byte)criteria.IV_SPE); - seeds = seeds[..count]; - foreach (ref var seed in seeds) - { - seed = LCRNG.Prev2(seed); - var s = seed; - - var a = LCRNG.Next16(ref s); - var b = LCRNG.Next16(ref s); - var pid = a << 16 | b; - if (criteria.Nature != Nature.Random && (Nature)(pid % 25) != criteria.Nature) - continue; - var form = EntityPID.GetUnownForm3(pid); - if (form != Form) - continue; - var lead = MethodH.GetSeed(this, seed, this, false, 2, 3); - if (!lead.IsValid()) // Verifies the slot and nature loop; if it passes, apply the details. - continue; - - pk.PID = pid; - var iv1 = LCRNG.Next16(ref s); - var iv2 = LCRNG.Next16(ref s); - pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF); - return true; - } - - return false; - } - protected virtual void SetEncounterMoves(PKM pk) => EncounterUtil.SetEncounterMoves(pk, Version, LevelMin); #endregion diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterSlot4.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterSlot4.cs index 5d5514cca..9eb8c81c8 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterSlot4.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterSlot4.cs @@ -84,21 +84,20 @@ public sealed record EncounterSlot4(EncounterArea4 Parent, ushort Species, byte private void SetPINGA(PK4 pk, EncounterCriteria criteria, PersonalInfo4 pi) { - var gender = criteria.GetGender(pi); - var nature = criteria.GetNature(); - var ability = criteria.GetAbilityFromNumber(Ability); - var lvl = new SingleLevelRange(LevelMin); bool hgss = pk.HGSS; - int ctr = 0; - do + uint seed; + if (hgss) { - var seed = PIDGenerator.SetRandomWildPID4(pk, nature, ability, gender, PIDType.Method_1); - if (!LeadFinder.TryGetLeadInfo4(this, lvl, hgss, seed, 4, out _)) - continue; - if (Species == (int)Core.Species.Unown) - pk.Form = GetUnownForm(seed, hgss); - break; - } while (ctr++ < 10_000); + if (!criteria.IsSpecifiedIVs() || !this.SetFromIVsK(pk, pi, criteria, out seed)) + seed = this.SetRandomK(pk, pi, criteria, Util.Rand32()); + } + else + { + if (!criteria.IsSpecifiedIVs() || !this.SetFromIVsJ(pk, pi, criteria, out seed)) + seed = this.SetRandomJ(pk, pi, criteria, Util.Rand32()); + } + if (Species == (int)Core.Species.Unown) + pk.Form = GetUnownForm(seed, hgss); } /// diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/GenerateMethodH.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/GenerateMethodH.cs new file mode 100644 index 000000000..d0a479102 --- /dev/null +++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen3/GenerateMethodH.cs @@ -0,0 +1,232 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generator methods for Generation 3. +/// +public static class GenerateMethodH +{ + public static void SetRandom(this T enc, PK3 pk, PersonalInfo3 pi, EncounterCriteria criteria, uint seed) + where T : IEncounterSlot3 + { + var gr = pi.Gender; + var ability = criteria.GetAbilityFromNumber(AbilityPermission.Any12); + var (min, max) = SlotMethodH.GetRange(enc.Type, enc.SlotNumber); + + // Generate Method H correlated PID and IVs, no lead (keep things simple). + while (true) + { + var esv = LCRNG.Next16(ref seed) % 100; + if (esv < min || esv > max) + continue; + var lv = LCRNG.Next16(ref seed); + var nature = LCRNG.Next16(ref seed) % 25; + if (criteria.IsSpecifiedNature() && nature != (byte)criteria.Nature) + continue; + + while (true) + { + var a = LCRNG.Next16(ref seed); + var b = LCRNG.Next16(ref seed); + var pid = GetPIDRegular(a, b); + if (pid % 25 != nature) + continue; + if ((pid & 1) != ability) + break; // try again + var gender = EntityGender.GetFromPIDAndRatio(pid, gr); + if (!criteria.IsGenderSatisfied(gender)) + break; // try again + + pk.MetLevel = pk.CurrentLevel = (byte)((lv % (enc.LevelMax - enc.LevelMin + 1)) + enc.LevelMin); + SetPIDIVSequential(pk, pid, seed); + return; + } + } + } + + public static void SetRandomUnown(this T enc, PK3 pk, EncounterCriteria criteria) + where T : INumberedSlot, ISpeciesForm + { + //bool checkForm = forms.Contains(criteria.Form); // not a property :( + var (min, max) = SlotMethodH.GetRangeGrass(enc.SlotNumber); + var seed = Util.Rand32(); + // Can't game the seed with % 100 increments as Unown's form calculation is based on the PID. + + while (true) + { + var esv = LCRNG.Next16(ref seed) % 100; + if (esv < min || esv > max) + continue; + // Skip the level roll, always results in the same level value. + seed = LCRNG.Next2(seed); + var nature = (seed >> 16) % 25; + if (criteria.IsSpecifiedNature() && nature != (byte)criteria.Nature) + continue; + + while (true) + { + var a = LCRNG.Next16(ref seed); + var b = LCRNG.Next16(ref seed); + var pid = a << 16 | b; + if (pid % 25 != nature) + continue; + var form = EntityPID.GetUnownForm3(pid); + if (form != enc.Form) + continue; + + SetPIDIVSequential(pk, pid, seed); + return; + } + } + } + + public static bool SetFromIVs(this T enc, PK3 pk, EncounterCriteria criteria) + where T : IEncounterSlot3 + { + var gr = pk.PersonalInfo.Gender; + (uint iv1, uint iv2) = GetCombinedIVs(criteria); + Span all = stackalloc uint[LCRNG.MaxCountSeedsIV]; + var count = LCRNGReversal.GetSeedsIVs(all, iv1 << 16, iv2 << 16); + var seeds = all[..count]; + foreach (ref var seed in seeds) + { + seed = LCRNG.Prev2(seed); + var s = seed; + + var a = LCRNG.Next16(ref s); + var b = LCRNG.Next16(ref s); + var pid = GetPIDRegular(a, b); + if (criteria.IsSpecifiedNature() && (Nature)(pid % 25) != criteria.Nature) + { + // Try again as Method 2 (BA-DE) + var o = s >> 16; + pid = GetPIDRegular(o, a); + if (criteria.IsSpecifiedNature() && (Nature)(pid % 25) != criteria.Nature) + continue; + seed = LCRNG.Prev(seed); + } + + var gender = EntityGender.GetFromPIDAndRatio(pid, gr); + if (!criteria.IsGenderSatisfied(gender)) + continue; + var lead = MethodH.GetSeed(enc, seed, enc, pk.E, gender, 3); + if (!lead.IsValid()) // Verifies the slot, (min) level, and nature loop; if it passes, apply the details. + continue; + + pk.PID = pid; + pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF); + return true; + } + + // Try again as Method 4 (BAC-E) + count = LCRNGReversalSkip.GetSeedsIVs(all, iv1 << 16, iv2 << 16); + seeds = all[..count]; + foreach (ref var seed in seeds) + { + seed = LCRNG.Prev2(seed); + var s = seed; + + var a = LCRNG.Next16(ref s); + var b = LCRNG.Next16(ref s); + var pid = GetPIDRegular(a, b); + if (criteria.IsSpecifiedNature() && (Nature)(pid % 25) != criteria.Nature) + continue; + + var gender = EntityGender.GetFromPIDAndRatio(pid, gr); + if (!criteria.IsGenderSatisfied(gender)) + continue; + var lead = MethodH.GetSeed(enc, seed, enc, pk.E, gender, 3); + if (!lead.IsValid()) // Verifies the slot and nature loop; if it passes, apply the details. + continue; + + pk.PID = pid; + pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF); + return true; + } + + return false; + } + + public static bool SetFromIVsUnown(this T enc, PK3 pk, EncounterCriteria criteria) + where T : IEncounterSlot3 + { + (uint iv1, uint iv2) = GetCombinedIVs(criteria); + Span all = stackalloc uint[LCRNG.MaxCountSeedsIV]; + var count = LCRNGReversal.GetSeedsIVs(all, iv1 << 16, iv2 << 16); + var seeds = all[..count]; + foreach (ref var seed in seeds) + { + seed = LCRNG.Prev2(seed); + var s = seed; + + var a = LCRNG.Next16(ref s); + var b = LCRNG.Next16(ref s); + var pid = GetPIDUnown(a, b); + if ((criteria.IsSpecifiedNature() && (Nature)(pid % 25) != criteria.Nature) || EntityPID.GetUnownForm3(pid) != enc.Form) + { + // Try again as Method 2 (BA-DE) + var o = s >> 16; + pid = GetPIDUnown(o, a); + if (criteria.IsSpecifiedNature() && (Nature)(pid % 25) != criteria.Nature) + continue; + var form = EntityPID.GetUnownForm3(pid); + if (form != enc.Form) + continue; + seed = LCRNG.Prev(seed); + } + var lead = MethodH.GetSeed(enc, seed, enc, false, 2, 3); + if (!lead.IsValid()) // Verifies the slot and nature loop; if it passes, apply the details. + continue; + + pk.PID = pid; + pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF); + return true; + } + + // Try again as Method 4 (BAC-E) + count = LCRNGReversalSkip.GetSeedsIVs(all, iv1 << 16, iv2 << 16); + seeds = all[..count]; + foreach (ref var seed in seeds) + { + seed = LCRNG.Prev2(seed); + var s = seed; + + var a = LCRNG.Next16(ref s); + var b = LCRNG.Next16(ref s); + var pid = GetPIDUnown(a, b); + if (criteria.IsSpecifiedNature() && (Nature)(pid % 25) != criteria.Nature) + continue; + var form = EntityPID.GetUnownForm3(pid); + if (form != enc.Form) + continue; + var lead = MethodH.GetSeed(enc, seed, enc, false, 2, 3); + if (!lead.IsValid()) // Verifies the slot and nature loop; if it passes, apply the details. + continue; + + pk.PID = pid; + pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF); + return true; + } + + return false; + } + + public static uint GetPIDUnown(uint a, uint b) => a << 16 | b; + public static uint GetPIDRegular(uint a, uint b) => b << 16 | a; + + private static void SetPIDIVSequential(PK3 pk, uint pid, uint rand) + { + pk.PID = pid; + var iv1 = LCRNG.Next16(ref rand); + var iv2 = LCRNG.Next16(ref rand); + pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF); + } + + private static (uint iv1, uint iv2) GetCombinedIVs(EncounterCriteria criteria) + { + uint iv1 = (uint)criteria.IV_HP | (uint)criteria.IV_ATK << 5 | (uint)criteria.IV_DEF << 10; + uint iv2 = (uint)criteria.IV_SPE | (uint)criteria.IV_SPA << 5 | (uint)criteria.IV_SPD << 10; + return (iv1, iv2); + } +} diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodJ.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodJ.cs new file mode 100644 index 000000000..c05786826 --- /dev/null +++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodJ.cs @@ -0,0 +1,129 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generator methods for Generation 4. +/// +public static class GenerateMethodJ +{ + /// + /// Applies a random PID and IVs to the entity, based on the and . + /// + /// Encounter slot to generate for + /// Entity to modify + /// Personal Info of the entity + /// Criteria to match + /// Initial seed to use; will be modified during the loop if the first seed fails + /// Method 1 origin seed applied + public static uint SetRandomJ(this T enc, PK4 pk, PersonalInfo4 pi, EncounterCriteria criteria, uint seed) + where T : IEncounterSlot4 + { + var gr = pi.Gender; + var ability = criteria.GetAbilityFromNumber(AbilityPermission.Any12); + var (min, max) = SlotMethodJ.GetRange(enc.Type, enc.SlotNumber); + bool randLevel = MethodJ.IsLevelRand(enc); + + // Generate Method J correlated PID and IVs, no lead (keep things simple). + while (true) + { + var esv = LCRNG.Next16(ref seed) / 656; + if (esv < min || esv > max) + continue; + var lv = randLevel ? LCRNG.Next16(ref seed) : 0; + var nature = LCRNG.Next16(ref seed) % 25; + if (criteria.IsSpecifiedNature() && nature != (byte)criteria.Nature) + continue; + + while (true) + { + var a = LCRNG.Next16(ref seed); + var b = LCRNG.Next16(ref seed); + var pid = GetPIDRegular(a, b); + if (pid % 25 != nature) + continue; + if ((pid & 1) != ability) + break; // try again + var gender = EntityGender.GetFromPIDAndRatio(pid, gr); + if (!criteria.IsGenderSatisfied(gender)) + 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; + } + SetPIDIVSequential(pk, pid, seed); + pk.Gender = gender; + pk.Ability = (pid & 1) == 0 ? pi.Ability1 : pi.Ability2; + return LCRNG.Prev4(seed); + } + } + } + + /// + /// Applies the IVs from the , assuming the IVs are possible and a Method 1 spread is possible. + /// + /// Encounter slot to generate for + /// Entity to modify + /// Personal Info of the entity + /// Criteria to match + /// Method 1 origin seed applied + /// True if the PID/IV was valid & applied to the entity. + public static bool SetFromIVsJ(this T enc, PK4 pk, PersonalInfo4 pi, EncounterCriteria criteria, out uint origin) + where T : IEncounterSlot4 + { + var gr = pi.Gender; + (uint iv1, uint iv2) = GetCombinedIVs(criteria); + Span all = stackalloc uint[LCRNG.MaxCountSeedsIV]; + var count = LCRNGReversal.GetSeedsIVs(all, iv1 << 16, iv2 << 16); + var seeds = all[..count]; + foreach (ref var seed in seeds) + { + seed = LCRNG.Prev2(seed); + var s = seed; + + var a = LCRNG.Next16(ref s); + var b = LCRNG.Next16(ref s); + var pid = GetPIDRegular(a, b); + if (criteria.IsSpecifiedNature() && (Nature)(pid % 25) != criteria.Nature) + continue; + + var gender = EntityGender.GetFromPIDAndRatio(pid, gr); + if (!criteria.IsGenderSatisfied(gender)) + continue; + var lead = MethodJ.GetSeed(enc, seed, enc, 4); + if (!lead.IsValid()) // Verifies the slot, (min) level, and nature loop; if it passes, apply the details. + continue; + + pk.PID = pid; + pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF); + pk.Gender = gender; + pk.Ability = (pid & 1) == 0 ? pi.Ability1 : pi.Ability2; + origin = seed; + return true; + } + + origin = 0; + return false; + } + + public static uint GetPIDRegular(uint a, uint b) => b << 16 | a; + + private static void SetPIDIVSequential(PK4 pk, uint pid, uint rand) + { + pk.PID = pid; + var iv1 = LCRNG.Next16(ref rand); + var iv2 = LCRNG.Next16(ref rand); + pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF); + } + + private static (uint iv1, uint iv2) GetCombinedIVs(EncounterCriteria criteria) + { + uint iv1 = (uint)criteria.IV_HP | (uint)criteria.IV_ATK << 5 | (uint)criteria.IV_DEF << 10; + uint iv2 = (uint)criteria.IV_SPE | (uint)criteria.IV_SPA << 5 | (uint)criteria.IV_SPD << 10; + return (iv1, iv2); + } +} diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodK.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodK.cs new file mode 100644 index 000000000..705f98aee --- /dev/null +++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/GenerateMethodK.cs @@ -0,0 +1,127 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Generator methods for Generation 4. +/// +public static class GenerateMethodK +{ + /// + /// Applies a random PID and IVs to the entity, based on the and . + /// + /// Encounter slot to generate for + /// Entity to modify + /// Personal Info of the entity + /// Criteria to match + /// Initial seed to use; will be modified during the loop if the first seed fails + /// Method 1 origin seed applied + public static uint SetRandomK(this T enc, PK4 pk, PersonalInfo4 pi, EncounterCriteria criteria, uint seed) + where T : IEncounterSlot4 + { + var gr = pi.Gender; + var ability = criteria.GetAbilityFromNumber(AbilityPermission.Any12); + var (min, max) = SlotMethodK.GetRange(enc.Type, enc.SlotNumber); + bool randLevel = MethodK.IsLevelRand(enc); + var modulo = enc.Type.IsSafari() ? 10 : 100; + + // Generate Method K correlated PID and IVs, no lead (keep things simple). + while (true) + { + var esv = LCRNG.Next16(ref seed) % modulo; + if (esv < min || esv > max) + continue; + var lv = randLevel ? LCRNG.Next16(ref seed) : 0; + var nature = LCRNG.Next16(ref seed) % 25; + if (criteria.IsSpecifiedNature() && nature != (byte)criteria.Nature) + continue; + + while (true) + { + var a = LCRNG.Next16(ref seed); + var b = LCRNG.Next16(ref seed); + var pid = GetPIDRegular(a, b); + if (pid % 25 != nature) + continue; + if ((pid & 1) != ability) + break; // try again + var gender = EntityGender.GetFromPIDAndRatio(pid, gr); + if (!criteria.IsGenderSatisfied(gender)) + break; // try again + + if (randLevel) + pk.MetLevel = pk.CurrentLevel = (byte)((lv % (enc.LevelMax - enc.LevelMin + 1)) + enc.LevelMin); + pk.PID = pid; + var iv1 = LCRNG.Next16(ref seed); + var iv2 = LCRNG.Next16(ref seed); + pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF); + + if (enc.Type is SlotType4.BugContest && !MethodK.IsAny31(iv1) && !MethodK.IsAny31(iv2)) + break; // try again + pk.Gender = gender; + pk.Ability = (pid & 1) == 0 ? pi.Ability1 : pi.Ability2; + return LCRNG.Prev4(seed); + } + } + } + + /// + /// Applies the IVs from the , assuming the IVs are possible and a Method 1 spread is possible. + /// + /// Encounter slot to generate for + /// Entity to modify + /// Personal Info of the entity + /// Criteria to match + /// Method 1 origin seed applied + /// True if the PID/IV was valid & applied to the entity. + public static bool SetFromIVsK(this T enc, PK4 pk, PersonalInfo4 pi, EncounterCriteria criteria, out uint origin) + where T : IEncounterSlot4 + { + var gr = pi.Gender; + (uint iv1, uint iv2) = GetCombinedIVs(criteria); + Span all = stackalloc uint[LCRNG.MaxCountSeedsIV]; + var count = LCRNGReversal.GetSeedsIVs(all, iv1 << 16, iv2 << 16); + var seeds = all[..count]; + foreach (ref var seed in seeds) + { + seed = LCRNG.Prev2(seed); + var s = seed; + + var a = LCRNG.Next16(ref s); + var b = LCRNG.Next16(ref s); + var pid = GetPIDRegular(a, b); + if (criteria.IsSpecifiedNature() && (Nature)(pid % 25) != criteria.Nature) + continue; + + var gender = EntityGender.GetFromPIDAndRatio(pid, gr); + if (!criteria.IsGenderSatisfied(gender)) + continue; + var lead = MethodK.GetSeed(enc, seed, enc, 4); + if (!lead.IsValid()) // Verifies the slot, (min) level, and nature loop; if it passes, apply the details. + continue; + + pk.PID = pid; + pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF); + pk.Gender = gender; + pk.Ability = (pid & 1) == 0 ? pi.Ability1 : pi.Ability2; + origin = seed; + return true; + } + + origin = 0; + return false; + } + + public static uint GetPIDRegular(uint a, uint b) => b << 16 | a; + + private static void SetPIDIVSequential(PK4 pk, uint pid, uint rand) + { + } + + private static (uint iv1, uint iv2) GetCombinedIVs(EncounterCriteria criteria) + { + uint iv1 = (uint)criteria.IV_HP | (uint)criteria.IV_ATK << 5 | (uint)criteria.IV_DEF << 10; + uint iv2 = (uint)criteria.IV_SPE | (uint)criteria.IV_SPA << 5 | (uint)criteria.IV_SPD << 10; + return (iv1, iv2); + } +} diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodJ.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodJ.cs index 35f861849..2b345ee6b 100644 --- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodJ.cs +++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodJ.cs @@ -317,7 +317,7 @@ public static class MethodJ result = default; return false; } - private static bool IsLevelRand(T enc) where T : IEncounterSlot4 => enc.Type.IsLevelRandDPPt(); + public static bool IsLevelRand(T enc) where T : IEncounterSlot4 => enc.Type.IsLevelRandDPPt(); private static bool IsSlotValidFrom1Skip(FrameCheckDetails ctx, out uint result) where T : IEncounterSlot4 diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs index 0e8745889..66e3bd597 100644 --- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs +++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs @@ -243,8 +243,12 @@ public static class MethodK } } + /// + /// Checks if any IV component value is 31. + /// + /// BCC re-rolls 3x if none are 31. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsAny31(uint iv16) + public static bool IsAny31(uint iv16) => IsLow5Bits31(iv16) || IsLow5Bits31(iv16 >> 5) || IsLow5Bits31(iv16 >> 10); @@ -336,7 +340,7 @@ public static class MethodK result = default; return false; } - private static bool IsLevelRand(T enc) where T : IEncounterSlot4 => enc.Type.IsLevelRandHGSS(); + public static bool IsLevelRand(T enc) where T : IEncounterSlot4 => enc.Type.IsLevelRandHGSS(); private static bool IsSlotValidFrom1Skip(FrameCheckDetails ctx, out uint result) where T : IEncounterSlot4 diff --git a/PKHeX.WinForms/Subforms/SAV_Encounters.cs b/PKHeX.WinForms/Subforms/SAV_Encounters.cs index 920ce387d..617ab4c56 100644 --- a/PKHeX.WinForms/Subforms/SAV_Encounters.cs +++ b/PKHeX.WinForms/Subforms/SAV_Encounters.cs @@ -188,7 +188,7 @@ public partial class SAV_Encounters : Form var set = new ShowdownSet(editor); var criteria = EncounterCriteria.GetCriteria(set, editor.PersonalInfo); if (!isInChain) - criteria = criteria with { Gender = FixedGenderUtil.GenderRandom }; // Genderless tabs and a gendered enc -> let's play safe. + criteria = criteria with { Gender = default }; // Genderless tabs and a gendered enc -> let's play safe. return criteria; } diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_Misc5.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_Misc5.cs index 2a3a0e156..e6e4f0420 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_Misc5.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_Misc5.cs @@ -621,10 +621,10 @@ public partial class SAV_Misc5 : Form source.Remove(slot); s.Species = slot.Species; s.Form = slot.Form; - s.Gender = slot.Gender == FixedGenderUtil.GenderRandom ? PersonalTable.B2W2[slot.Species].RandomGender() : slot.Gender; + s.Gender = !((IFixedGender)slot).IsFixedGender ? PersonalTable.B2W2[slot.Species].RandomGender() : slot.Gender; slot.Moves.CopyTo(moves); - var count = moves.Length - moves.Count((ushort)0); + var count = moves.Length - moves.Count(0); s.Move = count == 0 ? (ushort)0 : moves[rnd.Next(count)]; } ChangeArea(this, EventArgs.Empty); // refresh diff --git a/Tests/PKHeX.Core.Tests/Legality/BreedTests.cs b/Tests/PKHeX.Core.Tests/Legality/BreedTests.cs index c4a76f29c..6b594c23c 100644 --- a/Tests/PKHeX.Core.Tests/Legality/BreedTests.cs +++ b/Tests/PKHeX.Core.Tests/Legality/BreedTests.cs @@ -87,6 +87,6 @@ public class BreedTests // fixed order should be different now. expected.SequenceEqual(moves).Should().BeFalse(); // nonzero move count should be same - expected.Count((ushort)0).Should().Be(moves.Count((ushort)0)); + expected.Count(0).Should().Be(moves.Count(0)); } }