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<T>`. No allocation on heap, or `IEnumerable` iterators.
* Verifies all egg ribbons using a separate method, explicitly implemented. No reflection overhead.
* Separates each ribbon interface to separate `static` classes. Easier to identify code needing change on new game update.
* Extracted logic for specific ribbons. Can easily revise complicated ribbon's acquisition rules.
* Simplifies GiveAll/RemoveAll legal ribbon mutations. No reflection overhead, and no allocation.
* Can be expanded in the future if we need to track conditions for ribbon acquisition (was Sinnoh Champ received in BDSP or Gen4?)

End result is a more performant implementation and easier to maintain & reuse logic.
This commit is contained in:
Kurt
2022-08-15 21:04:30 -07:00
committed by GitHub
parent a4274d370f
commit 768047cd80
40 changed files with 2008 additions and 1316 deletions

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
@@ -8,238 +7,80 @@ namespace PKHeX.Core;
/// </summary>
public static class RibbonApplicator
{
private static List<string> GetAllRibbonNames(PKM pk) => RibbonInfo.GetRibbonInfo(pk).ConvertAll(z => z.Name);
/// <summary>
/// Gets a list of valid ribbons for the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Entity to fetch the list for.</param>
/// <param name="allRibbons">All ribbon names.</param>
/// <returns>List of all valid ribbon names.</returns>
public static IReadOnlyList<string> GetValidRibbons(PKM pk, IList<string> allRibbons)
{
var clone = pk.Clone();
return SetAllValidRibbons(allRibbons, clone);
}
/// <summary>
/// Gets a list of valid ribbons for the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Entity to fetch the list for.</param>
/// <returns>List of all valid ribbon names.</returns>
public static IReadOnlyList<string> GetValidRibbons(PKM pk)
{
var names = GetAllRibbonNames(pk);
return GetValidRibbons(pk, names);
}
/// <summary>
/// Gets a list of valid ribbons for the <see cref="pk"/> that can be removed.
/// </summary>
/// <param name="pk">Entity to fetch the list for.</param>
/// <param name="allRibbons">All ribbon names.</param>
/// <returns>List of all removable ribbon names.</returns>
public static IReadOnlyList<string> GetRemovableRibbons(PKM pk, IList<string> allRibbons)
{
var clone = pk.Clone();
return RemoveAllValidRibbons(allRibbons, clone);
}
/// <summary>
/// Gets a list of valid ribbons for the <see cref="pk"/> that can be removed.
/// </summary>
/// <param name="pk">Entity to fetch the list for.</param>
/// <returns>List of all removable ribbon names.</returns>
public static IReadOnlyList<string> GetRemovableRibbons(PKM pk)
{
var names = GetAllRibbonNames(pk);
return GetRemovableRibbons(pk, names);
}
/// <summary>
/// Sets all valid ribbons to the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Entity to set ribbons for.</param>
/// <returns>True if any ribbons were applied.</returns>
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));
/// <summary>
/// Sets all valid ribbons to the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Entity to set ribbons for.</param>
/// <param name="ribNames">Ribbon names to try setting.</param>
/// <returns>True if any ribbons were applied.</returns>
public static bool SetAllValidRibbons(PKM pk, List<string> ribNames)
/// <inheritdoc cref="SetAllValidRibbons(PKM)"/>
public static void SetAllValidRibbons(LegalityAnalysis la)
{
var list = SetAllValidRibbons(ribNames, pk);
return list.Count != 0;
}
private static IReadOnlyList<string> SetAllValidRibbons(IList<string> allRibbons, PKM pk)
{
var la = new LegalityAnalysis(pk);
var valid = new List<string>();
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);
}
/// <summary>
/// Sets all valid ribbons to the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Entity to set ribbons for.</param>
/// <returns>True if any ribbons were removed.</returns>
public static bool RemoveAllValidRibbons(PKM pk)
public static void RemoveAllValidRibbons(PKM pk) => RemoveAllValidRibbons(new LegalityAnalysis(pk));
/// <inheritdoc cref="RemoveAllValidRibbons(PKM)"/>
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);
}
/// <summary>
/// Sets all valid ribbons to the <see cref="pk"/>.
/// Parses the Entity for all ribbons, then fixes any ribbon that was invalid.
/// </summary>
/// <param name="pk">Entity to set ribbons for.</param>
/// <param name="ribNames">Ribbon names to try setting.</param>
/// <returns>True if any ribbons were removed.</returns>
public static bool RemoveAllValidRibbons(PKM pk, List<string> ribNames)
public static void FixInvalidRibbons(RibbonVerifierArguments args)
{
var list = RemoveAllValidRibbons(ribNames, pk);
return list.Count != 0;
Span<RibbonResult> result = stackalloc RibbonResult[RibbonVerifier.MaxRibbonCount];
var count = RibbonVerifier.GetRibbonResults(args, result);
foreach (var ribbon in result[..count])
ribbon.Fix(args);
}
private static IReadOnlyList<string> RemoveAllValidRibbons(IList<string> allRibbons, PKM pk)
private static void SetAllRibbonState(RibbonVerifierArguments args, bool desiredState)
{
var la = new LegalityAnalysis(pk);
var valid = new List<string>();
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<string> allRibbons, ICollection<string> 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<string> allRibbons, ICollection<string> 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);
}
}

View File

@@ -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;
}

View File

@@ -40,9 +40,6 @@ public static class LegalityCheckStrings
/// <summary>Format text for exporting the <see cref="PIDIV.Type"/> that was matched for the <see cref="PKM"/></summary>
public static string L_FPIDType_0 { get; set; } = "PID Type: {0}";
/// <summary>Severity string for <see cref="Severity.Indeterminate"/></summary>
public static string L_SIndeterminate { get; set; } = "Indeterminate";
/// <summary>Severity string for <see cref="Severity.Invalid"/></summary>
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}";

View File

@@ -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));
}

View File

@@ -0,0 +1,127 @@
using System;
using static PKHeX.Core.RibbonIndex3;
namespace PKHeX.Core;
/// <summary>
/// Ribbons from Generation 3 that were not carried forward to future formats.
/// </summary>
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),
};
}

View File

@@ -0,0 +1,114 @@
using System;
using static PKHeX.Core.RibbonIndex4;
namespace PKHeX.Core;
/// <summary>
/// Ribbons from Generation 4 that were not carried forward to future formats.
/// </summary>
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),
};
}

View File

@@ -1,28 +1,64 @@
namespace PKHeX.Core;
using System;
using static PKHeX.Core.RibbonParseFlags;
/// <summary>
/// Legality Check Parse object containing information about a single ribbon.
/// </summary>
internal sealed class RibbonResult
namespace PKHeX.Core;
public readonly record struct RibbonResult
{
/// <summary>Ribbon Display Name</summary>
public string Name { get; private set; }
/// <summary>Ribbon should not be present.</summary>
/// <remarks>If this is false, the Ribbon is missing.</remarks>
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;
/// <summary>
/// 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.
/// </summary>
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;
/// <summary>
/// Property Name that the ribbon can be get/set with, or looked up for localization.
/// </summary>
public string PropertyName => Type switch
{
Name += $" / {other.Name}";
Mainline => ((RibbonIndex)Value).GetPropertyName(),
Index3 => ((RibbonIndex3)Value).GetPropertyName(),
Index4 => ((RibbonIndex4)Value).GetPropertyName(),
_ => throw new ArgumentOutOfRangeException(),
};
/// <summary>
/// Updates the ribbon state depending on the <see cref="args"/> and <see cref="IsMissing"/> state.
/// </summary>
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,
}

View File

@@ -0,0 +1,25 @@
using System;
namespace PKHeX.Core;
public ref struct RibbonResultList
{
private readonly Span<RibbonResult> Span;
public int Count { get; private set; }
public RibbonResultList(Span<RibbonResult> 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));
}

View File

@@ -0,0 +1,342 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Rules for obtaining ribbons.
/// </summary>
public static class RibbonRules
{
/// <summary>
/// Checks if the input can receive the <see cref="IRibbonSetCommon7.RibbonChampionAlola"/> ribbon.
/// </summary>
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;
}
/// <summary>
/// Checks if the input can receive the <see cref="IRibbonSetCommon3.RibbonEffort"/> ribbon.
/// </summary>
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,
};
/// <summary>
/// Checks if the input can receive the <see cref="IRibbonSetCommon6.RibbonBestFriends"/> ribbon.
/// </summary>
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,
};
/// <summary>
/// Checks if the input can receive the <see cref="IRibbonSetCommon4.RibbonFootprint"/> ribbon.
/// </summary>
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;
}
/// <summary>
/// Checks if the entity participated in SW/SH ranked battles for the <see cref="IRibbonSetCommon8.RibbonMasterRank"/> ribbon.
/// </summary>
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;
}
/// <summary>
/// Checks if the input can receive the <see cref="IRibbonSetCommon6.RibbonTraining"/> ribbon.
/// </summary>
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;
}
/// <summary>
/// Checks if the entity participated in battles for the <see cref="IRibbonSetCommon8.RibbonTowerMaster"/> ribbon.
/// </summary>
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);
}
/// <summary>
/// Checks if the input can receive the <see cref="IRibbonSetUnique3.RibbonWinning"/> ribbon.
/// </summary>
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);
}
/// <summary>
/// Checks if the input can receive the <see cref="IRibbonSetUnique3.RibbonVictory"/> ribbon.
/// </summary>
public static bool IsRibbonValidVictory(EvolutionHistory evos)
{
if (evos.HasVisitedGen3)
return IsAllowedBattleFrontier(evos.Gen3[0].Species);
return false;
}
/// <summary>
/// Checks if the input can receive the <see cref="IRibbonSetCommon8.RibbonTwinklingStar"/> ribbon.
/// </summary>
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;
}
/// <summary>
/// Checks if any of the species it existed as in BD/SP lacked footprints.
/// </summary>
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,
};
/// <summary>
/// Checks if the input can receive the <see cref="IRibbonSetEvent3.RibbonNational"/> ribbon.
/// </summary>
/// <remarks>
/// If returns true, must have the ribbon. If returns false, must not have the ribbon.
/// </remarks>
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;
}
/// <summary>
/// Gets the max count values the input can receive for the <see cref="IRibbonSetCommon6.RibbonCountMemoryContest"/> and <see cref="IRibbonSetCommon6.RibbonCountMemoryBattle"/> ribbon counts.
/// </summary>
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);
}
}

View File

@@ -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;
/// <summary>
/// Maximum amount of ribbons to consider when allocating a span for parsing.
/// </summary>
/// <remarks>
/// Minor optimization is to stackalloc as little as possible, without too much calculation.
/// <see cref="RibbonIndex3.MAX_COUNT"/> + <see cref="RibbonIndex4.MAX_COUNT"/> is 48, but are not present after Gen5.
/// <see cref="PK5"/> only has 80 ribbons implemented.
/// Instead of using the sum of all 3 enums, we can use <see cref="RibbonIndex.MAX_COUNT"/> as the true maximum count.
/// </remarks>
public const int MaxRibbonCount = (int)RibbonIndex.MAX_COUNT;
public override void Verify(LegalityAnalysis data)
{
// Flag VC (Gen1/2) ribbons using Gen7 origin rules.
@@ -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<RibbonResult> 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));
}
/// <summary>
/// Checks if the <see cref="index"/> is not an invalid/missing ribbon in the result parse.
/// </summary>
/// <param name="index">Ribbon Index to check for</param>
/// <param name="args">Inputs to analyze</param>
/// <returns>True if not present in the flagged result span.</returns>
public static bool IsValidExtra(RibbonIndex index, RibbonVerifierArguments args)
{
Span<RibbonResult> result = stackalloc RibbonResult[MaxRibbonCount];
int count = GetRibbonResults(args, result);
if (count == 0)
return true;
var span = result[..count];
foreach (var x in span)
{
var msg = string.Join(Environment.NewLine, result);
data.AddLine(GetInvalid(msg));
if (x.Equals(index))
return false;
}
return true;
}
/// <summary>
/// Uses the input <see cref="args"/> and stores results in the <see cref="result"/> span.
/// </summary>
/// <param name="args">Inputs to analyze</param>
/// <param name="result">Result storage</param>
/// <returns>Count of elements filled in the <see cref="result"/> span.</returns>
public static int GetRibbonResults(RibbonVerifierArguments args, Span<RibbonResult> result)
{
var list = new RibbonResultList(result);
return GetRibbonResults(args, ref list);
}
private static int GetRibbonResults(RibbonVerifierArguments args, ref RibbonResultList list)
{
if (!args.Entity.IsEgg)
Parse(args, ref list);
else
ParseEgg(args, ref list);
return list.Count;
}
private static string GetMessage(ReadOnlySpan<RibbonResult> result)
{
var total = result.Length;
int missing = GetCountMissing(result);
int invalid = total - missing;
var sb = new StringBuilder(total * 20);
if (missing != 0)
AppendAll(result, sb, LRibbonFMissing_0, true);
if (invalid != 0)
{
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<string> GetIncorrectRibbons(PKM pk, EvolutionHistory evos, IEncounterTemplate enc)
private static int GetCountMissing(ReadOnlySpan<RibbonResult> result)
{
List<string> missingRibbons = new();
List<string> invalidRibbons = new();
var ribs = GetRibbonResults(pk, evos, enc);
foreach (var bad in ribs)
(bad.Invalid ? invalidRibbons : missingRibbons).Add(bad.Name);
var result = new List<string>();
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<RibbonResult> result, StringBuilder sb, string startText, bool stateMissing)
{
int added = 0;
sb.Append(startText);
foreach (var x in result)
{
if (x.IsMissing != stateMissing)
continue;
if (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<RibbonResult> 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<RibbonResult> 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<RibbonResult> GetMissingContestRibbons(IReadOnlyList<bool> bits, IReadOnlyList<string> 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<RibbonResult> 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<RibbonResult> 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<RibbonResult> 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<RibbonResult> 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<RibbonResult> 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<RibbonResult> 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<RibbonResult> 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<RibbonResult> 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<RibbonResult> 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<RibbonResult> GetInvalidRibbonsNone(IReadOnlyList<bool> bits, IReadOnlyList<string> names)
{
for (int i = 0; i < bits.Count; i++)
{
if (bits[i])
yield return new RibbonResult(names[i]);
}
}
private static IEnumerable<RibbonResult> GetInvalidRibbonsNoneSkipIndex(IReadOnlyList<bool> bits, IReadOnlyList<string> 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,
};
}

View File

@@ -0,0 +1,10 @@
namespace PKHeX.Core;
/// <summary>
/// Wraps details used for parsing ribbon states.
/// </summary>
/// <param name="Entity">Entity to parse</param>
/// <param name="Encounter">Encounter originated as</param>
/// <param name="History">History of visitation</param>
/// <remarks>For Generation 1/2 encounters, use the transferred encounter data object.</remarks>
public readonly record struct RibbonVerifierArguments(PKM Entity, IEncounterTemplate Encounter, EvolutionHistory History);

View File

@@ -0,0 +1,31 @@
using static PKHeX.Core.RibbonIndex;
namespace PKHeX.Core;
/// <summary>
/// Parsing logic for <see cref="IRibbonSetCommon3"/>.
/// </summary>
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);
}
}

View File

@@ -0,0 +1,70 @@
using static PKHeX.Core.RibbonIndex;
namespace PKHeX.Core;
/// <summary>
/// Parsing logic for <see cref="IRibbonSetCommon4"/>.
/// </summary>
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);
}
}

View File

@@ -0,0 +1,190 @@
using static PKHeX.Core.RibbonIndex;
namespace PKHeX.Core;
/// <summary>
/// Parsing logic for <see cref="IRibbonSetCommon6"/>.
/// </summary>
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);
}
}

View File

@@ -0,0 +1,41 @@
using static PKHeX.Core.RibbonIndex;
namespace PKHeX.Core;
/// <summary>
/// Parsing logic for <see cref="IRibbonSetCommon7"/>.
/// </summary>
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);
}
}

View File

@@ -0,0 +1,77 @@
using static PKHeX.Core.RibbonIndex;
namespace PKHeX.Core;
/// <summary>
/// Parsing logic for <see cref="IRibbonSetCommon8"/>.
/// </summary>
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);
}
}

View File

@@ -0,0 +1,73 @@
using static PKHeX.Core.RibbonIndex;
namespace PKHeX.Core;
/// <summary>
/// Parsing logic for <see cref="IRibbonSetEvent3"/>.
/// </summary>
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);
}
}

View File

@@ -0,0 +1,55 @@
using static PKHeX.Core.RibbonIndex;
namespace PKHeX.Core;
/// <summary>
/// Parsing logic for <see cref="IRibbonSetEvent4"/>.
/// </summary>
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);
}
}

View File

@@ -0,0 +1,49 @@
using static PKHeX.Core.RibbonIndex3;
namespace PKHeX.Core;
/// <summary>
/// Parsing logic for <see cref="IRibbonSetOnly3"/>.
/// </summary>
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);
}
}

View File

@@ -0,0 +1,37 @@
using static PKHeX.Core.RibbonIndex3;
namespace PKHeX.Core;
/// <summary>
/// Parsing logic for <see cref="IRibbonSetUnique3"/>.
/// </summary>
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);
}
}

View File

@@ -0,0 +1,135 @@
namespace PKHeX.Core;
/// <summary>
/// Parsing logic for <see cref="IRibbonSetUnique4"/>.
/// </summary>
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);
}
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary> Common Ribbons introduced in Generation 3 </summary>
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;

View File

@@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary> Common Ribbons introduced in Generation 4 </summary>
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;

View File

@@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary> Common Ribbons introduced in Generation 6 </summary>
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;
/// <summary>
/// Checks if the <see cref="set"/> has all five contest stat ribbons true.
/// </summary>
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)
{

View File

@@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary> Common Ribbons introduced in Generation 7 </summary>
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;

View File

@@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary> Common Ribbons introduced in Generation 8 </summary>
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;

View File

@@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary> Ribbons introduced in Generation 3 for Special Events </summary>
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;

View File

@@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary> Ribbons introduced in Generation 4 for Special Events </summary>
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;

View File

@@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary> Ribbons that originated in Generation 3 and were only present within that Generation. </summary>
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;
}

View File

@@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary> Ribbons introduced in Generation 3 and were transferred to future Generations (4 and 5 only). </summary>
public interface IRibbonSetUnique3
@@ -9,22 +9,3 @@ public interface IRibbonSetUnique3
/// <summary> Ribbon awarded for clearing Hoenn's Battle Tower's Lv. 100 challenge. </summary>
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;
}

View File

@@ -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;
}

View File

@@ -1,9 +1,12 @@
namespace PKHeX.Core;
using System;
using static PKHeX.Core.RibbonIndex;
namespace PKHeX.Core;
/// <summary>
/// Ribbon Indexes for Generation 8
/// </summary>
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),
};
}