2021-12-07 08:54:39 +00:00
using System ;
using static PKHeX . Core . ContestStatGranting ;
namespace PKHeX.Core ;
2022-03-06 20:16:36 +00:00
/// <summary>
/// Logic for checking and applying <see cref="IContestStats"/>.
/// </summary>
2021-12-07 08:54:39 +00:00
public static class ContestStatInfo
{
2022-01-12 07:29:01 +00:00
private const int LowestFeelBlock3 = 1 ; // quad Nutpea
2022-02-06 08:30:38 +00:00
private const int LowestFeelPoffin4 = 11 ; // Beauty Haircut
2022-01-12 07:29:01 +00:00
private const int LowestFeelPoffin8b = 7 ;
2022-06-14 04:57:57 +00:00
private const int HighestFeelPoffin8b = 79 ;
2022-01-12 07:29:01 +00:00
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 ;
2021-12-07 08:54:39 +00:00
2022-05-31 04:43:52 +00:00
public static void SetSuggestedContestStats ( this PKM pk , IEncounterTemplate enc , EvolutionHistory h )
2021-12-07 08:54:39 +00:00
{
if ( pk is not IContestStatsMutable s )
return ;
2022-05-31 04:43:52 +00:00
var restrict = GetContestStatRestriction ( pk , pk . Generation , h ) ;
2021-12-09 06:43:56 +00:00
var baseStat = GetReferenceTemplate ( enc ) ;
if ( restrict = = None | | pk . Species is not ( int ) Species . Milotic )
baseStat . CopyContestStatsTo ( s ) ; // reset
2021-12-07 08:54:39 +00:00
else
2022-01-12 07:29:01 +00:00
s . SetAllContestStatsTo ( MaxContestStat , restrict = = NoSheen ? baseStat . CNT_Sheen : MaxContestStat ) ;
2021-12-09 06:43:56 +00:00
}
2022-05-31 04:43:52 +00:00
public static void SetMaxContestStats ( this PKM pk , IEncounterTemplate enc , EvolutionHistory h )
2021-12-09 06:43:56 +00:00
{
if ( pk is not IContestStatsMutable s )
return ;
2022-05-31 04:43:52 +00:00
var restrict = GetContestStatRestriction ( pk , enc . Generation , h ) ;
2021-12-09 06:43:56 +00:00
var baseStat = GetReferenceTemplate ( enc ) ;
if ( restrict = = None )
return ;
2022-01-12 07:29:01 +00:00
s . SetAllContestStatsTo ( MaxContestStat , restrict = = NoSheen ? baseStat . CNT_Sheen : MaxContestStat ) ;
2021-12-07 08:54:39 +00:00
}
2022-05-31 04:43:52 +00:00
public static ContestStatGranting GetContestStatRestriction ( PKM pk , int origin , EvolutionHistory h ) = > origin switch
2021-12-07 08:54:39 +00:00
{
3 = > pk . Format < 6 ? CorrelateSheen : Mixed ,
4 = > pk . Format < 6 ? CorrelateSheen : Mixed ,
2022-06-03 02:11:37 +00:00
5 = > pk . Format < 6 ? None : ! pk . HasVisitedBDSP ( h . Gen8b ) ? NoSheen : Mixed , // ORAS Contests
6 = > ! pk . AO & & pk . IsUntraded ? None : ! pk . HasVisitedBDSP ( h . Gen8b ) ? NoSheen : Mixed ,
_ = > pk . HasVisitedBDSP ( h . Gen8b ) ? CorrelateSheen : None , // BDSP Contests
2021-12-07 08:54:39 +00:00
} ;
public static int CalculateMaximumSheen ( IContestStats s , int nature , IContestStats initial , bool pokeBlock3 )
{
if ( s . IsAnyContestStatMax ( ) )
return MaxContestStat ;
if ( s . IsContestEqual ( initial ) )
return initial . CNT_Sheen ;
if ( pokeBlock3 )
2021-12-10 07:33:54 +00:00
return CalculateMaximumSheen3 ( s , nature , initial ) ;
2021-12-07 08:54:39 +00:00
2021-12-10 07:33:54 +00:00
var avg = GetAverageFeel ( s , nature , initial ) ;
if ( avg < = 0 )
return initial . CNT_Sheen ;
2022-02-25 07:17:16 +00:00
2022-02-04 08:37:29 +00:00
if ( avg < = 2 )
2022-06-14 13:15:37 +00:00
return 59 ;
2021-12-10 07:33:54 +00:00
2021-12-07 08:54:39 +00:00
// Can get trash poffins by burning and spilling on purpose.
2022-06-14 04:57:57 +00:00
return Math . Min ( MaxContestStat , avg * HighestFeelPoffin8b ) ;
2021-12-26 02:51:02 +00:00
}
2022-01-12 07:29:01 +00:00
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 ) ,
2022-02-15 05:32:52 +00:00
_ = > throw new IndexOutOfRangeException ( nameof ( method ) ) ,
2022-01-12 07:29:01 +00:00
} ;
2021-12-26 02:51:02 +00:00
// Slightly better stat:sheen ratio than Gen4; prefer if has visited.
public static int CalculateMinimumSheen8b ( IContestStats s , int nature , IContestStats 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
2022-01-12 07:29:01 +00:00
avg = ( BestSheenStat8b * avg ) / MaxContestStat ;
2021-12-26 02:51:02 +00:00
return Math . Min ( BestSheenStat8b , Math . Max ( LowestFeelPoffin8b , avg ) ) ;
2021-12-07 08:54:39 +00:00
}
2022-01-12 07:29:01 +00:00
public static int CalculateMinimumSheen3 ( 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
avg = ( BestSheenStat3 * avg ) / MaxContestStat ;
return Math . Min ( BestSheenStat3 , Math . Max ( LowestFeelBlock3 , avg ) ) ;
}
public static int CalculateMinimumSheen4 ( IContestStats s , int nature , IContestStats initial )
2021-12-07 08:54:39 +00:00
{
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
2021-12-10 03:30:12 +00:00
2022-01-12 07:29:01 +00:00
return Math . Min ( MaxContestStat , Math . Max ( LowestFeelPoffin4 , avg ) ) ;
2021-12-07 08:54:39 +00:00
}
2021-12-10 07:33:54 +00:00
private static int CalculateMaximumSheen3 ( IContestStats s , int nature , IContestStats initial )
{
// By using Enigma and Lansat and a 25 +1/-1, can get a +9/+19s at minimum RPM
2022-06-11 16:36:50 +00:00
// 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
2021-12-10 07:33:54 +00:00
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.
2022-06-11 16:36:50 +00:00
var permit = has3 ? ( sum * 83 / 6 ) : ( sum * 19 / 9 ) ;
2021-12-26 02:51:02 +00:00
return Math . Min ( MaxContestStat , Math . Max ( LowestFeelBlock3 , permit ) ) ;
2021-12-10 07:33:54 +00:00
}
2021-12-07 08:54:39 +00:00
private static int GetAverageFeel ( IContestStats s , int nature , IContestStats initial )
2021-12-10 07:33:54 +00:00
{
var sum = GetGainedSum ( s , nature , initial ) ;
2022-02-04 08:37:29 +00:00
return ( int ) Math . Ceiling ( sum / 5f ) ;
2021-12-10 07:33:54 +00:00
}
private static int GetGainedSum ( IContestStats s , int nature , IContestStats initial )
2021-12-07 08:54:39 +00:00
{
ReadOnlySpan < sbyte > span = NatureAmpTable . AsSpan ( 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 ) ;
2021-12-10 07:33:54 +00:00
return sum ;
2021-12-07 08:54:39 +00:00
}
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 IContestStats GetReferenceTemplate ( IEncounterTemplate initial ) = > initial is IContestStats s ? s : DummyNone ;
2022-03-06 20:16:36 +00:00
private sealed class DummyContestNone : IContestStats
2021-12-07 08:54:39 +00:00
{
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 readonly sbyte [ ] NatureAmpTable =
{
2021-12-10 08:15:04 +00:00
// Spicy, Dry, Sweet, Bitter, Sour
2021-12-07 08:54:39 +00:00
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
} ;
}