mirror of
https://github.com/kwsch/PKHeX
synced 2024-12-23 19:03:11 +00:00
768047cd80
* Rewrite ribbon verification * Explicitly verifies all ribbons instead of chained iterators. * Verifies using only the stack, using `struct` and `Span<T>`. No allocation on heap, or `IEnumerable` iterators. * Verifies all egg ribbons using a separate method, explicitly implemented. No reflection overhead. * Separates each ribbon interface to separate `static` classes. Easier to identify code needing change on new game update. * Extracted logic for specific ribbons. Can easily revise complicated ribbon's acquisition rules. * Simplifies GiveAll/RemoveAll legal ribbon mutations. No reflection overhead, and no allocation. * Can be expanded in the future if we need to track conditions for ribbon acquisition (was Sinnoh Champ received in BDSP or Gen4?) End result is a more performant implementation and easier to maintain & reuse logic.
183 lines
6.2 KiB
C#
183 lines
6.2 KiB
C#
using System;
|
|
using System.Text;
|
|
using static PKHeX.Core.LegalityCheckStrings;
|
|
|
|
namespace PKHeX.Core;
|
|
|
|
/// <summary>
|
|
/// Verifies the <see cref="PKM"/> Ribbon values.
|
|
/// </summary>
|
|
public sealed class RibbonVerifier : Verifier
|
|
{
|
|
protected override CheckIdentifier Identifier => CheckIdentifier.Ribbon;
|
|
|
|
/// <summary>
|
|
/// Maximum amount of ribbons to consider when allocating a span for parsing.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Minor optimization is to stackalloc as little as possible, without too much calculation.
|
|
/// <see cref="RibbonIndex3.MAX_COUNT"/> + <see cref="RibbonIndex4.MAX_COUNT"/> is 48, but are not present after Gen5.
|
|
/// <see cref="PK5"/> only has 80 ribbons implemented.
|
|
/// Instead of using the sum of all 3 enums, we can use <see cref="RibbonIndex.MAX_COUNT"/> as the true maximum count.
|
|
/// </remarks>
|
|
public const int MaxRibbonCount = (int)RibbonIndex.MAX_COUNT;
|
|
|
|
public override void Verify(LegalityAnalysis data)
|
|
{
|
|
// Flag VC (Gen1/2) ribbons using Gen7 origin rules.
|
|
var enc = data.EncounterMatch;
|
|
var pk = data.Entity;
|
|
|
|
// Check Unobtainable Ribbons
|
|
var args = new RibbonVerifierArguments(pk, enc, data.Info.EvoChainsAllGens);
|
|
Span<RibbonResult> result = stackalloc RibbonResult[MaxRibbonCount];
|
|
int count = GetRibbonResults(args, result);
|
|
if (count == 0)
|
|
{
|
|
data.AddLine(GetValid(LRibbonAllValid));
|
|
return;
|
|
}
|
|
|
|
var msg = GetMessage(result[..count]);
|
|
data.AddLine(GetInvalid(msg));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the <see cref="index"/> is not an invalid/missing ribbon in the result parse.
|
|
/// </summary>
|
|
/// <param name="index">Ribbon Index to check for</param>
|
|
/// <param name="args">Inputs to analyze</param>
|
|
/// <returns>True if not present in the flagged result span.</returns>
|
|
public static bool IsValidExtra(RibbonIndex index, RibbonVerifierArguments args)
|
|
{
|
|
Span<RibbonResult> result = stackalloc RibbonResult[MaxRibbonCount];
|
|
int count = GetRibbonResults(args, result);
|
|
if (count == 0)
|
|
return true;
|
|
|
|
var span = result[..count];
|
|
foreach (var x in span)
|
|
{
|
|
if (x.Equals(index))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uses the input <see cref="args"/> and stores results in the <see cref="result"/> span.
|
|
/// </summary>
|
|
/// <param name="args">Inputs to analyze</param>
|
|
/// <param name="result">Result storage</param>
|
|
/// <returns>Count of elements filled in the <see cref="result"/> span.</returns>
|
|
public static int GetRibbonResults(RibbonVerifierArguments args, Span<RibbonResult> result)
|
|
{
|
|
var list = new RibbonResultList(result);
|
|
return GetRibbonResults(args, ref list);
|
|
}
|
|
|
|
private static int GetRibbonResults(RibbonVerifierArguments args, ref RibbonResultList list)
|
|
{
|
|
if (!args.Entity.IsEgg)
|
|
Parse(args, ref list);
|
|
else
|
|
ParseEgg(args, ref list);
|
|
|
|
return list.Count;
|
|
}
|
|
|
|
private static string GetMessage(ReadOnlySpan<RibbonResult> result)
|
|
{
|
|
var total = result.Length;
|
|
int missing = GetCountMissing(result);
|
|
int invalid = total - missing;
|
|
var sb = new StringBuilder(total * 20);
|
|
if (missing != 0)
|
|
AppendAll(result, sb, LRibbonFMissing_0, true);
|
|
if (invalid != 0)
|
|
{
|
|
if (missing != 0) // need to visually separate the message
|
|
sb.Append(Environment.NewLine);
|
|
AppendAll(result, sb, LRibbonFInvalid_0, false);
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static int GetCountMissing(ReadOnlySpan<RibbonResult> result)
|
|
{
|
|
int count = 0;
|
|
foreach (var x in result)
|
|
{
|
|
if (x.IsMissing)
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
private const string MessageSplitNextRibbon = ", ";
|
|
|
|
private static void AppendAll(ReadOnlySpan<RibbonResult> result, StringBuilder sb, string startText, bool stateMissing)
|
|
{
|
|
int added = 0;
|
|
sb.Append(startText);
|
|
foreach (var x in result)
|
|
{
|
|
if (x.IsMissing != stateMissing)
|
|
continue;
|
|
if (added++ != 0)
|
|
sb.Append(MessageSplitNextRibbon);
|
|
var localized = RibbonStrings.GetName(x.PropertyName);
|
|
sb.Append(localized);
|
|
}
|
|
}
|
|
|
|
private static void Parse(RibbonVerifierArguments args, ref RibbonResultList list)
|
|
{
|
|
var pk = args.Entity;
|
|
if (pk is IRibbonSetOnly3 o3)
|
|
o3.Parse(args, ref list);
|
|
if (pk is IRibbonSetEvent3 e3)
|
|
e3.Parse(args, ref list);
|
|
if (pk is IRibbonSetEvent4 e4)
|
|
e4.Parse(args, ref list);
|
|
if (pk is IRibbonSetUnique3 u3)
|
|
u3.Parse(args, ref list);
|
|
if (pk is IRibbonSetCommon3 s3)
|
|
s3.Parse(args, ref list);
|
|
if (pk is IRibbonSetUnique4 u4)
|
|
u4.Parse(args, ref list);
|
|
if (pk is IRibbonSetCommon4 s4)
|
|
s4.Parse(args, ref list);
|
|
if (pk is IRibbonSetCommon6 s6)
|
|
s6.Parse(args, ref list);
|
|
if (pk is IRibbonSetCommon7 s7)
|
|
s7.Parse(args, ref list);
|
|
if (pk is IRibbonSetCommon8 s8)
|
|
s8.Parse(args, ref list);
|
|
}
|
|
|
|
private static void ParseEgg(RibbonVerifierArguments args, ref RibbonResultList list)
|
|
{
|
|
var pk = args.Entity;
|
|
if (pk is IRibbonSetOnly3 o3)
|
|
o3.ParseEgg(ref list);
|
|
if (pk is IRibbonSetEvent3 e3)
|
|
e3.ParseEgg(ref list);
|
|
if (pk is IRibbonSetEvent4 e4)
|
|
e4.ParseEgg(args, ref list); // Some event eggs can have ribbons!
|
|
if (pk is IRibbonSetUnique3 u3)
|
|
u3.ParseEgg(ref list);
|
|
if (pk is IRibbonSetCommon3 s3)
|
|
s3.ParseEgg(ref list);
|
|
if (pk is IRibbonSetUnique4 u4)
|
|
u4.ParseEgg(ref list);
|
|
if (pk is IRibbonSetCommon4 s4)
|
|
s4.ParseEgg(ref list);
|
|
if (pk is IRibbonSetCommon6 s6)
|
|
s6.ParseEgg(ref list);
|
|
if (pk is IRibbonSetCommon7 s7)
|
|
s7.ParseEgg(ref list);
|
|
if (pk is IRibbonSetCommon8 s8)
|
|
s8.ParseEgg(ref list);
|
|
}
|
|
}
|