mirror of
https://github.com/kwsch/PKHeX
synced 2024-12-04 17:59:16 +00:00
88830e0d00
Updates from net46->net7, dropping support for mono in favor of using the latest runtime (along with the performance/API improvements). Releases will be posted as 64bit only for now. Refactors a good amount of internal API methods to be more performant and more customizable for future updates & fixes. Adds functionality for Batch Editor commands to `>`, `<` and <=/>= TID/SID properties renamed to TID16/SID16 for clarity; other properties exposed for Gen7 / display variants. Main window has a new layout to account for DPI scaling (8 point grid) Fixed: Tatsugiri and Paldean Tauros now output Showdown form names as Showdown expects Changed: Gen9 species now interact based on the confirmed National Dex IDs (closes #3724) Fixed: Pokedex set all no longer clears species with unavailable non-base forms (closes #3720) Changed: Hyper Training suggestions now apply for level 50 in SV. (closes #3714) Fixed: B2/W2 hatched egg met locations exclusive to specific versions are now explicitly checked (closes #3691) Added: Properties for ribbon/mark count (closes #3659) Fixed: Traded SV eggs are now checked correctly (closes #3692)
255 lines
9.6 KiB
C#
255 lines
9.6 KiB
C#
using System;
|
|
using static PKHeX.Core.ContestStatGranting;
|
|
|
|
namespace PKHeX.Core;
|
|
|
|
/// <summary>
|
|
/// Logic for checking and applying <see cref="IContestStatsReadOnly"/>.
|
|
/// </summary>
|
|
public static class ContestStatInfo
|
|
{
|
|
private const int LowestFeelBlock3 = 1; // quad Nutpea
|
|
private const int LowestFeelPoffin4 = 11; // Beauty Haircut
|
|
private const int LowestFeelPoffin8b = 7;
|
|
private const int HighestFeelPoffin8b = 79;
|
|
|
|
private const byte MaxContestStat = 255;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private const int BestSheenStat3 = 214;
|
|
|
|
/// <summary>
|
|
/// Using optimal poffins in BD/SP, roughly 120-140; be generous.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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
|
|
/// </remarks>
|
|
private const int BestSheenStat8b = 120;
|
|
|
|
public static void SetSuggestedContestStats(this PKM pk, IEncounterTemplate enc, EvolutionHistory h)
|
|
{
|
|
if (pk is not IContestStats s)
|
|
return;
|
|
|
|
var restrict = GetContestStatRestriction(pk, pk.Generation, h);
|
|
var baseStat = GetReferenceTemplate(enc);
|
|
if (restrict == None || pk.Species is not (int)Species.Milotic)
|
|
baseStat.CopyContestStatsTo(s); // reset
|
|
else
|
|
s.SetAllContestStatsTo(MaxContestStat, restrict == NoSheen ? baseStat.CNT_Sheen : MaxContestStat);
|
|
}
|
|
|
|
public static void SetMaxContestStats(this PKM pk, IEncounterTemplate enc, EvolutionHistory h)
|
|
{
|
|
if (pk is not IContestStats s)
|
|
return;
|
|
var restrict = GetContestStatRestriction(pk, enc.Generation, h);
|
|
var baseStat = GetReferenceTemplate(enc);
|
|
if (restrict == None)
|
|
return;
|
|
s.SetAllContestStatsTo(MaxContestStat, restrict == NoSheen ? baseStat.CNT_Sheen : MaxContestStat);
|
|
}
|
|
|
|
public static ContestStatGranting GetContestStatRestriction(PKM pk, int origin, EvolutionHistory h) => origin switch
|
|
{
|
|
3 => pk.Format < 6 ? CorrelateSheen : Mixed,
|
|
4 => pk.Format < 6 ? CorrelateSheen : Mixed,
|
|
|
|
5 => pk.Format < 6 ? None : !h.HasVisitedBDSP ? NoSheen : Mixed, // ORAS Contests
|
|
6 => pk is { AO: false, IsUntraded: true } ? None : !h.HasVisitedBDSP ? NoSheen : Mixed,
|
|
|
|
_ => h.HasVisitedBDSP ? CorrelateSheen : None, // BDSP Contests
|
|
};
|
|
|
|
public static int CalculateMaximumSheen(IContestStatsReadOnly s, int nature, IContestStatsReadOnly initial, bool pokeBlock3)
|
|
{
|
|
if (s.IsAnyContestStatMax())
|
|
return MaxContestStat;
|
|
|
|
if (s.IsContestEqual(initial))
|
|
return initial.CNT_Sheen;
|
|
|
|
if (pokeBlock3)
|
|
return CalculateMaximumSheen3(s, nature, initial);
|
|
|
|
var avg = GetAverageFeel(s, nature, initial);
|
|
if (avg <= 0)
|
|
return initial.CNT_Sheen;
|
|
|
|
if (avg <= 2)
|
|
return 59;
|
|
|
|
// Can get trash poffins by burning and spilling on purpose.
|
|
return Math.Min(MaxContestStat, avg * HighestFeelPoffin8b);
|
|
}
|
|
|
|
public static int CalculateMinimumSheen(IContestStatsReadOnly s, IContestStatsReadOnly initial, INature pk, ContestStatGrantingSheen method) => method switch
|
|
{
|
|
ContestStatGrantingSheen.Gen8b => CalculateMinimumSheen8b(s, pk.Nature, initial),
|
|
ContestStatGrantingSheen.Gen3 => CalculateMinimumSheen3(s, pk.Nature, initial),
|
|
ContestStatGrantingSheen.Gen4 => CalculateMinimumSheen4(s, pk.Nature, initial),
|
|
_ => throw new ArgumentOutOfRangeException(nameof(method)),
|
|
};
|
|
|
|
// Slightly better stat:sheen ratio than Gen4; prefer if has visited.
|
|
public static int CalculateMinimumSheen8b(IContestStatsReadOnly s, int nature, IContestStatsReadOnly initial)
|
|
{
|
|
if (s.IsContestEqual(initial))
|
|
return initial.CNT_Sheen;
|
|
|
|
var rawAvg = GetAverageFeel(s, 0, initial);
|
|
if (rawAvg == MaxContestStat)
|
|
return BestSheenStat8b;
|
|
|
|
var avg = Math.Max(1, nature % 6 == 0 ? rawAvg : GetAverageFeel(s, nature, initial));
|
|
avg = Math.Min(rawAvg, avg); // be generous
|
|
avg = (BestSheenStat8b * avg) / MaxContestStat;
|
|
|
|
return Math.Clamp(avg, LowestFeelPoffin8b, BestSheenStat8b);
|
|
}
|
|
|
|
public static int CalculateMinimumSheen3(IContestStatsReadOnly s, int nature, IContestStatsReadOnly 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
|
|
|
|
avg = (BestSheenStat3 * avg) / MaxContestStat;
|
|
return Math.Clamp(avg, LowestFeelBlock3, BestSheenStat3);
|
|
}
|
|
|
|
public static int CalculateMinimumSheen4(IContestStatsReadOnly s, int nature, IContestStatsReadOnly 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.Clamp(avg, LowestFeelPoffin4, MaxContestStat);
|
|
}
|
|
|
|
private static int CalculateMaximumSheen3(IContestStatsReadOnly s, int nature, IContestStatsReadOnly initial)
|
|
{
|
|
// By using Enigma and Lansat and a 25 +1/-1, can get a +9/+19s at minimum RPM
|
|
// By using Strib, Chilan, Niniku, or Topo, can get a black +2/2/2 & 83 block (6:83) at minimum RPM.
|
|
// https://github.com/kwsch/PKHeX/issues/3517
|
|
var sum = GetGainedSum(s, nature, initial);
|
|
if (sum == 0)
|
|
return 0;
|
|
|
|
static int Gained2(byte a, byte b) => a - b >= 2 ? 1 : 0;
|
|
int gained = Gained2(s.CNT_Cool, initial.CNT_Cool)
|
|
+ Gained2(s.CNT_Beauty, initial.CNT_Beauty)
|
|
+ Gained2(s.CNT_Cute, initial.CNT_Cute)
|
|
+ Gained2(s.CNT_Smart, initial.CNT_Smart)
|
|
+ Gained2(s.CNT_Tough, initial.CNT_Tough);
|
|
bool has3 = gained >= 3;
|
|
|
|
// Prefer the bad-black-block correlation if more than 3 stats have gains >= 2.
|
|
var permit = has3 ? (sum * 83 / 6) : (sum * 19 / 9);
|
|
return Math.Clamp(permit, LowestFeelBlock3, MaxContestStat);
|
|
}
|
|
|
|
private static int GetAverageFeel(IContestStatsReadOnly s, int nature, IContestStatsReadOnly initial)
|
|
{
|
|
var sum = GetGainedSum(s, nature, initial);
|
|
return (int)Math.Ceiling(sum / 5f);
|
|
}
|
|
|
|
private static int GetGainedSum(IContestStatsReadOnly s, int nature, IContestStatsReadOnly initial)
|
|
{
|
|
var span = NatureAmpTable.Slice(5 * nature, 5);
|
|
int sum = 0;
|
|
sum += GetAmpedStat(span, 0, s.CNT_Cool - initial.CNT_Cool);
|
|
sum += GetAmpedStat(span, 1, s.CNT_Beauty - initial.CNT_Beauty);
|
|
sum += GetAmpedStat(span, 2, s.CNT_Cute - initial.CNT_Cute);
|
|
sum += GetAmpedStat(span, 3, s.CNT_Smart - initial.CNT_Smart);
|
|
sum += GetAmpedStat(span, 4, s.CNT_Tough - initial.CNT_Tough);
|
|
return sum;
|
|
}
|
|
|
|
private static int GetAmpedStat(ReadOnlySpan<sbyte> amps, int index, int gain)
|
|
{
|
|
var amp = amps[index];
|
|
if (amp == 0)
|
|
return gain;
|
|
return gain + GetStatAdjustment(gain, amp);
|
|
}
|
|
|
|
private static int GetStatAdjustment(int gain, sbyte amp)
|
|
{
|
|
// Undo the favor factor
|
|
var undoFactor = amp == 1 ? 11 : 9;
|
|
var boost = Boost(gain, undoFactor);
|
|
return amp == -1 ? boost : -boost;
|
|
|
|
static int Boost(int stat, int factor)
|
|
{
|
|
var remainder = stat % factor;
|
|
var boost = stat / factor;
|
|
|
|
if (remainder >= 5)
|
|
++boost;
|
|
return boost;
|
|
}
|
|
}
|
|
|
|
private static readonly DummyContestNone DummyNone = new();
|
|
|
|
public static IContestStatsReadOnly GetReferenceTemplate(IEncounterTemplate initial) => initial as IContestStatsReadOnly ?? DummyNone;
|
|
|
|
private sealed class DummyContestNone : IContestStatsReadOnly
|
|
{
|
|
public byte CNT_Cool => 0;
|
|
public byte CNT_Beauty => 0;
|
|
public byte CNT_Cute => 0;
|
|
public byte CNT_Smart => 0;
|
|
public byte CNT_Tough => 0;
|
|
public byte CNT_Sheen => 0;
|
|
}
|
|
|
|
private static ReadOnlySpan<sbyte> NatureAmpTable => new sbyte[]
|
|
{
|
|
// Spicy, Dry, Sweet, Bitter, Sour
|
|
0, 0, 0, 0, 0, // Hardy
|
|
1, 0, 0, 0,-1, // Lonely
|
|
1, 0,-1, 0, 0, // Brave
|
|
1,-1, 0, 0, 0, // Adamant
|
|
1, 0, 0,-1, 0, // Naughty
|
|
-1, 0, 0, 0, 1, // Bold
|
|
0, 0, 0, 0, 0, // Docile
|
|
0, 0,-1, 0, 1, // Relaxed
|
|
0,-1, 0, 0, 1, // Impish
|
|
0, 0, 0,-1, 1, // Lax
|
|
-1, 0, 1, 0, 0, // Timid
|
|
0, 0, 1, 0,-1, // Hasty
|
|
0, 0, 0, 0, 0, // Serious
|
|
0,-1, 1, 0, 0, // Jolly
|
|
0, 0, 1,-1, 0, // Naive
|
|
-1, 1, 0, 0, 0, // Modest
|
|
0, 1, 0, 0,-1, // Mild
|
|
0, 1,-1, 0, 0, // Quiet
|
|
0, 0, 0, 0, 0, // Bashful
|
|
0, 1, 0,-1, 0, // Rash
|
|
-1, 0, 0, 1, 0, // Calm
|
|
0, 0, 0, 1,-1, // Gentle
|
|
0, 0,-1, 1, 0, // Sassy
|
|
0,-1, 0, 1, 0, // Careful
|
|
0, 0, 0, 0, 0, // Quirky
|
|
};
|
|
}
|