2018-05-18 05:43:07 +00:00
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
2021-01-17 08:05:07 +00:00
using System.Diagnostics.CodeAnalysis ;
2018-05-18 05:43:07 +00:00
using System.Globalization ;
using System.Linq ;
using System.Reflection ;
using static PKHeX . Core . MessageStrings ;
namespace PKHeX.Core
{
2019-07-14 22:06:45 +00:00
/// <summary>
/// Logic for editing many <see cref="PKM"/> with user provided <see cref="StringInstruction"/> list.
/// </summary>
2018-05-18 05:43:07 +00:00
public static class BatchEditing
{
public static readonly Type [ ] Types =
{
2019-09-23 23:56:47 +00:00
typeof ( PK8 ) ,
2018-11-14 03:10:43 +00:00
typeof ( PB7 ) ,
2018-05-18 05:43:07 +00:00
typeof ( PK7 ) , typeof ( PK6 ) , typeof ( PK5 ) , typeof ( PK4 ) , typeof ( BK4 ) ,
typeof ( PK3 ) , typeof ( XK3 ) , typeof ( CK3 ) ,
2020-10-09 21:44:12 +00:00
typeof ( PK2 ) , typeof ( SK2 ) , typeof ( PK1 ) ,
2018-05-18 05:43:07 +00:00
} ;
2018-05-19 02:19:15 +00:00
2019-12-09 01:52:17 +00:00
public static readonly string [ ] CustomProperties = { PROP_LEGAL , PROP_RIBBONS } ;
2018-05-18 05:43:07 +00:00
public static readonly string [ ] [ ] Properties = GetPropArray ( ) ;
2018-06-14 01:52:09 +00:00
private static readonly Dictionary < string , PropertyInfo > [ ] Props = Types . Select ( z = > ReflectUtil . GetAllPropertyInfoPublic ( z )
2018-05-18 05:43:07 +00:00
. GroupBy ( p = > p . Name ) . Select ( g = > g . First ( ) ) . ToDictionary ( p = > p . Name ) )
. ToArray ( ) ;
private const string CONST_RAND = "$rand" ;
private const string CONST_SHINY = "$shiny" ;
private const string CONST_SUGGEST = "$suggest" ;
private const string CONST_BYTES = "$[]" ;
private const string PROP_LEGAL = "Legal" ;
2019-12-09 01:52:17 +00:00
private const string PROP_RIBBONS = "Ribbons" ;
2018-05-19 02:19:15 +00:00
private const string IdentifierContains = nameof ( PKM . Identifier ) + "Contains" ;
2018-05-18 05:43:07 +00:00
private static string [ ] [ ] GetPropArray ( )
{
var p = new string [ Types . Length ] [ ] ;
for ( int i = 0 ; i < p . Length ; i + + )
{
2018-06-14 01:52:09 +00:00
var pz = ReflectUtil . GetPropertiesPublic ( Types [ i ] ) ;
2018-05-18 05:43:07 +00:00
p [ i ] = pz . Concat ( CustomProperties ) . OrderBy ( a = > a ) . ToArray ( ) ;
}
// Properties for any PKM
2018-06-14 01:52:09 +00:00
var any = ReflectUtil . GetPropertiesPublic ( typeof ( PK1 ) ) . Union ( p . SelectMany ( a = > a ) ) . OrderBy ( a = > a ) . ToArray ( ) ;
2018-05-18 05:43:07 +00:00
// Properties shared by all PKM
var all = p . Aggregate ( new HashSet < string > ( p [ 0 ] ) , ( h , e ) = > { h . IntersectWith ( e ) ; return h ; } ) . OrderBy ( a = > a ) . ToArray ( ) ;
var p1 = new string [ Types . Length + 2 ] [ ] ;
Array . Copy ( p , 0 , p1 , 1 , p . Length ) ;
p1 [ 0 ] = any ;
p1 [ p1 . Length - 1 ] = all ;
return p1 ;
}
2018-05-19 02:19:15 +00:00
/// <summary>
/// Tries to fetch the <see cref="PKM"/> property from the cache of available properties.
/// </summary>
2019-02-21 06:23:54 +00:00
/// <param name="pk">Pokémon to check</param>
2018-05-19 02:19:15 +00:00
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
2021-01-17 08:05:07 +00:00
public static bool TryGetHasProperty ( PKM pk , string name , [ NotNullWhen ( true ) ] out PropertyInfo ? pi )
2018-05-18 05:43:07 +00:00
{
2021-01-17 08:05:07 +00:00
var type = pk . GetType ( ) ;
return TryGetHasProperty ( type , name , out pi ) ;
}
/// <summary>
/// Tries to fetch the <see cref="PKM"/> property from the cache of available properties.
/// </summary>
/// <param name="type">Type to check</param>
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
public static bool TryGetHasProperty ( Type type , string name , [ NotNullWhen ( true ) ] out PropertyInfo ? pi )
{
var index = Array . IndexOf ( Types , type ) ;
if ( index < 0 )
{
pi = null ;
return false ;
}
var props = Props [ index ] ;
2018-05-18 05:43:07 +00:00
return props . TryGetValue ( name , out pi ) ;
}
2021-01-17 08:05:07 +00:00
/// <summary>
/// Gets a list of <see cref="PKM"/> types that implement the requested <see cref="property"/>.
/// </summary>
public static IEnumerable < string > GetTypesImplementing ( string property )
{
for ( int i = 0 ; i < Types . Length ; i + + )
{
var type = Types [ i ] ;
var props = Props [ i ] ;
if ( ! props . TryGetValue ( property , out var pi ) )
continue ;
yield return $"{type.Name}: {pi.PropertyType.Name}" ;
}
}
2018-05-19 02:19:15 +00:00
/// <summary>
/// Gets the type of the <see cref="PKM"/> property using the saved cache of properties.
/// </summary>
/// <param name="propertyName">Property Name to fetch the type for</param>
/// <param name="typeIndex">Type index (within <see cref="Types"/>. Leave empty (0) for a nonspecific format.</param>
/// <returns>Short name of the property's type.</returns>
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
public static string? GetPropertyType ( string propertyName , int typeIndex = 0 )
2018-05-18 05:43:07 +00:00
{
if ( CustomProperties . Contains ( propertyName ) )
return "Custom" ;
if ( typeIndex = = 0 ) // Any
{
foreach ( var p in Props )
2018-07-29 20:27:48 +00:00
{
2018-05-18 05:43:07 +00:00
if ( p . TryGetValue ( propertyName , out var pi ) )
return pi . PropertyType . Name ;
2018-07-29 20:27:48 +00:00
}
2018-05-18 05:43:07 +00:00
return null ;
}
2019-09-14 18:48:07 +00:00
int index = typeIndex - 1 > = Props . Length ? 0 : typeIndex - 1 ; // All vs Specific
2018-05-19 02:19:15 +00:00
var pr = Props [ index ] ;
2018-05-18 05:43:07 +00:00
if ( ! pr . TryGetValue ( propertyName , out var info ) )
return null ;
return info . PropertyType . Name ;
}
2018-05-19 02:19:15 +00:00
/// <summary>
/// Initializes the <see cref="StringInstruction"/> list with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
/// </summary>
/// <param name="il">Instructions to initialize.</param>
2018-05-18 05:43:07 +00:00
public static void ScreenStrings ( IEnumerable < StringInstruction > il )
{
foreach ( var i in il . Where ( i = > ! i . PropertyValue . All ( char . IsDigit ) ) )
{
string pv = i . PropertyValue ;
if ( pv . StartsWith ( "$" ) & & ! pv . StartsWith ( CONST_BYTES ) & & pv . Contains ( ',' ) )
i . SetRandRange ( pv ) ;
SetInstructionScreenedValue ( i ) ;
}
}
2018-05-19 02:19:15 +00:00
/// <summary>
/// Initializes the <see cref="StringInstruction"/> with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
/// </summary>
/// <param name="i">Instruction to initialize.</param>
2018-05-18 05:43:07 +00:00
private static void SetInstructionScreenedValue ( StringInstruction i )
{
switch ( i . PropertyName )
{
case nameof ( PKM . Species ) : i . SetScreenedValue ( GameInfo . Strings . specieslist ) ; return ;
case nameof ( PKM . HeldItem ) : i . SetScreenedValue ( GameInfo . Strings . itemlist ) ; return ;
case nameof ( PKM . Ability ) : i . SetScreenedValue ( GameInfo . Strings . abilitylist ) ; return ;
case nameof ( PKM . Nature ) : i . SetScreenedValue ( GameInfo . Strings . natures ) ; return ;
case nameof ( PKM . Ball ) : i . SetScreenedValue ( GameInfo . Strings . balllist ) ; return ;
2020-12-25 18:58:33 +00:00
case nameof ( PKM . Move1 ) or nameof ( PKM . Move2 ) or nameof ( PKM . Move3 ) or nameof ( PKM . Move4 ) :
case nameof ( PKM . RelearnMove1 ) or nameof ( PKM . RelearnMove2 ) or nameof ( PKM . RelearnMove3 ) or nameof ( PKM . RelearnMove4 ) :
2018-05-18 05:43:07 +00:00
i . SetScreenedValue ( GameInfo . Strings . movelist ) ; return ;
}
}
2018-05-19 02:19:15 +00:00
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
2019-02-21 06:23:54 +00:00
/// <param name="pk">Object to check.</param>
/// <returns>True if <see cref="pk"/> matches all filters.</returns>
public static bool IsFilterMatch ( IEnumerable < StringInstruction > filters , PKM pk ) = > filters . All ( z = > IsFilterMatch ( z , pk , Props [ Array . IndexOf ( Types , pk . GetType ( ) ) ] ) ) ;
2018-05-19 02:19:15 +00:00
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="obj">Object to check.</param>
/// <returns>True if <see cref="obj"/> matches all filters.</returns>
2018-06-01 02:59:05 +00:00
public static bool IsFilterMatch ( IEnumerable < StringInstruction > filters , object obj )
2018-05-19 02:19:15 +00:00
{
foreach ( var cmd in filters )
{
if ( ! ReflectUtil . HasProperty ( obj , cmd . PropertyName , out var pi ) )
return false ;
2019-02-21 06:23:54 +00:00
try
{
if ( pi . IsValueEqual ( obj , cmd . PropertyValue ) = = cmd . Evaluator )
continue ;
}
2020-09-19 05:11:13 +00:00
#pragma warning disable CA1031 // Do not catch general exception types
// User provided inputs can mismatch the type's required value format, and fail to be compared.
2019-02-21 06:23:54 +00:00
catch ( Exception e )
2020-09-19 05:11:13 +00:00
#pragma warning restore CA1031 // Do not catch general exception types
2019-02-21 06:23:54 +00:00
{
Debug . WriteLine ( $"Unable to compare {cmd.PropertyName} to {cmd.PropertyValue}." ) ;
Debug . WriteLine ( e . Message ) ;
}
2018-05-19 02:19:15 +00:00
return false ;
}
return true ;
}
/// <summary>
/// Tries to modify the <see cref="PKM"/>.
/// </summary>
2019-02-21 06:23:54 +00:00
/// <param name="pk">Object to modify.</param>
2018-05-19 02:19:15 +00:00
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
2019-02-21 06:23:54 +00:00
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
2018-05-19 02:19:15 +00:00
/// <returns>Result of the attempted modification.</returns>
2019-02-21 06:23:54 +00:00
public static bool TryModify ( PKM pk , IEnumerable < StringInstruction > filters , IEnumerable < StringInstruction > modifications )
2018-05-18 05:43:07 +00:00
{
2019-02-21 06:23:54 +00:00
var result = TryModifyPKM ( pk , filters , modifications ) ;
2018-05-19 02:19:15 +00:00
return result = = ModifyResult . Modified ;
}
/// <summary>
/// Tries to modify the <see cref="PKMInfo"/>.
/// </summary>
2019-02-21 06:23:54 +00:00
/// <param name="pk">Command Filter</param>
2018-05-19 02:19:15 +00:00
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
2019-02-21 06:23:54 +00:00
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
2018-05-19 02:19:15 +00:00
/// <returns>Result of the attempted modification.</returns>
2019-02-21 06:23:54 +00:00
internal static ModifyResult TryModifyPKM ( PKM pk , IEnumerable < StringInstruction > filters , IEnumerable < StringInstruction > modifications )
2018-05-19 02:19:15 +00:00
{
2019-02-21 06:23:54 +00:00
if ( ! pk . ChecksumValid | | pk . Species = = 0 )
2018-05-18 05:43:07 +00:00
return ModifyResult . Invalid ;
2020-12-22 01:17:56 +00:00
var info = new PKMInfo ( pk ) ;
2019-02-21 06:23:54 +00:00
var pi = Props [ Array . IndexOf ( Types , pk . GetType ( ) ) ] ;
2018-05-19 02:19:15 +00:00
foreach ( var cmd in filters )
2018-05-18 05:43:07 +00:00
{
try
{
2018-06-01 02:59:05 +00:00
if ( ! IsFilterMatch ( cmd , info , pi ) )
2018-05-19 02:19:15 +00:00
return ModifyResult . Filtered ;
}
2020-09-19 05:11:13 +00:00
#pragma warning disable CA1031 // Do not catch general exception types
// Swallow any error because this can be malformed user input.
2018-05-19 02:19:15 +00:00
catch ( Exception ex )
2020-09-19 05:11:13 +00:00
#pragma warning restore CA1031 // Do not catch general exception types
2018-05-19 02:19:15 +00:00
{
Debug . WriteLine ( MsgBEModifyFailCompare + " " + ex . Message , cmd . PropertyName , cmd . PropertyValue ) ;
return ModifyResult . Error ;
2018-05-18 05:43:07 +00:00
}
}
ModifyResult result = ModifyResult . Modified ;
2018-05-19 02:19:15 +00:00
foreach ( var cmd in modifications )
2018-05-18 05:43:07 +00:00
{
try
{
2018-05-19 02:19:15 +00:00
var tmp = SetPKMProperty ( cmd , info , pi ) ;
2018-06-14 01:52:09 +00:00
if ( tmp ! = ModifyResult . Modified )
2018-05-18 05:43:07 +00:00
result = tmp ;
}
2020-09-06 17:53:13 +00:00
#pragma warning disable CA1031 // Do not catch general exception types
// Swallow any error because this can be malformed user input.
catch ( Exception ex )
#pragma warning restore CA1031 // Do not catch general exception types
{
Debug . WriteLine ( MsgBEModifyFail + " " + ex . Message , cmd . PropertyName , cmd . PropertyValue ) ;
}
2018-05-18 05:43:07 +00:00
}
return result ;
}
2018-05-19 02:19:15 +00:00
/// <summary>
/// Sets the if the <see cref="PKMInfo"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="info">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filtered, else false.</returns>
private static ModifyResult SetPKMProperty ( StringInstruction cmd , PKMInfo info , IReadOnlyDictionary < string , PropertyInfo > props )
2018-05-18 05:43:07 +00:00
{
2019-10-27 19:57:04 +00:00
var pk = info . Entity ;
2018-05-18 05:43:07 +00:00
if ( cmd . PropertyValue . StartsWith ( CONST_BYTES ) )
2019-02-21 06:23:54 +00:00
return SetByteArrayProperty ( pk , cmd ) ;
2018-05-18 05:43:07 +00:00
2020-04-15 23:23:20 +00:00
if ( cmd . PropertyValue . StartsWith ( CONST_SUGGEST , true , CultureInfo . CurrentCulture ) )
2019-12-09 01:52:17 +00:00
return SetSuggestedPKMProperty ( cmd . PropertyName , info , cmd . PropertyValue ) ;
2018-05-20 21:29:13 +00:00
if ( cmd . PropertyValue = = CONST_RAND & & cmd . PropertyName = = nameof ( PKM . Moves ) )
2020-04-12 23:07:59 +00:00
return SetMoves ( pk , info . Legality . GetMoveSet ( true ) ) ;
2018-05-18 05:43:07 +00:00
2019-02-21 06:23:54 +00:00
if ( SetComplexProperty ( pk , cmd ) )
2018-05-18 05:43:07 +00:00
return ModifyResult . Modified ;
2018-05-19 02:19:15 +00:00
if ( ! props . TryGetValue ( cmd . PropertyName , out var pi ) )
return ModifyResult . Error ;
2018-06-14 01:52:09 +00:00
if ( ! pi . CanWrite )
return ModifyResult . Error ;
2018-05-18 05:43:07 +00:00
object val = cmd . Random ? ( object ) cmd . RandomValue : cmd . PropertyValue ;
2019-02-21 06:23:54 +00:00
ReflectUtil . SetValue ( pi , pk , val ) ;
2018-05-19 02:19:15 +00:00
return ModifyResult . Modified ;
2018-05-18 05:43:07 +00:00
}
2018-05-19 02:19:15 +00:00
/// <summary>
/// Checks if the <see cref="PKMInfo"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="info">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
2018-06-01 02:59:05 +00:00
/// <returns>True if filter matches, else false.</returns>
private static bool IsFilterMatch ( StringInstruction cmd , PKMInfo info , IReadOnlyDictionary < string , PropertyInfo > props )
2018-05-18 05:43:07 +00:00
{
2018-05-19 02:19:15 +00:00
if ( IsLegalFiltered ( cmd , ( ) = > info . Legal ) )
return true ;
2019-10-27 19:57:04 +00:00
return IsPropertyFiltered ( cmd , info . Entity , props ) ;
2018-05-19 02:19:15 +00:00
}
2018-05-18 05:43:07 +00:00
2018-05-19 02:19:15 +00:00
/// <summary>
/// Checks if the <see cref="PKM"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
2019-02-21 06:23:54 +00:00
/// <param name="pk">Pokémon to check.</param>
2018-05-19 02:19:15 +00:00
/// <param name="props">PropertyInfo cache (optional)</param>
2018-06-01 02:59:05 +00:00
/// <returns>True if filter matches, else false.</returns>
2019-02-21 06:23:54 +00:00
private static bool IsFilterMatch ( StringInstruction cmd , PKM pk , IReadOnlyDictionary < string , PropertyInfo > props )
2018-05-19 02:19:15 +00:00
{
2019-02-21 06:23:54 +00:00
if ( IsLegalFiltered ( cmd , ( ) = > new LegalityAnalysis ( pk ) . Valid ) )
2018-05-19 02:19:15 +00:00
return true ;
2019-02-21 06:23:54 +00:00
return IsPropertyFiltered ( cmd , pk , props ) ;
2018-05-19 02:19:15 +00:00
}
2018-05-21 02:26:36 +00:00
/// <summary>
/// Checks if the <see cref="PKM"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
2019-02-21 06:23:54 +00:00
/// <param name="pk">Pokémon to check.</param>
2018-05-21 02:26:36 +00:00
/// <param name="props">PropertyInfo cache</param>
/// <returns>True if filtered, else false.</returns>
2019-02-21 06:23:54 +00:00
private static bool IsPropertyFiltered ( StringInstruction cmd , PKM pk , IReadOnlyDictionary < string , PropertyInfo > props )
2018-05-21 02:26:36 +00:00
{
2019-02-21 06:23:54 +00:00
if ( IsIdentifierFiltered ( cmd , pk ) )
2018-05-21 02:26:36 +00:00
return true ;
if ( ! props . TryGetValue ( cmd . PropertyName , out var pi ) )
return false ;
2018-06-14 01:52:09 +00:00
if ( ! pi . CanRead )
return false ;
2019-02-21 06:23:54 +00:00
return pi . IsValueEqual ( pk , cmd . PropertyValue ) = = cmd . Evaluator ;
2018-05-19 02:19:15 +00:00
}
/// <summary>
/// Checks if the <see cref="PKM"/> should be filtered due to its <see cref="PKM.Identifier"/> containing a value.
/// </summary>
/// <param name="cmd">Command Filter</param>
2019-02-21 06:23:54 +00:00
/// <param name="pk">Pokémon to check.</param>
2018-05-19 02:19:15 +00:00
/// <returns>True if filtered, else false.</returns>
2019-02-21 06:23:54 +00:00
private static bool IsIdentifierFiltered ( StringInstruction cmd , PKM pk )
2018-05-19 02:19:15 +00:00
{
if ( cmd . PropertyName ! = IdentifierContains )
return false ;
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
bool result = pk . Identifier ? . Contains ( cmd . PropertyValue ) ? ? false ;
2018-05-27 19:27:44 +00:00
return result = = cmd . Evaluator ;
2018-05-19 02:19:15 +00:00
}
/// <summary>
/// Checks if the <see cref="PKM"/> should be filtered due to its legality.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="isLegal">Function to check if the <see cref="PKM"/> is legal.</param>
/// <returns>True if filtered, else false.</returns>
private static bool IsLegalFiltered ( StringInstruction cmd , Func < bool > isLegal )
{
if ( cmd . PropertyName ! = PROP_LEGAL )
return false ;
if ( ! bool . TryParse ( cmd . PropertyValue , out bool legal ) )
return true ;
2018-05-27 19:27:44 +00:00
return legal = = isLegal ( ) = = cmd . Evaluator ;
2018-05-18 05:43:07 +00:00
}
2018-05-19 02:19:15 +00:00
/// <summary>
/// Sets the <see cref="PKM"/> data with a suggested value based on its <see cref="LegalityAnalysis"/>.
/// </summary>
/// <param name="name">Property to modify.</param>
/// <param name="info">Cached info storing Legal data.</param>
2019-12-09 01:52:17 +00:00
/// <param name="propValue">Suggestion string which starts with <see cref="CONST_SUGGEST"/></param>
private static ModifyResult SetSuggestedPKMProperty ( string name , PKMInfo info , string propValue )
2018-05-18 05:43:07 +00:00
{
2020-06-17 02:46:22 +00:00
static bool IsAll ( string p ) = > p . EndsWith ( "All" , true , CultureInfo . CurrentCulture ) ;
static bool IsNone ( string p ) = > p . EndsWith ( "None" , true , CultureInfo . CurrentCulture ) ;
2019-10-27 19:57:04 +00:00
var pk = info . Entity ;
2018-05-19 02:19:15 +00:00
switch ( name )
2018-05-18 05:43:07 +00:00
{
2018-12-12 06:27:42 +00:00
// pb7 only
2019-02-21 06:23:54 +00:00
case nameof ( PB7 . Stat_CP ) when pk is PB7 pb7 :
2018-12-12 06:27:42 +00:00
pb7 . ResetCP ( ) ;
return ModifyResult . Modified ;
2019-02-21 06:23:54 +00:00
case nameof ( PB7 . HeightAbsolute ) when pk is PB7 pb7 :
2018-12-12 06:27:42 +00:00
pb7 . HeightAbsolute = pb7 . CalcHeightAbsolute ;
return ModifyResult . Modified ;
2019-02-21 06:23:54 +00:00
case nameof ( PB7 . WeightAbsolute ) when pk is PB7 pb7 :
2018-12-12 06:27:42 +00:00
pb7 . WeightAbsolute = pb7 . CalcWeightAbsolute ;
return ModifyResult . Modified ;
2020-04-13 16:23:31 +00:00
// Date Copy
case nameof ( PKM . EggMetDate ) :
pk . EggMetDate = pk . MetDate ;
return ModifyResult . Modified ;
case nameof ( PKM . MetDate ) :
pk . MetDate = pk . EggMetDate ;
return ModifyResult . Modified ;
2019-11-23 20:02:49 +00:00
case nameof ( PKM . Nature ) when pk . Format > = 8 :
pk . Nature = pk . StatNature ;
return ModifyResult . Modified ;
case nameof ( PKM . StatNature ) when pk . Format > = 8 :
pk . StatNature = pk . Nature ;
return ModifyResult . Modified ;
2018-12-04 04:53:37 +00:00
case nameof ( PKM . Stats ) :
2019-07-12 23:41:13 +00:00
pk . ResetPartyStats ( ) ;
2018-12-04 04:53:37 +00:00
return ModifyResult . Modified ;
2018-06-06 04:31:42 +00:00
case nameof ( IHyperTrain . HyperTrainFlags ) :
2019-02-21 06:23:54 +00:00
pk . SetSuggestedHyperTrainingData ( ) ;
2018-05-19 02:19:15 +00:00
return ModifyResult . Modified ;
2018-05-18 05:43:07 +00:00
case nameof ( PKM . RelearnMoves ) :
2019-11-23 20:02:49 +00:00
if ( pk . Format > = 8 )
{
pk . ClearRecordFlags ( ) ;
2020-06-17 02:46:22 +00:00
if ( IsAll ( propValue ) )
2019-12-09 01:52:17 +00:00
pk . SetRecordFlags ( ) ; // all
2020-06-17 02:46:22 +00:00
else if ( ! IsNone ( propValue ) )
2019-12-09 01:52:17 +00:00
pk . SetRecordFlags ( pk . Moves ) ; // whatever fit the current moves
2019-11-23 20:02:49 +00:00
}
2019-09-10 07:21:51 +00:00
pk . SetRelearnMoves ( info . SuggestedRelearn ) ;
2018-05-19 02:19:15 +00:00
return ModifyResult . Modified ;
2019-12-09 01:52:17 +00:00
case PROP_RIBBONS :
2020-06-17 02:46:22 +00:00
if ( IsNone ( propValue ) )
2019-12-09 01:52:17 +00:00
RibbonApplicator . RemoveAllValidRibbons ( pk ) ;
else // All
RibbonApplicator . SetAllValidRibbons ( pk ) ;
return ModifyResult . Modified ;
2018-05-18 05:43:07 +00:00
case nameof ( PKM . Met_Location ) :
2020-01-13 03:40:36 +00:00
var encounter = EncounterSuggestion . GetSuggestedMetInfo ( pk ) ;
2018-05-18 05:43:07 +00:00
if ( encounter = = null )
2018-05-19 02:19:15 +00:00
return ModifyResult . Error ;
2018-05-18 05:43:07 +00:00
2020-01-13 03:40:36 +00:00
int level = encounter . LevelMin ;
2018-05-18 05:43:07 +00:00
int location = encounter . Location ;
2020-06-17 02:46:22 +00:00
int minimumLevel = EncounterSuggestion . GetLowestLevel ( pk , encounter . LevelMin ) ;
2018-05-18 05:43:07 +00:00
2019-02-21 06:23:54 +00:00
pk . Met_Level = level ;
pk . Met_Location = location ;
2020-06-17 02:46:22 +00:00
pk . CurrentLevel = Math . Max ( minimumLevel , level ) ;
2018-05-18 05:43:07 +00:00
2018-05-19 02:19:15 +00:00
return ModifyResult . Modified ;
2018-05-18 05:43:07 +00:00
2020-01-04 04:21:02 +00:00
case nameof ( PKM . Heal ) :
pk . Heal ( ) ;
return ModifyResult . Modified ;
case nameof ( PKM . HealPP ) :
pk . HealPP ( ) ;
return ModifyResult . Modified ;
2020-12-25 18:58:33 +00:00
case nameof ( PKM . Move1_PP ) or nameof ( PKM . Move2_PP ) or nameof ( PKM . Move3_PP ) or nameof ( PKM . Move4_PP ) :
2020-01-04 04:21:02 +00:00
pk . SetSuggestedMovePP ( name [ 4 ] - '1' ) ; // 0-3 int32
return ModifyResult . Modified ;
2018-05-18 05:43:07 +00:00
case nameof ( PKM . Moves ) :
2020-04-12 23:07:59 +00:00
return SetMoves ( pk , info . Legality . GetMoveSet ( ) ) ;
2018-05-18 05:43:07 +00:00
2019-12-04 04:14:36 +00:00
case nameof ( PKM . Ball ) :
2020-01-26 00:47:44 +00:00
BallApplicator . ApplyBallLegalByColor ( pk ) ;
2019-12-04 04:14:36 +00:00
return ModifyResult . Modified ;
2018-05-18 05:43:07 +00:00
default :
2018-05-19 02:19:15 +00:00
return ModifyResult . Error ;
2018-05-18 05:43:07 +00:00
}
}
2018-05-20 21:29:13 +00:00
/// <summary>
/// Sets the provided moves in a random order.
/// </summary>
2019-02-21 06:23:54 +00:00
/// <param name="pk">Pokémon to modify.</param>
2018-05-20 21:29:13 +00:00
/// <param name="moves">Moves to apply.</param>
2019-02-21 06:23:54 +00:00
private static ModifyResult SetMoves ( PKM pk , int [ ] moves )
2018-05-20 21:29:13 +00:00
{
2019-02-21 06:23:54 +00:00
pk . SetMoves ( moves ) ;
2020-07-03 06:17:05 +00:00
pk . HealPP ( ) ;
2018-05-20 21:29:13 +00:00
return ModifyResult . Modified ;
}
2018-05-19 02:19:15 +00:00
/// <summary>
2020-06-17 02:46:22 +00:00
/// Sets the <see cref="PKM"/> byte array property to a specified value.
2018-05-19 02:19:15 +00:00
/// </summary>
2019-02-21 06:23:54 +00:00
/// <param name="pk">Pokémon to modify.</param>
2018-05-19 02:19:15 +00:00
/// <param name="cmd">Modification</param>
2019-02-21 06:23:54 +00:00
private static ModifyResult SetByteArrayProperty ( PKM pk , StringInstruction cmd )
2018-05-18 05:43:07 +00:00
{
switch ( cmd . PropertyName )
{
2019-02-21 06:23:54 +00:00
case nameof ( PKM . Nickname_Trash ) :
2020-06-17 02:46:22 +00:00
pk . Nickname_Trash = ConvertToBytes ( cmd . PropertyValue ) ;
2018-05-19 02:19:15 +00:00
return ModifyResult . Modified ;
2019-02-21 06:23:54 +00:00
case nameof ( PKM . OT_Trash ) :
2020-06-17 02:46:22 +00:00
pk . OT_Trash = ConvertToBytes ( cmd . PropertyValue ) ;
2018-05-19 02:19:15 +00:00
return ModifyResult . Modified ;
2018-05-18 05:43:07 +00:00
default :
2018-05-19 02:19:15 +00:00
return ModifyResult . Error ;
2018-05-18 05:43:07 +00:00
}
2020-06-17 02:46:22 +00:00
static byte [ ] ConvertToBytes ( string str ) = > str . Substring ( CONST_BYTES . Length ) . Split ( ',' ) . Select ( z = > Convert . ToByte ( z . Trim ( ) , 16 ) ) . ToArray ( ) ;
2018-05-18 05:43:07 +00:00
}
2018-05-19 02:19:15 +00:00
/// <summary>
/// Sets the <see cref="PKM"/> property to a non-specific smart value.
/// </summary>
2019-02-21 06:23:54 +00:00
/// <param name="pk">Pokémon to modify.</param>
2018-05-19 02:19:15 +00:00
/// <param name="cmd">Modification</param>
2019-07-14 22:06:45 +00:00
/// <returns>True if modified, false if no modifications done.</returns>
2019-02-21 06:23:54 +00:00
private static bool SetComplexProperty ( PKM pk , StringInstruction cmd )
2018-05-18 05:43:07 +00:00
{
2020-06-17 02:46:22 +00:00
static DateTime ParseDate ( string val ) = > DateTime . ParseExact ( val , "yyyyMMdd" , CultureInfo . InvariantCulture , DateTimeStyles . None ) ;
2019-07-14 22:06:45 +00:00
2019-02-21 06:23:54 +00:00
if ( cmd . PropertyName = = nameof ( PKM . MetDate ) )
2020-06-17 02:46:22 +00:00
pk . MetDate = ParseDate ( cmd . PropertyValue ) ;
2019-02-21 06:23:54 +00:00
else if ( cmd . PropertyName = = nameof ( PKM . EggMetDate ) )
2020-06-17 02:46:22 +00:00
pk . EggMetDate = ParseDate ( cmd . PropertyValue ) ;
2019-02-21 06:23:54 +00:00
else if ( cmd . PropertyName = = nameof ( PKM . EncryptionConstant ) & & cmd . PropertyValue = = CONST_RAND )
pk . EncryptionConstant = Util . Rand32 ( ) ;
else if ( ( cmd . PropertyName = = nameof ( PKM . Ability ) | | cmd . PropertyName = = nameof ( PKM . AbilityNumber ) ) & & cmd . PropertyValue . StartsWith ( "$" ) )
pk . RefreshAbility ( Convert . ToInt16 ( cmd . PropertyValue [ 1 ] ) - 0x30 ) ;
else if ( cmd . PropertyName = = nameof ( PKM . PID ) & & cmd . PropertyValue = = CONST_RAND )
pk . SetPIDGender ( pk . Gender ) ;
else if ( cmd . PropertyName = = nameof ( PKM . EncryptionConstant ) & & cmd . PropertyValue = = nameof ( PKM . PID ) )
pk . EncryptionConstant = pk . PID ;
2020-04-15 23:23:20 +00:00
else if ( cmd . PropertyName = = nameof ( PKM . PID ) & & cmd . PropertyValue . StartsWith ( CONST_SHINY , true , CultureInfo . CurrentCulture ) )
2020-10-25 17:42:48 +00:00
CommonEdits . SetShiny ( pk , cmd . PropertyValue . EndsWith ( "0" ) ? Shiny . AlwaysSquare : cmd . PropertyValue . EndsWith ( "1" ) ? Shiny . AlwaysStar : Shiny . Random ) ;
2019-02-21 06:23:54 +00:00
else if ( cmd . PropertyName = = nameof ( PKM . Species ) & & cmd . PropertyValue = = "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
Array . Clear ( pk . Data , 0 , pk . Data . Length ) ;
2018-05-18 05:43:07 +00:00
else if ( cmd . PropertyName . StartsWith ( "IV" ) & & cmd . PropertyValue = = CONST_RAND )
2019-02-21 06:23:54 +00:00
SetRandomIVs ( pk , cmd ) ;
else if ( cmd . PropertyName = = nameof ( PKM . IsNicknamed ) & & string . Equals ( cmd . PropertyValue , "false" , StringComparison . OrdinalIgnoreCase ) )
pk . SetDefaultNickname ( ) ;
2018-05-18 05:43:07 +00:00
else
return false ;
return true ;
}
2018-05-19 02:19:15 +00:00
/// <summary>
/// Sets the <see cref="PKM"/> IV(s) to a random value.
/// </summary>
2019-02-21 06:23:54 +00:00
/// <param name="pk">Pokémon to modify.</param>
2018-05-19 02:19:15 +00:00
/// <param name="cmd">Modification</param>
2019-02-21 06:23:54 +00:00
private static void SetRandomIVs ( PKM pk , StringInstruction cmd )
2018-05-18 05:43:07 +00:00
{
2019-02-21 06:23:54 +00:00
if ( cmd . PropertyName = = nameof ( PKM . IVs ) )
2018-05-18 05:43:07 +00:00
{
2019-02-21 06:23:54 +00:00
pk . SetRandomIVs ( ) ;
2018-05-18 05:43:07 +00:00
return ;
}
2019-02-21 06:23:54 +00:00
if ( TryGetHasProperty ( pk , cmd . PropertyName , out var pi ) )
ReflectUtil . SetValue ( pi , pk , Util . Rand . Next ( pk . MaxIV + 1 ) ) ;
2018-05-18 05:43:07 +00:00
}
}
}