mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-23 12:33:06 +00:00
parent
fcd97d5989
commit
543359fff6
6 changed files with 276 additions and 26 deletions
|
@ -94,6 +94,9 @@ namespace PKHeX.Core
|
|||
public static string LBallUnavailable { get; set; } = "Ball unobtainable in origin Generation.";
|
||||
|
||||
public static string LContestZero { get; set; } = "Contest Stats should be 0.";
|
||||
public static string LContestZeroSheen { get; set; } = "Contest Stat Sheen should be 0.";
|
||||
public static string LContestSheenTooLow_0 { get; set; } = "Contest Stat Sheen should be >= {0}.";
|
||||
public static string LContestSheenTooHigh_0 { get; set; } = "Contest Stat Sheen should be <= {0}.";
|
||||
|
||||
public static string LDateOutsideDistributionWindow { get; set; } = "Met Date is outside of distribution window.";
|
||||
|
||||
|
|
|
@ -1,33 +1,67 @@
|
|||
namespace PKHeX.Core
|
||||
using static PKHeX.Core.ContestStatGranting;
|
||||
using static PKHeX.Core.ContestStatInfo;
|
||||
using static PKHeX.Core.LegalityCheckStrings;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the Contest stat details.
|
||||
/// </summary>
|
||||
public sealed class ContestStatVerifier : Verifier
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies the Contest stat details.
|
||||
/// </summary>
|
||||
public sealed class ContestStatVerifier : Verifier
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Memory;
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
{
|
||||
protected override CheckIdentifier Identifier => CheckIdentifier.Memory;
|
||||
public override void Verify(LegalityAnalysis data)
|
||||
var pkm = data.pkm;
|
||||
if (pkm is not IContestStats s)
|
||||
return;
|
||||
|
||||
// If no stats have been increased from the initial amount, then we're done here.
|
||||
// some encounters have contest stats built in. they're already checked by the initial encounter match.
|
||||
if (!s.HasContestStats())
|
||||
return;
|
||||
|
||||
// Check the correlation of Stats & Sheen!
|
||||
// In generations 3,4 and BDSP, blocks/poffins have a feel(sheen) equal to sheen=sum(stats)/5, with +/- 10% for a favored stat.
|
||||
// In generation 6 (ORAS), they don't award any sheen, so any value is legal.
|
||||
|
||||
var correlation = GetContestStatRestriction(pkm, data.Info.Generation);
|
||||
if (correlation == None)
|
||||
{
|
||||
var pkm = data.pkm;
|
||||
if (pkm.Format <= 4)
|
||||
return; // legal || not present
|
||||
|
||||
if (pkm is IContestStats s && s.HasContestStats() && !CanHaveContestStats(pkm, s, data.Info.Generation))
|
||||
data.AddLine(GetInvalid(LegalityCheckStrings.LContestZero));
|
||||
|
||||
// some encounters have contest stats built in. they're already checked by the initial encounter match.
|
||||
// We're only here because we have contest stat values. We aren't permitted to have any, so flag it.
|
||||
data.AddLine(GetInvalid(LContestZero));
|
||||
}
|
||||
|
||||
private static bool CanHaveContestStats(PKM pkm, IContestStats s, int generation) => generation switch
|
||||
else if (correlation == NoSheen)
|
||||
{
|
||||
1 => false,
|
||||
2 => false,
|
||||
3 => true,
|
||||
4 => true,
|
||||
5 => s.CNT_Sheen == 0 && pkm.Format >= 6, // ORAS Contests
|
||||
6 => s.CNT_Sheen == 0 && (!pkm.IsUntraded || pkm.AO),
|
||||
8 => pkm.BDSP, // BDSP Contests
|
||||
_ => false,
|
||||
};
|
||||
// We can get contest stat values, but we can't get any for Sheen.
|
||||
// Any combination of non-sheen is ok, but nonzero sheen is illegal.
|
||||
if (s.CNT_Sheen != 0)
|
||||
data.AddLine(GetInvalid(LContestZeroSheen));
|
||||
}
|
||||
else if (correlation == CorrelateSheen)
|
||||
{
|
||||
bool gen3 = data.Info.Generation == 3;
|
||||
|
||||
// Check for stat values that exceed a valid sheen value.
|
||||
var initial = GetReferenceTemplate(data.Info.EncounterMatch);
|
||||
var minSheen = CalculateMinimumSheen(s, pkm.Nature, initial, gen3);
|
||||
if (s.CNT_Sheen < minSheen)
|
||||
data.AddLine(GetInvalid(string.Format(LContestSheenTooLow_0, minSheen)));
|
||||
|
||||
// Check for sheen values that are too high.
|
||||
var maxSheen = CalculateMaximumSheen(s, pkm.Nature, initial, gen3);
|
||||
if (s.CNT_Sheen > maxSheen)
|
||||
data.AddLine(GetInvalid(string.Format(LContestSheenTooHigh_0, maxSheen)));
|
||||
}
|
||||
else if (correlation == Mixed)
|
||||
{
|
||||
bool gen3 = data.Info.Generation == 3;
|
||||
|
||||
// Check for sheen values that are too high.
|
||||
var initial = GetReferenceTemplate(data.Info.EncounterMatch);
|
||||
var maxSheen = CalculateMaximumSheen(s, pkm.Nature, initial, gen3);
|
||||
if (s.CNT_Sheen > maxSheen)
|
||||
data.AddLine(GetInvalid(string.Format(LContestSheenTooHigh_0, maxSheen)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
13
PKHeX.Core/Legality/Verifiers/Misc/ContestStatGranting.cs
Normal file
13
PKHeX.Core/Legality/Verifiers/Misc/ContestStatGranting.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
public enum ContestStatGranting
|
||||
{
|
||||
/// <summary> Not possible to get any contest stats. </summary>
|
||||
None,
|
||||
/// <summary> Contest stats are possible to obtain, but must be correlated to sheen at most 1:1. </summary>
|
||||
CorrelateSheen,
|
||||
/// <summary> Contest stats are possible to obtain, but cannot obtain any sheen value. </summary>
|
||||
NoSheen,
|
||||
/// <summary> Contest stats are possible to obtain, and has visited a multitude of games such that any value of sheen is possible. </summary>
|
||||
Mixed,
|
||||
}
|
157
PKHeX.Core/Legality/Verifiers/Misc/ContestStatInfo.cs
Normal file
157
PKHeX.Core/Legality/Verifiers/Misc/ContestStatInfo.cs
Normal file
|
@ -0,0 +1,157 @@
|
|||
using System;
|
||||
using static PKHeX.Core.ContestStatGranting;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public static class ContestStatInfo
|
||||
{
|
||||
private const int WorstFeelBlock = 3;
|
||||
private const int WorstFeelPoffin = 17;
|
||||
private const int MaxContestStat = 255;
|
||||
|
||||
public static void SetSuggestedContestStats(PKM pk, IEncounterTemplate enc)
|
||||
{
|
||||
if (pk is not IContestStatsMutable s)
|
||||
return;
|
||||
|
||||
var restrict = GetContestStatRestriction(pk, pk.Generation);
|
||||
if (restrict == None)
|
||||
s.SetAllContestStatsTo(0, 0); // zero
|
||||
if (pk.Species is not (int)Species.Milotic)
|
||||
GetReferenceTemplate(enc).CopyContestStatsTo(s); // reset
|
||||
else
|
||||
s.SetAllContestStatsTo(MaxContestStat, restrict == NoSheen ? (byte)0 : (byte)255);
|
||||
}
|
||||
|
||||
public static ContestStatGranting GetContestStatRestriction(PKM pk, int origin) => origin switch
|
||||
{
|
||||
3 => pk.Format < 6 ? CorrelateSheen : Mixed,
|
||||
4 => pk.Format < 6 ? CorrelateSheen : Mixed,
|
||||
|
||||
5 => pk.Format >= 6 ? NoSheen : None, // ORAS Contests
|
||||
6 => pk.AO || !pk.IsUntraded ? NoSheen : None,
|
||||
8 => pk.BDSP ? CorrelateSheen : None, // BDSP Contests
|
||||
_ => None,
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
var avg = GetAverageFeel(s, nature, initial);
|
||||
if (avg <= 0)
|
||||
return initial.CNT_Sheen;
|
||||
|
||||
if (pokeBlock3)
|
||||
{
|
||||
var fudge = (avg * 225) / 100;
|
||||
return Math.Min(MaxContestStat, Math.Max(WorstFeelBlock, fudge));
|
||||
}
|
||||
|
||||
// Can get trash poffins by burning and spilling on purpose.
|
||||
return Math.Min(MaxContestStat, avg * WorstFeelPoffin);
|
||||
}
|
||||
|
||||
public static int CalculateMinimumSheen(IContestStats s, int nature, IContestStats initial, bool pokeBlock3)
|
||||
{
|
||||
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
|
||||
if (pokeBlock3)
|
||||
return Math.Min(MaxContestStat, Math.Max(WorstFeelBlock, avg));
|
||||
else
|
||||
return Math.Min(MaxContestStat, Math.Max(WorstFeelPoffin, avg));
|
||||
}
|
||||
|
||||
private static int GetAverageFeel(IContestStats s, int nature, IContestStats initial)
|
||||
{
|
||||
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);
|
||||
return sum / 5;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
private class DummyContestNone : IContestStats
|
||||
{
|
||||
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 =
|
||||
{
|
||||
// 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
|
||||
};
|
||||
}
|
|
@ -74,6 +74,9 @@ namespace PKHeX.Core
|
|||
return ot.Length <= len;
|
||||
}
|
||||
|
||||
if (e is EncounterTrade { HasTrainerName: true })
|
||||
return true; // already verified
|
||||
|
||||
if (e is MysteryGift mg && mg.OT_Name.Length == ot.Length)
|
||||
return true; // Mattle Ho-Oh
|
||||
return false;
|
||||
|
|
|
@ -73,6 +73,23 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
public static bool IsContestEqual(this IContestStats current, IContestStats initial)
|
||||
{
|
||||
if (current.CNT_Cool != initial.CNT_Cool)
|
||||
return false;
|
||||
if (current.CNT_Beauty != initial.CNT_Beauty)
|
||||
return false;
|
||||
if (current.CNT_Cute != initial.CNT_Cute)
|
||||
return false;
|
||||
if (current.CNT_Smart != initial.CNT_Smart)
|
||||
return false;
|
||||
if (current.CNT_Tough != initial.CNT_Tough)
|
||||
return false;
|
||||
if (current.CNT_Sheen != initial.CNT_Sheen)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void CopyContestStatsTo(this IContestStats source, IContestStatsMutable dest)
|
||||
{
|
||||
dest.CNT_Cool = source.CNT_Cool;
|
||||
|
@ -82,5 +99,28 @@
|
|||
dest.CNT_Tough = source.CNT_Tough;
|
||||
dest.CNT_Sheen = source.CNT_Sheen;
|
||||
}
|
||||
|
||||
public static void SetAllContestStatsTo(this IContestStatsMutable dest, byte value, byte sheen)
|
||||
{
|
||||
dest.CNT_Cool = value;
|
||||
dest.CNT_Beauty = value;
|
||||
dest.CNT_Cute = value;
|
||||
dest.CNT_Smart = value;
|
||||
dest.CNT_Tough = value;
|
||||
dest.CNT_Sheen = sheen;
|
||||
}
|
||||
|
||||
private const byte CONTEST_MAX = 255;
|
||||
|
||||
/// <summary>
|
||||
/// Check if any contest stat besides <see cref="IContestStats.CNT_Sheen"/> is equal to <see cref="CONTEST_MAX"/>.
|
||||
/// </summary>
|
||||
/// <param name="s">Entity to check</param>
|
||||
/// <returns>True if any equals <see cref="CONTEST_MAX"/></returns>
|
||||
public static bool IsAnyContestStatMax(this IContestStats s) => CONTEST_MAX == s.CNT_Cool
|
||||
|| CONTEST_MAX == s.CNT_Beauty
|
||||
|| CONTEST_MAX == s.CNT_Cute
|
||||
|| CONTEST_MAX == s.CNT_Smart
|
||||
|| CONTEST_MAX == s.CNT_Tough;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue