diff --git a/PKHeX.Core/Legality/Verifiers/ContestStatVerifier.cs b/PKHeX.Core/Legality/Verifiers/ContestStatVerifier.cs
index 643bb0493..b44e86c74 100644
--- a/PKHeX.Core/Legality/Verifiers/ContestStatVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/ContestStatVerifier.cs
@@ -41,13 +41,13 @@ public sealed class ContestStatVerifier : Verifier
else if (correlation == CorrelateSheen)
{
bool gen3 = data.Info.Generation == 3;
+ bool bdsp = pkm.HasVisitedBDSP(data.Info.EncounterOriginal.Species);
+ var method = gen3 ? ContestStatGrantingSheen.Gen3 :
+ bdsp ? ContestStatGrantingSheen.Gen8b : ContestStatGrantingSheen.Gen4;
// Check for stat values that exceed a valid sheen value.
var initial = GetReferenceTemplate(data.Info.EncounterMatch);
- bool bdsp = pkm.HasVisitedBDSP(data.Info.EncounterOriginal.Species);
- var minSheen = bdsp
- ? CalculateMinimumSheen8b(s, pkm.Nature, initial)
- : CalculateMinimumSheen(s, pkm.Nature, initial, gen3);
+ var minSheen = CalculateMinimumSheen(s, initial, pkm, method);
if (s.CNT_Sheen < minSheen)
data.AddLine(GetInvalid(string.Format(LContestSheenTooLow_0, minSheen)));
diff --git a/PKHeX.Core/Legality/Verifiers/Misc/ContestStatGranting.cs b/PKHeX.Core/Legality/Verifiers/Misc/ContestStatGranting.cs
index 39e571833..3f3cc14f7 100644
--- a/PKHeX.Core/Legality/Verifiers/Misc/ContestStatGranting.cs
+++ b/PKHeX.Core/Legality/Verifiers/Misc/ContestStatGranting.cs
@@ -11,3 +11,10 @@ public enum ContestStatGranting
/// Contest stats are possible to obtain, and has visited a multitude of games such that any value of sheen is possible.
Mixed,
}
+
+public enum ContestStatGrantingSheen
+{
+ Gen3,
+ Gen4,
+ Gen8b,
+}
diff --git a/PKHeX.Core/Legality/Verifiers/Misc/ContestStatInfo.cs b/PKHeX.Core/Legality/Verifiers/Misc/ContestStatInfo.cs
index 91af32234..8e4dac0f1 100644
--- a/PKHeX.Core/Legality/Verifiers/Misc/ContestStatInfo.cs
+++ b/PKHeX.Core/Legality/Verifiers/Misc/ContestStatInfo.cs
@@ -5,11 +5,26 @@ namespace PKHeX.Core;
public static class ContestStatInfo
{
- private const int LowestFeelBlock3 = 3;
- private const int LowestFeelPoffin4 = 17;
- private const int LowestFeelPoffin8b = 8;
- private const int MaxContestStat = 255;
- private const int BestSheenStat8b = 160; // using optimal poffins in BD/SP, roughly 160-170; be generous.
+ private const int LowestFeelBlock3 = 1; // quad Nutpea
+ private const int LowestFeelPoffin4 = 16;
+ private const int LowestFeelPoffin8b = 7;
+
+ private const byte MaxContestStat = 255;
+
+ ///
+ /// By giving out all-Nutpea blocks in 3, you can have contest stats all maxed while feel is at 214 if the random stats of the foul blocks line up perfectly.
+ ///
+ private const int BestSheenStat3 = 214;
+
+ ///
+ /// Using optimal poffins in BD/SP, roughly 120-140; be generous.
+ ///
+ ///
+ /// Liechi-Pomeg-Qualot-Occa etc. (2 each), which puts sheen at 140 and will max all stats if the cook times are 47.43 or faster
+ /// Leppa-Pomeg-Qualot-Occa etc. (3 each), which puts sheen at 135 and will max all stats if the cook times are 43.32 or faster
+ /// Liechi-Leppa-Pomeg-Qualot etc. (2 each), which puts sheen at 120 and will max all stats if the cook times area 40.40 or faster
+ ///
+ private const int BestSheenStat8b = 120;
public static void SetSuggestedContestStats(this PKM pk, IEncounterTemplate enc)
{
@@ -21,7 +36,7 @@ public static class ContestStatInfo
if (restrict == None || pk.Species is not (int)Species.Milotic)
baseStat.CopyContestStatsTo(s); // reset
else
- s.SetAllContestStatsTo(MaxContestStat, restrict == NoSheen ? baseStat.CNT_Sheen : (byte)255);
+ s.SetAllContestStatsTo(MaxContestStat, restrict == NoSheen ? baseStat.CNT_Sheen : MaxContestStat);
}
public static void SetMaxContestStats(this PKM pk, IEncounterTemplate enc)
@@ -32,7 +47,7 @@ public static class ContestStatInfo
var baseStat = GetReferenceTemplate(enc);
if (restrict == None)
return;
- s.SetAllContestStatsTo(MaxContestStat, restrict == NoSheen ? baseStat.CNT_Sheen : (byte)255);
+ s.SetAllContestStatsTo(MaxContestStat, restrict == NoSheen ? baseStat.CNT_Sheen : MaxContestStat);
}
public static ContestStatGranting GetContestStatRestriction(PKM pk, int origin) => origin switch
@@ -65,6 +80,14 @@ public static class ContestStatInfo
return Math.Min(MaxContestStat, avg * LowestFeelPoffin4);
}
+ public static int CalculateMinimumSheen(IContestStats s, IContestStats initial, INature pkm, ContestStatGrantingSheen method) => method switch
+ {
+ ContestStatGrantingSheen.Gen8b => CalculateMinimumSheen8b(s, pkm.Nature, initial),
+ ContestStatGrantingSheen.Gen3 => CalculateMinimumSheen3(s, pkm.Nature, initial),
+ ContestStatGrantingSheen.Gen4 => CalculateMinimumSheen4(s, pkm.Nature, initial),
+ _ => throw new IndexOutOfRangeException(nameof(method))
+ };
+
// Slightly better stat:sheen ratio than Gen4; prefer if has visited.
public static int CalculateMinimumSheen8b(IContestStats s, int nature, IContestStats initial)
{
@@ -77,12 +100,12 @@ public static class ContestStatInfo
var avg = Math.Max(1, nature % 6 == 0 ? rawAvg : GetAverageFeel(s, nature, initial));
avg = Math.Min(rawAvg, avg); // be generous
- avg = (avg * 8) / 10; // even more generous?? sheen formula undocumented
+ avg = (BestSheenStat8b * avg) / MaxContestStat;
return Math.Min(BestSheenStat8b, Math.Max(LowestFeelPoffin8b, avg));
}
- public static int CalculateMinimumSheen(IContestStats s, int nature, IContestStats initial, bool pokeBlock3)
+ public static int CalculateMinimumSheen3(IContestStats s, int nature, IContestStats initial)
{
if (s.IsContestEqual(initial))
return initial.CNT_Sheen;
@@ -94,8 +117,23 @@ public static class ContestStatInfo
var avg = Math.Max(1, nature % 6 == 0 ? rawAvg : GetAverageFeel(s, nature, initial));
avg = Math.Min(rawAvg, avg); // be generous
- var worst = pokeBlock3 ? LowestFeelBlock3 : LowestFeelPoffin4;
- return Math.Min(MaxContestStat, Math.Max(worst, avg));
+ avg = (BestSheenStat3 * avg) / MaxContestStat;
+ return Math.Min(BestSheenStat3, Math.Max(LowestFeelBlock3, avg));
+ }
+
+ public static int CalculateMinimumSheen4(IContestStats s, int nature, IContestStats initial)
+ {
+ if (s.IsContestEqual(initial))
+ return initial.CNT_Sheen;
+
+ var rawAvg = GetAverageFeel(s, 0, initial);
+ if (rawAvg == MaxContestStat)
+ return MaxContestStat;
+
+ var avg = Math.Max(1, nature % 6 == 0 ? rawAvg : GetAverageFeel(s, nature, initial));
+ avg = Math.Min(rawAvg, avg); // be generous
+
+ return Math.Min(MaxContestStat, Math.Max(LowestFeelPoffin4, avg));
}
private static int CalculateMaximumSheen3(IContestStats s, int nature, IContestStats initial)