2018-06-24 05:00:01 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2017-09-04 02:51:29 +00:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using static PKHeX.Core.LegalityCheckStrings;
|
|
|
|
|
|
|
|
|
|
namespace PKHeX.Core
|
|
|
|
|
{
|
2018-07-21 03:22:46 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Verifies the <see cref="PKM"/> Ribbon values.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed class RibbonVerifier : Verifier
|
2017-09-04 02:51:29 +00:00
|
|
|
|
{
|
2018-06-24 05:00:01 +00:00
|
|
|
|
protected override CheckIdentifier Identifier => CheckIdentifier.Ribbon;
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2018-06-24 05:00:01 +00:00
|
|
|
|
public override void Verify(LegalityAnalysis data)
|
|
|
|
|
{
|
2020-06-17 02:46:22 +00:00
|
|
|
|
var encounter = data.EncounterMatch;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
var pkm = data.pkm;
|
|
|
|
|
var Info = data.Info;
|
|
|
|
|
// Check Unobtainable Ribbons
|
2020-06-17 02:46:22 +00:00
|
|
|
|
var encounterContent = encounter is MysteryGift mg ? mg.Content : encounter;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.IsEgg)
|
|
|
|
|
{
|
|
|
|
|
if (GetIncorrectRibbonsEgg(pkm, encounterContent))
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(GetInvalid(LRibbonEgg));
|
2018-06-24 05:00:01 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int gen = Info.Generation < 3 ? 7 : Info.Generation; // Flag VC (Gen1/2) ribbons using Gen7 origin rules.
|
|
|
|
|
var result = GetIncorrectRibbons(pkm, encounterContent, gen);
|
|
|
|
|
if (result.Count != 0)
|
|
|
|
|
{
|
|
|
|
|
var msg = string.Join(Environment.NewLine, result.Where(s => !string.IsNullOrEmpty(s)));
|
|
|
|
|
data.AddLine(GetInvalid(msg));
|
|
|
|
|
}
|
|
|
|
|
else
|
2018-07-27 02:34:27 +00:00
|
|
|
|
{
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(GetValid(LRibbonAllValid));
|
2018-07-27 02:34:27 +00:00
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static List<string> GetIncorrectRibbons(PKM pkm, object encounterContent, int gen)
|
2017-09-04 02:51:29 +00:00
|
|
|
|
{
|
|
|
|
|
List<string> missingRibbons = new List<string>();
|
|
|
|
|
List<string> invalidRibbons = new List<string>();
|
|
|
|
|
IEnumerable<RibbonResult> ribs = GetRibbonResults(pkm, encounterContent, gen);
|
|
|
|
|
foreach (var bad in ribs)
|
|
|
|
|
(bad.Invalid ? invalidRibbons : missingRibbons).Add(bad.Name);
|
|
|
|
|
|
|
|
|
|
var result = new List<string>();
|
|
|
|
|
if (missingRibbons.Count > 0)
|
2019-02-25 06:20:02 +00:00
|
|
|
|
result.Add(string.Format(LRibbonFMissing_0, string.Join(", ", missingRibbons.Select(z => z.Replace("Ribbon", string.Empty)))));
|
2017-09-04 02:51:29 +00:00
|
|
|
|
if (invalidRibbons.Count > 0)
|
2019-02-25 06:20:02 +00:00
|
|
|
|
result.Add(string.Format(LRibbonFInvalid_0, string.Join(", ", invalidRibbons.Select(z => z.Replace("Ribbon", string.Empty)))));
|
2017-09-04 02:51:29 +00:00
|
|
|
|
return result;
|
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2018-06-24 05:00:01 +00:00
|
|
|
|
private static bool GetIncorrectRibbonsEgg(PKM pkm, object encounterContent)
|
2017-09-06 03:32:07 +00:00
|
|
|
|
{
|
2020-06-17 02:46:22 +00:00
|
|
|
|
var names = ReflectUtil.GetPropertiesStartWithPrefix(pkm.GetType(), "Ribbon");
|
2017-12-12 00:01:24 +00:00
|
|
|
|
if (encounterContent is IRibbonSetEvent3 event3)
|
2020-06-17 02:46:22 +00:00
|
|
|
|
names = names.Except(event3.RibbonNames());
|
2017-12-12 00:01:24 +00:00
|
|
|
|
if (encounterContent is IRibbonSetEvent4 event4)
|
2020-06-17 02:46:22 +00:00
|
|
|
|
names = names.Except(event4.RibbonNames());
|
2017-09-06 03:32:07 +00:00
|
|
|
|
|
2020-06-17 02:46:22 +00:00
|
|
|
|
foreach (var value in names.Select(name => ReflectUtil.GetValue(pkm, name)))
|
2017-09-06 03:32:07 +00:00
|
|
|
|
{
|
2020-06-17 02:46:22 +00:00
|
|
|
|
if (value is null)
|
PKHeX.Core Nullable cleanup (#2401)
* Handle some nullable cases
Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data)
Make some classes have explicit constructors instead of { } initialization
* Handle bits more obviously without null
* Make SaveFile.BAK explicitly readonly again
* merge constructor methods to have readonly fields
* Inline some properties
* More nullable handling
* Rearrange box actions
define straightforward classes to not have any null properties
* Make extrabyte reference array immutable
* Move tooltip creation to designer
* Rearrange some logic to reduce nesting
* Cache generated fonts
* Split mystery gift album purpose
* Handle more tooltips
* Disallow null setters
* Don't capture RNG object, only type enum
* Unify learnset objects
Now have readonly properties which are never null
don't new() empty learnsets (>800 Learnset objects no longer created,
total of 2400 objects since we also new() a move & level array)
optimize g1/2 reader for early abort case
* Access rewrite
Initialize blocks in a separate object, and get via that object
removes a couple hundred "might be null" warnings since blocks are now readonly getters
some block references have been relocated, but interfaces should expose all that's needed
put HoF6 controls in a groupbox, and disable
* Readonly personal data
* IVs non nullable for mystery gift
* Explicitly initialize forced encounter moves
* Make shadow objects readonly & non-null
Put murkrow fix in binary data resource, instead of on startup
* Assign dex form fetch on constructor
Fixes legality parsing edge cases
also handle cxd parse for valid; exit before exception is thrown in FrameGenerator
* Remove unnecessary null checks
* Keep empty value until init
SetPouch sets the value to an actual one during load, but whatever
* Readonly team lock data
* Readonly locks
Put locked encounters at bottom (favor unlocked)
* Mail readonly data / offset
Rearrange some call flow and pass defaults
Add fake classes for SaveDataEditor mocking
Always party size, no need to check twice in stat editor
use a fake save file as initial data for savedata editor, and for
gamedata (wow i found a usage)
constrain eventwork editor to struct variable types (uint, int, etc),
thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
|
|
|
|
continue;
|
2020-06-17 02:46:22 +00:00
|
|
|
|
if (HasFlag(value) || HasCount(value))
|
2017-09-06 03:32:07 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
2019-10-08 01:40:09 +00:00
|
|
|
|
static bool HasFlag(object o) => o is bool z && z;
|
|
|
|
|
static bool HasCount(object o) => o is int z && z > 0;
|
2017-09-06 03:32:07 +00:00
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2017-09-04 02:51:29 +00:00
|
|
|
|
private static IEnumerable<RibbonResult> GetRibbonResults(PKM pkm, object encounterContent, int gen)
|
|
|
|
|
{
|
|
|
|
|
return GetInvalidRibbons(pkm, gen)
|
|
|
|
|
.Concat(GetInvalidRibbonsEvent1(pkm, encounterContent))
|
|
|
|
|
.Concat(GetInvalidRibbonsEvent2(pkm, encounterContent));
|
|
|
|
|
}
|
2018-05-12 15:13:39 +00:00
|
|
|
|
|
2017-09-04 02:51:29 +00:00
|
|
|
|
private static IEnumerable<RibbonResult> GetInvalidRibbons(PKM pkm, int gen)
|
|
|
|
|
{
|
|
|
|
|
bool artist = false;
|
|
|
|
|
if (pkm is IRibbonSetOnly3 o3)
|
|
|
|
|
{
|
2020-08-09 05:55:44 +00:00
|
|
|
|
artist = o3.ShouldHaveArtistRibbon();
|
|
|
|
|
if (o3.RibbonWorld) // is a part of Event4, but O3 doesn't have the others
|
|
|
|
|
yield return new RibbonResult(nameof(o3.RibbonWorld));
|
2017-09-04 02:51:29 +00:00
|
|
|
|
}
|
|
|
|
|
if (pkm is IRibbonSetUnique3 u3)
|
|
|
|
|
{
|
|
|
|
|
if (gen != 3 || !IsAllowedBattleFrontier(pkm.Species))
|
|
|
|
|
{
|
|
|
|
|
if (u3.RibbonWinning)
|
|
|
|
|
yield return new RibbonResult(nameof(u3.RibbonWinning));
|
|
|
|
|
if (u3.RibbonVictory)
|
|
|
|
|
yield return new RibbonResult(nameof(u3.RibbonVictory));
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-06-17 02:46:22 +00:00
|
|
|
|
|
2017-09-04 02:51:29 +00:00
|
|
|
|
if (pkm is IRibbonSetUnique4 u4)
|
|
|
|
|
{
|
2020-05-10 03:45:19 +00:00
|
|
|
|
if (!IsAllowedBattleFrontier(pkm.Species, pkm.AltForm, 4) || gen > 4)
|
2018-07-27 02:34:27 +00:00
|
|
|
|
{
|
2017-09-04 02:51:29 +00:00
|
|
|
|
foreach (var z in GetInvalidRibbonsNone(u4.RibbonBitsAbility(), u4.RibbonNamesAbility()))
|
|
|
|
|
yield return z;
|
2018-07-27 02:34:27 +00:00
|
|
|
|
}
|
2017-09-04 02:51:29 +00:00
|
|
|
|
|
2020-06-17 02:46:22 +00:00
|
|
|
|
var c3 = u4.RibbonBitsContest3();
|
|
|
|
|
var c3n = u4.RibbonNamesContest3();
|
|
|
|
|
var iter3 = gen == 3 ? GetMissingContestRibbons(c3, c3n) : GetInvalidRibbonsNone(c3, c3n);
|
|
|
|
|
foreach (var z in iter3)
|
2017-09-04 02:51:29 +00:00
|
|
|
|
yield return z;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 5; ++i)
|
|
|
|
|
artist |= c3[3 | i << 2]; // any master rank ribbon
|
|
|
|
|
|
2020-06-17 02:46:22 +00:00
|
|
|
|
var c4 = u4.RibbonBitsContest4();
|
|
|
|
|
var c4n = u4.RibbonNamesContest4();
|
|
|
|
|
var iter4 = (gen == 3 || gen == 4) && IsAllowedInContest4(pkm.Species) ? GetMissingContestRibbons(c4, c4n) : GetInvalidRibbonsNone(c4, c4n);
|
|
|
|
|
foreach (var z in iter4)
|
|
|
|
|
yield return z;
|
2017-09-04 02:51:29 +00:00
|
|
|
|
}
|
|
|
|
|
if (pkm is IRibbonSetCommon4 s4)
|
|
|
|
|
{
|
|
|
|
|
bool inhabited4 = 3 <= gen && gen <= 4;
|
2018-10-28 04:59:31 +00:00
|
|
|
|
var iterate = GetInvalidRibbons4Any(pkm, s4, gen);
|
2017-09-04 02:51:29 +00:00
|
|
|
|
if (!inhabited4)
|
|
|
|
|
iterate = iterate.Concat(GetInvalidRibbonsNone(s4.RibbonBitsOnly(), s4.RibbonNamesOnly()));
|
|
|
|
|
foreach (var z in iterate)
|
|
|
|
|
yield return z;
|
|
|
|
|
}
|
|
|
|
|
if (pkm is IRibbonSetCommon6 s6)
|
|
|
|
|
{
|
2017-12-07 15:57:07 +00:00
|
|
|
|
artist = s6.RibbonCountMemoryContest >= 4;
|
2017-09-04 02:51:29 +00:00
|
|
|
|
bool inhabited6 = 3 <= gen && gen <= 6;
|
|
|
|
|
|
|
|
|
|
var iterate = inhabited6
|
|
|
|
|
? GetInvalidRibbons6Any(pkm, s6, gen)
|
|
|
|
|
: 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));
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-07 04:22:23 +00:00
|
|
|
|
// Gen8+ replaced with Max Friendship. Gen6/7 uses affection.
|
2020-08-02 18:06:30 +00:00
|
|
|
|
if (pkm is IAffection a && s6.RibbonBestFriends) // can't lower affection
|
2019-12-07 04:22:23 +00:00
|
|
|
|
{
|
2020-08-02 18:06:30 +00:00
|
|
|
|
if (a.OT_Affection < 255 && pkm.IsUntraded)
|
2019-12-07 04:22:23 +00:00
|
|
|
|
yield return new RibbonResult(nameof(s6.RibbonBestFriends));
|
|
|
|
|
}
|
2017-09-04 02:51:29 +00:00
|
|
|
|
}
|
|
|
|
|
if (pkm is IRibbonSetCommon7 s7)
|
|
|
|
|
{
|
2020-02-17 01:41:38 +00:00
|
|
|
|
bool inhabited7 = gen <= 7 && !pkm.GG;
|
2017-09-04 02:51:29 +00:00
|
|
|
|
var iterate = inhabited7 ? GetInvalidRibbons7Any(pkm, s7) : GetInvalidRibbonsNone(s7.RibbonBits(), s7.RibbonNames());
|
|
|
|
|
foreach (var z in iterate)
|
|
|
|
|
yield return z;
|
|
|
|
|
}
|
|
|
|
|
if (pkm is IRibbonSetCommon3 s3)
|
|
|
|
|
{
|
|
|
|
|
if (s3.RibbonChampionG3Hoenn && gen != 3)
|
|
|
|
|
yield return new RibbonResult(nameof(s3.RibbonChampionG3Hoenn)); // RSE HoF
|
|
|
|
|
if (s3.RibbonArtist && (gen != 3 || !artist))
|
|
|
|
|
yield return new RibbonResult(nameof(s3.RibbonArtist)); // RSE Master Rank Portrait
|
|
|
|
|
if (s3.RibbonEffort && gen == 5 && pkm.Format == 5) // unobtainable in Gen 5
|
|
|
|
|
yield return new RibbonResult(nameof(s3.RibbonEffort));
|
|
|
|
|
}
|
2019-12-07 19:58:56 +00:00
|
|
|
|
if (pkm is IRibbonSetCommon8 s8)
|
|
|
|
|
{
|
2019-12-08 07:39:32 +00:00
|
|
|
|
bool inhabited8 = gen <= 8;
|
2020-06-20 02:51:57 +00:00
|
|
|
|
var iterate = inhabited8 ? GetInvalidRibbons8Any(pkm, s8) : GetInvalidRibbonsNone(s8.RibbonBits(), s8.RibbonNames());
|
2019-12-07 19:58:56 +00:00
|
|
|
|
foreach (var z in iterate)
|
|
|
|
|
yield return z;
|
|
|
|
|
}
|
2017-09-04 02:51:29 +00:00
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2020-06-17 02:46:22 +00:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-04 02:51:29 +00:00
|
|
|
|
private static IEnumerable<RibbonResult> GetInvalidRibbons4Any(PKM pkm, IRibbonSetCommon4 s4, int gen)
|
|
|
|
|
{
|
|
|
|
|
if (s4.RibbonRecord)
|
|
|
|
|
yield return new RibbonResult(nameof(s4.RibbonRecord)); // Unobtainable
|
2020-01-21 07:09:28 +00:00
|
|
|
|
if (s4.RibbonFootprint && !CanHaveFootprintRibbon(pkm, gen))
|
2017-09-04 02:51:29 +00:00
|
|
|
|
yield return new RibbonResult(nameof(s4.RibbonFootprint));
|
|
|
|
|
|
|
|
|
|
bool gen34 = gen == 3 || gen == 4;
|
|
|
|
|
bool not6 = pkm.Format < 6 || gen > 6 || gen < 3;
|
|
|
|
|
bool noDaily = !gen34 && not6;
|
2018-07-27 02:34:27 +00:00
|
|
|
|
bool noCosmetic = !gen34 && (not6 || (pkm.XY && pkm.IsUntraded));
|
2017-09-04 02:51:29 +00:00
|
|
|
|
|
|
|
|
|
if (noDaily)
|
2018-07-27 02:34:27 +00:00
|
|
|
|
{
|
2017-09-04 02:51:29 +00:00
|
|
|
|
foreach (var z in GetInvalidRibbonsNone(s4.RibbonBitsDaily(), s4.RibbonNamesDaily()))
|
|
|
|
|
yield return z;
|
2018-07-27 02:34:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-04 02:51:29 +00:00
|
|
|
|
if (noCosmetic)
|
2018-07-27 02:34:27 +00:00
|
|
|
|
{
|
2017-09-04 02:51:29 +00:00
|
|
|
|
foreach (var z in GetInvalidRibbonsNone(s4.RibbonBitsCosmetic(), s4.RibbonNamesCosmetic()))
|
|
|
|
|
yield return z;
|
2018-07-27 02:34:27 +00:00
|
|
|
|
}
|
2017-09-04 02:51:29 +00:00
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2020-01-21 07:09:28 +00:00
|
|
|
|
private static bool CanHaveFootprintRibbon(PKM pkm, int gen)
|
|
|
|
|
{
|
|
|
|
|
if (gen <= 4) // Friendship Check unnecessary - can decrease after obtaining ribbon.
|
|
|
|
|
return true;
|
|
|
|
|
// Gen5: Can't obtain
|
|
|
|
|
// Gen6/7: Increase level by 30 from original level
|
2020-01-21 17:05:39 +00:00
|
|
|
|
if (pkm.Format >= 6 && (gen != 8 && !pkm.GG) && (pkm.CurrentLevel - pkm.Met_Level >= 30))
|
2020-01-21 07:09:28 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
// Gen8: Can't obtain
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-04 02:51:29 +00:00
|
|
|
|
private static IEnumerable<RibbonResult> GetInvalidRibbons6Any(PKM pkm, IRibbonSetCommon6 s6, int gen)
|
|
|
|
|
{
|
|
|
|
|
foreach (var p in GetInvalidRibbons6Memory(pkm, s6, gen))
|
|
|
|
|
yield return p;
|
|
|
|
|
|
|
|
|
|
bool untraded = pkm.IsUntraded;
|
|
|
|
|
var iter = untraded ? GetInvalidRibbons6Untraded(pkm, s6) : GetInvalidRibbons6Traded(pkm, s6);
|
|
|
|
|
foreach (var p in iter)
|
|
|
|
|
yield return p;
|
|
|
|
|
|
2018-03-26 23:42:18 +00:00
|
|
|
|
var contest = s6.RibbonBitsContest();
|
|
|
|
|
bool allContest = contest.All(z => z);
|
2018-07-27 02:34:27 +00:00
|
|
|
|
if ((allContest ^ s6.RibbonContestStar) && !(untraded && pkm.XY)) // if not already checked
|
2017-09-04 02:51:29 +00:00
|
|
|
|
yield return new RibbonResult(nameof(s6.RibbonContestStar), s6.RibbonContestStar);
|
|
|
|
|
|
2018-03-26 23:42:18 +00:00
|
|
|
|
// Each contest victory requires a contest participation; each participation gives 20 OT affection (not current trainer).
|
2020-02-15 19:43:21 +00:00
|
|
|
|
// Affection is discarded on PK7->PK8 in favor of friendship, which can be lowered.
|
2020-08-02 18:06:30 +00:00
|
|
|
|
if (pkm is IAffection a)
|
2020-02-15 19:43:21 +00:00
|
|
|
|
{
|
2020-08-02 18:06:30 +00:00
|
|
|
|
var affect = a.OT_Affection;
|
2020-02-15 19:43:21 +00:00
|
|
|
|
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);
|
|
|
|
|
}
|
2018-03-26 23:42:18 +00:00
|
|
|
|
|
2020-06-17 02:46:22 +00:00
|
|
|
|
const int memChatelaine = 30;
|
2020-08-01 00:25:14 +00:00
|
|
|
|
bool hasChampMemory = pkm.Format <= 7 && pkm is ITrainerMemories m && (m.HT_Memory == memChatelaine || m.OT_Memory == memChatelaine);
|
2018-05-26 02:54:25 +00:00
|
|
|
|
if (!IsAllowedBattleFrontier(pkm.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;
|
|
|
|
|
}
|
2017-09-04 02:51:29 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2017-09-04 02:51:29 +00:00
|
|
|
|
private static IEnumerable<RibbonResult> GetInvalidRibbons6Memory(PKM pkm, IRibbonSetCommon6 s6, int gen)
|
|
|
|
|
{
|
|
|
|
|
int contest = 0;
|
|
|
|
|
int battle = 0;
|
|
|
|
|
switch (gen)
|
|
|
|
|
{
|
|
|
|
|
case 3:
|
|
|
|
|
contest = IsAllowedInContest4(pkm.Species) ? 40 : 20;
|
|
|
|
|
battle = IsAllowedBattleFrontier(pkm.Species) ? 8 : 0;
|
|
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
contest = IsAllowedInContest4(pkm.Species) ? 20 : 0;
|
|
|
|
|
battle = IsAllowedBattleFrontier(pkm.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));
|
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2017-09-04 02:51:29 +00:00
|
|
|
|
private static IEnumerable<RibbonResult> GetInvalidRibbons6Untraded(PKM pkm, IRibbonSetCommon6 s6)
|
|
|
|
|
{
|
|
|
|
|
if (pkm.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 (pkm.AO)
|
|
|
|
|
{
|
|
|
|
|
if (s6.RibbonChampionKalos)
|
|
|
|
|
yield return new RibbonResult(nameof(s6.RibbonChampionKalos));
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2017-09-04 02:51:29 +00:00
|
|
|
|
private static IEnumerable<RibbonResult> GetInvalidRibbons6Traded(PKM pkm, IRibbonSetCommon6 s6)
|
|
|
|
|
{
|
2019-09-21 03:55:36 +00:00
|
|
|
|
// Medal count is wiped on transfer to pk8
|
|
|
|
|
if (s6.RibbonTraining && pkm.Format <= 7)
|
2017-09-04 02:51:29 +00:00
|
|
|
|
{
|
|
|
|
|
const int req = 12; // only first 12
|
2019-09-21 03:55:36 +00:00
|
|
|
|
int count = ((ISuperTrain)pkm).SuperTrainingMedalCount(req);
|
2017-09-04 02:51:29 +00:00
|
|
|
|
if (count < req)
|
|
|
|
|
yield return new RibbonResult(nameof(s6.RibbonTraining));
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-17 02:46:22 +00:00
|
|
|
|
const int memChampion = 27;
|
2020-08-01 00:25:14 +00:00
|
|
|
|
bool hasChampMemory = pkm is ITrainerMemories m && ((pkm.Format < 8 && m.HT_Memory == memChampion) || (pkm.Gen6 && m.OT_Memory == memChampion));
|
2017-09-04 02:51:29 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2017-09-04 02:51:29 +00:00
|
|
|
|
private static IEnumerable<RibbonResult> GetInvalidRibbons7Any(PKM pkm, IRibbonSetCommon7 s7)
|
|
|
|
|
{
|
|
|
|
|
if (!IsAllowedBattleFrontier(pkm.Species))
|
|
|
|
|
{
|
|
|
|
|
if (s7.RibbonBattleRoyale)
|
|
|
|
|
yield return new RibbonResult(nameof(s7.RibbonBattleRoyale));
|
2020-07-10 03:53:57 +00:00
|
|
|
|
if (s7.RibbonBattleTreeGreat && !pkm.USUM && pkm.IsUntraded)
|
2017-09-04 02:51:29 +00:00
|
|
|
|
yield return new RibbonResult(nameof(s7.RibbonBattleTreeGreat));
|
|
|
|
|
if (s7.RibbonBattleTreeMaster)
|
|
|
|
|
yield return new RibbonResult(nameof(s7.RibbonBattleTreeMaster));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-20 02:51:57 +00:00
|
|
|
|
private static IEnumerable<RibbonResult> GetInvalidRibbons8Any(PKM pkm, IRibbonSetCommon8 s8)
|
2019-12-07 19:58:56 +00:00
|
|
|
|
{
|
|
|
|
|
if (!pkm.InhabitedGeneration(8) || !((PersonalInfoSWSH)PersonalTable.SWSH[pkm.Species]).IsPresentInGame)
|
|
|
|
|
{
|
|
|
|
|
if (s8.RibbonChampionGalar)
|
|
|
|
|
yield return new RibbonResult(nameof(s8.RibbonChampionGalar));
|
2020-07-10 03:53:57 +00:00
|
|
|
|
if (s8.RibbonTowerMaster && !pkm.SWSH && pkm.IsUntraded)
|
2019-12-07 19:58:56 +00:00
|
|
|
|
yield return new RibbonResult(nameof(s8.RibbonTowerMaster));
|
|
|
|
|
if (s8.RibbonMasterRank)
|
|
|
|
|
yield return new RibbonResult(nameof(s8.RibbonMasterRank));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-06-17 02:46:22 +00:00
|
|
|
|
const int memChampion = 27;
|
2020-08-01 00:25:14 +00:00
|
|
|
|
{
|
|
|
|
|
bool hasChampMemory = (pkm.Format == 8 && pkm is IMemoryHT h && h.HT_Memory == memChampion) || (pkm.Gen8 && pkm is IMemoryOT o && o.OT_Memory == memChampion);
|
|
|
|
|
if (hasChampMemory && !s8.RibbonChampionGalar)
|
|
|
|
|
yield return new RibbonResult(nameof(s8.RibbonChampionGalar));
|
|
|
|
|
}
|
2020-06-17 03:16:21 +00:00
|
|
|
|
|
|
|
|
|
// Legends cannot compete in Ranked, thus cannot reach Master Rank and obtain the ribbon.
|
2020-06-20 02:24:58 +00:00
|
|
|
|
// Past gen Pokemon can get the ribbon only if they've been reset.
|
2020-06-20 02:51:57 +00:00
|
|
|
|
if (s8.RibbonMasterRank && !CanParticipateInRankedSWSH(pkm))
|
2020-06-17 03:16:21 +00:00
|
|
|
|
yield return new RibbonResult(nameof(s8.RibbonMasterRank));
|
2020-07-10 03:53:57 +00:00
|
|
|
|
|
|
|
|
|
if (s8.RibbonTowerMaster)
|
|
|
|
|
{
|
|
|
|
|
if (!pkm.SWSH && pkm.IsUntraded)
|
|
|
|
|
yield return new RibbonResult(nameof(s8.RibbonTowerMaster));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// 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;
|
2020-08-01 00:25:14 +00:00
|
|
|
|
if (pkm is IMemoryOT o && o.OT_Memory == strongest || pkm is IMemoryHT h && h.HT_Memory == strongest)
|
2020-07-10 03:53:57 +00:00
|
|
|
|
{
|
|
|
|
|
if (pkm.Gen8 || !IsAllowedBattleFrontier(pkm.Species) || (pkm is IRibbonSetCommon6 s6 && !s6.RibbonBattlerSkillful))
|
|
|
|
|
yield return new RibbonResult(nameof(s8.RibbonTowerMaster));
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-12-07 19:58:56 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-20 02:51:57 +00:00
|
|
|
|
private static bool CanParticipateInRankedSWSH(PKM pkm)
|
|
|
|
|
{
|
|
|
|
|
if (!pkm.SWSH && pkm is IBattleVersion v && v.BattleVersion == 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// Clamp to permitted species
|
|
|
|
|
var spec = pkm.Species;
|
2020-06-20 04:20:19 +00:00
|
|
|
|
|
|
|
|
|
if (638 <= spec && spec <= 640)
|
|
|
|
|
return true; // Sub Legends
|
2020-06-20 02:51:57 +00:00
|
|
|
|
if (722 <= spec && spec <= 730)
|
|
|
|
|
return true; // Gen7 starters
|
|
|
|
|
var pi = (PersonalInfoSWSH)PersonalTable.SWSH[spec];
|
|
|
|
|
var galarDex = pi.PokeDexIndex;
|
2020-06-20 04:20:19 +00:00
|
|
|
|
var armorDex = pi.ArmorDexIndex;
|
|
|
|
|
if (1 <= galarDex && galarDex <= 397)
|
|
|
|
|
return true;
|
|
|
|
|
if (1 <= armorDex && armorDex <= 210)
|
|
|
|
|
return true;
|
2020-06-20 02:51:57 +00:00
|
|
|
|
|
2020-06-20 04:20:19 +00:00
|
|
|
|
return false;
|
2020-06-20 02:51:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-04 02:51:29 +00:00
|
|
|
|
private static IEnumerable<RibbonResult> GetInvalidRibbonsEvent1(PKM pkm, object encounterContent)
|
|
|
|
|
{
|
|
|
|
|
if (!(pkm is IRibbonSetEvent3 set1))
|
|
|
|
|
yield break;
|
|
|
|
|
var names = set1.RibbonNames();
|
|
|
|
|
var sb = set1.RibbonBits();
|
PKHeX.Core Nullable cleanup (#2401)
* Handle some nullable cases
Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data)
Make some classes have explicit constructors instead of { } initialization
* Handle bits more obviously without null
* Make SaveFile.BAK explicitly readonly again
* merge constructor methods to have readonly fields
* Inline some properties
* More nullable handling
* Rearrange box actions
define straightforward classes to not have any null properties
* Make extrabyte reference array immutable
* Move tooltip creation to designer
* Rearrange some logic to reduce nesting
* Cache generated fonts
* Split mystery gift album purpose
* Handle more tooltips
* Disallow null setters
* Don't capture RNG object, only type enum
* Unify learnset objects
Now have readonly properties which are never null
don't new() empty learnsets (>800 Learnset objects no longer created,
total of 2400 objects since we also new() a move & level array)
optimize g1/2 reader for early abort case
* Access rewrite
Initialize blocks in a separate object, and get via that object
removes a couple hundred "might be null" warnings since blocks are now readonly getters
some block references have been relocated, but interfaces should expose all that's needed
put HoF6 controls in a groupbox, and disable
* Readonly personal data
* IVs non nullable for mystery gift
* Explicitly initialize forced encounter moves
* Make shadow objects readonly & non-null
Put murkrow fix in binary data resource, instead of on startup
* Assign dex form fetch on constructor
Fixes legality parsing edge cases
also handle cxd parse for valid; exit before exception is thrown in FrameGenerator
* Remove unnecessary null checks
* Keep empty value until init
SetPouch sets the value to an actual one during load, but whatever
* Readonly team lock data
* Readonly locks
Put locked encounters at bottom (favor unlocked)
* Mail readonly data / offset
Rearrange some call flow and pass defaults
Add fake classes for SaveDataEditor mocking
Always party size, no need to check twice in stat editor
use a fake save file as initial data for savedata editor, and for
gamedata (wow i found a usage)
constrain eventwork editor to struct variable types (uint, int, etc),
thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
|
|
|
|
var eb = encounterContent is IRibbonSetEvent3 e3 ? e3.RibbonBits() : new bool[sb.Length];
|
2017-09-04 02:51:29 +00:00
|
|
|
|
|
|
|
|
|
if (pkm.Gen3)
|
|
|
|
|
{
|
|
|
|
|
eb[0] = sb[0]; // permit Earth Ribbon
|
|
|
|
|
if (pkm.Version == 15 && encounterContent is EncounterStaticShadow s)
|
|
|
|
|
{
|
|
|
|
|
// only require national ribbon if no longer on origin game
|
2018-04-29 15:31:57 +00:00
|
|
|
|
bool xd = s.Version == GameVersion.XD;
|
2018-07-27 02:34:27 +00:00
|
|
|
|
eb[1] = !((xd && pkm is XK3 x && !x.RibbonNational) || (!xd && pkm is CK3 c && !c.RibbonNational));
|
2017-09-04 02:51:29 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < sb.Length; i++)
|
2018-07-27 02:34:27 +00:00
|
|
|
|
{
|
2017-09-04 02:51:29 +00:00
|
|
|
|
if (sb[i] != eb[i])
|
|
|
|
|
yield return new RibbonResult(names[i], !eb[i]); // only flag if invalid
|
2018-07-27 02:34:27 +00:00
|
|
|
|
}
|
2017-09-04 02:51:29 +00:00
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2017-09-04 02:51:29 +00:00
|
|
|
|
private static IEnumerable<RibbonResult> GetInvalidRibbonsEvent2(PKM pkm, object encounterContent)
|
|
|
|
|
{
|
|
|
|
|
if (!(pkm is IRibbonSetEvent4 set2))
|
|
|
|
|
yield break;
|
|
|
|
|
var names = set2.RibbonNames();
|
|
|
|
|
var sb = set2.RibbonBits();
|
PKHeX.Core Nullable cleanup (#2401)
* Handle some nullable cases
Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data)
Make some classes have explicit constructors instead of { } initialization
* Handle bits more obviously without null
* Make SaveFile.BAK explicitly readonly again
* merge constructor methods to have readonly fields
* Inline some properties
* More nullable handling
* Rearrange box actions
define straightforward classes to not have any null properties
* Make extrabyte reference array immutable
* Move tooltip creation to designer
* Rearrange some logic to reduce nesting
* Cache generated fonts
* Split mystery gift album purpose
* Handle more tooltips
* Disallow null setters
* Don't capture RNG object, only type enum
* Unify learnset objects
Now have readonly properties which are never null
don't new() empty learnsets (>800 Learnset objects no longer created,
total of 2400 objects since we also new() a move & level array)
optimize g1/2 reader for early abort case
* Access rewrite
Initialize blocks in a separate object, and get via that object
removes a couple hundred "might be null" warnings since blocks are now readonly getters
some block references have been relocated, but interfaces should expose all that's needed
put HoF6 controls in a groupbox, and disable
* Readonly personal data
* IVs non nullable for mystery gift
* Explicitly initialize forced encounter moves
* Make shadow objects readonly & non-null
Put murkrow fix in binary data resource, instead of on startup
* Assign dex form fetch on constructor
Fixes legality parsing edge cases
also handle cxd parse for valid; exit before exception is thrown in FrameGenerator
* Remove unnecessary null checks
* Keep empty value until init
SetPouch sets the value to an actual one during load, but whatever
* Readonly team lock data
* Readonly locks
Put locked encounters at bottom (favor unlocked)
* Mail readonly data / offset
Rearrange some call flow and pass defaults
Add fake classes for SaveDataEditor mocking
Always party size, no need to check twice in stat editor
use a fake save file as initial data for savedata editor, and for
gamedata (wow i found a usage)
constrain eventwork editor to struct variable types (uint, int, etc),
thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
|
|
|
|
var eb = encounterContent is IRibbonSetEvent4 e4 ? e4.RibbonBits() : new bool[sb.Length];
|
2017-09-04 02:51:29 +00:00
|
|
|
|
|
2020-08-21 23:35:49 +00:00
|
|
|
|
if (encounterContent is EncounterStatic7 s && s.RibbonWishing)
|
2017-09-04 02:51:29 +00:00
|
|
|
|
eb[1] = true; // require Wishing Ribbon
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < sb.Length; i++)
|
2018-07-27 02:34:27 +00:00
|
|
|
|
{
|
2017-09-04 02:51:29 +00:00
|
|
|
|
if (sb[i] != eb[i])
|
|
|
|
|
yield return new RibbonResult(names[i], !eb[i]); // only flag if invalid
|
2018-07-27 02:34:27 +00:00
|
|
|
|
}
|
2017-09-04 02:51:29 +00:00
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2017-09-04 02:51:29 +00:00
|
|
|
|
private static IEnumerable<RibbonResult> GetInvalidRibbonsNone(IReadOnlyList<bool> bits, IReadOnlyList<string> names)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < bits.Count; i++)
|
2018-07-27 02:34:27 +00:00
|
|
|
|
{
|
2017-09-04 02:51:29 +00:00
|
|
|
|
if (bits[i])
|
|
|
|
|
yield return new RibbonResult(names[i]);
|
2018-07-27 02:34:27 +00:00
|
|
|
|
}
|
2017-09-04 02:51:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool IsAllowedInContest4(int species) => species != 201 && species != 132; // Disallow Unown and Ditto
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2018-10-28 04:59:31 +00:00
|
|
|
|
private static bool IsAllowedBattleFrontier(int species) => !Legal.BattleFrontierBanlist.Contains(species);
|
|
|
|
|
|
|
|
|
|
private static bool IsAllowedBattleFrontier(int species, int form, int gen)
|
2017-09-04 02:51:29 +00:00
|
|
|
|
{
|
2019-12-09 01:39:19 +00:00
|
|
|
|
if (gen == 4 && species == (int)Species.Pichu && form == 1) // spiky
|
2017-09-04 02:51:29 +00:00
|
|
|
|
return false;
|
2018-10-28 04:59:31 +00:00
|
|
|
|
return IsAllowedBattleFrontier(species);
|
2017-09-04 02:51:29 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|