2022-06-26 06:08:28 +00:00
using System ;
2020-05-25 03:00:32 +00:00
using System.Collections.Generic ;
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
2022-06-18 18:04:24 +00:00
namespace PKHeX.Core ;
/// <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
{
2022-06-18 18:04:24 +00:00
protected override CheckIdentifier Identifier = > Misc ;
2018-07-27 02:34:27 +00:00
2022-06-18 18:04:24 +00:00
public override void Verify ( LegalityAnalysis data )
{
var pk = data . Entity ;
if ( pk . IsEgg )
2018-06-24 05:00:01 +00:00
{
2022-06-18 18:04:24 +00:00
VerifyMiscEggCommon ( data ) ;
2020-08-12 02:42:40 +00:00
2022-11-25 01:42:17 +00:00
if ( pk is IContestStatsReadOnly s & & s . HasContestStats ( ) )
2022-06-18 18:04:24 +00:00
data . AddLine ( GetInvalid ( LEggContest , Egg ) ) ;
2018-06-24 05:00:01 +00:00
2022-06-18 18:04:24 +00:00
switch ( pk )
2020-05-25 03:00:32 +00:00
{
2022-06-18 18:04:24 +00:00
case PK5 pk5 when pk5 . PokeStarFame ! = 0 & & pk5 . IsEgg :
data . AddLine ( GetInvalid ( LEggShinyPokeStar , Egg ) ) ;
2020-05-25 03:00:32 +00:00
break ;
2022-06-18 18:04:24 +00:00
case PK4 pk4 when pk4 . ShinyLeaf ! = 0 :
data . AddLine ( GetInvalid ( LEggShinyLeaf , Egg ) ) ;
2020-05-25 03:00:32 +00:00
break ;
2022-06-18 18:04:24 +00:00
case PK4 pk4 when pk4 . PokeathlonStat ! = 0 :
data . AddLine ( GetInvalid ( LEggPokeathlon , Egg ) ) ;
2020-05-25 03:00:32 +00:00
break ;
2022-06-18 18:04:24 +00:00
case PK3 when pk . Language ! = 1 : // All Eggs are Japanese and flagged specially for localized string
data . AddLine ( GetInvalid ( string . Format ( LOTLanguage , LanguageID . Japanese , ( LanguageID ) pk . Language ) , Egg ) ) ;
2022-02-05 01:35:15 +00:00
break ;
2020-05-25 03:00:32 +00:00
}
2022-06-18 18:04:24 +00:00
if ( pk is IHomeTrack { Tracker : not 0 } )
data . AddLine ( GetInvalid ( LTransferTrackerShouldBeZero ) ) ;
}
else
{
VerifyMiscMovePP ( data ) ;
}
2018-08-28 03:44:26 +00:00
2022-06-18 18:04:24 +00:00
switch ( pk )
{
case PK7 { ResortEventStatus : > = ResortEventState . MAX } :
data . AddLine ( GetInvalid ( LTransferBad ) ) ;
break ;
case PB7 pb7 :
VerifyBelugaStats ( data , pb7 ) ;
break ;
case PK8 pk8 :
VerifySWSHStats ( data , pk8 ) ;
break ;
case PB8 pb8 :
VerifyBDSPStats ( data , pb8 ) ;
break ;
case PA8 pa8 :
VerifyPLAStats ( data , pa8 ) ;
break ;
2022-11-25 01:42:17 +00:00
case PK9 pk9 :
VerifySVStats ( data , pk9 ) ;
break ;
2022-06-18 18:04:24 +00:00
}
2022-05-31 04:43:52 +00:00
2022-06-18 18:04:24 +00:00
if ( pk . Format > = 6 )
VerifyFullness ( data , pk ) ;
2022-05-31 04:43:52 +00:00
2022-06-18 18:04:24 +00:00
var enc = data . EncounterMatch ;
if ( enc is IEncounterServerDate { IsDateRestricted : true } serverGift )
{
var date = new DateTime ( pk . Met_Year + 2000 , pk . Met_Month , pk . Met_Day ) ;
// HOME Gifts for Sinnoh/Hisui starters were forced JPN until May 20, 2022 (UTC).
if ( enc is WB8 { CardID : 9015 or 9016 or 9017 } or WA8 { CardID : 9018 or 9019 or 9020 } )
{
if ( date < new DateTime ( 2022 , 5 , 20 ) & & pk . Language ! = ( int ) LanguageID . Japanese )
2021-02-04 06:57:59 +00:00
data . AddLine ( GetInvalid ( LDateOutsideDistributionWindow ) ) ;
}
2021-02-15 00:00:43 +00:00
2022-06-18 18:04:24 +00:00
var result = serverGift . IsValidDate ( date ) ;
if ( result = = EncounterServerDateCheck . Invalid )
data . AddLine ( GetInvalid ( LDateOutsideDistributionWindow ) ) ;
}
else if ( enc is IOverworldCorrelation8 z )
{
var match = z . IsOverworldCorrelationCorrect ( pk ) ;
var req = z . GetRequirement ( pk ) ;
if ( match )
{
var seed = Overworld8RNG . GetOriginalSeed ( pk ) ;
data . Info . PIDIV = new PIDIV ( PIDType . Overworld8 , seed ) ;
2021-02-14 23:14:45 +00:00
}
2022-06-18 18:04:24 +00:00
bool valid = req switch
2021-11-20 02:23:49 +00:00
{
2022-06-18 18:04:24 +00:00
OverworldCorrelation8Requirement . MustHave = > match ,
OverworldCorrelation8Requirement . MustNotHave = > ! match ,
_ = > true ,
} ;
2021-11-20 02:23:49 +00:00
2022-06-18 18:04:24 +00:00
if ( ! valid )
data . AddLine ( GetInvalid ( LPIDTypeMismatch ) ) ;
}
else if ( enc is IStaticCorrelation8b s8b )
{
var match = s8b . IsStaticCorrelationCorrect ( pk ) ;
var req = s8b . GetRequirement ( pk ) ;
if ( match )
data . Info . PIDIV = new PIDIV ( PIDType . Roaming8b , pk . EncryptionConstant ) ;
2021-11-20 02:23:49 +00:00
2022-06-18 18:04:24 +00:00
bool valid = req switch
2022-05-06 22:43:23 +00:00
{
2022-06-18 18:04:24 +00:00
StaticCorrelation8bRequirement . MustHave = > match ,
StaticCorrelation8bRequirement . MustNotHave = > ! match ,
_ = > true ,
} ;
2021-02-04 06:57:59 +00:00
2022-06-18 18:04:24 +00:00
if ( ! valid )
data . AddLine ( GetInvalid ( LPIDTypeMismatch ) ) ;
2022-02-05 01:35:15 +00:00
}
2022-06-18 18:04:24 +00:00
else if ( enc is IMasteryInitialMoveShop8 m )
2022-02-05 01:35:15 +00:00
{
2022-06-18 18:04:24 +00:00
if ( ! m . IsForcedMasteryCorrect ( pk ) )
data . AddLine ( GetInvalid ( LEncMasteryInitial ) ) ;
}
2022-02-05 01:35:15 +00:00
2022-06-18 18:04:24 +00:00
VerifyMiscFatefulEncounter ( data ) ;
VerifyMiscPokerus ( data ) ;
}
2022-02-05 01:35:15 +00:00
2022-11-25 01:42:17 +00:00
private void VerifySVStats ( LegalityAnalysis data , PK9 pk9 )
{
VerifyStatNature ( data , pk9 ) ;
if ( ! pk9 . IsBattleVersionValid ( data . Info . EvoChainsAllGens ) )
data . AddLine ( GetInvalid ( LStatBattleVersionInvalid ) ) ;
var enc = data . EncounterOriginal ;
if ( CheckHeightWeightOdds ( enc ) & & pk9 . HeightScalar = = 0 & & pk9 . WeightScalar = = 0 & & ParseSettings . ZeroHeightWeight ! = Severity . Valid )
data . AddLine ( Get ( LStatInvalidHeightWeight , ParseSettings . ZeroHeightWeight , Encounter ) ) ;
if ( enc is EncounterEgg g & & UnreleasedSV . Contains ( g . Species | g . Form < < 11 ) )
data . AddLine ( GetInvalid ( LTransferBad ) ) ;
2022-11-25 22:12:19 +00:00
if ( ! IsObedienceLevelValid ( pk9 , pk9 . Obedience_Level , pk9 . Met_Level ) )
2022-11-25 01:42:17 +00:00
data . AddLine ( GetInvalid ( LTransferObedienceLevel ) ) ;
if ( pk9 . Tracker ! = 0 )
data . AddLine ( GetInvalid ( LTransferTrackerShouldBeZero ) ) ;
2022-11-25 23:55:10 +00:00
if ( enc is EncounterEgg or EncounterSlot9 & & ! Tera9RNG . IsMatchTeraTypePersonalEgg ( enc . Species , enc . Form , ( byte ) pk9 . TeraTypeOriginal ) )
2022-11-25 01:42:17 +00:00
data . AddLine ( GetInvalid ( LTeraTypeMismatch ) ) ;
2022-11-26 00:01:54 +00:00
if ( pk9 . IsEgg & & pk9 . TeraTypeOverride ! = ( MoveType ) TeraTypeUtil . OverrideNone )
data . AddLine ( GetInvalid ( LTeraTypeIncorrect ) ) ;
2022-11-25 01:42:17 +00:00
VerifyTechRecordSV ( data , pk9 ) ;
}
2022-11-25 23:55:10 +00:00
private static bool IsObedienceLevelValid ( PKM pk , byte current , int expectObey )
2022-11-25 01:42:17 +00:00
{
2022-11-25 23:55:10 +00:00
if ( current > pk . CurrentLevel )
return false ;
if ( ! pk . IsUntraded )
2022-11-25 01:42:17 +00:00
return current > = expectObey ;
return current = = expectObey ;
}
private static readonly HashSet < int > UnreleasedSV = new ( )
{
( int ) Species . Charmander , // Charmander : distribution raids happening on Dec 1, 2022
( int ) Species . Diglett | ( 1 < < 11 ) , // Diglett-1
( int ) Species . Meowth | ( 1 < < 11 ) , // Meowth-1
( int ) Species . Growlithe | ( 1 < < 11 ) , // Growlithe-1
( int ) Species . Slowpoke | ( 1 < < 11 ) , // Slowpoke-1
( int ) Species . Grimer | ( 1 < < 11 ) , // Grimer-1
( int ) Species . Voltorb | ( 1 < < 11 ) , // Voltorb-1
( int ) Species . Tauros , // Tauros-0
( int ) Species . Cyndaquil , // Cyndaquil
( int ) Species . Qwilfish | ( 1 < < 11 ) , // Qwilfish-1
( int ) Species . Sneasel | ( 1 < < 11 ) , // Sneasel-1
( int ) Species . Oshawott , // Oshawott
( int ) Species . Basculin | ( 2 < < 11 ) , // Basculin-2
( int ) Species . Zorua | ( 1 < < 11 ) , // Zorua-1
( int ) Species . Chespin , // Chespin
( int ) Species . Fennekin , // Fennekin
( int ) Species . Froakie , // Froakie
( int ) Species . Carbink , // Carbink
( int ) Species . Rowlet , // Rowlet
( int ) Species . Grookey , // Grookey
( int ) Species . Scorbunny , // Scorbunny
( int ) Species . Sobble , // Sobble
// Silly Workaround for evolution chain reversal not being iteratively implemented -- block Hisuians
( int ) Species . Sliggoo | ( 1 < < 11 ) ,
( int ) Species . Overqwil ,
( int ) Species . Wyrdeer ,
( int ) Species . Kleavor ,
( int ) Species . Ursaluna ,
( int ) Species . Decidueye | ( 1 < < 11 ) , // Rowlet
( int ) Species . Typhlosion | ( 1 < < 11 ) , // Cyndaquil
( int ) Species . Samurott | ( 1 < < 11 ) , // Oshawott
} ;
2022-06-18 18:04:24 +00:00
private void VerifyMiscPokerus ( LegalityAnalysis data )
{
var pk = data . Entity ;
if ( pk . Format = = 1 )
return ;
var strain = pk . PKRS_Strain ;
var days = pk . PKRS_Days ;
bool strainValid = Pokerus . IsStrainValid ( pk , strain , days ) ;
if ( ! strainValid )
data . AddLine ( GetInvalid ( string . Format ( LPokerusStrainUnobtainable_0 , strain ) ) ) ;
bool daysValid = Pokerus . IsDurationValid ( strain , days , out var max ) ;
if ( ! daysValid )
data . AddLine ( GetInvalid ( string . Format ( LPokerusDaysTooHigh_0 , max ) ) ) ;
}
2018-06-24 05:00:01 +00:00
2022-06-18 18:04:24 +00:00
public void VerifyMiscG1 ( LegalityAnalysis data )
{
var pk = data . Entity ;
if ( pk . IsEgg )
VerifyMiscEggCommon ( data ) ;
2018-06-24 05:00:01 +00:00
2022-06-18 18:04:24 +00:00
if ( pk is not PK1 pk1 )
2022-09-25 01:07:58 +00:00
{
if ( pk is ICaughtData2 { CaughtData : not 0 } t )
{
var time = t . Met_TimeOfDay ;
bool valid = data . EncounterOriginal is EncounterTrade2 ? time = = 0 : time is 1 or 2 or 3 ;
if ( ! valid )
data . AddLine ( new CheckResult ( Severity . Invalid , LMetDetailTimeOfDay , Encounter ) ) ;
}
2022-06-18 18:04:24 +00:00
return ;
2022-09-25 01:07:58 +00:00
}
2018-06-24 05:00:01 +00:00
2022-06-18 18:04:24 +00:00
VerifyMiscG1Types ( data , pk1 ) ;
VerifyMiscG1CatchRate ( data , pk1 ) ;
}
2018-07-27 02:34:27 +00:00
2022-06-18 18:04:24 +00:00
private void VerifyMiscG1Types ( LegalityAnalysis data , PK1 pk1 )
{
2022-11-25 01:42:17 +00:00
var Type_A = pk1 . Type1 ;
var Type_B = pk1 . Type2 ;
2022-06-18 18:04:24 +00:00
var species = pk1 . Species ;
if ( species = = ( int ) Species . Porygon )
2018-06-24 05:00:01 +00:00
{
2022-06-18 18:04:24 +00:00
// Can have any type combination of any species by using Conversion.
if ( ! GBRestrictions . TypeIDExists ( Type_A ) )
2018-06-24 05:00:01 +00:00
{
2022-06-18 18:04:24 +00:00
data . AddLine ( GetInvalid ( LG1TypePorygonFail1 ) ) ;
2018-06-24 05:00:01 +00:00
}
2022-06-18 18:04:24 +00:00
if ( ! GBRestrictions . TypeIDExists ( Type_B ) )
2018-06-24 05:00:01 +00:00
{
2022-06-18 18:04:24 +00:00
data . AddLine ( GetInvalid ( LG1TypePorygonFail2 ) ) ;
}
else // Both types exist, ensure a Gen1 species has this combination
{
var TypesAB_Match = PersonalTable . RB . IsValidTypeCombination ( Type_A , Type_B ) ;
var result = TypesAB_Match ? GetValid ( LG1TypeMatchPorygon ) : GetInvalid ( LG1TypePorygonFail ) ;
data . AddLine ( result ) ;
2018-06-24 05:00:01 +00:00
}
}
2022-06-18 18:04:24 +00:00
else // Types must match species types
2018-06-24 05:00:01 +00:00
{
2022-06-18 18:04:24 +00:00
var pi = PersonalTable . RB [ species ] ;
var Type_A_Match = Type_A = = pi . Type1 ;
var Type_B_Match = Type_B = = pi . Type2 ;
var first = Type_A_Match ? GetValid ( LG1TypeMatch1 ) : GetInvalid ( LG1Type1Fail ) ;
var second = Type_B_Match | | ( ParseSettings . AllowGBCartEra & & ( ( species is ( int ) Species . Magnemite or ( int ) Species . Magneton ) & & Type_B = = 9 ) ) // Steel Magnemite via Stadium2
? GetValid ( LG1TypeMatch2 ) : GetInvalid ( LG1Type2Fail ) ;
data . AddLine ( first ) ;
data . AddLine ( second ) ;
}
}
2018-08-28 03:44:26 +00:00
2022-06-18 18:04:24 +00:00
private void VerifyMiscG1CatchRate ( LegalityAnalysis data , PK1 pk1 )
{
var catch_rate = pk1 . Catch_Rate ;
var tradeback = GBRestrictions . IsTimeCapsuleTransferred ( pk1 , data . Info . Moves , data . EncounterMatch ) ;
var result = tradeback is TimeCapsuleEvaluation . NotTransferred or TimeCapsuleEvaluation . BadCatchRate
? GetWasNotTradeback ( tradeback )
: GetWasTradeback ( tradeback ) ;
data . AddLine ( result ) ;
CheckResult GetWasTradeback ( TimeCapsuleEvaluation timeCapsuleEvalution )
{
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
if ( PK1 . IsCatchRateHeldItem ( catch_rate ) )
2022-06-18 18:04:24 +00:00
return GetValid ( LG1CatchRateMatchTradeback ) ;
if ( timeCapsuleEvalution = = TimeCapsuleEvaluation . BadCatchRate )
return GetInvalid ( LG1CatchRateItem ) ;
2018-07-27 02:34:27 +00:00
2022-06-18 18:04:24 +00:00
return GetWasNotTradeback ( timeCapsuleEvalution ) ;
2018-06-24 05:00:01 +00:00
}
2018-07-27 02:34:27 +00:00
2022-06-18 18:04:24 +00:00
CheckResult GetWasNotTradeback ( TimeCapsuleEvaluation timeCapsuleEvalution )
2018-06-24 05:00:01 +00:00
{
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
if ( Array . Exists ( data . Info . Moves , z = > z . Generation = = 2 ) )
2022-06-18 18:04:24 +00:00
return GetInvalid ( LG1CatchRateItem ) ;
var e = data . EncounterMatch ;
if ( e is EncounterStatic1E { Version : GameVersion . Stadium } or EncounterTrade1 )
return GetValid ( LG1CatchRateMatchPrevious ) ; // Encounters detected by the catch rate, cant be invalid if match this encounters
2022-08-27 06:43:36 +00:00
ushort species = pk1 . Species ;
2022-08-26 17:07:24 +00:00
if ( GBRestrictions . Species_NotAvailable_CatchRate . Contains ( ( byte ) species ) & & catch_rate = = PersonalTable . RB [ species ] . CatchRate )
2018-06-24 05:00:01 +00:00
{
2022-06-18 18:04:24 +00:00
if ( species ! = ( int ) Species . Dragonite | | catch_rate ! = 45 | | ! e . Version . Contains ( GameVersion . YW ) )
return GetInvalid ( LG1CatchRateEvo ) ;
2018-06-24 05:00:01 +00:00
}
2022-06-18 18:04:24 +00:00
if ( ! GBRestrictions . RateMatchesEncounter ( e . Species , e . Version , catch_rate ) )
return GetInvalid ( timeCapsuleEvalution = = TimeCapsuleEvaluation . Transferred12 ? LG1CatchRateChain : LG1CatchRateNone ) ;
return GetValid ( LG1CatchRateMatchPrevious ) ;
2018-06-24 05:00:01 +00:00
}
2022-06-18 18:04:24 +00:00
}
2018-07-27 02:34:27 +00:00
2022-06-18 18:04:24 +00:00
private static void VerifyMiscFatefulEncounter ( LegalityAnalysis data )
{
var pk = data . Entity ;
var enc = data . EncounterMatch ;
switch ( enc )
2019-12-26 22:28:01 +00:00
{
2022-06-18 18:04:24 +00:00
case WC3 { Fateful : true } w :
if ( w . IsEgg )
{
// Eggs hatched in RS clear the obedience flag!
// Hatching in Gen3 doesn't change the origin version.
if ( pk . Format ! = 3 )
return ; // possible hatched in either game, don't bother checking
if ( pk . Met_Location < = 087 ) // hatched in RS or Emerald
return ; // possible hatched in either game, don't bother checking
// 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 ;
case MysteryGift g : // WC3 handled above
VerifyReceivability ( data , g ) ;
VerifyFatefulMysteryGift ( data , g ) ;
return ;
case EncounterStatic { Fateful : true } : // ingame fateful
case EncounterSlot3PokeSpot : // ingame pokespot
case EncounterTrade4RanchSpecial : // ranch varied PID
VerifyFatefulIngameActive ( data ) ;
return ;
2019-12-26 22:28:01 +00:00
}
2022-06-18 18:04:24 +00:00
if ( pk . FatefulEncounter )
data . AddLine ( GetInvalid ( LFatefulInvalid , Fateful ) ) ;
}
private static void VerifyMiscMovePP ( LegalityAnalysis data )
{
var pk = data . Entity ;
2019-12-26 22:28:01 +00:00
2022-06-18 18:04:24 +00:00
if ( ! Legal . IsPPUpAvailable ( pk ) ) // No PP Ups
2018-06-24 05:00:01 +00:00
{
2022-06-18 18:04:24 +00:00
if ( pk . Move1_PPUps is not 0 )
data . AddLine ( GetInvalid ( string . Format ( LMovePPUpsTooHigh_0 , 1 ) , CurrentMove ) ) ;
if ( pk . Move2_PPUps is not 0 )
data . AddLine ( GetInvalid ( string . Format ( LMovePPUpsTooHigh_0 , 2 ) , CurrentMove ) ) ;
if ( pk . Move3_PPUps is not 0 )
data . AddLine ( GetInvalid ( string . Format ( LMovePPUpsTooHigh_0 , 3 ) , CurrentMove ) ) ;
if ( pk . Move4_PPUps is not 0 )
data . AddLine ( GetInvalid ( string . Format ( LMovePPUpsTooHigh_0 , 4 ) , CurrentMove ) ) ;
}
2018-06-24 05:00:01 +00:00
2022-06-18 18:04:24 +00:00
if ( pk . Move1_PP > pk . GetMovePP ( pk . Move1 , pk . Move1_PPUps ) )
data . AddLine ( GetInvalid ( string . Format ( LMovePPTooHigh_0 , 1 ) , CurrentMove ) ) ;
if ( pk . Move2_PP > pk . GetMovePP ( pk . Move2 , pk . Move2_PPUps ) )
data . AddLine ( GetInvalid ( string . Format ( LMovePPTooHigh_0 , 2 ) , CurrentMove ) ) ;
if ( pk . Move3_PP > pk . GetMovePP ( pk . Move3 , pk . Move3_PPUps ) )
data . AddLine ( GetInvalid ( string . Format ( LMovePPTooHigh_0 , 3 ) , CurrentMove ) ) ;
if ( pk . Move4_PP > pk . GetMovePP ( pk . Move4 , pk . Move4_PPUps ) )
data . AddLine ( GetInvalid ( string . Format ( LMovePPTooHigh_0 , 4 ) , CurrentMove ) ) ;
}
2018-06-24 05:00:01 +00:00
2022-06-18 18:04:24 +00:00
private static void VerifyMiscEggCommon ( LegalityAnalysis data )
{
var pk = data . Entity ;
if ( pk . Move1_PPUps > 0 | | pk . Move2_PPUps > 0 | | pk . Move3_PPUps > 0 | | pk . Move4_PPUps > 0 )
data . AddLine ( GetInvalid ( LEggPPUp , Egg ) ) ;
2022-06-26 06:08:28 +00:00
if ( ! IsZeroMovePP ( pk ) )
2022-06-18 18:04:24 +00:00
data . AddLine ( GetInvalid ( LEggPP , Egg ) ) ;
2019-12-21 22:51:52 +00:00
2022-06-18 18:04:24 +00:00
var enc = data . EncounterMatch ;
if ( ! EggStateLegality . GetIsEggHatchCyclesValid ( pk , enc ) )
data . AddLine ( GetInvalid ( LEggHatchCycles , Egg ) ) ;
2018-07-27 02:34:27 +00:00
2022-06-18 18:04:24 +00:00
if ( pk . Format > = 6 & & enc is EncounterEgg & & ! MovesMatchRelearn ( pk ) )
2021-08-13 22:36:30 +00:00
{
2022-06-18 18:04:24 +00:00
var moves = string . Join ( ", " , ParseSettings . GetMoveNames ( pk . Moves ) ) ;
var msg = string . Format ( LMoveFExpect_0 , moves ) ;
data . AddLine ( GetInvalid ( msg , Egg ) ) ;
2021-08-13 22:36:30 +00:00
}
2022-11-25 01:42:17 +00:00
if ( pk is ITechRecord record )
2018-06-24 05:00:01 +00:00
{
2022-11-25 01:42:17 +00:00
if ( record . GetMoveRecordFlagAny ( ) )
2022-06-18 18:04:24 +00:00
data . AddLine ( GetInvalid ( LEggRelearnFlags , Egg ) ) ;
if ( pk . StatNature ! = pk . Nature )
data . AddLine ( GetInvalid ( LEggNature , Egg ) ) ;
2018-06-24 05:00:01 +00:00
}
2022-06-18 18:04:24 +00:00
}
2018-07-27 02:34:27 +00:00
2022-06-26 06:08:28 +00:00
private static bool IsZeroMovePP ( PKM pk )
{
if ( pk . Move1_PP ! = pk . GetMovePP ( pk . Move1 , 0 ) )
return false ;
if ( pk . Move2_PP ! = pk . GetMovePP ( pk . Move2 , 0 ) )
return false ;
if ( pk . Move3_PP ! = pk . GetMovePP ( pk . Move3 , 0 ) )
return false ;
if ( pk . Move4_PP ! = pk . GetMovePP ( pk . Move4 , 0 ) )
return false ;
return true ;
}
2022-06-18 18:04:24 +00:00
private static bool MovesMatchRelearn ( PKM pk )
{
if ( pk . Move1 ! = pk . RelearnMove1 )
return false ;
if ( pk . Move2 ! = pk . RelearnMove2 )
return false ;
if ( pk . Move3 ! = pk . RelearnMove3 )
return false ;
if ( pk . Move4 ! = pk . RelearnMove4 )
return false ;
return true ;
}
private static void VerifyFatefulMysteryGift ( LegalityAnalysis data , MysteryGift g )
{
var pk = data . Entity ;
if ( g is PGF { IsShiny : true } )
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
{
2022-06-18 18:04:24 +00:00
var Info = data . Info ;
Info . PIDIV = MethodFinder . Analyze ( pk ) ;
if ( Info . PIDIV . Type ! = PIDType . G5MGShiny & & pk . Egg_Location ! = Locations . LinkTrade5 )
data . AddLine ( GetInvalid ( LPIDTypeMismatch , PID ) ) ;
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
}
2022-06-18 18:04:24 +00:00
bool shouldHave = GetFatefulState ( g ) ;
var result = pk . FatefulEncounter = = shouldHave
? GetValid ( LFatefulMystery , Fateful )
: GetInvalid ( LFatefulMysteryMissing , Fateful ) ;
data . AddLine ( result ) ;
}
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 ;
}
private static void VerifyReceivability ( LegalityAnalysis data , MysteryGift g )
{
var pk = data . Entity ;
switch ( g )
2018-07-28 05:26:27 +00:00
{
2022-06-18 18:04:24 +00:00
case WC6 wc6 when ! wc6 . CanBeReceivedByVersion ( pk . Version ) & & ! pk . WasTradedEgg :
case WC7 wc7 when ! wc7 . CanBeReceivedByVersion ( pk . Version ) & & ! pk . WasTradedEgg :
case WC8 wc8 when ! wc8 . CanBeReceivedByVersion ( pk . Version ) :
case WB8 wb8 when ! wb8 . CanBeReceivedByVersion ( pk . Version , pk ) :
case WA8 wa8 when ! wa8 . CanBeReceivedByVersion ( pk . Version , pk ) :
data . AddLine ( GetInvalid ( LEncGiftVersionNotDistributed , GameOrigin ) ) ;
return ;
case WC6 wc6 when wc6 . RestrictLanguage ! = 0 & & pk . Language ! = wc6 . RestrictLanguage :
data . AddLine ( GetInvalid ( string . Format ( LOTLanguage , wc6 . RestrictLanguage , pk . Language ) , CheckIdentifier . Language ) ) ;
return ;
case WC7 wc7 when wc7 . RestrictLanguage ! = 0 & & pk . Language ! = wc7 . RestrictLanguage :
data . AddLine ( GetInvalid ( string . Format ( LOTLanguage , wc7 . RestrictLanguage , pk . Language ) , CheckIdentifier . Language ) ) ;
return ;
2018-07-28 05:26:27 +00:00
}
2022-06-18 18:04:24 +00:00
}
private static void VerifyWC3Shiny ( LegalityAnalysis data , WC3 g3 )
{
// check for shiny locked gifts
if ( ! g3 . Shiny . IsValid ( data . Entity ) )
data . AddLine ( GetInvalid ( LEncGiftShinyMismatch , Fateful ) ) ;
}
2018-07-28 05:26:27 +00:00
2022-06-18 18:04:24 +00:00
private static void VerifyFatefulIngameActive ( LegalityAnalysis data )
{
var pk = data . Entity ;
var result = pk . FatefulEncounter
? GetValid ( LFateful , Fateful )
: GetInvalid ( LFatefulMissing , Fateful ) ;
data . AddLine ( result ) ;
}
public void VerifyVersionEvolution ( LegalityAnalysis data )
{
var pk = data . Entity ;
if ( pk . Format < 7 | | data . EncounterMatch . Species = = pk . Species )
return ;
// No point using the evolution tree. Just handle certain species.
switch ( pk . Species )
2018-06-24 05:00:01 +00:00
{
2022-06-18 18:04:24 +00:00
case ( int ) Species . Lycanroc when pk . Format = = 7 & & ( ( pk . Form = = 0 & & Moon ( ) ) | | ( pk . Form = = 1 & & Sun ( ) ) ) :
case ( int ) Species . Solgaleo when Moon ( ) :
case ( int ) Species . Lunala when Sun ( ) :
bool Sun ( ) = > ( pk . Version & 1 ) = = 0 ;
bool Moon ( ) = > ( pk . Version & 1 ) = = 1 ;
if ( pk . IsUntraded )
data . AddLine ( GetInvalid ( LEvoTradeRequired , Evolution ) ) ;
break ;
2018-06-24 05:00:01 +00:00
}
2022-06-18 18:04:24 +00:00
}
2018-07-27 02:34:27 +00:00
2022-06-18 18:04:24 +00:00
private static void VerifyFullness ( LegalityAnalysis data , PKM pk )
{
if ( pk . IsEgg )
2018-06-24 05:00:01 +00:00
{
2022-06-18 18:04:24 +00:00
if ( pk . Fullness ! = 0 )
data . AddLine ( GetInvalid ( string . Format ( LMemoryStatFullness , "0" ) , Encounter ) ) ;
if ( pk . Enjoyment ! = 0 )
data . AddLine ( GetInvalid ( string . Format ( LMemoryStatEnjoyment , "0" ) , Encounter ) ) ;
return ;
2018-06-24 05:00:01 +00:00
}
2022-06-18 18:04:24 +00:00
if ( pk . Format > = 8 )
2018-06-24 05:00:01 +00:00
{
2022-06-18 18:04:24 +00:00
if ( pk . Fullness > 245 ) // Exiting camp is -10
data . AddLine ( GetInvalid ( string . Format ( LMemoryStatFullness , "<=245" ) , Encounter ) ) ;
else if ( pk . Fullness is not 0 & & pk is not PK8 )
data . AddLine ( GetInvalid ( string . Format ( LMemoryStatFullness , "0" ) , Encounter ) ) ;
2018-06-24 05:00:01 +00:00
2022-06-18 18:04:24 +00:00
if ( pk . Enjoyment ! = 0 )
data . AddLine ( GetInvalid ( string . Format ( LMemoryStatEnjoyment , "0" ) , Encounter ) ) ;
return ;
2018-06-24 05:00:01 +00:00
}
2018-11-17 01:42:50 +00:00
2022-06-18 18:04:24 +00:00
if ( pk . Format ! = 6 | | ! pk . IsUntraded | | pk . XY )
return ;
2020-05-31 20:29:13 +00:00
2022-06-18 18:04:24 +00:00
// OR/AS PK6
if ( pk . Fullness = = 0 )
return ;
if ( pk . Species ! = data . EncounterMatch . Species )
return ; // evolved
2022-05-31 04:43:52 +00:00
2022-06-18 18:04:24 +00:00
if ( Unfeedable . Contains ( pk . Species ) )
data . AddLine ( GetInvalid ( string . Format ( LMemoryStatFullness , "0" ) , Encounter ) ) ;
}
2020-05-25 03:00:32 +00:00
2022-08-27 06:43:36 +00:00
private static readonly HashSet < ushort > Unfeedable = new ( )
2022-06-18 18:04:24 +00:00
{
( int ) Species . Metapod ,
( int ) Species . Kakuna ,
( int ) Species . Pineco ,
( int ) Species . Silcoon ,
( int ) Species . Cascoon ,
( int ) Species . Shedinja ,
( int ) Species . Spewpa ,
} ;
private static void VerifyBelugaStats ( LegalityAnalysis data , PB7 pb7 )
{
VerifyAbsoluteSizes ( data , pb7 ) ;
if ( pb7 . Stat_CP ! = pb7 . CalcCP & & ! IsStarterLGPE ( pb7 ) )
data . AddLine ( GetInvalid ( LStatIncorrectCP , Encounter ) ) ;
}
2020-05-25 03:00:32 +00:00
2022-06-18 18:04:24 +00:00
private static void VerifyAbsoluteSizes ( LegalityAnalysis data , IScaledSizeValue obj )
{
// ReSharper disable once CompareOfFloatsByEqualityOperator -- THESE MUST MATCH EXACTLY
if ( obj . HeightAbsolute ! = obj . CalcHeightAbsolute )
data . AddLine ( GetInvalid ( LStatIncorrectHeight , Encounter ) ) ;
// ReSharper disable once CompareOfFloatsByEqualityOperator -- THESE MUST MATCH EXACTLY
if ( obj . WeightAbsolute ! = obj . CalcWeightAbsolute )
data . AddLine ( GetInvalid ( LStatIncorrectWeight , Encounter ) ) ;
}
2020-05-25 03:00:32 +00:00
2022-06-18 18:04:24 +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 ,
} ;
2020-05-25 03:00:32 +00:00
2022-06-18 18:04:24 +00:00
private void VerifySWSHStats ( LegalityAnalysis data , PK8 pk8 )
{
var social = pk8 . Sociability ;
if ( pk8 . IsEgg )
2020-05-25 03:00:32 +00:00
{
2022-06-18 18:04:24 +00:00
if ( social ! = 0 )
data . AddLine ( GetInvalid ( LMemorySocialZero , Encounter ) ) ;
}
else if ( social > byte . MaxValue )
2022-02-05 01:35:15 +00:00
{
2022-06-18 18:04:24 +00:00
data . AddLine ( GetInvalid ( string . Format ( LMemorySocialTooHigh_0 , byte . MaxValue ) , Encounter ) ) ;
2022-02-05 01:35:15 +00:00
}
2022-06-18 18:04:24 +00:00
VerifyStatNature ( data , pk8 ) ;
if ( ! pk8 . IsBattleVersionValid ( data . Info . EvoChainsAllGens ) )
data . AddLine ( GetInvalid ( LStatBattleVersionInvalid ) ) ;
var enc = data . EncounterMatch ;
2022-11-25 01:42:17 +00:00
bool originGMax = enc is IGigantamaxReadOnly { CanGigantamax : true } ;
2022-06-18 18:04:24 +00:00
if ( originGMax ! = pk8 . CanGigantamax )
2018-11-17 01:42:50 +00:00
{
2022-06-18 18:04:24 +00:00
bool ok = ! pk8 . IsEgg & & pk8 . CanToggleGigantamax ( pk8 . Species , pk8 . Form , enc . Species , enc . Form ) ;
var chk = ok ? GetValid ( LStatGigantamaxValid ) : GetInvalid ( LStatGigantamaxInvalid ) ;
data . AddLine ( chk ) ;
2018-12-06 02:38:05 +00:00
}
2022-06-18 18:04:24 +00:00
if ( pk8 . DynamaxLevel ! = 0 )
2021-08-13 22:36:30 +00:00
{
2022-06-18 18:04:24 +00:00
if ( ! pk8 . CanHaveDynamaxLevel ( pk8 ) | | pk8 . DynamaxLevel > 10 )
data . AddLine ( GetInvalid ( LStatDynamaxInvalid ) ) ;
}
2019-11-16 01:34:18 +00:00
2022-06-18 18:04:24 +00:00
if ( CheckHeightWeightOdds ( data . EncounterMatch ) & & pk8 . HeightScalar = = 0 & & pk8 . WeightScalar = = 0 & & ParseSettings . ZeroHeightWeight ! = Severity . Valid )
data . AddLine ( Get ( LStatInvalidHeightWeight , ParseSettings . ZeroHeightWeight , Encounter ) ) ;
2022-11-25 01:42:17 +00:00
VerifyTechRecordSWSH ( data , pk8 ) ;
2022-06-18 18:04:24 +00:00
}
private void VerifyPLAStats ( LegalityAnalysis data , PA8 pa8 )
{
VerifyAbsoluteSizes ( data , pa8 ) ;
2022-08-04 01:17:46 +00:00
if ( ! data . Info . EvoChainsAllGens . HasVisitedSWSH )
2022-02-05 01:35:15 +00:00
{
2022-06-18 18:04:24 +00:00
var affix = pa8 . AffixedRibbon ;
if ( affix ! = - 1 ) // None
data . AddLine ( GetInvalid ( string . Format ( LRibbonMarkingAffixedF_0 , affix ) ) ) ;
}
2022-02-05 01:35:15 +00:00
2022-06-18 18:04:24 +00:00
var social = pa8 . Sociability ;
if ( social ! = 0 )
data . AddLine ( GetInvalid ( LMemorySocialZero , Encounter ) ) ;
2022-02-05 01:35:15 +00:00
2022-06-18 18:04:24 +00:00
VerifyStatNature ( data , pa8 ) ;
2022-02-05 01:35:15 +00:00
2022-06-18 18:04:24 +00:00
if ( ! pa8 . IsBattleVersionValid ( data . Info . EvoChainsAllGens ) )
data . AddLine ( GetInvalid ( LStatBattleVersionInvalid ) ) ;
2022-02-05 01:35:15 +00:00
2022-06-18 18:04:24 +00:00
if ( pa8 . CanGigantamax )
data . AddLine ( GetInvalid ( LStatGigantamaxInvalid ) ) ;
2022-02-05 01:35:15 +00:00
2022-06-18 18:04:24 +00:00
if ( pa8 . DynamaxLevel ! = 0 )
data . AddLine ( GetInvalid ( LStatDynamaxInvalid ) ) ;
2022-02-05 01:35:15 +00:00
2022-06-18 18:04:24 +00:00
if ( pa8 . GetMoveRecordFlagAny ( ) & & ! pa8 . IsEgg ) // already checked for eggs
data . AddLine ( GetInvalid ( LEggRelearnFlags ) ) ;
2022-02-05 01:35:15 +00:00
2022-06-18 18:04:24 +00:00
if ( CheckHeightWeightOdds ( data . EncounterMatch ) & & pa8 . HeightScalar = = 0 & & pa8 . WeightScalar = = 0 & & ParseSettings . ZeroHeightWeight ! = Severity . Valid )
data . AddLine ( Get ( LStatInvalidHeightWeight , ParseSettings . ZeroHeightWeight , Encounter ) ) ;
2022-11-25 01:42:17 +00:00
VerifyTechRecordSWSH ( data , pa8 ) ;
2022-06-18 18:04:24 +00:00
}
2022-02-05 01:35:15 +00:00
2022-06-18 18:04:24 +00:00
private void VerifyBDSPStats ( LegalityAnalysis data , PB8 pb8 )
{
2022-08-04 01:17:46 +00:00
if ( ! data . Info . EvoChainsAllGens . HasVisitedSWSH )
2021-11-20 02:23:49 +00:00
{
2022-06-18 18:04:24 +00:00
var affix = pb8 . AffixedRibbon ;
if ( affix ! = - 1 ) // None
data . AddLine ( GetInvalid ( string . Format ( LRibbonMarkingAffixedF_0 , affix ) ) ) ;
}
2021-11-20 17:04:09 +00:00
2022-06-18 18:04:24 +00:00
var social = pb8 . Sociability ;
if ( social ! = 0 )
data . AddLine ( GetInvalid ( LMemorySocialZero , Encounter ) ) ;
2021-11-20 02:23:49 +00:00
2022-06-18 18:04:24 +00:00
if ( pb8 . IsDprIllegal )
data . AddLine ( GetInvalid ( LTransferFlagIllegal ) ) ;
if ( pb8 . Species is ( int ) Species . Spinda or ( int ) Species . Nincada & & ! pb8 . BDSP )
data . AddLine ( GetInvalid ( LTransferNotPossible ) ) ;
if ( pb8 . Species is ( int ) Species . Spinda & & pb8 . Tracker ! = 0 )
data . AddLine ( GetInvalid ( LTransferTrackerShouldBeZero ) ) ;
2022-02-22 04:13:27 +00:00
2022-06-18 18:04:24 +00:00
VerifyStatNature ( data , pb8 ) ;
2021-11-20 02:23:49 +00:00
2022-06-18 18:04:24 +00:00
if ( ! pb8 . IsBattleVersionValid ( data . Info . EvoChainsAllGens ) )
data . AddLine ( GetInvalid ( LStatBattleVersionInvalid ) ) ;
2021-11-20 02:23:49 +00:00
2022-06-18 18:04:24 +00:00
if ( pb8 . CanGigantamax )
data . AddLine ( GetInvalid ( LStatGigantamaxInvalid ) ) ;
2021-11-20 02:23:49 +00:00
2022-06-18 18:04:24 +00:00
if ( pb8 . DynamaxLevel ! = 0 )
data . AddLine ( GetInvalid ( LStatDynamaxInvalid ) ) ;
2021-11-20 02:23:49 +00:00
2022-06-18 18:04:24 +00:00
if ( pb8 . GetMoveRecordFlagAny ( ) & & ! pb8 . IsEgg ) // already checked for eggs
data . AddLine ( GetInvalid ( LEggRelearnFlags ) ) ;
2021-11-20 02:23:49 +00:00
2022-06-18 18:04:24 +00:00
if ( CheckHeightWeightOdds ( data . EncounterMatch ) & & pb8 . HeightScalar = = 0 & & pb8 . WeightScalar = = 0 & & ParseSettings . ZeroHeightWeight ! = Severity . Valid )
data . AddLine ( Get ( LStatInvalidHeightWeight , ParseSettings . ZeroHeightWeight , Encounter ) ) ;
2022-11-25 01:42:17 +00:00
VerifyTechRecordSWSH ( data , pb8 ) ;
2022-06-18 18:04:24 +00:00
}
2021-11-25 03:29:02 +00:00
2022-06-18 18:04:24 +00:00
private static bool CheckHeightWeightOdds ( IEncounterTemplate enc )
{
if ( enc . Generation < 8 )
return false ;
if ( enc is WC8 { IsHOMEGift : true } )
2021-11-25 07:03:30 +00:00
return false ;
2022-11-25 01:42:17 +00:00
return true ;
2022-06-18 18:04:24 +00:00
}
2021-11-20 02:23:49 +00:00
2022-06-18 18:04:24 +00:00
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 ) ) ;
}
2020-10-25 02:10:59 +00:00
2022-11-25 01:42:17 +00:00
private void VerifyTechRecordSWSH < T > ( LegalityAnalysis data , T pk ) where T : PKM , ITechRecord
{
static string GetMoveName ( int index ) = > ParseSettings . MoveStrings [ LearnSource8SWSH . TR_SWSH [ index ] ] ;
var evos = data . Info . EvoChainsAllGens . Gen8 ;
if ( evos . Length = = 0 )
{
for ( int i = 0 ; i < PersonalInfo8SWSH . CountTR ; i + + )
{
if ( ! pk . GetMoveRecordFlag ( i ) )
continue ;
data . AddLine ( GetInvalid ( string . Format ( LMoveSourceTR , GetMoveName ( i ) ) ) ) ;
}
}
else
{
static PersonalInfo8SWSH GetPersonal ( EvoCriteria evo ) = > PersonalTable . SWSH . GetFormEntry ( evo . Species , evo . Form ) ;
PersonalInfo8SWSH ? pi = null ;
for ( int i = 0 ; i < PersonalInfo8SWSH . CountTR ; i + + )
{
if ( ! pk . GetMoveRecordFlag ( i ) )
continue ;
2022-11-25 03:13:34 +00:00
if ( ( pi ? ? = GetPersonal ( evos [ 0 ] ) ) . TMHM [ i + PersonalInfo8SWSH . CountTM ] )
2022-11-25 01:42:17 +00:00
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 ( pk . Species = = ( int ) Species . Calyrex )
{
var form = pk . Form ;
// 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 ;
}
data . AddLine ( GetInvalid ( string . Format ( LMoveSourceTR , GetMoveName ( i ) ) ) ) ;
}
}
}
2022-08-27 19:53:30 +00:00
private static bool CanLearnTR ( ushort species , byte form , int tr )
2022-06-18 18:04:24 +00:00
{
var pi = PersonalTable . SWSH . GetFormEntry ( species , form ) ;
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
return pi . TMHM [ tr + PersonalInfo8SWSH . CountTM ] ;
2018-06-24 05:00:01 +00:00
}
2022-11-25 01:42:17 +00:00
private void VerifyTechRecordSV ( LegalityAnalysis data , PK9 pk )
{
static string GetMoveName ( int index ) = > ParseSettings . MoveStrings [ LearnSource9SV . TM_SV [ index ] ] ;
var evos = data . Info . EvoChainsAllGens . Gen9 ;
if ( evos . Length = = 0 )
{
for ( int i = 0 ; i < PersonalInfo9SV . CountTM ; i + + )
{
if ( ! pk . GetMoveRecordFlag ( i ) )
continue ;
data . AddLine ( GetInvalid ( string . Format ( LMoveSourceTR , GetMoveName ( i ) ) ) ) ;
}
}
else
{
static PersonalInfo9SV GetPersonal ( EvoCriteria evo ) = > PersonalTable . SV . GetFormEntry ( evo . Species , evo . Form ) ;
PersonalInfo9SV ? pi = null ;
for ( int i = 0 ; i < PersonalInfo9SV . CountTM ; i + + )
{
if ( ! pk . GetMoveRecordFlag ( i ) )
continue ;
if ( ( pi ? ? = GetPersonal ( evos [ 0 ] ) ) . TMHM [ i ] )
continue ;
data . AddLine ( GetInvalid ( string . Format ( LMoveSourceTR , GetMoveName ( i ) ) ) ) ;
}
}
}
2018-06-24 05:00:01 +00:00
}