using System; using System.Text; using static PKHeX.Core.LegalityCheckStrings; namespace PKHeX.Core; /// /// Verifies the Ribbon values. /// public sealed class RibbonVerifier : Verifier { protected override CheckIdentifier Identifier => CheckIdentifier.Ribbon; /// /// Maximum amount of ribbons to consider when allocating a span for parsing. /// /// /// Minor optimization is to stackalloc as little as possible, without too much calculation. /// + is 48, but are not present after Gen5. /// only has 80 ribbons implemented. /// Instead of using the sum of all 3 enums, we can use as the true maximum count. /// 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 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)); } /// /// Checks if the is not an invalid/missing ribbon in the result parse. /// /// Ribbon Index to check for /// Inputs to analyze /// True if not present in the flagged result span. public static bool IsValidExtra(RibbonIndex index, RibbonVerifierArguments args) { Span 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; } /// /// Uses the input and stores results in the span. /// /// Inputs to analyze /// Result storage /// Count of elements filled in the span. public static int GetRibbonResults(RibbonVerifierArguments args, Span 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 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 result) { int count = 0; foreach (var x in result) { if (x.IsMissing) count++; } return count; } private const string MessageSplitNextRibbon = ", "; private static void AppendAll(ReadOnlySpan 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); } }