2022-06-18 18:04:24 +00:00
using System ;
2018-05-18 05:43:07 +00:00
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.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
2022-06-18 18:04:24 +00:00
namespace PKHeX.Core ;
/// <summary>
/// Logic for editing many <see cref="PKM"/> with user provided <see cref="StringInstruction"/> list.
/// </summary>
public static class BatchEditing
2018-05-18 05:43:07 +00:00
{
2022-06-18 18:04:24 +00:00
public static readonly Type [ ] Types =
{
typeof ( PK8 ) , typeof ( PA8 ) , typeof ( PB8 ) ,
typeof ( PB7 ) ,
typeof ( PK7 ) , typeof ( PK6 ) , typeof ( PK5 ) , typeof ( PK4 ) , typeof ( BK4 ) ,
typeof ( PK3 ) , typeof ( XK3 ) , typeof ( CK3 ) ,
typeof ( PK2 ) , typeof ( SK2 ) , typeof ( PK1 ) ,
} ;
2019-07-14 22:06:45 +00:00
/// <summary>
2022-06-18 18:04:24 +00:00
/// Extra properties to show in the list of selectable properties (GUI)
2019-07-14 22:06:45 +00:00
/// </summary>
2022-06-18 18:04:24 +00:00
public static readonly List < string > CustomProperties = new ( )
2018-05-18 05:43:07 +00:00
{
2022-06-18 18:04:24 +00:00
PROP_LEGAL , PROP_TYPENAME , PROP_RIBBONS , PROP_CONTESTSTATS , PROP_MOVEMASTERY ,
IdentifierContains , nameof ( ISlotInfo . Slot ) , nameof ( SlotInfoBox . Box ) ,
} ;
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
/// <summary>
/// Property names, indexed by <see cref="Types"/>.
/// </summary>
public static string [ ] [ ] Properties = > GetProperties . Value ;
2022-03-08 03:42:08 +00:00
2022-06-18 18:04:24 +00:00
private static readonly Lazy < string [ ] [ ] > GetProperties = new ( ( ) = > GetPropArray ( Types , CustomProperties ) ) ;
2018-05-18 05:43:07 +00:00
2022-06-18 18:04:24 +00:00
private static readonly Dictionary < string , PropertyInfo > [ ] Props = GetPropertyDictionaries ( Types ) ;
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
private static Dictionary < string , PropertyInfo > [ ] GetPropertyDictionaries ( IReadOnlyList < Type > types )
{
var result = new Dictionary < string , PropertyInfo > [ types . Count ] ;
for ( int i = 0 ; i < types . Count ; i + + )
result [ i ] = GetPropertyDictionary ( types [ i ] , ReflectUtil . GetAllPropertyInfoPublic ) ;
return result ;
}
2018-05-18 05:43:07 +00:00
2022-06-18 18:04:24 +00:00
private static Dictionary < string , PropertyInfo > GetPropertyDictionary ( Type type , Func < Type , IEnumerable < PropertyInfo > > selector )
{
var dict = new Dictionary < string , PropertyInfo > ( ) ;
var props = selector ( type ) ;
foreach ( var p in props )
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
if ( ! dict . ContainsKey ( p . Name ) )
dict . Add ( p . Name , p ) ;
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
return dict ;
}
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
internal const string CONST_RAND = "$rand" ;
internal const string CONST_SHINY = "$shiny" ;
internal const string CONST_SUGGEST = "$suggest" ;
private const string CONST_BYTES = "$[]" ;
private const char CONST_POINTER = '*' ;
internal const char CONST_SPECIAL = '$' ;
internal const string PROP_LEGAL = "Legal" ;
internal const string PROP_TYPENAME = "ObjectType" ;
internal const string PROP_RIBBONS = "Ribbons" ;
2022-06-26 06:08:28 +00:00
internal const string PROP_EVS = "EVs" ;
2022-06-18 18:04:24 +00:00
internal const string PROP_CONTESTSTATS = "ContestStats" ;
internal const string PROP_MOVEMASTERY = "MoveMastery" ;
internal const string IdentifierContains = nameof ( IdentifierContains ) ;
private static string [ ] [ ] GetPropArray ( IReadOnlyList < Type > types , IReadOnlyList < string > extra )
{
var result = new string [ types . Count + 2 ] [ ] ;
var p = result . AsSpan ( 1 , types . Count ) ;
2018-05-18 05:43:07 +00:00
2022-06-18 18:04:24 +00:00
for ( int i = 0 ; i < p . Length ; i + + )
2018-05-18 05:43:07 +00:00
{
2022-06-18 18:04:24 +00:00
var props = ReflectUtil . GetPropertiesPublic ( types [ i ] ) ;
p [ i ] = props . Concat ( extra ) . OrderBy ( a = > a ) . ToArray ( ) ;
2018-05-18 05:43:07 +00:00
}
2022-06-18 18:04:24 +00:00
// Properties for any PKM
// Properties shared by all PKM
var first = p [ 0 ] ;
var any = new HashSet < string > ( first ) ;
var all = new HashSet < string > ( first ) ;
for ( int i = 1 ; i < p . Length ; i + + )
2018-05-18 05:43:07 +00:00
{
2022-06-18 18:04:24 +00:00
any . UnionWith ( p [ i ] ) ;
all . IntersectWith ( p [ i ] ) ;
2021-01-17 08:05:07 +00:00
}
2022-06-18 18:04:24 +00:00
result [ 0 ] = any . OrderBy ( z = > z ) . ToArray ( ) ;
result [ ^ 1 ] = all . OrderBy ( z = > z ) . ToArray ( ) ;
return result ;
}
/// <summary>
/// Tries to fetch the <see cref="PKM"/> property from the cache of available properties.
/// </summary>
/// <param name="pk">Pokémon 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 ( PKM pk , string name , [ NotNullWhen ( true ) ] out PropertyInfo ? pi )
{
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 )
2021-01-17 08:05:07 +00:00
{
2022-06-18 18:04:24 +00:00
pi = null ;
return false ;
2018-05-18 05:43:07 +00:00
}
2022-06-18 18:04:24 +00:00
var props = Props [ index ] ;
return props . TryGetValue ( name , out pi ) ;
}
2018-05-18 05:43:07 +00:00
2022-06-18 18:04:24 +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 + + )
2021-01-17 08:05:07 +00:00
{
2022-06-18 18:04:24 +00:00
var type = Types [ i ] ;
var props = Props [ i ] ;
if ( ! props . TryGetValue ( property , out var pi ) )
continue ;
yield return $"{type.Name}: {pi.PropertyType.Name}" ;
2021-01-17 08:05:07 +00:00
}
2022-06-18 18:04:24 +00:00
}
2021-01-17 08:05:07 +00:00
2022-06-18 18:04:24 +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>
public static string? GetPropertyType ( string propertyName , int typeIndex = 0 )
{
if ( CustomProperties . Contains ( propertyName ) )
return "Custom" ;
2018-05-18 05:43:07 +00:00
2022-06-18 18:04:24 +00:00
if ( typeIndex = = 0 ) // Any
{
foreach ( var p in Props )
2018-05-18 05:43:07 +00:00
{
2022-06-18 18:04:24 +00:00
if ( p . TryGetValue ( propertyName , out var pi ) )
return pi . PropertyType . Name ;
2018-05-18 05:43:07 +00:00
}
2022-06-18 18:04:24 +00:00
return null ;
2018-05-18 05:43:07 +00:00
}
2022-06-18 18:04:24 +00:00
int index = typeIndex - 1 > = Props . Length ? 0 : typeIndex - 1 ; // All vs Specific
var pr = Props [ index ] ;
if ( ! pr . TryGetValue ( propertyName , out var info ) )
return null ;
return info . PropertyType . Name ;
}
/// <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>
public static void ScreenStrings ( IEnumerable < StringInstruction > il )
{
foreach ( var i in il . Where ( i = > ! i . PropertyValue . All ( char . IsDigit ) ) )
2018-05-18 05:43:07 +00:00
{
2022-06-18 18:04:24 +00:00
string pv = i . PropertyValue ;
if ( pv . StartsWith ( CONST_SPECIAL ) & & ! pv . StartsWith ( CONST_BYTES , StringComparison . Ordinal ) & & pv . Contains ( ',' ) )
i . SetRandRange ( pv ) ;
2018-05-18 05:43:07 +00:00
2022-06-18 18:04:24 +00:00
SetInstructionScreenedValue ( i ) ;
2018-05-18 05:43:07 +00:00
}
2022-06-18 18:04:24 +00:00
}
2018-05-18 05:43:07 +00:00
2022-06-18 18:04:24 +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>
private static void SetInstructionScreenedValue ( StringInstruction i )
{
switch ( i . PropertyName )
2018-05-18 05:43:07 +00:00
{
2022-06-18 18:04:24 +00:00
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 ;
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 ) :
i . SetScreenedValue ( GameInfo . Strings . movelist ) ; return ;
2018-05-18 05:43:07 +00:00
}
2022-06-18 18:04:24 +00:00
}
2018-05-18 05:43:07 +00:00
2022-06-18 18:04:24 +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="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 ( ) ) ] ) ) ;
/// <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="pk">Object to check.</param>
/// <returns>True if <see cref="pk"/> matches all filters.</returns>
public static bool IsFilterMatchMeta ( IEnumerable < StringInstruction > filters , SlotCache pk )
{
foreach ( var i in filters )
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
foreach ( var filter in BatchFilters . FilterMeta )
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
if ( ! filter . IsMatch ( i . PropertyName ) )
continue ;
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
if ( ! filter . IsFiltered ( pk , i ) )
return false ;
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
break ;
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
return 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
/// <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>
public static bool IsFilterMatch ( IEnumerable < StringInstruction > filters , object obj )
{
foreach ( var cmd in filters )
2018-05-19 02:19:15 +00:00
{
2022-06-18 18:04:24 +00:00
if ( cmd . PropertyName is PROP_TYPENAME )
2018-05-19 02:19:15 +00:00
{
2022-06-18 18:04:24 +00:00
if ( ( obj . GetType ( ) . Name = = cmd . PropertyValue ) ! = cmd . Evaluator )
2018-05-19 02:19:15 +00:00
return false ;
2022-06-18 18:04:24 +00:00
continue ;
}
if ( ! ReflectUtil . HasProperty ( obj , cmd . PropertyName , out var pi ) )
2018-05-19 02:19:15 +00:00
return false ;
2022-06-18 18:04:24 +00:00
try
{
if ( pi . IsValueEqual ( obj , cmd . PropertyValue ) = = cmd . Evaluator )
continue ;
2018-05-19 02:19:15 +00:00
}
2022-06-18 18:04:24 +00:00
// User provided inputs can mismatch the type's required value format, and fail to be compared.
catch ( Exception e )
{
Debug . WriteLine ( $"Unable to compare {cmd.PropertyName} to {cmd.PropertyValue}." ) ;
Debug . WriteLine ( e . Message ) ;
}
return false ;
2018-05-19 02:19:15 +00:00
}
2022-06-18 18:04:24 +00:00
return true ;
}
/// <summary>
/// Tries to modify the <see cref="PKM"/>.
/// </summary>
/// <param name="pk">Object to modify.</param>
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
/// <returns>Result of the attempted modification.</returns>
public static bool TryModify ( PKM pk , IEnumerable < StringInstruction > filters , IEnumerable < StringInstruction > modifications )
{
var result = TryModifyPKM ( pk , filters , modifications ) ;
return result = = ModifyResult . Modified ;
}
/// <summary>
/// Tries to modify the <see cref="BatchInfo"/>.
/// </summary>
/// <param name="pk">Command Filter</param>
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
/// <returns>Result of the attempted modification.</returns>
internal static ModifyResult TryModifyPKM ( PKM pk , IEnumerable < StringInstruction > filters , IEnumerable < StringInstruction > modifications )
{
if ( ! pk . ChecksumValid | | pk . Species = = 0 )
return ModifyResult . Invalid ;
2018-05-19 02:19:15 +00:00
2022-06-18 18:04:24 +00:00
var info = new BatchInfo ( pk ) ;
var pi = Props [ Array . IndexOf ( Types , pk . GetType ( ) ) ] ;
foreach ( var cmd in filters )
2018-05-18 05:43:07 +00:00
{
2022-06-18 18:04:24 +00:00
try
{
if ( ! IsFilterMatch ( cmd , info , pi ) )
return ModifyResult . Filtered ;
}
// Swallow any error because this can be malformed user input.
catch ( Exception ex )
{
Debug . WriteLine ( MsgBEModifyFailCompare + " " + ex . Message , cmd . PropertyName , cmd . PropertyValue ) ;
return ModifyResult . Error ;
}
2018-05-19 02:19:15 +00:00
}
2022-06-18 18:04:24 +00:00
ModifyResult result = ModifyResult . Modified ;
foreach ( var cmd in modifications )
2018-05-19 02:19:15 +00:00
{
2022-06-18 18:04:24 +00:00
try
2018-05-18 05:43:07 +00:00
{
2022-06-18 18:04:24 +00:00
var tmp = SetPKMProperty ( cmd , info , pi ) ;
if ( tmp ! = ModifyResult . Modified )
result = tmp ;
2018-05-18 05:43:07 +00:00
}
2022-06-18 18:04:24 +00:00
// Swallow any error because this can be malformed user input.
catch ( Exception ex )
2018-05-18 05:43:07 +00:00
{
2022-06-18 18:04:24 +00:00
Debug . WriteLine ( MsgBEModifyFail + " " + ex . Message , cmd . PropertyName , cmd . PropertyValue ) ;
2018-05-18 05:43:07 +00:00
}
}
2022-06-18 18:04:24 +00:00
return result ;
}
2018-05-19 02:19:15 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Sets the if the <see cref="BatchInfo"/> 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 , BatchInfo info , IReadOnlyDictionary < string , PropertyInfo > props )
{
var pk = info . Entity ;
if ( cmd . PropertyValue . StartsWith ( CONST_BYTES , StringComparison . Ordinal ) )
return SetByteArrayProperty ( pk , cmd ) ;
2018-05-18 05:43:07 +00:00
2022-06-18 18:04:24 +00:00
if ( cmd . PropertyValue . StartsWith ( CONST_SUGGEST , StringComparison . OrdinalIgnoreCase ) )
return SetSuggestedPKMProperty ( cmd . PropertyName , info , cmd . PropertyValue ) ;
if ( cmd . PropertyValue = = CONST_RAND & & cmd . PropertyName = = nameof ( PKM . Moves ) )
return SetMoves ( pk , info . Legality . GetMoveSet ( true ) ) ;
2018-05-18 05:43:07 +00:00
2022-06-18 18:04:24 +00:00
if ( SetComplexProperty ( pk , cmd ) )
return ModifyResult . Modified ;
2018-05-18 05:43:07 +00:00
2022-06-18 18:04:24 +00:00
if ( ! props . TryGetValue ( cmd . PropertyName , out var pi ) )
return ModifyResult . Error ;
2018-05-19 02:19:15 +00:00
2022-06-18 18:04:24 +00:00
if ( ! pi . CanWrite )
return ModifyResult . Error ;
2018-06-14 01:52:09 +00:00
2022-06-18 18:04:24 +00:00
object val ;
if ( cmd . Random )
val = cmd . RandomValue ;
else if ( cmd . PropertyValue . StartsWith ( CONST_POINTER ) & & props . TryGetValue ( cmd . PropertyValue [ 1. . ] , out var opi ) )
val = opi . GetValue ( pk ) ;
else
val = cmd . PropertyValue ;
2022-03-05 06:35:04 +00:00
2022-06-18 18:04:24 +00:00
ReflectUtil . SetValue ( pi , pk , val ) ;
return ModifyResult . Modified ;
}
2018-05-19 02:19:15 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Checks if the <see cref="BatchInfo"/> 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 filter matches, else false.</returns>
private static bool IsFilterMatch ( StringInstruction cmd , BatchInfo info , IReadOnlyDictionary < string , PropertyInfo > props )
{
var match = BatchFilters . FilterMods . Find ( z = > z . IsMatch ( cmd . PropertyName ) ) ;
if ( match ! = null )
return match . IsFiltered ( info , cmd ) ;
return IsPropertyFiltered ( cmd , info . Entity , props ) ;
}
2018-05-18 05:43:07 +00:00
2022-06-18 18:04:24 +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>
/// <param name="pk">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filter matches, else false.</returns>
private static bool IsFilterMatch ( StringInstruction cmd , PKM pk , IReadOnlyDictionary < string , PropertyInfo > props )
{
var match = BatchFilters . FilterMods . Find ( z = > z . IsMatch ( cmd . PropertyName ) ) ;
if ( match ! = null )
return match . IsFiltered ( pk , cmd ) ;
return IsPropertyFiltered ( cmd , pk , props ) ;
}
2018-05-19 02:19:15 +00:00
2022-06-18 18:04:24 +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>
/// <param name="pk">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache</param>
/// <returns>True if filtered, else false.</returns>
private static bool IsPropertyFiltered ( StringInstruction cmd , PKM pk , IReadOnlyDictionary < string , PropertyInfo > props )
{
if ( ! props . TryGetValue ( cmd . PropertyName , out var pi ) )
return false ;
if ( ! pi . CanRead )
return false ;
return pi . IsValueEqual ( pk , cmd . PropertyValue ) = = cmd . Evaluator ;
}
2018-05-19 02:19:15 +00:00
2022-06-18 18:04:24 +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>
/// <param name="propValue">Suggestion string which starts with <see cref="CONST_SUGGEST"/></param>
private static ModifyResult SetSuggestedPKMProperty ( string name , BatchInfo info , string propValue )
{
var first = BatchMods . SuggestionMods . Find ( z = > z . IsMatch ( name , propValue , info ) ) ;
if ( first ! = null )
return first . Modify ( name , propValue , info ) ;
return ModifyResult . Error ;
}
/// <summary>
/// Sets the <see cref="PKM"/> byte array property to a specified value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="cmd">Modification</param>
private static ModifyResult SetByteArrayProperty ( PKM pk , StringInstruction cmd )
{
switch ( cmd . PropertyName )
2018-05-18 05:43:07 +00:00
{
2022-06-18 18:04:24 +00:00
case nameof ( PKM . Nickname_Trash ) : ConvertToBytes ( cmd . PropertyValue ) . CopyTo ( pk . Nickname_Trash ) ; return ModifyResult . Modified ;
case nameof ( PKM . OT_Trash ) : ConvertToBytes ( cmd . PropertyValue ) . CopyTo ( pk . OT_Trash ) ; return ModifyResult . Modified ;
case nameof ( PKM . HT_Trash ) : ConvertToBytes ( cmd . PropertyValue ) . CopyTo ( pk . HT_Trash ) ; return ModifyResult . Modified ;
default :
return ModifyResult . Error ;
2018-05-20 21:29:13 +00:00
}
2022-06-18 18:04:24 +00:00
static byte [ ] ConvertToBytes ( string str )
2018-05-18 05:43:07 +00:00
{
2022-06-18 18:04:24 +00:00
var arr = str [ CONST_BYTES . Length . . ] . Split ( ',' ) ;
return Array . ConvertAll ( arr , z = > Convert . ToByte ( z . Trim ( ) , 16 ) ) ;
2018-05-18 05:43:07 +00:00
}
2022-06-18 18:04:24 +00:00
}
2018-05-19 02:19:15 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Sets the <see cref="PKM"/> property to a non-specific smart value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="cmd">Modification</param>
/// <returns>True if modified, false if no modifications done.</returns>
private static bool SetComplexProperty ( PKM pk , StringInstruction cmd )
{
if ( cmd . PropertyName . StartsWith ( "IV" , StringComparison . Ordinal ) & & cmd . PropertyValue = = CONST_RAND )
2018-05-18 05:43:07 +00:00
{
2022-06-18 18:04:24 +00:00
SetRandomIVs ( pk , cmd ) ;
2018-05-18 05:43:07 +00:00
return true ;
}
2018-05-19 02:19:15 +00:00
2022-06-18 18:04:24 +00:00
var match = BatchMods . ComplexMods . Find ( z = > z . IsMatch ( cmd . PropertyName , cmd . PropertyValue ) ) ;
if ( match = = null )
return false ;
match . Modify ( pk , cmd ) ;
return true ;
}
2018-05-18 05:43:07 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Sets the <see cref="PKM"/> IV(s) to a random value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="cmd">Modification</param>
private static void SetRandomIVs ( PKM pk , StringInstruction cmd )
{
if ( cmd . PropertyName = = nameof ( PKM . IVs ) )
{
pk . SetRandomIVs ( ) ;
return ;
2018-05-18 05:43:07 +00:00
}
2022-06-18 18:04:24 +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
}
}