2022-06-26 06:08:28 +00:00
|
|
|
|
using System;
|
2016-09-04 06:14:05 +00:00
|
|
|
|
using System.Collections.Generic;
|
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
|
|
|
|
{
|
2022-06-26 06:08:28 +00:00
|
|
|
|
private static readonly string[] StatNames = { "HP", "Atk", "Def", "Spe", "SpA", "SpD" };
|
2022-06-18 18:04:24 +00:00
|
|
|
|
private static readonly string[] Splitters = {"\r\n", "\n"};
|
|
|
|
|
private static readonly string[] StatSplitters = { " / ", " " };
|
2022-06-26 06:08:28 +00:00
|
|
|
|
private const string LineSplit = ": ";
|
|
|
|
|
private const string ItemSplit = " @ ";
|
|
|
|
|
private static readonly char[] ParenJunk = { '(', ')', '[', ']' };
|
2022-06-18 18:04:24 +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)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
|
|
|
|
|
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/>
|
|
|
|
|
public int Nature { get; 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/>
|
2022-06-26 06:08:28 +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/>
|
2022-06-26 06:08:28 +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/>
|
|
|
|
|
public int HiddenPowerType { get; set; } = -1;
|
2018-12-29 00:54:01 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
/// <inheritdoc/>
|
2022-08-27 06:43:36 +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/>
|
|
|
|
|
public bool CanGigantamax { get; set; }
|
2019-11-16 22:37:33 +00:00
|
|
|
|
|
2022-08-04 00:10:00 +00:00
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public byte DynamaxLevel { get; set; } = 10;
|
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Any lines that failed to be parsed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly List<string> InvalidLines = new();
|
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>
|
|
|
|
|
public ShowdownSet(string input) : this(input.Split(Splitters, 0)) { }
|
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
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
private void LoadLines(IEnumerable<string> lines)
|
|
|
|
|
{
|
|
|
|
|
ParseLines(lines);
|
2016-09-20 05:59:15 +00:00
|
|
|
|
|
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-08-04 00:10:00 +00:00
|
|
|
|
if (Species is (int)Meowstic or (int)Indeedee or (int)Basculegion)
|
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 static IEnumerable<string> GetSanitizedLines(IEnumerable<string> lines)
|
|
|
|
|
{
|
|
|
|
|
foreach (var line in lines)
|
2022-05-14 15:28:13 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
var trim = line.Trim();
|
|
|
|
|
if (trim.Length <= 2)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Sanitize apostrophes & dashes
|
|
|
|
|
if (trim.IndexOf('\'') != -1)
|
|
|
|
|
trim = trim.Replace('\'', '’');
|
|
|
|
|
if (trim.IndexOf('–') != -1)
|
|
|
|
|
trim = trim.Replace('–', '-');
|
|
|
|
|
yield return trim;
|
2022-05-14 15:28:13 +00:00
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
}
|
2022-05-14 15:28:13 +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
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
private void ParseLines(IEnumerable<string> lines)
|
|
|
|
|
{
|
|
|
|
|
lines = GetSanitizedLines(lines);
|
|
|
|
|
using var e = lines.GetEnumerator();
|
|
|
|
|
if (!e.MoveNext())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
ParseFirstLine(e.Current!);
|
|
|
|
|
int movectr = 0;
|
|
|
|
|
while (e.MoveNext())
|
2018-03-11 18:39:58 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
var line = e.Current!;
|
2022-06-27 03:02:57 +00:00
|
|
|
|
if (line.Length < 3)
|
2022-06-18 18:04:24 +00:00
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (line[0] == '-')
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
string moveString = ParseLineMove(line);
|
|
|
|
|
int move = StringUtil.FindIndexIgnoreCase(Strings.movelist, moveString);
|
|
|
|
|
if (move < 0)
|
|
|
|
|
InvalidLines.Add($"Unknown Move: {moveString}");
|
2022-08-27 06:43:36 +00:00
|
|
|
|
else if (Array.IndexOf(Moves, (ushort)move) != -1)
|
2022-06-18 18:04:24 +00:00
|
|
|
|
InvalidLines.Add($"Duplicate Move: {moveString}");
|
|
|
|
|
else
|
2022-08-27 06:43:36 +00:00
|
|
|
|
Moves[movectr++] = (ushort)move;
|
2022-06-18 18:04:24 +00:00
|
|
|
|
|
|
|
|
|
if (movectr == MaxMoveCount)
|
|
|
|
|
return; // End of moves, end of set data
|
|
|
|
|
continue;
|
2019-02-12 05:49:05 +00:00
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
if (movectr != 0)
|
|
|
|
|
break;
|
|
|
|
|
|
2022-06-26 06:08:28 +00:00
|
|
|
|
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);
|
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
if (!valid)
|
|
|
|
|
InvalidLines.Add(line);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool ParseSingle(string identifier)
|
|
|
|
|
{
|
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
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
private bool ParseEntry(string identifier, string value)
|
|
|
|
|
{
|
|
|
|
|
switch (identifier)
|
2018-04-26 01:45:31 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
case "Ability" or "Trait": return (Ability = StringUtil.FindIndexIgnoreCase(Strings.abilitylist, value)) >= 0;
|
|
|
|
|
case "Shiny": return Shiny = StringUtil.IsMatchIgnoreCase("Yes", value);
|
|
|
|
|
case "Gigantamax": return CanGigantamax = StringUtil.IsMatchIgnoreCase("Yes", value);
|
|
|
|
|
case "Nature": return (Nature = StringUtil.FindIndexIgnoreCase(Strings.natures, value)) >= 0;
|
|
|
|
|
case "EV" or "EVs": ParseLineEVs(value); return true;
|
|
|
|
|
case "IV" or "IVs": ParseLineIVs(value); return true;
|
2022-08-04 00:50:11 +00:00
|
|
|
|
case "Dynamax Level": return ParseDynamax(value);
|
2022-06-18 18:04:24 +00:00
|
|
|
|
case "Level":
|
|
|
|
|
{
|
|
|
|
|
if (!int.TryParse(value.Trim(), out int val))
|
|
|
|
|
return false;
|
|
|
|
|
Level = val;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
case "Friendship" or "Happiness":
|
2018-04-26 01:45:31 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
if (!int.TryParse(value.Trim(), out int val))
|
2018-06-16 03:30:23 +00:00
|
|
|
|
return false;
|
2022-06-18 18:04:24 +00:00
|
|
|
|
Friendship = val;
|
|
|
|
|
return true;
|
2018-04-26 01:45:31 +00:00
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
default:
|
|
|
|
|
return false;
|
2018-04-26 01:45:31 +00:00
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
}
|
2018-04-26 01:45:31 +00:00
|
|
|
|
|
2022-08-04 00:50:11 +00:00
|
|
|
|
private bool ParseDynamax(string value)
|
|
|
|
|
{
|
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;
|
|
|
|
|
return (DynamaxLevel = (byte)val) is (>= 0 and <= 10);
|
|
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
if (Species is <= 0 or > MAX_SPECIES)
|
|
|
|
|
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
|
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 ivs = GetStringStats(IVs, Context.Generation() < 3 ? 15 : 31);
|
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]}");
|
|
|
|
|
if (Level != 100)
|
|
|
|
|
result.Add($"Level: {Level}");
|
|
|
|
|
if (Shiny)
|
|
|
|
|
result.Add("Shiny: Yes");
|
2022-08-15 01:54:08 +00:00
|
|
|
|
if (DynamaxLevel != 10 && Context == EntityContext.Gen8)
|
2022-08-04 00:10:00 +00:00
|
|
|
|
result.Add($"Dynamax Level: {DynamaxLevel}");
|
|
|
|
|
if (CanGigantamax)
|
|
|
|
|
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})";
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-26 06:08:28 +00:00
|
|
|
|
private static string[] GetStringStats(ReadOnlySpan<int> stats, int ignoreValue)
|
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)
|
|
|
|
|
return Array.Empty<string>();
|
|
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
if (statValue == 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;
|
|
|
|
|
|
2022-06-26 06:08:28 +00:00
|
|
|
|
if (move != (int)Move.HiddenPower)
|
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-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)
|
|
|
|
|
{
|
|
|
|
|
if (pk.Species <= 0)
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
if (pk is IGigantamax g)
|
|
|
|
|
CanGigantamax = g.CanGigantamax;
|
2022-08-04 00:10:00 +00:00
|
|
|
|
if (pk is IDynamaxLevel d)
|
|
|
|
|
DynamaxLevel = d.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-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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ParseFirstLine(string first)
|
|
|
|
|
{
|
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
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
private void ParseItemName(string itemName)
|
|
|
|
|
{
|
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 (TrySetItem(Context))
|
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
|
|
|
|
if (TrySetItem(EntityContext.Gen3))
|
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
|
|
|
|
if (TrySetItem(EntityContext.Gen2))
|
2022-06-18 18:04:24 +00:00
|
|
|
|
return;
|
|
|
|
|
InvalidLines.Add($"Unknown Item: {itemName}");
|
|
|
|
|
|
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 TrySetItem(EntityContext context)
|
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);
|
2022-06-18 18:04:24 +00:00
|
|
|
|
int item = StringUtil.FindIndexIgnoreCase(items, itemName);
|
|
|
|
|
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
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
private void ParseFirstLineNoItem(string line)
|
|
|
|
|
{
|
|
|
|
|
// Gender Detection
|
|
|
|
|
if (line.EndsWith("(M)", StringComparison.Ordinal))
|
2016-09-20 05:59:15 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
line = line[..^3];
|
|
|
|
|
Gender = 0;
|
|
|
|
|
}
|
|
|
|
|
else if (line.EndsWith("(F)", StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
line = line[..^3];
|
|
|
|
|
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
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
private bool ParseSpeciesForm(string speciesLine)
|
|
|
|
|
{
|
|
|
|
|
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.
|
|
|
|
|
int end = speciesLine.LastIndexOf('-');
|
|
|
|
|
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;
|
|
|
|
|
FormName = speciesLine[(end + 1)..];
|
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;
|
|
|
|
|
FormName = speciesLine[sn.Length..];
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-02-13 01:36:15 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// Version Megas
|
|
|
|
|
end = speciesLine.LastIndexOf('-', Math.Max(0, end - 1));
|
|
|
|
|
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;
|
|
|
|
|
FormName = speciesLine[(end + 1)..];
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2022-06-18 18:04:24 +00:00
|
|
|
|
}
|
2018-02-13 01:36:15 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
private void ParseSpeciesNickname(string line)
|
|
|
|
|
{
|
|
|
|
|
int index = line.LastIndexOf('(');
|
|
|
|
|
string species, nickname;
|
|
|
|
|
if (index > 1) // parenthesis value after: Nickname (Species), correct.
|
|
|
|
|
{
|
|
|
|
|
nickname = line[..index].Trim();
|
|
|
|
|
species = line[index..].Trim();
|
|
|
|
|
species = RemoveAll(species, ParenJunk); // Trim out excess data
|
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;
|
|
|
|
|
int end = line.IndexOf(')');
|
|
|
|
|
var tmp = line[start..end];
|
|
|
|
|
if (end < line.Length - 2)
|
2016-09-20 05:59:15 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +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
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
species = tmp;
|
|
|
|
|
nickname = string.Empty;
|
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))
|
|
|
|
|
Nickname = nickname;
|
|
|
|
|
else if (ParseSpeciesForm(nickname))
|
|
|
|
|
Nickname = species;
|
|
|
|
|
}
|
2016-09-20 05:59:15 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
private string ParseLineMove(string line)
|
|
|
|
|
{
|
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
|
|
|
|
|
2022-06-27 03:02:57 +00:00
|
|
|
|
string moveString = line.Trim();
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
string type = moveString[13..];
|
|
|
|
|
type = RemoveAll(type, ParenJunk); // Trim out excess data
|
|
|
|
|
int hpVal = StringUtil.FindIndexIgnoreCase(Strings.types, type) - 1; // Get HP Type
|
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
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
private void ParseLineEVs(string line)
|
|
|
|
|
{
|
|
|
|
|
var list = SplitLineStats(line);
|
|
|
|
|
if ((list.Length & 1) == 1)
|
|
|
|
|
InvalidLines.Add("Unknown EV input.");
|
|
|
|
|
for (int i = 0; i < list.Length / 2; i++)
|
2016-09-20 05:59:15 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
int pos = i * 2;
|
2022-06-26 06:08:28 +00:00
|
|
|
|
var statName = list[pos + 1];
|
|
|
|
|
int index = StringUtil.FindIndexIgnoreCase(StatNames, statName);
|
|
|
|
|
if (index < 0 || !ushort.TryParse(list[pos + 0], out var value))
|
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
InvalidLines.Add($"Unknown EV stat: {list[pos]}");
|
2022-06-26 06:08:28 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
EVs[index] = value;
|
2016-09-20 05:59:15 +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 void ParseLineIVs(string line)
|
|
|
|
|
{
|
|
|
|
|
var list = SplitLineStats(line);
|
|
|
|
|
if ((list.Length & 1) == 1)
|
|
|
|
|
InvalidLines.Add("Unknown IV input.");
|
|
|
|
|
for (int i = 0; i < list.Length / 2; i++)
|
2022-05-14 15:28:13 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
int pos = i * 2;
|
2022-06-26 06:08:28 +00:00
|
|
|
|
var statName = list[pos + 1];
|
|
|
|
|
int index = StringUtil.FindIndexIgnoreCase(StatNames, statName);
|
|
|
|
|
if (index < 0 || !byte.TryParse(list[pos + 0], out var value))
|
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
InvalidLines.Add($"Unknown IV stat: {list[pos]}");
|
2022-06-26 06:08:28 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
IVs[index] = value;
|
2022-05-14 15:28:13 +00:00
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
}
|
2019-02-08 05:40:20 +00:00
|
|
|
|
|
2022-06-26 06:08:28 +00:00
|
|
|
|
private static string RemoveAll(string original, ReadOnlySpan<char> remove)
|
2022-06-18 18:04:24 +00:00
|
|
|
|
{
|
|
|
|
|
Span<char> result = stackalloc char[original.Length];
|
|
|
|
|
int ctr = 0;
|
|
|
|
|
foreach (var c in original)
|
2016-09-20 05:59:15 +00:00
|
|
|
|
{
|
2022-06-26 06:08:28 +00:00
|
|
|
|
if (remove.IndexOf(c) == -1)
|
2022-06-18 18:04:24 +00:00
|
|
|
|
result[ctr++] = c;
|
2016-09-20 05:59:15 +00:00
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
if (ctr == original.Length)
|
|
|
|
|
return original;
|
|
|
|
|
return new string(result[..ctr].ToArray());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string[] SplitLineStats(string line)
|
|
|
|
|
{
|
|
|
|
|
// Because people think they can type sets out...
|
|
|
|
|
return line
|
|
|
|
|
.Replace("SAtk", "SpA").Replace("Sp Atk", "SpA")
|
|
|
|
|
.Replace("SDef", "SpD").Replace("Sp Def", "SpD")
|
|
|
|
|
.Replace("Spd", "Spe").Replace("Speed", "Spe").Split(StatSplitters, StringSplitOptions.None);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
|
|
|
|
}
|