diff --git a/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs b/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs index 57387f800..6ca6b04a3 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs @@ -126,6 +126,12 @@ public EncounterCriteria() /// > if an Ability is specified; otherwise, . public bool IsSpecifiedAbility() => Ability != Any12H; + /// + /// Determines whether the shiny value is explicitly specified rather than set to random. + /// + /// > if a Shiny is specified; otherwise, . + public bool IsSpecifiedShiny() => Shiny != Shiny.Random; + /// /// Determines whether all IVs are specified in the criteria. /// @@ -183,6 +189,20 @@ public int GetCountSpecifiedIVs() => Convert.ToInt32(IV_HP != RandomIV) _ => throw new ArgumentOutOfRangeException(nameof(ability), ability, null), }; + /// + /// Determines whether the specified shiny properties satisfy the shiny criteria based on the current setting. + /// + /// > if the index satisfies the shiny criteria; otherwise, . + public bool IsSatisfiedShiny(uint xor, uint cmp) => Shiny switch + { + Shiny.Random => true, + Shiny.Never => xor > cmp, // not shiny + Shiny.AlwaysSquare => xor == 0, // square shiny + Shiny.AlwaysStar => xor < cmp && xor != 0, // star shiny + Shiny.Always => xor < cmp, // shiny + _ => false, // shouldn't be set + }; + /// /// Determines whether the specified Nature satisfies the criteria. /// diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8U.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8U.cs index 753546d8b..fd217886f 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8U.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8U.cs @@ -57,6 +57,10 @@ protected override void SetPINGA(PK8 pk, in EncounterCriteria criteria, Personal { Span iv = stackalloc int[6]; + // Honor a shiny request only at the end; generate as never-shiny to avoid shiny PID rejection in main RNG method. + var isShinyRequested = criteria.Shiny.IsShiny(); + var iterCriteria = criteria with { Shiny = ShinyMethod }; + int ctr = 0; var rand = new Xoroshiro128Plus(Util.Rand.Rand64()); var param = GetParam(pi); @@ -64,21 +68,22 @@ protected override void SetPINGA(PK8 pk, in EncounterCriteria criteria, Personal const int max = 100_000; do { - if (TryApply(pk, seed = rand.Next(), iv, param, criteria)) + if (TryApply(pk, seed = rand.Next(), iv, param, iterCriteria)) break; } while (++ctr < max); if (ctr == max) // fail { - if (!TryApply(pk, seed = rand.Next(), iv, param, criteria.WithoutIVs())) + iterCriteria = iterCriteria.WithoutIVs(); + if (!TryApply(pk, seed = rand.Next(), iv, param, iterCriteria)) { - var tmp = EncounterCriteria.Unrestricted with { Shiny = ShinyMethod }; - while (!TryApply(pk, seed = rand.Next(), iv, param, tmp)) { } + iterCriteria = EncounterCriteria.Unrestricted with { Shiny = ShinyMethod }; + while (!TryApply(pk, seed = rand.Next(), iv, param, iterCriteria)) { } } } FinishCorrelation(pk, seed); - if (criteria.Shiny.IsShiny()) + if (isShinyRequested) pk.PID = ShinyUtil.GetShinyPID(pk.TID16, pk.SID16, pk.PID, ShinyXor); } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen9a/LumioseRNG.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen9a/LumioseRNG.cs index 5a5055930..90a3737cf 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen9a/LumioseRNG.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen9a/LumioseRNG.cs @@ -36,10 +36,10 @@ public static bool GenerateData(PA9 pk, in GenerateParam9a enc, in EncounterCrit { var rand = new Xoroshiro128Plus(seed); pk.EncryptionConstant = (uint)rand.NextInt(uint.MaxValue); - pk.PID = GetAdaptedPID(ref rand, pk, enc); - - if (enc.Shiny is Shiny.Random && criteria.Shiny.IsShiny() != pk.IsShiny) + var pid = GetAdaptedPID(ref rand, pk, enc); + if (enc.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(ShinyUtil.GetShinyXor(pid, pk.ID32), 16)) return false; + pk.PID = pid; Span ivs = [UNSET, UNSET, UNSET, UNSET, UNSET, UNSET]; if (enc.IVs.IsSpecified) diff --git a/PKHeX.Core/Legality/RNG/Methods/Gen8/Overworld8RNG.cs b/PKHeX.Core/Legality/RNG/Methods/Gen8/Overworld8RNG.cs index 6ffc86788..231b5567d 100644 --- a/PKHeX.Core/Legality/RNG/Methods/Gen8/Overworld8RNG.cs +++ b/PKHeX.Core/Legality/RNG/Methods/Gen8/Overworld8RNG.cs @@ -55,7 +55,8 @@ private static bool TryApplyFromSeed(PK8 pk, in EncounterCriteria criteria, Shin if (shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar) return false; } - + if (shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16)) + return false; pk.PID = pid; // IVs diff --git a/PKHeX.Core/Legality/RNG/Methods/Gen8/RaidRNG.cs b/PKHeX.Core/Legality/RNG/Methods/Gen8/RaidRNG.cs index 083f72ee0..abdaab812 100644 --- a/PKHeX.Core/Legality/RNG/Methods/Gen8/RaidRNG.cs +++ b/PKHeX.Core/Legality/RNG/Methods/Gen8/RaidRNG.cs @@ -160,6 +160,8 @@ public static bool TryApply(PK8 pk, ulong seed, Span ivs, in GenerateParam8 var trID = (uint)rng.NextInt(); var pid = (uint)rng.NextInt(); + + // Battle var xor = GetShinyXor(pid, trID); bool isShiny = xor < 16; if (isShiny && param.Shiny == Shiny.Never) @@ -167,9 +169,8 @@ public static bool TryApply(PK8 pk, ulong seed, Span ivs, in GenerateParam8 ForceShinyState(false, ref pid, trID, 0); isShiny = false; } - if (param.Shiny is Shiny.Random && isShiny != criteria.Shiny.IsShiny()) - return false; + // Captured if (isShiny) { if (!GetIsShiny6(pk.ID32, pid)) @@ -181,6 +182,8 @@ public static bool TryApply(PK8 pk, ulong seed, Span ivs, in GenerateParam8 pid ^= 0x1000_0000; } + if (param.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16)) + return false; pk.PID = pid; const int UNSET = -1; diff --git a/PKHeX.Core/Legality/RNG/Methods/Gen8a/Overworld8aRNG.cs b/PKHeX.Core/Legality/RNG/Methods/Gen8a/Overworld8aRNG.cs index 25451a2e4..e11a7bc6c 100644 --- a/PKHeX.Core/Legality/RNG/Methods/Gen8a/Overworld8aRNG.cs +++ b/PKHeX.Core/Legality/RNG/Methods/Gen8a/Overworld8aRNG.cs @@ -129,6 +129,8 @@ public static bool TryApplyFromSeed(PA8 pk, in EncounterCriteria criteria, in Ov if (para.Shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar) return false; } + if (para.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16)) + return false; pk.PID = pid; Span ivs = [UNSET, UNSET, UNSET, UNSET, UNSET, UNSET]; diff --git a/PKHeX.Core/Legality/RNG/Methods/Gen8b/Roaming8bRNG.cs b/PKHeX.Core/Legality/RNG/Methods/Gen8b/Roaming8bRNG.cs index c9407df88..94f9634e1 100644 --- a/PKHeX.Core/Legality/RNG/Methods/Gen8b/Roaming8bRNG.cs +++ b/PKHeX.Core/Legality/RNG/Methods/Gen8b/Roaming8bRNG.cs @@ -65,6 +65,8 @@ public static bool TryApplyFromSeed(PB8 pk, in EncounterCriteria criteria, Shiny if (shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar) return false; } + if (shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16)) + return false; pk.PID = pid; // Check IVs: Create flawless IVs at random indexes, then the random IVs for not flawless. diff --git a/PKHeX.Core/Legality/RNG/Methods/Gen8b/Wild8bRNG.cs b/PKHeX.Core/Legality/RNG/Methods/Gen8b/Wild8bRNG.cs index f98eb2f3b..fb669ab8a 100644 --- a/PKHeX.Core/Legality/RNG/Methods/Gen8b/Wild8bRNG.cs +++ b/PKHeX.Core/Legality/RNG/Methods/Gen8b/Wild8bRNG.cs @@ -65,6 +65,8 @@ public static bool TryApplyFromSeed(PB8 pk, in EncounterCriteria criteria, Shiny if (shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar) return false; } + if (shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16)) + return false; pk.PID = pid; // Check IVs: Create flawless IVs at random indexes, then the random IVs for not flawless. diff --git a/PKHeX.Core/Legality/RNG/Methods/Gen9/Encounter9RNG.cs b/PKHeX.Core/Legality/RNG/Methods/Gen9/Encounter9RNG.cs index 4bf85080a..7d9866428 100644 --- a/PKHeX.Core/Legality/RNG/Methods/Gen9/Encounter9RNG.cs +++ b/PKHeX.Core/Legality/RNG/Methods/Gen9/Encounter9RNG.cs @@ -62,10 +62,10 @@ public static bool GenerateData(PK9 pk, in GenerateParam9 enc, in EncounterCrite { var rand = new Xoroshiro128Plus(seed); pk.EncryptionConstant = (uint)rand.NextInt(uint.MaxValue); - pk.PID = GetAdaptedPID(ref rand, pk, enc); - - if (enc.Shiny is Shiny.Random && criteria.Shiny.IsShiny() != pk.IsShiny) + var pid = GetAdaptedPID(ref rand, pk, enc); + if (enc.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16)) return false; + pk.PID = pid; const int UNSET = -1; const int MAX = 31; diff --git a/PKHeX.WinForms/Settings/StartupSettings.cs b/PKHeX.WinForms/Settings/StartupSettings.cs index 8f59a4311..0d98eb505 100644 --- a/PKHeX.WinForms/Settings/StartupSettings.cs +++ b/PKHeX.WinForms/Settings/StartupSettings.cs @@ -39,15 +39,14 @@ public sealed class StartupSettings : IStartupSettings public List RecentlyLoaded { get; set; } = new(DefaultMaxRecent); private const int DefaultMaxRecent = 10; - private uint MaxRecentCount = DefaultMaxRecent; [LocalizedDescription("Amount of recently loaded save files to remember.")] public uint RecentlyLoadedMaxCount { - get => MaxRecentCount; + get; // Sanity check to not let the user foot-gun themselves a slow recall time. - set => MaxRecentCount = Math.Clamp(value, 1, 1000); - } + set => field = Math.Clamp(value, 1, 1000); + } = DefaultMaxRecent; // Don't let invalid values slip into the startup version. @@ -89,7 +88,7 @@ public void LoadSaveFile(string path) { var recent = RecentlyLoaded; // Remove from list if already present. - if (!recent.Remove(path) && recent.Count >= MaxRecentCount) + if (!recent.Remove(path) && recent.Count >= RecentlyLoadedMaxCount) recent.RemoveAt(recent.Count - 1); recent.Insert(0, path); }