2022-06-26 06:08:28 +00:00
using System ;
2016-09-04 06:14:05 +00:00
using System.Collections.Generic ;
2023-01-22 04:02:33 +00:00
using System.Text ;
2020-12-29 05:42:54 +00:00
using static PKHeX . Core . Species ;
2016-06-20 04:22:43 +00:00
2022-06-18 18:04:24 +00:00
namespace PKHeX.Core ;
/// <summary>
/// Logic for exporting and importing <see cref="PKM"/> data in Pokémon Showdown's text format.
/// </summary>
public sealed class ShowdownSet : IBattleTemplate
2016-06-20 04:22:43 +00:00
{
2023-12-04 04:13:20 +00:00
private static readonly string [ ] StatNames = [ "HP" , "Atk" , "Def" , "Spe" , "SpA" , "SpD" ] ;
2022-06-26 06:08:28 +00:00
private const string LineSplit = ": " ;
private const string ItemSplit = " @ " ;
2022-06-18 18:04:24 +00:00
private const int MAX_SPECIES = ( int ) MAX_COUNT - 1 ;
2022-08-18 06:50:14 +00:00
internal const string DefaultLanguage = GameLanguage . DefaultLanguage ;
private static readonly GameStrings DefaultStrings = GameInfo . GetStrings ( DefaultLanguage ) ;
2018-07-14 02:13:25 +00:00
2023-12-04 04:13:20 +00:00
private static ReadOnlySpan < ushort > DashedSpecies = >
[
2022-11-25 01:42:17 +00:00
( int ) NidoranF , ( int ) NidoranM ,
( int ) HoOh ,
( int ) Jangmoo , ( int ) Hakamoo , ( int ) Kommoo ,
( int ) TingLu , ( int ) ChienPao , ( int ) WoChien , ( int ) ChiYu ,
2023-12-04 04:13:20 +00:00
] ;
2022-11-25 01:42:17 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
2022-08-27 06:43:36 +00:00
public ushort Species { get ; private set ; }
2016-06-20 04:22:43 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
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 EntityContext Context { get ; private set ; } = RecentTrainerCache . Context ;
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
2022-06-26 06:08:28 +00:00
public string Nickname { get ; private set ; } = string . Empty ;
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
public int Gender { get ; private set ; } = - 1 ;
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
public int HeldItem { get ; private set ; }
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
public int Ability { get ; private set ; } = - 1 ;
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
public int Level { get ; private set ; } = 100 ;
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
public bool Shiny { get ; private set ; }
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
public int Friendship { get ; private set ; } = 255 ;
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
2022-11-25 01:42:17 +00:00
public int Nature { get ; private set ; } = - 1 ;
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
public string FormName { get ; private set ; } = string . Empty ;
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
2022-08-27 06:43:36 +00:00
public byte Form { get ; private set ; }
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
2023-12-04 04:13:20 +00:00
public int [ ] EVs { get ; } = [ 00 , 00 , 00 , 00 , 00 , 00 ] ;
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
2023-12-04 04:13:20 +00:00
public int [ ] IVs { get ; } = [ 31 , 31 , 31 , 31 , 31 , 31 ] ;
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
2022-11-25 01:42:17 +00:00
public int HiddenPowerType { get ; private set ; } = - 1 ;
public MoveType TeraType { get ; private set ; } = MoveType . Any ;
2018-12-29 00:54:01 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
2023-12-04 04:13:20 +00:00
public ushort [ ] Moves { get ; } = [ 0 , 0 , 0 , 0 ] ;
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <inheritdoc/>
2022-11-25 01:42:17 +00:00
public bool CanGigantamax { get ; private set ; }
2019-11-16 22:37:33 +00:00
2022-08-04 00:10:00 +00:00
/// <inheritdoc/>
2022-11-25 01:42:17 +00:00
public byte DynamaxLevel { get ; private set ; } = 10 ;
2022-08-04 00:10:00 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Any lines that failed to be parsed.
/// </summary>
2023-01-22 04:02:33 +00:00
public readonly List < string > InvalidLines = new ( 0 ) ;
2016-06-20 04:22:43 +00:00
2022-06-18 18:04:24 +00:00
private GameStrings Strings { get ; set ; } = DefaultStrings ;
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Loads a new <see cref="ShowdownSet"/> from the input string.
/// </summary>
/// <param name="input">Single-line string which will be split before loading.</param>
2023-01-22 04:02:33 +00:00
public ShowdownSet ( ReadOnlySpan < char > input ) = > LoadLines ( input . EnumerateLines ( ) ) ;
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Loads a new <see cref="ShowdownSet"/> from the input string.
/// </summary>
/// <param name="lines">Enumerable list of lines.</param>
public ShowdownSet ( IEnumerable < string > lines ) = > LoadLines ( lines ) ;
2018-07-14 16:55:22 +00:00
2023-01-22 04:02:33 +00:00
private void LoadLines ( SpanLineEnumerator lines )
{
ParseLines ( lines ) ;
SanitizeResult ( ) ;
}
2022-06-18 18:04:24 +00:00
private void LoadLines ( IEnumerable < string > lines )
{
ParseLines ( lines ) ;
2023-01-22 04:02:33 +00:00
SanitizeResult ( ) ;
}
2016-09-20 05:59:15 +00:00
2023-01-22 04:02:33 +00:00
private void SanitizeResult ( )
{
2022-06-18 18:04:24 +00:00
FormName = ShowdownParsing . SetShowdownFormName ( Species , FormName , Ability ) ;
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
Form = ShowdownParsing . GetFormFromString ( FormName , Strings , Species , Context ) ;
2021-05-19 20:12:18 +00:00
2022-06-18 18:04:24 +00:00
// Handle edge case with fixed-gender forms.
2022-11-25 01:42:17 +00:00
if ( Species is ( int ) Meowstic or ( int ) Indeedee or ( int ) Basculegion or ( int ) Oinkologne )
2022-06-18 18:04:24 +00:00
ReviseGenderedForms ( ) ;
}
2021-12-05 01:56:56 +00:00
2022-06-18 18:04:24 +00:00
private void ReviseGenderedForms ( )
{
if ( Gender = = 1 ) // Recognized with (F)
2021-12-05 01:56:56 +00:00
{
2022-06-18 18:04:24 +00:00
FormName = "F" ;
Form = 1 ;
}
else
{
FormName = Form = = 1 ? "F" : "M" ;
Gender = Form ;
2018-03-11 18:39:58 +00:00
}
2022-06-18 18:04:24 +00:00
}
2018-07-29 19:47:38 +00:00
2022-06-18 18:04:24 +00:00
private const int MaxMoveCount = 4 ;
2019-10-08 01:40:09 +00:00
2023-01-27 03:03:06 +00:00
// Skip lines that are too short or too long.
// Longest line is ~74 (Gen2 EVs)
// Length permitted: 3-80
// The shortest Pokémon name in Japanese is "ニ" (Ni) which is the name for the Pokémon, Nidoran♂ (male Nidoran). It has only one letter.
// We will handle this 1-2 letter edge case only if the line is the first line of the set, in the rare chance we are importing for a non-English language?
private const int MinLength = 3 ;
private const int MaxLength = 80 ;
2023-08-28 06:05:50 +00:00
private static bool IsLengthOutOfRange ( ReadOnlySpan < char > trim ) = > IsLengthOutOfRange ( trim . Length ) ;
private static bool IsLengthOutOfRange ( int length ) = > ( uint ) ( length - MinLength ) > MaxLength - MinLength ;
2023-01-27 05:58:04 +00:00
2023-01-22 04:02:33 +00:00
private void ParseLines ( SpanLineEnumerator lines )
2022-06-18 18:04:24 +00:00
{
int movectr = 0 ;
2023-01-22 04:02:33 +00:00
bool first = true ;
foreach ( var line in lines )
2018-03-11 18:39:58 +00:00
{
2023-01-22 04:02:33 +00:00
ReadOnlySpan < char > trim = line . Trim ( ) ;
2023-01-27 03:03:06 +00:00
if ( IsLengthOutOfRange ( trim ) )
2023-01-22 04:02:33 +00:00
{
2023-01-27 03:03:06 +00:00
// Try for other languages just in case.
if ( first & & trim . Length ! = 0 )
{
ParseFirstLine ( trim ) ;
first = false ;
continue ;
}
2023-01-22 04:02:33 +00:00
InvalidLines . Add ( line . ToString ( ) ) ;
2022-06-18 18:04:24 +00:00
continue ;
2023-01-22 04:02:33 +00:00
}
2022-06-18 18:04:24 +00:00
2023-01-22 04:02:33 +00:00
if ( first )
2016-06-20 04:22:43 +00:00
{
2023-01-22 04:02:33 +00:00
ParseFirstLine ( trim ) ;
first = false ;
2022-06-18 18:04:24 +00:00
continue ;
2019-02-12 05:49:05 +00:00
}
2023-01-22 04:02:33 +00:00
if ( ParseLine ( trim , ref movectr ) )
return ; // End of moves, end of set data
}
}
2016-06-20 04:22:43 +00:00
2023-01-22 04:02:33 +00:00
private void ParseLines ( IEnumerable < string > lines )
{
int movectr = 0 ;
bool first = true ;
foreach ( var line in lines )
{
ReadOnlySpan < char > trim = line . Trim ( ) ;
2023-01-27 03:03:06 +00:00
if ( IsLengthOutOfRange ( trim ) )
2022-06-26 06:08:28 +00:00
{
2023-01-27 03:03:06 +00:00
// Try for other languages just in case.
if ( first & & trim . Length ! = 0 )
{
ParseFirstLine ( trim ) ;
first = false ;
continue ;
}
2023-01-22 04:02:33 +00:00
InvalidLines . Add ( line ) ;
continue ;
2022-06-26 06:08:28 +00:00
}
2023-01-22 04:02:33 +00:00
if ( first )
2022-06-26 06:08:28 +00:00
{
2023-01-22 04:02:33 +00:00
ParseFirstLine ( trim ) ;
first = false ;
continue ;
2022-06-26 06:08:28 +00:00
}
2023-01-22 04:02:33 +00:00
if ( ParseLine ( trim , ref movectr ) )
return ; // End of moves, end of set data
2016-06-20 04:22:43 +00:00
}
2022-06-18 18:04:24 +00:00
}
2023-01-22 04:02:33 +00:00
private bool ParseLine ( ReadOnlySpan < char > line , ref int movectr )
{
if ( line [ 0 ] is '-' or '– ' )
{
var moveString = ParseLineMove ( line ) ;
int move = StringUtil . FindIndexIgnoreCase ( Strings . movelist , moveString ) ;
if ( move < 0 )
InvalidLines . Add ( $"Unknown Move: {moveString}" ) ;
else if ( Array . IndexOf ( Moves , ( ushort ) move ) ! = - 1 )
InvalidLines . Add ( $"Duplicate Move: {moveString}" ) ;
else
Moves [ movectr + + ] = ( ushort ) move ;
return movectr = = MaxMoveCount ;
}
if ( movectr ! = 0 )
return true ;
bool valid ;
var split = line . IndexOf ( LineSplit , StringComparison . Ordinal ) ;
if ( split = = - 1 )
{
valid = ParseSingle ( line ) ; // Nature
}
else
{
var left = line [ . . split ] . Trim ( ) ;
var right = line [ ( split + LineSplit . Length ) . . ] . Trim ( ) ;
valid = ParseEntry ( left , right ) ;
}
if ( ! valid )
InvalidLines . Add ( line . ToString ( ) ) ;
return false ;
}
private bool ParseSingle ( ReadOnlySpan < char > identifier )
2022-06-18 18:04:24 +00:00
{
2022-06-27 03:02:57 +00:00
if ( ! identifier . EndsWith ( "Nature" , StringComparison . OrdinalIgnoreCase ) )
2022-06-18 18:04:24 +00:00
return false ;
2022-06-26 06:08:28 +00:00
var firstSpace = identifier . IndexOf ( ' ' ) ;
if ( firstSpace = = - 1 )
return false ;
var naturestr = identifier [ . . firstSpace ] ;
2022-06-18 18:04:24 +00:00
return ( Nature = StringUtil . FindIndexIgnoreCase ( Strings . natures , naturestr ) ) > = 0 ;
}
2017-06-18 01:37:19 +00:00
2023-01-22 04:02:33 +00:00
private bool ParseEntry ( ReadOnlySpan < char > identifier , ReadOnlySpan < char > value ) = > identifier switch
2022-06-18 18:04:24 +00:00
{
2022-09-07 04:04:40 +00:00
"Ability" = > ( Ability = StringUtil . FindIndexIgnoreCase ( Strings . abilitylist , value ) ) > = 0 ,
"Nature" = > ( Nature = StringUtil . FindIndexIgnoreCase ( Strings . natures , value ) ) > = 0 ,
"Shiny" = > Shiny = StringUtil . IsMatchIgnoreCase ( "Yes" , value ) ,
"Gigantamax" = > CanGigantamax = StringUtil . IsMatchIgnoreCase ( "Yes" , value ) ,
"Friendship" = > ParseFriendship ( value ) ,
"EVs" = > ParseLineEVs ( value ) ,
"IVs" = > ParseLineIVs ( value ) ,
"Level" = > ParseLevel ( value ) ,
"Dynamax Level" = > ParseDynamax ( value ) ,
2022-11-25 01:42:17 +00:00
"Tera Type" = > ParseTeraType ( value ) ,
2022-09-01 05:22:23 +00:00
_ = > false ,
} ;
2023-01-22 04:02:33 +00:00
private bool ParseLevel ( ReadOnlySpan < char > value )
2022-09-01 05:22:23 +00:00
{
if ( ! int . TryParse ( value . Trim ( ) , out var val ) )
return false ;
if ( ( uint ) val is 0 or > 100 )
return false ;
Level = val ;
return true ;
}
2023-01-22 04:02:33 +00:00
private bool ParseFriendship ( ReadOnlySpan < char > value )
2022-09-01 05:22:23 +00:00
{
if ( ! int . TryParse ( value . Trim ( ) , out var val ) )
return false ;
if ( ( uint ) val > byte . MaxValue )
return false ;
Friendship = val ;
return true ;
2022-06-18 18:04:24 +00:00
}
2018-04-26 01:45:31 +00:00
2023-01-22 04:02:33 +00:00
private bool ParseDynamax ( ReadOnlySpan < char > value )
2022-08-04 00:50:11 +00:00
{
2022-08-15 01:54:08 +00:00
Context = EntityContext . Gen8 ;
2022-08-04 00:50:11 +00:00
var val = Util . ToInt32 ( value ) ;
if ( ( uint ) val > 10 )
return false ;
2023-07-09 21:10:40 +00:00
DynamaxLevel = ( byte ) val ;
return true ;
2022-08-04 00:50:11 +00:00
}
2023-01-22 04:02:33 +00:00
private bool ParseTeraType ( ReadOnlySpan < char > value )
2022-11-25 01:42:17 +00:00
{
Context = EntityContext . Gen9 ;
var types = Strings . types ;
var val = StringUtil . FindIndexIgnoreCase ( types , value ) ;
if ( val < 0 )
2023-12-18 03:45:13 +00:00
return false ;
if ( val = = TeraTypeUtil . StellarTypeDisplayStringIndex )
2023-12-18 00:41:15 +00:00
val = TeraTypeUtil . Stellar ;
2022-11-25 01:42:17 +00:00
TeraType = ( MoveType ) val ;
return true ;
}
2022-06-18 18:04:24 +00:00
/// <summary>
/// Gets the standard Text representation of the set details.
/// </summary>
public string Text = > GetText ( ) ;
2018-07-14 16:55:22 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Gets the localized Text representation of the set details.
/// </summary>
/// <param name="lang">2 character language code</param>
2022-08-18 06:50:14 +00:00
public string LocalizedText ( string lang = DefaultLanguage ) = > LocalizedText ( GameLanguage . GetLanguageIndex ( lang ) ) ;
2016-06-20 04:22:43 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Gets the localized Text representation of the set details.
/// </summary>
/// <param name="lang">Language ID</param>
private string LocalizedText ( int lang )
{
var strings = GameInfo . GetStrings ( lang ) ;
return GetText ( strings ) ;
}
2018-07-14 02:13:25 +00:00
2022-06-18 18:04:24 +00:00
private string GetText ( GameStrings ? strings = null )
{
2022-08-30 22:00:45 +00:00
if ( Species is 0 or > MAX_SPECIES )
2022-06-18 18:04:24 +00:00
return string . Empty ;
2019-02-12 05:49:05 +00:00
2022-06-18 18:04:24 +00:00
if ( strings ! = null )
Strings = strings ;
2018-07-29 19:47:38 +00:00
2022-06-18 18:04:24 +00:00
var result = GetSetLines ( ) ;
return string . Join ( Environment . NewLine , result ) ;
}
public List < string > GetSetLines ( )
{
var result = new List < string > ( ) ;
// First Line: Name, Nickname, Gender, Item
var form = ShowdownParsing . GetShowdownFormName ( Species , FormName ) ;
result . Add ( GetStringFirstLine ( form ) ) ;
// IVs
2023-02-26 21:51:58 +00:00
var maxIV = Context . Generation ( ) < 3 ? 15 : 31 ;
var ivs = GetStringStats ( IVs , maxIV ) ;
2022-06-26 06:08:28 +00:00
if ( ivs . Length ! = 0 )
2022-06-18 18:04:24 +00:00
result . Add ( $"IVs: {string.Join(" / ", ivs)}" ) ;
// EVs
2022-06-26 06:08:28 +00:00
var evs = GetStringStats ( EVs , 0 ) ;
if ( evs . Length ! = 0 )
2022-06-18 18:04:24 +00:00
result . Add ( $"EVs: {string.Join(" / ", evs)}" ) ;
// Secondary Stats
if ( ( uint ) Ability < Strings . Ability . Count )
result . Add ( $"Ability: {Strings.Ability[Ability]}" ) ;
2023-12-18 00:41:15 +00:00
if ( Context = = EntityContext . Gen9 & & TeraType ! = MoveType . Any )
{
2024-01-02 23:45:35 +00:00
if ( ( uint ) TeraType < = TeraTypeUtil . MaxType ) // Fairy
2023-12-18 00:41:15 +00:00
result . Add ( $"Tera Type: {Strings.Types[(int)TeraType]}" ) ;
else if ( ( uint ) TeraType = = TeraTypeUtil . Stellar )
result . Add ( $"Tera Type: {Strings.Types[TeraTypeUtil.StellarTypeDisplayStringIndex]}" ) ;
}
2022-06-18 18:04:24 +00:00
if ( Level ! = 100 )
result . Add ( $"Level: {Level}" ) ;
if ( Shiny )
result . Add ( "Shiny: Yes" ) ;
2023-02-26 21:51:58 +00:00
if ( Context = = EntityContext . Gen8 & & DynamaxLevel ! = 10 )
2022-08-04 00:10:00 +00:00
result . Add ( $"Dynamax Level: {DynamaxLevel}" ) ;
2023-02-26 21:51:58 +00:00
if ( Context = = EntityContext . Gen8 & & CanGigantamax )
2022-08-04 00:10:00 +00:00
result . Add ( "Gigantamax: Yes" ) ;
2022-06-18 18:04:24 +00:00
if ( ( uint ) Nature < Strings . Natures . Count )
result . Add ( $"{Strings.Natures[Nature]} Nature" ) ;
// Moves
result . AddRange ( GetStringMoves ( ) ) ;
return result ;
}
private string GetStringFirstLine ( string form )
{
string specForm = Strings . Species [ Species ] ;
if ( form . Length ! = 0 )
specForm + = $"-{form.Replace(" Mega ", " Mega - ")}" ;
else if ( Species = = ( int ) NidoranM )
specForm = specForm . Replace ( "♂" , "-M" ) ;
else if ( Species = = ( int ) NidoranF )
specForm = specForm . Replace ( "♀" , "-F" ) ;
string result = GetSpeciesNickname ( specForm ) ;
// omit genderless or nonspecific
if ( Gender is 1 )
result + = " (F)" ;
else if ( Gender is 0 )
result + = " (M)" ;
if ( HeldItem > 0 )
2017-06-18 20:02:02 +00:00
{
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
var items = Strings . GetItemStrings ( Context ) ;
2022-06-18 18:04:24 +00:00
if ( ( uint ) HeldItem < items . Length )
result + = $" @ {items[HeldItem]}" ;
2017-06-18 20:02:02 +00:00
}
2022-06-18 18:04:24 +00:00
return result ;
}
2018-07-29 19:47:38 +00:00
2022-06-18 18:04:24 +00:00
private string GetSpeciesNickname ( string specForm )
{
if ( Nickname . Length = = 0 )
return specForm ;
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
bool isNicknamed = SpeciesName . IsNicknamedAnyLanguage ( Species , Nickname , Context . Generation ( ) ) ;
2022-06-18 18:04:24 +00:00
if ( ! isNicknamed )
return specForm ;
return $"{Nickname} ({specForm})" ;
}
2023-11-15 03:36:11 +00:00
public static string [ ] GetStringStats < T > ( ReadOnlySpan < T > stats , T ignoreValue ) where T : IEquatable < T >
2022-06-18 18:04:24 +00:00
{
2022-06-26 06:08:28 +00:00
var count = stats . Length - stats . Count ( ignoreValue ) ;
if ( count = = 0 )
2023-12-04 04:13:20 +00:00
return [ ] ;
2022-06-26 06:08:28 +00:00
var result = new string [ count ] ;
int ctr = 0 ;
2022-06-18 18:04:24 +00:00
for ( int i = 0 ; i < stats . Length ; i + + )
2018-07-29 19:47:38 +00:00
{
2022-06-26 06:08:28 +00:00
var statIndex = GetStatIndexStored ( i ) ;
var statValue = stats [ statIndex ] ;
2023-11-15 03:36:11 +00:00
if ( statValue . Equals ( ignoreValue ) )
2022-06-18 18:04:24 +00:00
continue ; // ignore unused stats
2022-06-26 06:08:28 +00:00
var statName = StatNames [ statIndex ] ;
result [ ctr + + ] = $"{statValue} {statName}" ;
2018-07-29 19:47:38 +00:00
}
2022-06-18 18:04:24 +00:00
return result ;
}
2018-07-29 19:47:38 +00:00
2022-06-18 18:04:24 +00:00
private IEnumerable < string > GetStringMoves ( )
{
var moves = Strings . Move ;
2022-08-27 06:43:36 +00:00
foreach ( var move in Moves )
2017-06-18 20:02:02 +00:00
{
2022-08-27 19:53:30 +00:00
if ( move = = 0 | | move > = moves . Count )
2022-06-18 18:04:24 +00:00
continue ;
2023-02-26 21:51:58 +00:00
if ( move ! = ( int ) Move . HiddenPower | | HiddenPowerType = = - 1 )
2016-06-20 04:22:43 +00:00
{
2022-06-26 06:08:28 +00:00
yield return $"- {moves[move]}" ;
2022-06-18 18:04:24 +00:00
continue ;
2016-06-20 04:22:43 +00:00
}
2022-06-18 18:04:24 +00:00
2022-06-26 06:08:28 +00:00
var type = 1 + HiddenPowerType ; // skip Normal
var typeName = Strings . Types [ type ] ;
yield return $"- {moves[move]} [{typeName}]" ;
2017-06-18 20:02:02 +00:00
}
2022-06-18 18:04:24 +00:00
}
2018-07-29 19:47:38 +00:00
2022-06-26 06:08:28 +00:00
private static int GetStatIndexStored ( int displayIndex ) = > displayIndex switch
{
3 = > 4 ,
4 = > 5 ,
5 = > 3 ,
_ = > displayIndex ,
} ;
2022-11-25 01:42:17 +00:00
/// <summary>
/// Forces some properties to indicate the set for future display values.
/// </summary>
/// <param name="pk">PKM to convert to string</param>
public void InterpretAsPreview ( PKM pk )
{
if ( pk . Format < = 2 ) // Nature preview from IVs
Nature = Experience . GetNatureVC ( pk . EXP ) ;
}
2022-06-18 18:04:24 +00:00
/// <summary>
/// Converts the <see cref="PKM"/> data into an importable set format for Pokémon Showdown.
/// </summary>
/// <param name="pk">PKM to convert to string</param>
/// <returns>New ShowdownSet object representing the input <see cref="pk"/></returns>
public ShowdownSet ( PKM pk )
{
2022-08-30 22:00:45 +00:00
if ( pk . Species = = 0 )
2022-06-18 18:04:24 +00:00
return ;
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
Context = pk . Context ;
2022-06-18 18:04:24 +00:00
Nickname = pk . Nickname ;
Species = pk . Species ;
HeldItem = pk . HeldItem ;
Ability = pk . Ability ;
2022-06-26 06:08:28 +00:00
pk . GetEVs ( EVs ) ;
pk . GetIVs ( IVs ) ;
pk . GetMoves ( Moves ) ;
2022-06-18 18:04:24 +00:00
Nature = pk . StatNature ;
2022-06-26 06:08:28 +00:00
Gender = ( uint ) pk . Gender < 2 ? pk . Gender : 2 ;
2022-06-18 18:04:24 +00:00
Friendship = pk . CurrentFriendship ;
2022-06-26 06:08:28 +00:00
Level = pk . CurrentLevel ;
2022-06-18 18:04:24 +00:00
Shiny = pk . IsShiny ;
2024-01-23 03:06:38 +00:00
if ( pk is PK8 g ) // Only set Gigantamax if it is a PK8
{
2022-06-18 18:04:24 +00:00
CanGigantamax = g . CanGigantamax ;
2024-01-23 03:06:38 +00:00
DynamaxLevel = g . DynamaxLevel ;
}
2022-06-18 18:04:24 +00:00
2022-08-27 06:43:36 +00:00
if ( Array . IndexOf ( Moves , ( ushort ) Move . HiddenPower ) ! = - 1 )
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
HiddenPowerType = HiddenPower . GetType ( IVs , Context ) ;
2022-11-25 01:42:17 +00:00
if ( pk is ITeraType t )
TeraType = t . TeraType ;
2022-06-18 18:04:24 +00:00
if ( pk is IHyperTrain h )
2017-06-18 20:02:02 +00:00
{
2022-06-18 18:04:24 +00:00
for ( int i = 0 ; i < 6 ; i + + )
2016-06-20 04:22:43 +00:00
{
2022-06-18 18:04:24 +00:00
if ( h . IsHyperTrained ( i ) )
IVs [ i ] = pk . MaxIV ;
2016-06-20 04:22:43 +00:00
}
}
2017-06-18 20:02:02 +00:00
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
FormName = ShowdownParsing . GetStringFromForm ( Form = pk . Form , Strings , Species , Context ) ;
2022-06-18 18:04:24 +00:00
}
2023-01-22 04:02:33 +00:00
private void ParseFirstLine ( ReadOnlySpan < char > first )
2022-06-18 18:04:24 +00:00
{
2022-06-26 06:08:28 +00:00
int itemSplit = first . IndexOf ( ItemSplit , StringComparison . Ordinal ) ;
if ( itemSplit ! = - 1 )
2018-05-21 01:33:38 +00:00
{
2022-06-26 06:08:28 +00:00
var itemName = first [ ( itemSplit + ItemSplit . Length ) . . ] ;
var speciesName = first [ . . itemSplit ] ;
2020-02-08 01:50:42 +00:00
2022-06-18 18:04:24 +00:00
ParseItemName ( itemName ) ;
2022-06-26 06:08:28 +00:00
ParseFirstLineNoItem ( speciesName ) ;
2016-06-20 04:22:43 +00:00
}
2022-06-18 18:04:24 +00:00
else
2018-06-16 03:30:23 +00:00
{
2022-06-18 18:04:24 +00:00
ParseFirstLineNoItem ( first ) ;
2018-06-16 03:30:23 +00:00
}
2022-06-18 18:04:24 +00:00
}
2018-07-29 19:47:38 +00:00
2023-01-22 04:02:33 +00:00
private void ParseItemName ( ReadOnlySpan < char > itemName )
2022-06-18 18:04:24 +00:00
{
2023-01-22 04:02:33 +00:00
if ( TrySetItem ( Context , itemName ) )
2022-06-18 18:04:24 +00:00
return ;
2023-01-22 04:02:33 +00:00
if ( TrySetItem ( EntityContext . Gen3 , itemName ) )
2022-06-18 18:04:24 +00:00
return ;
2023-01-22 04:02:33 +00:00
if ( TrySetItem ( EntityContext . Gen2 , itemName ) )
2022-06-18 18:04:24 +00:00
return ;
InvalidLines . Add ( $"Unknown Item: {itemName}" ) ;
2023-01-22 04:02:33 +00:00
bool TrySetItem ( EntityContext context , ReadOnlySpan < char > span )
2018-06-16 03:30:23 +00:00
{
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
var items = Strings . GetItemStrings ( context ) ;
2023-01-22 04:02:33 +00:00
int item = StringUtil . FindIndexIgnoreCase ( items , span ) ;
2022-06-18 18:04:24 +00:00
if ( item < 0 )
return false ;
HeldItem = item ;
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
Context = context ;
2022-06-18 18:04:24 +00:00
return true ;
2018-06-16 03:30:23 +00:00
}
2022-06-18 18:04:24 +00:00
}
2018-07-29 19:47:38 +00:00
2023-01-22 04:02:33 +00:00
private void ParseFirstLineNoItem ( ReadOnlySpan < char > line )
2022-06-18 18:04:24 +00:00
{
// Gender Detection
if ( line . EndsWith ( "(M)" , StringComparison . Ordinal ) )
2016-09-20 05:59:15 +00:00
{
2023-02-26 21:51:58 +00:00
line = line [ . . ^ 3 ] . TrimEnd ( ) ;
2022-06-18 18:04:24 +00:00
Gender = 0 ;
}
else if ( line . EndsWith ( "(F)" , StringComparison . Ordinal ) )
{
2023-02-26 21:51:58 +00:00
line = line [ . . ^ 3 ] . TrimEnd ( ) ;
2022-06-18 18:04:24 +00:00
Gender = 1 ;
2018-02-13 01:36:15 +00:00
}
2018-07-29 19:47:38 +00:00
2022-06-18 18:04:24 +00:00
// Nickname Detection
if ( line . IndexOf ( '(' ) ! = - 1 & & line . IndexOf ( ')' ) ! = - 1 )
ParseSpeciesNickname ( line ) ;
else
ParseSpeciesForm ( line ) ;
}
private const string Gmax = "-Gmax" ;
2020-12-29 05:42:54 +00:00
2023-01-22 04:02:33 +00:00
private bool ParseSpeciesForm ( ReadOnlySpan < char > speciesLine )
2022-06-18 18:04:24 +00:00
{
speciesLine = speciesLine . Trim ( ) ;
if ( speciesLine . Length = = 0 )
return false ;
if ( speciesLine . EndsWith ( Gmax , StringComparison . Ordinal ) )
2018-02-13 01:36:15 +00:00
{
2022-06-18 18:04:24 +00:00
CanGigantamax = true ;
speciesLine = speciesLine [ . . ^ Gmax . Length ] ;
}
2021-05-15 06:41:14 +00:00
2022-08-27 06:43:36 +00:00
var speciesIndex = StringUtil . FindIndexIgnoreCase ( Strings . specieslist , speciesLine ) ;
if ( speciesIndex > 0 )
{
// success, nothing else !
Species = ( ushort ) speciesIndex ;
2022-06-18 18:04:24 +00:00
return true ;
2022-08-27 06:43:36 +00:00
}
2020-03-14 19:21:42 +00:00
2022-06-18 18:04:24 +00:00
// Form string present.
2023-01-27 05:58:04 +00:00
int end = speciesLine . IndexOf ( '-' ) ;
2022-06-18 18:04:24 +00:00
if ( end < 0 )
return false ;
2020-03-14 19:21:42 +00:00
2022-08-27 06:43:36 +00:00
speciesIndex = StringUtil . FindIndexIgnoreCase ( Strings . specieslist , speciesLine [ . . end ] ) ;
if ( speciesIndex > 0 )
{
Species = ( ushort ) speciesIndex ;
2023-01-22 04:02:33 +00:00
FormName = speciesLine [ ( end + 1 ) . . ] . ToString ( ) ;
2022-06-18 18:04:24 +00:00
return true ;
2022-08-27 06:43:36 +00:00
}
2017-12-01 02:24:31 +00:00
2022-06-18 18:04:24 +00:00
// failure to parse, check edge cases
foreach ( var e in DashedSpecies )
{
var sn = Strings . Species [ e ] ;
if ( ! speciesLine . StartsWith ( sn . Replace ( "♂" , "-M" ) . Replace ( "♀" , "-F" ) , StringComparison . Ordinal ) )
continue ;
Species = e ;
2023-01-22 04:02:33 +00:00
FormName = speciesLine [ sn . Length . . ] . ToString ( ) ;
2022-06-18 18:04:24 +00:00
return true ;
}
2018-02-13 01:36:15 +00:00
2022-06-18 18:04:24 +00:00
// Version Megas
2023-01-22 04:02:33 +00:00
end = speciesLine [ Math . Max ( 0 , end - 1 ) . . ] . LastIndexOf ( '-' ) ;
2022-06-18 18:04:24 +00:00
if ( end < 0 )
return false ;
2018-02-13 01:36:15 +00:00
2022-08-27 06:43:36 +00:00
speciesIndex = StringUtil . FindIndexIgnoreCase ( Strings . specieslist , speciesLine [ . . end ] ) ;
if ( speciesIndex > 0 )
{
Species = ( ushort ) speciesIndex ;
2023-01-22 04:02:33 +00:00
FormName = speciesLine [ ( end + 1 ) . . ] . ToString ( ) ;
2022-08-27 06:43:36 +00:00
return true ;
}
return false ;
2022-06-18 18:04:24 +00:00
}
2018-02-13 01:36:15 +00:00
2023-01-22 04:02:33 +00:00
private void ParseSpeciesNickname ( ReadOnlySpan < char > line )
2022-06-18 18:04:24 +00:00
{
2023-02-26 21:51:58 +00:00
// Entering into this method requires both ( and ) to be present within the input line.
2022-06-18 18:04:24 +00:00
int index = line . LastIndexOf ( '(' ) ;
2023-02-26 21:51:58 +00:00
ReadOnlySpan < char > species ;
ReadOnlySpan < char > nickname ;
2022-06-18 18:04:24 +00:00
if ( index > 1 ) // parenthesis value after: Nickname (Species), correct.
{
2023-02-26 21:51:58 +00:00
nickname = line [ . . index ] . TrimEnd ( ) ;
species = line [ ( index + 1 ) . . ] ;
if ( species . Length > 0 & & species [ ^ 1 ] = = ')' )
species = species [ . . ^ 1 ] ;
2016-09-20 05:59:15 +00:00
}
2022-06-18 18:04:24 +00:00
else // parenthesis value before: (Species) Nickname, incorrect
2016-09-20 05:59:15 +00:00
{
2022-06-18 18:04:24 +00:00
int start = index + 1 ;
2023-02-26 21:51:58 +00:00
int end = line . LastIndexOf ( ')' ) ;
2022-06-18 18:04:24 +00:00
var tmp = line [ start . . end ] ;
if ( end < line . Length - 2 )
2016-09-20 05:59:15 +00:00
{
2023-02-26 21:51:58 +00:00
nickname = line [ ( end + 2 ) . . ] ;
species = tmp ;
2016-09-20 05:59:15 +00:00
}
2022-06-18 18:04:24 +00:00
else // (Species), or garbage
2016-09-20 05:59:15 +00:00
{
2023-02-26 21:51:58 +00:00
species = tmp ;
2023-12-04 04:13:20 +00:00
nickname = [ ] ;
2016-09-20 05:59:15 +00:00
}
}
2018-07-29 19:47:38 +00:00
2022-06-18 18:04:24 +00:00
if ( ParseSpeciesForm ( species ) )
2023-01-22 04:02:33 +00:00
Nickname = nickname . ToString ( ) ;
2022-06-18 18:04:24 +00:00
else if ( ParseSpeciesForm ( nickname ) )
2023-01-22 04:02:33 +00:00
Nickname = species . ToString ( ) ;
2022-06-18 18:04:24 +00:00
}
2016-09-20 05:59:15 +00:00
2023-01-22 04:02:33 +00:00
private ReadOnlySpan < char > ParseLineMove ( ReadOnlySpan < char > line )
2022-06-18 18:04:24 +00:00
{
2022-06-27 03:02:57 +00:00
var startSearch = line [ 1 ] = = ' ' ? 2 : 1 ;
var option = line . IndexOf ( '/' ) ;
line = option ! = - 1 ? line [ startSearch . . option ] : line [ startSearch . . ] ;
2018-07-29 19:47:38 +00:00
2023-01-22 04:02:33 +00:00
var moveString = line . Trim ( ) ;
2022-06-27 03:02:57 +00:00
var hiddenPowerName = Strings . Move [ ( int ) Move . HiddenPower ] ;
if ( ! moveString . StartsWith ( hiddenPowerName , StringComparison . OrdinalIgnoreCase ) )
2022-06-18 18:04:24 +00:00
return moveString ; // regular move
2018-07-14 02:13:25 +00:00
2022-06-27 03:02:57 +00:00
if ( moveString . Length = = hiddenPowerName . Length )
2022-06-18 18:04:24 +00:00
return hiddenPowerName ;
// Defined Hidden Power
2023-02-26 21:51:58 +00:00
var type = GetHiddenPowerType ( moveString [ ( hiddenPowerName . Length + 1 ) . . ] ) ;
2023-12-18 00:41:15 +00:00
var types = Strings . types . AsSpan ( 1 , HiddenPower . TypeCount ) ;
int hpVal = StringUtil . FindIndexIgnoreCase ( types , type ) ; // Get HP Type
2023-02-26 21:51:58 +00:00
if ( hpVal = = - 1 )
return hiddenPowerName ;
2018-07-29 19:47:38 +00:00
2022-06-18 18:04:24 +00:00
HiddenPowerType = hpVal ;
if ( ! Array . TrueForAll ( IVs , z = > z = = 31 ) )
2016-09-20 05:59:15 +00:00
{
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
if ( ! HiddenPower . SetIVsForType ( hpVal , IVs , Context ) )
2022-06-18 18:04:24 +00:00
InvalidLines . Add ( $"Invalid IVs for Hidden Power Type: {type}" ) ;
}
else if ( hpVal > = 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
HiddenPower . SetIVs ( hpVal , IVs , Context ) ; // Alter IVs
2016-09-20 05:59:15 +00:00
}
2022-06-18 18:04:24 +00:00
else
{
InvalidLines . Add ( $"Invalid Hidden Power Type: {type}" ) ;
}
2022-06-27 03:02:57 +00:00
return hiddenPowerName ;
2022-06-18 18:04:24 +00:00
}
2018-07-29 19:47:38 +00:00
2023-02-26 21:51:58 +00:00
private static ReadOnlySpan < char > GetHiddenPowerType ( ReadOnlySpan < char > line )
{
var type = line . Trim ( ) ;
if ( type . Length = = 0 )
return type ;
if ( type [ 0 ] = = '(' & & type [ ^ 1 ] = = ')' )
2024-01-02 23:45:35 +00:00
return type [ 1. . ^ 1 ] . Trim ( ) ;
2023-02-26 21:51:58 +00:00
if ( type [ 0 ] = = '[' & & type [ ^ 1 ] = = ']' )
return type [ 1. . ^ 1 ] . Trim ( ) ;
return type ;
}
2023-01-22 04:02:33 +00:00
private bool ParseLineEVs ( ReadOnlySpan < char > line )
2022-06-18 18:04:24 +00:00
{
2023-01-22 04:02:33 +00:00
int start = 0 ;
while ( true )
2016-09-20 05:59:15 +00:00
{
2023-01-22 04:02:33 +00:00
var chunk = line [ start . . ] ;
var separator = chunk . IndexOf ( '/' ) ;
var len = separator = = - 1 ? chunk . Length : separator ;
var tuple = chunk [ . . len ] . Trim ( ) ;
if ( ! AbsorbValue ( tuple ) )
InvalidLines . Add ( $"Invalid EV tuple: {tuple}" ) ;
if ( separator = = - 1 )
break ; // no more stats
start + = separator + 1 ;
2016-09-20 05:59:15 +00:00
}
2022-09-01 05:22:23 +00:00
return true ;
2023-01-22 04:02:33 +00:00
bool AbsorbValue ( ReadOnlySpan < char > text )
{
var space = text . IndexOf ( ' ' ) ;
if ( space = = - 1 )
return false ;
var stat = text [ ( space + 1 ) . . ] . Trim ( ) ;
var statIndex = StringUtil . FindIndexIgnoreCase ( StatNames , stat ) ;
if ( statIndex = = - 1 )
return false ;
var value = text [ . . space ] . Trim ( ) ;
if ( ! ushort . TryParse ( value , out var statValue ) )
return false ;
EVs [ statIndex ] = statValue ;
return true ;
}
2022-06-18 18:04:24 +00:00
}
2018-07-29 19:47:38 +00:00
2023-01-22 04:02:33 +00:00
private bool ParseLineIVs ( ReadOnlySpan < char > line )
2022-06-18 18:04:24 +00:00
{
2023-01-22 04:02:33 +00:00
int start = 0 ;
while ( true )
2022-05-14 15:28:13 +00:00
{
2023-01-22 04:02:33 +00:00
var chunk = line [ start . . ] ;
var separator = chunk . IndexOf ( '/' ) ;
var len = separator = = - 1 ? chunk . Length : separator ;
var tuple = chunk [ . . len ] . Trim ( ) ;
if ( ! AbsorbValue ( tuple ) )
InvalidLines . Add ( $"Invalid IV tuple: {tuple}" ) ;
if ( separator = = - 1 )
break ; // no more stats
start + = separator + 1 ;
2022-05-14 15:28:13 +00:00
}
2022-09-01 05:22:23 +00:00
return true ;
2023-01-22 04:02:33 +00:00
bool AbsorbValue ( ReadOnlySpan < char > text )
{
var space = text . IndexOf ( ' ' ) ;
if ( space = = - 1 )
return false ;
var stat = text [ ( space + 1 ) . . ] . Trim ( ) ;
var statIndex = StringUtil . FindIndexIgnoreCase ( StatNames , stat ) ;
if ( statIndex = = - 1 )
return false ;
var value = text [ . . space ] . Trim ( ) ;
if ( ! byte . TryParse ( value , out var statValue ) )
return false ;
IVs [ statIndex ] = statValue ;
return true ;
}
2022-06-18 18:04:24 +00:00
}
2016-06-20 04:22:43 +00:00
}