2016-06-20 04:22:43 +00:00
using System ;
2016-09-04 06:14:05 +00:00
using System.Collections.Generic ;
2016-06-20 04:22:43 +00:00
using System.Linq ;
2017-01-08 07:54:09 +00:00
namespace PKHeX.Core
2016-06-20 04:22:43 +00:00
{
2017-10-24 06:12:58 +00:00
/// <summary>
/// Logic for exporting and importing <see cref="PKM"/> data in Pokémon Showdown's text format.
/// </summary>
2019-11-16 22:37:33 +00:00
public sealed class ShowdownSet : IGigantamax
2016-06-20 04:22:43 +00:00
{
2017-12-27 00:29:35 +00:00
private static readonly string [ ] genders = { "M" , "F" , "" } ;
2018-01-30 01:20:12 +00:00
private static readonly string [ ] genderForms = { "" , "F" , "" } ;
2018-07-14 02:13:25 +00:00
private static readonly string [ ] StatNames = { "HP" , "Atk" , "Def" , "SpA" , "SpD" , "Spe" } ;
2018-07-14 16:55:22 +00:00
private static readonly string [ ] Splitters = { "\r\n" , "\n" } ;
2018-07-22 02:20:11 +00:00
private static readonly string [ ] StatSplitters = { " / " , " " } ;
2018-07-14 16:55:22 +00:00
private static readonly string [ ] LineSplit = { ": " } ;
2018-07-22 02:20:11 +00:00
private static readonly string [ ] ItemSplit = { " @ " } ;
2019-02-08 05:40:20 +00:00
private static readonly char [ ] ParenJunk = { '[' , ']' , '(' , ')' } ;
2019-02-12 05:49:05 +00:00
private static readonly ushort [ ] DashedSpecies = { 782 , 783 , 784 , 250 , 032 , 029 } ; // Kommo-o, Ho-Oh, Nidoran-M, Nidoran-F
private const int MAX_SPECIES = ( int ) Core . Species . MAX_COUNT - 1 ;
2020-02-08 01:50:42 +00:00
private static readonly GameStrings DefaultStrings = GameInfo . GetStrings ( GameLanguage . DefaultLanguage ) ;
2018-07-14 02:13:25 +00:00
2018-07-14 16:55:22 +00:00
/// <summary>
/// <see cref="PKM.Species"/> of the Set entity.
/// </summary>
public int Species { get ; private set ; } = - 1 ;
2016-06-20 04:22:43 +00:00
2018-07-14 16:55:22 +00:00
/// <summary>
/// <see cref="PKM.Format"/> of the Set entity it is specific to.
/// </summary>
2019-02-12 05:49:05 +00:00
public int Format { get ; private set ; } = PKMConverter . Format ;
2018-07-14 16:55:22 +00:00
/// <summary>
/// <see cref="PKM.Nickname"/> of the Set entity.
/// </summary>
2019-02-12 05:49:05 +00:00
public string Nickname { get ; set ; } = string . Empty ;
2018-07-14 16:55:22 +00:00
/// <summary>
/// <see cref="PKM.Gender"/> name of the Set entity.
/// </summary>
2019-02-12 05:49:05 +00:00
public string Gender { get ; private set ; } = string . Empty ;
2018-07-14 16:55:22 +00:00
/// <summary>
/// <see cref="PKM.HeldItem"/> of the Set entity.
/// </summary>
2017-06-18 01:37:19 +00:00
public int HeldItem { get ; private set ; }
2018-07-14 16:55:22 +00:00
/// <summary>
/// <see cref="PKM.Ability"/> of the Set entity.
/// </summary>
2018-04-26 01:45:31 +00:00
public int Ability { get ; private set ; } = - 1 ;
2018-07-14 16:55:22 +00:00
/// <summary>
/// <see cref="PKM.CurrentLevel"/> of the Set entity.
/// </summary>
2016-09-20 05:59:15 +00:00
public int Level { get ; private set ; } = 100 ;
2018-07-14 16:55:22 +00:00
/// <summary>
/// <see cref="PKM.CurrentLevel"/> of the Set entity.
/// </summary>
2016-09-20 05:59:15 +00:00
public bool Shiny { get ; private set ; }
2018-07-14 16:55:22 +00:00
/// <summary>
/// <see cref="PKM.CurrentFriendship"/> of the Set entity.
/// </summary>
2016-09-20 05:59:15 +00:00
public int Friendship { get ; private set ; } = 255 ;
2018-07-14 16:55:22 +00:00
/// <summary>
2019-11-16 01:34:18 +00:00
/// <see cref="PKM.StatNature"/> of the Set entity.
2018-07-14 16:55:22 +00:00
/// </summary>
2019-02-12 05:49:05 +00:00
public int Nature { get ; set ; } = - 1 ;
2018-07-14 16:55:22 +00:00
/// <summary>
2019-02-11 05:31:27 +00:00
/// <see cref="PKM.AltForm"/> name of the Set entity, stored in PKHeX style (instead of Showdown's)
2018-07-14 16:55:22 +00:00
/// </summary>
2019-02-12 05:49:05 +00:00
public string Form { get ; private set ; } = string . Empty ;
2018-07-14 16:55:22 +00:00
/// <summary>
/// <see cref="PKM.AltForm"/> of the Set entity.
/// </summary>
2018-01-30 01:20:12 +00:00
public int FormIndex { get ; private set ; }
2018-07-14 16:55:22 +00:00
/// <summary>
/// <see cref="PKM.EVs"/> of the Set entity.
/// </summary>
2016-09-20 05:59:15 +00:00
public int [ ] EVs { get ; private set ; } = { 00 , 00 , 00 , 00 , 00 , 00 } ;
2018-07-14 16:55:22 +00:00
/// <summary>
/// <see cref="PKM.IVs"/> of the Set entity.
/// </summary>
2016-09-20 05:59:15 +00:00
public int [ ] IVs { get ; private set ; } = { 31 , 31 , 31 , 31 , 31 , 31 } ;
2018-07-14 16:55:22 +00:00
2018-12-29 00:54:01 +00:00
/// <summary>
/// <see cref="PKM.HPType"/> of the Set entity.
/// </summary>
public int HiddenPowerType { get ; set ; } = - 1 ;
2018-07-14 16:55:22 +00:00
/// <summary>
/// <see cref="PKM.Moves"/> of the Set entity.
/// </summary>
public int [ ] Moves { get ; } = { 0 , 0 , 0 , 0 } ;
2019-11-16 22:37:33 +00:00
/// <summary>
/// <see cref="IGigantamax.CanGigantamax"/> of the Set entity.
/// </summary>
public bool CanGigantamax { get ; set ; }
2018-07-14 16:55:22 +00:00
/// <summary>
/// Any lines that failed to be parsed.
/// </summary>
2016-09-10 02:13:48 +00:00
public readonly List < string > InvalidLines = new List < string > ( ) ;
2016-06-20 04:22:43 +00:00
2018-07-14 16:55:22 +00:00
private GameStrings Strings { get ; set ; } = DefaultStrings ;
2016-09-20 05:59:15 +00:00
private int [ ] IVsSpeedFirst = > new [ ] { IVs [ 0 ] , IVs [ 1 ] , IVs [ 2 ] , IVs [ 5 ] , IVs [ 3 ] , IVs [ 4 ] } ;
private int [ ] IVsSpeedLast = > new [ ] { IVs [ 0 ] , IVs [ 1 ] , IVs [ 2 ] , IVs [ 4 ] , IVs [ 5 ] , IVs [ 3 ] } ;
private int [ ] EVsSpeedFirst = > new [ ] { EVs [ 0 ] , EVs [ 1 ] , EVs [ 2 ] , EVs [ 5 ] , EVs [ 3 ] , EVs [ 4 ] } ;
private int [ ] EVsSpeedLast = > new [ ] { EVs [ 0 ] , EVs [ 1 ] , EVs [ 2 ] , EVs [ 4 ] , EVs [ 5 ] , EVs [ 3 ] } ;
2016-09-18 05:10:27 +00:00
2018-07-14 16:55:22 +00:00
/// <summary>
2019-02-12 05:49:05 +00:00
/// Loads a new blank <see cref="ShowdownSet"/>.
/// </summary>
public ShowdownSet ( ) { }
/// <summary>
/// Loads a new <see cref="ShowdownSet"/> from the input string.
2018-07-14 16:55:22 +00:00
/// </summary>
/// <param name="input">Single-line string which will be split before loading.</param>
2019-02-12 05:49:05 +00:00
public ShowdownSet ( string input )
2016-06-20 04:22:43 +00:00
{
2019-02-12 05:49:05 +00:00
var lines = input . Split ( Splitters , StringSplitOptions . None ) ;
2018-03-11 18:39:58 +00:00
LoadLines ( lines ) ;
}
2018-07-14 16:55:22 +00:00
/// <summary>
2019-02-12 05:49:05 +00:00
/// Loads a new <see cref="ShowdownSet"/> from the input string.
2018-07-14 16:55:22 +00:00
/// </summary>
/// <param name="lines">Enumerable list of lines.</param>
2018-03-11 18:39:58 +00:00
public ShowdownSet ( IEnumerable < string > lines )
{
LoadLines ( lines ) ;
}
2018-07-14 16:55:22 +00:00
2018-03-11 18:39:58 +00:00
private void LoadLines ( IEnumerable < string > lines )
{
2019-02-08 05:40:20 +00:00
lines = lines . Select ( z = > z . Replace ( '\'' , '’ ' ) . Replace ( '– ' , '-' ) . Trim ( ) ) ; // Sanitize apostrophes & dashes
2018-03-11 18:39:58 +00:00
lines = lines . Where ( z = > z . Length > 2 ) ;
2016-06-20 04:22:43 +00:00
2018-03-11 18:39:58 +00:00
ParseLines ( lines ) ;
2016-09-20 05:59:15 +00:00
2018-03-11 18:39:58 +00:00
Form = ConvertFormFromShowdown ( Form , Species , Ability ) ;
// Set Form
2019-02-12 05:49:05 +00:00
if ( Form . Length = = 0 )
{
FormIndex = 0 ;
return ;
}
2019-09-19 02:58:23 +00:00
string [ ] formStrings = FormConverter . GetFormList ( Species , Strings . Types , Strings . forms , genderForms , Format ) ;
2019-02-12 05:49:05 +00:00
FormIndex = Math . Max ( 0 , Array . FindIndex ( formStrings , z = > z . Contains ( Form ) ) ) ;
2018-03-11 18:39:58 +00:00
}
2018-07-29 19:47:38 +00:00
2019-10-08 01:40:09 +00:00
private const int MaxMoveCount = 4 ;
2018-03-11 18:39:58 +00:00
private void ParseLines ( IEnumerable < string > lines )
{
2019-10-26 19:33:58 +00:00
// ReSharper disable once GenericEnumeratorNotDisposed
2019-10-08 01:40:09 +00:00
using var e = lines . GetEnumerator ( ) ;
if ( ! e . MoveNext ( ) )
return ;
ParseFirstLine ( e . Current ) ;
int movectr = 0 ;
while ( e . MoveNext ( ) )
2016-06-20 04:22:43 +00:00
{
2019-10-08 01:40:09 +00:00
var line = e . Current ;
if ( string . IsNullOrWhiteSpace ( line ) )
continue ;
2019-02-12 05:49:05 +00:00
2019-10-08 01:40:09 +00:00
if ( line [ 0 ] = = '-' )
2016-06-20 04:22:43 +00:00
{
2019-10-08 01:40:09 +00:00
string moveString = ParseLineMove ( line ) ;
2020-01-02 23:49:35 +00:00
int move = StringUtil . FindIndexIgnoreCase ( Strings . movelist , moveString ) ;
2019-10-08 01:40:09 +00:00
if ( move < 0 )
InvalidLines . Add ( $"Unknown Move: {moveString}" ) ;
2020-03-04 02:23:00 +00:00
else if ( Moves . Contains ( move ) )
InvalidLines . Add ( $"Duplicate Move: {moveString}" ) ;
2019-10-08 01:40:09 +00:00
else
Moves [ movectr + + ] = move ;
if ( movectr = = MaxMoveCount )
return ; // End of moves, end of set data
continue ;
2016-06-20 04:22:43 +00:00
}
2019-10-08 01:40:09 +00:00
2019-11-17 00:34:28 +00:00
if ( movectr ! = 0 )
break ;
2019-10-08 01:40:09 +00:00
var split = line . Split ( LineSplit , StringSplitOptions . None ) ;
var valid = split . Length = = 1
? ParseSingle ( line ) // Nature
: ParseEntry ( split [ 0 ] . Trim ( ) , split [ 1 ] . Trim ( ) ) ;
if ( ! valid )
InvalidLines . Add ( line ) ;
2019-02-12 05:49:05 +00:00
}
}
2016-06-20 04:22:43 +00:00
2019-02-12 05:49:05 +00:00
private bool ParseSingle ( string identifier )
{
2019-10-08 01:40:09 +00:00
if ( ! identifier . EndsWith ( "Nature" ) )
return false ;
var naturestr = identifier . Split ( ' ' ) [ 0 ] . Trim ( ) ;
2020-01-02 23:49:35 +00:00
return ( Nature = StringUtil . FindIndexIgnoreCase ( Strings . natures , naturestr ) ) > = 0 ;
2016-06-20 04:22:43 +00:00
}
2017-06-18 01:37:19 +00:00
2019-02-12 05:49:05 +00:00
private bool ParseEntry ( string identifier , string value )
2018-04-26 01:45:31 +00:00
{
2019-02-12 05:49:05 +00:00
switch ( identifier )
2018-04-26 01:45:31 +00:00
{
2020-01-02 23:49:35 +00:00
case "Trait" : case "Ability" : return ( Ability = StringUtil . FindIndexIgnoreCase ( Strings . abilitylist , value ) ) > = 0 ;
2019-02-12 05:49:05 +00:00
case "Shiny" : return Shiny = value . Trim ( ) = = "Yes" ;
2020-01-02 23:49:35 +00:00
case "Nature" : return ( Nature = StringUtil . FindIndexIgnoreCase ( Strings . natures , value ) ) > = 0 ;
2019-02-12 05:49:05 +00:00
case "EV" : case "EVs" : ParseLineEVs ( value ) ; return true ;
case "IV" : case "IVs" : ParseLineIVs ( value ) ; return true ;
2018-06-16 03:30:23 +00:00
case "Level" :
{
2019-02-12 05:49:05 +00:00
if ( ! int . TryParse ( value . Trim ( ) , out int val ) )
2018-06-16 03:30:23 +00:00
return false ;
Level = val ;
return true ;
}
case "Happiness" : case "Friendship" :
{
2019-02-12 05:49:05 +00:00
if ( ! int . TryParse ( value . Trim ( ) , out int val ) )
2018-06-16 03:30:23 +00:00
return false ;
Friendship = val ;
return true ;
}
default :
return false ;
2018-04-26 01:45:31 +00:00
}
}
2018-07-14 16:55:22 +00:00
/// <summary>
/// Gets the standard Text representation of the set details.
/// </summary>
2017-06-18 01:37:19 +00:00
public string Text = > GetText ( ) ;
2018-07-14 16:55:22 +00:00
/// <summary>
/// Gets the localized Text representation of the set details.
/// </summary>
/// <param name="lang">2 character language code</param>
2019-06-22 17:50:32 +00:00
public string LocalizedText ( string lang ) = > LocalizedText ( GameLanguage . GetLanguageIndex ( lang ) ) ;
2018-07-14 16:55:22 +00:00
/// <summary>
/// Gets the localized Text representation of the set details.
/// </summary>
/// <param name="lang">Language ID</param>
2018-07-29 19:47:38 +00:00
private string LocalizedText ( int lang )
{
var strings = GameInfo . GetStrings ( lang ) ;
return GetText ( strings ) ;
}
2018-07-14 16:55:22 +00:00
PKHeX.Core Nullable cleanup (#2401)
* Handle some nullable cases
Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data)
Make some classes have explicit constructors instead of { } initialization
* Handle bits more obviously without null
* Make SaveFile.BAK explicitly readonly again
* merge constructor methods to have readonly fields
* Inline some properties
* More nullable handling
* Rearrange box actions
define straightforward classes to not have any null properties
* Make extrabyte reference array immutable
* Move tooltip creation to designer
* Rearrange some logic to reduce nesting
* Cache generated fonts
* Split mystery gift album purpose
* Handle more tooltips
* Disallow null setters
* Don't capture RNG object, only type enum
* Unify learnset objects
Now have readonly properties which are never null
don't new() empty learnsets (>800 Learnset objects no longer created,
total of 2400 objects since we also new() a move & level array)
optimize g1/2 reader for early abort case
* Access rewrite
Initialize blocks in a separate object, and get via that object
removes a couple hundred "might be null" warnings since blocks are now readonly getters
some block references have been relocated, but interfaces should expose all that's needed
put HoF6 controls in a groupbox, and disable
* Readonly personal data
* IVs non nullable for mystery gift
* Explicitly initialize forced encounter moves
* Make shadow objects readonly & non-null
Put murkrow fix in binary data resource, instead of on startup
* Assign dex form fetch on constructor
Fixes legality parsing edge cases
also handle cxd parse for valid; exit before exception is thrown in FrameGenerator
* Remove unnecessary null checks
* Keep empty value until init
SetPouch sets the value to an actual one during load, but whatever
* Readonly team lock data
* Readonly locks
Put locked encounters at bottom (favor unlocked)
* Mail readonly data / offset
Rearrange some call flow and pass defaults
Add fake classes for SaveDataEditor mocking
Always party size, no need to check twice in stat editor
use a fake save file as initial data for savedata editor, and for
gamedata (wow i found a usage)
constrain eventwork editor to struct variable types (uint, int, etc),
thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
private string GetText ( GameStrings ? strings = null )
2016-06-20 04:22:43 +00:00
{
2018-07-13 00:09:39 +00:00
if ( Species < = 0 | | Species > MAX_SPECIES )
2017-11-18 06:19:23 +00:00
return string . Empty ;
2016-06-20 04:22:43 +00:00
2018-07-14 02:13:25 +00:00
if ( strings ! = null )
Strings = strings ;
2019-02-12 05:49:05 +00:00
var result = GetSetLines ( ) ;
return string . Join ( Environment . NewLine , result ) ;
}
public List < string > GetSetLines ( )
{
2017-06-18 20:02:02 +00:00
var result = new List < string > ( ) ;
// First Line: Name, Nickname, Gender, Item
2020-03-14 00:43:21 +00:00
var form = ConvertFormToShowdown ( Form , Species ) ;
2017-06-18 20:02:02 +00:00
result . Add ( GetStringFirstLine ( form ) ) ;
// IVs
2019-02-12 05:49:05 +00:00
var ivs = GetStringStats ( IVsSpeedLast , Format < 3 ? 15 : 31 ) ;
if ( ivs . Count > 0 )
result . Add ( $"IVs: {string.Join(" / ", ivs)}" ) ;
2017-06-18 20:02:02 +00:00
// EVs
2019-02-12 05:49:05 +00:00
var evs = GetStringStats ( EVsSpeedLast , 0 ) ;
if ( evs . Count > 0 )
result . Add ( $"EVs: {string.Join(" / ", evs)}" ) ;
2017-06-18 20:02:02 +00:00
// Secondary Stats
2018-12-26 06:59:52 +00:00
if ( ( uint ) Ability < Strings . Ability . Count )
2018-07-14 02:13:25 +00:00
result . Add ( $"Ability: {Strings.Ability[Ability]}" ) ;
2018-05-12 04:44:09 +00:00
if ( Level ! = 100 )
result . Add ( $"Level: {Level}" ) ;
2017-06-18 20:02:02 +00:00
if ( Shiny )
result . Add ( "Shiny: Yes" ) ;
2018-12-26 06:59:52 +00:00
if ( ( uint ) Nature < Strings . Natures . Count )
2018-07-14 02:13:25 +00:00
result . Add ( $"{Strings.Natures[Nature]} Nature" ) ;
2017-06-18 20:02:02 +00:00
// Moves
result . AddRange ( GetStringMoves ( ) ) ;
2019-02-12 05:49:05 +00:00
return result ;
2017-06-18 20:02:02 +00:00
}
2018-07-29 19:47:38 +00:00
2017-06-18 20:02:02 +00:00
private string GetStringFirstLine ( string form )
{
2018-07-14 02:13:25 +00:00
string specForm = Strings . Species [ Species ] ;
2019-02-12 05:49:05 +00:00
if ( form . Length ! = 0 )
2017-09-30 05:58:25 +00:00
specForm + = $"-{form.Replace(" Mega ", " Mega - ")}" ;
2020-02-08 01:42:28 +00:00
else if ( Species = = ( int ) Core . Species . NidoranM )
specForm = specForm . Replace ( "♂" , "-M" ) ;
else if ( Species = = ( int ) Core . Species . NidoranF )
specForm = specForm . Replace ( "♀" , "-F" ) ;
2016-10-04 02:24:46 +00:00
2020-03-14 00:43:21 +00:00
if ( CanGigantamax )
specForm + = Gmax ;
2018-07-29 19:47:38 +00:00
string result = GetSpeciesNickname ( specForm ) ;
2019-02-12 05:49:05 +00:00
if ( Gender . Length ! = 0 )
2016-09-18 05:10:27 +00:00
result + = $" ({Gender})" ;
2018-04-26 01:45:31 +00:00
if ( HeldItem > 0 )
{
2018-07-14 02:13:25 +00:00
var items = Strings . GetItemStrings ( Format ) ;
2018-12-26 06:59:52 +00:00
if ( ( uint ) HeldItem < items . Count )
2018-07-14 02:13:25 +00:00
result + = $" @ {items[HeldItem]}" ;
2018-04-26 01:45:31 +00:00
}
2017-06-18 20:02:02 +00:00
return result ;
}
2018-07-29 19:47:38 +00:00
private string GetSpeciesNickname ( string specForm )
{
2019-02-12 05:49:05 +00:00
if ( Nickname . Length = = 0 )
2018-07-29 19:47:38 +00:00
return specForm ;
2020-02-08 01:42:16 +00:00
bool isNicknamed = SpeciesName . IsNicknamedAnyLanguage ( Species , Nickname , Format ) ;
if ( ! isNicknamed )
2018-07-29 19:47:38 +00:00
return specForm ;
return $"{Nickname} ({specForm})" ;
}
2019-02-12 05:49:05 +00:00
private static IList < string > GetStringStats ( int [ ] stats , int ignore )
2017-06-18 20:02:02 +00:00
{
2019-02-12 05:49:05 +00:00
var result = new List < string > ( ) ;
2017-06-18 20:02:02 +00:00
for ( int i = 0 ; i < stats . Length ; i + + )
2016-06-20 04:22:43 +00:00
{
2019-02-12 05:49:05 +00:00
if ( stats [ i ] = = ignore )
continue ; // ignore unused stats
result . Add ( $"{stats[i]} {StatNames[i]}" ) ;
2016-06-20 04:22:43 +00:00
}
2019-02-12 05:49:05 +00:00
return result ;
2017-06-18 20:02:02 +00:00
}
2018-07-29 19:47:38 +00:00
2017-06-18 20:02:02 +00:00
private IEnumerable < string > GetStringMoves ( )
{
2018-07-14 02:13:25 +00:00
foreach ( int move in Moves . Where ( move = > move ! = 0 & & move < Strings . Move . Count ) )
2016-06-20 04:22:43 +00:00
{
2016-06-28 05:26:39 +00:00
if ( move = = 237 ) // Hidden Power
2020-02-08 05:34:05 +00:00
{
2020-02-08 01:50:42 +00:00
yield return $"- {Strings.Move[move]} [{Strings.Types[1 + HiddenPowerType]}]" ;
2020-02-08 05:34:05 +00:00
continue ;
}
2020-02-08 01:50:42 +00:00
yield return $"- {Strings.Move[move]}" ;
2016-06-20 04:22:43 +00:00
}
}
2017-06-18 20:02:02 +00:00
2017-12-27 00:29:35 +00:00
/// <summary>
/// Converts the <see cref="PKM"/> data into an importable set format for Pokémon Showdown.
/// </summary>
/// <param name="pkm">PKM to convert to string</param>
/// <returns>Multi line set data</returns>
2017-06-18 01:37:19 +00:00
public static string GetShowdownText ( PKM pkm )
2016-06-20 04:22:43 +00:00
{
2017-11-18 06:19:23 +00:00
if ( pkm . Species = = 0 )
return string . Empty ;
2018-05-21 01:33:38 +00:00
return new ShowdownSet ( pkm ) . Text ;
}
2018-07-14 16:55:22 +00:00
/// <summary>
/// Converts the <see cref="PKM"/> data into an importable set format for Pokémon Showdown.
/// </summary>
/// <param name="pkm">PKM to convert to string</param>
/// <returns>New ShowdownSet object representing the input <see cref="pkm"/></returns>
2018-05-21 01:33:38 +00:00
public ShowdownSet ( PKM pkm )
{
2018-05-21 02:29:19 +00:00
if ( pkm . Species < = 0 )
return ;
2019-02-11 05:31:27 +00:00
Format = pkm . Format ;
2018-05-21 02:29:19 +00:00
Nickname = pkm . Nickname ;
Species = pkm . Species ;
HeldItem = pkm . HeldItem ;
Ability = pkm . Ability ;
EVs = pkm . EVs ;
IVs = pkm . IVs ;
Moves = pkm . Moves ;
2019-11-26 19:36:20 +00:00
Nature = pkm . StatNature ;
2018-05-21 02:29:19 +00:00
Gender = genders [ pkm . Gender < 2 ? pkm . Gender : 2 ] ;
Friendship = pkm . CurrentFriendship ;
2019-11-16 01:34:18 +00:00
Level = Experience . GetLevel ( pkm . EXP , pkm . PersonalInfo . EXPGrowth ) ;
2018-05-21 02:29:19 +00:00
Shiny = pkm . IsShiny ;
2019-11-16 22:37:33 +00:00
if ( pkm is IGigantamax g )
CanGigantamax = g . CanGigantamax ;
2020-02-08 01:50:42 +00:00
HiddenPowerType = HiddenPower . GetType ( IVs , Format ) ;
if ( pkm is IHyperTrain h )
{
for ( int i = 0 ; i < 6 ; i + + )
{
if ( h . GetHT ( i ) )
IVs [ i ] = pkm . MaxIV ;
}
}
2019-02-11 05:31:27 +00:00
SetFormString ( pkm . AltForm ) ;
}
public void SetFormString ( int index )
{
FormIndex = index ;
2019-02-12 05:49:05 +00:00
if ( index < = 0 )
{
Form = string . Empty ;
return ;
}
2019-09-19 02:58:23 +00:00
var Forms = FormConverter . GetFormList ( Species , Strings . Types , Strings . forms , genderForms , Format ) ;
2019-02-12 05:49:05 +00:00
Form = FormIndex > = Forms . Length ? string . Empty : Forms [ index ] ;
2016-06-20 04:22:43 +00:00
}
2018-06-16 03:30:23 +00:00
private void ParseFirstLine ( string first )
{
if ( first . Contains ( " @ " ) )
{
2018-07-22 02:20:11 +00:00
string [ ] pieces = first . Split ( ItemSplit , StringSplitOptions . None ) ;
2019-02-12 05:49:05 +00:00
string itemstr = pieces [ pieces . Length - 1 ] . Trim ( ) ;
2018-06-16 03:30:23 +00:00
ParseItemStr ( itemstr ) ;
ParseFirstLineNoItem ( pieces [ 0 ] ) ;
}
else
2018-07-29 19:47:38 +00:00
{
2018-06-16 03:30:23 +00:00
ParseFirstLineNoItem ( first . Trim ( ) ) ;
2018-07-29 19:47:38 +00:00
}
2018-06-16 03:30:23 +00:00
}
2018-07-29 19:47:38 +00:00
2018-06-16 03:30:23 +00:00
private void ParseItemStr ( string itemstr )
{
2018-07-14 02:13:25 +00:00
if ( tryGetItem ( Format ) )
2018-06-16 03:30:23 +00:00
return ;
2018-07-14 02:13:25 +00:00
if ( tryGetItem ( 3 ) )
return ;
if ( tryGetItem ( 2 ) )
return ;
InvalidLines . Add ( $"Unknown Item: {itemstr}" ) ;
bool tryGetItem ( int format )
2018-06-16 03:30:23 +00:00
{
2018-07-14 02:13:25 +00:00
var items = ( string [ ] ) Strings . GetItemStrings ( format ) ; // ireadonlylist->string[] must be possible for the provided strings
2020-01-02 23:49:35 +00:00
int item = StringUtil . FindIndexIgnoreCase ( items , itemstr ) ;
2018-07-14 02:13:25 +00:00
if ( item < 0 )
return false ;
2018-06-16 03:30:23 +00:00
HeldItem = item ;
2019-02-12 05:49:05 +00:00
Format = format ;
2018-07-14 02:13:25 +00:00
return true ;
2018-06-16 03:30:23 +00:00
}
}
2018-07-29 19:47:38 +00:00
2018-06-16 03:30:23 +00:00
private void ParseFirstLineNoItem ( string line )
2016-09-20 05:59:15 +00:00
{
// Gender Detection
string last3 = line . Substring ( line . Length - 3 ) ;
if ( last3 = = "(M)" | | last3 = = "(F)" )
{
Gender = last3 . Substring ( 1 , 1 ) ;
line = line . Substring ( 0 , line . Length - 3 ) ;
}
2020-01-02 23:49:35 +00:00
else if ( line . Contains ( Strings . Species [ ( int ) Core . Species . Meowstic ] ) | | line . Contains ( Strings . Species [ ( int ) Core . Species . Indeedee ] ) ) // Meowstic Edge Case with no gender provided
2018-07-29 19:47:38 +00:00
{
2018-04-22 16:53:52 +00:00
Gender = "M" ;
2018-07-29 19:47:38 +00:00
}
2018-05-12 15:13:39 +00:00
2016-09-20 05:59:15 +00:00
// Nickname Detection
2019-01-21 05:55:28 +00:00
if ( line . Contains ( '(' ) & & line . Contains ( ')' ) )
2018-02-13 01:36:15 +00:00
ParseSpeciesNickname ( line ) ;
else
ParseSpeciesForm ( line ) ;
}
2018-07-29 19:47:38 +00:00
2018-02-13 01:36:15 +00:00
private bool ParseSpeciesForm ( string spec )
{
2016-09-20 05:59:15 +00:00
spec = spec . Trim ( ) ;
2020-01-02 23:49:35 +00:00
if ( ( Species = StringUtil . FindIndexIgnoreCase ( Strings . specieslist , spec ) ) > = 0 ) // success, nothing else!
2018-02-13 01:36:15 +00:00
return true ;
2016-09-20 05:59:15 +00:00
2020-03-14 00:43:21 +00:00
if ( spec . EndsWith ( Gmax ) )
{
CanGigantamax = true ;
spec = spec . Substring ( 0 , spec . Length - Gmax . Length ) ;
}
2018-01-31 00:18:39 +00:00
// Forme string present.
int end = spec . LastIndexOf ( '-' ) ;
if ( end < 0 )
2018-02-13 01:36:15 +00:00
return false ;
2016-09-20 05:59:15 +00:00
2020-01-02 23:49:35 +00:00
Species = StringUtil . FindIndexIgnoreCase ( Strings . specieslist , spec . Substring ( 0 , end ) ) ;
2018-02-04 17:44:19 +00:00
Form = spec . Substring ( end + 1 ) ;
2017-12-01 02:24:31 +00:00
2018-02-13 01:36:15 +00:00
if ( Species > = 0 )
2018-02-16 02:59:38 +00:00
return true ;
2018-02-13 01:36:15 +00:00
// failure to parse, check edge cases
2018-07-22 02:20:11 +00:00
foreach ( var e in DashedSpecies )
2017-12-01 02:24:31 +00:00
{
2018-07-14 02:13:25 +00:00
if ( ! spec . StartsWith ( Strings . Species [ e ] . Replace ( "♂" , "-M" ) . Replace ( "♀" , "-F" ) ) )
2018-02-13 01:36:15 +00:00
continue ;
Species = e ;
2018-07-14 02:13:25 +00:00
Form = spec . Substring ( Strings . Species [ e ] . Length ) ;
2018-02-16 02:59:38 +00:00
return true ;
2017-12-01 02:24:31 +00:00
}
2018-02-13 01:36:15 +00:00
// Version Megas
end = spec . LastIndexOf ( '-' , Math . Max ( 0 , end - 1 ) ) ;
if ( end < 0 )
return false ;
2020-01-02 23:49:35 +00:00
Species = StringUtil . FindIndexIgnoreCase ( Strings . specieslist , spec . Substring ( 0 , end ) ) ;
2018-02-13 01:36:15 +00:00
Form = spec . Substring ( end + 1 ) ;
return Species > = 0 ;
2016-09-20 05:59:15 +00:00
}
2018-07-29 19:47:38 +00:00
2018-02-13 01:36:15 +00:00
private void ParseSpeciesNickname ( string line )
2016-09-20 05:59:15 +00:00
{
2018-07-30 04:54:02 +00:00
int index = line . LastIndexOf ( '(' ) ;
2016-09-20 05:59:15 +00:00
string n1 , n2 ;
2017-03-20 07:03:31 +00:00
if ( index > 1 ) // correct format
2016-09-20 05:59:15 +00:00
{
2018-08-02 01:28:05 +00:00
n1 = line . Substring ( 0 , index ) . Trim ( ) ;
2016-09-20 05:59:15 +00:00
n2 = line . Substring ( index ) . Trim ( ) ;
2019-02-08 05:40:20 +00:00
n2 = RemoveAll ( n2 , ParenJunk ) ; // Trim out excess data
2016-09-20 05:59:15 +00:00
}
else // nickname first (manually created set, incorrect)
{
2018-07-30 04:54:02 +00:00
int end = line . IndexOf ( ')' ) ;
2016-09-20 05:59:15 +00:00
n2 = line . Substring ( index + 1 , end - 1 ) ;
n1 = line . Substring ( end + 2 ) ;
}
2018-02-13 01:36:15 +00:00
if ( ParseSpeciesForm ( n2 ) )
{
// successful parse on n2=>Species/Form, n1 is nickname
Nickname = n1 ;
return ;
}
// other case is possibly true (or both invalid).
Nickname = n2 ;
ParseSpeciesForm ( n1 ) ;
2016-09-20 05:59:15 +00:00
}
2018-07-29 19:47:38 +00:00
2017-06-18 01:37:19 +00:00
private string ParseLineMove ( string line )
2016-09-20 05:59:15 +00:00
{
2019-02-12 05:49:05 +00:00
const int hiddenPower = 237 ;
2020-01-20 06:30:09 +00:00
string moveString = line . Substring ( line [ 1 ] = = ' ' ? 2 : 1 ) . Split ( '/' ) [ 0 ] . Trim ( ) ;
2019-02-12 05:49:05 +00:00
if ( ! moveString . StartsWith ( Strings . Move [ hiddenPower ] ) ) // Hidden Power
return moveString ; // regular move
2016-09-20 05:59:15 +00:00
2018-07-29 19:47:38 +00:00
if ( moveString . Length < = 13 )
2019-02-12 05:49:05 +00:00
return Strings . Move [ hiddenPower ] ;
2018-07-29 19:47:38 +00:00
2016-09-20 05:59:15 +00:00
// Defined Hidden Power
2019-02-12 05:49:05 +00:00
string type = moveString . Substring ( 13 ) ;
2019-02-08 05:40:20 +00:00
type = RemoveAll ( type , ParenJunk ) ; // Trim out excess data
2020-01-02 23:49:35 +00:00
int hpVal = StringUtil . FindIndexIgnoreCase ( Strings . types , type ) - 1 ; // Get HP Type
2018-07-14 02:13:25 +00:00
2018-12-29 00:54:01 +00:00
HiddenPowerType = hpVal ;
2018-07-29 19:47:38 +00:00
if ( IVs . Any ( z = > z ! = 31 ) )
{
if ( ! HiddenPower . SetIVsForType ( hpVal , IVs , Format ) )
InvalidLines . Add ( $"Invalid IVs for Hidden Power Type: {type}" ) ;
}
else if ( hpVal > = 0 )
{
2019-03-16 19:07:22 +00:00
IVs = HiddenPower . SetIVs ( hpVal , IVs , Format ) ; // Get IVs
2018-07-29 19:47:38 +00:00
}
else
{
InvalidLines . Add ( $"Invalid Hidden Power Type: {type}" ) ;
2016-09-20 05:59:15 +00:00
}
2019-02-12 05:49:05 +00:00
return Strings . Move [ hiddenPower ] ;
2016-09-20 05:59:15 +00:00
}
2018-07-29 19:47:38 +00:00
2017-06-18 01:37:19 +00:00
private void ParseLineEVs ( string line )
2016-09-20 05:59:15 +00:00
{
2019-02-12 05:49:05 +00:00
var list = SplitLineStats ( line ) ;
if ( ( list . Length & 1 ) = = 1 )
2017-01-29 08:32:02 +00:00
InvalidLines . Add ( "Unknown EV input." ) ;
2019-02-12 05:49:05 +00:00
for ( int i = 0 ; i < list . Length / 2 ; i + + )
2016-09-20 05:59:15 +00:00
{
2018-07-22 02:20:11 +00:00
int pos = i * 2 ;
2020-01-02 23:49:35 +00:00
int index = StringUtil . FindIndexIgnoreCase ( StatNames , list [ pos + 1 ] ) ;
2019-02-12 05:49:05 +00:00
if ( index > = 0 & & ushort . TryParse ( list [ pos + 0 ] , out var EV ) )
2016-09-20 05:59:15 +00:00
EVs [ index ] = EV ;
else
2019-02-12 05:49:05 +00:00
InvalidLines . Add ( $"Unknown EV stat: {list[pos]}" ) ;
2016-09-20 05:59:15 +00:00
}
2018-02-08 02:28:56 +00:00
EVs = EVsSpeedFirst ;
2016-09-20 05:59:15 +00:00
}
2018-07-29 19:47:38 +00:00
2017-06-18 01:37:19 +00:00
private void ParseLineIVs ( string line )
2016-09-20 05:59:15 +00:00
{
2019-02-12 05:49:05 +00:00
var list = SplitLineStats ( line ) ;
if ( ( list . Length & 1 ) = = 1 )
2017-01-29 08:32:02 +00:00
InvalidLines . Add ( "Unknown IV input." ) ;
2019-02-12 05:49:05 +00:00
for ( int i = 0 ; i < list . Length / 2 ; i + + )
2016-09-20 05:59:15 +00:00
{
2019-02-12 05:49:05 +00:00
int pos = i * 2 ;
2020-01-02 23:49:35 +00:00
int index = StringUtil . FindIndexIgnoreCase ( StatNames , list [ pos + 1 ] ) ;
2019-02-12 05:49:05 +00:00
if ( index > = 0 & & byte . TryParse ( list [ pos + 0 ] , out var IV ) )
2016-09-20 05:59:15 +00:00
IVs [ index ] = IV ;
else
2019-02-12 05:49:05 +00:00
InvalidLines . Add ( $"Unknown IV stat: {list[pos]}" ) ;
2016-09-20 05:59:15 +00:00
}
2018-02-08 02:28:56 +00:00
IVs = IVsSpeedFirst ;
2016-09-20 05:59:15 +00:00
}
2018-07-29 19:47:38 +00:00
2019-10-08 01:40:09 +00:00
private const string Minior = "Meteor" ;
2020-03-14 00:43:21 +00:00
private const string Gmax = "-Gmax" ;
2019-10-08 01:40:09 +00:00
2017-09-02 15:26:51 +00:00
private static string ConvertFormToShowdown ( string form , int spec )
{
2019-02-12 05:49:05 +00:00
if ( form . Length = = 0 )
2017-09-02 15:26:51 +00:00
{
2019-10-08 01:40:09 +00:00
return spec switch
{
( int ) Core . Species . Minior = > Minior ,
_ = > form
} ;
2017-09-02 15:26:51 +00:00
}
switch ( spec )
{
2019-06-01 17:22:49 +00:00
case ( int ) Core . Species . Basculin when form = = "Blue" :
2018-05-07 01:46:51 +00:00
return "Blue-Striped" ;
2019-06-01 17:22:49 +00:00
case ( int ) Core . Species . Vivillon when form = = "Poké Ball" :
return "Pokeball" ;
case ( int ) Core . Species . Zygarde :
2017-11-18 03:14:27 +00:00
form = form . Replace ( "-C" , string . Empty ) ;
2018-09-13 04:16:14 +00:00
return form . Replace ( "50%" , string . Empty ) ;
2019-06-01 17:22:49 +00:00
case ( int ) Core . Species . Minior :
2017-09-02 15:26:51 +00:00
if ( form . StartsWith ( "M-" ) )
2019-10-08 01:40:09 +00:00
return Minior ;
2017-11-18 03:14:27 +00:00
return form . Replace ( "C-" , string . Empty ) ;
2019-06-01 17:22:49 +00:00
case ( int ) Core . Species . Necrozma when form = = "Dusk" :
2017-11-18 03:14:27 +00:00
return $"{form}-Mane" ;
2019-06-01 17:22:49 +00:00
case ( int ) Core . Species . Necrozma when form = = "Dawn" :
2017-11-18 03:14:27 +00:00
return $"{form}-Wings" ;
2017-09-02 15:26:51 +00:00
2019-06-01 17:22:49 +00:00
case ( int ) Core . Species . Furfrou :
case ( int ) Core . Species . Greninja :
case ( int ) Core . Species . Rockruff :
2019-12-04 00:41:52 +00:00
case ( int ) Core . Species . Polteageist :
case ( int ) Core . Species . Sinistea :
2017-11-18 03:14:27 +00:00
return string . Empty ;
2017-09-02 15:26:51 +00:00
default :
2017-11-18 03:14:27 +00:00
if ( Legal . Totem_USUM . Contains ( spec ) & & form = = "Large" )
2019-06-01 17:22:49 +00:00
return Legal . Totem_Alolan . Contains ( spec ) & & spec ! = ( int ) Core . Species . Mimikyu ? "Alola-Totem" : "Totem" ;
2019-02-08 05:40:20 +00:00
return form . Replace ( ' ' , '-' ) ;
2017-09-02 15:26:51 +00:00
}
}
2018-07-29 19:47:38 +00:00
2017-09-02 15:26:51 +00:00
private static string ConvertFormFromShowdown ( string form , int spec , int ability )
{
2019-02-12 05:49:05 +00:00
if ( form . Length = = 0 )
form = form . Replace ( ' ' , '-' ) ; // inconsistencies are great
2017-09-02 15:26:51 +00:00
switch ( spec )
{
2019-06-01 17:22:49 +00:00
case ( int ) Core . Species . Basculin when form = = "Blue-Striped" :
2017-09-02 15:26:51 +00:00
return "Blue" ;
2019-06-01 17:22:49 +00:00
case ( int ) Core . Species . Greninja when ability = = 210 :
2017-09-02 15:26:51 +00:00
return "Ash" ; // Battle Bond
2019-06-01 17:22:49 +00:00
case ( int ) Core . Species . Vivillon when form = = "Pokeball" :
2017-09-02 15:26:51 +00:00
return "Poké Ball" ;
// Zygarde
2019-06-01 17:22:49 +00:00
case ( int ) Core . Species . Zygarde when form . Length = = 0 :
2019-02-12 05:49:05 +00:00
return ability = = 211 ? "50%-C" : "50%" ;
2019-06-01 17:22:49 +00:00
case ( int ) Core . Species . Zygarde when form = = "Complete" :
2018-09-13 04:16:14 +00:00
return form ;
2019-06-01 17:22:49 +00:00
case ( int ) Core . Species . Zygarde when ability = = 211 :
2017-09-02 15:26:51 +00:00
return "-C" ; // Power Construct
2019-06-01 17:22:49 +00:00
case ( int ) Core . Species . Rockruff when ability = = 020 : // Rockruff-1
2017-11-18 03:14:27 +00:00
return "Dusk" ;
2017-09-02 15:26:51 +00:00
// Minior
2019-10-08 01:40:09 +00:00
case ( int ) Core . Species . Minior when form . Length ! = 0 & & form ! = Minior :
2017-09-30 05:58:25 +00:00
return $"C-{form}" ;
2017-09-02 15:26:51 +00:00
2017-11-18 03:14:27 +00:00
// Necrozma
2020-01-16 04:48:53 +00:00
case ( int ) Core . Species . Necrozma when form = = "Dusk-Mane" | | form = = "Dusk Mane" :
2017-11-18 03:14:27 +00:00
return "Dusk" ;
2020-01-16 04:48:53 +00:00
case ( int ) Core . Species . Necrozma when form = = "Dawn-Wings" | | form = = "Dawn Wings" :
2017-11-18 03:14:27 +00:00
return "Dawn" ;
2020-01-02 23:49:35 +00:00
2019-11-22 22:17:52 +00:00
// Toxtricity
case ( int ) Core . Species . Toxtricity when form = = "Low-Key" :
return "Low Key" ;
2017-11-18 03:14:27 +00:00
2019-12-04 00:41:52 +00:00
// Darmanitan
case ( int ) Core . Species . Darmanitan :
2020-01-17 02:54:09 +00:00
if ( form = = "Galar-Zen" )
return "Galar Zen" ;
2019-12-04 00:41:52 +00:00
return form ;
2017-09-02 15:26:51 +00:00
default :
2019-02-12 05:49:05 +00:00
if ( Legal . Totem_USUM . Contains ( spec ) & & form . EndsWith ( "Totem" ) )
2017-11-18 04:38:43 +00:00
return "Large" ;
2017-09-02 15:26:51 +00:00
return form ;
}
}
2016-09-20 05:59:15 +00:00
2019-02-08 05:40:20 +00:00
private static string RemoveAll ( string original , char [ ] remove ) = > string . Concat ( original . Where ( z = > ! remove . Contains ( z ) ) ) ;
2017-06-18 01:37:19 +00:00
private static string [ ] SplitLineStats ( string line )
2016-09-20 05:59:15 +00:00
{
// Because people think they can type sets out...
return line
. Replace ( "SAtk" , "SpA" ) . Replace ( "Sp Atk" , "SpA" )
. Replace ( "SDef" , "SpD" ) . Replace ( "Sp Def" , "SpD" )
2018-07-22 02:20:11 +00:00
. Replace ( "Spd" , "Spe" ) . Replace ( "Speed" , "Spe" ) . Split ( StatSplitters , StringSplitOptions . None ) ;
2016-09-20 05:59:15 +00:00
}
2018-07-29 19:47:38 +00:00
2018-03-11 18:39:58 +00:00
/// <summary>
/// Fetches <see cref="ShowdownSet"/> data from the input <see cref="lines"/>.
/// </summary>
/// <param name="lines">Raw lines containing numerous multi-line set data.</param>
/// <returns><see cref="ShowdownSet"/> objects until <see cref="lines"/> is consumed.</returns>
public static IEnumerable < ShowdownSet > GetShowdownSets ( IEnumerable < string > lines )
{
2019-02-12 05:49:05 +00:00
// exported sets always have >4 moves; new List will always require 1 resizing, allocate 2x to save 1 reallocation.
// intro, nature, ability, (ivs, evs, shiny, level) 4*moves
2018-03-11 18:39:58 +00:00
var setLines = new List < string > ( 8 ) ;
foreach ( var line in lines )
{
if ( ! string . IsNullOrWhiteSpace ( line ) )
{
setLines . Add ( line ) ;
continue ;
}
2018-06-09 15:12:47 +00:00
if ( setLines . Count = = 0 )
continue ;
2018-03-11 18:39:58 +00:00
yield return new ShowdownSet ( setLines ) ;
setLines . Clear ( ) ;
}
2018-06-09 15:12:47 +00:00
if ( setLines . Count ! = 0 )
yield return new ShowdownSet ( setLines ) ;
2018-03-11 18:39:58 +00:00
}
2018-05-20 03:48:03 +00:00
/// <summary>
/// Fetches ShowdownSet lines from the input <see cref="PKM"/> data.
/// </summary>
/// <param name="data">Pokémon data to summarize.</param>
2019-01-21 05:55:28 +00:00
/// <returns>Consumable list of <see cref="Text"/> lines.</returns>
2018-06-17 04:46:43 +00:00
public static IEnumerable < string > GetShowdownSets ( IEnumerable < PKM > data ) = > data . Where ( p = > p . Species ! = 0 ) . Select ( GetShowdownText ) ;
2018-05-20 03:48:03 +00:00
/// <summary>
/// Fetches ShowdownSet lines from the input <see cref="PKM"/> data, and combines it into one string.
/// </summary>
/// <param name="data">Pokémon data to summarize.</param>
/// <param name="separator">Splitter between each set.</param>
2019-01-21 05:55:28 +00:00
/// <returns>Single string containing all <see cref="Text"/> lines.</returns>
2018-06-17 04:46:43 +00:00
public static string GetShowdownSets ( IEnumerable < PKM > data , string separator ) = > string . Join ( separator , GetShowdownSets ( data ) ) ;
2019-08-21 02:50:28 +00:00
/// <summary>
/// Gets a localized string preview of the provided <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon data</param>
/// <param name="language">Language code</param>
/// <returns>Multi-line string</returns>
public static string GetLocalizedPreviewText ( PKM pk , string language )
{
var set = new ShowdownSet ( pk ) ;
if ( pk . Format < = 2 ) // Nature preview from IVs
set . Nature = Experience . GetNatureVC ( pk . EXP ) ;
return set . LocalizedText ( language ) ;
}
2016-06-20 04:22:43 +00:00
}
}