From 768047cd80dad7a48818e1afbd1e4ce02c79b078 Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 15 Aug 2022 21:04:30 -0700 Subject: [PATCH] Legality: Rewrite Ribbon Verifier (#3570) * Rewrite ribbon verification * Explicitly verifies all ribbons instead of chained iterators. * Verifies using only the stack, using `struct` and `Span`. 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. --- .../Editing/Applicators/RibbonApplicator.cs | 235 +---- .../Bulk/Suggestion/BatchModifications.cs | 5 +- .../Formatting/LegalityCheckStrings.cs | 7 +- PKHeX.Core/Legality/Verifiers/MarkVerifier.cs | 7 +- .../Verifiers/Ribbons/RibbonIndex3.cs | 127 +++ .../Verifiers/Ribbons/RibbonIndex4.cs | 114 +++ .../Verifiers/Ribbons/RibbonResult.cs | 76 +- .../Verifiers/Ribbons/RibbonResultList.cs | 25 + .../Legality/Verifiers/Ribbons/RibbonRules.cs | 342 ++++++++ .../Verifiers/Ribbons/RibbonVerifier.cs | 807 +++--------------- .../Ribbons/RibbonVerifierArguments.cs | 10 + .../Ribbons/RibbonVerifierCommon3.cs | 31 + .../Ribbons/RibbonVerifierCommon4.cs | 70 ++ .../Ribbons/RibbonVerifierCommon6.cs | 190 +++++ .../Ribbons/RibbonVerifierCommon7.cs | 41 + .../Ribbons/RibbonVerifierCommon8.cs | 77 ++ .../Verifiers/Ribbons/RibbonVerifierEvent3.cs | 73 ++ .../Verifiers/Ribbons/RibbonVerifierEvent4.cs | 55 ++ .../Verifiers/Ribbons/RibbonVerifierOnly3.cs | 49 ++ .../Ribbons/RibbonVerifierUnique3.cs | 37 + .../Ribbons/RibbonVerifierUnique4.cs | 135 +++ .../checks/LegalityCheckStrings_de.txt | 4 +- .../checks/LegalityCheckStrings_en.txt | 4 +- .../checks/LegalityCheckStrings_es.txt | 4 +- .../checks/LegalityCheckStrings_fr.txt | 4 +- .../checks/LegalityCheckStrings_it.txt | 4 +- .../checks/LegalityCheckStrings_ja.txt | 4 +- .../checks/LegalityCheckStrings_ko.txt | 4 +- .../checks/LegalityCheckStrings_zh.txt | 4 +- PKHeX.Core/Ribbons/IRibbonSetCommon3.cs | 19 +- PKHeX.Core/Ribbons/IRibbonSetCommon4.cs | 59 +- PKHeX.Core/Ribbons/IRibbonSetCommon6.cs | 55 +- PKHeX.Core/Ribbons/IRibbonSetCommon7.cs | 21 +- PKHeX.Core/Ribbons/IRibbonSetCommon8.cs | 23 +- PKHeX.Core/Ribbons/IRibbonSetEvent3.cs | 23 +- PKHeX.Core/Ribbons/IRibbonSetEvent4.cs | 27 +- PKHeX.Core/Ribbons/IRibbonSetOnly3.cs | 17 +- PKHeX.Core/Ribbons/IRibbonSetUnique3.cs | 21 +- PKHeX.Core/Ribbons/IRibbonSetUnique4.cs | 140 --- PKHeX.Core/Ribbons/RibbonIndex.cs | 374 +++++++- 40 files changed, 2008 insertions(+), 1316 deletions(-) create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonIndex3.cs create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonIndex4.cs create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonResultList.cs create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonRules.cs create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierArguments.cs create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon3.cs create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon4.cs create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon6.cs create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon7.cs create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon8.cs create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierEvent3.cs create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierEvent4.cs create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierOnly3.cs create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierUnique3.cs create mode 100644 PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierUnique4.cs diff --git a/PKHeX.Core/Editing/Applicators/RibbonApplicator.cs b/PKHeX.Core/Editing/Applicators/RibbonApplicator.cs index 7b80b1b16..c9d21c46e 100644 --- a/PKHeX.Core/Editing/Applicators/RibbonApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/RibbonApplicator.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; namespace PKHeX.Core; @@ -8,238 +7,80 @@ namespace PKHeX.Core; /// public static class RibbonApplicator { - private static List GetAllRibbonNames(PKM pk) => RibbonInfo.GetRibbonInfo(pk).ConvertAll(z => z.Name); - - /// - /// Gets a list of valid ribbons for the . - /// - /// Entity to fetch the list for. - /// All ribbon names. - /// List of all valid ribbon names. - public static IReadOnlyList GetValidRibbons(PKM pk, IList allRibbons) - { - var clone = pk.Clone(); - return SetAllValidRibbons(allRibbons, clone); - } - - /// - /// Gets a list of valid ribbons for the . - /// - /// Entity to fetch the list for. - /// List of all valid ribbon names. - public static IReadOnlyList GetValidRibbons(PKM pk) - { - var names = GetAllRibbonNames(pk); - return GetValidRibbons(pk, names); - } - - /// - /// Gets a list of valid ribbons for the that can be removed. - /// - /// Entity to fetch the list for. - /// All ribbon names. - /// List of all removable ribbon names. - public static IReadOnlyList GetRemovableRibbons(PKM pk, IList allRibbons) - { - var clone = pk.Clone(); - return RemoveAllValidRibbons(allRibbons, clone); - } - - /// - /// Gets a list of valid ribbons for the that can be removed. - /// - /// Entity to fetch the list for. - /// List of all removable ribbon names. - public static IReadOnlyList GetRemovableRibbons(PKM pk) - { - var names = GetAllRibbonNames(pk); - return GetRemovableRibbons(pk, names); - } - /// /// Sets all valid ribbons to the . /// /// Entity to set ribbons for. - /// True if any ribbons were applied. - public static bool SetAllValidRibbons(PKM pk) - { - var ribNames = GetAllRibbonNames(pk); - ribNames.RemoveAll(z => z.StartsWith("RibbonMark", StringComparison.Ordinal)); // until marking legality is handled - return SetAllValidRibbons(pk, ribNames); - } + public static void SetAllValidRibbons(PKM pk) => SetAllValidRibbons(new LegalityAnalysis(pk)); - /// - /// Sets all valid ribbons to the . - /// - /// Entity to set ribbons for. - /// Ribbon names to try setting. - /// True if any ribbons were applied. - public static bool SetAllValidRibbons(PKM pk, List ribNames) + /// + public static void SetAllValidRibbons(LegalityAnalysis la) { - var list = SetAllValidRibbons(ribNames, pk); - return list.Count != 0; - } - - private static IReadOnlyList SetAllValidRibbons(IList allRibbons, PKM pk) - { - var la = new LegalityAnalysis(pk); - var valid = new List(); - - while (TryApplyAllRibbons(pk, la, allRibbons, valid) != 0) - { - // Repeat the operation until no more ribbons are set. - } + var args = new RibbonVerifierArguments(la.Entity, la.EncounterMatch, la.Info.EvoChainsAllGens); + SetAllRibbonState(args, true); + FixInvalidRibbons(args); // Ribbon Deadlock - if (pk is IRibbonSetCommon6 c6) - InvertDeadlockContest(c6, la, true); - - return valid; + if (la.Entity is IRibbonSetCommon6 c6) + InvertDeadlockContest(c6, true, args); } /// /// Sets all valid ribbons to the . /// /// Entity to set ribbons for. - /// True if any ribbons were removed. - public static bool RemoveAllValidRibbons(PKM pk) + public static void RemoveAllValidRibbons(PKM pk) => RemoveAllValidRibbons(new LegalityAnalysis(pk)); + + /// + public static void RemoveAllValidRibbons(LegalityAnalysis la) { - var ribNames = GetAllRibbonNames(pk); - return RemoveAllValidRibbons(pk, ribNames); + var args = new RibbonVerifierArguments(la.Entity, la.EncounterMatch, la.Info.EvoChainsAllGens); + SetAllRibbonState(args, false); + FixInvalidRibbons(args); } /// - /// Sets all valid ribbons to the . + /// Parses the Entity for all ribbons, then fixes any ribbon that was invalid. /// - /// Entity to set ribbons for. - /// Ribbon names to try setting. - /// True if any ribbons were removed. - public static bool RemoveAllValidRibbons(PKM pk, List ribNames) + public static void FixInvalidRibbons(RibbonVerifierArguments args) { - var list = RemoveAllValidRibbons(ribNames, pk); - return list.Count != 0; + Span result = stackalloc RibbonResult[RibbonVerifier.MaxRibbonCount]; + var count = RibbonVerifier.GetRibbonResults(args, result); + foreach (var ribbon in result[..count]) + ribbon.Fix(args); } - private static IReadOnlyList RemoveAllValidRibbons(IList allRibbons, PKM pk) + private static void SetAllRibbonState(RibbonVerifierArguments args, bool desiredState) { - var la = new LegalityAnalysis(pk); - var valid = new List(); + for (RibbonIndex3 r = 0; r < RibbonIndex3.MAX_COUNT; r++) + r.Fix(args, desiredState); + for (RibbonIndex4 r = 0; r < RibbonIndex4.MAX_COUNT; r++) + r.Fix(args, desiredState); - // Ribbon Deadlock - if (pk is IRibbonSetCommon6 c6) - InvertDeadlockContest(c6, la, false); - - while (TryRemoveAllRibbons(pk, la, allRibbons, valid) != 0) + if (desiredState) { - // Repeat the operation until no more ribbons are set. + // Skip Marks, don't set them. + for (RibbonIndex r = 0; r <= RibbonIndex.MasterRank; r++) + r.Fix(args, desiredState); + for (RibbonIndex r = RibbonIndex.Pioneer; r < RibbonIndex.MAX_COUNT; r++) + r.Fix(args, desiredState); } - - return valid; - } - - private static int TryApplyAllRibbons(PKM pk, LegalityAnalysis la, IList allRibbons, ICollection valid) - { - int applied = 0; - for (int i = 0; i < allRibbons.Count;) + else { - la.ResetParse(); - var rib = allRibbons[i]; - var success = TryApplyRibbon(pk, la, rib); - if (success) - { - ++applied; - allRibbons.RemoveAt(i); - valid.Add(rib); - } - else - { - RemoveRibbon(pk, rib); - ++i; - } - } - - return applied; - } - - private static int TryRemoveAllRibbons(PKM pk, LegalityAnalysis la, IList allRibbons, ICollection valid) - { - int removed = 0; - for (int i = 0; i < allRibbons.Count;) - { - la.ResetParse(); - var rib = allRibbons[i]; - var success = TryRemoveRibbon(pk, la, rib); - if (success) - { - ++removed; - allRibbons.RemoveAt(i); - valid.Add(rib); - } - else - { - SetRibbonValue(pk, rib, 1); - ++i; - } - } - - return removed; - } - - private static void RemoveRibbon(PKM pk, string rib) => SetRibbonValue(pk, rib, 0); - - private static bool TryRemoveRibbon(PKM pk, LegalityAnalysis la, string rib) - { - RemoveRibbon(pk, rib); - return UpdateIsValid(la); - } - - private static bool TryApplyRibbon(PKM pk, LegalityAnalysis la, string rib) - { - SetRibbonValue(pk, rib, 1); - return UpdateIsValid(la); - } - - private static bool UpdateIsValid(LegalityAnalysis la) - { - LegalityAnalyzers.Ribbon.Verify(la); - foreach (var p in la.Results) - { - if (!p.Valid) - return false; - } - return true; - } - - private static void SetRibbonValue(PKM pk, string rib, int value) - { - switch (rib) - { - case nameof(PK7.RibbonCountMemoryBattle): - ReflectUtil.SetValue(pk, rib, value * (pk.Gen4 ? 6 : 8)); - break; - case nameof(PK7.RibbonCountMemoryContest): - ReflectUtil.SetValue(pk, rib, value * (pk.Gen4 ? 20 : 40)); - break; - default: - if (rib.StartsWith("RibbonCountG3", StringComparison.Ordinal)) - ReflectUtil.SetValue(pk, rib, value * 4); - else - ReflectUtil.SetValue(pk, rib, value != 0); - break; + // Remove Marks too. + for (RibbonIndex r = 0; r < RibbonIndex.MAX_COUNT; r++) + r.Fix(args, desiredState); } } - private static void InvertDeadlockContest(IRibbonSetCommon6 c6, LegalityAnalysis la, bool desiredState) + private static void InvertDeadlockContest(IRibbonSetCommon6 c6, bool desiredState, RibbonVerifierArguments args) { // RibbonContestStar depends on having all contest ribbons, and having RibbonContestStar requires all. // Since the above logic sets individual ribbons, we must try setting this deadlock pair manually. if (c6.RibbonMasterToughness == desiredState || c6.RibbonContestStar == desiredState) return; - la.ResetParse(); c6.RibbonMasterToughness = c6.RibbonContestStar = desiredState; - bool result = UpdateIsValid(la); - if (!result) - c6.RibbonMasterToughness = c6.RibbonContestStar = !desiredState; + FixInvalidRibbons(args); } } diff --git a/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs b/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs index c35feee37..bf93b5cc1 100644 --- a/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs +++ b/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs @@ -54,11 +54,10 @@ public static ModifyResult SetSuggestedMasteryData(BatchInfo info, string propVa public static ModifyResult SetSuggestedRibbons(BatchInfo info, string value) { - var pk = info.Entity; if (IsNone(value)) - RibbonApplicator.RemoveAllValidRibbons(pk); + RibbonApplicator.RemoveAllValidRibbons(info.Legality); else // All - RibbonApplicator.SetAllValidRibbons(pk); + RibbonApplicator.SetAllValidRibbons(info.Legality); return ModifyResult.Modified; } diff --git a/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs b/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs index 0951b0d55..d74e0b756 100644 --- a/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs +++ b/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs @@ -40,9 +40,6 @@ public static class LegalityCheckStrings /// Format text for exporting the that was matched for the public static string L_FPIDType_0 { get; set; } = "PID Type: {0}"; - /// Severity string for - public static string L_SIndeterminate { get; set; } = "Indeterminate"; - /// Severity string for public static string L_SInvalid { get; set; } = "Invalid"; @@ -436,8 +433,8 @@ public static class LegalityCheckStrings public static string LRibbonAllValid { get; set; } = "All ribbons accounted for."; public static string LRibbonEgg { get; set; } = "Can't receive Ribbon(s) as an Egg."; - public static string LRibbonFInvalid_0 { get; set; } = "Invalid Ribbons: {0}"; - public static string LRibbonFMissing_0 { get; set; } = "Missing Ribbons: {0}"; + public static string LRibbonFInvalid_0 { get; set; } = "Invalid Ribbons: "; + public static string LRibbonFMissing_0 { get; set; } = "Missing Ribbons: "; public static string LRibbonMarkingFInvalid_0 { get; set; } = "Invalid Marking: {0}"; public static string LRibbonMarkingAffixedF_0 { get; set; } = "Invalid Affixed Ribbon/Marking: {0}"; diff --git a/PKHeX.Core/Legality/Verifiers/MarkVerifier.cs b/PKHeX.Core/Legality/Verifiers/MarkVerifier.cs index fea140e95..c8bf3bcc4 100644 --- a/PKHeX.Core/Legality/Verifiers/MarkVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/MarkVerifier.cs @@ -1,4 +1,3 @@ -using System.Linq; using static PKHeX.Core.LegalityCheckStrings; namespace PKHeX.Core; @@ -182,10 +181,10 @@ private void VerifyShedinjaAffixed(LegalityAnalysis data, sbyte affix, PKM pk, I var clone = pk.Clone(); clone.Species = (int) Species.Nincada; - ((IRibbonIndex) clone).SetRibbon(affix); - var parse = RibbonVerifier.GetRibbonResults(clone, data.Info.EvoChainsAllGens, enc); + var args = new RibbonVerifierArguments(clone, enc, data.Info.EvoChainsAllGens); + ((RibbonIndex)affix).Fix(args, true); var name = GetRibbonNameSafe((RibbonIndex)affix); - bool invalid = parse.FirstOrDefault(z => z.Name == name)?.Invalid == true; + bool invalid = RibbonVerifier.IsValidExtra((RibbonIndex)affix, args); var severity = invalid ? Severity.Invalid : Severity.Fishy; data.AddLine(Get(string.Format(LRibbonMarkingAffixedF_0, name), severity)); } diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonIndex3.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonIndex3.cs new file mode 100644 index 000000000..a6bad8c46 --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonIndex3.cs @@ -0,0 +1,127 @@ +using System; +using static PKHeX.Core.RibbonIndex3; + +namespace PKHeX.Core; + +/// +/// Ribbons from Generation 3 that were not carried forward to future formats. +/// +public enum RibbonIndex3 : byte +{ + // Battle: Gen3 + Winning, + Victory, + + // Contest: Gen3 + Cool, + CoolSuper, + CoolHyper, + CoolMaster, + Beauty, + BeautySuper, + BeautyHyper, + BeautyMaster, + Cute, + CuteSuper, + CuteHyper, + CuteMaster, + Smart, + SmartSuper, + SmartHyper, + SmartMaster, + Tough, + ToughSuper, + ToughHyper, + ToughMaster, + + MAX_COUNT, +} + +public static class RibbonIndex3Extensions +{ + public static void Fix(this RibbonIndex3 r, RibbonVerifierArguments args, bool state) + { + var pk = args.Entity; + if (r is Victory or Winning) + { + if (pk is not IRibbonSetUnique3 u3) + return; + if (r is Victory) + u3.RibbonVictory = state; + else + u3.RibbonWinning = state; + return; + } + + if (pk is IRibbonSetOnly3 o3) + { + var value = state ? 4 : 0; + if (r is Cool) + o3.RibbonCountG3Cool = value; + else if (r is Beauty) + o3.RibbonCountG3Beauty = value; + else if (r is Cute) + o3.RibbonCountG3Cute = value; + else if (r is Smart) + o3.RibbonCountG3Smart = value; + else if (r is Tough) + o3.RibbonCountG3Tough = value; + return; + } + + if (pk is not IRibbonSetUnique4 u4) + return; + + _ = r switch + { + Cool => u4.RibbonG3Cool = state, + CoolSuper => u4.RibbonG3CoolSuper = state, + CoolHyper => u4.RibbonG3CoolHyper = state, + CoolMaster => u4.RibbonG3CoolMaster = state, + Beauty => u4.RibbonG3Beauty = state, + BeautySuper => u4.RibbonG3BeautySuper = state, + BeautyHyper => u4.RibbonG3BeautyHyper = state, + BeautyMaster => u4.RibbonG3BeautyMaster = state, + Cute => u4.RibbonG3Cute = state, + CuteSuper => u4.RibbonG3CuteSuper = state, + CuteHyper => u4.RibbonG3CuteHyper = state, + CuteMaster => u4.RibbonG3CuteMaster = state, + Smart => u4.RibbonG3Smart = state, + SmartSuper => u4.RibbonG3SmartSuper = state, + SmartHyper => u4.RibbonG3SmartHyper = state, + SmartMaster => u4.RibbonG3SmartMaster = state, + Tough => u4.RibbonG3Tough = state, + ToughSuper => u4.RibbonG3ToughSuper = state, + ToughHyper => u4.RibbonG3ToughHyper = state, + ToughMaster => u4.RibbonG3ToughMaster = state, + _ => throw new ArgumentOutOfRangeException(nameof(r), r, null), + }; + } + + public static string GetPropertyName(this RibbonIndex3 r) => r switch + { + Winning => nameof(IRibbonSetUnique3.RibbonWinning), + Victory => nameof(IRibbonSetUnique3.RibbonVictory), + Cool => nameof(IRibbonSetUnique4.RibbonG3Cool), + CoolSuper => nameof(IRibbonSetUnique4.RibbonG3CoolSuper), + CoolHyper => nameof(IRibbonSetUnique4.RibbonG3CoolHyper), + CoolMaster => nameof(IRibbonSetUnique4.RibbonG3CoolMaster), + Beauty => nameof(IRibbonSetUnique4.RibbonG3Beauty), + BeautySuper => nameof(IRibbonSetUnique4.RibbonG3BeautySuper), + BeautyHyper => nameof(IRibbonSetUnique4.RibbonG3BeautyHyper), + BeautyMaster => nameof(IRibbonSetUnique4.RibbonG3BeautyMaster), + Cute => nameof(IRibbonSetUnique4.RibbonG3Cute), + CuteSuper => nameof(IRibbonSetUnique4.RibbonG3CuteSuper), + CuteHyper => nameof(IRibbonSetUnique4.RibbonG3CuteHyper), + CuteMaster => nameof(IRibbonSetUnique4.RibbonG3CuteMaster), + Smart => nameof(IRibbonSetUnique4.RibbonG3Smart), + SmartSuper => nameof(IRibbonSetUnique4.RibbonG3SmartSuper), + SmartHyper => nameof(IRibbonSetUnique4.RibbonG3SmartHyper), + SmartMaster => nameof(IRibbonSetUnique4.RibbonG3SmartMaster), + Tough => nameof(IRibbonSetUnique4.RibbonG3Tough), + ToughSuper => nameof(IRibbonSetUnique4.RibbonG3ToughSuper), + ToughHyper => nameof(IRibbonSetUnique4.RibbonG3ToughHyper), + ToughMaster => nameof(IRibbonSetUnique4.RibbonG3ToughMaster), + _ => throw new ArgumentOutOfRangeException(nameof(r), r, null), + }; +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonIndex4.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonIndex4.cs new file mode 100644 index 000000000..130818dc6 --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonIndex4.cs @@ -0,0 +1,114 @@ +using System; +using static PKHeX.Core.RibbonIndex4; + +namespace PKHeX.Core; + +/// +/// Ribbons from Generation 4 that were not carried forward to future formats. +/// +public enum RibbonIndex4 : byte +{ + // Battle: Gen4 + Ability, + AbilityGreat, + AbilityDouble, + AbilityMulti, + AbilityPair, + AbilityWorld, + + // Contest: Gen4 + Cool, + CoolGreat, + CoolUltra, + CoolMaster, + Beauty, + BeautyGreat, + BeautyUltra, + BeautyMaster, + Cute, + CuteGreat, + CuteUltra, + CuteMaster, + Smart, + SmartGreat, + SmartUltra, + SmartMaster, + Tough, + ToughGreat, + ToughUltra, + ToughMaster, + + MAX_COUNT, +} + +public static class RibbonIndex4Extensions +{ + public static void Fix(this RibbonIndex4 r, RibbonVerifierArguments args, bool state) + { + var pk = args.Entity; + if (pk is not IRibbonSetUnique4 u4) + return; + + _ = r switch + { + RibbonIndex4.Ability => u4.RibbonAbility = state, + AbilityGreat => u4.RibbonAbilityGreat = state, + AbilityDouble => u4.RibbonAbilityDouble = state, + AbilityMulti => u4.RibbonAbilityMulti = state, + AbilityPair => u4.RibbonAbilityPair = state, + AbilityWorld => u4.RibbonAbilityWorld = state, + Cool => u4.RibbonG4Cool = state, + CoolGreat => u4.RibbonG4CoolGreat = state, + CoolUltra => u4.RibbonG4CoolUltra = state, + CoolMaster => u4.RibbonG4CoolMaster = state, + Beauty => u4.RibbonG4Beauty = state, + BeautyGreat => u4.RibbonG4BeautyGreat = state, + BeautyUltra => u4.RibbonG4BeautyUltra = state, + BeautyMaster => u4.RibbonG4BeautyMaster = state, + Cute => u4.RibbonG4Cute = state, + CuteGreat => u4.RibbonG4CuteGreat = state, + CuteUltra => u4.RibbonG4CuteUltra = state, + CuteMaster => u4.RibbonG4CuteMaster = state, + Smart => u4.RibbonG4Smart = state, + SmartGreat => u4.RibbonG4SmartGreat = state, + SmartUltra => u4.RibbonG4SmartUltra = state, + SmartMaster => u4.RibbonG4SmartMaster = state, + Tough => u4.RibbonG4Tough = state, + ToughGreat => u4.RibbonG4ToughGreat = state, + ToughUltra => u4.RibbonG4ToughUltra = state, + ToughMaster => u4.RibbonG4ToughMaster = state, + _ => throw new ArgumentOutOfRangeException(nameof(r), r, null) + }; + } + + public static string GetPropertyName(this RibbonIndex4 r) => r switch + { + RibbonIndex4.Ability => nameof(IRibbonSetUnique4.RibbonAbility), + AbilityGreat => nameof(IRibbonSetUnique4.RibbonAbilityGreat), + AbilityDouble => nameof(IRibbonSetUnique4.RibbonAbilityDouble), + AbilityMulti => nameof(IRibbonSetUnique4.RibbonAbilityMulti), + AbilityPair => nameof(IRibbonSetUnique4.RibbonAbilityPair), + AbilityWorld => nameof(IRibbonSetUnique4.RibbonAbilityWorld), + Cool => nameof(IRibbonSetUnique4.RibbonG4Cool), + CoolGreat => nameof(IRibbonSetUnique4.RibbonG4CoolGreat), + CoolUltra => nameof(IRibbonSetUnique4.RibbonG4CoolUltra), + CoolMaster => nameof(IRibbonSetUnique4.RibbonG4CoolMaster), + Beauty => nameof(IRibbonSetUnique4.RibbonG4Beauty), + BeautyGreat => nameof(IRibbonSetUnique4.RibbonG4BeautyGreat), + BeautyUltra => nameof(IRibbonSetUnique4.RibbonG4BeautyUltra), + BeautyMaster => nameof(IRibbonSetUnique4.RibbonG4BeautyMaster), + Cute => nameof(IRibbonSetUnique4.RibbonG4Cute), + CuteGreat => nameof(IRibbonSetUnique4.RibbonG4CuteGreat), + CuteUltra => nameof(IRibbonSetUnique4.RibbonG4CuteUltra), + CuteMaster => nameof(IRibbonSetUnique4.RibbonG4CuteMaster), + Smart => nameof(IRibbonSetUnique4.RibbonG4Smart), + SmartGreat => nameof(IRibbonSetUnique4.RibbonG4SmartGreat), + SmartUltra => nameof(IRibbonSetUnique4.RibbonG4SmartUltra), + SmartMaster => nameof(IRibbonSetUnique4.RibbonG4SmartMaster), + Tough => nameof(IRibbonSetUnique4.RibbonG4Tough), + ToughGreat => nameof(IRibbonSetUnique4.RibbonG4ToughGreat), + ToughUltra => nameof(IRibbonSetUnique4.RibbonG4ToughUltra), + ToughMaster => nameof(IRibbonSetUnique4.RibbonG4ToughMaster), + _ => throw new ArgumentOutOfRangeException(nameof(r), r, null), + }; +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonResult.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonResult.cs index 6fb085927..3c408ea93 100644 --- a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonResult.cs +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonResult.cs @@ -1,28 +1,64 @@ -namespace PKHeX.Core; +using System; +using static PKHeX.Core.RibbonParseFlags; -/// -/// Legality Check Parse object containing information about a single ribbon. -/// -internal sealed class RibbonResult +namespace PKHeX.Core; + +public readonly record struct RibbonResult { - /// Ribbon Display Name - public string Name { get; private set; } - - /// Ribbon should not be present. - /// If this is false, the Ribbon is missing. - public bool Invalid { get; } - - public RibbonResult(string prop, bool invalid = true) - { - Name = RibbonStrings.GetName(prop); - Invalid = invalid; - } + private readonly byte Value; + private readonly RibbonParseFlags Flags; /// - /// Merges the result name with another provided result. + /// True: ribbon is missing -- should have the ribbon. + /// False: ribbon is invalid -- should not have the ribbon. /// - public void Combine(RibbonResult other) + public bool IsMissing => (Flags & Missing) != 0; + + private RibbonParseFlags Type => Flags & ~Missing; + + public RibbonResult(RibbonIndex index, bool missing = false) { Value = (byte)index; Flags = missing ? MissingM : Mainline; } + public RibbonResult(RibbonIndex3 index, bool missing = false) { Value = (byte)index; Flags = missing ? Missing3 : Index3; } + public RibbonResult(RibbonIndex4 index, bool missing = false) { Value = (byte)index; Flags = missing ? Missing4 : Index4; } + public bool Equals(RibbonIndex index) => Type == Mainline && Value == (byte)index; + public bool Equals(RibbonIndex3 index) => Type == Index3 && Value == (byte)index; + public bool Equals(RibbonIndex4 index) => Type == Index4 && Value == (byte)index; + + /// + /// Property Name that the ribbon can be get/set with, or looked up for localization. + /// + public string PropertyName => Type switch { - Name += $" / {other.Name}"; + Mainline => ((RibbonIndex)Value).GetPropertyName(), + Index3 => ((RibbonIndex3)Value).GetPropertyName(), + Index4 => ((RibbonIndex4)Value).GetPropertyName(), + _ => throw new ArgumentOutOfRangeException(), + }; + + /// + /// Updates the ribbon state depending on the and state. + /// + public void Fix(RibbonVerifierArguments args) + { + switch (Type) + { + case Mainline: ((RibbonIndex)Value).Fix(args, IsMissing); break; + case Index3: ((RibbonIndex3)Value).Fix(args, IsMissing); break; + case Index4: ((RibbonIndex4)Value).Fix(args, IsMissing); break; + default: throw new ArgumentOutOfRangeException(); + } } } + +[Flags] +public enum RibbonParseFlags : byte +{ + None = 0, + Mainline, + Index3, + Index4, + + Missing = 0x80, + MissingM = Missing | Mainline, + Missing3 = Missing | Index3, + Missing4 = Missing | Index4, +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonResultList.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonResultList.cs new file mode 100644 index 000000000..4f98907b1 --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonResultList.cs @@ -0,0 +1,25 @@ +using System; + +namespace PKHeX.Core; + +public ref struct RibbonResultList +{ + private readonly Span Span; + public int Count { get; private set; } + + public RibbonResultList(Span span) + { + Span = span; + Count = 0; + } + + private void Add(RibbonResult item) + { + Span[Count] = item; + ++Count; + } + + public void Add(RibbonIndex index, bool missing = false) => Add(new(index, missing)); + public void Add(RibbonIndex3 index, bool missing = false) => Add(new(index, missing)); + public void Add(RibbonIndex4 index, bool missing = false) => Add(new(index, missing)); +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonRules.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonRules.cs new file mode 100644 index 000000000..7c538e703 --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonRules.cs @@ -0,0 +1,342 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Rules for obtaining ribbons. +/// +public static class RibbonRules +{ + /// + /// Checks if the input can receive the ribbon. + /// + public static bool IsRibbonValidAlolaChamp(IRibbonSetCommon7 s7, IEncounterTemplate enc, bool inhabited7) + { + // If the encounter comes with the ribbon, it must have the ribbon. + if (enc is IRibbonSetCommon7 { RibbonChampionAlola: true }) + return s7.RibbonChampionAlola; + // If it has visited, it can be either state. + if (inhabited7) + return true; + // If it has not visited, it must not have it. + return !s7.RibbonChampionAlola; + } + + /// + /// Checks if the input can receive the ribbon. + /// + public static bool IsRibbonValidEffort(PKM pk, EvolutionHistory evos, int gen) => gen switch + { + 5 when pk.Format == 5 => false, // Not available in BW/B2W2 + 8 when !evos.HasVisitedSWSH && !evos.HasVisitedBDSP => false, // not available in PLA + _ => true, + }; + + /// + /// Checks if the input can receive the ribbon. + /// + public static bool IsRibbonValidBestFriends(PKM pk, EvolutionHistory evos, int gen) => gen switch + { + < 7 when pk is { IsUntraded: true } and IAffection { OT_Affection: < 255 } => false, // Gen6/7 uses affection. Can't lower it on OT! + 8 when !evos.HasVisitedSWSH && !evos.HasVisitedBDSP => false, // Gen8+ replaced with Max Friendship. + _ => true, + }; + + /// + /// Checks if the input can receive the ribbon. + /// + public static bool IsRibbonValidFootprint(PKM pk, EvolutionHistory evos) + { + // Gen3/4: Friendship maxed. Can decrease after obtaining ribbon, no check needed. + if (evos.HasVisitedGen3 || evos.HasVisitedGen4) + return true; + + // Gen5: Can't obtain + if (pk.Format < 6) + return false; + + // Gen6/7: Increase level by 30 from original level + static bool IsWellTraveled30(PKM pk) => pk.CurrentLevel - pk.Met_Level >= 30; + if ((evos.HasVisitedGen6 || evos.HasVisitedGen7) && IsWellTraveled30(pk)) + return true; + + // Gen8-BDSP: Variable by species Footprint + if (evos.HasVisitedBDSP) + { + if (IsAnyWithoutFootprint8b(evos.Gen8b)) + return true; // no footprint + if (IsWellTraveled30(pk)) + return true; // traveled well + } + + // Otherwise: Can't obtain + return false; + } + + /// + /// Checks if the entity participated in SW/SH ranked battles for the ribbon. + /// + public static bool IsRibbonValidMasterRankSWSH(PKM pk, IEncounterTemplate enc, EvolutionHistory evos) + { + if (!evos.HasVisitedSWSH) + return false; + + if (enc.Generation < 8 && pk is IBattleVersion { BattleVersion: 0 }) + return false; + + // GO transfers: Capture date is global time, and not console changeable. + bool hasRealDate = enc.Version == GameVersion.GO || enc is IEncounterServerDate { IsDateRestricted: true }; + if (hasRealDate) + { + var met = pk.MetDate; + if (met > new DateTime(2022, 11, 1)) // Series 13 end date +1 day (wiggle room) + return false; // Ranked is done! + } + + // Series 13 rule-set was the first time Ranked Battles allowed the use of Mythical Pokémon. + // All species that can exist in SW/SH can compete in ranked. + return true; + } + + /// + /// Checks if the input can receive the ribbon. + /// + public static bool IsRibbonValidSuperTraining(ISuperTrain pk) + { + // It is assumed that the entity existed in the Gen6 game to receive the ribbon. + // We only enter this method if the entity implements the interface. + const int req = 12; // only first 12 are required to get the ribbon. + int count = pk.SuperTrainingMedalCount(req); + return count >= req; + } + + /// + /// Checks if the entity participated in battles for the ribbon. + /// + public static bool IsRibbonValidTowerMaster(EvolutionHistory evos) + { + if (evos.HasVisitedSWSH) + return true; // Anything in SW/SH can be used in battle tower. + + if (!evos.HasVisitedBDSP) + return false; + // Mythicals cannot be used in BD/SP's Battle Tower + return !Legal.Mythicals.Contains(evos.Gen8b[0].Species); + } + + /// + /// Checks if the input can receive the ribbon. + /// + public static bool IsRibbonValidWinning(PKM pk, IEncounterTemplate enc, EvolutionHistory evos) + { + if (!evos.HasVisitedGen3) + return false; + if (!IsAllowedBattleFrontier(evos.Gen3[0].Species)) + return false; + + // Can only obtain if the current level on receiving the ribbon is <= level 50. + if (pk.Format == 3) // Stored value is not yet overwritten (G3->G4), check directly. + return pk.Met_Level <= 50; + + // Most encounter types can be below level 50; only Shadow Dragonite & Tyranitar, and select Gen3 Event Gifts. + // These edge cases can't be obtained below level 50, unlike some wild Pokémon which can be encountered at different locations for lower levels. + if (enc.LevelMin <= 50) + return true; + + return enc is not (EncounterStaticShadow or WC3); + } + + /// + /// Checks if the input can receive the ribbon. + /// + public static bool IsRibbonValidVictory(EvolutionHistory evos) + { + if (evos.HasVisitedGen3) + return IsAllowedBattleFrontier(evos.Gen3[0].Species); + return false; + } + + /// + /// Checks if the input can receive the ribbon. + /// + public static bool IsRibbonValidTwinklingStar(EvolutionHistory evos, PKM pk) + { + // Can currently only obtain from BD/SP. + if (!evos.HasVisitedBDSP) + return false; + + // Can only obtain if it has already completed all the other contests and received the summation ribbon. + if (pk is IRibbonSetCommon6 { RibbonContestStar: false }) + return false; + + return true; + } + + /// + /// Checks if any of the species it existed as in BD/SP lacked footprints. + /// + private static bool IsAnyWithoutFootprint8b(EvoCriteria[] evos) + { + var arr = HasFootprintBDSP; + foreach (var evo in evos) + { + var species = evo.Species; + if ((uint)species >= arr.Length) + continue; + if (!arr[species]) + return true; + } + return false; + } + + // Derived from ROM data: true for all Footprint types besides 5 (5 = no feet). + // If true, requires gaining 30 levels to obtain ribbon. If false, can obtain ribbon at any level. + private static readonly bool[] HasFootprintBDSP = + { + true, true, true, true, true, true, true, true, true, true, + true, false, true, true, false, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, false, false, true, false, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, false, false, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + false, false, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, false, true, true, + false, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, false, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, false, true, true, false, false, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, false, true, true, true, true, true, true, + true, true, true, false, true, true, true, true, true, true, + true, true, true, true, true, true, true, false, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, false, true, false, true, + true, true, true, false, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + false, true, true, true, true, true, true, true, true, false, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, false, false, true, + true, true, true, false, false, false, false, false, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, false, true, false, false, true, false, false, false, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, false, false, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, false, true, true, true, true, true, true, true, + true, true, true, true, false, true, false, true, true, true, + true, true, true, true, true, true, false, true, true, true, + true, true, true, true, + }; + + /// + /// Checks if the input can receive the ribbon. + /// + /// + /// If returns true, must have the ribbon. If returns false, must not have the ribbon. + /// + public static bool GetValidRibbonStateNational(PKM pk, IEncounterTemplate enc) + { + // Can only obtain from Generation 3 Shadow Pokémon + if (enc.Generation != 3) + return false; + + if (enc is not EncounterStaticShadow) + return false; + + // Ribbon is awarded when the Pokémon is purified in the game of origin. + if (pk is IShadowPKM { IsShadow: true }) + return false; + + return true; + } + + /// + /// Gets the max count values the input can receive for the and ribbon counts. + /// + public static (byte Contest, byte Battle) GetMaxMemoryCounts(EvolutionHistory evos, PKM pk, IEncounterTemplate enc) + { + // Contest: 20 in both Generations. + const byte MaxContest4 = 20; + const byte MaxContest3 = 20; + const byte MaxContestBoth = MaxContest3 + MaxContest4; // 40 + + // Battle: 2 in Gen3, 6 in Gen4; one (Winning) in Gen3 has extra restrictions. + const byte MaxBattle3 = 2; + const byte MaxBattle4 = 6; + const byte MaxBattleBoth = MaxBattle3 + MaxBattle4; // 8 + const byte MaxBattleBothNoWinning = MaxBattleBoth - 1; // 7 + + if (evos.HasVisitedGen3) + { + var head = evos.Gen3[0]; // Checking contest with Gen3 head is fine; all false cases cannot evolve (evolution chain is same Gen3/Gen4). + var contest = IsAllowedContest4(head.Species, head.Form) ? MaxContestBoth : MaxContest3; + var battle = IsAllowedBattleFrontier(head.Species) ? IsRibbonValidWinning(pk, enc, evos) ? MaxBattleBoth : MaxBattleBothNoWinning : (byte)0; + return (contest, battle); + } + if (evos.HasVisitedGen4) + { + var head = evos.Gen4[0]; + var contest = IsAllowedContest4(head.Species, head.Form) ? MaxContest4 : (byte)0; + var battle = IsAllowedBattleFrontier(head.Species) ? MaxBattle4 : (byte)0; + return (contest, battle); + } + return default; + } + + public static bool IsAllowedContest3(EvolutionHistory evos) + { + // Any species can enter contests in Gen3. + return evos.HasVisitedGen3; + } + + public static bool IsAllowedContest4(EvolutionHistory evos) + { + if (!evos.HasVisitedGen4) + return false; + var head = evos.Gen4[0]; + return IsAllowedContest4(head.Species, head.Form); + } + + public static bool IsAllowedContest4(int species, int form) => species switch + { + // Disallow Unown and Ditto, and Spiky Pichu (cannot trade) + (int)Species.Ditto => false, + (int)Species.Unown => false, + (int)Species.Pichu when form == 1 => false, + _ => true, + }; + + public static bool IsAllowedBattleFrontier(int species) => !Legal.BattleFrontierBanlist.Contains(species); + + public static bool IsAllowedBattleFrontier4(EvolutionHistory evos) + { + if (!evos.HasVisitedGen4) + return false; + var head = evos.Gen4[0]; + return IsAllowedBattleFrontier(head.Species, head.Form, 4); + } + + public static bool IsAllowedBattleFrontier(int species, int form, int gen) + { + if (gen == 4 && species == (int)Species.Pichu && form == 1) // spiky + return false; + return IsAllowedBattleFrontier(species); + } +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifier.cs index 279131533..412d8b04f 100644 --- a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifier.cs @@ -1,6 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; +using System.Text; using static PKHeX.Core.LegalityCheckStrings; namespace PKHeX.Core; @@ -12,6 +11,17 @@ 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. @@ -19,696 +29,155 @@ public override void Verify(LegalityAnalysis data) var pk = data.Entity; // Check Unobtainable Ribbons - if (pk.IsEgg) + var args = new RibbonVerifierArguments(pk, enc, data.Info.EvoChainsAllGens); + Span result = stackalloc RibbonResult[MaxRibbonCount]; + int count = GetRibbonResults(args, result); + if (count == 0) { - if (GetIncorrectRibbonsEgg(pk, enc)) - data.AddLine(GetInvalid(LRibbonEgg)); + data.AddLine(GetValid(LRibbonAllValid)); return; } - var result = GetIncorrectRibbons(pk, data.Info.EvoChainsAllGens, enc); - if (result.Count != 0) + 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) { - var msg = string.Join(Environment.NewLine, result); - data.AddLine(GetInvalid(msg)); + 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) { - data.AddLine(GetValid(LRibbonAllValid)); + if (missing != 0) // need to visually separate the message + sb.Append(Environment.NewLine); + AppendAll(result, sb, LRibbonFInvalid_0, false); } + return sb.ToString(); } - private static List GetIncorrectRibbons(PKM pk, EvolutionHistory evos, IEncounterTemplate enc) + private static int GetCountMissing(ReadOnlySpan result) { - List missingRibbons = new(); - List invalidRibbons = new(); - var ribs = GetRibbonResults(pk, evos, enc); - foreach (var bad in ribs) - (bad.Invalid ? invalidRibbons : missingRibbons).Add(bad.Name); - - var result = new List(); - if (missingRibbons.Count > 0) - result.Add(string.Format(LRibbonFMissing_0, string.Join(", ", missingRibbons).Replace(RibbonInfo.PropertyPrefix, string.Empty))); - if (invalidRibbons.Count > 0) - result.Add(string.Format(LRibbonFInvalid_0, string.Join(", ", invalidRibbons).Replace(RibbonInfo.PropertyPrefix, string.Empty))); - return result; - } - - private static bool GetIncorrectRibbonsEgg(PKM pk, IEncounterTemplate enc) - { - var names = ReflectUtil.GetPropertiesStartWithPrefix(pk.GetType(), RibbonInfo.PropertyPrefix); - if (enc is IRibbonSetEvent3 event3) - names = names.Except(event3.RibbonNames()); - if (enc is IRibbonSetEvent4 event4) - names = names.Except(event4.RibbonNames()); - - foreach (var value in names.Select(name => ReflectUtil.GetValue(pk, name))) + int count = 0; + foreach (var x in result) { - if (value is null) + 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 (HasFlag(value) || HasCount(value)) - return true; - - static bool HasFlag(object o) => o is true; - static bool HasCount(object o) => o is > 0; + if (added++ != 0) + sb.Append(MessageSplitNextRibbon); + var localized = RibbonStrings.GetName(x.PropertyName); + sb.Append(localized); } - return false; } - internal static IEnumerable GetRibbonResults(PKM pk, EvolutionHistory evos, IEncounterTemplate enc) + private static void Parse(RibbonVerifierArguments args, ref RibbonResultList list) { - return GetInvalidRibbons(pk, evos, enc) - .Concat(GetInvalidRibbonsEvent1(pk, enc)) - .Concat(GetInvalidRibbonsEvent2(pk, enc)); - } - - private static IEnumerable GetInvalidRibbons(PKM pk, EvolutionHistory evos, IEncounterTemplate enc) - { - // is a part of Event4, but O3 doesn't have the others - if (pk is IRibbonSetOnly3 {RibbonWorld: true}) - yield return new RibbonResult(nameof(IRibbonSetOnly3.RibbonWorld)); - + 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) - { - if (enc.Generation != 3) - { - if (u3.RibbonWinning) - yield return new RibbonResult(nameof(u3.RibbonWinning)); - if (u3.RibbonVictory) - yield return new RibbonResult(nameof(u3.RibbonVictory)); - } - else - { - if (u3.RibbonWinning && !CanHaveRibbonWinning(pk, enc, 3)) - yield return new RibbonResult(nameof(u3.RibbonWinning)); - if (u3.RibbonVictory && !CanHaveRibbonVictory(pk, 3)) - yield return new RibbonResult(nameof(u3.RibbonVictory)); - } - } - - int gen = enc.Generation; - if (pk is IRibbonSetUnique4 u4) - { - if (!IsAllowedBattleFrontier(pk.Species, pk.Form, 4) || gen > 4) - { - foreach (var z in GetInvalidRibbonsNone(u4.RibbonBitsAbility(), u4.RibbonNamesAbility())) - yield return z; - } - - var c3 = u4.RibbonBitsContest3(); - var c3n = u4.RibbonNamesContest3(); - var iter3 = gen == 3 ? GetMissingContestRibbons(c3, c3n) : GetInvalidRibbonsNone(c3, c3n); - foreach (var z in iter3) - yield return z; - - var c4 = u4.RibbonBitsContest4(); - var c4n = u4.RibbonNamesContest4(); - var iter4 = (gen is 3 or 4) && IsAllowedInContest4(pk.Species, pk.Form) ? GetMissingContestRibbons(c4, c4n) : GetInvalidRibbonsNone(c4, c4n); - foreach (var z in iter4) - yield return z; - } - if (pk is IRibbonSetCommon4 s4) - { - bool inhabited4 = gen is 3 or 4; - var iterate = GetInvalidRibbons4Any(pk, evos, s4, gen); - if (!inhabited4) - { - if (evos.HasVisitedBDSP) // Allow Sinnoh Champion. ILCA reused the Gen4 ribbon for the remake. - iterate = iterate.Concat(GetInvalidRibbonsNoneSkipIndex(s4.RibbonBitsOnly(), s4.RibbonNamesOnly(), 1)); - else - iterate = iterate.Concat(GetInvalidRibbonsNone(s4.RibbonBitsOnly(), s4.RibbonNamesOnly())); - } - foreach (var z in iterate) - yield return z; - } - if (pk is IRibbonSetCommon6 s6) - { - bool inhabited6 = gen is >= 3 and <= 6; - - var iterate = inhabited6 - ? GetInvalidRibbons6Any(pk, s6, gen, enc) - : pk.Format >= 8 - ? GetInvalidRibbons6AnyG8(s6, evos) - : GetInvalidRibbonsNone(s6.RibbonBits(), s6.RibbonNamesBool()); - foreach (var z in iterate) - yield return z; - - if (!inhabited6) - { - if (s6.RibbonCountMemoryContest > 0) - yield return new RibbonResult(nameof(s6.RibbonCountMemoryContest)); - if (s6.RibbonCountMemoryBattle > 0) - yield return new RibbonResult(nameof(s6.RibbonCountMemoryBattle)); - } - - if (s6.RibbonBestFriends && !IsRibbonValidBestFriend(pk, evos, gen)) - yield return new RibbonResult(nameof(IRibbonSetCommon6.RibbonBestFriends)); - } - if (pk is IRibbonSetCommon7 s7) - { - bool inhabited7 = evos.HasVisitedGen7; - bool alolaValid = GetIsAlolaChampValid(s7, enc, inhabited7); - if (!alolaValid) - yield return new RibbonResult(nameof(s7.RibbonChampionAlola)); - - if (!inhabited7 || !IsAllowedBattleFrontier(pk.Species)) - { - if (s7.RibbonBattleRoyale) - yield return new RibbonResult(nameof(s7.RibbonBattleRoyale)); - if (s7.RibbonBattleTreeGreat && !pk.USUM && pk.IsUntraded) - yield return new RibbonResult(nameof(s7.RibbonBattleTreeGreat)); - if (s7.RibbonBattleTreeMaster) - yield return new RibbonResult(nameof(s7.RibbonBattleTreeMaster)); - } - } + u3.Parse(args, ref list); if (pk is IRibbonSetCommon3 s3) - { - if (s3.RibbonChampionG3 && gen != 3) - yield return new RibbonResult(nameof(s3.RibbonChampionG3)); // RSE HoF - if (s3.RibbonArtist && gen != 3) - yield return new RibbonResult(nameof(s3.RibbonArtist)); // RSE Master Rank Portrait - if (s3.RibbonEffort && !IsRibbonValidEffort(pk, evos, gen)) // unobtainable in Gen 5 - yield return new RibbonResult(nameof(s3.RibbonEffort)); - } + 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) - { - bool inhabited8 = gen <= 8; - var iterate = inhabited8 ? GetInvalidRibbons8Any(pk, s8, enc, evos) : GetInvalidRibbonsNone(s8.RibbonBits(), s8.RibbonNames()); - foreach (var z in iterate) - yield return z; - } + s8.Parse(args, ref list); } - private static bool GetIsAlolaChampValid(IRibbonSetCommon7 s7, IEncounterTemplate enc, bool inhabited7) + private static void ParseEgg(RibbonVerifierArguments args, ref RibbonResultList list) { - // If the encounter comes with the ribbon, it must have the ribbon. - if (enc is IRibbonSetCommon7 { RibbonChampionAlola: true }) - return s7.RibbonChampionAlola; - // If it has visited, it can be either state. - if (inhabited7) - return true; - // If it has not visited, it must not have it. - return !s7.RibbonChampionAlola; + 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); } - - private static bool IsRibbonValidEffort(PKM pk, EvolutionHistory evos, int gen) => gen switch - { - 5 when pk.Format == 5 => false, - 8 when !evos.HasVisitedSWSH && !evos.HasVisitedBDSP => false, - _ => true, - }; - - private static bool IsRibbonValidBestFriend(PKM pk, EvolutionHistory evos, int gen) => gen switch - { - < 7 when pk is { IsUntraded: true } and IAffection { OT_Affection: < 255 } => false, // Gen6/7 uses affection. Can't lower it on OT! - 8 when !evos.HasVisitedSWSH && !evos.HasVisitedBDSP => false, // Gen8+ replaced with Max Friendship. - _ => true, - }; - - private static IEnumerable GetMissingContestRibbons(IReadOnlyList bits, IReadOnlyList names) - { - for (int i = 0; i < bits.Count; i += 4) - { - bool required = false; - for (int j = i + 3; j >= i; j--) - { - if (bits[j]) - required = true; - else if (required) yield return new RibbonResult(names[j], false); - } - } - } - - private static IEnumerable GetInvalidRibbons4Any(PKM pk, EvolutionHistory evos, IRibbonSetCommon4 s4, int gen) - { - if (s4.RibbonRecord) - yield return new RibbonResult(nameof(s4.RibbonRecord)); // Unobtainable - if (s4.RibbonFootprint && !CanHaveFootprintRibbon(pk, evos, gen)) - yield return new RibbonResult(nameof(s4.RibbonFootprint)); - - bool visitBDSP = evos.HasVisitedBDSP; - bool gen34 = gen is 3 or 4; - bool not6 = pk.Format < 6 || gen is > 6 or < 3; - bool noDaily = !gen34 && not6 && !visitBDSP; - bool noSinnoh = pk is G4PKM { Species: (int)Species.Pichu, Form: 1 }; // Spiky Pichu - bool noCosmetic = (!gen34 && (not6 || (pk.XY && pk.IsUntraded)) && !visitBDSP) || noSinnoh; - - if (noSinnoh) - { - if (s4.RibbonChampionSinnoh) - yield return new RibbonResult(nameof(s4.RibbonChampionSinnoh)); - } - - if (noDaily) - { - foreach (var z in GetInvalidRibbonsNone(s4.RibbonBitsDaily(), s4.RibbonNamesDaily())) - yield return z; - } - - if (noCosmetic) - { - foreach (var z in GetInvalidRibbonsNone(s4.RibbonBitsCosmetic(), s4.RibbonNamesCosmetic())) - yield return z; - } - } - - private static IEnumerable GetInvalidRibbons6Any(PKM pk, IRibbonSetCommon6 s6, int gen, IEncounterTemplate enc) - { - foreach (var p in GetInvalidRibbons6Memory(pk, s6, gen, enc)) - yield return p; - - bool untraded = pk.IsUntraded || (enc is EncounterStatic6 {Species:(int)Species.Pikachu, Form: not 0}); // Disallow cosplay pikachu from XY ribbons - var iter = untraded ? GetInvalidRibbons6Untraded(pk, s6) : GetInvalidRibbons6Traded(pk, s6); - foreach (var p in iter) - yield return p; - - var contest = s6.RibbonBitsContest(); - bool allContest = contest.All(z => z); - if ((allContest != s6.RibbonContestStar) && !(untraded && pk.XY)) // if not already checked - yield return new RibbonResult(nameof(s6.RibbonContestStar), s6.RibbonContestStar); - - // Each contest victory requires a contest participation; each participation gives 20 OT affection (not current trainer). - // Affection is discarded on PK7->PK8 in favor of friendship, which can be lowered. - if (pk is IAffection a) - { - var affect = a.OT_Affection; - var contMemory = s6.RibbonNamesContest(); - int contCount = 0; - var present = contMemory.Where((_, i) => contest[i] && affect < 20 * ++contCount); - foreach (var rib in present) - yield return new RibbonResult(rib); - } - - // Gen6 can get the memory on those who did not participate by being in the party with other participants. - // This includes those who cannot enter into the Maison; having memory and no ribbon. - const int memChatelaine = 30; - bool hasChampMemory = enc.Generation == 7 && pk.Format == 7 && pk is ITrainerMemories m && (m.HT_Memory == memChatelaine || m.OT_Memory == memChatelaine); - if (!IsAllowedBattleFrontier(pk.Species)) - { - if (hasChampMemory || s6.RibbonBattlerSkillful) // having memory and not ribbon is too rare, just flag here. - yield return new RibbonResult(nameof(s6.RibbonBattlerSkillful)); - if (s6.RibbonBattlerExpert) - yield return new RibbonResult(nameof(s6.RibbonBattlerExpert)); - yield break; - } - if (!hasChampMemory || s6.RibbonBattlerSkillful || s6.RibbonBattlerExpert) - yield break; - - var result = new RibbonResult(nameof(s6.RibbonBattlerSkillful), false); - result.Combine(new RibbonResult(nameof(s6.RibbonBattlerExpert))); - yield return result; - } - - private static IEnumerable GetInvalidRibbons6AnyG8(IRibbonSetCommon6 s6, EvolutionHistory evos) - { - if (!evos.HasVisitedBDSP) - { - var none = GetInvalidRibbonsNone(s6.RibbonBits(), s6.RibbonNamesBool()); - foreach (var x in none) - yield return x; - yield break; - } - - if (s6.RibbonChampionKalos) - yield return new RibbonResult(nameof(s6.RibbonChampionKalos)); - if (s6.RibbonChampionG6Hoenn) - yield return new RibbonResult(nameof(s6.RibbonChampionG6Hoenn)); - //if (s6.RibbonBestFriends) - // yield return new RibbonResult(nameof(s6.RibbonBestFriends)); - if (s6.RibbonTraining) - yield return new RibbonResult(nameof(s6.RibbonTraining)); - if (s6.RibbonBattlerSkillful) - yield return new RibbonResult(nameof(s6.RibbonBattlerSkillful)); - if (s6.RibbonBattlerExpert) - yield return new RibbonResult(nameof(s6.RibbonBattlerExpert)); - - if (s6.RibbonCountMemoryContest != 0) - yield return new RibbonResult(nameof(s6.RibbonCountMemoryContest)); - if (s6.RibbonCountMemoryBattle != 0) - yield return new RibbonResult(nameof(s6.RibbonCountMemoryBattle)); - - // Can get contest ribbons via BD/SP contests. - //if (s6.RibbonContestStar) - // yield return new RibbonResult(nameof(s6.RibbonContestStar)); - //if (s6.RibbonMasterCoolness) - // yield return new RibbonResult(nameof(s6.RibbonMasterCoolness)); - //if (s6.RibbonMasterBeauty) - // yield return new RibbonResult(nameof(s6.RibbonMasterBeauty)); - //if (s6.RibbonMasterCuteness) - // yield return new RibbonResult(nameof(s6.RibbonMasterCuteness)); - //if (s6.RibbonMasterCleverness) - // yield return new RibbonResult(nameof(s6.RibbonMasterCleverness)); - //if (s6.RibbonMasterToughness) - // yield return new RibbonResult(nameof(s6.RibbonMasterToughness)); - - var contest = s6.RibbonBitsContest(); - bool allContest = contest.All(z => z); - if (allContest != s6.RibbonContestStar) // if not already checked - yield return new RibbonResult(nameof(s6.RibbonContestStar), s6.RibbonContestStar); - } - - private static IEnumerable GetInvalidRibbons6Memory(PKM pk, IRibbonSetCommon6 s6, int gen, IEncounterTemplate enc) - { - int contest = 0; - int battle = 0; - switch (gen) - { - case 3: - contest = IsAllowedInContest4(pk.Species, pk.Form) ? 40 : 20; - battle = IsAllowedBattleFrontier(pk.Species) ? CanHaveRibbonWinning(pk, enc, 3) ? 8 : 7 : 0; - break; - case 4: - contest = IsAllowedInContest4(pk.Species, pk.Form) ? 20 : 0; - battle = IsAllowedBattleFrontier(pk.Species) ? 6 : 0; - break; - } - if (s6.RibbonCountMemoryContest > contest) - yield return new RibbonResult(nameof(s6.RibbonCountMemoryContest)); - if (s6.RibbonCountMemoryBattle > battle) - yield return new RibbonResult(nameof(s6.RibbonCountMemoryBattle)); - } - - private static IEnumerable GetInvalidRibbons6Untraded(PKM pk, IRibbonSetCommon6 s6) - { - if (pk.XY) - { - if (s6.RibbonChampionG6Hoenn) - yield return new RibbonResult(nameof(s6.RibbonChampionG6Hoenn)); - - if (s6.RibbonContestStar) - yield return new RibbonResult(nameof(s6.RibbonContestStar)); - if (s6.RibbonMasterCoolness) - yield return new RibbonResult(nameof(s6.RibbonMasterCoolness)); - if (s6.RibbonMasterBeauty) - yield return new RibbonResult(nameof(s6.RibbonMasterBeauty)); - if (s6.RibbonMasterCuteness) - yield return new RibbonResult(nameof(s6.RibbonMasterCuteness)); - if (s6.RibbonMasterCleverness) - yield return new RibbonResult(nameof(s6.RibbonMasterCleverness)); - if (s6.RibbonMasterToughness) - yield return new RibbonResult(nameof(s6.RibbonMasterToughness)); - } - else if (pk.AO) - { - if (s6.RibbonChampionKalos) - yield return new RibbonResult(nameof(s6.RibbonChampionKalos)); - } - } - - private static IEnumerable GetInvalidRibbons6Traded(PKM pk, IRibbonSetCommon6 s6) - { - // Medal count is wiped on transfer to pk8 - if (s6.RibbonTraining && pk.Format <= 7) - { - const int req = 12; // only first 12 - int count = ((ISuperTrain)pk).SuperTrainingMedalCount(req); - if (count < req) - yield return new RibbonResult(nameof(s6.RibbonTraining)); - } - - const int memChampion = 27; - bool hasChampMemory = pk is ITrainerMemories m && ((pk.Format < 8 && m.HT_Memory == memChampion) || (pk.Gen6 && m.OT_Memory == memChampion)); - if (!hasChampMemory || s6.RibbonChampionKalos || s6.RibbonChampionG6Hoenn) - yield break; - - var result = new RibbonResult(nameof(s6.RibbonChampionKalos), false); - result.Combine(new RibbonResult(nameof(s6.RibbonChampionG6Hoenn))); - yield return result; - } - - private static IEnumerable GetInvalidRibbons8Any(PKM pk, IRibbonSetCommon8 s8, IEncounterTemplate enc, EvolutionHistory evos) - { - if (!CanObtainTowerMaster(evos) && s8.RibbonTowerMaster) - { - yield return new RibbonResult(nameof(s8.RibbonTowerMaster)); - } - if (!evos.HasVisitedSWSH) - { - if (s8.RibbonChampionGalar) - yield return new RibbonResult(nameof(s8.RibbonChampionGalar)); - if (s8.RibbonMasterRank) - yield return new RibbonResult(nameof(s8.RibbonMasterRank)); - } - else - { - const int memChampion = 27; - { - bool hasChampMemory = (pk.Format == 8 && pk is IMemoryHT {HT_Memory: memChampion}) || - (enc.Generation == 8 && pk is IMemoryOT {OT_Memory: memChampion}); - if (hasChampMemory && !s8.RibbonChampionGalar) - yield return new RibbonResult(nameof(s8.RibbonChampionGalar)); - } - - // Legends cannot compete in Ranked, thus cannot reach Master Rank and obtain the ribbon. - // Past gen Pokemon can get the ribbon only if they've been reset. - if (s8.RibbonMasterRank && !CanParticipateInRankedSWSH(pk, enc, evos)) - yield return new RibbonResult(nameof(s8.RibbonMasterRank)); - - if (!s8.RibbonTowerMaster) - { - // If the Tower Master ribbon is not present but a memory hint implies it should... - // This memory can also be applied in Gen6/7 via defeating the Chatelaines, where legends are disallowed. - const int strongest = 30; - if (pk is IMemoryOT {OT_Memory: strongest} or IMemoryHT {HT_Memory: strongest}) - { - if (enc.Generation == 8 || !IsAllowedBattleFrontier(pk.Species) || pk is IRibbonSetCommon6 {RibbonBattlerSkillful: false}) - yield return new RibbonResult(nameof(s8.RibbonTowerMaster)); - } - } - } - - if (s8.RibbonTwinklingStar && (!evos.HasVisitedBDSP || pk is IRibbonSetCommon6 {RibbonContestStar:false})) - { - yield return new RibbonResult(nameof(s8.RibbonTwinklingStar)); - } - - // received when capturing photos with Pokémon in the Photography Studio - if (!evos.HasVisitedPLA && s8.RibbonPioneer) - { - yield return new RibbonResult(nameof(s8.RibbonPioneer)); - } - } - - private static bool CanObtainTowerMaster(EvolutionHistory evos) - { - if (evos.HasVisitedSWSH) - return true; // Anything in SW/SH can be used in battle tower. - - if (!evos.HasVisitedBDSP) - return false; - // Mythicals cannot be used in BD/SP's Battle Tower - return !Legal.Mythicals.Contains(evos.Gen8b[0].Species); - } - - private static bool CanParticipateInRankedSWSH(PKM pk, IEncounterTemplate enc, EvolutionHistory evos) - { - bool exist = enc.Generation switch - { - < 8 => pk is IBattleVersion { BattleVersion: (int)GameVersion.SW or (int)GameVersion.SH }, - _ => evos.HasVisitedSWSH, - }; - if (!exist) - return false; - - // Clamp to permitted species - var species = pk.Species; - if (species > Legal.MaxSpeciesID_8_R2) - return false; - - // Series 13 rule-set was the first time Ranked Battles allowed the use of Mythical Pokémon. - if (Legal.Legends.Contains(species)) - { - if (enc.Version == GameVersion.GO || enc is IEncounterServerDate { IsDateRestricted: true }) // Capture date is global time, and not console changeable. - { - if (pk.MetDate > new DateTime(2022, 11, 1)) // Series 13 end date - return false; - } - } - - return PersonalTable.SWSH.IsPresentInGame(species, pk.Form); - } - - private static IEnumerable GetInvalidRibbonsEvent1(PKM pk, IEncounterTemplate enc) - { - if (pk is not IRibbonSetEvent3 set1) - yield break; - var names = set1.RibbonNames(); - var sb = set1.RibbonBits(); - var eb = enc is IRibbonSetEvent3 e3 ? e3.RibbonBits() : new bool[sb.Length]; - - if (enc.Generation == 3) - { - eb[0] = sb[0]; // permit Earth Ribbon - if (pk.Version == 15 && enc is EncounterStaticShadow s) - { - // only require national ribbon if no longer on origin game - bool untraded = s.Version == GameVersion.XD - ? pk is XK3 {RibbonNational: false} - : pk is CK3 {RibbonNational: false}; - eb[1] = !untraded; - } - } - - for (int i = 0; i < sb.Length; i++) - { - if (sb[i] != eb[i]) - yield return new RibbonResult(names[i], !eb[i]); // only flag if invalid - } - } - - private static IEnumerable GetInvalidRibbonsEvent2(PKM pk, IEncounterTemplate enc) - { - if (pk is not IRibbonSetEvent4 set2) - yield break; - var names = set2.RibbonNames(); - var sb = set2.RibbonBits(); - var eb = enc is IRibbonSetEvent4 e4 ? e4.RibbonBits() : new bool[sb.Length]; - - if (enc is EncounterStatic7 {Species: (int)Species.Magearna}) - eb[1] = true; // require Wishing Ribbon - - for (int i = 0; i < sb.Length; i++) - { - if (sb[i] != eb[i]) - yield return new RibbonResult(names[i], !eb[i]); // only flag if invalid - } - } - - private static IEnumerable GetInvalidRibbonsNone(IReadOnlyList bits, IReadOnlyList names) - { - for (int i = 0; i < bits.Count; i++) - { - if (bits[i]) - yield return new RibbonResult(names[i]); - } - } - - private static IEnumerable GetInvalidRibbonsNoneSkipIndex(IReadOnlyList bits, IReadOnlyList names, int skipIndex) - { - for (int i = 0; i < bits.Count; i++) - { - if (bits[i] && i != skipIndex) - yield return new RibbonResult(names[i]); - } - } - - private static bool IsAllowedInContest4(int species, int form) => species switch - { - // Disallow Unown and Ditto, and Spiky Pichu (cannot trade) - (int)Species.Ditto => false, - (int)Species.Unown => false, - (int)Species.Pichu when form == 1 => false, - _ => true, - }; - - private static bool IsAllowedBattleFrontier(int species) => !Legal.BattleFrontierBanlist.Contains(species); - - private static bool IsAllowedBattleFrontier(int species, int form, int gen) - { - if (gen == 4 && species == (int)Species.Pichu && form == 1) // spiky - return false; - return IsAllowedBattleFrontier(species); - } - - private static bool CanHaveFootprintRibbon(PKM pk, EvolutionHistory evos, int gen) - { - if (gen <= 4) // Friendship Check unnecessary - can decrease after obtaining ribbon. - return true; - // Gen5: Can't obtain - if (pk.Format < 6) - return false; - - // Gen6/7: Increase level by 30 from original level - if (gen != 8 && !pk.GG && (pk.CurrentLevel - pk.Met_Level >= 30)) - return true; - - // Gen8-BDSP: Variable by species Footprint - if (evos.HasVisitedBDSP) - { - if (Array.Exists(evos.Gen8b, z => !HasFootprintBDSP[z.Species])) - return true; // no footprint - if (pk.CurrentLevel - pk.Met_Level >= 30) - return true; // traveled well - } - - // Otherwise: Can't obtain - return false; - } - - private static bool CanHaveRibbonWinning(PKM pk, IEncounterTemplate enc, int gen) - { - if (gen != 3) - return false; - if (!IsAllowedBattleFrontier(pk.Species)) - return false; - if (pk.Format == 3) - return pk.Met_Level <= 50; - - // Most encounter types can be below level 50; only Shadow Dragonite & Tyranitar, and select Gen3 Event Gifts. - // These edge cases can't be obtained below level 50, unlike some wild Pokémon which can be encountered at different locations for lower levels. - if (enc.LevelMin <= 50) - return true; - - return enc is not (EncounterStaticShadow or WC3); - } - - private static bool CanHaveRibbonVictory(PKM pk, int gen) - { - return gen == 3 && IsAllowedBattleFrontier(pk.Species); - } - - // Footprint type is not Type 5, requiring 30 levels. - private static readonly bool[] HasFootprintBDSP = - { - true, true, true, true, true, true, true, true, true, true, - true, false, true, true, false, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, false, false, true, false, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, false, false, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - false, false, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, false, true, true, - false, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, false, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, false, true, true, false, false, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, false, true, true, true, true, true, true, - true, true, true, false, true, true, true, true, true, true, - true, true, true, true, true, true, true, false, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, false, true, false, true, - true, true, true, false, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - false, true, true, true, true, true, true, true, true, false, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, false, false, true, - true, true, true, false, false, false, false, false, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, false, true, false, false, true, false, false, false, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, false, false, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, false, true, true, true, true, true, true, true, - true, true, true, true, false, true, false, true, true, true, - true, true, true, true, true, true, false, true, true, true, - true, true, true, true, - }; } diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierArguments.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierArguments.cs new file mode 100644 index 000000000..dac9fc44a --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierArguments.cs @@ -0,0 +1,10 @@ +namespace PKHeX.Core; + +/// +/// Wraps details used for parsing ribbon states. +/// +/// Entity to parse +/// Encounter originated as +/// History of visitation +/// For Generation 1/2 encounters, use the transferred encounter data object. +public readonly record struct RibbonVerifierArguments(PKM Entity, IEncounterTemplate Encounter, EvolutionHistory History); diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon3.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon3.cs new file mode 100644 index 000000000..7b2f29aae --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon3.cs @@ -0,0 +1,31 @@ +using static PKHeX.Core.RibbonIndex; + +namespace PKHeX.Core; + +/// +/// Parsing logic for . +/// +public static class RibbonVerifierCommon3 +{ + public static void Parse(this IRibbonSetCommon3 r, RibbonVerifierArguments args, ref RibbonResultList list) + { + var evos = args.History; + var pk = args.Entity; + if (r.RibbonChampionG3 && !evos.HasVisitedGen3) + list.Add(ChampionG3); + if (r.RibbonArtist && !evos.HasVisitedGen3) + list.Add(Artist); + if (r.RibbonEffort && !RibbonRules.IsRibbonValidEffort(pk, evos, args.Encounter.Generation)) + list.Add(Effort); + } + + public static void ParseEgg(this IRibbonSetCommon3 r, ref RibbonResultList list) + { + if (r.RibbonChampionG3) + list.Add(ChampionG3); + if (r.RibbonArtist) + list.Add(Artist); + if (r.RibbonEffort) + list.Add(Effort); + } +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon4.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon4.cs new file mode 100644 index 000000000..c64a6b1b5 --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon4.cs @@ -0,0 +1,70 @@ +using static PKHeX.Core.RibbonIndex; + +namespace PKHeX.Core; + +/// +/// Parsing logic for . +/// +public static class RibbonVerifierCommon4 +{ + public static void Parse(this IRibbonSetCommon4 r, RibbonVerifierArguments args, ref RibbonResultList list) + { + var pk = args.Entity; + var evos = args.History; + if (r.RibbonFootprint && !RibbonRules.IsRibbonValidFootprint(pk, evos)) + list.Add(Footprint); + if (r.RibbonRecord) + list.Add(Record); // Unobtainable + + bool gen4 = evos.HasVisitedGen4; + bool gen6 = evos.HasVisitedGen6; + bool bdsp = evos.HasVisitedBDSP; + bool oras6 = gen6 && !(pk.IsUntraded && pk.XY); + bool sinnoh4 = gen4 && args.Encounter is not EncounterStatic4 { Species: (int)Species.Pichu, Form: 1 }; + bool sinnohChamp = sinnoh4 || bdsp; // no gen4 hg/ss + bool cosmetic = sinnoh4 || oras6 || bdsp; // no gen4 hg/ss + bool daily = gen4 || gen6 || bdsp; + + if (r.RibbonLegend && !gen4) + list.Add(Legend); + if (r.RibbonChampionSinnoh && !sinnohChamp) + list.Add(ChampionSinnoh); + if (!daily) + FlagDaily(r, ref list); + if (!cosmetic) + FlagCosmetic(r, ref list); + } + + private static void FlagDaily(IRibbonSetCommon4 r, ref RibbonResultList list) + { + if (r.RibbonAlert) list.Add(Alert); + if (r.RibbonShock) list.Add(Shock); + if (r.RibbonDowncast) list.Add(Downcast); + if (r.RibbonCareless) list.Add(Careless); + if (r.RibbonRelax) list.Add(Relax); + if (r.RibbonSnooze) list.Add(Snooze); + if (r.RibbonSmile) list.Add(Smile); + } + + private static void FlagCosmetic(IRibbonSetCommon4 r, ref RibbonResultList list) + { + if (r.RibbonGorgeous) list.Add(Gorgeous); + if (r.RibbonRoyal) list.Add(Royal); + if (r.RibbonGorgeousRoyal) list.Add(GorgeousRoyal); + } + + public static void ParseEgg(this IRibbonSetCommon4 r, ref RibbonResultList list) + { + if (r.RibbonFootprint) + list.Add(Footprint); + if (r.RibbonRecord) + list.Add(Record); + if (r.RibbonLegend) + list.Add(Legend); + if (r.RibbonChampionSinnoh) + list.Add(ChampionSinnoh); + + FlagDaily(r, ref list); + FlagCosmetic(r, ref list); + } +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon6.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon6.cs new file mode 100644 index 000000000..b483c92b4 --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon6.cs @@ -0,0 +1,190 @@ +using static PKHeX.Core.RibbonIndex; + +namespace PKHeX.Core; + +/// +/// Parsing logic for . +/// +public static class RibbonVerifierCommon6 +{ + public static void Parse(this IRibbonSetCommon6 r, RibbonVerifierArguments args, ref RibbonResultList list) + { + GetInvalidRibbons6Memory(r, args, ref list); + var pk = args.Entity; + var evos = args.History; + + bool gen6 = evos.HasVisitedGen6; + bool bdsp = evos.HasVisitedBDSP; + bool kalos6 = gen6 && !(pk.IsUntraded && pk.AO) && args.Encounter is not EncounterStatic6 { Species: (int)Species.Pikachu, Form: not 0 }; + bool oras6 = gen6 && !(pk.IsUntraded && pk.XY); + bool contest = oras6 || bdsp; + + bool k = r.RibbonChampionKalos; + bool h = r.RibbonChampionG6Hoenn; + if (k && !kalos6) + list.Add(ChampionKalos); + if (h && !oras6) + list.Add(ChampionG6Hoenn); + if (!k && !h) // no champ ribbon, check memory. + CheckChampionMemory(args, ref list); + + if (!contest) + { + FlagContest(r, ref list); + } + else + { + // Winning a contest in Gen6 adds 20 to OT affection. Each ribbon, add 20 to our expected minimum. + if (pk is IAffection a) // False in Gen8+ + FlagContestAffection(r, ref list, a.OT_Affection); + + // Winning all contests grants the Contest Star ribbon. + // If we have all ribbons and the star is not present, flag it as missing. + // If we have the star and not all are present, flag it as invalid. + bool allContest = r.HasAllContestRibbons(); + if (allContest != r.RibbonContestStar) + list.Add(ContestStar, !r.RibbonContestStar); + } + + if (r.RibbonBestFriends && !RibbonRules.IsRibbonValidBestFriends(args.Entity, evos, args.Encounter.Generation)) + list.Add(BestFriends); + + if (!gen6) + { + if (r.RibbonTraining) + list.Add(Training); + + // Maison + if (r.RibbonBattlerSkillful) + list.Add(BattlerSkillful); + if (r.RibbonBattlerExpert) + list.Add(BattlerExpert); + } + else + { + if (r.RibbonTraining && pk is ISuperTrain s && !RibbonRules.IsRibbonValidSuperTraining(s)) + list.Add(Training); + + // Maison + CheckMaisonRibbons(r, args, ref list); + } + } + + public static void ParseEgg(this IRibbonSetCommon6 r, ref RibbonResultList list) + { + if (r.RibbonChampionKalos) + list.Add(ChampionKalos); + if (r.RibbonChampionG6Hoenn) + list.Add(ChampionG6Hoenn); + if (r.RibbonBestFriends) + list.Add(BestFriends); + if (r.RibbonTraining) + list.Add(Training); + if (r.RibbonBattlerSkillful) + list.Add(BattlerSkillful); + if (r.RibbonBattlerExpert) + list.Add(BattlerExpert); + + FlagContest(r, ref list); + } + + private static void GetInvalidRibbons6Memory(IRibbonSetCommon6 r, RibbonVerifierArguments args, ref RibbonResultList list) + { + (int contest, int battle) = RibbonRules.GetMaxMemoryCounts(args.History, args.Entity, args.Encounter); + if (r.RibbonCountMemoryContest > contest) + list.Add(CountMemoryContest); + if (r.RibbonCountMemoryBattle > battle) + list.Add(CountMemoryBattle); + } + + private static void FlagContestAffection(IRibbonSetCommon6 r, ref RibbonResultList list, int current) + { + int expect = 0; + if (r.RibbonMasterCoolness && current < (expect += 20)) + list.Add(MasterCoolness); + if (r.RibbonMasterBeauty && current < (expect += 20)) + list.Add(MasterBeauty); + if (r.RibbonMasterCuteness && current < (expect += 20)) + list.Add(MasterCuteness); + if (r.RibbonMasterCleverness && current < (expect += 20)) + list.Add(MasterCleverness); + if (r.RibbonMasterToughness && current < (expect + 20)) + list.Add(MasterToughness); + } + + private static void FlagContest(IRibbonSetCommon6 r, ref RibbonResultList list) + { + if (r.RibbonContestStar) + list.Add(ContestStar); + if (r.RibbonMasterCoolness) + list.Add(MasterCoolness); + if (r.RibbonMasterBeauty) + list.Add(MasterBeauty); + if (r.RibbonMasterCuteness) + list.Add(MasterCuteness); + if (r.RibbonMasterCleverness) + list.Add(MasterCleverness); + if (r.RibbonMasterToughness) + list.Add(MasterToughness); + } + + private static void CheckChampionMemory(RibbonVerifierArguments args, ref RibbonResultList list) + { + var pk = args.Entity; + var enc = args.Encounter; + bool hasChampMemory = GetHasGen6ChampMemory(pk, enc); + if (!hasChampMemory) + return; + + var ribbon = pk.XY ? ChampionKalos : ChampionG6Hoenn; + list.Add(ribbon, true); + } + + private static void CheckMaisonRibbons(IRibbonSetCommon6 r, RibbonVerifierArguments args, ref RibbonResultList list) + { + var pk = args.Entity; + var enc = args.Encounter; + bool hasChatelaine6Memory = GetHasGen6ChatelaineMemory(pk, enc); + + if (!RibbonRules.IsAllowedBattleFrontier(pk.Species)) + { + if (hasChatelaine6Memory || r.RibbonBattlerSkillful) // having memory and not ribbon is too rare, just flag here. + list.Add(BattlerSkillful); + if (r.RibbonBattlerExpert) + list.Add(BattlerExpert); + return; + } + + if (!hasChatelaine6Memory) + return; + + if (r.RibbonBattlerSkillful || r.RibbonBattlerExpert) + return; + + list.Add(BattlerSkillful, true); + //list.Add(BattlerExpert, true); // overkill to flag both as required. One is sufficient. + } + + private static bool GetHasGen6ChampMemory(PKM pk, IEncounterTemplate enc) + { + if (pk is not ITrainerMemories m) + return false; + + // Gen6 can get the memory with any party member when defeating the champion. + const int memChampion = 27; + return (enc.Generation == 6 && m.OT_Memory == memChampion) + || (pk.Format < 8 && m.HT_Memory == memChampion); + } + + private static bool GetHasGen6ChatelaineMemory(PKM pk, IEncounterTemplate enc) + { + if (pk is not ITrainerMemories m) + return false; + + // Gen6 can get the memory on those who did not participate by being in the party with other participants. + // This includes those who cannot enter into the Maison; having memory and no ribbon. + const int memChatelaine = 30; + return (enc.Generation == 6 && m.OT_Memory == memChatelaine) + || (pk.Format < 8 && m.HT_Memory == memChatelaine); + } +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon7.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon7.cs new file mode 100644 index 000000000..664cc7c35 --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon7.cs @@ -0,0 +1,41 @@ +using static PKHeX.Core.RibbonIndex; + +namespace PKHeX.Core; + +/// +/// Parsing logic for . +/// +public static class RibbonVerifierCommon7 +{ + public static void Parse(this IRibbonSetCommon7 r, RibbonVerifierArguments args, ref RibbonResultList list) + { + bool inhabited7 = args.History.HasVisitedGen7; + bool alolaValid = RibbonRules.IsRibbonValidAlolaChamp(r, args.Encounter, inhabited7); + if (!alolaValid) + list.Add(ChampionAlola); + + var pk = args.Entity; + if (inhabited7 && RibbonRules.IsAllowedBattleFrontier(args.History.Gen7[0].Species)) + return; // Can have all 3 ribbons. + + if (r.RibbonBattleRoyale) + list.Add(BattleRoyale); + if (r.RibbonBattleTreeGreat && pk.IsUntraded && !pk.USUM) + list.Add(BattleTreeGreat); + if (r.RibbonBattleTreeMaster) + list.Add(BattleTreeMaster); + } + + public static void ParseEgg(this IRibbonSetCommon7 r, ref RibbonResultList list) + { + if (r.RibbonChampionAlola) + list.Add(ChampionAlola); + + if (r.RibbonBattleRoyale) + list.Add(BattleRoyale); + if (r.RibbonBattleTreeGreat) + list.Add(BattleTreeGreat); + if (r.RibbonBattleTreeMaster) + list.Add(BattleTreeMaster); + } +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon8.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon8.cs new file mode 100644 index 000000000..ea55e6500 --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierCommon8.cs @@ -0,0 +1,77 @@ +using static PKHeX.Core.RibbonIndex; + +namespace PKHeX.Core; + +/// +/// Parsing logic for . +/// +public static class RibbonVerifierCommon8 +{ + public static void Parse(this IRibbonSetCommon8 r, RibbonVerifierArguments args, ref RibbonResultList list) + { + var evos = args.History; + if (r.RibbonTowerMaster && !RibbonRules.IsRibbonValidTowerMaster(evos)) + list.Add(TowerMaster); + + var pk = args.Entity; + if (!evos.HasVisitedSWSH) + { + if (r.RibbonChampionGalar) + list.Add(ChampionGalar); + if (r.RibbonMasterRank) + list.Add(MasterRank); + } + else + { + // If it can exist in SW/SH, it can have the ribbon. + // If it doesn't have the ribbon and has the memory of having it, then flag it. + var enc = args.Encounter; + if (!r.RibbonChampionGalar) + { + const int memChampion = 27; + bool hasChampMemory = (enc.Generation == 8 && pk is IMemoryOT { OT_Memory: memChampion }) + || (pk.Format == 8 && pk is IMemoryHT { HT_Memory: memChampion }); + if (hasChampMemory) + list.Add(ChampionGalar); + } + + // Legends cannot compete in Ranked, thus cannot reach Master Rank and obtain the ribbon. + // Past gen Pokemon can get the ribbon only if they've been reset. + if (r.RibbonMasterRank && !RibbonRules.IsRibbonValidMasterRankSWSH(pk, enc, evos)) + list.Add(MasterRank); + + if (!r.RibbonTowerMaster) + { + // If the Tower Master ribbon is not present but a memory hint implies it should... + // This memory can also be applied in Gen6/7 via defeating the Chatelaines, where legends are disallowed. + const int strongest = 30; + if (pk is IMemoryOT { OT_Memory: strongest } or IMemoryHT { HT_Memory: strongest }) + { + if (enc.Generation == 8 || !RibbonRules.IsAllowedBattleFrontier(pk.Species) || pk is IRibbonSetCommon6 { RibbonBattlerSkillful: false }) + list.Add(TowerMaster, true); + } + } + } + + if (r.RibbonTwinklingStar && !RibbonRules.IsRibbonValidTwinklingStar(evos, pk)) + list.Add(TwinklingStar); + + // received when capturing photos with Pokémon in the Photography Studio + if (r.RibbonPioneer && !evos.HasVisitedPLA) + list.Add(Pioneer); + } + + public static void ParseEgg(this IRibbonSetCommon8 r, ref RibbonResultList list) + { + if (r.RibbonChampionGalar) + list.Add(ChampionGalar); + if (r.RibbonTowerMaster) + list.Add(TowerMaster); + if (r.RibbonMasterRank) + list.Add(MasterRank); + if (r.RibbonTwinklingStar) + list.Add(TwinklingStar); + if (r.RibbonPioneer) + list.Add(Pioneer); + } +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierEvent3.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierEvent3.cs new file mode 100644 index 000000000..0adbf23ce --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierEvent3.cs @@ -0,0 +1,73 @@ +using static PKHeX.Core.RibbonIndex; + +namespace PKHeX.Core; + +/// +/// Parsing logic for . +/// +public static class RibbonVerifierEvent3 +{ + public static void Parse(this IRibbonSetEvent3 r, RibbonVerifierArguments args, ref RibbonResultList list) + { + var enc = args.Encounter; + if (enc is IRibbonSetEvent3 e) + { + // The Earth Ribbon is a ribbon exclusive to Pokémon Colosseum and Pokémon XD: Gale of Darkness + // Awarded to all Pokémon on the player's team when they complete the Mt. Battle challenge without switching the team at any point. + if (e.RibbonEarth) + { + if (!r.RibbonEarth) + list.Add(Earth); + } + else if (r.RibbonEarth && enc.Generation != 3) + { + list.Add(Earth); + } + + if (r.RibbonNational != e.RibbonNational) + list.Add(National); + if (r.RibbonCountry != e.RibbonCountry) + list.Add(Country); + if (r.RibbonChampionBattle != e.RibbonChampionBattle) + list.Add(ChampionBattle); + if (r.RibbonChampionRegional != e.RibbonChampionRegional) + list.Add(ChampionRegional); + if (r.RibbonChampionNational != e.RibbonChampionNational) + list.Add(ChampionNational); + } + else + { + // The Earth Ribbon is a ribbon exclusive to Pokémon Colosseum and Pokémon XD: Gale of Darkness + // Awarded to all Pokémon on the player's team when they complete the Mt. Battle challenge without switching the team at any point. + if (r.RibbonEarth && enc.Generation != 3) + list.Add(Earth); + + if (r.RibbonNational != RibbonRules.GetValidRibbonStateNational(args.Entity, enc)) + list.Add(National); + if (r.RibbonCountry) + list.Add(Country); + if (r.RibbonChampionBattle) + list.Add(ChampionBattle); + if (r.RibbonChampionRegional) + list.Add(ChampionRegional); + if (r.RibbonChampionNational) + list.Add(ChampionNational); + } + } + + public static void ParseEgg(this IRibbonSetEvent3 r, ref RibbonResultList list) + { + if (r.RibbonEarth) + list.Add(Earth); + if (r.RibbonNational) + list.Add(National); + if (r.RibbonCountry) + list.Add(Country); + if (r.RibbonChampionBattle) + list.Add(ChampionBattle); + if (r.RibbonChampionRegional) + list.Add(ChampionRegional); + if (r.RibbonChampionNational) + list.Add(ChampionNational); + } +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierEvent4.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierEvent4.cs new file mode 100644 index 000000000..b401f87db --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierEvent4.cs @@ -0,0 +1,55 @@ +using static PKHeX.Core.RibbonIndex; + +namespace PKHeX.Core; + +/// +/// Parsing logic for . +/// +public static class RibbonVerifierEvent4 +{ + public static void Parse(this IRibbonSetEvent4 r, RibbonVerifierArguments args, ref RibbonResultList list) + { + var enc = args.Encounter; + if (r.RibbonClassic && enc is not IRibbonSetEvent4 { RibbonClassic: true }) + list.Add(Classic); + if ((r.RibbonWishing && enc is not IRibbonSetEvent4 { RibbonWishing: true }) || (enc is EncounterStatic7 { Species: (int)Species.Magearna } && !r.RibbonWishing)) + list.Add(Wishing); + if (r.RibbonPremier && enc is not IRibbonSetEvent4 { RibbonPremier: true }) + list.Add(Premier); + if (r.RibbonEvent && enc is not IRibbonSetEvent4 { RibbonEvent: true }) + list.Add(Event); + if (r.RibbonBirthday && enc is not IRibbonSetEvent4 { RibbonBirthday: true }) + list.Add(Birthday); + if (r.RibbonSpecial && enc is not IRibbonSetEvent4 { RibbonSpecial: true }) + list.Add(Special); + if (r.RibbonWorld && enc is not IRibbonSetEvent4 { RibbonWorld: true }) + list.Add(World); + if (r.RibbonChampionWorld && enc is not IRibbonSetEvent4 { RibbonChampionWorld: true }) + list.Add(ChampionWorld); + if (r.RibbonSouvenir && enc is not IRibbonSetEvent4 { RibbonSouvenir: true }) + list.Add(Souvenir); + } + + public static void ParseEgg(this IRibbonSetEvent4 r, RibbonVerifierArguments args, ref RibbonResultList list) + { + var enc = args.Encounter; + if (r.RibbonClassic && enc is not IRibbonSetEvent4 { RibbonClassic: true }) + list.Add(Classic); + if (r.RibbonWishing && enc is not IRibbonSetEvent4 { RibbonWishing: true }) + list.Add(Wishing); + if (r.RibbonPremier && enc is not IRibbonSetEvent4 { RibbonPremier: true }) + list.Add(Premier); + if (r.RibbonEvent && enc is not IRibbonSetEvent4 { RibbonEvent: true }) + list.Add(Event); + if (r.RibbonBirthday && enc is not IRibbonSetEvent4 { RibbonBirthday: true }) + list.Add(Birthday); + if (r.RibbonSpecial && enc is not IRibbonSetEvent4 { RibbonSpecial: true }) + list.Add(Special); + if (r.RibbonWorld && enc is not IRibbonSetEvent4 { RibbonWorld: true }) + list.Add(World); + if (r.RibbonChampionWorld && enc is not IRibbonSetEvent4 { RibbonChampionWorld: true }) + list.Add(ChampionWorld); + if (r.RibbonSouvenir && enc is not IRibbonSetEvent4 { RibbonSouvenir: true }) + list.Add(Souvenir); + } +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierOnly3.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierOnly3.cs new file mode 100644 index 000000000..1b8da8a43 --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierOnly3.cs @@ -0,0 +1,49 @@ +using static PKHeX.Core.RibbonIndex3; + +namespace PKHeX.Core; + +/// +/// Parsing logic for . +/// +public static class RibbonVerifierOnly3 +{ + public static void Parse(this IRibbonSetOnly3 r, RibbonVerifierArguments args, ref RibbonResultList list) + { + if (r.RibbonWorld) + list.Add(RibbonIndex.World); + + var max = RibbonRules.IsAllowedContest3(args.History) ? 4 : 0; + FlagContest(r, ref list, max); + } + + private static void FlagContest(IRibbonSetOnly3 r, ref RibbonResultList list, int max = 4) + { + if (r.RibbonCountG3Cool > max) + list.Add(Cool); + if (r.RibbonCountG3Beauty > max) + list.Add(Beauty); + if (r.RibbonCountG3Cute > max) + list.Add(Cute); + if (r.RibbonCountG3Smart > max) + list.Add(Smart); + if (r.RibbonCountG3Tough > max) + list.Add(Tough); + } + + public static void ParseEgg(this IRibbonSetOnly3 r, ref RibbonResultList list) + { + if (r.RibbonWorld) + list.Add(RibbonIndex.World); + + if (r.RibbonCountG3Cool != 0) + list.Add(Cool); + if (r.RibbonCountG3Beauty != 0) + list.Add(Beauty); + if (r.RibbonCountG3Cute != 0) + list.Add(Cute); + if (r.RibbonCountG3Smart != 0) + list.Add(Smart); + if (r.RibbonCountG3Tough != 0) + list.Add(Tough); + } +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierUnique3.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierUnique3.cs new file mode 100644 index 000000000..150010fd0 --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierUnique3.cs @@ -0,0 +1,37 @@ +using static PKHeX.Core.RibbonIndex3; + +namespace PKHeX.Core; + +/// +/// Parsing logic for . +/// +public static class RibbonVerifierUnique3 +{ + public static void Parse(this IRibbonSetUnique3 r, RibbonVerifierArguments args, ref RibbonResultList list) + { + var evos = args.History; + if (evos.HasVisitedGen3) + { + PKM pk = args.Entity; + if (r.RibbonWinning && !RibbonRules.IsRibbonValidWinning(pk, args.Encounter, evos)) + list.Add(Winning); + if (r.RibbonVictory && !RibbonRules.IsRibbonValidVictory(evos)) + list.Add(Victory); + } + else // Gen4/5 + { + if (r.RibbonWinning) + list.Add(Winning); + if (r.RibbonVictory) + list.Add(Victory); + } + } + + public static void ParseEgg(this IRibbonSetUnique3 r, ref RibbonResultList list) + { + if (r.RibbonWinning) + list.Add(Winning); + if (r.RibbonVictory) + list.Add(Victory); + } +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierUnique4.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierUnique4.cs new file mode 100644 index 000000000..21e889293 --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifierUnique4.cs @@ -0,0 +1,135 @@ +namespace PKHeX.Core; + +/// +/// Parsing logic for . +/// +public static class RibbonVerifierUnique4 +{ + public static void Parse(this IRibbonSetUnique4 r, RibbonVerifierArguments args, ref RibbonResultList list) + { + var evos = args.History; + if (!RibbonRules.IsAllowedBattleFrontier4(evos)) + FlagAnyAbility(r, ref list); + + if (RibbonRules.IsAllowedContest3(evos)) + AddMissingContest3(r, ref list); + else + FlagAnyContest3(r, ref list); + + if (RibbonRules.IsAllowedContest4(evos)) + AddMissingContest4(r, ref list); + else + FlagAnyContest4(r, ref list); + } + + public static void ParseEgg(this IRibbonSetUnique4 r, ref RibbonResultList list) + { + FlagAnyAbility(r, ref list); + FlagAnyContest3(r, ref list); + FlagAnyContest4(r, ref list); + } + + private static void AddMissingContest3(IRibbonSetUnique4 r, ref RibbonResultList list) + { + static void CheckSet(bool Master, bool Hyper, bool Super, bool Initial, ref RibbonResultList list, RibbonIndex3 index) + { + bool top = Master; + if (Hyper) + top = true; + else if (top) + list.Add((RibbonIndex3)((byte)index + 2)); + + if (Super) + top = true; + else if (top) + list.Add((RibbonIndex3)((byte)index + 1)); + + if (top && !Initial) + list.Add(index); + } + CheckSet(r.RibbonG3CoolMaster, r.RibbonG3CoolHyper, r.RibbonG3CoolSuper, r.RibbonG3Cool, ref list, RibbonIndex3.Cool); + CheckSet(r.RibbonG3BeautyMaster, r.RibbonG3BeautyHyper, r.RibbonG3BeautySuper, r.RibbonG3Beauty, ref list, RibbonIndex3.Beauty); + CheckSet(r.RibbonG3CuteMaster , r.RibbonG3CuteHyper, r.RibbonG3CuteSuper, r.RibbonG3Cute, ref list, RibbonIndex3.Cute); + CheckSet(r.RibbonG3SmartMaster, r.RibbonG3SmartHyper, r.RibbonG3SmartSuper, r.RibbonG3Smart, ref list, RibbonIndex3.Smart); + CheckSet(r.RibbonG3ToughMaster, r.RibbonG3ToughHyper, r.RibbonG3ToughSuper, r.RibbonG3Tough, ref list, RibbonIndex3.Tough); + } + + private static void AddMissingContest4(IRibbonSetUnique4 r, ref RibbonResultList list) + { + static void CheckSet(bool Master, bool Hyper, bool Super, bool Initial, ref RibbonResultList list, RibbonIndex4 index) + { + bool top = Master; + if (Hyper) + top = true; + else if (top) + list.Add((RibbonIndex4)((byte)index + 2)); + + if (Super) + top = true; + else if (top) + list.Add((RibbonIndex4)((byte)index + 1)); + + if (top && !Initial) + list.Add(index); + } + CheckSet(r.RibbonG3CoolMaster, r.RibbonG3CoolHyper, r.RibbonG3CoolSuper, r.RibbonG3Cool, ref list, RibbonIndex4.Cool); + CheckSet(r.RibbonG3BeautyMaster, r.RibbonG3BeautyHyper, r.RibbonG3BeautySuper, r.RibbonG3Beauty, ref list, RibbonIndex4.Beauty); + CheckSet(r.RibbonG3CuteMaster , r.RibbonG3CuteHyper, r.RibbonG3CuteSuper, r.RibbonG3Cute, ref list, RibbonIndex4.Cute); + CheckSet(r.RibbonG3SmartMaster, r.RibbonG3SmartHyper, r.RibbonG3SmartSuper, r.RibbonG3Smart, ref list, RibbonIndex4.Smart); + CheckSet(r.RibbonG3ToughMaster, r.RibbonG3ToughHyper, r.RibbonG3ToughSuper, r.RibbonG3Tough, ref list, RibbonIndex4.Tough); + } + + private static void FlagAnyAbility(IRibbonSetUnique4 r, ref RibbonResultList list) + { + if (r.RibbonAbility) + list.Add(RibbonIndex4.Ability); + if (r.RibbonAbilityGreat) + list.Add(RibbonIndex4.AbilityGreat); + if (r.RibbonAbilityDouble) + list.Add(RibbonIndex4.AbilityDouble); + if (r.RibbonAbilityMulti) + list.Add(RibbonIndex4.AbilityMulti); + if (r.RibbonAbilityPair) + list.Add(RibbonIndex4.AbilityPair); + } + + private static void FlagAnyContest3(IRibbonSetUnique4 r, ref RibbonResultList list) + { + static void CheckSet(bool Master, bool Hyper, bool Super, bool Initial, ref RibbonResultList list, RibbonIndex3 index) + { + if (Master) + list.Add((RibbonIndex3)((byte)index + 3)); + if (Hyper) + list.Add((RibbonIndex3)((byte)index + 2)); + if (Super) + list.Add((RibbonIndex3)((byte)index + 1)); + if (Initial) + list.Add(index); + } + CheckSet(r.RibbonG3CoolMaster, r.RibbonG3CoolHyper, r.RibbonG3CoolSuper, r.RibbonG3Cool, ref list, RibbonIndex3.Cool); + CheckSet(r.RibbonG3BeautyMaster, r.RibbonG3BeautyHyper, r.RibbonG3BeautySuper, r.RibbonG3Beauty, ref list, RibbonIndex3.Beauty); + CheckSet(r.RibbonG3CuteMaster , r.RibbonG3CuteHyper, r.RibbonG3CuteSuper, r.RibbonG3Cute, ref list, RibbonIndex3.Cute); + CheckSet(r.RibbonG3SmartMaster, r.RibbonG3SmartHyper, r.RibbonG3SmartSuper, r.RibbonG3Smart, ref list, RibbonIndex3.Smart); + CheckSet(r.RibbonG3ToughMaster, r.RibbonG3ToughHyper, r.RibbonG3ToughSuper, r.RibbonG3Tough, ref list, RibbonIndex3.Tough); + } + + private static void FlagAnyContest4(IRibbonSetUnique4 r, ref RibbonResultList list) + { + static void CheckSet(bool Master, bool Hyper, bool Super, bool Initial, ref RibbonResultList list, RibbonIndex4 index) + { + if (Master) + list.Add((RibbonIndex4)((byte)index + 3)); + if (Hyper) + list.Add((RibbonIndex4)((byte)index + 2)); + if (Super) + list.Add((RibbonIndex4)((byte)index + 1)); + if (Initial) + list.Add(index); + } + CheckSet(r.RibbonG3CoolMaster, r.RibbonG3CoolHyper, r.RibbonG3CoolSuper, r.RibbonG3Cool, ref list, RibbonIndex4.Cool); + CheckSet(r.RibbonG3BeautyMaster, r.RibbonG3BeautyHyper, r.RibbonG3BeautySuper, r.RibbonG3Beauty, ref list, RibbonIndex4.Beauty); + CheckSet(r.RibbonG3CuteMaster , r.RibbonG3CuteHyper, r.RibbonG3CuteSuper, r.RibbonG3Cute, ref list, RibbonIndex4.Cute); + CheckSet(r.RibbonG3SmartMaster, r.RibbonG3SmartHyper, r.RibbonG3SmartSuper, r.RibbonG3Smart, ref list, RibbonIndex4.Smart); + CheckSet(r.RibbonG3ToughMaster, r.RibbonG3ToughHyper, r.RibbonG3ToughSuper, r.RibbonG3Tough, ref list, RibbonIndex4.Tough); + } +} diff --git a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_de.txt b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_de.txt index 4e1896153..ff7f06a50 100644 --- a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_de.txt +++ b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_de.txt @@ -347,8 +347,8 @@ LPokerusDaysTooHigh_0 = Pokérus Days Remaining value is too high; expected <= { LPokerusStrainUnobtainable_0 = Pokérus Strain {0} cannot be obtained. LRibbonAllValid = All ribbons accounted for. LRibbonEgg = Can't receive Ribbon(s) as an Egg. -LRibbonFInvalid_0 = Invalid Ribbons: {0} -LRibbonFMissing_0 = Missing Ribbons: {0} +LRibbonFInvalid_0 = Invalid Ribbons: +LRibbonFMissing_0 = Missing Ribbons: LRibbonMarkingAffixedF_0 = Invalid Affixed Ribbon/Marking: {0} LRibbonMarkingFInvalid_0 = Invalid Marking: {0} LStatAlphaInvalid = Alpha Flag mismatch. diff --git a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_en.txt b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_en.txt index fc182b42c..c307ef076 100644 --- a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_en.txt +++ b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_en.txt @@ -347,8 +347,8 @@ LPokerusDaysTooHigh_0 = Pokérus Days Remaining value is too high; expected <= { LPokerusStrainUnobtainable_0 = Pokérus Strain {0} cannot be obtained. LRibbonAllValid = All ribbons accounted for. LRibbonEgg = Can't receive Ribbon(s) as an Egg. -LRibbonFInvalid_0 = Invalid Ribbons: {0} -LRibbonFMissing_0 = Missing Ribbons: {0} +LRibbonFInvalid_0 = Invalid Ribbons: +LRibbonFMissing_0 = Missing Ribbons: LRibbonMarkingAffixedF_0 = Invalid Affixed Ribbon/Marking: {0} LRibbonMarkingFInvalid_0 = Invalid Marking: {0} LStatAlphaInvalid = Alpha Flag mismatch. diff --git a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_es.txt b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_es.txt index 37b85b221..b695806ee 100644 --- a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_es.txt +++ b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_es.txt @@ -347,8 +347,8 @@ LPokerusDaysTooHigh_0 = Pokérus Days Remaining value is too high; expected <= { LPokerusStrainUnobtainable_0 = Pokérus Strain {0} cannot be obtained. LRibbonAllValid = Todas las cintas están justificadas. LRibbonEgg = No se pueden recibir Cintas siendo un Huevo. -LRibbonFInvalid_0 = Cintas inválidas: {0} -LRibbonFMissing_0 = Cintas que faltan: {0} +LRibbonFInvalid_0 = Cintas inválidas: +LRibbonFMissing_0 = Cintas que faltan: LRibbonMarkingAffixedF_0 = Cinta / marca pegada no válida: {0} LRibbonMarkingFInvalid_0 = Marca Inválida: {0} LStatAlphaInvalid = Alpha Flag mismatch. diff --git a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_fr.txt b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_fr.txt index 47c3b50f0..3a96263c4 100644 --- a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_fr.txt +++ b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_fr.txt @@ -347,8 +347,8 @@ LPokerusDaysTooHigh_0 = Pokérus Days Remaining value is too high; expected <= { LPokerusStrainUnobtainable_0 = Pokérus Strain {0} cannot be obtained. LRibbonAllValid = Tous les rubans ont été comptabilisés. LRibbonEgg = L'Œuf ne peut pas recevoir de Rubans. -LRibbonFInvalid_0 = Rubans invalides : {0} -LRibbonFMissing_0 = Rubans manquants : {0} +LRibbonFInvalid_0 = Rubans invalides : +LRibbonFMissing_0 = Rubans manquants : LRibbonMarkingAffixedF_0 = Ruban / marquage apposé non valide: {0} LRibbonMarkingFInvalid_0 = Marquage non valide: {0} LStatAlphaInvalid = Alpha Flag mismatch. diff --git a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_it.txt b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_it.txt index 5c929a887..57ce94aac 100644 --- a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_it.txt +++ b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_it.txt @@ -347,8 +347,8 @@ LPokerusDaysTooHigh_0 = I giorni di Pokérus rimanenti sono troppi, previsto <= LPokerusStrainUnobtainable_0 = Pokérus Strain {0} non può essere ottenuta. LRibbonAllValid = Tutti i fiocchi sono stati contabilizzati. LRibbonEgg = Non può avere fiocchi da Uovo. -LRibbonFInvalid_0 = Fiocchi invalidi: {0} -LRibbonFMissing_0 = Fiocchi mancanti: {0} +LRibbonFInvalid_0 = Fiocchi invalidi: +LRibbonFMissing_0 = Fiocchi mancanti: LRibbonMarkingAffixedF_0 = Fioccho o Emblema fissato invalido: {0} LRibbonMarkingFInvalid_0 = Emblema invalido: {0} LStatAlphaInvalid = Alfa non corrispondente. diff --git a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_ja.txt b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_ja.txt index c4dc6ae5d..9a573a1db 100644 --- a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_ja.txt +++ b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_ja.txt @@ -347,8 +347,8 @@ LPokerusDaysTooHigh_0 = Pokérus Days Remaining value is too high; expected <= { LPokerusStrainUnobtainable_0 = Pokérus Strain {0} cannot be obtained. LRibbonAllValid = All ribbons accounted for. LRibbonEgg = タマゴにリボンは設定できません -LRibbonFInvalid_0 = 無効なリボンが設定されています: {0} -LRibbonFMissing_0 = 次のリボンが不足しています: {0} +LRibbonFInvalid_0 = 無効なリボンが設定されています: +LRibbonFMissing_0 = 次のリボンが不足しています: LRibbonMarkingAffixedF_0 = Invalid Affixed Ribbon/Marking: {0} LRibbonMarkingFInvalid_0 = Invalid Marking: {0} LStatAlphaInvalid = Alpha Flag mismatch. diff --git a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_ko.txt b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_ko.txt index a1d574ad5..7bb1e72f4 100644 --- a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_ko.txt +++ b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_ko.txt @@ -347,8 +347,8 @@ LPokerusDaysTooHigh_0 = Pokérus Days Remaining value is too high; expected <= { LPokerusStrainUnobtainable_0 = Pokérus Strain {0} cannot be obtained. LRibbonAllValid = 모든 리본이 채워졌습니다. LRibbonEgg = 알은 리본을 얻을 수 없습니다. -LRibbonFInvalid_0 = 사용할 수 없는 리본: {0} -LRibbonFMissing_0 = 없는 리본: {0} +LRibbonFInvalid_0 = 사용할 수 없는 리본: +LRibbonFMissing_0 = 없는 리본: LRibbonMarkingAffixedF_0 = Invalid Affixed Ribbon/Marking: {0} LRibbonMarkingFInvalid_0 = Invalid Marking: {0} LStatAlphaInvalid = Alpha Flag mismatch. diff --git a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_zh.txt b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_zh.txt index 4e5e5b5b1..7f1626045 100644 --- a/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_zh.txt +++ b/PKHeX.Core/Resources/legality/checks/LegalityCheckStrings_zh.txt @@ -347,8 +347,8 @@ LPokerusDaysTooHigh_0 = Pokérus Days Remaining value is too high; expected <= { LPokerusStrainUnobtainable_0 = Pokérus Strain {0} cannot be obtained. LRibbonAllValid = 所有奖章合法。 LRibbonEgg = 蛋不能接受奖章。 -LRibbonFInvalid_0 = 不合法奖章: {0} -LRibbonFMissing_0 = 缺失奖章: {0} +LRibbonFInvalid_0 = 不合法奖章: +LRibbonFMissing_0 = 缺失奖章: LRibbonMarkingAffixedF_0 = 无效的奖章/证章: {0} LRibbonMarkingFInvalid_0 = 无效标记: {0} LStatAlphaInvalid = Alpha Flag mismatch. diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon3.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon3.cs index bf42d59c7..50f985100 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetCommon3.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetCommon3.cs @@ -1,4 +1,4 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; /// Common Ribbons introduced in Generation 3 public interface IRibbonSetCommon3 @@ -10,23 +10,6 @@ public interface IRibbonSetCommon3 internal static partial class RibbonExtensions { - private static readonly string[] RibbonSetNamesCommon3 = - { - nameof(IRibbonSetCommon3.RibbonChampionG3), nameof(IRibbonSetCommon3.RibbonArtist), nameof(IRibbonSetCommon3.RibbonEffort), - }; - - internal static bool[] RibbonBits(this IRibbonSetCommon3 set) - { - return new[] - { - set.RibbonChampionG3, - set.RibbonArtist, - set.RibbonEffort, - }; - } - - internal static string[] RibbonNames(this IRibbonSetCommon3 _) => RibbonSetNamesCommon3; - internal static void CopyRibbonSetCommon3(this IRibbonSetCommon3 set, IRibbonSetCommon3 dest) { dest.RibbonChampionG3 = set.RibbonChampionG3; diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon4.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon4.cs index 8bb77e06d..8d091c10f 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetCommon4.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetCommon4.cs @@ -1,4 +1,4 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; /// Common Ribbons introduced in Generation 4 public interface IRibbonSetCommon4 @@ -21,63 +21,6 @@ public interface IRibbonSetCommon4 internal static partial class RibbonExtensions { - private static readonly string[] RibbonSetNamesCommon4 = - { - nameof(IRibbonSetCommon4.RibbonGorgeous), nameof(IRibbonSetCommon4.RibbonRoyal), nameof(IRibbonSetCommon4.RibbonGorgeousRoyal), - }; - - internal static bool[] RibbonBitsCosmetic(this IRibbonSetCommon4 set) - { - return new[] - { - set.RibbonGorgeous, - set.RibbonRoyal, - set.RibbonGorgeousRoyal, - }; - } - - internal static string[] RibbonNamesCosmetic(this IRibbonSetCommon4 _) => RibbonSetNamesCommon4; - - private static readonly string[] RibbonSetNamesCommon4Only = - { - nameof(IRibbonSetCommon4.RibbonRecord), nameof(IRibbonSetCommon4.RibbonChampionSinnoh), nameof(IRibbonSetCommon4.RibbonLegend), - }; - - internal static bool[] RibbonBitsOnly(this IRibbonSetCommon4 set) - { - return new[] - { - set.RibbonRecord, - set.RibbonChampionSinnoh, - set.RibbonLegend, - }; - } - - internal static string[] RibbonNamesOnly(this IRibbonSetCommon4 _) => RibbonSetNamesCommon4Only; - - private static readonly string[] RibbonSetNamesCommon4Daily = - { - nameof(IRibbonSetCommon4.RibbonAlert), nameof(IRibbonSetCommon4.RibbonShock), - nameof(IRibbonSetCommon4.RibbonDowncast), nameof(IRibbonSetCommon4.RibbonCareless), nameof(IRibbonSetCommon4.RibbonRelax), - nameof(IRibbonSetCommon4.RibbonSnooze), nameof(IRibbonSetCommon4.RibbonSmile), - }; - - internal static bool[] RibbonBitsDaily(this IRibbonSetCommon4 set) - { - return new[] - { - set.RibbonAlert, - set.RibbonShock, - set.RibbonDowncast, - set.RibbonCareless, - set.RibbonRelax, - set.RibbonSnooze, - set.RibbonSmile, - }; - } - - internal static string[] RibbonNamesDaily(this IRibbonSetCommon4 _) => RibbonSetNamesCommon4Daily; - internal static void CopyRibbonSetCommon4(this IRibbonSetCommon4 set, IRibbonSetCommon4 dest) { dest.RibbonChampionSinnoh = set.RibbonChampionSinnoh; diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon6.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon6.cs index f6c7a86e9..4e92d327a 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetCommon6.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetCommon6.cs @@ -1,4 +1,4 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; /// Common Ribbons introduced in Generation 6 public interface IRibbonSetCommon6 @@ -23,55 +23,10 @@ public interface IRibbonSetCommon6 internal static partial class RibbonExtensions { - private static readonly string[] RibbonSetNamesCommon6Bool = - { - nameof(IRibbonSetCommon6.RibbonChampionKalos), nameof(IRibbonSetCommon6.RibbonChampionG6Hoenn), // nameof(IRibbonSetCommon6.RibbonBestFriends), - nameof(IRibbonSetCommon6.RibbonTraining), nameof(IRibbonSetCommon6.RibbonBattlerSkillful), nameof(IRibbonSetCommon6.RibbonBattlerExpert), - nameof(IRibbonSetCommon6.RibbonContestStar), nameof(IRibbonSetCommon6.RibbonMasterCoolness), nameof(IRibbonSetCommon6.RibbonMasterBeauty), - nameof(IRibbonSetCommon6.RibbonMasterCuteness), nameof(IRibbonSetCommon6.RibbonMasterCleverness), nameof(IRibbonSetCommon6.RibbonMasterToughness), - }; - - private static readonly string[] RibbonSetNamesCommon6Contest = - { - nameof(IRibbonSetCommon6.RibbonMasterCoolness), nameof(IRibbonSetCommon6.RibbonMasterBeauty), - nameof(IRibbonSetCommon6.RibbonMasterCuteness), nameof(IRibbonSetCommon6.RibbonMasterCleverness), - nameof(IRibbonSetCommon6.RibbonMasterToughness), - }; - - internal static bool[] RibbonBits(this IRibbonSetCommon6 set) - { - return new[] - { - set.RibbonChampionKalos, - set.RibbonChampionG6Hoenn, - //set.RibbonBestFriends, - set.RibbonTraining, - set.RibbonBattlerSkillful, - set.RibbonBattlerExpert, - - set.RibbonContestStar, - set.RibbonMasterCoolness, - set.RibbonMasterBeauty, - set.RibbonMasterCuteness, - set.RibbonMasterCleverness, - set.RibbonMasterToughness, - }; - } - - internal static bool[] RibbonBitsContest(this IRibbonSetCommon6 set) - { - return new[] - { - set.RibbonMasterCoolness, - set.RibbonMasterBeauty, - set.RibbonMasterCuteness, - set.RibbonMasterCleverness, - set.RibbonMasterToughness, - }; - } - - internal static string[] RibbonNamesBool(this IRibbonSetCommon6 _) => RibbonSetNamesCommon6Bool; - internal static string[] RibbonNamesContest(this IRibbonSetCommon6 _) => RibbonSetNamesCommon6Contest; + /// + /// Checks if the has all five contest stat ribbons true. + /// + public static bool HasAllContestRibbons(this IRibbonSetCommon6 set) => set.RibbonMasterCoolness && set.RibbonMasterBeauty && set.RibbonMasterCuteness && set.RibbonMasterCleverness && set.RibbonMasterToughness; internal static void CopyRibbonSetCommon6(this IRibbonSetCommon6 set, IRibbonSetCommon6 dest) { diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon7.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon7.cs index 943da5811..b38722186 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetCommon7.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetCommon7.cs @@ -1,4 +1,4 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; /// Common Ribbons introduced in Generation 7 public interface IRibbonSetCommon7 @@ -11,25 +11,6 @@ public interface IRibbonSetCommon7 internal static partial class RibbonExtensions { - private static readonly string[] RibbonSetNamesCommon7 = - { - nameof(IRibbonSetCommon7.RibbonChampionAlola), nameof(IRibbonSetCommon7.RibbonBattleRoyale), - nameof(IRibbonSetCommon7.RibbonBattleTreeGreat), nameof(IRibbonSetCommon7.RibbonBattleTreeMaster), - }; - - internal static bool[] RibbonBits(this IRibbonSetCommon7 set) - { - return new[] - { - set.RibbonChampionAlola, - set.RibbonBattleRoyale, - set.RibbonBattleTreeGreat, - set.RibbonBattleTreeMaster, - }; - } - - internal static string[] RibbonNames(this IRibbonSetCommon7 _) => RibbonSetNamesCommon7; - internal static void CopyRibbonSetCommon7(this IRibbonSetCommon7 set, IRibbonSetCommon7 dest) { dest.RibbonChampionAlola = set.RibbonChampionAlola; diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon8.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon8.cs index 4b99a9cce..196fae0a5 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetCommon8.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetCommon8.cs @@ -1,4 +1,4 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; /// Common Ribbons introduced in Generation 8 public interface IRibbonSetCommon8 @@ -12,27 +12,6 @@ public interface IRibbonSetCommon8 internal static partial class RibbonExtensions { - private static readonly string[] RibbonSetNamesCommon8 = - { - nameof(IRibbonSetCommon8.RibbonChampionGalar), nameof(IRibbonSetCommon8.RibbonTowerMaster), - nameof(IRibbonSetCommon8.RibbonMasterRank), - nameof(IRibbonSetCommon8.RibbonTwinklingStar), nameof(IRibbonSetCommon8.RibbonPioneer), - }; - - internal static bool[] RibbonBits(this IRibbonSetCommon8 set) - { - return new[] - { - set.RibbonChampionGalar, - set.RibbonTowerMaster, - set.RibbonMasterRank, - set.RibbonTwinklingStar, - set.RibbonPioneer, - }; - } - - internal static string[] RibbonNames(this IRibbonSetCommon8 _) => RibbonSetNamesCommon8; - internal static void CopyRibbonSetCommon8(this IRibbonSetCommon8 set, IRibbonSetCommon8 dest) { dest.RibbonChampionGalar = set.RibbonChampionGalar; diff --git a/PKHeX.Core/Ribbons/IRibbonSetEvent3.cs b/PKHeX.Core/Ribbons/IRibbonSetEvent3.cs index 7861167d5..294f8780c 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetEvent3.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetEvent3.cs @@ -1,4 +1,4 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; /// Ribbons introduced in Generation 3 for Special Events public interface IRibbonSetEvent3 @@ -13,27 +13,6 @@ public interface IRibbonSetEvent3 internal static partial class RibbonExtensions { - private static readonly string[] RibbonSetNamesEvent3 = - { - nameof(IRibbonSetEvent3.RibbonEarth), nameof(IRibbonSetEvent3.RibbonNational), nameof(IRibbonSetEvent3.RibbonCountry), - nameof(IRibbonSetEvent3.RibbonChampionBattle), nameof(IRibbonSetEvent3.RibbonChampionRegional), nameof(IRibbonSetEvent3.RibbonChampionNational), - }; - - internal static bool[] RibbonBits(this IRibbonSetEvent3 set) - { - return new[] - { - set.RibbonEarth, - set.RibbonNational, - set.RibbonCountry, - set.RibbonChampionBattle, - set.RibbonChampionRegional, - set.RibbonChampionNational, - }; - } - - internal static string[] RibbonNames(this IRibbonSetEvent3 _) => RibbonSetNamesEvent3; - internal static void CopyRibbonSetEvent3(this IRibbonSetEvent3 set, IRibbonSetEvent3 dest) { dest.RibbonEarth = set.RibbonEarth; diff --git a/PKHeX.Core/Ribbons/IRibbonSetEvent4.cs b/PKHeX.Core/Ribbons/IRibbonSetEvent4.cs index 048ef8f64..98d663fa9 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetEvent4.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetEvent4.cs @@ -1,4 +1,4 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; /// Ribbons introduced in Generation 4 for Special Events public interface IRibbonSetEvent4 @@ -16,31 +16,6 @@ public interface IRibbonSetEvent4 internal static partial class RibbonExtensions { - private static readonly string[] RibbonSetNamesEvent4 = - { - nameof(IRibbonSetEvent4.RibbonClassic), nameof(IRibbonSetEvent4.RibbonWishing), nameof(IRibbonSetEvent4.RibbonPremier), - nameof(IRibbonSetEvent4.RibbonEvent), nameof(IRibbonSetEvent4.RibbonBirthday), nameof(IRibbonSetEvent4.RibbonSpecial), - nameof(IRibbonSetEvent4.RibbonWorld), nameof(IRibbonSetEvent4.RibbonChampionWorld), nameof(IRibbonSetEvent4.RibbonSouvenir), - }; - - internal static bool[] RibbonBits(this IRibbonSetEvent4 set) - { - return new[] - { - set.RibbonClassic, - set.RibbonWishing, - set.RibbonPremier, - set.RibbonEvent, - set.RibbonBirthday, - set.RibbonSpecial, - set.RibbonWorld, - set.RibbonChampionWorld, - set.RibbonSouvenir, - }; - } - - internal static string[] RibbonNames(this IRibbonSetEvent4 _) => RibbonSetNamesEvent4; - internal static void CopyRibbonSetEvent4(this IRibbonSetEvent4 set, IRibbonSetEvent4 dest) { dest.RibbonClassic = set.RibbonClassic; diff --git a/PKHeX.Core/Ribbons/IRibbonSetOnly3.cs b/PKHeX.Core/Ribbons/IRibbonSetOnly3.cs index b8055f1a3..4ea797d90 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetOnly3.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetOnly3.cs @@ -1,4 +1,4 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; /// Ribbons that originated in Generation 3 and were only present within that Generation. public interface IRibbonSetOnly3 @@ -15,18 +15,3 @@ public interface IRibbonSetOnly3 bool Unused3 { get; set; } bool Unused4 { get; set; } } - -internal static partial class RibbonExtensions -{ - private static readonly string[] RibbonSetNamesOnly3 = - { - nameof(IRibbonSetOnly3.RibbonCountG3Cool), nameof(IRibbonSetOnly3.RibbonCountG3Beauty), nameof(IRibbonSetOnly3.RibbonCountG3Cute), - nameof(IRibbonSetOnly3.RibbonCountG3Smart), nameof(IRibbonSetOnly3.RibbonCountG3Tough), - - nameof(IRibbonSetOnly3.RibbonWorld), - nameof(IRibbonSetOnly3.Unused1), nameof(IRibbonSetOnly3.Unused2), - nameof(IRibbonSetOnly3.Unused3), nameof(IRibbonSetOnly3.Unused4), - }; - - internal static string[] RibbonNames(this IRibbonSetOnly3 _) => RibbonSetNamesOnly3; -} diff --git a/PKHeX.Core/Ribbons/IRibbonSetUnique3.cs b/PKHeX.Core/Ribbons/IRibbonSetUnique3.cs index b4f1d364a..2faa9ab6f 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetUnique3.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetUnique3.cs @@ -1,4 +1,4 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; /// Ribbons introduced in Generation 3 and were transferred to future Generations (4 and 5 only). public interface IRibbonSetUnique3 @@ -9,22 +9,3 @@ public interface IRibbonSetUnique3 /// Ribbon awarded for clearing Hoenn's Battle Tower's Lv. 100 challenge. bool RibbonVictory { get; set; } } - -internal static partial class RibbonExtensions -{ - private static readonly string[] RibbonSetNamesUnique3 = - { - nameof(IRibbonSetUnique3.RibbonWinning), nameof(IRibbonSetUnique3.RibbonVictory), - }; - - internal static bool[] RibbonBits(this IRibbonSetUnique3 set) - { - return new[] - { - set.RibbonWinning, - set.RibbonVictory, - }; - } - - internal static string[] RibbonNames(this IRibbonSetUnique3 _) => RibbonSetNamesUnique3; -} diff --git a/PKHeX.Core/Ribbons/IRibbonSetUnique4.cs b/PKHeX.Core/Ribbons/IRibbonSetUnique4.cs index 0f0f2f093..6dcba1064 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetUnique4.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetUnique4.cs @@ -52,143 +52,3 @@ public interface IRibbonSetUnique4 bool RibbonG4ToughUltra { get; set; } bool RibbonG4ToughMaster { get; set; } } - -internal static partial class RibbonExtensions -{ - private static readonly string[] RibbonSetNamesUnique4Ability = - { - nameof(IRibbonSetUnique4.RibbonAbility), - nameof(IRibbonSetUnique4.RibbonAbilityGreat), - nameof(IRibbonSetUnique4.RibbonAbilityDouble), - nameof(IRibbonSetUnique4.RibbonAbilityMulti), - nameof(IRibbonSetUnique4.RibbonAbilityPair), - nameof(IRibbonSetUnique4.RibbonAbilityWorld), - }; - - private static readonly string[] RibbonSetNamesUnique4Contest3 = - { - nameof(IRibbonSetUnique4.RibbonG3Cool), - nameof(IRibbonSetUnique4.RibbonG3CoolSuper), - nameof(IRibbonSetUnique4.RibbonG3CoolHyper), - nameof(IRibbonSetUnique4.RibbonG3CoolMaster), - nameof(IRibbonSetUnique4.RibbonG3Beauty), - nameof(IRibbonSetUnique4.RibbonG3BeautySuper), - nameof(IRibbonSetUnique4.RibbonG3BeautyHyper), - nameof(IRibbonSetUnique4.RibbonG3BeautyMaster), - nameof(IRibbonSetUnique4.RibbonG3Cute), - nameof(IRibbonSetUnique4.RibbonG3CuteSuper), - nameof(IRibbonSetUnique4.RibbonG3CuteHyper), - nameof(IRibbonSetUnique4.RibbonG3CuteMaster), - nameof(IRibbonSetUnique4.RibbonG3Smart), - nameof(IRibbonSetUnique4.RibbonG3SmartSuper), - nameof(IRibbonSetUnique4.RibbonG3SmartHyper), - nameof(IRibbonSetUnique4.RibbonG3SmartMaster), - nameof(IRibbonSetUnique4.RibbonG3Tough), - nameof(IRibbonSetUnique4.RibbonG3ToughSuper), - nameof(IRibbonSetUnique4.RibbonG3ToughHyper), - nameof(IRibbonSetUnique4.RibbonG3ToughMaster), - }; - - private static readonly string[] RibbonSetNamesUnique4Contest4 = - { - nameof(IRibbonSetUnique4.RibbonG4Cool), - nameof(IRibbonSetUnique4.RibbonG4CoolGreat), - nameof(IRibbonSetUnique4.RibbonG4CoolUltra), - nameof(IRibbonSetUnique4.RibbonG4CoolMaster), - nameof(IRibbonSetUnique4.RibbonG4Beauty), - nameof(IRibbonSetUnique4.RibbonG4BeautyGreat), - nameof(IRibbonSetUnique4.RibbonG4BeautyUltra), - nameof(IRibbonSetUnique4.RibbonG4BeautyMaster), - nameof(IRibbonSetUnique4.RibbonG4Cute), - nameof(IRibbonSetUnique4.RibbonG4CuteGreat), - nameof(IRibbonSetUnique4.RibbonG4CuteUltra), - nameof(IRibbonSetUnique4.RibbonG4CuteMaster), - nameof(IRibbonSetUnique4.RibbonG4Smart), - nameof(IRibbonSetUnique4.RibbonG4SmartGreat), - nameof(IRibbonSetUnique4.RibbonG4SmartUltra), - nameof(IRibbonSetUnique4.RibbonG4SmartMaster), - nameof(IRibbonSetUnique4.RibbonG4Tough), - nameof(IRibbonSetUnique4.RibbonG4ToughGreat), - nameof(IRibbonSetUnique4.RibbonG4ToughUltra), - nameof(IRibbonSetUnique4.RibbonG4ToughMaster), - }; - - internal static bool[] RibbonBitsAbility(this IRibbonSetUnique4 set) - { - return new[] - { - set.RibbonAbility, - set.RibbonAbilityGreat, - set.RibbonAbilityDouble, - set.RibbonAbilityMulti, - set.RibbonAbilityPair, - set.RibbonAbilityWorld, - }; - } - - internal static bool[] RibbonBitsContest3(this IRibbonSetUnique4 set) - { - return new[] - { - set.RibbonG3Cool, - set.RibbonG3CoolSuper, - set.RibbonG3CoolHyper, - set.RibbonG3CoolMaster, - - set.RibbonG3Beauty, - set.RibbonG3BeautySuper, - set.RibbonG3BeautyHyper, - set.RibbonG3BeautyMaster, - - set.RibbonG3Cute, - set.RibbonG3CuteSuper, - set.RibbonG3CuteHyper, - set.RibbonG3CuteMaster, - - set.RibbonG3Smart, - set.RibbonG3SmartSuper, - set.RibbonG3SmartHyper, - set.RibbonG3SmartMaster, - - set.RibbonG3Tough, - set.RibbonG3ToughSuper, - set.RibbonG3ToughHyper, - set.RibbonG3ToughMaster, - }; - } - - internal static bool[] RibbonBitsContest4(this IRibbonSetUnique4 set) - { - return new[] - { - set.RibbonG4Cool, - set.RibbonG4CoolGreat, - set.RibbonG4CoolUltra, - set.RibbonG4CoolMaster, - - set.RibbonG4Beauty, - set.RibbonG4BeautyGreat, - set.RibbonG4BeautyUltra, - set.RibbonG4BeautyMaster, - - set.RibbonG4Cute, - set.RibbonG4CuteGreat, - set.RibbonG4CuteUltra, - set.RibbonG4CuteMaster, - - set.RibbonG4Smart, - set.RibbonG4SmartGreat, - set.RibbonG4SmartUltra, - set.RibbonG4SmartMaster, - - set.RibbonG4Tough, - set.RibbonG4ToughGreat, - set.RibbonG4ToughUltra, - set.RibbonG4ToughMaster, - }; - } - - internal static string[] RibbonNamesAbility(this IRibbonSetUnique4 _) => RibbonSetNamesUnique4Ability; - internal static string[] RibbonNamesContest3(this IRibbonSetUnique4 _) => RibbonSetNamesUnique4Contest3; - internal static string[] RibbonNamesContest4(this IRibbonSetUnique4 _) => RibbonSetNamesUnique4Contest4; -} diff --git a/PKHeX.Core/Ribbons/RibbonIndex.cs b/PKHeX.Core/Ribbons/RibbonIndex.cs index c1b57c33a..97d364480 100644 --- a/PKHeX.Core/Ribbons/RibbonIndex.cs +++ b/PKHeX.Core/Ribbons/RibbonIndex.cs @@ -1,9 +1,12 @@ -namespace PKHeX.Core; +using System; +using static PKHeX.Core.RibbonIndex; + +namespace PKHeX.Core; /// /// Ribbon Indexes for Generation 8 /// -public enum RibbonIndex +public enum RibbonIndex : byte { ChampionKalos, ChampionG3, @@ -115,17 +118,368 @@ public static class RibbonIndexExtensions { public static bool GetRibbonIndex(this IRibbonIndex x, RibbonIndex r) => x.GetRibbon((int)r); public static void SetRibbonIndex(this IRibbonIndex x, RibbonIndex r, bool value = true) => x.SetRibbon((int)r, value); + public static bool IsMark(this RibbonIndex r) => r is >= MarkLunchtime and <= MarkSlump; public static AreaWeather8 GetWeather8(this RibbonIndex x) => x switch { - RibbonIndex.MarkCloudy => AreaWeather8.Overcast, - RibbonIndex.MarkRainy => AreaWeather8.Raining, - RibbonIndex.MarkStormy => AreaWeather8.Thunderstorm, - RibbonIndex.MarkDry => AreaWeather8.Intense_Sun, - RibbonIndex.MarkSnowy => AreaWeather8.Snowing, - RibbonIndex.MarkBlizzard => AreaWeather8.Snowstorm, - RibbonIndex.MarkSandstorm => AreaWeather8.Sandstorm, - RibbonIndex.MarkMisty => AreaWeather8.Heavy_Fog, + MarkCloudy => AreaWeather8.Overcast, + MarkRainy => AreaWeather8.Raining, + MarkStormy => AreaWeather8.Thunderstorm, + MarkDry => AreaWeather8.Intense_Sun, + MarkSnowy => AreaWeather8.Snowing, + MarkBlizzard => AreaWeather8.Snowstorm, + MarkSandstorm => AreaWeather8.Sandstorm, + MarkMisty => AreaWeather8.Heavy_Fog, _ => AreaWeather8.None, }; + + private enum RibbonIndexGroup : byte + { + None, + Mark, + CountMemory, + Common3, + Common4, + Event3, + Event4, + Common6, + Common7, + Common8, + } + + private static RibbonIndexGroup GetGroup(this RibbonIndex r) + { + if (r.IsMark()) + return RibbonIndexGroup.Mark; + return r switch + { + ChampionG3 => RibbonIndexGroup.Common3, + Effort => RibbonIndexGroup.Common3, + Artist => RibbonIndexGroup.Common3, + + ChampionSinnoh => RibbonIndexGroup.Common4, + Alert => RibbonIndexGroup.Common4, + Shock => RibbonIndexGroup.Common4, + Downcast => RibbonIndexGroup.Common4, + Careless => RibbonIndexGroup.Common4, + Relax => RibbonIndexGroup.Common4, + Snooze => RibbonIndexGroup.Common4, + Smile => RibbonIndexGroup.Common4, + Gorgeous => RibbonIndexGroup.Common4, + Royal => RibbonIndexGroup.Common4, + GorgeousRoyal => RibbonIndexGroup.Common4, + Footprint => RibbonIndexGroup.Common4, + Record => RibbonIndexGroup.Common4, + Legend => RibbonIndexGroup.Common4, + + Country => RibbonIndexGroup.Event3, + National => RibbonIndexGroup.Event3, + Earth => RibbonIndexGroup.Event3, + ChampionBattle => RibbonIndexGroup.Event3, + ChampionRegional => RibbonIndexGroup.Event3, + ChampionNational => RibbonIndexGroup.Event3, + + World => RibbonIndexGroup.Event4, + Classic => RibbonIndexGroup.Event4, + Premier => RibbonIndexGroup.Event4, + Event => RibbonIndexGroup.Event4, + Birthday => RibbonIndexGroup.Event4, + Special => RibbonIndexGroup.Event4, + Souvenir => RibbonIndexGroup.Event4, + Wishing => RibbonIndexGroup.Event4, + ChampionWorld => RibbonIndexGroup.Event4, + + ChampionKalos => RibbonIndexGroup.Common6, + BestFriends => RibbonIndexGroup.Common6, + Training => RibbonIndexGroup.Common6, + BattlerSkillful => RibbonIndexGroup.Common6, + BattlerExpert => RibbonIndexGroup.Common6, + ChampionG6Hoenn => RibbonIndexGroup.Common6, + ContestStar => RibbonIndexGroup.Common6, + MasterCoolness => RibbonIndexGroup.Common6, + MasterBeauty => RibbonIndexGroup.Common6, + MasterCuteness => RibbonIndexGroup.Common6, + MasterCleverness => RibbonIndexGroup.Common6, + MasterToughness => RibbonIndexGroup.Common6, + + CountMemoryContest => RibbonIndexGroup.CountMemory, + CountMemoryBattle => RibbonIndexGroup.CountMemory, + + ChampionAlola => RibbonIndexGroup.Common7, + BattleRoyale => RibbonIndexGroup.Common7, + BattleTreeGreat => RibbonIndexGroup.Common7, + BattleTreeMaster => RibbonIndexGroup.Common7, + + ChampionGalar => RibbonIndexGroup.Common8, + TowerMaster => RibbonIndexGroup.Common8, + MasterRank => RibbonIndexGroup.Common8, + Pioneer => RibbonIndexGroup.Common8, + TwinklingStar => RibbonIndexGroup.Common8, + _ => RibbonIndexGroup.None, + }; + } + + public static void Fix(this RibbonIndex r, RibbonVerifierArguments args, bool state) + { + var pk = args.Entity; + var group = r.GetGroup(); + switch (group) + { + case RibbonIndexGroup.Mark: + r.FixMark(pk, state); + return; + case RibbonIndexGroup.CountMemory: + if (pk is not IRibbonSetCommon6 m6) + return; + (byte contest, byte battle) = state ? RibbonRules.GetMaxMemoryCounts(args.History, args.Entity, args.Encounter) : default; + if (r is CountMemoryContest) + m6.RibbonCountMemoryContest = contest; + else + m6.RibbonCountMemoryBattle = battle; + return; + case RibbonIndexGroup.Common3: + if (pk is not IRibbonSetCommon3 c3) + return; + if (r == ChampionG3) c3.RibbonChampionG3 = state; + else if (r == Effort) c3.RibbonEffort = state; + else if (r == Artist) c3.RibbonArtist = state; + return; + case RibbonIndexGroup.Common4: + if (pk is not IRibbonSetCommon4 c4) + return; + if (r == ChampionSinnoh) c4.RibbonChampionSinnoh = state; + else if (r == Alert) c4.RibbonAlert = state; + else if (r == Shock) c4.RibbonShock = state; + else if (r == Downcast) c4.RibbonDowncast = state; + else if (r == Careless) c4.RibbonCareless = state; + else if (r == Relax) c4.RibbonRelax = state; + else if (r == Snooze) c4.RibbonSnooze = state; + else if (r == Smile) c4.RibbonSmile = state; + else if (r == Gorgeous) c4.RibbonGorgeous = state; + else if (r == Royal) c4.RibbonRoyal = state; + else if (r == GorgeousRoyal) c4.RibbonGorgeousRoyal = state; + else if (r == Footprint) c4.RibbonFootprint = state; + else if (r == Record) c4.RibbonRecord = state; + else if (r == Legend) c4.RibbonLegend = state; + return; + case RibbonIndexGroup.Event3: + if (pk is not IRibbonSetEvent3 e3) + return; + if (r == Country) e3.RibbonCountry = state; + else if (r == National) e3.RibbonNational = state; + else if (r == Earth) e3.RibbonEarth = state; + else if (r == ChampionBattle) e3.RibbonChampionBattle = state; + else if (r == ChampionRegional) e3.RibbonChampionRegional = state; + else if (r == ChampionNational) e3.RibbonChampionNational = state; + return; + case RibbonIndexGroup.Event4: + if (pk is not IRibbonSetEvent4 e4) + return; + if (r == World) e4.RibbonWorld = state; + else if (r == Classic) e4.RibbonClassic = state; + else if (r == Premier) e4.RibbonPremier = state; + else if (r == Event) e4.RibbonEvent = state; + else if (r == Birthday) e4.RibbonBirthday = state; + else if (r == Special) e4.RibbonSpecial = state; + else if (r == Souvenir) e4.RibbonSouvenir = state; + else if (r == Wishing) e4.RibbonWishing = state; + else if (r == ChampionWorld) e4.RibbonChampionWorld = state; + return; + case RibbonIndexGroup.Common6: + if (pk is not IRibbonSetCommon6 c6) + return; + if (r == ChampionKalos) c6.RibbonChampionKalos = state; + else if (r == BestFriends) c6.RibbonBestFriends = state; + else if (r == Training) c6.RibbonTraining = state; + else if (r == BattlerSkillful) c6.RibbonBattlerSkillful = state; + else if (r == BattlerExpert) c6.RibbonBattlerExpert = state; + else if (r == ChampionG6Hoenn) c6.RibbonChampionG6Hoenn = state; + else if (r == ContestStar) c6.RibbonContestStar = state; + else if (r == MasterCoolness) c6.RibbonMasterCoolness = state; + else if (r == MasterBeauty) c6.RibbonMasterBeauty = state; + else if (r == MasterCuteness) c6.RibbonMasterCuteness = state; + else if (r == MasterCleverness) c6.RibbonMasterCleverness = state; + else if (r == MasterToughness) c6.RibbonMasterToughness = state; + return; + case RibbonIndexGroup.Common7: + if (pk is not IRibbonSetCommon7 c7) + return; + if (r == ChampionAlola) c7.RibbonChampionAlola = state; + else if (r == BattleRoyale) c7.RibbonBattleRoyale = state; + else if (r == BattleTreeGreat) c7.RibbonBattleTreeGreat = state; + else if (r == BattleTreeMaster) c7.RibbonBattleTreeMaster = state; + return; + case RibbonIndexGroup.Common8: + if (pk is not IRibbonSetCommon8 c8) + return; + if (r == ChampionGalar) c8.RibbonChampionGalar = state; + else if (r == TowerMaster) c8.RibbonTowerMaster = state; + else if (r == MasterRank) c8.RibbonMasterRank = state; + else if (r == Pioneer) c8.RibbonPioneer = state; + else if (r == TwinklingStar) c8.RibbonTwinklingStar = state; + return; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private static void FixMark(this RibbonIndex r, PKM pk, bool state) + { + if (pk is not IRibbonSetMark8 m) + return; + _ = r switch + { + MarkLunchtime => m.RibbonMarkLunchtime = state, + MarkSleepyTime => m.RibbonMarkSleepyTime = state, + MarkDusk => m.RibbonMarkDusk = state, + MarkDawn => m.RibbonMarkDawn = state, + MarkCloudy => m.RibbonMarkCloudy = state, + MarkRainy => m.RibbonMarkRainy = state, + MarkStormy => m.RibbonMarkStormy = state, + MarkSnowy => m.RibbonMarkSnowy = state, + MarkBlizzard => m.RibbonMarkBlizzard = state, + MarkDry => m.RibbonMarkDry = state, + MarkSandstorm => m.RibbonMarkSandstorm = state, + MarkMisty => m.RibbonMarkMisty = state, + MarkDestiny => m.RibbonMarkDestiny = state, + MarkFishing => m.RibbonMarkFishing = state, + MarkCurry => m.RibbonMarkCurry = state, + MarkUncommon => m.RibbonMarkUncommon = state, + MarkRare => m.RibbonMarkRare = state, + MarkRowdy => m.RibbonMarkRowdy = state, + MarkAbsentMinded => m.RibbonMarkAbsentMinded = state, + MarkJittery => m.RibbonMarkJittery = state, + MarkExcited => m.RibbonMarkExcited = state, + MarkCharismatic => m.RibbonMarkCharismatic = state, + MarkCalmness => m.RibbonMarkCalmness = state, + MarkIntense => m.RibbonMarkIntense = state, + MarkZonedOut => m.RibbonMarkZonedOut = state, + MarkJoyful => m.RibbonMarkJoyful = state, + MarkAngry => m.RibbonMarkAngry = state, + MarkSmiley => m.RibbonMarkSmiley = state, + MarkTeary => m.RibbonMarkTeary = state, + MarkUpbeat => m.RibbonMarkUpbeat = state, + MarkPeeved => m.RibbonMarkPeeved = state, + MarkIntellectual => m.RibbonMarkIntellectual = state, + MarkFerocious => m.RibbonMarkFerocious = state, + MarkCrafty => m.RibbonMarkCrafty = state, + MarkScowling => m.RibbonMarkScowling = state, + MarkKindly => m.RibbonMarkKindly = state, + MarkFlustered => m.RibbonMarkFlustered = state, + MarkPumpedUp => m.RibbonMarkPumpedUp = state, + MarkZeroEnergy => m.RibbonMarkZeroEnergy = state, + MarkPrideful => m.RibbonMarkPrideful = state, + MarkUnsure => m.RibbonMarkUnsure = state, + MarkHumble => m.RibbonMarkHumble = state, + MarkThorny => m.RibbonMarkThorny = state, + MarkVigor => m.RibbonMarkVigor = state, + MarkSlump => m.RibbonMarkSlump = state, + _ => throw new ArgumentOutOfRangeException(nameof(r), r, null), + }; + } + + public static string GetPropertyName(this RibbonIndex r) => r switch + { + ChampionKalos => nameof(IRibbonSetCommon6.RibbonChampionKalos), + ChampionG3 => nameof(IRibbonSetCommon3.RibbonChampionG3), + ChampionSinnoh => nameof(IRibbonSetCommon4.RibbonChampionSinnoh), + BestFriends => nameof(IRibbonSetCommon6.RibbonBestFriends), + Training => nameof(IRibbonSetCommon6.RibbonTraining), + BattlerSkillful => nameof(IRibbonSetCommon6.RibbonBattlerSkillful), + BattlerExpert => nameof(IRibbonSetCommon6.RibbonBattlerExpert), + Effort => nameof(IRibbonSetCommon3.RibbonEffort), + Alert => nameof(IRibbonSetCommon4.RibbonAlert), + Shock => nameof(IRibbonSetCommon4.RibbonShock), + Downcast => nameof(IRibbonSetCommon4.RibbonDowncast), + Careless => nameof(IRibbonSetCommon4.RibbonCareless), + Relax => nameof(IRibbonSetCommon4.RibbonRelax), + Snooze => nameof(IRibbonSetCommon4.RibbonSnooze), + Smile => nameof(IRibbonSetCommon4.RibbonSmile), + Gorgeous => nameof(IRibbonSetCommon4.RibbonGorgeous), + Royal => nameof(IRibbonSetCommon4.RibbonRoyal), + GorgeousRoyal => nameof(IRibbonSetCommon4.RibbonGorgeousRoyal), + Artist => nameof(IRibbonSetCommon3.RibbonArtist), + Footprint => nameof(IRibbonSetCommon4.RibbonFootprint), + Record => nameof(IRibbonSetCommon4.RibbonRecord), + Legend => nameof(IRibbonSetCommon4.RibbonLegend), + Country => nameof(IRibbonSetEvent3.RibbonCountry), + National => nameof(IRibbonSetEvent3.RibbonNational), + Earth => nameof(IRibbonSetEvent3.RibbonEarth), + World => nameof(IRibbonSetEvent4.RibbonWorld), + Classic => nameof(IRibbonSetEvent4.RibbonClassic), + Premier => nameof(IRibbonSetEvent4.RibbonPremier), + Event => nameof(IRibbonSetEvent4.RibbonEvent), + Birthday => nameof(IRibbonSetEvent4.RibbonBirthday), + Special => nameof(IRibbonSetEvent4.RibbonSpecial), + Souvenir => nameof(IRibbonSetEvent4.RibbonSouvenir), + Wishing => nameof(IRibbonSetEvent4.RibbonWishing), + ChampionBattle => nameof(IRibbonSetEvent3.RibbonChampionBattle), + ChampionRegional => nameof(IRibbonSetEvent3.RibbonChampionRegional), + ChampionNational => nameof(IRibbonSetEvent3.RibbonChampionNational), + ChampionWorld => nameof(IRibbonSetEvent4.RibbonChampionWorld), + CountMemoryContest => nameof(IRibbonSetCommon6.RibbonCountMemoryContest), + CountMemoryBattle => nameof(IRibbonSetCommon6.RibbonCountMemoryBattle), + ChampionG6Hoenn => nameof(IRibbonSetCommon6.RibbonChampionG6Hoenn), + ContestStar => nameof(IRibbonSetCommon6.RibbonContestStar), + MasterCoolness => nameof(IRibbonSetCommon6.RibbonMasterCoolness), + MasterBeauty => nameof(IRibbonSetCommon6.RibbonMasterBeauty), + MasterCuteness => nameof(IRibbonSetCommon6.RibbonMasterCuteness), + MasterCleverness => nameof(IRibbonSetCommon6.RibbonMasterCleverness), + MasterToughness => nameof(IRibbonSetCommon6.RibbonMasterToughness), + ChampionAlola => nameof(IRibbonSetCommon7.RibbonChampionAlola), + BattleRoyale => nameof(IRibbonSetCommon7.RibbonBattleRoyale), + BattleTreeGreat => nameof(IRibbonSetCommon7.RibbonBattleTreeGreat), + BattleTreeMaster => nameof(IRibbonSetCommon7.RibbonBattleTreeMaster), + ChampionGalar => nameof(IRibbonSetCommon8.RibbonChampionGalar), + TowerMaster => nameof(IRibbonSetCommon8.RibbonTowerMaster), + MasterRank => nameof(IRibbonSetCommon8.RibbonMasterRank), + MarkLunchtime => nameof(IRibbonSetMark8.RibbonMarkLunchtime), + MarkSleepyTime => nameof(IRibbonSetMark8.RibbonMarkSleepyTime), + MarkDusk => nameof(IRibbonSetMark8.RibbonMarkDusk), + MarkDawn => nameof(IRibbonSetMark8.RibbonMarkDawn), + MarkCloudy => nameof(IRibbonSetMark8.RibbonMarkCloudy), + MarkRainy => nameof(IRibbonSetMark8.RibbonMarkRainy), + MarkStormy => nameof(IRibbonSetMark8.RibbonMarkStormy), + MarkSnowy => nameof(IRibbonSetMark8.RibbonMarkSnowy), + MarkBlizzard => nameof(IRibbonSetMark8.RibbonMarkBlizzard), + MarkDry => nameof(IRibbonSetMark8.RibbonMarkDry), + MarkSandstorm => nameof(IRibbonSetMark8.RibbonMarkSandstorm), + MarkMisty => nameof(IRibbonSetMark8.RibbonMarkMisty), + MarkDestiny => nameof(IRibbonSetMark8.RibbonMarkDestiny), + MarkFishing => nameof(IRibbonSetMark8.RibbonMarkFishing), + MarkCurry => nameof(IRibbonSetMark8.RibbonMarkCurry), + MarkUncommon => nameof(IRibbonSetMark8.RibbonMarkUncommon), + MarkRare => nameof(IRibbonSetMark8.RibbonMarkRare), + MarkRowdy => nameof(IRibbonSetMark8.RibbonMarkRowdy), + MarkAbsentMinded => nameof(IRibbonSetMark8.RibbonMarkAbsentMinded), + MarkJittery => nameof(IRibbonSetMark8.RibbonMarkJittery), + MarkExcited => nameof(IRibbonSetMark8.RibbonMarkExcited), + MarkCharismatic => nameof(IRibbonSetMark8.RibbonMarkCharismatic), + MarkCalmness => nameof(IRibbonSetMark8.RibbonMarkCalmness), + MarkIntense => nameof(IRibbonSetMark8.RibbonMarkIntense), + MarkZonedOut => nameof(IRibbonSetMark8.RibbonMarkZonedOut), + MarkJoyful => nameof(IRibbonSetMark8.RibbonMarkJoyful), + MarkAngry => nameof(IRibbonSetMark8.RibbonMarkAngry), + MarkSmiley => nameof(IRibbonSetMark8.RibbonMarkSmiley), + MarkTeary => nameof(IRibbonSetMark8.RibbonMarkTeary), + MarkUpbeat => nameof(IRibbonSetMark8.RibbonMarkUpbeat), + MarkPeeved => nameof(IRibbonSetMark8.RibbonMarkPeeved), + MarkIntellectual => nameof(IRibbonSetMark8.RibbonMarkIntellectual), + MarkFerocious => nameof(IRibbonSetMark8.RibbonMarkFerocious), + MarkCrafty => nameof(IRibbonSetMark8.RibbonMarkCrafty), + MarkScowling => nameof(IRibbonSetMark8.RibbonMarkScowling), + MarkKindly => nameof(IRibbonSetMark8.RibbonMarkKindly), + MarkFlustered => nameof(IRibbonSetMark8.RibbonMarkFlustered), + MarkPumpedUp => nameof(IRibbonSetMark8.RibbonMarkPumpedUp), + MarkZeroEnergy => nameof(IRibbonSetMark8.RibbonMarkZeroEnergy), + MarkPrideful => nameof(IRibbonSetMark8.RibbonMarkPrideful), + MarkUnsure => nameof(IRibbonSetMark8.RibbonMarkUnsure), + MarkHumble => nameof(IRibbonSetMark8.RibbonMarkHumble), + MarkThorny => nameof(IRibbonSetMark8.RibbonMarkThorny), + MarkVigor => nameof(IRibbonSetMark8.RibbonMarkVigor), + MarkSlump => nameof(IRibbonSetMark8.RibbonMarkSlump), + Pioneer => nameof(IRibbonSetCommon8.RibbonPioneer), + TwinklingStar => nameof(IRibbonSetCommon8.RibbonTwinklingStar), + _ => throw new ArgumentOutOfRangeException(nameof(r), r, null), + }; }