2018-06-24 05:00:01 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using static PKHeX.Core.LegalityCheckStrings;
|
2018-10-05 01:52:00 +00:00
|
|
|
|
using static PKHeX.Core.Encounters6;
|
2019-11-26 19:01:09 +00:00
|
|
|
|
using static PKHeX.Core.Encounters8;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
|
|
|
|
namespace PKHeX.Core
|
|
|
|
|
{
|
2018-07-02 02:17:37 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Verifies the <see cref="PKM.OT_Memory"/>, <see cref="PKM.HT_Memory"/>, and associated values.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed class MemoryVerifier : Verifier
|
2018-06-24 05:00:01 +00:00
|
|
|
|
{
|
|
|
|
|
protected override CheckIdentifier Identifier => CheckIdentifier.Memory;
|
|
|
|
|
|
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
|
|
|
|
private static readonly CheckResult NONE = new CheckResult(CheckIdentifier.Memory);
|
|
|
|
|
|
2018-06-24 05:00:01 +00:00
|
|
|
|
public override void Verify(LegalityAnalysis data)
|
|
|
|
|
{
|
2018-09-01 21:11:12 +00:00
|
|
|
|
if (data.pkm.Format < 6)
|
|
|
|
|
return;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
var hist = VerifyHistory(data);
|
2018-07-02 02:17:37 +00:00
|
|
|
|
VerifyOTMemory(data);
|
|
|
|
|
VerifyHTMemory(data);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
data.AddLine(hist);
|
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2018-06-24 05:00:01 +00:00
|
|
|
|
private CheckResult VerifyHistory(LegalityAnalysis data)
|
|
|
|
|
{
|
|
|
|
|
var pkm = data.pkm;
|
|
|
|
|
var Info = data.Info;
|
|
|
|
|
var EncounterMatch = data.EncounterMatch;
|
|
|
|
|
|
|
|
|
|
if (Info.Generation < 6)
|
|
|
|
|
{
|
2018-07-27 02:34:27 +00:00
|
|
|
|
if ((pkm.OT_Affection != 0 && Info.Generation <= 2) || IsInvalidContestAffection(pkm))
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryStatAffectionOT0);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.OT_Memory > 0 || pkm.OT_Feeling > 0 || pkm.OT_Intensity > 0 || pkm.OT_TextVar > 0)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryIndexIDOT0);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pkm.Format >= 6 && Info.Generation != pkm.Format && pkm.CurrentHandler != 1)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LTransferHTFlagRequired);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
|
|
|
|
if (pkm.HT_Gender > 1)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(string.Format(LMemoryHTGender, pkm.HT_Gender));
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
|
|
|
|
if (EncounterMatch is WC6 wc6 && wc6.OT_Name.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
if (pkm.OT_Friendship != PersonalTable.AO[EncounterMatch.Species].BaseFriendship)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryStatFriendshipOTBaseEvent);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.OT_Affection != 0 && (pkm.AO || !pkm.IsUntraded) && IsInvalidContestAffection(pkm))
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryStatAffectionOT0Event);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.CurrentHandler != 1)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryHTEvent);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
else if (EncounterMatch is WC7 wc7 && wc7.OT_Name.Length > 0 && wc7.TID != 18075) // Ash Pikachu QR Gift doesn't set Current Handler
|
|
|
|
|
{
|
|
|
|
|
if (pkm.OT_Friendship != PersonalTable.USUM[EncounterMatch.Species].BaseFriendship)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryStatFriendshipOTBaseEvent);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.OT_Affection != 0)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryStatAffectionOT0Event);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.CurrentHandler != 1)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryHTEvent);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
else if (EncounterMatch is MysteryGift mg && mg.Format < 6 && pkm.Format >= 6)
|
|
|
|
|
{
|
|
|
|
|
if (pkm.OT_Affection != 0 && IsInvalidContestAffection(pkm))
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryStatAffectionOT0Event);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.CurrentHandler != 1)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryHTEvent);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check sequential order (no zero gaps)
|
2018-07-14 17:34:34 +00:00
|
|
|
|
if (pkm is IGeoTrack t)
|
2018-06-24 05:00:01 +00:00
|
|
|
|
{
|
2018-07-14 17:34:34 +00:00
|
|
|
|
var valid = t.GetValidity();
|
|
|
|
|
if (valid == GeoValid.CountryAfterPreviousEmpty)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LGeoBadOrder);
|
2018-07-14 17:34:34 +00:00
|
|
|
|
if (valid == GeoValid.RegionWithoutCountry)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LGeoNoRegion);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
if (pkm.Format >= 7)
|
2018-07-14 17:34:34 +00:00
|
|
|
|
return VerifyHistory7(data);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
|
|
|
|
// Determine if we should check for Handling Trainer Memories
|
|
|
|
|
// A Pokémon is untraded if...
|
2018-07-12 02:13:09 +00:00
|
|
|
|
bool untraded = GetIsUntradedByEncounterMemories(pkm, EncounterMatch, Info.Generation);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (untraded) // Is not Traded
|
|
|
|
|
{
|
|
|
|
|
if (pkm.HT_Name.Length != 0)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LGeoNoCountryHT);
|
2018-07-14 17:34:34 +00:00
|
|
|
|
if (pkm is IGeoTrack g && g.Geo1_Country != 0)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LGeoNoHT);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.HT_Memory != 0)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryMissingHTName);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.CurrentHandler != 0) // Badly edited; PKHeX doesn't trip this.
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryHTFlagInvalid);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.HT_Friendship != 0)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryStatFriendshipHT0);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.HT_Affection != 0)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryStatAffectionHT0);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.XY && pkm is IContestStats s && s.HasContestStats())
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LContestZero);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
|
|
|
|
if (VerifyHistoryUntradedHandler(pkm, out CheckResult chk1))
|
|
|
|
|
return chk1;
|
|
|
|
|
if (EncounterMatch.Species != pkm.Species && VerifyHistoryUntradedEvolution(pkm, Info.EvoChainsAllGens, out CheckResult chk2))
|
|
|
|
|
return chk2;
|
|
|
|
|
}
|
|
|
|
|
else // Is Traded
|
|
|
|
|
{
|
|
|
|
|
if (pkm.Format == 6 && pkm.HT_Memory == 0 && !pkm.IsEgg)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryMissingHT);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Memory ChecksResult
|
|
|
|
|
if (pkm.IsEgg)
|
|
|
|
|
{
|
|
|
|
|
if (pkm.HT_Memory != 0)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryArgBadHT);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.OT_Memory != 0)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryArgBadEggOT);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
else if (!(EncounterMatch is WC6))
|
|
|
|
|
{
|
|
|
|
|
if (pkm.OT_Memory == 0 ^ !pkm.Gen6)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryMissingOT);
|
2018-07-12 00:43:48 +00:00
|
|
|
|
if (Info.Generation < 6 && pkm.OT_Affection != 0 && IsInvalidContestAffection(pkm))
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LMemoryStatAffectionOT0);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
// Unimplemented: Ingame Trade Memories
|
|
|
|
|
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetValid(LMemoryValid);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2018-07-14 17:34:34 +00:00
|
|
|
|
private CheckResult VerifyHistory7(LegalityAnalysis data)
|
2018-06-24 05:00:01 +00:00
|
|
|
|
{
|
|
|
|
|
var pkm = data.pkm;
|
|
|
|
|
var EncounterMatch = data.EncounterMatch;
|
|
|
|
|
var Info = data.Info;
|
|
|
|
|
|
2018-07-14 17:34:34 +00:00
|
|
|
|
if (pkm.VC1 && pkm is IGeoTrack g)
|
2018-06-24 05:00:01 +00:00
|
|
|
|
{
|
2018-07-14 17:34:34 +00:00
|
|
|
|
var hasGeo = g.Geo1_Country != 0;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (!hasGeo)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LGeoMemoryMissing);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((2 >= Info.Generation || Info.Generation >= 7) && pkm is IContestStats s && s.HasContestStats())
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetInvalid(LContestZero);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
|
|
|
|
if (!pkm.WasEvent && pkm.HT_Name.Length == 0) // Is not Traded
|
|
|
|
|
{
|
|
|
|
|
if (VerifyHistoryUntradedHandler(pkm, out CheckResult chk1))
|
|
|
|
|
return chk1;
|
|
|
|
|
if (EncounterMatch.Species != pkm.Species && VerifyHistoryUntradedEvolution(pkm, Info.EvoChainsAllGens, out CheckResult chk2))
|
|
|
|
|
return chk2;
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-01 21:11:12 +00:00
|
|
|
|
return GetValid(LMemoryValid);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2018-07-02 02:17:37 +00:00
|
|
|
|
private bool VerifyHistoryUntradedHandler(PKM pkm, out CheckResult result)
|
2018-06-24 05:00:01 +00:00
|
|
|
|
{
|
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
|
|
|
|
result = NONE;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.CurrentHandler != 0) // Badly edited; PKHeX doesn't trip this.
|
2018-09-01 21:11:12 +00:00
|
|
|
|
result = GetInvalid(LMemoryHTFlagInvalid);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
else if (pkm.HT_Friendship != 0)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
result = GetInvalid(LMemoryStatFriendshipHT0);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
else if (pkm.HT_Affection != 0)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
result = GetInvalid(LMemoryStatAffectionHT0);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2018-07-02 02:17:37 +00:00
|
|
|
|
private bool VerifyHistoryUntradedEvolution(PKM pkm, IReadOnlyList<EvoCriteria>[] chain, out CheckResult result)
|
2018-06-24 05:00:01 +00:00
|
|
|
|
{
|
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
|
|
|
|
result = NONE;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
// Handling Trainer string is empty implying it has not been traded.
|
|
|
|
|
// If it must be trade evolved, flag it.
|
|
|
|
|
|
|
|
|
|
if (pkm.Species == 350) // Milotic
|
|
|
|
|
{
|
|
|
|
|
if (Legal.IsTradeEvolved(chain, pkm.Format))
|
|
|
|
|
return false;
|
|
|
|
|
if (pkm is IContestStats s && s.CNT_Beauty < 170) // Beauty Contest Stat Requirement
|
2018-09-01 21:11:12 +00:00
|
|
|
|
result = GetInvalid(LEvoBeautyTradeLow);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
else if (pkm.CurrentLevel == 1)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
result = GetInvalid(LEvoBeautyUntrained);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (!Legal.IsTradeEvolved(chain, pkm.Format))
|
|
|
|
|
return false;
|
2018-09-01 21:11:12 +00:00
|
|
|
|
result = GetInvalid(LEvoTradeRequiredMemory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2018-06-24 05:00:01 +00:00
|
|
|
|
private CheckResult VerifyCommonMemory(PKM pkm, int handler)
|
|
|
|
|
{
|
2019-05-11 07:59:07 +00:00
|
|
|
|
var memory = MemoryVariableSet.Read(pkm, handler);
|
|
|
|
|
int matchingMoveMemory = Array.IndexOf(Memories.MoveSpecificMemories[0], memory.MemoryID);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (matchingMoveMemory != -1 && pkm.Species != 235 && !Legal.GetCanLearnMachineMove(pkm, Memories.MoveSpecificMemories[1][matchingMoveMemory], 6))
|
2019-05-11 07:59:07 +00:00
|
|
|
|
return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler));
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2019-05-11 07:59:07 +00:00
|
|
|
|
switch (memory.MemoryID)
|
2018-07-27 02:34:27 +00:00
|
|
|
|
{
|
2019-05-11 07:59:07 +00:00
|
|
|
|
case 6 when !Memories.LocationsWithPKCenter.Contains(memory.Variable):
|
|
|
|
|
return GetInvalid(string.Format(LMemoryArgBadPokecenter, memory.Handler));
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2018-07-27 02:34:27 +00:00
|
|
|
|
// {0} saw {2} carrying {1} on its back. {4} that {3}.
|
2019-11-16 01:34:18 +00:00
|
|
|
|
case 21 when !Legal.GetCanLearnMachineMove(new PK6 {Species = memory.Variable, EXP = Experience.GetEXP(100, PersonalTable.XY.GetFormeIndex(memory.Variable, 0))}, 19, 6):
|
2019-05-11 07:59:07 +00:00
|
|
|
|
return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler));
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2019-05-11 07:59:07 +00:00
|
|
|
|
case 16 when memory.Variable == 0 || !Legal.GetCanKnowMove(pkm, memory.Variable, 6):
|
|
|
|
|
case 48 when memory.Variable == 0 || !Legal.GetCanKnowMove(pkm, memory.Variable, 6):
|
|
|
|
|
return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler));
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2018-07-27 02:34:27 +00:00
|
|
|
|
// {0} was able to remember {2} at {1}'s instruction. {4} that {3}.
|
2019-11-26 19:01:09 +00:00
|
|
|
|
case 49 when memory.Variable == 0 && !GetIsMoveLearnable(pkm, handler, memory.Variable):
|
2019-05-11 07:59:07 +00:00
|
|
|
|
return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler));
|
2018-07-27 02:34:27 +00:00
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2019-05-11 07:59:07 +00:00
|
|
|
|
if (!Memories.CanHaveIntensity(memory.MemoryID, memory.Intensity))
|
2019-11-26 19:01:09 +00:00
|
|
|
|
{
|
|
|
|
|
if (pkm.GenNumber < 8) // todo: memory intensity checks for gen8
|
|
|
|
|
return GetInvalid(string.Format(LMemoryIndexIntensityMin, memory.Handler, Memories.GetMinimumIntensity(memory.MemoryID)));
|
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2019-05-11 07:59:07 +00:00
|
|
|
|
if (memory.MemoryID != 4 && !Memories.CanHaveFeeling(memory.MemoryID, memory.Feeling))
|
2019-11-26 19:01:09 +00:00
|
|
|
|
{
|
|
|
|
|
if (pkm.GenNumber < 8) // todo: memory feeling checks for gen8
|
|
|
|
|
return GetInvalid(string.Format(LMemoryFeelInvalid, memory.Handler));
|
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2019-05-11 07:59:07 +00:00
|
|
|
|
return GetValid(string.Format(LMemoryF_0_Valid, memory.Handler));
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-26 19:01:09 +00:00
|
|
|
|
private static bool GetIsMoveLearnable(PKM pkm, int handler, int move)
|
|
|
|
|
{
|
|
|
|
|
int gen = handler == 0 ? pkm.GenNumber : pkm.Format >= 8 ? 8 : 6;
|
|
|
|
|
return Legal.GetCanRelearnMove(pkm, move, gen);
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-24 05:00:01 +00:00
|
|
|
|
private void VerifyOTMemoryIs(LegalityAnalysis data, int m, int i, int t, int f)
|
|
|
|
|
{
|
|
|
|
|
var pkm = data.pkm;
|
|
|
|
|
if (pkm.OT_Memory != m)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryIndexID, L_XOT, m)));
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.OT_Intensity != i)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryIndexIntensity, L_XOT, i)));
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.OT_TextVar != t)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryIndexVar, L_XOT, t)));
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.OT_Feeling != f)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryIndexFeel, L_XOT, f)));
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2018-07-02 02:17:37 +00:00
|
|
|
|
private void VerifyOTMemory(LegalityAnalysis data)
|
2018-06-24 05:00:01 +00:00
|
|
|
|
{
|
|
|
|
|
var pkm = data.pkm;
|
|
|
|
|
var Info = data.Info;
|
|
|
|
|
if (Info.Generation < 6 || pkm.IsEgg)
|
|
|
|
|
{
|
|
|
|
|
VerifyOTMemoryIs(data, 0, 0, 0, 0); // empty
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (data.EncounterMatch)
|
|
|
|
|
{
|
2019-01-13 07:50:31 +00:00
|
|
|
|
case WC6 g when !g.IsEgg && g.OTGender != 3:
|
2018-06-24 05:00:01 +00:00
|
|
|
|
VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling);
|
|
|
|
|
return;
|
2019-01-13 07:50:31 +00:00
|
|
|
|
case WC7 g when !g.IsEgg && g.OTGender != 3:
|
2018-06-30 22:01:16 +00:00
|
|
|
|
VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
return;
|
2019-11-26 19:22:01 +00:00
|
|
|
|
case WC8 g when !g.IsEgg && g.OTGender != 3:
|
|
|
|
|
VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling);
|
|
|
|
|
return;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2019-11-26 19:22:01 +00:00
|
|
|
|
case IMemoryOT t when !(t is MysteryGift):
|
2019-11-26 19:01:09 +00:00
|
|
|
|
VerifyOTMemoryIs(data, t.OT_Memory, t.OT_Intensity, t.OT_TextVar, t.OT_Feeling);
|
|
|
|
|
return;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (pkm.OT_Memory)
|
|
|
|
|
{
|
|
|
|
|
case 2: // {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}.
|
|
|
|
|
if (pkm.Egg_Location == 0)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, string.Format(LMemoryArgBadHatch, L_XOT), CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2019-11-26 19:01:09 +00:00
|
|
|
|
case 4 when pkm.Gen6: // {0} became {1}’s friend when it arrived via Link Trade at... {2}. {4} that {3}.
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, string.Format(LMemoryArgBadOTEgg, L_XOT), CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
case 6: // {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}.
|
2018-10-13 00:45:52 +00:00
|
|
|
|
int matchingOriginGame = Array.IndexOf(Memories.LocationsWithPKCenter, pkm.OT_TextVar);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (matchingOriginGame != -1)
|
|
|
|
|
{
|
2018-10-13 00:45:52 +00:00
|
|
|
|
var gameID = Memories.GetGameVersionForPokeCenterIndex(matchingOriginGame);
|
|
|
|
|
if (!gameID.Contains((GameVersion)pkm.Version))
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, string.Format(LMemoryArgBadLocation, L_XOT), CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
data.AddLine(VerifyCommonMemory(pkm, 0));
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
case 14:
|
2018-10-05 01:52:00 +00:00
|
|
|
|
if (!GetCanBeCaptured(pkm.OT_TextVar, Info.Generation, (GameVersion)pkm.Version))
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, string.Format(LMemoryArgBadSpecies, L_XOT), CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
else
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Valid, string.Format(LMemoryArgSpecies, L_XOT), CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (pkm.XY && Memories.Memory_NotXY.Contains(pkm.OT_Memory))
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, string.Format(LMemoryArgBadID, L_XOT), CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.AO && Memories.Memory_NotAO.Contains(pkm.OT_Memory))
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, string.Format(LMemoryArgBadID, L_XOT), CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
|
|
|
|
data.AddLine(VerifyCommonMemory(pkm, 0));
|
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2018-07-02 02:17:37 +00:00
|
|
|
|
private void VerifyHTMemory(LegalityAnalysis data)
|
2018-06-24 05:00:01 +00:00
|
|
|
|
{
|
|
|
|
|
var pkm = data.pkm;
|
|
|
|
|
if (pkm.Format < 6)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var Info = data.Info;
|
2019-11-26 19:01:09 +00:00
|
|
|
|
if (pkm.Format == 7)
|
2018-06-24 05:00:01 +00:00
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Bank Transfer adds in the Link Trade Memory.
|
|
|
|
|
* Trading 7<->7 between games (not Bank) clears this data.
|
|
|
|
|
*/
|
|
|
|
|
if (pkm.HT_Memory == 0)
|
|
|
|
|
{
|
|
|
|
|
if (pkm.HT_TextVar != 0 || pkm.HT_Intensity != 0 || pkm.HT_Feeling != 0)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, LMemoryCleared, CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transfer 6->7 & withdraw to same HT => keeps past gen memory
|
|
|
|
|
// Don't require link trade memory for these past gen cases
|
|
|
|
|
int gen = Info.Generation;
|
|
|
|
|
if (3 <= gen && gen < 7 && pkm.CurrentHandler == 1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (pkm.HT_Memory != 4)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, LMemoryIndexLinkHT, CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.HT_TextVar != 0)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, LMemoryIndexArgHT, CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.HT_Intensity != 1)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, LMemoryIndexIntensityHT1, CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
if (pkm.HT_Feeling > 10)
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, LMemoryIndexFeelHT09, CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (pkm.HT_Memory)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
if (string.IsNullOrEmpty(pkm.HT_Name))
|
|
|
|
|
return;
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, LMemoryMissingHT, CheckIdentifier.Memory); return;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
case 1: // {0} met {1} at... {2}. {1} threw a Poké Ball at it, and they started to travel together. {4} that {3}.
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, string.Format(LMemoryArgBadCatch, L_XHT), CheckIdentifier.Memory); return;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
|
|
|
|
case 2: // {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}.
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, string.Format(LMemoryArgBadHatch, L_XHT), CheckIdentifier.Memory); return;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
|
|
|
|
case 14:
|
2018-10-05 01:52:00 +00:00
|
|
|
|
if (GetCanBeCaptured(pkm.HT_TextVar, 6))
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Valid, string.Format(LMemoryArgSpecies, L_XHT), CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
else
|
2018-09-01 21:11:12 +00:00
|
|
|
|
data.AddLine(Severity.Invalid, string.Format(LMemoryArgBadSpecies, L_XHT), CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
data.AddLine(VerifyCommonMemory(pkm, 1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ORAS contests mistakenly apply 20 affection to the OT instead of the current handler's value
|
|
|
|
|
private static bool IsInvalidContestAffection(PKM pkm) => pkm.OT_Affection != 255 && pkm.OT_Affection % 20 != 0;
|
2018-07-12 02:13:09 +00:00
|
|
|
|
|
|
|
|
|
private static bool GetIsUntradedByEncounterMemories(PKM pkm, IEncounterable EncounterMatch, int generation)
|
|
|
|
|
{
|
|
|
|
|
if (generation < 6)
|
|
|
|
|
return false;
|
|
|
|
|
|
2018-07-14 17:34:34 +00:00
|
|
|
|
bool untraded = pkm.HT_Name.Length == 0 || (pkm is IGeoTrack g && g.Geo1_Country == 0);
|
2019-01-13 07:50:31 +00:00
|
|
|
|
if (EncounterMatch is WC6 gift)
|
|
|
|
|
return gift.OTGender == 3 && untraded;
|
2018-07-12 02:13:09 +00:00
|
|
|
|
return untraded;
|
|
|
|
|
}
|
2018-10-05 01:52:00 +00:00
|
|
|
|
|
|
|
|
|
private static bool GetCanBeCaptured(int species, int gen, GameVersion version = GameVersion.Any)
|
|
|
|
|
{
|
|
|
|
|
switch (gen)
|
|
|
|
|
{
|
|
|
|
|
// Capture Memory only obtainable via Gen 6.
|
|
|
|
|
case 6:
|
|
|
|
|
switch (version)
|
|
|
|
|
{
|
|
|
|
|
case GameVersion.Any:
|
|
|
|
|
return Legal.FriendSafari.Contains(species)
|
|
|
|
|
|| GetCanBeCaptured(species, SlotsX, StaticX)
|
|
|
|
|
|| GetCanBeCaptured(species, SlotsY, StaticY)
|
|
|
|
|
|| GetCanBeCaptured(species, SlotsA, StaticA)
|
|
|
|
|
|| GetCanBeCaptured(species, SlotsO, StaticO);
|
|
|
|
|
case GameVersion.X:
|
|
|
|
|
return Legal.FriendSafari.Contains(species)
|
|
|
|
|
|| GetCanBeCaptured(species, SlotsX, StaticX);
|
|
|
|
|
case GameVersion.Y:
|
|
|
|
|
return Legal.FriendSafari.Contains(species)
|
|
|
|
|
|| GetCanBeCaptured(species, SlotsY, StaticY);
|
|
|
|
|
|
|
|
|
|
case GameVersion.AS:
|
|
|
|
|
return GetCanBeCaptured(species, SlotsA, StaticA);
|
|
|
|
|
case GameVersion.OR:
|
|
|
|
|
return GetCanBeCaptured(species, SlotsO, StaticO);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2019-11-26 19:01:09 +00:00
|
|
|
|
|
|
|
|
|
case 8:
|
|
|
|
|
{
|
|
|
|
|
switch (version)
|
|
|
|
|
{
|
|
|
|
|
case GameVersion.SW:
|
|
|
|
|
return GetCanBeCaptured(species, SlotsSW, StaticSW);
|
|
|
|
|
case GameVersion.SH:
|
|
|
|
|
return GetCanBeCaptured(species, SlotsSH, StaticSH);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2018-10-05 01:52:00 +00:00
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool GetCanBeCaptured(int species, IEnumerable<EncounterArea> area, IEnumerable<EncounterStatic> statics)
|
|
|
|
|
{
|
|
|
|
|
if (area.Any(loc => loc.Slots.Any(slot => slot.Species == species)))
|
|
|
|
|
return true;
|
|
|
|
|
if (statics.Any(enc => enc.Species == species && !enc.Gift))
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
}
|