2018-11-17 01:42:50 +00:00
using System ;
2020-05-25 03:00:32 +00:00
using System.Collections.Generic ;
2018-11-17 01:42:50 +00:00
using System.Linq ;
2018-06-24 05:00:01 +00:00
using static PKHeX . Core . LegalityCheckStrings ;
2019-05-11 17:12:14 +00:00
using static PKHeX . Core . CheckIdentifier ;
2018-06-24 05:00:01 +00:00
namespace PKHeX.Core
{
2018-07-02 02:17:37 +00:00
/// <summary>
/// Verifies miscellaneous data including <see cref="PKM.FatefulEncounter"/> and minor values.
/// </summary>
public sealed class MiscVerifier : Verifier
2018-06-24 05:00:01 +00:00
{
2019-05-11 17:12:14 +00:00
protected override CheckIdentifier Identifier = > Misc ;
2018-07-27 02:34:27 +00:00
2018-06-24 05:00:01 +00:00
public override void Verify ( LegalityAnalysis data )
{
var pkm = data . pkm ;
if ( pkm . IsEgg )
{
VerifyMiscEggCommon ( data ) ;
if ( pkm is IContestStats s & & s . HasContestStats ( ) )
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LEggContest , Egg ) ) ;
2018-07-27 02:34:27 +00:00
switch ( pkm )
2018-06-24 05:00:01 +00:00
{
2019-01-01 05:01:54 +00:00
case PK5 pk5 when pk5 . PokeStarFame ! = 0 & & pk5 . IsEgg :
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LEggShinyPokeStar , Egg ) ) ;
2019-01-01 05:01:54 +00:00
break ;
2018-07-27 02:34:27 +00:00
case PK4 pk4 when pk4 . ShinyLeaf ! = 0 :
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LEggShinyLeaf , Egg ) ) ;
2018-07-27 02:34:27 +00:00
break ;
case PK4 pk4 when pk4 . PokéathlonStat ! = 0 :
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LEggPokeathlon , Egg ) ) ;
2018-07-27 02:34:27 +00:00
break ;
2020-12-22 07:37:07 +00:00
case PK3 when pkm . Language ! = 1 : // All Eggs are Japanese and flagged specially for localized string
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( string . Format ( LOTLanguage , LanguageID . Japanese , ( LanguageID ) pkm . Language ) , Egg ) ) ;
2018-07-27 02:34:27 +00:00
break ;
2018-06-24 05:00:01 +00:00
}
2020-08-12 02:42:40 +00:00
2020-12-29 08:58:08 +00:00
if ( pkm is IHomeTrack { Tracker : not 0 } )
2020-08-12 02:42:40 +00:00
data . AddLine ( GetInvalid ( LTransferTrackerShouldBeZero ) ) ;
2018-06-24 05:00:01 +00:00
}
2019-12-26 22:28:01 +00:00
else
{
VerifyMiscMovePP ( data ) ;
}
2018-06-24 05:00:01 +00:00
2020-05-25 03:00:32 +00:00
switch ( pkm )
{
2021-11-20 02:23:49 +00:00
case PK7 { ResortEventStatus : > = ResortEventState . MAX } :
2020-05-25 03:00:32 +00:00
data . AddLine ( GetInvalid ( LTransferBad ) ) ;
break ;
case PB7 pb7 :
VerifyBelugaStats ( data , pb7 ) ;
break ;
case PK8 pk8 :
VerifySWSHStats ( data , pk8 ) ;
break ;
2021-11-20 02:23:49 +00:00
case PB8 pb8 :
VerifyBDSPStats ( data , pb8 ) ;
break ;
2020-05-25 03:00:32 +00:00
}
if ( pkm . Format > = 6 )
VerifyFullness ( data , pkm ) ;
2018-08-28 03:44:26 +00:00
2021-08-13 22:36:30 +00:00
var enc = data . EncounterMatch ;
if ( enc is WC8 { IsHOMEGift : true } w )
2021-02-04 06:57:59 +00:00
{
2021-02-04 14:12:47 +00:00
var date = new DateTime ( pkm . Met_Year + 2000 , pkm . Met_Month , pkm . Met_Day ) ;
2021-07-13 15:14:04 +00:00
if ( ! EncountersHOME . IsValidDateWC8 ( w . CardID , date ) )
2021-02-04 06:57:59 +00:00
data . AddLine ( GetInvalid ( LDateOutsideDistributionWindow ) ) ;
}
2021-08-13 22:36:30 +00:00
else if ( enc is IOverworldCorrelation8 z )
2021-02-14 23:14:45 +00:00
{
var match = z . IsOverworldCorrelationCorrect ( pkm ) ;
2021-02-15 00:00:43 +00:00
var req = z . GetRequirement ( pkm ) ;
if ( match )
{
var seed = Overworld8RNG . GetOriginalSeed ( pkm ) ;
data . Info . PIDIV = new PIDIV ( PIDType . Overworld8 , seed ) ;
}
bool valid = req switch
{
OverworldCorrelation8Requirement . MustHave = > match ,
OverworldCorrelation8Requirement . MustNotHave = > ! match ,
_ = > true ,
} ;
if ( ! valid )
data . AddLine ( GetInvalid ( LPIDTypeMismatch ) ) ;
2021-02-14 23:14:45 +00:00
}
2021-11-20 02:23:49 +00:00
else if ( enc is IStaticCorrelation8b s8b )
{
var match = s8b . IsStaticCorrelationCorrect ( pkm ) ;
var req = s8b . GetRequirement ( pkm ) ;
if ( match )
data . Info . PIDIV = new PIDIV ( PIDType . Roaming8b , pkm . EncryptionConstant ) ;
bool valid = req switch
{
StaticCorrelation8bRequirement . MustHave = > match ,
StaticCorrelation8bRequirement . MustNotHave = > ! match ,
_ = > true ,
} ;
if ( ! valid )
data . AddLine ( GetInvalid ( LPIDTypeMismatch ) ) ;
}
2021-02-04 06:57:59 +00:00
2018-06-24 05:00:01 +00:00
VerifyMiscFatefulEncounter ( data ) ;
}
public void VerifyMiscG1 ( LegalityAnalysis data )
{
var pkm = data . pkm ;
if ( pkm . IsEgg )
VerifyMiscEggCommon ( data ) ;
2020-12-22 01:12:39 +00:00
if ( pkm is not PK1 pk1 )
2018-06-24 05:00:01 +00:00
return ;
VerifyMiscG1Types ( data , pk1 ) ;
VerifyMiscG1CatchRate ( data , pk1 ) ;
}
2018-07-27 02:34:27 +00:00
2018-06-24 05:00:01 +00:00
private void VerifyMiscG1Types ( LegalityAnalysis data , PK1 pk1 )
{
var Type_A = pk1 . Type_A ;
var Type_B = pk1 . Type_B ;
2020-09-13 21:40:10 +00:00
var species = pk1 . Species ;
if ( species = = ( int ) Species . Porygon )
2018-06-24 05:00:01 +00:00
{
2018-07-01 17:49:11 +00:00
// Can have any type combination of any species by using Conversion.
2019-05-11 17:12:14 +00:00
if ( ! GBRestrictions . TypeIDExists ( Type_A ) )
2018-07-27 02:34:27 +00:00
{
2018-09-01 21:11:12 +00:00
data . AddLine ( GetInvalid ( LG1TypePorygonFail1 ) ) ;
2018-07-27 02:34:27 +00:00
}
2019-05-11 17:12:14 +00:00
if ( ! GBRestrictions . TypeIDExists ( Type_B ) )
2018-07-27 02:34:27 +00:00
{
2018-09-01 21:11:12 +00:00
data . AddLine ( GetInvalid ( LG1TypePorygonFail2 ) ) ;
2018-07-27 02:34:27 +00:00
}
2020-09-13 21:40:10 +00:00
else // Both types exist, ensure a Gen1 species has this combination
2018-06-24 05:00:01 +00:00
{
var TypesAB_Match = PersonalTable . RB . IsValidTypeCombination ( Type_A , Type_B ) ;
2018-09-01 21:11:12 +00:00
var result = TypesAB_Match ? GetValid ( LG1TypeMatchPorygon ) : GetInvalid ( LG1TypePorygonFail ) ;
2018-07-01 17:49:11 +00:00
data . AddLine ( result ) ;
2018-06-24 05:00:01 +00:00
}
}
else // Types must match species types
{
2020-09-13 21:40:10 +00:00
var pi = PersonalTable . RB [ species ] ;
var Type_A_Match = Type_A = = pi . Type1 ;
var Type_B_Match = Type_B = = pi . Type2 ;
2018-06-24 05:00:01 +00:00
2018-09-01 21:11:12 +00:00
var first = Type_A_Match ? GetValid ( LG1TypeMatch1 ) : GetInvalid ( LG1Type1Fail ) ;
2020-12-25 20:30:26 +00:00
var second = Type_B_Match | | ( ParseSettings . AllowGBCartEra & & ( ( species is ( int ) Species . Magnemite or ( int ) Species . Magneton ) & & Type_B = = 9 ) ) // Steel Magnemite via Stadium2
2020-09-07 01:36:37 +00:00
? GetValid ( LG1TypeMatch2 ) : GetInvalid ( LG1Type2Fail ) ;
2018-06-24 05:00:01 +00:00
data . AddLine ( first ) ;
data . AddLine ( second ) ;
}
}
2018-07-27 02:34:27 +00:00
2018-06-24 05:00:01 +00:00
private void VerifyMiscG1CatchRate ( LegalityAnalysis data , PK1 pk1 )
{
var catch_rate = pk1 . Catch_Rate ;
2021-08-20 22:59:54 +00:00
var tradeback = GBRestrictions . IsTimeCapsuleTransferred ( pk1 , data . Info . Moves , data . EncounterMatch ) ;
var result = tradeback = = TimeCapsuleEvaluation . NotTransferred
? GetWasNotTradeback ( tradeback )
: GetWasTradeback ( tradeback ) ;
2018-08-28 03:44:26 +00:00
data . AddLine ( result ) ;
2021-08-20 22:59:54 +00:00
CheckResult GetWasTradeback ( TimeCapsuleEvaluation timeCapsuleEvalution )
2018-06-24 05:00:01 +00:00
{
2018-08-28 03:44:26 +00:00
if ( catch_rate = = 0 | | Legal . HeldItems_GSC . Contains ( ( ushort ) catch_rate ) )
2018-09-01 21:11:12 +00:00
return GetValid ( LG1CatchRateMatchTradeback ) ;
2021-08-20 22:59:54 +00:00
if ( timeCapsuleEvalution = = TimeCapsuleEvaluation . BadCatchRate )
2018-09-01 21:11:12 +00:00
return GetInvalid ( LG1CatchRateItem ) ;
2018-08-28 03:44:26 +00:00
2021-08-20 22:59:54 +00:00
return GetWasNotTradeback ( timeCapsuleEvalution ) ;
2018-08-28 03:44:26 +00:00
}
2018-07-27 02:34:27 +00:00
2021-08-20 22:59:54 +00:00
CheckResult GetWasNotTradeback ( TimeCapsuleEvaluation timeCapsuleEvalution )
2018-08-28 03:44:26 +00:00
{
2021-01-02 22:47:39 +00:00
var e = data . EncounterMatch ;
2021-06-10 05:54:21 +00:00
if ( e is EncounterStatic1E { Version : GameVersion . Stadium } or EncounterTrade1 )
2018-09-01 21:11:12 +00:00
return GetValid ( LG1CatchRateMatchPrevious ) ; // Encounters detected by the catch rate, cant be invalid if match this encounters
2020-09-13 21:40:10 +00:00
int species = pk1 . Species ;
2021-06-10 05:54:21 +00:00
if ( GBRestrictions . Species_NotAvailable_CatchRate . Contains ( species ) & & catch_rate = = PersonalTable . RB [ species ] . CatchRate )
{
if ( species ! = ( int ) Species . Dragonite | | catch_rate ! = 45 | | ! e . Version . Contains ( GameVersion . YW ) )
return GetInvalid ( LG1CatchRateEvo ) ;
}
if ( ! GBRestrictions . RateMatchesEncounter ( e . Species , e . Version , catch_rate ) )
2021-08-20 22:59:54 +00:00
return GetInvalid ( timeCapsuleEvalution = = TimeCapsuleEvaluation . Transferred12 ? LG1CatchRateChain : LG1CatchRateNone ) ;
2018-09-01 21:11:12 +00:00
return GetValid ( LG1CatchRateMatchPrevious ) ;
2018-06-24 05:00:01 +00:00
}
}
2018-07-27 02:34:27 +00:00
2018-07-01 17:49:11 +00:00
private static void VerifyMiscFatefulEncounter ( LegalityAnalysis data )
2018-06-24 05:00:01 +00:00
{
var pkm = data . pkm ;
2021-01-11 02:15:33 +00:00
var enc = data . EncounterMatch ;
switch ( enc )
2018-06-24 05:00:01 +00:00
{
2020-12-22 06:33:48 +00:00
case WC3 { Fateful : true } w :
2018-06-24 05:00:01 +00:00
if ( w . IsEgg )
{
// Eggs hatched in RS clear the obedience flag!
2020-03-20 00:05:16 +00:00
// Hatching in Gen3 doesn't change the origin version.
2018-06-24 05:00:01 +00:00
if ( pkm . Format ! = 3 )
return ; // possible hatched in either game, don't bother checking
2020-03-20 00:05:16 +00:00
if ( pkm . Met_Location < = 087 ) // hatched in RS or Emerald
return ; // possible hatched in either game, don't bother checking
2018-06-24 05:00:01 +00:00
// else, ensure fateful is active (via below)
}
VerifyFatefulIngameActive ( data ) ;
VerifyWC3Shiny ( data , w ) ;
return ;
case WC3 w :
if ( w . Version = = GameVersion . XD )
return ; // Can have either state
VerifyWC3Shiny ( data , w ) ;
break ;
2020-12-24 23:23:38 +00:00
case MysteryGift g : // WC3 handled above
2018-07-28 05:26:27 +00:00
VerifyReceivability ( data , g ) ;
2018-06-24 05:00:01 +00:00
VerifyFatefulMysteryGift ( data , g ) ;
return ;
2020-12-22 06:33:48 +00:00
case EncounterStatic { Fateful : true } : // ingame fateful
2020-12-22 07:37:07 +00:00
case EncounterSlot3PokeSpot : // ingame pokespot
2020-12-24 23:23:38 +00:00
case EncounterTrade4RanchSpecial : // ranch varied PID
2018-06-24 05:00:01 +00:00
VerifyFatefulIngameActive ( data ) ;
return ;
}
if ( pkm . FatefulEncounter )
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LFatefulInvalid , Fateful ) ) ;
2018-06-24 05:00:01 +00:00
}
2018-07-27 02:34:27 +00:00
2019-12-26 22:28:01 +00:00
private static void VerifyMiscMovePP ( LegalityAnalysis data )
{
var pkm = data . pkm ;
if ( pkm . Move1_PP > pkm . GetMovePP ( pkm . Move1 , pkm . Move1_PPUps ) )
2021-01-09 21:05:56 +00:00
data . AddLine ( GetInvalid ( string . Format ( LMovePPTooHigh_0 , 1 ) , CurrentMove ) ) ;
2019-12-26 22:28:01 +00:00
if ( pkm . Move2_PP > pkm . GetMovePP ( pkm . Move2 , pkm . Move2_PPUps ) )
2021-01-09 21:05:56 +00:00
data . AddLine ( GetInvalid ( string . Format ( LMovePPTooHigh_0 , 2 ) , CurrentMove ) ) ;
2019-12-26 22:28:01 +00:00
if ( pkm . Move3_PP > pkm . GetMovePP ( pkm . Move3 , pkm . Move3_PPUps ) )
2021-01-09 21:05:56 +00:00
data . AddLine ( GetInvalid ( string . Format ( LMovePPTooHigh_0 , 3 ) , CurrentMove ) ) ;
2019-12-26 22:28:01 +00:00
if ( pkm . Move4_PP > pkm . GetMovePP ( pkm . Move4 , pkm . Move4_PPUps ) )
2021-01-09 21:05:56 +00:00
data . AddLine ( GetInvalid ( string . Format ( LMovePPTooHigh_0 , 4 ) , CurrentMove ) ) ;
2019-12-26 22:28:01 +00:00
}
2018-07-01 17:49:11 +00:00
private static void VerifyMiscEggCommon ( LegalityAnalysis data )
2018-06-24 05:00:01 +00:00
{
var pkm = data . pkm ;
if ( pkm . Move1_PPUps > 0 | | pkm . Move2_PPUps > 0 | | pkm . Move3_PPUps > 0 | | pkm . Move4_PPUps > 0 )
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LEggPPUp , Egg ) ) ;
2018-07-27 02:34:27 +00:00
if ( pkm . Move1_PP ! = pkm . GetMovePP ( pkm . Move1 , 0 ) | | pkm . Move2_PP ! = pkm . GetMovePP ( pkm . Move2 , 0 ) | | pkm . Move3_PP ! = pkm . GetMovePP ( pkm . Move3 , 0 ) | | pkm . Move4_PP ! = pkm . GetMovePP ( pkm . Move4 , 0 ) )
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LEggPP , Egg ) ) ;
2018-06-24 05:00:01 +00:00
2021-01-11 02:15:33 +00:00
var enc = data . EncounterMatch ;
var HatchCycles = enc is EncounterStatic s ? s . EggCycles : 0 ;
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
if ( HatchCycles = = 0 ) // no value set
2018-06-24 05:00:01 +00:00
HatchCycles = pkm . PersonalInfo . HatchCycles ;
2020-05-24 17:46:08 +00:00
if ( pkm . OT_Friendship > HatchCycles )
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LEggHatchCycles , Egg ) ) ;
2018-06-24 05:00:01 +00:00
2021-08-13 22:36:30 +00:00
if ( pkm . Format > = 6 & & enc is EncounterEgg & & ! MovesMatchRelearn ( pkm ) )
2018-06-24 05:00:01 +00:00
{
2020-11-28 02:45:06 +00:00
var moves = string . Join ( ", " , ParseSettings . GetMoveNames ( pkm . Moves ) ) ;
2018-09-01 21:11:12 +00:00
var msg = string . Format ( LMoveFExpect_0 , moves ) ;
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( msg , Egg ) ) ;
2018-06-24 05:00:01 +00:00
}
2019-12-21 22:51:52 +00:00
2021-11-20 02:23:49 +00:00
if ( pkm is G8PKM pk8 )
2019-12-21 22:51:52 +00:00
{
if ( pk8 . HasAnyMoveRecordFlag ( ) )
data . AddLine ( GetInvalid ( LEggRelearnFlags , Egg ) ) ;
if ( pk8 . StatNature ! = pk8 . Nature )
data . AddLine ( GetInvalid ( LEggNature , Egg ) ) ;
}
2018-06-24 05:00:01 +00:00
}
2018-07-27 02:34:27 +00:00
2021-08-13 22:36:30 +00:00
private static bool MovesMatchRelearn ( PKM pkm )
{
if ( pkm . Move1 ! = pkm . RelearnMove1 )
return false ;
if ( pkm . Move2 ! = pkm . RelearnMove2 )
return false ;
if ( pkm . Move3 ! = pkm . RelearnMove3 )
return false ;
if ( pkm . Move4 ! = pkm . RelearnMove4 )
return false ;
return true ;
}
2018-07-01 17:49:11 +00:00
private static void VerifyFatefulMysteryGift ( LegalityAnalysis data , MysteryGift g )
2018-06-24 05:00:01 +00:00
{
var pkm = data . pkm ;
2020-12-22 06:33:48 +00:00
if ( g is PGF { IsShiny : true } )
2018-06-24 05:00:01 +00:00
{
var Info = data . Info ;
Info . PIDIV = MethodFinder . Analyze ( pkm ) ;
2019-05-11 03:46:49 +00:00
if ( Info . PIDIV . Type ! = PIDType . G5MGShiny & & pkm . Egg_Location ! = Locations . LinkTrade5 )
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LPIDTypeMismatch , PID ) ) ;
2018-06-24 05:00:01 +00:00
}
Track a PKM's Box,Slot,StorageFlags,Identifier metadata separately (#3222)
* Track a PKM's Box,Slot,StorageFlags,Identifier metadata separately
Don't store within the object, track the slot origin data separately.
Batch editing now pre-filters if using Box/Slot/Identifier logic; split up mods/filters as they're starting to get pretty hefty.
- Requesting a Box Data report now shows all slots in the save file (party, misc)
- Can now exclude backup saves from database search via toggle (separate from settings preventing load entirely)
- Replace some linq usages with direct code
* Remove WasLink virtual in PKM
Inline any logic, since we now have encounter objects to indicate matching, rather than the proto-legality logic checking properties of a PKM.
* Use Fateful to directly check gen5 mysterygift origins
No other encounter types in gen5 apply Fateful
* Simplify double ball comparison
Used to be separate for deferral cases, now no longer needed to be separate.
* Grab move/relearn reference and update locally
Fix relearn move identifier
* Inline defog HM transfer preference check
HasMove is faster than getting moves & checking contains. Skips allocation by setting values directly.
* Extract more met location metadata checks: WasBredEgg
* Replace Console.Write* with Debug.Write*
There's no console output UI, so don't include them in release builds.
* Inline WasGiftEgg, WasEvent, and WasEventEgg logic
Adios legality tags that aren't entirely correct for the specific format. Just put the computations in EncounterFinder.
2021-06-23 03:23:48 +00:00
bool shouldHave = GetFatefulState ( g ) ;
var result = pkm . FatefulEncounter = = shouldHave
2019-05-11 17:12:14 +00:00
? GetValid ( LFatefulMystery , Fateful )
: GetInvalid ( LFatefulMysteryMissing , Fateful ) ;
2018-07-01 17:49:11 +00:00
data . AddLine ( result ) ;
2018-06-24 05:00:01 +00:00
}
2018-07-27 02:34:27 +00:00
Track a PKM's Box,Slot,StorageFlags,Identifier metadata separately (#3222)
* Track a PKM's Box,Slot,StorageFlags,Identifier metadata separately
Don't store within the object, track the slot origin data separately.
Batch editing now pre-filters if using Box/Slot/Identifier logic; split up mods/filters as they're starting to get pretty hefty.
- Requesting a Box Data report now shows all slots in the save file (party, misc)
- Can now exclude backup saves from database search via toggle (separate from settings preventing load entirely)
- Replace some linq usages with direct code
* Remove WasLink virtual in PKM
Inline any logic, since we now have encounter objects to indicate matching, rather than the proto-legality logic checking properties of a PKM.
* Use Fateful to directly check gen5 mysterygift origins
No other encounter types in gen5 apply Fateful
* Simplify double ball comparison
Used to be separate for deferral cases, now no longer needed to be separate.
* Grab move/relearn reference and update locally
Fix relearn move identifier
* Inline defog HM transfer preference check
HasMove is faster than getting moves & checking contains. Skips allocation by setting values directly.
* Extract more met location metadata checks: WasBredEgg
* Replace Console.Write* with Debug.Write*
There's no console output UI, so don't include them in release builds.
* Inline WasGiftEgg, WasEvent, and WasEventEgg logic
Adios legality tags that aren't entirely correct for the specific format. Just put the computations in EncounterFinder.
2021-06-23 03:23:48 +00:00
private static bool GetFatefulState ( MysteryGift g )
{
if ( g is WC6 { IsLinkGift : true } )
return false ; // Pokémon Link fake-gifts do not have Fateful
return true ;
}
2018-07-28 05:26:27 +00:00
private static void VerifyReceivability ( LegalityAnalysis data , MysteryGift g )
{
var pkm = data . pkm ;
switch ( g )
{
2018-10-27 16:44:47 +00:00
case WC6 wc6 when ! wc6 . CanBeReceivedByVersion ( pkm . Version ) & & ! pkm . WasTradedEgg :
case WC7 wc7 when ! wc7 . CanBeReceivedByVersion ( pkm . Version ) & & ! pkm . WasTradedEgg :
2021-11-20 02:23:49 +00:00
case WC8 wc8 when ! wc8 . CanBeReceivedByVersion ( pkm . Version ) :
case WB8 wb8 when ! wb8 . CanBeReceivedByVersion ( pkm . Version ) :
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LEncGiftVersionNotDistributed , GameOrigin ) ) ;
2018-07-28 05:26:27 +00:00
return ;
2020-09-04 02:00:46 +00:00
case WC6 wc6 when wc6 . RestrictLanguage ! = 0 & & pkm . Language ! = wc6 . RestrictLanguage :
2019-09-19 02:58:23 +00:00
data . AddLine ( GetInvalid ( string . Format ( LOTLanguage , wc6 . RestrictLanguage , pkm . Language ) , CheckIdentifier . Language ) ) ;
2018-07-28 05:26:27 +00:00
return ;
2020-09-04 02:00:46 +00:00
case WC7 wc7 when wc7 . RestrictLanguage ! = 0 & & pkm . Language ! = wc7 . RestrictLanguage :
2019-09-19 02:58:23 +00:00
data . AddLine ( GetInvalid ( string . Format ( LOTLanguage , wc7 . RestrictLanguage , pkm . Language ) , CheckIdentifier . Language ) ) ;
2018-07-28 05:26:27 +00:00
return ;
}
}
2018-07-01 17:49:11 +00:00
private static void VerifyWC3Shiny ( LegalityAnalysis data , WC3 g3 )
2018-06-24 05:00:01 +00:00
{
// check for shiny locked gifts
if ( ! g3 . Shiny . IsValid ( data . pkm ) )
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LEncGiftShinyMismatch , Fateful ) ) ;
2018-06-24 05:00:01 +00:00
}
2018-07-27 02:34:27 +00:00
2018-07-01 17:49:11 +00:00
private static void VerifyFatefulIngameActive ( LegalityAnalysis data )
2018-06-24 05:00:01 +00:00
{
var pkm = data . pkm ;
2018-07-01 17:49:11 +00:00
var result = pkm . FatefulEncounter
2019-05-11 17:12:14 +00:00
? GetValid ( LFateful , Fateful )
: GetInvalid ( LFatefulMissing , Fateful ) ;
2018-07-01 17:49:11 +00:00
data . AddLine ( result ) ;
2018-06-24 05:00:01 +00:00
}
2020-03-20 22:18:59 +00:00
public void VerifyVersionEvolution ( LegalityAnalysis data )
2018-06-24 05:00:01 +00:00
{
var pkm = data . pkm ;
if ( pkm . Format < 7 | | data . EncounterMatch . Species = = pkm . Species )
return ;
// No point using the evolution tree. Just handle certain species.
switch ( pkm . Species )
{
2020-12-11 04:42:30 +00:00
case ( int ) Species . Lycanroc when pkm . Format = = 7 & & ( ( pkm . Form = = 0 & & Moon ( ) ) | | ( pkm . Form = = 1 & & Sun ( ) ) ) :
2019-11-16 01:34:18 +00:00
case ( int ) Species . Solgaleo when Moon ( ) :
case ( int ) Species . Lunala when Sun ( ) :
bool Sun ( ) = > ( pkm . Version & 1 ) = = 0 ;
bool Moon ( ) = > ( pkm . Version & 1 ) = = 1 ;
2018-06-24 05:00:01 +00:00
if ( pkm . IsUntraded )
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LEvoTradeRequired , Evolution ) ) ;
2018-06-24 05:00:01 +00:00
break ;
}
}
2018-11-17 01:42:50 +00:00
2020-05-25 03:00:32 +00:00
private static void VerifyFullness ( LegalityAnalysis data , PKM pkm )
{
2020-05-31 20:29:13 +00:00
if ( pkm . IsEgg )
2020-05-25 03:00:32 +00:00
{
if ( pkm . Fullness ! = 0 )
data . AddLine ( GetInvalid ( string . Format ( LMemoryStatFullness , 0 ) , Encounter ) ) ;
if ( pkm . Enjoyment ! = 0 )
data . AddLine ( GetInvalid ( string . Format ( LMemoryStatEnjoyment , 0 ) , Encounter ) ) ;
2020-05-31 20:29:13 +00:00
return ;
}
if ( pkm . Format > = 8 )
{
2021-05-24 05:06:40 +00:00
if ( pkm . Fullness > 245 ) // Exiting camp is -10
data . AddLine ( GetInvalid ( string . Format ( LMemoryStatFullness , "<=245" ) , Encounter ) ) ;
2020-05-31 20:29:13 +00:00
if ( pkm . Enjoyment ! = 0 )
data . AddLine ( GetInvalid ( string . Format ( LMemoryStatEnjoyment , 0 ) , Encounter ) ) ;
2020-05-25 03:00:32 +00:00
return ;
}
if ( pkm . Format ! = 6 | | ! pkm . IsUntraded | | pkm . XY )
return ;
// OR/AS PK6
if ( pkm . Fullness = = 0 )
return ;
if ( pkm . Species ! = data . EncounterMatch . Species )
return ; // evolved
if ( Unfeedable . Contains ( pkm . Species ) )
data . AddLine ( GetInvalid ( string . Format ( LMemoryStatFullness , 0 ) , Encounter ) ) ;
}
2020-12-22 01:17:56 +00:00
private static readonly HashSet < int > Unfeedable = new ( )
2020-05-25 03:00:32 +00:00
{
( int ) Species . Metapod ,
( int ) Species . Kakuna ,
( int ) Species . Pineco ,
( int ) Species . Silcoon ,
( int ) Species . Cascoon ,
( int ) Species . Shedinja ,
( int ) Species . Spewpa ,
} ;
2018-11-17 01:42:50 +00:00
private static void VerifyBelugaStats ( LegalityAnalysis data , PB7 pb7 )
{
2019-01-23 05:08:48 +00:00
// ReSharper disable once CompareOfFloatsByEqualityOperator -- THESE MUST MATCH EXACTLY
if ( ! IsCloseEnough ( pb7 . HeightAbsolute , pb7 . CalcHeightAbsolute ) )
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LStatIncorrectHeight , Encounter ) ) ;
2019-01-23 05:08:48 +00:00
// ReSharper disable once CompareOfFloatsByEqualityOperator -- THESE MUST MATCH EXACTLY
if ( ! IsCloseEnough ( pb7 . WeightAbsolute , pb7 . CalcWeightAbsolute ) )
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LStatIncorrectWeight , Encounter ) ) ;
2021-08-13 22:36:30 +00:00
if ( pb7 . Stat_CP ! = pb7 . CalcCP & & ! IsStarterLGPE ( pb7 ) )
2019-05-11 17:12:14 +00:00
data . AddLine ( GetInvalid ( LStatIncorrectCP , Encounter ) ) ;
2018-12-06 02:38:05 +00:00
}
2019-01-23 05:08:48 +00:00
private static bool IsCloseEnough ( float a , float b )
{
var ia = BitConverter . ToInt32 ( BitConverter . GetBytes ( a ) , 0 ) ;
var ib = BitConverter . ToInt32 ( BitConverter . GetBytes ( b ) , 0 ) ;
2019-02-08 00:57:17 +00:00
return Math . Abs ( ia - ib ) < = 7 ;
2019-01-23 05:08:48 +00:00
}
2021-08-13 22:36:30 +00:00
private static bool IsStarterLGPE ( ISpeciesForm pk ) = > pk . Species switch
{
( int ) Species . Pikachu when pk . Form = = 8 = > true ,
( int ) Species . Eevee when pk . Form = = 1 = > true ,
_ = > false ,
} ;
2019-11-16 01:34:18 +00:00
private void VerifySWSHStats ( LegalityAnalysis data , PK8 pk8 )
{
if ( pk8 . Favorite )
data . AddLine ( GetInvalid ( LFavoriteMarkingUnavailable , Encounter ) ) ;
2021-05-23 18:22:09 +00:00
var social = pk8 . Sociability ;
if ( pk8 . IsEgg )
{
if ( social ! = 0 )
data . AddLine ( GetInvalid ( LMemorySocialZero , Encounter ) ) ;
}
else if ( social > byte . MaxValue )
{
data . AddLine ( GetInvalid ( string . Format ( LMemorySocialTooHigh_0 , byte . MaxValue ) , Encounter ) ) ;
}
2021-11-20 02:23:49 +00:00
VerifyStatNature ( data , pk8 ) ;
2020-06-19 23:51:15 +00:00
var bv = pk8 . BattleVersion ;
if ( bv ! = 0 )
{
2020-07-19 22:48:45 +00:00
if ( ( bv ! = ( int ) GameVersion . SW & & bv ! = ( int ) GameVersion . SH ) | | pk8 . SWSH )
2020-06-19 23:51:15 +00:00
data . AddLine ( GetInvalid ( LStatBattleVersionInvalid ) ) ;
}
2020-08-17 06:06:49 +00:00
var enc = data . EncounterMatch ;
2020-12-22 06:33:48 +00:00
bool originGMax = enc is IGigantamax { CanGigantamax : true } ;
2020-06-19 23:51:15 +00:00
if ( originGMax ! = pk8 . CanGigantamax )
{
2020-12-11 04:42:30 +00:00
bool ok = ! pk8 . IsEgg & & pk8 . CanToggleGigantamax ( pk8 . Species , pk8 . Form , enc . Species , enc . Form ) ;
2020-06-24 23:59:26 +00:00
var chk = ok ? GetValid ( LStatGigantamaxValid ) : GetInvalid ( LStatGigantamaxInvalid ) ;
data . AddLine ( chk ) ;
2020-06-19 23:51:15 +00:00
}
2019-11-16 01:34:18 +00:00
if ( pk8 . DynamaxLevel ! = 0 )
{
2020-05-16 22:15:41 +00:00
if ( ! pk8 . CanHaveDynamaxLevel ( pk8 ) | | pk8 . DynamaxLevel > 10 )
2019-11-16 01:34:18 +00:00
data . AddLine ( GetInvalid ( LStatDynamaxInvalid ) ) ;
}
PersonalInfo ? pi = null ;
2021-08-13 22:36:30 +00:00
for ( int i = 0 ; i < PersonalInfoSWSH . CountTR ; i + + )
2019-11-16 01:34:18 +00:00
{
if ( ! pk8 . GetMoveRecordFlag ( i ) )
continue ;
2020-10-25 02:10:59 +00:00
if ( ( pi ? ? = pk8 . PersonalInfo ) . TMHM [ i + 100 ] )
continue ;
// Calyrex-0 can have TR flags for Calyrex-1/2 after it has force unlearned them.
// Re-fusing can be reacquire the move via relearner, rather than needing another TR.
// Calyrex-0 cannot reacquire the move via relearner, even though the TR is checked off in the TR list.
if ( pk8 . Species = = ( int ) Species . Calyrex )
{
2020-12-11 04:42:30 +00:00
var form = pk8 . Form ;
2020-10-25 02:10:59 +00:00
// Check if another alt form can learn the TR
if ( ( form ! = 1 & & CanLearnTR ( ( int ) Species . Calyrex , 1 , i ) ) | | ( form ! = 2 & & CanLearnTR ( ( int ) Species . Calyrex , 2 , i ) ) )
continue ;
}
2021-08-13 22:36:30 +00:00
data . AddLine ( GetInvalid ( string . Format ( LMoveSourceTR , ParseSettings . MoveStrings [ Legal . TMHM_SWSH [ i + PersonalInfoSWSH . CountTM ] ] ) ) ) ;
2019-11-16 01:34:18 +00:00
}
2021-11-20 02:23:49 +00:00
// weight/height scalars can be legally 0 so don't bother checking
}
private void VerifyBDSPStats ( LegalityAnalysis data , PB8 pb8 )
{
if ( pb8 . Favorite )
data . AddLine ( GetInvalid ( LFavoriteMarkingUnavailable , Encounter ) ) ;
2021-11-20 17:04:09 +00:00
var affix = pb8 . AffixedRibbon ;
if ( affix ! = - 1 ) // None
data . AddLine ( GetInvalid ( string . Format ( LRibbonMarkingAffixedF_0 , affix ) ) ) ;
2021-11-20 02:23:49 +00:00
var social = pb8 . Sociability ;
if ( social ! = 0 )
data . AddLine ( GetInvalid ( LMemorySocialZero , Encounter ) ) ;
VerifyStatNature ( data , pb8 ) ;
var bv = pb8 . BattleVersion ;
if ( bv ! = 0 )
data . AddLine ( GetInvalid ( LStatBattleVersionInvalid ) ) ;
if ( pb8 . CanGigantamax )
GetInvalid ( LStatGigantamaxInvalid ) ;
if ( pb8 . DynamaxLevel ! = 0 )
data . AddLine ( GetInvalid ( LStatDynamaxInvalid ) ) ;
if ( pb8 . HasAnyMoveRecordFlag ( ) & & ! pb8 . IsEgg ) // already checked for eggs
data . AddLine ( GetInvalid ( LEggRelearnFlags ) ) ;
// weight/height scalars can be legally 0 so don't bother checking
}
private void VerifyStatNature ( LegalityAnalysis data , PKM pk )
{
var sn = pk . StatNature ;
if ( sn = = pk . Nature )
return ;
// Only allow Serious nature (12); disallow all other neutral natures.
if ( sn ! = 12 & & ( sn > 24 | | sn % 6 = = 0 ) )
data . AddLine ( GetInvalid ( LStatNatureInvalid ) ) ;
2019-11-16 01:34:18 +00:00
}
2020-10-25 02:10:59 +00:00
private static bool CanLearnTR ( int species , int form , int tr )
{
2020-12-11 04:42:30 +00:00
var pi = PersonalTable . SWSH . GetFormEntry ( species , form ) ;
2021-08-13 22:36:30 +00:00
return pi . TMHM [ tr + PersonalInfoSWSH . CountTM ] ;
2020-10-25 02:10:59 +00:00
}
2018-06-24 05:00:01 +00:00
}
}