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;
|
2020-12-29 05:42:54 +00:00
|
|
|
|
using static PKHeX.Core.Species;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
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>
|
2020-04-12 20:05:29 +00:00
|
|
|
|
public sealed class ShowdownSet : IBattleTemplate
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2017-12-27 00:29:35 +00:00
|
|
|
|
private static readonly string[] genders = {"M", "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
|
2020-12-29 05:42:54 +00:00
|
|
|
|
private const int MAX_SPECIES = (int)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
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2018-07-14 16:55:22 +00:00
|
|
|
|
public int Species { get; private set; } = -1;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2019-02-12 05:49:05 +00:00
|
|
|
|
public int Format { get; private set; } = PKMConverter.Format;
|
2018-07-14 16:55:22 +00:00
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2019-02-12 05:49:05 +00:00
|
|
|
|
public string Nickname { get; set; } = string.Empty;
|
2018-07-14 16:55:22 +00:00
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2019-02-12 05:49:05 +00:00
|
|
|
|
public string Gender { get; private set; } = string.Empty;
|
2018-07-14 16:55:22 +00:00
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public int HeldItem { get; private set; }
|
2018-07-14 16:55:22 +00:00
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2018-04-26 01:45:31 +00:00
|
|
|
|
public int Ability { get; private set; } = -1;
|
2018-07-14 16:55:22 +00:00
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2016-09-20 05:59:15 +00:00
|
|
|
|
public int Level { get; private set; } = 100;
|
2018-07-14 16:55:22 +00:00
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2016-09-20 05:59:15 +00:00
|
|
|
|
public bool Shiny { get; private set; }
|
2018-07-14 16:55:22 +00:00
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2016-09-20 05:59:15 +00:00
|
|
|
|
public int Friendship { get; private set; } = 255;
|
2018-07-14 16:55:22 +00:00
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2019-02-12 05:49:05 +00:00
|
|
|
|
public int Nature { get; set; } = -1;
|
2018-07-14 16:55:22 +00:00
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2020-12-11 04:42:30 +00:00
|
|
|
|
public string FormName { get; private set; } = string.Empty;
|
2018-07-14 16:55:22 +00:00
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2020-12-11 04:42:30 +00:00
|
|
|
|
public int Form { get; private set; }
|
2018-07-14 16:55:22 +00:00
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
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
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
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
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2018-12-29 00:54:01 +00:00
|
|
|
|
public int HiddenPowerType { get; set; } = -1;
|
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2018-07-14 16:55:22 +00:00
|
|
|
|
public int[] Moves { get; } = {0, 0, 0, 0};
|
|
|
|
|
|
2020-04-12 20:05:29 +00:00
|
|
|
|
/// <inheritdoc/>
|
2019-11-16 22:37:33 +00:00
|
|
|
|
public bool CanGigantamax { get; set; }
|
|
|
|
|
|
2018-07-14 16:55:22 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Any lines that failed to be parsed.
|
|
|
|
|
/// </summary>
|
2020-12-22 01:17:56 +00:00
|
|
|
|
public readonly List<string> InvalidLines = new();
|
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
|
|
|
|
|
2019-02-12 05:49:05 +00:00
|
|
|
|
/// <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>
|
2020-04-12 20:05:29 +00:00
|
|
|
|
public ShowdownSet(string input) : this(input.Split(Splitters, 0)) { }
|
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>
|
2020-04-12 20:05:29 +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
|
|
|
|
|
2020-12-29 05:42:54 +00:00
|
|
|
|
FormName = ShowdownParsing.SetShowdownFormName(Species, FormName, Ability);
|
|
|
|
|
Form = ShowdownParsing.GetFormFromString(FormName, Strings, Species, Format);
|
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-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-12-25 18:58:33 +00:00
|
|
|
|
case "Ability" or "Trait": return (Ability = StringUtil.FindIndexIgnoreCase(Strings.abilitylist, value)) >= 0;
|
2019-02-12 05:49:05 +00:00
|
|
|
|
case "Shiny": return Shiny = value.Trim() == "Yes";
|
2020-07-31 03:16:57 +00:00
|
|
|
|
case "Gigantamax": return CanGigantamax = value.Trim() == "Yes";
|
2020-01-02 23:49:35 +00:00
|
|
|
|
case "Nature": return (Nature = StringUtil.FindIndexIgnoreCase(Strings.natures, value)) >= 0;
|
2020-12-25 18:58:33 +00:00
|
|
|
|
case "EV" or "EVs": ParseLineEVs(value); return true;
|
|
|
|
|
case "IV" or "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;
|
|
|
|
|
}
|
2020-12-25 18:58:33 +00:00
|
|
|
|
case "Friendship" or "Happiness":
|
2018-06-16 03:30:23 +00:00
|
|
|
|
{
|
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-12-29 05:42:54 +00:00
|
|
|
|
var form = ShowdownParsing.GetShowdownFormName(Species, FormName);
|
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}");
|
2020-07-31 03:16:57 +00:00
|
|
|
|
if (CanGigantamax)
|
|
|
|
|
result.Add("Gigantamax: Yes");
|
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-12-29 05:42:54 +00:00
|
|
|
|
else if (Species == (int)NidoranM)
|
2020-02-08 01:42:28 +00:00
|
|
|
|
specForm = specForm.Replace("♂", "-M");
|
2020-12-29 05:42:54 +00:00
|
|
|
|
else if (Species == (int)NidoranF)
|
2020-02-08 01:42:28 +00:00
|
|
|
|
specForm = specForm.Replace("♀", "-F");
|
2016-10-04 02:24:46 +00:00
|
|
|
|
|
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
|
|
|
|
|
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++)
|
|
|
|
|
{
|
2021-01-02 00:39:33 +00:00
|
|
|
|
if (h.IsHyperTrained(i))
|
2020-02-08 01:50:42 +00:00
|
|
|
|
IVs[i] = pkm.MaxIV;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-29 05:42:54 +00:00
|
|
|
|
FormName = ShowdownParsing.GetStringFromForm(Form = pkm.Form, Strings, Species, Format);
|
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);
|
2020-06-17 02:46:22 +00:00
|
|
|
|
string itemName = pieces[pieces.Length - 1].Trim();
|
2018-06-16 03:30:23 +00:00
|
|
|
|
|
2020-06-17 02:46:22 +00:00
|
|
|
|
ParseItemName(itemName);
|
2018-06-16 03:30:23 +00:00
|
|
|
|
ParseFirstLineNoItem(pieces[0]);
|
|
|
|
|
}
|
|
|
|
|
else
|
2018-07-29 19:47:38 +00:00
|
|
|
|
{
|
2020-06-05 16:55:00 +00:00
|
|
|
|
ParseFirstLineNoItem(first);
|
2018-07-29 19:47:38 +00:00
|
|
|
|
}
|
2018-06-16 03:30:23 +00:00
|
|
|
|
}
|
2018-07-29 19:47:38 +00:00
|
|
|
|
|
2020-06-17 02:46:22 +00:00
|
|
|
|
private void ParseItemName(string itemName)
|
2018-06-16 03:30:23 +00:00
|
|
|
|
{
|
2020-06-17 02:46:22 +00:00
|
|
|
|
if (TrySetItem(Format))
|
2018-06-16 03:30:23 +00:00
|
|
|
|
return;
|
2020-06-17 02:46:22 +00:00
|
|
|
|
if (TrySetItem(3))
|
2018-07-14 02:13:25 +00:00
|
|
|
|
return;
|
2020-06-17 02:46:22 +00:00
|
|
|
|
if (TrySetItem(2))
|
2018-07-14 02:13:25 +00:00
|
|
|
|
return;
|
2020-06-17 02:46:22 +00:00
|
|
|
|
InvalidLines.Add($"Unknown Item: {itemName}");
|
2018-07-14 02:13:25 +00:00
|
|
|
|
|
2020-06-17 02:46:22 +00:00
|
|
|
|
bool TrySetItem(int format)
|
2018-06-16 03:30:23 +00:00
|
|
|
|
{
|
2020-06-17 02:46:22 +00:00
|
|
|
|
var items = (string[])Strings.GetItemStrings(format); // IReadOnlyList<string>->string[] must be possible for the provided strings
|
|
|
|
|
int item = StringUtil.FindIndexIgnoreCase(items, itemName);
|
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
|
2020-12-29 05:42:54 +00:00
|
|
|
|
if (line.EndsWith("(M)") || line.EndsWith("(F)"))
|
2016-09-20 05:59:15 +00:00
|
|
|
|
{
|
2020-12-29 05:42:54 +00:00
|
|
|
|
Gender = line[line.Length - 2].ToString();
|
2016-09-20 05:59:15 +00:00
|
|
|
|
line = line.Substring(0, line.Length - 3);
|
|
|
|
|
}
|
2020-12-29 05:42:54 +00:00
|
|
|
|
else // Meowstic Edge Case with no gender provided
|
2018-07-29 19:47:38 +00:00
|
|
|
|
{
|
2020-12-29 05:42:54 +00:00
|
|
|
|
var s = Strings.Species;
|
|
|
|
|
if (line.Contains(s[(int)Meowstic]) || line.Contains(s[(int)Indeedee]))
|
|
|
|
|
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
|
|
|
|
|
2020-12-29 05:42:54 +00:00
|
|
|
|
private const string Gmax = "-Gmax";
|
|
|
|
|
|
|
|
|
|
private bool ParseSpeciesForm(string speciesLine)
|
2018-02-13 01:36:15 +00:00
|
|
|
|
{
|
2020-12-29 05:42:54 +00:00
|
|
|
|
speciesLine = speciesLine.Trim();
|
|
|
|
|
if (speciesLine.EndsWith(Gmax))
|
2020-03-14 00:43:21 +00:00
|
|
|
|
{
|
|
|
|
|
CanGigantamax = true;
|
2020-12-29 05:42:54 +00:00
|
|
|
|
speciesLine = speciesLine.Substring(0, speciesLine.Length - Gmax.Length);
|
2020-03-14 00:43:21 +00:00
|
|
|
|
}
|
2020-03-14 19:21:42 +00:00
|
|
|
|
|
2020-12-29 05:42:54 +00:00
|
|
|
|
if ((Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, speciesLine)) >= 0) // success, nothing else!
|
2020-03-14 19:21:42 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
2020-06-17 02:46:22 +00:00
|
|
|
|
// Form string present.
|
2020-12-29 05:42:54 +00:00
|
|
|
|
int end = speciesLine.LastIndexOf('-');
|
2018-01-31 00:18:39 +00:00
|
|
|
|
if (end < 0)
|
2018-02-13 01:36:15 +00:00
|
|
|
|
return false;
|
2016-09-20 05:59:15 +00:00
|
|
|
|
|
2020-12-29 05:42:54 +00:00
|
|
|
|
Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, speciesLine.Substring(0, end));
|
|
|
|
|
FormName = speciesLine.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
|
|
|
|
{
|
2020-06-05 16:55:00 +00:00
|
|
|
|
var sn = Strings.Species[e];
|
2020-12-29 05:42:54 +00:00
|
|
|
|
if (!speciesLine.StartsWith(sn.Replace("♂", "-M").Replace("♀", "-F")))
|
2018-02-13 01:36:15 +00:00
|
|
|
|
continue;
|
|
|
|
|
Species = e;
|
2020-12-29 05:42:54 +00:00
|
|
|
|
FormName = speciesLine.Substring(sn.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
|
2020-12-29 05:42:54 +00:00
|
|
|
|
end = speciesLine.LastIndexOf('-', Math.Max(0, end - 1));
|
2018-02-13 01:36:15 +00:00
|
|
|
|
if (end < 0)
|
|
|
|
|
return false;
|
2020-12-29 05:42:54 +00:00
|
|
|
|
Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, speciesLine.Substring(0, end));
|
|
|
|
|
FormName = speciesLine.Substring(end + 1);
|
2018-02-13 01:36:15 +00:00
|
|
|
|
|
|
|
|
|
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);
|
2020-06-05 16:55:00 +00:00
|
|
|
|
n1 = end < line.Length - 2 ? line.Substring(end + 2) : n2;
|
2016-09-20 05:59:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
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]);
|
2020-06-17 02:46:22 +00:00
|
|
|
|
if (index >= 0 && byte.TryParse(list[pos + 0], out var iv))
|
|
|
|
|
IVs[index] = iv;
|
2016-09-20 05:59:15 +00:00
|
|
|
|
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-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
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
|
|
|
|
}
|