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 ;
2021-05-27 19:20:00 +00:00
using static PKHeX . Core . BatchModifications ;
2018-05-18 05:43:07 +00:00
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
2021-06-09 03:28:13 +00:00
public static readonly List < string > CustomProperties = new ( ) { PROP_LEGAL , PROP_TYPENAME , 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" ;
2021-06-09 03:28:13 +00:00
private const string PROP_TYPENAME = "ObjectType" ;
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 ;
2021-05-14 22:30:55 +00:00
p1 [ ^ 1 ] = all ;
2018-05-18 05:43:07 +00:00
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 )
{
2021-06-09 03:28:13 +00:00
if ( cmd . PropertyName is PROP_TYPENAME )
{
if ( ( obj . GetType ( ) . Name = = cmd . PropertyValue ) ! = cmd . Evaluator )
return false ;
2021-06-10 01:40:17 +00:00
continue ;
2021-06-09 03:28:13 +00:00
}
2018-05-19 02:19:15 +00:00
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>
2021-04-20 08:50:47 +00:00
/// Tries to modify the <see cref="BatchInfo"/>.
2018-05-19 02:19:15 +00:00
/// </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 ;
2021-04-20 08:50:47 +00:00
var info = new BatchInfo ( 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>
2021-04-20 08:50:47 +00:00
/// Sets the if the <see cref="BatchInfo"/> should be filtered due to the <see cref="StringInstruction"/> provided.
2018-05-19 02:19:15 +00:00
/// </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>
2021-04-20 08:50:47 +00:00
private static ModifyResult SetPKMProperty ( StringInstruction cmd , BatchInfo 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 ;
2021-06-03 19:04:19 +00:00
object val = cmd . Random ? 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>
2021-04-20 08:50:47 +00:00
/// Checks if the <see cref="BatchInfo"/> should be filtered due to the <see cref="StringInstruction"/> provided.
2018-05-19 02:19:15 +00:00
/// </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>
2021-04-20 08:50:47 +00:00
private static bool IsFilterMatch ( StringInstruction cmd , BatchInfo info , IReadOnlyDictionary < string , PropertyInfo > props )
2018-05-18 05:43:07 +00:00
{
2021-05-27 21:43:32 +00:00
var match = FilterMods . Find ( z = > z . IsMatch ( cmd . PropertyName ) ) ;
if ( match ! = null )
return match . IsFiltered ( info , cmd ) ;
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
{
2021-05-27 21:43:32 +00:00
var match = FilterMods . Find ( z = > z . IsMatch ( cmd . PropertyName ) ) ;
if ( match ! = null )
return match . IsFiltered ( pk , cmd ) ;
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
{
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>
/// 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>
2021-04-20 08:50:47 +00:00
private static ModifyResult SetSuggestedPKMProperty ( string name , BatchInfo info , string propValue )
2018-05-18 05:43:07 +00:00
{
2021-05-27 19:20:00 +00:00
var first = SuggestionMods . Find ( z = > z . IsMatch ( name , propValue , info ) ) ;
if ( first ! = null )
return first . Modify ( name , propValue , info ) ;
return ModifyResult . Error ;
2018-05-20 21:29:13 +00:00
}
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
}
2021-05-14 22:30:55 +00:00
static byte [ ] ConvertToBytes ( string str ) = > str [ 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
{
2021-05-27 19:20:00 +00:00
if ( cmd . PropertyName . StartsWith ( "IV" ) & & cmd . PropertyValue = = CONST_RAND )
{
2019-02-21 06:23:54 +00:00
SetRandomIVs ( pk , cmd ) ;
2021-05-27 19:20:00 +00:00
return true ;
}
var match = ComplexMods . Find ( z = > z . IsMatch ( cmd . PropertyName , cmd . PropertyValue ) ) ;
if ( match = = null )
2018-05-18 05:43:07 +00:00
return false ;
2021-05-27 19:20:00 +00:00
match . Modify ( pk , cmd ) ;
2018-05-18 05:43:07 +00:00
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
}
2021-05-27 19:20:00 +00:00
2021-05-27 21:43:32 +00:00
public static readonly List < IComplexFilter > FilterMods = new ( )
{
new ComplexFilter ( PROP_LEGAL ,
( pkm , cmd ) = > new LegalityAnalysis ( pkm ) . Valid = = cmd . Evaluator ,
( info , cmd ) = > info . Legality . Valid = = cmd . Evaluator ) ,
2021-06-09 03:28:13 +00:00
new ComplexFilter ( PROP_TYPENAME ,
( pkm , cmd ) = > ( pkm . GetType ( ) . Name = = cmd . PropertyValue ) = = cmd . Evaluator ,
( info , cmd ) = > ( info . Entity . GetType ( ) . Name = = cmd . PropertyValue ) = = cmd . Evaluator ) ,
2021-05-27 21:43:32 +00:00
new ComplexFilter ( IdentifierContains ,
( pkm , cmd ) = > pkm . Identifier ? . Contains ( cmd . PropertyValue ) = = cmd . Evaluator ,
( info , cmd ) = > info . Entity . Identifier ? . Contains ( cmd . PropertyValue ) = = cmd . Evaluator ) ,
} ;
2021-05-27 19:20:00 +00:00
public static readonly List < ISuggestModification > SuggestionMods = new ( )
{
// PB7 Specific
new TypeSuggestion < PB7 > ( nameof ( PB7 . Stat_CP ) , p = > p . ResetCP ( ) ) ,
new TypeSuggestion < PB7 > ( nameof ( PB7 . HeightAbsolute ) , p = > p . HeightAbsolute = p . CalcHeightAbsolute ) ,
new TypeSuggestion < PB7 > ( nameof ( PB7 . WeightAbsolute ) , p = > p . WeightAbsolute = p . CalcWeightAbsolute ) ,
// Date Copy
new TypeSuggestion < PKM > ( nameof ( PKM . EggMetDate ) , p = > p . EggMetDate = p . MetDate ) ,
new TypeSuggestion < PKM > ( nameof ( PKM . MetDate ) , p = > p . MetDate = p . EggMetDate ) ,
new TypeSuggestion < PKM > ( nameof ( PKM . Nature ) , p = > p . Format > = 8 , p = > p . Nature = p . StatNature ) ,
new TypeSuggestion < PKM > ( nameof ( PKM . StatNature ) , p = > p . Format > = 8 , p = > p . StatNature = p . Nature ) ,
new TypeSuggestion < PKM > ( nameof ( PKM . Stats ) , p = > p . ResetPartyStats ( ) ) ,
new TypeSuggestion < PKM > ( nameof ( PKM . Ball ) , p = > BallApplicator . ApplyBallLegalByColor ( p ) ) ,
new TypeSuggestion < PKM > ( nameof ( PKM . Heal ) , p = > p . Heal ( ) ) ,
new TypeSuggestion < PKM > ( nameof ( PKM . HealPP ) , p = > p . HealPP ( ) ) ,
new TypeSuggestion < PKM > ( nameof ( IHyperTrain . HyperTrainFlags ) , p = > p . SetSuggestedHyperTrainingData ( ) ) ,
new TypeSuggestion < PKM > ( nameof ( PKM . Move1_PP ) , p = > p . SetSuggestedMovePP ( 0 ) ) ,
new TypeSuggestion < PKM > ( nameof ( PKM . Move2_PP ) , p = > p . SetSuggestedMovePP ( 1 ) ) ,
new TypeSuggestion < PKM > ( nameof ( PKM . Move3_PP ) , p = > p . SetSuggestedMovePP ( 2 ) ) ,
new TypeSuggestion < PKM > ( nameof ( PKM . Move4_PP ) , p = > p . SetSuggestedMovePP ( 3 ) ) ,
new ComplexSuggestion ( nameof ( PKM . Moves ) , ( _ , _ , info ) = > SetMoves ( info . Entity , info . Legality . GetMoveSet ( ) ) ) ,
new ComplexSuggestion ( nameof ( PKM . RelearnMoves ) , ( _ , value , info ) = > SetSuggestedRelearnData ( info , value ) ) ,
new ComplexSuggestion ( PROP_RIBBONS , ( _ , value , info ) = > SetSuggestedRibbons ( info , value ) ) ,
new ComplexSuggestion ( nameof ( PKM . Met_Location ) , ( _ , _ , info ) = > SetSuggestedMetData ( info ) ) ,
} ;
private static DateTime ParseDate ( string val ) = > DateTime . ParseExact ( val , "yyyyMMdd" , CultureInfo . InvariantCulture , DateTimeStyles . None ) ;
public static readonly List < IComplexSet > ComplexMods = new ( )
{
// Date
new ComplexSet ( nameof ( PKM . MetDate ) , ( pk , cmd ) = > pk . MetDate = ParseDate ( cmd . PropertyValue ) ) ,
new ComplexSet ( nameof ( PKM . EggMetDate ) , ( pk , cmd ) = > pk . EggMetDate = ParseDate ( cmd . PropertyValue ) ) ,
// Value Swap
new ComplexSet ( nameof ( PKM . EncryptionConstant ) , value = > value = = nameof ( PKM . PID ) , ( pk , _ ) = > pk . EncryptionConstant = pk . PID ) ,
new ComplexSet ( nameof ( PKM . PID ) , value = > value = = nameof ( PKM . EncryptionConstant ) , ( pk , _ ) = > pk . PID = pk . EncryptionConstant ) ,
// Realign to Derived Value
new ComplexSet ( nameof ( PKM . Ability ) , value = > value . StartsWith ( "$" ) , ( pk , cmd ) = > pk . RefreshAbility ( Convert . ToInt16 ( cmd . PropertyValue [ 1 ] ) - 0x30 ) ) ,
new ComplexSet ( nameof ( PKM . AbilityNumber ) , value = > value . StartsWith ( "$" ) , ( pk , cmd ) = > pk . RefreshAbility ( Convert . ToInt16 ( cmd . PropertyValue [ 1 ] ) - 0x30 ) ) ,
// Random
new ComplexSet ( nameof ( PKM . EncryptionConstant ) , value = > value = = CONST_RAND , ( pk , _ ) = > pk . EncryptionConstant = Util . Rand32 ( ) ) ,
new ComplexSet ( nameof ( PKM . PID ) , value = > value = = CONST_RAND , ( pk , _ ) = > pk . PID = Util . Rand32 ( ) ) ,
new ComplexSet ( nameof ( PKM . Gender ) , value = > value = = CONST_RAND , ( pk , _ ) = > pk . SetPIDGender ( pk . Gender ) ) ,
// Shiny
new ComplexSet ( nameof ( PKM . PID ) ,
value = > value . StartsWith ( CONST_SHINY , true , CultureInfo . CurrentCulture ) ,
( pk , cmd ) = >
CommonEdits . SetShiny ( pk , cmd . PropertyValue . EndsWith ( "0" ) ? Shiny . AlwaysSquare : cmd . PropertyValue . EndsWith ( "1" ) ? Shiny . AlwaysStar : Shiny . Random ) ) ,
new ComplexSet ( nameof ( PKM . Species ) , value = > value = = "0" , ( pk , _ ) = > Array . Clear ( pk . Data , 0 , pk . Data . Length ) ) ,
new ComplexSet ( nameof ( PKM . IsNicknamed ) , value = > string . Equals ( value , "false" , StringComparison . OrdinalIgnoreCase ) , ( pk , _ ) = > pk . SetDefaultNickname ( ) ) ,
} ;
2018-05-18 05:43:07 +00:00
}
}