Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
using System ;
using System.Text ;
namespace PKHeX.Core ;
2022-08-11 07:46:41 +00:00
/// <summary>
/// Stores parsed data about how a move was learned.
/// </summary>
/// <param name="Info">Info about the game it was learned in.</param>
/// <param name="EvoStage">Evolution stage index within the <see cref="MoveLearnInfo.Environment"/> evolution list it existed in.</param>
/// <param name="Generation">Rough indicator of generation the <see cref="MoveLearnInfo.Environment"/> was.</param>
/// <param name="Expect">Optional value used when the move is not legal, to indicate that another move ID should have been in that move slot instead.</param>
2022-08-27 06:43:36 +00:00
public readonly record struct MoveResult ( MoveLearnInfo Info , byte EvoStage = 0 , byte Generation = 0 , ushort Expect = 0 )
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
{
public bool IsParsed = > this ! = default ;
public bool Valid = > Info . Method . IsValid ( ) ;
internal MoveResult ( LearnMethod method , LearnEnvironment game = 0 ) : this ( new MoveLearnInfo ( method , game ) , Generation : game . GetGeneration ( ) ) { }
public string Summary ( ISpeciesForm current , EvolutionHistory history )
{
var sb = new StringBuilder ( 48 ) ;
Info . Summarize ( sb ) ;
if ( Info . Method . HasExpectedMove ( ) )
{
var name = ParseSettings . MoveStrings [ Expect ] ;
var str = LegalityCheckStrings . LMoveFExpectSingle_0 ;
sb . Append ( ' ' ) . AppendFormat ( str , name ) ;
return sb . ToString ( ) ;
}
var detail = GetDetail ( history ) ;
if ( detail . Species = = 0 )
return sb . ToString ( ) ;
if ( detail . Species = = current . Species & & detail . Form = = current . Form )
return sb . ToString ( ) ;
sb . Append ( ' ' ) . Append ( ParseSettings . SpeciesStrings [ detail . Species ] ) ;
if ( detail . Form ! = current . Form )
sb . Append ( '-' ) . Append ( detail . Form ) ;
return sb . ToString ( ) ;
}
private EvoCriteria GetDetail ( EvolutionHistory history )
{
var evos = Info . Environment . GetEvolutions ( history ) ;
var stage = EvoStage ;
if ( stage > = evos . Length )
return default ;
return evos [ stage ] ;
}
/// <summary> Checks if the Move should be present in a Relearn move pool (assuming Gen6+ origins). </summary>
/// <remarks>Invalid moves that can't be validated should be here, hence the inclusion.</remarks>
public bool ShouldBeInRelearnMoves ( ) = > IsRelearn | | ! Valid ;
public bool IsRelearn = > Info . Method . IsRelearn ( ) ;
public Severity Judgement = > Valid ? Severity . Valid : Severity . Invalid ;
public string Rating = > Judgement . Description ( ) ;
public string Format ( string format , int index , PKM pk , EvolutionHistory history ) = > string . Format ( format , Rating , index , Summary ( pk , history ) ) ;
public static readonly MoveResult Initial = new ( LearnMethod . Initial ) ;
public static readonly MoveResult Relearn = new ( LearnMethod . Relearn ) ;
public static readonly MoveResult Empty = new ( LearnMethod . Empty ) ;
public static readonly MoveResult Duplicate = new ( LearnMethod . Duplicate ) ;
public static readonly MoveResult EmptyInvalid = new ( LearnMethod . EmptyInvalid ) ;
public static readonly MoveResult Sketch = new ( LearnMethod . Sketch ) ;
2022-08-27 06:43:36 +00:00
public static MoveResult Unobtainable ( ushort expect ) = > new ( LearnMethod . UnobtainableExpect ) { Expect = expect } ;
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
public static MoveResult Unobtainable ( ) = > new ( LearnMethod . Unobtainable ) ;
public static bool AllValid ( ReadOnlySpan < MoveResult > span )
{
foreach ( var result in span )
{
if ( ! result . Valid )
return false ;
}
return true ;
}
public static bool AllParsed ( ReadOnlySpan < MoveResult > span )
{
foreach ( var result in span )
{
if ( ! result . IsParsed )
return false ;
}
return true ;
}
}