mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-03-22 01:55:10 -05:00
Compare commits
No commits in common. "master" and "22.09.13" have entirely different histories.
|
|
@ -5,22 +5,30 @@ root = true
|
|||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.txt]
|
||||
insert_final_newline = false
|
||||
# Solution Files
|
||||
[*.sln]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# XML Project Files
|
||||
[*.{slnx,csproj}]
|
||||
[*.csproj]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
|
||||
# Code Files
|
||||
[*.{cs,vb}]
|
||||
[*.cs]
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
end_of_line = crlf
|
||||
|
||||
csharp_prefer_braces = when_multiline:warning
|
||||
dotnet_diagnostic.IDE0047.severity = none
|
||||
dotnet_diagnostic.IDE0048.severity = none
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggest
|
||||
|
|
@ -28,72 +36,10 @@ dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:sug
|
|||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggest
|
||||
dotnet_style_parentheses_in_other_operators = always_for_clarity:suggest
|
||||
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_prefer_braces = when_multiline:warning
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
csharp_prefer_system_threading_lock = true:suggestion
|
||||
csharp_style_namespace_declarations = block_scoped:silent
|
||||
csharp_style_prefer_method_group_conversion = true:silent
|
||||
csharp_style_prefer_top_level_statements = true:silent
|
||||
csharp_style_prefer_primary_constructors = true:suggestion
|
||||
csharp_style_expression_bodied_methods = false:silent
|
||||
csharp_style_expression_bodied_constructors = false:silent
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_properties = true:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_accessors = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
|
||||
[*.{cs,vb}]
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
# IDE0130: Namespace does not match folder structure
|
||||
dotnet_diagnostic.IDE0130.severity = none
|
||||
|
||||
# WFO1000: Property does not configure the code serialization for its property content.
|
||||
dotnet_diagnostic.WFO1000.severity = none
|
||||
12
.github/README-de.md
vendored
12
.github/README-de.md
vendored
|
|
@ -7,7 +7,7 @@ Save Editor für die Pokémon Hauptreihe, geschrieben in [C#](https://de.wikiped
|
|||
Die folgenden Dateien werden unterstützt:
|
||||
* Spielstände ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin)
|
||||
* GameCube Memory Card Daten (\*.raw, \*.bin), die GC Pokémon Spielstände enthalten.
|
||||
* Einzelne Pokémon (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
|
||||
* Einzelne Pokémon (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4)
|
||||
* Wunderkarten (\*.pgt, \*.pcd, \*.pgf, .wc\*), inklusive Konvertierung zu .pk\*
|
||||
* Import von GO Park Pokémon (\*.gp1) inklusive Konvertierung zu .pb7
|
||||
* Import von Teams aus entschlüsselten 3DS Battle Videos.
|
||||
|
|
@ -24,13 +24,13 @@ PKHeX erwartet entschlüsselte Spielstände. Da diese konsolenspezifisch verschl
|
|||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||
|
||||
## Erstellen
|
||||
|
||||
PKHeX ist eine Windows Forms Anwendung, welche die [.NET 10.0](https://dotnet.microsoft.com/download/dotnet/10.0) runtime benötigt.
|
||||
PKHeX ist eine Windows Forms Anwendung, die das [.NET Framework v4.6](https://www.microsoft.com/de-de/download/details.aspx?id=48137) benötigt, mit experimenteller Unterstützung für [.NET 6.0](https://dotnet.microsoft.com/download/dotnet/6.0).
|
||||
|
||||
Die Anwendung kann mit jedem Kompiler erstellt werden, der C# 14 unterstützt.
|
||||
Die Anwendung kann mit jedem Kompiler erstellt werden, der C# 10 unterstützt.
|
||||
|
||||
### Erstell Konfiguration
|
||||
|
||||
|
|
@ -47,3 +47,7 @@ PKHeXs Pokémon Legends: Arceus Sprites kommen vom [National Pokédex - Icon Dex
|
|||
### IDE
|
||||
|
||||
PKHeX kann mit IDEs wie [Visual Studio](https://visualstudio.microsoft.com/de/downloads/) durch das Öffnen der .sln oder .csproj Dateien geöffnet werden.
|
||||
|
||||
### GNU/Linux
|
||||
|
||||
Da GNU/Linux nicht das Hauptbetriebssystem der PKHeX Entwickler ist können Bugs auftreten. Manche kommen möglicherweise von GNU/Linux spezifischem Code in Mono/Wine und können deshalb nicht von jedem reproduziert werden kann.
|
||||
|
|
|
|||
14
.github/README-es.md
vendored
14
.github/README-es.md
vendored
|
|
@ -7,7 +7,7 @@ Editor de guardado de las series principales de Pokémon, programado en [C#](htt
|
|||
Soporta los siguientes archivos:
|
||||
* Archivos de guardado ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin)
|
||||
* Archivos de Memory Card de GameCube (\*.raw, \*.bin) que contienen archivos de GC Pokémon.
|
||||
* Archivos de entidades individuales de Pokémon (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
|
||||
* Archivos de entidades individuales de Pokémon (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4)
|
||||
* Archivos de Regalos Misteriosos (\*.pgt, \*.pcd, \*.pgf, .wc\*) incluyendo conversión a .pk\*
|
||||
* Importar archivos de entidades de GO Park (\*.gp1) incluyendo conversión a .pb7
|
||||
* Importar equipos desde archivos Decrypted 3DS Battle Videos
|
||||
|
|
@ -18,19 +18,19 @@ La interfaz gráfica puede ser traducida con archivos de texto externos para dar
|
|||
|
||||
Pokémon Showdown asigna un código QR que puede ser importado/exportado para ayudar al compartir.
|
||||
|
||||
PKHeX espera archivos de guardado que no estén cifrados con las claves específicas de la consola. Use un gestor de archivos de guardado para importar y exportar información de la consola ([Checkpoint](https://github.com/FlagBrew/Checkpoint) o [JKSM](https://github.com/J-D-K/JKSM)).
|
||||
PKHeX espera archivos de guardado que no estén cifrados con las claves específicas de la consola. Use un gestor de archivos de guardado para importar y exportar información de la consola ([Checkpoint](https://github.com/FlagBrew/Checkpoint), save_manager, [JKSM](https://github.com/J-D-K/JKSM), o SaveDataFiler).
|
||||
|
||||
**No apoyamos ni toleramos las trampas a expensas de otros. No uses un Pokémon modificado significativamente en batalla o en intercambios con quienes no estén al tanto de que estás usando un Pokémon modificado.**
|
||||
|
||||
## Capturas de Pantalla
|
||||
|
||||

|
||||

|
||||
|
||||
## Building
|
||||
|
||||
PKHeX es una aplicación de Windows Forms que requiere [.NET 10.0](https://dotnet.microsoft.com/download/dotnet/10.0).
|
||||
PKHeX es una aplicación de Windows Forms que requiere [.NET Framework v4.6](https://www.microsoft.com/es-es/download/details.aspx?id=48137), con soporte experimental para [.NET 6.0](https://dotnet.microsoft.com/download/dotnet/6.0).
|
||||
|
||||
El archivo ejecutable puede ser construido con cualquier compilador que soporte C# 14.
|
||||
El archivo ejecutable puede ser construido con cualquier compilador que soporte C# 10.
|
||||
|
||||
### Configuraciones del Build
|
||||
|
||||
|
|
@ -47,3 +47,7 @@ PKHeX's Pokémon Legends: Arceus sprite collection is taken from the [National P
|
|||
### IDE
|
||||
|
||||
PKHeX se puede abrir con un IDE como [Visual Studio](https://visualstudio.microsoft.com/es/downloads/), abriendo los archivos .sln o .csproj.
|
||||
|
||||
### GNU/Linux
|
||||
|
||||
GNU/Linux no es el sistema operativo principal de los desarrolladores de este proyecto, así que probablemente haya errores o bugs; de los cuales algunos pueden provenir de código no específico de GNU/Linux desde Mono o de Wine, con lo cual puede haber otros usuarios que no puedan reproducir ese error.
|
||||
|
|
|
|||
54
.github/README-fr.md
vendored
54
.github/README-fr.md
vendored
|
|
@ -1,48 +1,52 @@
|
|||
PKHeX
|
||||
=====
|
||||

|
||||

|
||||
|
||||
Éditeur de sauvegarde des jeux principaux de la série Pokémon, programmé en [C#](https://fr.wikipedia.org/wiki/C_sharp).
|
||||
Éditeur de sauvegarde de la série de base Pokémon, programmé en [C#](https://fr.wikipedia.org/wiki/C_sharp).
|
||||
|
||||
Les fichiers suivants sont pris en charge :
|
||||
* Fichiers de sauvegarde (« main », \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin)
|
||||
* Fichiers de carte mémoire GameCube (\*.raw, \*.bin) contenant des sauvegardes de jeux Pokémon GameCube.
|
||||
* Fichiers d'entités Pokémon individuels (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
|
||||
* Fichiers de Cadeau Mystère (\*.pgt, \*.pcd, \*.pgf, .wc\*), incluant la conversion en .pk\*
|
||||
* Importation d'entités GO Park (\*.gp1), incluant la conversion en .pb7
|
||||
* Importation d'équipes à partir de vidéos de combat 3DS déchiffrées
|
||||
* Transfert d'une génération à l'autre, avec une conversion du format au passage.
|
||||
Prend en charge les fichiers suivants :
|
||||
* Enregistrer les fichiers ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin)
|
||||
* Fichiers de carte mémoire GameCube (\*.raw, \*.bin) contenant des sauvegardes de Pokémon GC.
|
||||
* Fichiers d'entités Pokémon individuels (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4)
|
||||
* Fichiers de cadeau mystère (\*.pgt, \*.pcd, \*.pgf, .wc\*) y compris la conversion en .pk\*
|
||||
* Importation d'entités GO Park (\*.gp1) incluant la conversion en .pb7
|
||||
* Importation d'équipes à partir de 3DS Battle Videos
|
||||
* Transfert d'une génération à l'autre, conversion des formats en cours de route.
|
||||
|
||||
Les données sont affichées sur une interface graphique, permettant de faire des modifications et des sauvegardes. L'interface peut être traduite avec des fichiers de ressources/textes externes afin que différentes langues puissent être prises en charge.
|
||||
Les données sont affichées dans une vue qui peut être modifiée et enregistrée. L'interface peut être traduite avec des fichiers de ressources/textes externes afin que différentes langues puissent être prises en charge.
|
||||
|
||||
Les sets Pokémon Showdown! et les QR codes peuvent être importés/exportés pour faciliter le partage.
|
||||
Les ensembles Pokémon Showdown et les QR codes peuvent être importés/exportés pour faciliter le partage.
|
||||
|
||||
PKHeX demande des fichiers de sauvegarde qui ne sont pas chiffrés par des clés spécifiques aux consoles. Utilisez un gestionnaire de sauvegardes pour importer et exporter des sauvegardes depuis une console ([Checkpoint](https://github.com/FlagBrew/Checkpoint), save_manager, [JKSM](https://github.com/J-D-K/JKSM), ou SaveDataFiler).
|
||||
PKHeX attend des fichiers de sauvegarde qui ne sont pas chiffrés avec des clés spécifiques à la console. Utilisez un gestionnaire de données enregistrées pour importer et exporter des données enregistrées à partir de la console ([Checkpoint](https://github.com/FlagBrew/Checkpoint), save_manager, [JKSM](https://github.com/J-D-K/JKSM) ou SaveDataFiler).
|
||||
|
||||
**Nous ne soutenons ni ne tolérons la tricherie aux dépens des autres. N'utilisez pas de Pokémon piratés de manière significative en combat ou en échanges avec ceux qui ne savent pas que des Pokémon piratés sont utilisés.**
|
||||
**Nous ne soutenons ni ne tolérons la tricherie aux dépens des autres. N'utilisez pas de Pokémon piratés de manière significative au combat ou dans des échanges avec ceux qui ne savent pas que des Pokémon piratés sont en cours d'utilisation.**
|
||||
|
||||
## Capture d'écran
|
||||
## Captures d'écran
|
||||
|
||||

|
||||

|
||||
|
||||
## Compilation
|
||||
## Construction
|
||||
|
||||
PKHeX est une application Windows Forms qui nécessite [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0).
|
||||
PKHeX est une application Windows Forms qui nécessite [.NET Framework v4.6](https://www.microsoft.com/fr-fr/download/details.aspx?id=48137), avec une prise en charge expérimentale de [.NET 6.0.](https://dotnet.microsoft.com/download/dotnet/6.0)
|
||||
|
||||
L'exécutable peut être compilé avec n'importe quel compilateur prenant en charge C# 14.
|
||||
L'exécutable peut être construit avec n'importe quel compilateur prenant en charge C# 10.
|
||||
|
||||
### Configurations de la compilation
|
||||
### Construire les configurations
|
||||
|
||||
Utilisez la configuration Debug ou Release lors de la compilation. Aucun code spécifique à une plateforme n'est utilisée dans le programme, donc soyez sans crainte !
|
||||
Utilisez les configurations Debug ou Release lors de la construction. Il n'y a pas de code spécifique à la plate-forme à craindre!
|
||||
|
||||
## Dépendances
|
||||
|
||||
Le code de génération des QR codes de PKHeX est extrait de [QRCoder](https://github.com/codebude/QRCoder), qui est [sous licence MIT](https://github.com/codebude/QRCoder/blob/master/LICENSE.txt).
|
||||
Le code de génération du QR code de PKHeX est extrait de [QRCoder](https://github.com/codebude/QRCoder), qui est [sous licence MIT](https://github.com/codebude/QRCoder/blob/master/LICENSE.txt).
|
||||
|
||||
La collection de sprites chromatiques de PKHeX est tirée de [pokesprite](https://github.com/msikma/pokesprite), qui est [sous licence MIT](https://github.com/msikma/pokesprite/blob/master/LICENSE).
|
||||
La collection de sprites shiny de PKHeX est tirée de [pokesprite](https://github.com/msikma/pokesprite), qui est [sous licence MIT](https://github.com/msikma/pokesprite/blob/master/LICENSE).
|
||||
|
||||
La collection de sprites Légendes Pokémon : Arceus de PKHeX est tirée du projet [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934), avec son abondance de collaborateurs et de contributeurs.
|
||||
PKHeX's Pokémon Legends: Arceus sprite collection is taken from the [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934) project and its abundance of collaborators and contributors.
|
||||
|
||||
## IDE
|
||||
|
||||
PKHeX peut être ouvert avec des IDEs tels que [Visual Studio](https://visualstudio.microsoft.com/fr/downloads/) en ouvrant le fichier .sln ou .csproj.
|
||||
PKHeX peut être ouvert avec des IDE tels que [Visual Studio](https://visualstudio.microsoft.com/fr/downloads/) en ouvrant le fichier .sln ou .csproj.
|
||||
|
||||
## GNU/Linux
|
||||
|
||||
GNU/Linux n'est pas le système d'exploitation principal des développeurs de ce programme, il peut donc y avoir des bugues; certains peuvent provenir de code non spécifique à GNU/Linux de Mono/Wine, donc d'autres utilisateurs peuvent ne pas être en mesure de reproduire l'erreur que vous rencontrez.
|
||||
|
|
|
|||
26
.github/README-it.md
vendored
26
.github/README-it.md
vendored
|
|
@ -4,37 +4,37 @@ PKHeX
|
|||
|
||||
Editor di Salvataggi Pokémon per la serie principale, programmato in [C#](https://it.wikipedia.org/wiki/C_sharp).
|
||||
|
||||
Supporta i seguenti tipi di file:
|
||||
Supporta i seguenti file:
|
||||
* File di salvataggio ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin)
|
||||
* File di Memory Card GameCube (\*.raw, \*.bin) contenenti File di Salvataggio Pokémon.
|
||||
* File di Entità Pokémon individuali (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
|
||||
* File di Entità Pokémon individuali (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4)
|
||||
* File di Dono Segreto (\*.pgt, \*.pcd, \*.pgf, .wc\*) inclusa conversione in .pk\*
|
||||
* Importazione di Entità del GO Park (\*.gp1) inclusa conversione in .pb7
|
||||
* Importazione di Entità del Go Park (\*.gp1) inclusa conversione in .pb7
|
||||
* Importazione di squadre da Video Lotta del 3DS decriptati
|
||||
* Trasferimento da una generazione all'altra, convertendo i formati propriamente.
|
||||
|
||||
I dati sono mostrati in una finestra che può essere modificata e salvata.
|
||||
L'interfaccia può essere tradotta tramite risorse/file di testo esterni, così che sia possibile supportare diverse lingue.
|
||||
L'interfaccia può essere tradotta con risorse/file di testo esterni, così che sia possibile supportare diversi linguaggi.
|
||||
|
||||
Set di Pokémon Showdown e QR Code possono essere importati/esportati per agevolare la condivisione di file.
|
||||
|
||||
PKHeX si aspetta file di salvataggio non criptati con le chiavi specifiche della console. È possibile utilizzare un gestore di salvataggi per importare ed esportare dati di salvataggio dalla console (come [Checkpoint](https://github.com/FlagBrew/Checkpoint) o [JKSM](https://github.com/J-D-K/JKSM)).
|
||||
PKHeX si aspetta file di salvataggio non criptati con le chiavi specifiche della console. È possibile usare un gestore di salvataggi per importare ed esportare dati di salvataggio dalla console ([Checkpoint](https://github.com/FlagBrew/Checkpoint), save_manager, [JKSM](https://github.com/J-D-K/JKSM), o SaveDataFiler).
|
||||
|
||||
**Non supportiamo e non perdoniamo l'utilizzo di cheat a scapito di altri giocatori. Non utilizzare Pokémon alterati in lotte o scambi con giocatori che non ne sono a conoscenza.**
|
||||
**Non supportiamo e non perdoniamo l'utilizzo di cheat a scapito di altri giocatori. Non utilizzare Pokémon modificati significativamente in lotte o scambi con giocatori che non ne sono a conoscenza.**
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||
|
||||
## Building
|
||||
|
||||
PKHeX è un applicazione Windows Form che necessita del [.NET Desktop Runtime 10.0](https://dotnet.microsoft.com/download/dotnet/10.0).
|
||||
PKHeX è un applicazione Windows Form e necessita [.NET Framework v4.6](https://www.microsoft.com/it-it/download/details.aspx?id=48137), con supporto sperimentale per [.NET 6.0](https://dotnet.microsoft.com/download/dotnet/6.0).
|
||||
|
||||
L'eseguibile può essere compilato con qualsiasi compiler che supporti C# 14.
|
||||
L'eseguibile può essere compilato con qualsiasi compiler che supporti C# 10.
|
||||
|
||||
### Configurazioni di Build
|
||||
|
||||
È possibile utilizzare la configurazione Debug o la configurazione Release per compilare la soluzione. Non c'è alcun codice specifico per piattaforma di cui doversi preoccupare!
|
||||
Puoi utilizzare la configurazione Debug o la configurazione Release per compilare. Non c'è alcun codice specifico per piattaforma di cui doversi preoccupare!
|
||||
|
||||
## Dipendenze
|
||||
|
||||
|
|
@ -42,8 +42,12 @@ Il codice per la generazione di QR Code è preso da [QRCoder](https://github.com
|
|||
|
||||
La collezione di sprite shiny è presa da [pokesprite](https://github.com/msikma/pokesprite), concessa in licenza sotto [the MIT license](https://github.com/msikma/pokesprite/blob/master/LICENSE).
|
||||
|
||||
La collezione di sprite per Leggende Pokémon: Arceus è presa dal progetto [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934), grazie a tutti i collaboratori e contribuenti.
|
||||
La collezione di sprite per Leggende Pokémon: Arceus è presa dal [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934) project grazie all'abbondanza dei collaboratori e dei contribuenti.
|
||||
|
||||
### IDE
|
||||
|
||||
PKHeX può essere aperto con IDE come [Visual Studio](https://visualstudio.microsoft.com/it/downloads/) aprendo il file .sln o il file .csproj.
|
||||
|
||||
### GNU/Linux
|
||||
|
||||
GNU/Linux non è il Sistema Operativo principale dei developer di questo programma, quindi potrebbero esserci bug; alcuni potrebbero provenire da codice GNU/Linux non specifico da Mono/Wine, per cui alcuni utenti potrebbero non essere in grado di riprodurre l'errore riscontrato.
|
||||
|
|
|
|||
49
.github/README-ko.md
vendored
49
.github/README-ko.md
vendored
|
|
@ -1,49 +0,0 @@
|
|||
PKHeX(포케헥스)
|
||||
=====
|
||||

|
||||
|
||||
포켓몬 코어 시리즈 세이브 에디터, [C#](https://en.wikipedia.org/wiki/C_Sharp_%28programming_language%29)으로 프로그래밍됨.
|
||||
|
||||
다음 파일을 지원합니다:
|
||||
* 세이브 파일 ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin)
|
||||
* GC 포켓몬 세이브 게임이 들어 있는 게임큐브 메모리 카드 파일(\*.raw, \*.bin) 포함
|
||||
* 개별 포켓몬 엔티티 파일 (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
|
||||
* 이상한소포 파일(\*.pgt, \*.pcd, \*.pgf, .wc\*)을 .pk로 변환하는 기능 포함
|
||||
* GO 파크 엔티티 가져오기 (\*.gp1) .pb7로 변환 포함
|
||||
* Decrypted 3DS Battle Videos에서 팀 가져오기
|
||||
* 한 세대에서 다른 세대로 이동하면서 그 과정에서 형식이 변환됩니다.
|
||||
|
||||
데이터는 편집하고 저장할 수 있는 보기로 표시됩니다.
|
||||
인터페이스는 리소스/외부 텍스트 파일로 번역할 수 있어 다양한 언어를 지원할 수 있습니다.
|
||||
|
||||
포켓몬 쇼다운 세트와 QR 코드를 가져오고 내보낼 수 있어 공유에 도움을 줄 수 있습니다.
|
||||
|
||||
PKHeX는 콘솔 전용 키로 암호화되지 않은 세이브 파일을 요구합니다. ([Checkpoint](https://github.com/FlagBrew/Checkpoint), save_manager, [JKSM](https://github.com/J-D-K/JKSM), 또는 SaveDataFiler)를 사용하여 콘솔에서 세이브 데이터를 가져오고 내보낼 수 있습니다.
|
||||
|
||||
**저희는 타인을 희생시키는 부정행위를 지지하거나 묵인하지 않습니다. 해킹된 포켓몬이 사용 중이라는 사실을 모르는 사람들과의 배틀 또는 통신에서 심각하게 해킹된 포켓몬을 사용하지 마십시오.**
|
||||
|
||||
## 스크린샷
|
||||
|
||||

|
||||
|
||||
## 빌드
|
||||
|
||||
PKHeX는 [.NET 10.0](https://dotnet.microsoft.com/download/dotnet/10.0)이 필요한 Windows Forms 애플리케이션입니다.
|
||||
|
||||
실행 파일은 C# 14을 지원하는 모든 컴파일러로 빌드할 수 있습니다.
|
||||
|
||||
### 빌드 구성
|
||||
|
||||
빌드할 때 디버그 또는 릴리스 빌드 구성을 사용하세요. 플랫폼 전용 코드는 걱정할 필요가 없습니다!
|
||||
|
||||
## 종속성
|
||||
|
||||
PKHeX의 QR 코드 생성 코드는 [the MIT license](https://github.com/codebude/QRCoder/blob/master/LICENSE.txt)에 따라 라이선스가 부여된 [QRCoder](https://github.com/codebude/QRCoder) 에서 가져왔습니다.
|
||||
|
||||
PKHeX의 이로치(색이다른) 스프라이트 컬렉션은 [the MIT license](https://github.com/msikma/pokesprite/blob/master/LICENSE)에 따라 라이선스가 부여된 [pokesprite](https://github.com/msikma/pokesprite)에서 가져왔습니다.
|
||||
|
||||
PKheX의 Pokémon LEGENDS 아르세우스 스프라이트 컬렉션은 [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934) 프로젝트와 수많은 협력자 및 기여자의 도움을 받아 만들어졌습니다.
|
||||
|
||||
### IDE(통합 개발 환경)
|
||||
|
||||
PKHeX는 .sln 또는 .csproj 파일을 열어 [Visual Studio](https://visualstudio.microsoft.com/downloads/)와 같은 IDE(통합 개발 환경)로 열 수 있습니다.
|
||||
48
.github/README-zh-Hans.md
vendored
48
.github/README-zh-Hans.md
vendored
|
|
@ -1,48 +0,0 @@
|
|||
PKHeX
|
||||
=====
|
||||

|
||||
|
||||
本程序是基于 [C#](https://zh.wikipedia.org/wiki/C♯) 语言进行编写的宝可梦核心系列游戏存档编辑器。
|
||||
|
||||
支持以下存档类型:
|
||||
* 存档文件 ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin)
|
||||
* GameCube 宝可梦游戏存档包含 GameCube 记忆存档 (\*.raw, \*.bin)
|
||||
* 单个宝可梦实体文件 (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
|
||||
* 神秘礼物文件 (\*.pgt, \*.pcd, \*.pgf, .wc\*) 并转换为 .pk\*
|
||||
* 导入 GO Park存档 (\*.gp1) 并转换为 .pb7
|
||||
* 从已破解的 3DS 对战视频中导入队伍
|
||||
* 支持宝可梦在不同世代的间转移,并转换文件格式
|
||||
|
||||
各项数据显示于窗口界面上以便编辑及保存。
|
||||
该界面可以通过 内部/外部 文本文件进行翻译,以便支持不同的语言。
|
||||
|
||||
可以导入/导出宝可梦Showdown代码和二维码以协助共享。
|
||||
|
||||
PKHeX 所读取存档文件必须是未经主机唯一密钥加密,因此请使用存档管理软件(如 [Checkpoint](https://github.com/FlagBrew/Checkpoint), save_manager, [JKSM](https://github.com/J-D-K/JKSM)或 SaveDataFiler)以从主机中导入导出存档 .
|
||||
|
||||
**我们反对且不会纵容通过作弊手段损害他人利益的行为。切勿将魔法宝可梦用于对战,或通过连接交换给不知情的训练家手中。**
|
||||
|
||||
## 截图
|
||||
|
||||

|
||||
|
||||
## 构建
|
||||
|
||||
PKHeX 是 Windows 窗口应用程序,依赖于 [.NET 10.0](https://dotnet.microsoft.com/download/dotnet/10.0)。
|
||||
|
||||
可以使用任何支持 C# 14 的编译器生成可执行文件。
|
||||
|
||||
### 构建配置
|
||||
|
||||
请使用 Debug 及 Release 配置通道进行构建,无须担心任何平台独有源代码相关问题!
|
||||
|
||||
## 依赖库
|
||||
|
||||
PKHeX 的 QR 码生成库来源于 [QRCoder](https://github.com/codebude/QRCoder), 依据 [MIT 许可证](https://github.com/codebude/QRCoder/blob/master/LICENSE.txt) 进行授权。
|
||||
|
||||
PKHeX 的异色精灵图示集合库来源于 [pokesprite](https://github.com/msikma/pokesprite), 依据 [MIT 许可证](https://github.com/codebude/QRCoder/blob/master/LICENSE.txt) 进行授权。
|
||||
|
||||
PKHeX 的“宝可梦传说:阿尔宙斯”精灵图片集来源于 [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934) 项目,及其多位各界协作者和贡献者。
|
||||
### IDE
|
||||
|
||||
PKHeX 可以通过打开 .sln 或 .csproj 文件来使用 [Visual Studio](https://visualstudio.microsoft.com/downloads/) 等 IDE 打开。
|
||||
48
.github/README-zh-Hant.md
vendored
48
.github/README-zh-Hant.md
vendored
|
|
@ -1,48 +0,0 @@
|
|||
PKHeX
|
||||
=====
|
||||

|
||||
|
||||
本程式係基於 [C#](https://zh.wikipedia.org/wiki/C♯) 語言進行編寫之寶可夢核心系列遊戲儲存資料編輯器。
|
||||
|
||||
本程式支援下述檔案:
|
||||
* 儲存資料檔 ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin)
|
||||
* 含 GameCube 寶可夢游戲儲存資料檔之 GameCube 記憶卡檔 (\*.raw, \*.bin)
|
||||
* 單個寶可夢資料檔 (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
|
||||
* 神秘禮物檔 (\*.pgt, \*.pcd, \*.pgf, .wc\*) 並轉換至 .pk\*
|
||||
* 匯入 Go 公園檔 (\*.gp1) 並轉換至 .pb7
|
||||
* 從已解密 3DS 對戰視訊中匯入隊伍信息
|
||||
* 將寶可夢從當前世代往後傳送,並順次轉換檔案格式
|
||||
|
||||
各項數據將顯示於介面上以便編輯及儲存。
|
||||
介面亦可透過挂載內置/外置文字檔案以支援顯示多種語言。
|
||||
|
||||
可匯入/匯出 配置資訊及 QR 碼以便進行共有分享。
|
||||
|
||||
PKHeX 所讀取檔案須未經主機唯一密鑰加密,因而請使用儲存資料管理軟體(如 [Checkpoint](https://github.com/FlagBrew/Checkpoint), [JKSM](https://github.com/J-D-K/JKSM))以從主機中匯入匯出儲存資料 .
|
||||
|
||||
**我們反對亦不會縱容透過作弊手段損害他人利益之行為。切勿將魔法寶可夢用於對戰,或連線交換至不知情之訓練家手中。**
|
||||
|
||||
## 螢幕擷取截圖
|
||||
|
||||

|
||||
|
||||
## 構建
|
||||
|
||||
PKHeX 係 Windows 窗體應用程式,其須依賴於 [.NET 10.0](https://dotnet.microsoft.com/download/dotnet/10.0)。
|
||||
|
||||
程式可透過任意支援 C# 14 之編譯器構建。
|
||||
|
||||
### 構建配置
|
||||
|
||||
請使用 Debug 及 Release 配置通道進行構建,無須擔心任何平台獨有源代碼相關問題!
|
||||
|
||||
## 依賴庫
|
||||
|
||||
PKHeX 之 QR 碼生成庫來源於 [QRCoder](https://github.com/codebude/QRCoder), 其以 [MIT 許可證](https://github.com/codebude/QRCoder/blob/master/LICENSE.txt) 進行分發。
|
||||
|
||||
PKHeX 之異色精靈圖示集合庫來源於 [pokesprite](https://github.com/msikma/pokesprite), 其以 [MIT 許可證](https://github.com/msikma/pokesprite/blob/master/LICENSE) 進行分發。
|
||||
|
||||
PKHeX 之「寶可夢傳説:阿爾宙斯」精靈圖示集合庫來源於 [National Pokédex - Icon Dex](https://www.deviantart.com/pikafan2000/art/National-Pokedex-Version-Delta-Icon-Dex-824897934) 項目,及其多位各界協作者和貢獻者。
|
||||
### IDE
|
||||
|
||||
PKHeX 可透過如 [Visual Studio](https://visualstudio.microsoft.com/downloads/) 等各類 IDE ,開啓 .sln 或 .csproj 檔案以打開。
|
||||
42
.gitignore
vendored
42
.gitignore
vendored
|
|
@ -169,17 +169,6 @@ UpgradeLog*.htm
|
|||
App_Data/*.mdf
|
||||
App_Data/*.ldf
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
#################
|
||||
## Jetbrains
|
||||
#################
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
#############
|
||||
## Qt Creator
|
||||
|
|
@ -227,6 +216,37 @@ $RECYCLE.BIN/
|
|||
pingme.txt
|
||||
|
||||
|
||||
#############
|
||||
## Python
|
||||
#############
|
||||
|
||||
*.py[co]
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist/
|
||||
build/
|
||||
eggs/
|
||||
parts/
|
||||
var/
|
||||
sdist/
|
||||
develop-eggs/
|
||||
.installed.cfg
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
|
||||
#################
|
||||
## MonoDevelop
|
||||
#################
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>26.03.20</Version>
|
||||
<LangVersion>14</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
<Product>PKHeX</Product>
|
||||
<Company>Project Pokémon</Company>
|
||||
<Authors>Kaphotics</Authors>
|
||||
<Copyright>Kaphotics</Copyright>
|
||||
<SourceRevisionId>$([System.DateTime]::UtcNow.ToString("yyMMddHHmmss"))</SourceRevisionId>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.Ball;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
|
@ -8,149 +10,152 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
public static class BallApplicator
|
||||
{
|
||||
private const Ball BallMin = Master; // first defined Enum value
|
||||
private const Ball BallMax = LAOrigin; // all indexes up to and including LAOrigin are defined Enum values.
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of <see cref="Ball"/> values that can be returned in a span.
|
||||
/// </summary>
|
||||
public const byte MaxBallSpanAlloc = (byte)BallMax + 1;
|
||||
|
||||
private static IEncounterTemplate Get(LegalityAnalysis la) => la.EncounterOriginal;
|
||||
|
||||
/// <remarks>
|
||||
/// Requires checking the <see cref="LegalityAnalysis"/>.
|
||||
/// </remarks>
|
||||
/// <inheritdoc cref="GetLegalBalls(Span{Ball}, PKM, IEncounterTemplate)"/>
|
||||
public static int GetLegalBalls(Span<Ball> result, PKM pk) => GetLegalBalls(result, pk, new LegalityAnalysis(pk));
|
||||
|
||||
/// <inheritdoc cref="GetLegalBalls(Span{Ball}, PKM, IEncounterTemplate)"/>
|
||||
public static int GetLegalBalls(Span<Ball> result, PKM pk, LegalityAnalysis la) => GetLegalBalls(result, pk, Get(la));
|
||||
|
||||
/// <summary>
|
||||
/// Gets all balls that are legal for the input <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
/// <param name="result">Result storage.</param>
|
||||
/// <param name="pk">Pokémon to retrieve a list of valid balls for.</param>
|
||||
/// <param name="enc">Encounter matched to.</param>
|
||||
/// <returns>Count of <see cref="Ball"/> values that the <see cref="PKM"/> is legal with.</returns>
|
||||
public static int GetLegalBalls(Span<Ball> result, PKM pk, IEncounterTemplate enc)
|
||||
{
|
||||
if (enc is EncounterInvalid)
|
||||
return 0;
|
||||
if (enc.Species is (ushort)Species.Nincada && pk.Species is (ushort)Species.Shedinja)
|
||||
return GetLegalBallsEvolvedShedinja(result, pk, enc);
|
||||
return LoadLegalBalls(result, pk, enc);
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<Ball> ShedinjaEvolve4 => [Sport, Poke];
|
||||
|
||||
private static int GetLegalBallsEvolvedShedinja(Span<Ball> result, PKM pk, IEncounterTemplate enc)
|
||||
{
|
||||
switch (enc)
|
||||
{
|
||||
case EncounterSlot4 s4 when IsNincadaEvolveInOrigin(pk, s4):
|
||||
ShedinjaEvolve4.CopyTo(result);
|
||||
return ShedinjaEvolve4.Length;
|
||||
case EncounterSlot3 s3 when IsNincadaEvolveInOrigin(pk, s3):
|
||||
return LoadLegalBalls(result, pk, enc);
|
||||
}
|
||||
result[0] = Poke;
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static bool IsNincadaEvolveInOrigin(PKM pk, IEncounterTemplate enc)
|
||||
{
|
||||
// Rough check to see if Nincada evolved in the origin context (Gen3/4).
|
||||
// Does not do PID/IV checks to know the original met level.
|
||||
var current = pk.CurrentLevel;
|
||||
var met = pk.MetLevel;
|
||||
if (pk.Format == enc.Generation)
|
||||
return current > met;
|
||||
return enc.LevelMin != met && current > enc.LevelMin;
|
||||
}
|
||||
|
||||
private static int LoadLegalBalls(Span<Ball> result, PKM pk, IEncounterTemplate enc)
|
||||
{
|
||||
int ctr = 0;
|
||||
for (var b = BallMin; b <= BallMax; b++)
|
||||
{
|
||||
if (BallVerifier.VerifyBall(enc, b, pk).IsValid)
|
||||
result[ctr++] = b;
|
||||
}
|
||||
return ctr;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Requires checking the <see cref="LegalityAnalysis"/>.
|
||||
/// Requires checking the <see cref="LegalityAnalysis"/> for every <see cref="Ball"/> that is tried.
|
||||
/// </remarks>
|
||||
/// <inheritdoc cref="ApplyBallLegalRandom(PKM, IEncounterTemplate)"/>
|
||||
public static byte ApplyBallLegalRandom(PKM pk) => ApplyBallLegalRandom(pk, new LegalityAnalysis(pk));
|
||||
|
||||
/// <inheritdoc cref="ApplyBallLegalRandom(PKM, IEncounterTemplate)"/>
|
||||
public static byte ApplyBallLegalRandom(PKM pk, LegalityAnalysis la) => ApplyBallLegalRandom(pk, Get(la));
|
||||
/// <param name="pk">Pokémon to retrieve a list of valid balls for.</param>
|
||||
/// <returns>Enumerable list of <see cref="Ball"/> values that the <see cref="PKM"/> is legal with.</returns>
|
||||
public static IEnumerable<Ball> GetLegalBalls(PKM pk)
|
||||
{
|
||||
var clone = pk.Clone();
|
||||
foreach (var b in BallList)
|
||||
{
|
||||
clone.Ball = (int)b;
|
||||
if (new LegalityAnalysis(clone).Valid)
|
||||
yield return b;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a random legal ball value if any exist.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="enc">Encounter matched to.</param>
|
||||
public static byte ApplyBallLegalRandom(PKM pk, IEncounterTemplate enc)
|
||||
{
|
||||
Span<Ball> balls = stackalloc Ball[MaxBallSpanAlloc];
|
||||
var count = GetLegalBalls(balls, pk, enc);
|
||||
balls = balls[..count];
|
||||
Util.Rand.Shuffle(balls);
|
||||
return ApplyFirstLegalBall(pk, balls, []);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Requires checking the <see cref="LegalityAnalysis"/>.
|
||||
/// Requires checking the <see cref="LegalityAnalysis"/> for every <see cref="Ball"/> that is tried.
|
||||
/// </remarks>
|
||||
/// <inheritdoc cref="ApplyBallLegalByColor(PKM, IEncounterTemplate, PersonalColor)"/>
|
||||
public static byte ApplyBallLegalByColor(PKM pk) => ApplyBallLegalByColor(pk, PersonalColorUtil.GetColor(pk));
|
||||
/// <inheritdoc cref="ApplyBallLegalByColor(PKM, IEncounterTemplate, PersonalColor)"/>
|
||||
public static byte ApplyBallLegalByColor(PKM pk, PersonalColor color) => ApplyBallLegalByColor(pk, new LegalityAnalysis(pk), color);
|
||||
/// <inheritdoc cref="ApplyBallLegalByColor(PKM, IEncounterTemplate, PersonalColor)"/>
|
||||
public static byte ApplyBallLegalByColor(PKM pk, LegalityAnalysis la, PersonalColor color) => ApplyBallLegalByColor(pk, Get(la), color);
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
public static int ApplyBallLegalRandom(PKM pk)
|
||||
{
|
||||
var balls = GetBallListFromColor(pk).ToArray();
|
||||
Util.Shuffle(balls.AsSpan());
|
||||
return ApplyFirstLegalBall(pk, balls);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a legal ball value if any exist, ordered by color.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Requires checking the <see cref="LegalityAnalysis"/> for every <see cref="Ball"/> that is tried.
|
||||
/// </remarks>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="enc">Encounter matched to.</param>
|
||||
/// <param name="color">Color preference to order by.</param>
|
||||
public static byte ApplyBallLegalByColor(PKM pk, IEncounterTemplate enc, PersonalColor color)
|
||||
public static int ApplyBallLegalByColor(PKM pk)
|
||||
{
|
||||
Span<Ball> balls = stackalloc Ball[MaxBallSpanAlloc];
|
||||
var count = GetLegalBalls(balls, pk, enc);
|
||||
balls = balls[..count];
|
||||
var prefer = PersonalColorUtil.GetPreferredByColor(enc, color);
|
||||
return ApplyFirstLegalBall(pk, balls, prefer);
|
||||
var balls = GetBallListFromColor(pk);
|
||||
return ApplyFirstLegalBall(pk, balls);
|
||||
}
|
||||
|
||||
private static byte ApplyFirstLegalBall(PKM pk, Span<Ball> legal, ReadOnlySpan<Ball> prefer)
|
||||
/// <summary>
|
||||
/// Applies a random ball value in a cyclical manner.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
public static int ApplyBallNext(PKM pk)
|
||||
{
|
||||
foreach (var ball in prefer)
|
||||
{
|
||||
if (Contains(legal, ball))
|
||||
return pk.Ball = (byte)ball;
|
||||
}
|
||||
foreach (var ball in legal)
|
||||
{
|
||||
if (!Contains(prefer, ball))
|
||||
return pk.Ball = (byte)ball;
|
||||
}
|
||||
return pk.Ball; // fail
|
||||
var balls = GetBallList(pk.Ball);
|
||||
var next = balls.First();
|
||||
return pk.Ball = (int)next;
|
||||
}
|
||||
|
||||
static bool Contains(ReadOnlySpan<Ball> balls, Ball ball)
|
||||
private static int ApplyFirstLegalBall(PKM pk, IEnumerable<Ball> balls)
|
||||
{
|
||||
foreach (var b in balls)
|
||||
{
|
||||
foreach (var b in balls)
|
||||
{
|
||||
if (b == ball)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
pk.Ball = (int)b;
|
||||
if (new LegalityAnalysis(pk).Valid)
|
||||
break;
|
||||
}
|
||||
return pk.Ball;
|
||||
}
|
||||
|
||||
private static IEnumerable<Ball> GetBallList(int ball)
|
||||
{
|
||||
var balls = BallList;
|
||||
var currentBall = (Ball)ball;
|
||||
return GetCircularOnce(balls, currentBall);
|
||||
}
|
||||
|
||||
private static IEnumerable<Ball> GetBallListFromColor(PKM pk)
|
||||
{
|
||||
// Gen1/2 don't store color in personal info
|
||||
var pi = pk.Format >= 3 ? pk.PersonalInfo : PersonalTable.USUM.GetFormEntry(pk.Species, 0);
|
||||
var color = (PersonalColor)pi.Color;
|
||||
var balls = BallColors[color];
|
||||
var currentBall = (Ball)pk.Ball;
|
||||
return GetCircularOnce(balls, currentBall);
|
||||
}
|
||||
|
||||
private static IEnumerable<T> GetCircularOnce<T>(T[] items, T current)
|
||||
{
|
||||
var currentIndex = Array.IndexOf(items, current);
|
||||
if (currentIndex < 0)
|
||||
currentIndex = items.Length - 2;
|
||||
for (int i = currentIndex + 1; i < items.Length; i++)
|
||||
yield return items[i];
|
||||
for (int i = 0; i <= currentIndex; i++)
|
||||
yield return items[i];
|
||||
}
|
||||
|
||||
private static readonly Ball[] BallList = (Ball[]) Enum.GetValues(typeof(Ball));
|
||||
|
||||
static BallApplicator()
|
||||
{
|
||||
var exclude = new[] {None, Poke};
|
||||
var end = new[] {Poke};
|
||||
var allBalls = BallList.Except(exclude).ToArray();
|
||||
var colors = (PersonalColor[])Enum.GetValues(typeof(PersonalColor));
|
||||
foreach (var c in colors)
|
||||
{
|
||||
var matchingColors = BallColors[c];
|
||||
var extra = allBalls.Except(matchingColors).ToArray();
|
||||
Util.Shuffle(extra.AsSpan());
|
||||
BallColors[c] = ArrayUtil.ConcatAll(matchingColors, extra, end);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Priority Match ball IDs that match the color ID in descending order
|
||||
/// </summary>
|
||||
private static readonly Dictionary<PersonalColor, Ball[]> BallColors = new()
|
||||
{
|
||||
[PersonalColor.Red] = new[] { Cherish, Repeat, Fast, Heal, Great, Dream, Lure },
|
||||
[PersonalColor.Blue] = new[] { Dive, Net, Great, Beast, Lure },
|
||||
[PersonalColor.Yellow] = new[] { Level, Ultra, Repeat, Quick, Moon },
|
||||
[PersonalColor.Green] = new[] { Safari, Friend, Nest, Dusk },
|
||||
[PersonalColor.Black] = new[] { Luxury, Heavy, Ultra, Moon, Net, Beast },
|
||||
|
||||
[PersonalColor.Brown] = new[] { Level, Heavy },
|
||||
[PersonalColor.Purple] = new[] { Master, Love, Dream, Heal },
|
||||
[PersonalColor.Gray] = new[] { Heavy, Premier, Luxury },
|
||||
[PersonalColor.White] = new[] { Premier, Timer, Luxury, Ultra },
|
||||
[PersonalColor.Pink] = new[] { Love, Dream, Heal },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Personal Data color IDs
|
||||
/// </summary>
|
||||
private enum PersonalColor : byte
|
||||
{
|
||||
Red,
|
||||
Blue,
|
||||
Yellow,
|
||||
Green,
|
||||
Black,
|
||||
|
||||
Brown,
|
||||
Purple,
|
||||
Gray,
|
||||
White,
|
||||
Pink,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,56 +1,39 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for applying a <see cref="PK1.CatchRate"/> value.
|
||||
/// Logic for applying a <see cref="PK1.Catch_Rate"/> value.
|
||||
/// </summary>
|
||||
public static class CatchRateApplicator
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the suggested <see cref="PK1.CatchRate"/> for the entity.
|
||||
/// </summary>
|
||||
public static int GetSuggestedCatchRate(PK1 pk, SaveFile sav)
|
||||
public static int GetSuggestedCatchRate(PK1 pk1, SaveFile sav)
|
||||
{
|
||||
var la = new LegalityAnalysis(pk);
|
||||
return GetSuggestedCatchRate(pk, sav, la);
|
||||
var la = new LegalityAnalysis(pk1);
|
||||
return GetSuggestedCatchRate(pk1, sav, la);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the suggested <see cref="PK1.CatchRate"/> for the entity.
|
||||
/// </summary>
|
||||
public static byte GetSuggestedCatchRate(PK1 pk, SaveFile sav, LegalityAnalysis la)
|
||||
public static byte GetSuggestedCatchRate(PK1 pk1, SaveFile sav, LegalityAnalysis la)
|
||||
{
|
||||
// If it is already valid, just use the current value.
|
||||
if (la.Valid)
|
||||
return pk.CatchRate;
|
||||
return pk1.Catch_Rate;
|
||||
|
||||
// If it has ever visited generation 2, the Held Item can be removed prior to trade back.
|
||||
if (la.Info.Generation == 2)
|
||||
return 0;
|
||||
|
||||
// Return the encounter's original value.
|
||||
var enc = la.EncounterMatch;
|
||||
switch (enc)
|
||||
var v = la.EncounterMatch;
|
||||
switch (v)
|
||||
{
|
||||
case EncounterGift1 { Trainer: EncounterGift1.TrainerType.Stadium, Species: (int)Species.Psyduck }:
|
||||
return pk.Japanese ? (byte)167 : (byte)168; // Amnesia Psyduck has different catch rates depending on language
|
||||
case EncounterTrade1 c:
|
||||
return c.GetInitialCatchRate();
|
||||
case EncounterStatic1E { Version: GameVersion.Stadium, Species: (int)Species.Psyduck}:
|
||||
return pk1.Japanese ? (byte)167 : (byte)168; // Amnesia Psyduck has different catch rates depending on language
|
||||
default:
|
||||
var pt = GetPersonalTable(sav, enc.Version);
|
||||
var pi = pt[enc.Species];
|
||||
return pi.CatchRate;
|
||||
{
|
||||
if (sav.Version.Contains(v.Version) || v.Version.Contains(sav.Version))
|
||||
return (byte)sav.Personal[v.Species].CatchRate;
|
||||
if (!GameVersion.RB.Contains(v.Version))
|
||||
return (byte)PersonalTable.Y[v.Species].CatchRate;
|
||||
return (byte)PersonalTable.RB[v.Species].CatchRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static PersonalTable1 GetPersonalTable(SaveFile sav, GameVersion version)
|
||||
{
|
||||
if (sav.Personal is PersonalTable1 pt)
|
||||
{
|
||||
var other = sav.Version;
|
||||
if (other.Contains(version) || version.Contains(other))
|
||||
return pt;
|
||||
}
|
||||
|
||||
if (!GameVersion.RB.Contains(version))
|
||||
return PersonalTable.Y;
|
||||
return PersonalTable.RB;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,78 +7,72 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
public static class GenderApplicator
|
||||
{
|
||||
extension(PKM pk)
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Gender"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Gender"/> value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="gender">Desired <see cref="PKM.Gender"/> value to set.</param>
|
||||
/// <remarks>Has special logic for an unspecified gender.</remarks>
|
||||
public static void SetSaneGender(this PKM pk, int gender)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Gender"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Gender"/> value.
|
||||
/// </summary>
|
||||
/// <param name="gender">Desired <see cref="PKM.Gender"/> value to set.</param>
|
||||
/// <remarks>Has special logic for an unspecified gender.</remarks>
|
||||
public void SetSaneGender(byte gender)
|
||||
int g = gender == -1 ? pk.GetSaneGender() : gender;
|
||||
pk.SetGender(g);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Gender"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Gender"/> value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="gender">Desired <see cref="PKM.Gender"/> value to set.</param>
|
||||
public static void SetGender(this PKM pk, int gender)
|
||||
{
|
||||
gender = Math.Min(2, Math.Max(0, gender));
|
||||
if (pk.Gender == gender)
|
||||
return;
|
||||
|
||||
if (pk.Format <= 2)
|
||||
{
|
||||
var g = gender > 2 ? pk.GetSaneGender() : gender;
|
||||
pk.SetGender(g);
|
||||
pk.SetAttackIVFromGender(gender);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetSaneGender(PKM, byte)"/>
|
||||
public void SetSaneGender(byte? gender)
|
||||
else if (pk.Format <= 5)
|
||||
{
|
||||
var g = gender ?? pk.GetSaneGender();
|
||||
pk.SetGender(g);
|
||||
pk.SetPIDGender(gender);
|
||||
pk.Gender = gender;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Gender"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Gender"/> value.
|
||||
/// </summary>
|
||||
/// <param name="gender">Desired <see cref="PKM.Gender"/> value to set.</param>
|
||||
public void SetGender(byte gender)
|
||||
else
|
||||
{
|
||||
gender = Math.Clamp(gender, (byte)0, (byte)2);
|
||||
if (pk.Gender == gender)
|
||||
return;
|
||||
|
||||
if (pk.Format <= 2)
|
||||
{
|
||||
pk.SetAttackIVFromGender(gender);
|
||||
}
|
||||
else if (pk.Format <= 5)
|
||||
{
|
||||
pk.SetPIDGender(gender);
|
||||
pk.Gender = gender;
|
||||
}
|
||||
else
|
||||
{
|
||||
pk.Gender = gender;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanity checks the provided <see cref="PKM.Gender"/> value, and returns a sane value.
|
||||
/// </summary>
|
||||
/// <returns>Most-legal <see cref="PKM.Gender"/> value</returns>
|
||||
public byte GetSaneGender()
|
||||
{
|
||||
var gt = pk.PersonalInfo.Gender;
|
||||
switch (gt)
|
||||
{
|
||||
case PersonalInfo.RatioMagicGenderless: return 2;
|
||||
case PersonalInfo.RatioMagicFemale: return 1;
|
||||
case PersonalInfo.RatioMagicMale: return 0;
|
||||
}
|
||||
if (!pk.IsGenderValid())
|
||||
return EntityGender.GetFromPIDAndRatio(pk.PID, gt);
|
||||
return pk.Gender;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="PKM.IV_ATK"/> for a Generation 1/2 format <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
/// <param name="gender">Desired <see cref="PKM.Gender"/>.</param>
|
||||
public void SetAttackIVFromGender(byte gender)
|
||||
{
|
||||
var rnd = Util.Rand;
|
||||
while (pk.Gender != gender)
|
||||
pk.IV_ATK = rnd.Next(16);
|
||||
pk.Gender = gender;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanity checks the provided <see cref="PKM.Gender"/> value, and returns a sane value.
|
||||
/// </summary>
|
||||
/// <param name="pk"></param>
|
||||
/// <returns>Most-legal <see cref="PKM.Gender"/> value</returns>
|
||||
public static int GetSaneGender(this PKM pk)
|
||||
{
|
||||
int gt = pk.PersonalInfo.Gender;
|
||||
switch (gt)
|
||||
{
|
||||
case PersonalInfo.RatioMagicGenderless: return 2;
|
||||
case PersonalInfo.RatioMagicFemale: return 1;
|
||||
case PersonalInfo.RatioMagicMale: return 0;
|
||||
}
|
||||
if (!pk.IsGenderValid())
|
||||
return EntityGender.GetFromPIDAndRatio(pk.PID, gt);
|
||||
return pk.Gender;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="PKM.IV_ATK"/> for a Generation 1/2 format <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="gender">Desired <see cref="PKM.Gender"/>.</param>
|
||||
public static void SetAttackIVFromGender(this PKM pk, int gender)
|
||||
{
|
||||
var rnd = Util.Rand;
|
||||
while (pk.Gender != gender)
|
||||
pk.IV_ATK = rnd.Next(16);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,24 +7,23 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
public static class HiddenPowerApplicator
|
||||
{
|
||||
extension(PKM pk)
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.IVs"/> to match a provided <see cref="hiddenPowerType"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="hiddenPowerType">Desired Hidden Power typing.</param>
|
||||
public static void SetHiddenPower(this PKM pk, int hiddenPowerType)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.IVs"/> to match a provided <see cref="hiddenPowerType"/>.
|
||||
/// </summary>
|
||||
/// <param name="hiddenPowerType">Desired Hidden Power typing.</param>
|
||||
public void SetHiddenPower(int hiddenPowerType)
|
||||
{
|
||||
Span<int> IVs = stackalloc int[6];
|
||||
pk.GetIVs(IVs);
|
||||
HiddenPower.SetIVsForType(hiddenPowerType, IVs, pk.Context);
|
||||
pk.SetIVs(IVs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.IVs"/> to match a provided <see cref="hiddenPowerType"/>.
|
||||
/// </summary>
|
||||
/// <param name="hiddenPowerType">Desired Hidden Power typing.</param>
|
||||
public void SetHiddenPower(MoveType hiddenPowerType) => pk.SetHiddenPower((int)hiddenPowerType);
|
||||
Span<int> IVs = stackalloc int[6];
|
||||
pk.GetIVs(IVs);
|
||||
HiddenPower.SetIVsForType(hiddenPowerType, IVs, pk.Context);
|
||||
pk.SetIVs(IVs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.IVs"/> to match a provided <see cref="hiddenPowerType"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="hiddenPowerType">Desired Hidden Power typing.</param>
|
||||
public static void SetHiddenPower(this PKM pk, MoveType hiddenPowerType) => pk.SetHiddenPower((int)hiddenPowerType);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using System;
|
||||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for modifying the <see cref="IAppliedMarkings"/>.
|
||||
/// Logic for modifying the <see cref="PKM.MarkValue"/>.
|
||||
/// </summary>
|
||||
public static class MarkingApplicator
|
||||
{
|
||||
|
|
@ -14,42 +14,21 @@ public static class MarkingApplicator
|
|||
public static Func<PKM, Func<int, int, int>> MarkingMethod { get; set; } = FlagHighLow;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the applied Markings to indicate flawless (or near-flawless) <see cref="PKM.IVs"/>.
|
||||
/// Sets the <see cref="PKM.MarkValue"/> to indicate flawless (or near-flawless) <see cref="PKM.IVs"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
public static void SetMarkings(this PKM pk)
|
||||
{
|
||||
if (pk is not IAppliedMarkings { MarkingCount: 6 })
|
||||
if (pk.MarkingCount < 6)
|
||||
return; // insufficient marking indexes
|
||||
|
||||
if (pk is IAppliedMarkings<MarkingColor> c)
|
||||
c.SetMarkings(pk);
|
||||
else if (pk is IAppliedMarkings<bool> b)
|
||||
b.SetMarkings(pk);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetMarkings(PKM)"/>
|
||||
public static void SetMarkings(this IAppliedMarkings<bool> mark, PKM pk)
|
||||
{
|
||||
var method = MarkingMethod(pk);
|
||||
mark.SetMarking(0, method(pk.IV_HP , 0) == 1);
|
||||
mark.SetMarking(1, method(pk.IV_ATK, 1) == 1);
|
||||
mark.SetMarking(2, method(pk.IV_DEF, 2) == 1);
|
||||
mark.SetMarking(3, method(pk.IV_SPA, 3) == 1);
|
||||
mark.SetMarking(4, method(pk.IV_SPD, 4) == 1);
|
||||
mark.SetMarking(5, method(pk.IV_SPE, 5) == 1);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetMarkings(PKM)"/>
|
||||
public static void SetMarkings(this IAppliedMarkings<MarkingColor> mark, PKM pk)
|
||||
{
|
||||
var method = MarkingMethod(pk);
|
||||
mark.SetMarking(0, (MarkingColor)method(pk.IV_HP, 0));
|
||||
mark.SetMarking(1, (MarkingColor)method(pk.IV_ATK, 1));
|
||||
mark.SetMarking(2, (MarkingColor)method(pk.IV_DEF, 2));
|
||||
mark.SetMarking(3, (MarkingColor)method(pk.IV_SPA, 3));
|
||||
mark.SetMarking(4, (MarkingColor)method(pk.IV_SPD, 4));
|
||||
mark.SetMarking(5, (MarkingColor)method(pk.IV_SPE, 5));
|
||||
pk.SetMarking(0, method(pk.IV_HP , 0));
|
||||
pk.SetMarking(1, method(pk.IV_ATK, 1));
|
||||
pk.SetMarking(2, method(pk.IV_DEF, 2));
|
||||
pk.SetMarking(3, method(pk.IV_SPA, 3));
|
||||
pk.SetMarking(4, method(pk.IV_SPD, 4));
|
||||
pk.SetMarking(5, method(pk.IV_SPE, 5));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -58,19 +37,18 @@ public static void SetMarkings(this IAppliedMarkings<MarkingColor> mark, PKM pk)
|
|||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="index">Marking index to toggle</param>
|
||||
/// <returns>Current marking value</returns>
|
||||
public static void ToggleMarking(this PKM pk, int index)
|
||||
public static int ToggleMarking(this PKM pk, int index)
|
||||
{
|
||||
if (pk is IAppliedMarkings<MarkingColor> c)
|
||||
c.SetMarking(index, c.GetMarking(index).Next());
|
||||
else if (pk is IAppliedMarkings<bool> b)
|
||||
b.SetMarking(index, !b.GetMarking(index));
|
||||
var marking = pk.GetMarking(index);
|
||||
var revised = NextMarking(pk.Format, marking);
|
||||
pk.SetMarking(index, revised);
|
||||
return revised;
|
||||
}
|
||||
|
||||
private static MarkingColor Next(this MarkingColor value) => value switch
|
||||
private static int NextMarking(int format, int marking) => format switch
|
||||
{
|
||||
MarkingColor.Blue => MarkingColor.Pink,
|
||||
MarkingColor.Pink => MarkingColor.None,
|
||||
_ => MarkingColor.Blue,
|
||||
<= 6 => marking ^ 1, // toggle : 0 (off) | 1 (on)
|
||||
_ => (marking + 1) % 3, // cycle 0->1->2->0... : 0 (none) | 1 (blue) | 2 (pink)
|
||||
};
|
||||
|
||||
private static Func<int, int, int> FlagHighLow(PKM pk)
|
||||
|
|
|
|||
|
|
@ -1,52 +1,50 @@
|
|||
namespace PKHeX.Core;
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for modifying the Memory parameters of a <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
public static class MemoryApplicator
|
||||
{
|
||||
extension(PKM pk)
|
||||
/// <summary>
|
||||
/// Sets all Memory related data to the default value (zero).
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
public static void ClearMemories(this PKM pk)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets all Memory related data to the default value (zero).
|
||||
/// </summary>
|
||||
public void ClearMemories()
|
||||
{
|
||||
if (pk is IAffection a)
|
||||
a.OriginalTrainerAffection = a.HandlingTrainerAffection = 0;
|
||||
if (pk is IMemoryOT o)
|
||||
o.ClearMemoriesOT();
|
||||
if (pk is IMemoryHT h)
|
||||
h.ClearMemoriesHT();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Memory details to a Hatched Egg's memories specific to <see cref="EntityContext.Gen6"/> origin.
|
||||
/// </summary>
|
||||
public void SetHatchMemory6()
|
||||
{
|
||||
if (pk is IMemoryOT o)
|
||||
{
|
||||
o.OriginalTrainerMemory = 2;
|
||||
o.OriginalTrainerMemoryFeeling = MemoryContext6.GetRandomFeeling6(2);
|
||||
o.OriginalTrainerMemoryIntensity = 1;
|
||||
o.OriginalTrainerMemoryVariable = pk.XY ? (ushort)43 : (ushort)27; // riverside road : battling spot
|
||||
}
|
||||
if (pk is IAffection a)
|
||||
a.OriginalTrainerAffection = 0;
|
||||
}
|
||||
if (pk is IAffection a)
|
||||
a.OT_Affection = a.HT_Affection = 0;
|
||||
if (pk is IMemoryOT o)
|
||||
o.ClearMemoriesOT();
|
||||
if (pk is IMemoryHT h)
|
||||
h.ClearMemoriesHT();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a random memory specific to <see cref="EntityContext.Gen6"/> origin.
|
||||
/// Sets the Memory details to a Hatched Egg's memories.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
public static void SetHatchMemory6(this PKM pk)
|
||||
{
|
||||
if (pk is IMemoryOT o)
|
||||
{
|
||||
o.OT_Memory = 2;
|
||||
o.OT_Feeling = MemoryContext6.GetRandomFeeling6(2);
|
||||
o.OT_Intensity = 1;
|
||||
o.OT_TextVar = pk.XY ? (ushort)43 : (ushort)27; // riverside road : battling spot
|
||||
}
|
||||
if (pk is IAffection a)
|
||||
a.OT_Affection = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a random memory specific to <see cref="GameVersion.Gen6"/> locality.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
public static void SetRandomMemory6(this PK6 pk)
|
||||
{
|
||||
// for lack of better randomization :)
|
||||
const byte memory = 63; // almost got lost when it explored a forest with {Trainer}
|
||||
pk.OriginalTrainerMemory = memory;
|
||||
pk.OriginalTrainerMemoryFeeling = MemoryContext6.GetRandomFeeling6(memory);
|
||||
pk.OriginalTrainerMemoryIntensity = MemoryContext6.MaxIntensity;
|
||||
pk.OT_Memory = 63;
|
||||
pk.OT_Intensity = 6;
|
||||
pk.OT_Feeling = MemoryContext6.GetRandomFeeling6(pk.OT_Memory);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,77 +7,104 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
public static class MoveApplicator
|
||||
{
|
||||
extension(PKM pk)
|
||||
/// <summary>
|
||||
/// Sets the individual PP Up count values depending if a Move is present in the move's slot or not.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="moves"><see cref="PKM.Moves"/> to use.</param>
|
||||
public static void SetMaximumPPUps(this PKM pk, ReadOnlySpan<ushort> moves)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the individual PP Up count values depending on if a Move is present in the move's slot or not.
|
||||
/// </summary>
|
||||
/// <param name="moves"><see cref="PKM.Moves"/> to use.</param>
|
||||
public void SetMaximumPPUps(ReadOnlySpan<ushort> moves)
|
||||
{
|
||||
pk.Move1_PPUps = GetPPUpCount(moves[0]);
|
||||
pk.Move2_PPUps = GetPPUpCount(moves[1]);
|
||||
pk.Move3_PPUps = GetPPUpCount(moves[2]);
|
||||
pk.Move4_PPUps = GetPPUpCount(moves[3]);
|
||||
pk.Move1_PPUps = GetPPUpCount(moves[0]);
|
||||
pk.Move2_PPUps = GetPPUpCount(moves[1]);
|
||||
pk.Move3_PPUps = GetPPUpCount(moves[2]);
|
||||
pk.Move4_PPUps = GetPPUpCount(moves[3]);
|
||||
|
||||
pk.SetMaximumPPCurrent(moves);
|
||||
static int GetPPUpCount(ushort moveID)
|
||||
{
|
||||
if (Legal.IsPPUpAvailable(moveID))
|
||||
return 3;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the individual PP Up count values depending on if a Move is present in the move slot or not.
|
||||
/// </summary>
|
||||
public void SetMaximumPPUps()
|
||||
{
|
||||
Span<ushort> moves = stackalloc ushort[4];
|
||||
pk.GetMoves(moves);
|
||||
pk.SetMaximumPPUps(moves);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="PKM.Moves"/> and updates the current PP counts.
|
||||
/// </summary>
|
||||
/// <param name="input"><see cref="PKM.Moves"/> to set.</param>
|
||||
/// <param name="maxPP">Option to maximize PP Ups</param>
|
||||
public void SetMoves(ReadOnlySpan<ushort> input, bool maxPP = false)
|
||||
{
|
||||
Span<ushort> moves = stackalloc ushort[4];
|
||||
if (input.Length <= 4)
|
||||
input.CopyTo(moves);
|
||||
else
|
||||
input[..4].CopyTo(moves);
|
||||
|
||||
// Remote all indexes with a value above the maximum move ID allowed by the format.
|
||||
var max = pk.MaxMoveID;
|
||||
for (int i = 0; i < moves.Length; i++)
|
||||
{
|
||||
if (moves[i] > max)
|
||||
moves[i] = 0;
|
||||
}
|
||||
|
||||
pk.SetMoves(moves);
|
||||
if (maxPP && Legal.IsPPUpAvailable(pk))
|
||||
pk.SetMaximumPPUps(moves);
|
||||
pk.FixMoves();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the individual PP count values for each move slot based on the maximum possible value.
|
||||
/// </summary>
|
||||
/// <param name="moves"><see cref="PKM.Moves"/> to use (if already known).</param>
|
||||
public void SetMaximumPPCurrent(ReadOnlySpan<ushort> moves)
|
||||
{
|
||||
// In some games, move[i] == 0` *should* set 0, but the game's configuration has a non-zero PP for `(None)`
|
||||
// (I'm looking at you, S/V and Z-A)
|
||||
pk.Move1_PP = pk.GetMovePP(moves.Length > 0 ? moves[0] : (ushort)0, pk.Move1_PPUps);
|
||||
pk.Move2_PP = pk.GetMovePP(moves.Length > 1 ? moves[1] : (ushort)0, pk.Move2_PPUps);
|
||||
pk.Move3_PP = pk.GetMovePP(moves.Length > 2 ? moves[2] : (ushort)0, pk.Move3_PPUps);
|
||||
pk.Move4_PP = pk.GetMovePP(moves.Length > 3 ? moves[3] : (ushort)0, pk.Move4_PPUps);
|
||||
}
|
||||
pk.SetMaximumPPCurrent(moves);
|
||||
static int GetPPUpCount(ushort moveID) => moveID != 0 ? 3 : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the individual PP Up count values depending if a Move is present in the move slot or not.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
public static void SetMaximumPPUps(this PKM pk)
|
||||
{
|
||||
Span<ushort> moves = stackalloc ushort[4];
|
||||
pk.GetMoves(moves);
|
||||
pk.SetMaximumPPUps(moves);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="PKM.Moves"/> and updates the current PP counts.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="input"><see cref="PKM.Moves"/> to set.</param>
|
||||
/// <param name="maxPP">Option to maximize PP Ups</param>
|
||||
public static void SetMoves(this PKM pk, ReadOnlySpan<ushort> input, bool maxPP = false)
|
||||
{
|
||||
Span<ushort> moves = stackalloc ushort[4];
|
||||
if (input.Length <= 4)
|
||||
input.CopyTo(moves);
|
||||
else
|
||||
input[..4].CopyTo(moves);
|
||||
|
||||
// Remote all indexes with a value above the maximum move ID allowed by the format.
|
||||
var max = pk.MaxMoveID;
|
||||
for (int i = 0; i < moves.Length; i++)
|
||||
{
|
||||
if (moves[i] > max)
|
||||
moves[i] = 0;
|
||||
}
|
||||
|
||||
pk.SetMoves(moves);
|
||||
if (maxPP && Legal.IsPPUpAvailable(pk))
|
||||
pk.SetMaximumPPUps(moves);
|
||||
else
|
||||
pk.SetMaximumPPCurrent(moves);
|
||||
pk.FixMoves();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the individual PP count values for each move slot based on the maximum possible value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="moves"><see cref="PKM.Moves"/> to use (if already known). Will fetch the current <see cref="PKM.Moves"/> if not provided.</param>
|
||||
public static void SetMaximumPPCurrent(this PKM pk, ReadOnlySpan<ushort> moves)
|
||||
{
|
||||
pk.Move1_PP = moves.Length == 0 ? 0 : pk.GetMovePP(moves[0], pk.Move1_PPUps);
|
||||
pk.Move2_PP = moves.Length <= 1 ? 0 : pk.GetMovePP(moves[1], pk.Move2_PPUps);
|
||||
pk.Move3_PP = moves.Length <= 2 ? 0 : pk.GetMovePP(moves[2], pk.Move3_PPUps);
|
||||
pk.Move4_PP = moves.Length <= 3 ? 0 : pk.GetMovePP(moves[3], pk.Move4_PPUps);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the individual PP count values for each move slot based on the maximum possible value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="moves"><see cref="PKM.Moves"/> to use (if already known). Will fetch the current <see cref="PKM.Moves"/> if not provided.</param>
|
||||
public static void SetMaximumPPCurrent(this PKM pk, Moveset moves)
|
||||
{
|
||||
pk.Move1_PP = moves.Move1 == 0 ? 0 : pk.GetMovePP(moves.Move1, pk.Move1_PPUps);
|
||||
pk.Move2_PP = moves.Move2 == 0 ? 0 : pk.GetMovePP(moves.Move2, pk.Move2_PPUps);
|
||||
pk.Move3_PP = moves.Move3 == 0 ? 0 : pk.GetMovePP(moves.Move3, pk.Move3_PPUps);
|
||||
pk.Move4_PP = moves.Move4 == 0 ? 0 : pk.GetMovePP(moves.Move4, pk.Move4_PPUps);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the individual PP count values for each move slot based on the maximum possible value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
public static void SetMaximumPPCurrent(this PKM pk)
|
||||
{
|
||||
Span<ushort> moves = stackalloc ushort[4];
|
||||
pk.GetMoves(moves);
|
||||
pk.SetMaximumPPCurrent(moves);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the Move PP for the desired move.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="index">Move PP to refresh.</param>
|
||||
public static void SetSuggestedMovePP(this PKM pk, int index) => pk.HealPPIndex(index);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
@ -7,109 +9,99 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
public static class MoveSetApplicator
|
||||
{
|
||||
extension(PKM pk)
|
||||
/// <summary>
|
||||
/// Gets a moveset for the provided <see cref="PKM"/> data.
|
||||
/// </summary>
|
||||
/// <param name="pk">PKM to generate for</param>
|
||||
/// <param name="random">Full movepool & shuffling</param>
|
||||
/// <returns>4 moves</returns>
|
||||
public static ushort[] GetMoveSet(this PKM pk, bool random = false)
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies a new legal moveset to the <see cref="pk"/>, with option to apply random moves instead.
|
||||
/// </summary>
|
||||
/// <param name="random">True to apply a random moveset, false to apply a level-up moveset.</param>
|
||||
public void SetMoveset(bool random = false)
|
||||
{
|
||||
Span<ushort> moves = stackalloc ushort[4];
|
||||
pk.GetMoveSet(moves, random);
|
||||
pk.SetMoves(moves);
|
||||
}
|
||||
var la = new LegalityAnalysis(pk);
|
||||
var moves = la.GetMoveSet(random);
|
||||
|
||||
/// <summary>
|
||||
/// Applies the suggested Relearn Moves to the <see cref="pk"/>.
|
||||
/// </summary>
|
||||
/// <param name="la">Legality Analysis to use.</param>
|
||||
public void SetRelearnMoves(LegalityAnalysis la)
|
||||
{
|
||||
Span<ushort> moves = stackalloc ushort[4];
|
||||
la.GetSuggestedRelearnMoves(moves);
|
||||
pk.SetRelearnMoves(moves);
|
||||
}
|
||||
if (random)
|
||||
return moves;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a moveset for the provided <see cref="PKM"/> data.
|
||||
/// </summary>
|
||||
/// <param name="moves">Result storage</param>
|
||||
/// <param name="random">Full movepool & shuffling</param>
|
||||
/// <returns>4 moves</returns>
|
||||
public void GetMoveSet(Span<ushort> moves, bool random = false)
|
||||
{
|
||||
var la = new LegalityAnalysis(pk);
|
||||
la.GetMoveSet(moves, random);
|
||||
var clone = pk.Clone();
|
||||
clone.SetMoves(moves);
|
||||
clone.SetMaximumPPCurrent(moves);
|
||||
var newLa = new LegalityAnalysis(clone);
|
||||
|
||||
if (random)
|
||||
return;
|
||||
|
||||
var clone = pk.Clone();
|
||||
clone.SetMoves(moves);
|
||||
var newLa = new LegalityAnalysis(clone);
|
||||
if (!newLa.Valid)
|
||||
newLa.GetMoveSet(moves, true);
|
||||
}
|
||||
// ReSharper disable once TailRecursiveCall
|
||||
return newLa.Valid ? moves : GetMoveSet(pk, true);
|
||||
}
|
||||
|
||||
extension(LegalityAnalysis la)
|
||||
/// <summary>
|
||||
/// Gets a moveset for the provided <see cref="PKM"/> data.
|
||||
/// </summary>
|
||||
/// <param name="la">Precomputed optional</param>
|
||||
/// <param name="random">Full movepool & shuffling</param>
|
||||
/// <returns>4 moves</returns>
|
||||
public static ushort[] GetMoveSet(this LegalityAnalysis la, bool random = false)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a moveset for the provided <see cref="PKM"/> data.
|
||||
/// </summary>
|
||||
/// <param name="moves">Result storage</param>
|
||||
/// <param name="random">Full movepool & shuffling</param>
|
||||
/// <returns>4 moves</returns>
|
||||
public void GetMoveSet(Span<ushort> moves, bool random = false)
|
||||
var m = la.GetSuggestedCurrentMoves(random ? MoveSourceType.All : MoveSourceType.Encounter);
|
||||
if (random && !la.Entity.IsEgg)
|
||||
Util.Shuffle(m.AsSpan());
|
||||
|
||||
const int count = 4;
|
||||
if (m.Length > count)
|
||||
return m.AsSpan(m.Length - count).ToArray();
|
||||
Array.Resize(ref m, count);
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches <see cref="PKM.RelearnMoves"/> based on the provided <see cref="LegalityAnalysis"/>.
|
||||
/// </summary>
|
||||
/// <param name="legal"><see cref="LegalityAnalysis"/> which contains parsed information pertaining to legality.</param>
|
||||
/// <param name="enc">Encounter the relearn moves should be suggested for. If not provided, will use the original encounter from the analysis. </param>
|
||||
/// <returns><see cref="PKM.RelearnMoves"/> best suited for the current <see cref="PKM"/> data.</returns>
|
||||
public static IReadOnlyList<ushort> GetSuggestedRelearnMoves(this LegalityAnalysis legal, IEncounterTemplate? enc = null)
|
||||
{
|
||||
enc ??= legal.EncounterOriginal;
|
||||
var m = legal.GetSuggestedRelearnMovesFromEncounter(enc);
|
||||
if (m.Any(z => z != 0))
|
||||
return m;
|
||||
|
||||
if (enc is MysteryGift or EncounterEgg)
|
||||
return m;
|
||||
|
||||
if (enc is EncounterSlot6AO {CanDexNav: true} dn)
|
||||
{
|
||||
la.GetSuggestedCurrentMoves(moves, random ? MoveSourceType.All : MoveSourceType.Encounter);
|
||||
if (random && !la.Entity.IsEgg)
|
||||
Util.Rand.Shuffle(moves);
|
||||
var moves = legal.Info.Moves;
|
||||
for (int i = 0; i < moves.Length; i++)
|
||||
{
|
||||
if (!moves[i].ShouldBeInRelearnMoves())
|
||||
continue;
|
||||
|
||||
var move = legal.Entity.GetMove(i);
|
||||
if (dn.CanBeDexNavMove(move))
|
||||
return new ushort[] { move, 0, 0, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches <see cref="PKM.RelearnMoves"/> based on the provided <see cref="LegalityAnalysis"/>.
|
||||
/// </summary>
|
||||
/// <param name="moves">Result storage</param>
|
||||
/// <param name="enc">Encounter the relearn moves should be suggested for. If not provided, will use the original encounter from the analysis. </param>
|
||||
/// <returns><see cref="PKM.RelearnMoves"/> best suited for the current <see cref="PKM"/> data.</returns>
|
||||
public void GetSuggestedRelearnMoves(Span<ushort> moves, IEncounterTemplate? enc = null)
|
||||
if (enc is EncounterSlot8b { IsUnderground: true } ug)
|
||||
{
|
||||
enc ??= la.EncounterOriginal;
|
||||
la.GetSuggestedRelearnMovesFromEncounter(moves, enc);
|
||||
if (moves[0] != 0)
|
||||
return;
|
||||
|
||||
if (enc is MysteryGift or IEncounterEgg)
|
||||
return;
|
||||
|
||||
if (enc is ISingleMoveBonus {IsMoveBonusPossible: true} bonus)
|
||||
var moves = legal.Info.Moves;
|
||||
for (int i = 0; i < moves.Length; i++)
|
||||
{
|
||||
var chk = la.Info.Moves;
|
||||
for (int i = 0; i < chk.Length; i++)
|
||||
{
|
||||
if (!chk[i].ShouldBeInRelearnMoves())
|
||||
continue;
|
||||
if (!moves[i].ShouldBeInRelearnMoves())
|
||||
continue;
|
||||
|
||||
var move = la.Entity.GetMove(i);
|
||||
if (!bonus.IsMoveBonus(move))
|
||||
continue;
|
||||
moves.Clear();
|
||||
moves[0] = move;
|
||||
return;
|
||||
}
|
||||
if (bonus.IsMoveBonusRequired && bonus.TryGetRandomMoveBonus(out var bonusMove))
|
||||
{
|
||||
moves.Clear();
|
||||
moves[0] = bonusMove;
|
||||
return;
|
||||
}
|
||||
var move = legal.Entity.GetMove(i);
|
||||
if (ug.CanBeUndergroundMove(move))
|
||||
return new ushort[] { move, 0, 0, 0 };
|
||||
}
|
||||
|
||||
var encounter = EncounterSuggestion.GetSuggestedMetInfo(la.Entity);
|
||||
if (encounter is IRelearn {Relearn: {HasMoves:true} r})
|
||||
r.CopyTo(moves);
|
||||
if (ug.GetBaseEggMove(out var any))
|
||||
return new ushort[] { any, 0, 0, 0 };
|
||||
}
|
||||
|
||||
var encounter = EncounterSuggestion.GetSuggestedMetInfo(legal.Entity);
|
||||
if (encounter is IRelearn {Relearn: {HasMoves:true} r})
|
||||
return r.ToArray();
|
||||
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,183 +7,114 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
public static class MoveShopRecordApplicator
|
||||
{
|
||||
extension(IMoveShop8 shop)
|
||||
public static void ClearMoveShopFlags(this IMoveShop8 shop)
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears all the "purchased" and "mastered" move shop flags.
|
||||
/// </summary>
|
||||
public void ClearMoveShopFlags()
|
||||
{
|
||||
var bits = shop.Permit;
|
||||
for (int i = 0; i < bits.RecordCountUsed; i++)
|
||||
shop.SetPurchasedRecordFlag(i, false);
|
||||
var bits = shop.MoveShopPermitFlags;
|
||||
for (int i = 0; i < bits.Length; i++)
|
||||
shop.SetPurchasedRecordFlag(i, false);
|
||||
|
||||
if (shop is IMoveShop8Mastery m)
|
||||
m.ClearMoveShopFlagsMastered();
|
||||
if (shop is IMoveShop8Mastery m)
|
||||
m.ClearMoveShopFlagsMastered();
|
||||
}
|
||||
|
||||
public static void ClearMoveShopFlagsMastered(this IMoveShop8Mastery shop)
|
||||
{
|
||||
var bits = shop.MoveShopPermitFlags;
|
||||
for (int i = 0; i < bits.Length; i++)
|
||||
shop.SetMasteredRecordFlag(i, false);
|
||||
}
|
||||
|
||||
public static void SetMoveShopFlags(this IMoveShop8Mastery shop, PKM pk)
|
||||
{
|
||||
Span<ushort> moves = stackalloc ushort[4];
|
||||
pk.GetMoves(moves);
|
||||
shop.SetMoveShopFlags(moves, pk);
|
||||
}
|
||||
|
||||
public static void SetMoveShopFlags(this IMoveShop8Mastery shop, ReadOnlySpan<ushort> moves, PKM pk)
|
||||
{
|
||||
var index = PersonalTable.LA.GetFormIndex(pk.Species, pk.Form);
|
||||
var learn = Legal.LevelUpLA[index];
|
||||
var mastery = Legal.MasteryLA[index];
|
||||
var level = pk.CurrentLevel;
|
||||
|
||||
shop.SetMoveShopFlags(moves, learn, mastery, level);
|
||||
}
|
||||
|
||||
public static void SetMoveShopFlagsAll(this IMoveShop8Mastery shop, PKM pk)
|
||||
{
|
||||
var index = PersonalTable.LA.GetFormIndex(pk.Species, pk.Form);
|
||||
var learn = Legal.LevelUpLA[index];
|
||||
var mastery = Legal.MasteryLA[index];
|
||||
var level = pk.CurrentLevel;
|
||||
|
||||
shop.SetMoveShopFlagsAll(learn, mastery, level);
|
||||
}
|
||||
|
||||
public static void SetMoveShopFlagsAll(this IMoveShop8Mastery shop, Learnset learn, Learnset mastery, int level)
|
||||
{
|
||||
var possible = shop.MoveShopPermitIndexes;
|
||||
var permit = shop.MoveShopPermitFlags;
|
||||
for (int index = 0; index < possible.Length; index++)
|
||||
{
|
||||
var move = possible[index];
|
||||
var allowed = permit[index];
|
||||
if (!allowed)
|
||||
continue;
|
||||
|
||||
SetMasteredFlag(shop, learn, mastery, level, index, move);
|
||||
}
|
||||
}
|
||||
|
||||
extension(IMoveShop8Mastery shop)
|
||||
public static void SetMoveShopFlags(this IMoveShop8Mastery shop, ReadOnlySpan<ushort> moves, Learnset learn, Learnset mastery, int level)
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears all the "mastered" move shop flags.
|
||||
/// </summary>
|
||||
public void ClearMoveShopFlagsMastered()
|
||||
var possible = shop.MoveShopPermitIndexes;
|
||||
var permit = shop.MoveShopPermitFlags;
|
||||
foreach (var move in moves)
|
||||
{
|
||||
var bits = shop.Permit;
|
||||
for (int i = 0; i < bits.RecordCountUsed; i++)
|
||||
shop.SetMasteredRecordFlag(i, false);
|
||||
var index = possible.IndexOf(move);
|
||||
if (index == -1)
|
||||
continue;
|
||||
if (!permit[index])
|
||||
continue;
|
||||
SetMasteredFlag(shop, learn, mastery, level, index, move);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetMasteredFlag(this IMoveShop8Mastery shop, Learnset learn, Learnset mastery, int level, int index, ushort move)
|
||||
{
|
||||
if (shop.GetMasteredRecordFlag(index))
|
||||
return;
|
||||
|
||||
if (level < (uint)learn.GetMoveLevel(move)) // Can't learn it yet; must purchase.
|
||||
{
|
||||
shop.SetPurchasedRecordFlag(index, true);
|
||||
shop.SetMasteredRecordFlag(index, true);
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the required move shop flags for the requested entity.
|
||||
/// </summary>
|
||||
public void SetMoveShopFlags(PKM pk)
|
||||
if (level < (uint)mastery.GetMoveLevel(move)) // Can't master it yet; must Seed of Mastery
|
||||
shop.SetMasteredRecordFlag(index, true);
|
||||
}
|
||||
|
||||
public static void SetEncounterMasteryFlags(this IMoveShop8Mastery shop, ReadOnlySpan<ushort> moves, Learnset mastery, int level)
|
||||
{
|
||||
var possible = shop.MoveShopPermitIndexes;
|
||||
var permit = shop.MoveShopPermitFlags;
|
||||
foreach (var move in moves)
|
||||
{
|
||||
Span<ushort> moves = stackalloc ushort[4];
|
||||
pk.GetMoves(moves);
|
||||
shop.SetMoveShopFlags(moves, pk);
|
||||
}
|
||||
var index = possible.IndexOf(move);
|
||||
if (index == -1)
|
||||
continue;
|
||||
if (!permit[index])
|
||||
continue;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the required move shop flags for the requested entity.
|
||||
/// </summary>
|
||||
public void SetMoveShopFlags(ReadOnlySpan<ushort> moves, PKM pk)
|
||||
{
|
||||
var (learn, mastery) = LearnSource8LA.GetLearnsetAndMastery(pk.Species, pk.Form);
|
||||
shop.SetMoveShopFlags(moves, learn, mastery, pk.CurrentLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all possible move shop flags for the requested entity.
|
||||
/// </summary>
|
||||
public void SetMoveShopFlagsAll(PKM pk)
|
||||
{
|
||||
var (learn, mastery) = LearnSource8LA.GetLearnsetAndMastery(pk.Species, pk.Form);
|
||||
shop.SetMoveShopFlagsAll(learn, mastery, pk.CurrentLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all possible move shop flags for the requested entity.
|
||||
/// </summary>
|
||||
public void SetMoveShopFlagsAll(Learnset learn, Learnset mastery, byte level)
|
||||
{
|
||||
var permit = shop.Permit;
|
||||
var possible = permit.RecordPermitIndexes;
|
||||
for (int index = 0; index < permit.RecordCountUsed; index++)
|
||||
{
|
||||
var allowed = permit.IsRecordPermitted(index);
|
||||
if (!allowed)
|
||||
continue;
|
||||
|
||||
var move = possible[index];
|
||||
shop.SetMasteredFlag(learn, mastery, level, index, move);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all move shop flags for the currently known moves.
|
||||
/// </summary>
|
||||
public void SetMoveShopFlags(ReadOnlySpan<ushort> moves, Learnset learn, Learnset mastery, byte level)
|
||||
{
|
||||
var permit = shop.Permit;
|
||||
var possible = permit.RecordPermitIndexes;
|
||||
foreach (var move in moves)
|
||||
{
|
||||
var index = possible.IndexOf(move);
|
||||
if (index == -1)
|
||||
continue;
|
||||
if (!permit.IsRecordPermitted(index))
|
||||
continue;
|
||||
shop.SetMasteredFlag(learn, mastery, level, index, move);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the "mastered" move shop flag for the requested move.
|
||||
/// </summary>
|
||||
public void SetMasteredFlag(Learnset learn, Learnset mastery, byte level, int index, ushort move)
|
||||
{
|
||||
if (shop.GetMasteredRecordFlag(index))
|
||||
return;
|
||||
|
||||
if (learn.TryGetLevelLearnMove(move, out var learnLevel))
|
||||
{
|
||||
if (level < learnLevel) // Can't learn it yet; must purchase.
|
||||
{
|
||||
shop.SetPurchasedRecordFlag(index, true);
|
||||
shop.SetMasteredRecordFlag(index, true);
|
||||
return;
|
||||
}
|
||||
if (mastery.TryGetLevelLearnMove(move, out var masterLevel) && level < masterLevel) // Can't master it yet; must Seed of Mastery
|
||||
shop.SetMasteredRecordFlag(index, true);
|
||||
// Otherwise, is innately mastered, no need to force the flag.
|
||||
}
|
||||
else // Can't learn it without purchasing.
|
||||
{
|
||||
if (shop.GetPurchasedRecordFlag(index))
|
||||
shop.SetMasteredRecordFlag(index, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the "mastered" move shop flag for the encounter.
|
||||
/// </summary>
|
||||
public void SetEncounterMasteryFlags(ReadOnlySpan<ushort> moves, Learnset mastery, byte metLevel, ushort alphaMove)
|
||||
{
|
||||
var permit = shop.Permit;
|
||||
var possible = permit.RecordPermitIndexes;
|
||||
foreach (var move in moves)
|
||||
{
|
||||
var index = possible.IndexOf(move);
|
||||
if (index == -1)
|
||||
continue;
|
||||
if (!permit.IsRecordPermitted(index))
|
||||
continue;
|
||||
|
||||
// If the Pokémon is caught with any move shop move in its learnset,
|
||||
// and it is high enough level to master it, the game will automatically
|
||||
// give it the "Mastered" flag but not the "Purchased" flag
|
||||
// For moves that are not in the learnset, set as mastered.
|
||||
if (!mastery.TryGetLevelLearnMove(move, out var masteryLevel) || metLevel >= masteryLevel)
|
||||
shop.SetMasteredRecordFlag(index, true);
|
||||
}
|
||||
|
||||
if (alphaMove != 0)
|
||||
{
|
||||
var index = possible.IndexOf(alphaMove);
|
||||
if (index != -1)
|
||||
shop.SetMasteredRecordFlag(index, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the "purchased" move shop flag for all possible moves.
|
||||
/// </summary>
|
||||
public void SetPurchasedFlagsAll(PKM pk)
|
||||
{
|
||||
var (learn, _) = LearnSource8LA.GetLearnsetAndMastery(pk.Species, pk.Form);
|
||||
var level = pk.CurrentLevel;
|
||||
var alpha = pk is PA8 pa ? pa.AlphaMove : (ushort)0;
|
||||
|
||||
var permit = shop.Permit;
|
||||
for (int index = 0; index < permit.RecordCountUsed; index++)
|
||||
{
|
||||
var allowed = permit.IsRecordPermitted(index);
|
||||
if (!allowed)
|
||||
continue;
|
||||
|
||||
// If it can learn it naturally, it can't be purchased anymore.
|
||||
var move = permit.RecordPermitIndexes[index];
|
||||
if (learn.TryGetLevelLearnMove(move, out var learnLevel) && learnLevel <= level)
|
||||
continue;
|
||||
|
||||
// Skip purchasing alpha moves, even though it was possible on early versions of the game.
|
||||
if (move == alpha)
|
||||
continue;
|
||||
|
||||
shop.SetPurchasedRecordFlag(index, true);
|
||||
}
|
||||
// If the Pokémon is caught with any move shop move in its learnset
|
||||
// and it is high enough level to master it, the game will automatically
|
||||
// give it the "Mastered" flag but not the "Purchased" flag
|
||||
// For moves that are not in the learnset, it returns -1 which is true, thus set as mastered.
|
||||
if (level >= mastery.GetMoveLevel(move))
|
||||
shop.SetMasteredRecordFlag(index, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
using System;
|
||||
using static PKHeX.Core.Ball;
|
||||
using static PKHeX.Core.PersonalColor;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public static class PersonalColorUtil
|
||||
{
|
||||
public static PersonalColor GetColor(PKM pk)
|
||||
{
|
||||
// Gen1/2 don't store color in personal info
|
||||
if (pk.Format < 3)
|
||||
return (PersonalColor)PersonalTable.USUM[pk.Species, 0].Color;
|
||||
return (PersonalColor)pk.PersonalInfo.Color;
|
||||
}
|
||||
|
||||
public static PersonalColor GetColor(IEncounterTemplate enc)
|
||||
{
|
||||
// Gen1/2 don't store color in personal info
|
||||
if (enc.Generation < 3)
|
||||
return (PersonalColor)PersonalTable.USUM[enc.Species, 0].Color;
|
||||
|
||||
var pt = GameData.GetPersonal(enc.Version);
|
||||
var pi = pt[enc.Species, enc.Form];
|
||||
return (PersonalColor)pi.Color;
|
||||
}
|
||||
|
||||
public static ReadOnlySpan<Ball> GetPreferredByColor(IEncounterTemplate enc) => GetPreferredByColor(enc, GetColor(enc));
|
||||
|
||||
public static ReadOnlySpan<Ball> GetPreferredByColor<T>(T enc, PersonalColor color) where T : IContext
|
||||
{
|
||||
if (enc.Context is EntityContext.Gen8a)
|
||||
return GetPreferredByColorLA(color);
|
||||
return GetPreferredByColor(color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Priority Match ball IDs that match the color ID
|
||||
/// </summary>
|
||||
public static ReadOnlySpan<Ball> GetPreferredByColor(PersonalColor color) => color switch
|
||||
{
|
||||
Red => [Repeat, Fast, Heal, Great, Dream, Lure],
|
||||
Blue => [Dive, Net, Great, Lure, Beast],
|
||||
Yellow => [Level, Ultra, Repeat, Quick, Moon],
|
||||
Green => [Safari, Friend, Nest, Dusk],
|
||||
Black => [Luxury, Heavy, Ultra, Moon, Net, Beast],
|
||||
Brown => [Level, Heavy],
|
||||
Purple => [Master, Love, Heal, Dream],
|
||||
Gray => [Heavy, Premier, Luxury],
|
||||
White => [Premier, Timer, Luxury, Ultra],
|
||||
_ => [Love, Heal, Dream],
|
||||
};
|
||||
|
||||
public static ReadOnlySpan<Ball> GetPreferredByColorLA(PersonalColor color) => color switch
|
||||
{
|
||||
Red => [LAPoke],
|
||||
Blue => [LAFeather, LAGreat, LAJet],
|
||||
Yellow => [LAUltra],
|
||||
Green => [LAPoke],
|
||||
Black => [LAGigaton, LALeaden, LAHeavy, LAUltra],
|
||||
Brown => [LAPoke],
|
||||
Purple => [LAPoke],
|
||||
Gray => [LAGigaton, LALeaden, LAHeavy],
|
||||
White => [LAWing, LAJet],
|
||||
_ => [LAPoke],
|
||||
};
|
||||
}
|
||||
|
|
@ -1,211 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for modifying the Plus Record flags of a <see cref="PA9"/>.
|
||||
/// </summary>
|
||||
public static class PlusRecordApplicator
|
||||
{
|
||||
extension(IPlusRecord record)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets all the Plus Record flags for the <see cref="record"/> to the given value.
|
||||
/// </summary>
|
||||
/// <param name="count">Total count of flags to modify [0,x).</param>
|
||||
/// <param name="value">Value to set for each record.</param>
|
||||
public void SetPlusFlagsAll(int count, bool value)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
record.SetMovePlusFlag(i, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the Plus Record flags for the <see cref="record"/>.
|
||||
/// </summary>
|
||||
/// <param name="count">Total count of flags to modify [0,x).</param>
|
||||
public void ClearPlusFlags(int count) => record.SetPlusFlagsAll(count, false);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Plus Record flags for the <see cref="record"/> based on the legality of learning moves.
|
||||
/// </summary>
|
||||
/// <param name="permit">Sanity check to retrieve plus record indexes.</param>
|
||||
/// <param name="la">Legality analysis of the Pokémon.</param>
|
||||
/// <param name="seedOfMastery">Use a Seed of Mastery to bypass the level requirement of mastering the move.</param>
|
||||
/// <param name="tm">Apply TM flags as Plus too.</param>
|
||||
public void SetPlusFlags(IPermitPlus permit, LegalityAnalysis la, bool seedOfMastery, bool tm)
|
||||
{
|
||||
// Hopefully this is only called for Legends: Z-A format entities.
|
||||
var entity = la.Entity;
|
||||
var context = entity.Context;
|
||||
var evos = la.Info.EvoChainsAllGens.Get(context);
|
||||
switch (la.Entity)
|
||||
{
|
||||
case PA9 pa9:
|
||||
{
|
||||
var learn = LearnSource9ZA.Instance;
|
||||
record.SetPlusFlagsNatural(permit, evos, learn, seedOfMastery);
|
||||
if (pa9 is { IsAlpha: true, ZA: true })
|
||||
{
|
||||
var table = PersonalTable.ZA;
|
||||
var enc = la.EncounterMatch;
|
||||
var epi = table[enc.Species, enc.Form];
|
||||
pa9.SetPlusFlagsSpecific(epi, epi.AlphaMove);
|
||||
}
|
||||
|
||||
if (tm)
|
||||
{
|
||||
var table = PersonalTable.ZA;
|
||||
record.SetPlusFlagsTM<PersonalTable9ZA, PersonalInfo9ZA>(permit, evos, table);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Exception("Format not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPlusFlags(PKM pk, IPermitPlus permit, PlusRecordApplicatorOption option)
|
||||
{
|
||||
record.ClearPlusFlags(permit.PlusCountTotal);
|
||||
if (option is PlusRecordApplicatorOption.None)
|
||||
return;
|
||||
if (option is PlusRecordApplicatorOption.ForceAll)
|
||||
{
|
||||
record.SetPlusFlagsAll(permit.PlusCountUsed, true);
|
||||
return;
|
||||
}
|
||||
|
||||
var la = new LegalityAnalysis(pk);
|
||||
record.SetPlusFlagsInternal(permit, option, la);
|
||||
}
|
||||
|
||||
public void SetPlusFlags(IPermitPlus permit, PlusRecordApplicatorOption option, LegalityAnalysis la)
|
||||
{
|
||||
record.ClearPlusFlags(permit.PlusCountTotal);
|
||||
if (option is PlusRecordApplicatorOption.None)
|
||||
return;
|
||||
if (option is PlusRecordApplicatorOption.ForceAll)
|
||||
{
|
||||
record.SetPlusFlagsAll(permit.PlusCountUsed, true);
|
||||
return;
|
||||
}
|
||||
|
||||
record.SetPlusFlagsInternal(permit, option, la);
|
||||
}
|
||||
|
||||
public void SetPlusFlagsNatural<TSource>(IPermitPlus permit, ReadOnlySpan<EvoCriteria> evos, TSource source, bool seedOfMastery) where TSource : ILearnSourceBonus
|
||||
{
|
||||
var indexes = permit.PlusMoveIndexes;
|
||||
foreach (var evo in evos)
|
||||
{
|
||||
record.SetPlusFlagsNatural(indexes, evo, source, seedOfMastery);
|
||||
if (evo.Form != 0 && evo.Species is (int)Species.Rotom or (int)Species.Hoopa)
|
||||
record.SetPlusFlagsNatural(indexes, evo with { Form = 0 }, source, seedOfMastery);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPlusFlagsNatural<TSource>(ReadOnlySpan<ushort> indexes, EvoCriteria evo, TSource source, bool seedOfMastery) where TSource : ILearnSourceBonus
|
||||
{
|
||||
var (levelUp, plus) = source.GetLearnsetAndOther(evo.Species, evo.Form);
|
||||
var set = seedOfMastery ? levelUp : plus;
|
||||
var levels = set.GetAllLevels();
|
||||
var moves = set.GetAllMoves();
|
||||
for (int i = 0; i < levels.Length; i++)
|
||||
{
|
||||
if (evo.LevelMax < levels[i])
|
||||
break;
|
||||
|
||||
var move = moves[i];
|
||||
var index = indexes.IndexOf(move);
|
||||
record.SetMovePlusFlag(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all moves that would be learned and naturally available as Plus based on the given level
|
||||
/// </summary>
|
||||
/// <param name="permit">Permit to use</param>
|
||||
/// <param name="plus">Learnset to use</param>
|
||||
/// <param name="level">Current level</param>
|
||||
/// <param name="extra">Extra moves to set as Plus</param>
|
||||
public void SetPlusFlagsEncounter(IPermitPlus permit, Learnset plus, byte level, params ReadOnlySpan<ushort> extra)
|
||||
{
|
||||
var indexes = permit.PlusMoveIndexes;
|
||||
var levels = plus.GetAllLevels();
|
||||
var moves = plus.GetAllMoves();
|
||||
|
||||
for (int i = 0; i < levels.Length; i++)
|
||||
{
|
||||
if (level < levels[i])
|
||||
break;
|
||||
|
||||
var move = moves[i];
|
||||
var index = indexes.IndexOf(move);
|
||||
record.SetMovePlusFlag(index);
|
||||
}
|
||||
|
||||
if (extra.Length != 0)
|
||||
record.SetPlusFlagsSpecific(permit, extra);
|
||||
}
|
||||
|
||||
|
||||
public void SetPlusFlagsSpecific(IPermitPlus permit, ushort move)
|
||||
{
|
||||
var indexes = permit.PlusMoveIndexes;
|
||||
var index = indexes.IndexOf(move);
|
||||
record.SetMovePlusFlag(index);
|
||||
}
|
||||
|
||||
public void SetPlusFlagsSpecific(IPermitPlus permit, params ReadOnlySpan<ushort> extra)
|
||||
{
|
||||
var indexes = permit.PlusMoveIndexes;
|
||||
foreach (var move in extra)
|
||||
{
|
||||
var index = indexes.IndexOf(move);
|
||||
record.SetMovePlusFlag(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPlusFlagsInternal(IPermitPlus permit, PlusRecordApplicatorOption option, LegalityAnalysis la)
|
||||
{
|
||||
if (option is PlusRecordApplicatorOption.LegalCurrent)
|
||||
record.SetPlusFlags(permit, la, false, false);
|
||||
else if (option is PlusRecordApplicatorOption.LegalCurrentTM)
|
||||
record.SetPlusFlags(permit, la, false, true);
|
||||
else if (option is PlusRecordApplicatorOption.LegalSeedTM)
|
||||
record.SetPlusFlags(permit, la, true, true);
|
||||
}
|
||||
|
||||
public void SetPlusFlagsTM<TTable, TInfo>(IPermitPlus permit, ReadOnlySpan<EvoCriteria> evos, TTable table)
|
||||
where TTable : IPersonalTable<TInfo>
|
||||
where TInfo : IPersonalInfo, IPersonalInfoTM
|
||||
{
|
||||
var indexes = permit.PlusMoveIndexes;
|
||||
foreach (var evo in evos)
|
||||
{
|
||||
var pi = table[evo.Species, evo.Form];
|
||||
for (int index = 0; index < indexes.Length; index++)
|
||||
{
|
||||
var move = indexes[index];
|
||||
var tmIndex = permit.RecordPermitIndexes.IndexOf(move);
|
||||
if (tmIndex != -1 && pi.GetIsLearnTM(tmIndex))
|
||||
record.SetMovePlusFlag(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetPlusFlags<T>(this T pk, IPermitPlus permit, PlusRecordApplicatorOption option)
|
||||
where T : PKM, IPlusRecord
|
||||
=> pk.SetPlusFlags(pk, permit, option);
|
||||
}
|
||||
|
||||
public enum PlusRecordApplicatorOption
|
||||
{
|
||||
None,
|
||||
ForceAll,
|
||||
LegalCurrent,
|
||||
LegalCurrentTM,
|
||||
LegalSeedTM,
|
||||
}
|
||||
|
|
@ -14,34 +14,15 @@ public static class RibbonApplicator
|
|||
public static void SetAllValidRibbons(PKM pk) => SetAllValidRibbons(new LegalityAnalysis(pk));
|
||||
|
||||
/// <inheritdoc cref="SetAllValidRibbons(PKM)"/>
|
||||
public static void SetAllValidRibbons(LegalityAnalysis la) => SetAllValidRibbons(la.Entity, la.EncounterMatch, la.Info.EvoChainsAllGens);
|
||||
|
||||
/// <inheritdoc cref="SetAllValidRibbons(PKM)"/>
|
||||
public static void SetAllValidRibbons(PKM pk, IEncounterTemplate enc, EvolutionHistory history)
|
||||
public static void SetAllValidRibbons(LegalityAnalysis la)
|
||||
{
|
||||
var args = new RibbonVerifierArguments(pk, enc, history);
|
||||
var args = new RibbonVerifierArguments(la.Entity, la.EncounterMatch, la.Info.EvoChainsAllGens);
|
||||
SetAllRibbonState(args, true);
|
||||
FixInvalidRibbons(args);
|
||||
|
||||
if (pk.IsEgg)
|
||||
return;
|
||||
|
||||
if (pk is IRibbonSetCommon6 c6)
|
||||
{
|
||||
// Medal Deadlock
|
||||
if (pk is ISuperTrain s && history.HasVisitedGen6)
|
||||
{
|
||||
s.SuperTrainBitFlags = RibbonRules.SetSuperTrainSupremelyTrained(s.SuperTrainBitFlags);
|
||||
if (pk.Format == 6) // cleared on 6->7 transfer; only set in Gen6.
|
||||
{
|
||||
s.SecretSuperTrainingUnlocked = true;
|
||||
s.SuperTrainSupremelyTrained = true;
|
||||
}
|
||||
c6.RibbonTraining = true;
|
||||
}
|
||||
// Ribbon Deadlock
|
||||
// Ribbon Deadlock
|
||||
if (la.Entity is IRibbonSetCommon6 c6)
|
||||
InvertDeadlockContest(c6, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -53,16 +34,7 @@ public static void SetAllValidRibbons(PKM pk, IEncounterTemplate enc, EvolutionH
|
|||
/// <inheritdoc cref="RemoveAllValidRibbons(PKM)"/>
|
||||
public static void RemoveAllValidRibbons(LegalityAnalysis la)
|
||||
{
|
||||
var pk = la.Entity;
|
||||
var enc = la.EncounterMatch;
|
||||
var history = la.Info.EvoChainsAllGens;
|
||||
RemoveAllValidRibbons(pk, enc, history);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="RemoveAllValidRibbons(PKM)"/>
|
||||
public static void RemoveAllValidRibbons(PKM pk, IEncounterTemplate enc, EvolutionHistory history)
|
||||
{
|
||||
var args = new RibbonVerifierArguments(pk, enc, history);
|
||||
var args = new RibbonVerifierArguments(la.Entity, la.EncounterMatch, la.Info.EvoChainsAllGens);
|
||||
SetAllRibbonState(args, false);
|
||||
FixInvalidRibbons(args);
|
||||
}
|
||||
|
|
@ -70,7 +42,7 @@ public static void RemoveAllValidRibbons(PKM pk, IEncounterTemplate enc, Evoluti
|
|||
/// <summary>
|
||||
/// Parses the Entity for all ribbons, then fixes any ribbon that was invalid.
|
||||
/// </summary>
|
||||
public static void FixInvalidRibbons(in RibbonVerifierArguments args)
|
||||
public static void FixInvalidRibbons(RibbonVerifierArguments args)
|
||||
{
|
||||
Span<RibbonResult> result = stackalloc RibbonResult[RibbonVerifier.MaxRibbonCount];
|
||||
var count = RibbonVerifier.GetRibbonResults(args, result);
|
||||
|
|
@ -78,7 +50,7 @@ public static void FixInvalidRibbons(in RibbonVerifierArguments args)
|
|||
ribbon.Fix(args);
|
||||
}
|
||||
|
||||
private static void SetAllRibbonState(in RibbonVerifierArguments args, bool desiredState)
|
||||
private static void SetAllRibbonState(RibbonVerifierArguments args, bool desiredState)
|
||||
{
|
||||
for (RibbonIndex3 r = 0; r < RibbonIndex3.MAX_COUNT; r++)
|
||||
r.Fix(args, desiredState);
|
||||
|
|
@ -87,10 +59,10 @@ private static void SetAllRibbonState(in RibbonVerifierArguments args, bool desi
|
|||
|
||||
if (desiredState)
|
||||
{
|
||||
// Skip personality marks (Encounter specific, never required); don't set them.
|
||||
// Skip Marks, don't set them.
|
||||
for (RibbonIndex r = 0; r <= RibbonIndex.MasterRank; r++)
|
||||
r.Fix(args, desiredState);
|
||||
for (RibbonIndex r = RibbonIndex.Hisui; r < RibbonIndex.MAX_COUNT; r++)
|
||||
for (RibbonIndex r = RibbonIndex.Pioneer; r < RibbonIndex.MAX_COUNT; r++)
|
||||
r.Fix(args, desiredState);
|
||||
}
|
||||
else
|
||||
|
|
@ -103,7 +75,6 @@ private static void SetAllRibbonState(in RibbonVerifierArguments args, bool desi
|
|||
|
||||
private static void InvertDeadlockContest(IRibbonSetCommon6 c6, bool desiredState)
|
||||
{
|
||||
// Contest Star is a deadlock ribbon with the Master ribbons, as it needs all five Master ribbons to be true.
|
||||
if (desiredState)
|
||||
c6.RibbonContestStar = c6.HasAllContestRibbons();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,212 +1,63 @@
|
|||
using System;
|
||||
using static PKHeX.Core.TechnicalRecordApplicatorOption;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for modifying the Technical Record flags of a <see cref="ITechRecord"/>.
|
||||
/// Logic for modifying the Technical Record flags of a <see cref="PK8"/>.
|
||||
/// </summary>
|
||||
public static class TechnicalRecordApplicator
|
||||
{
|
||||
extension(ITechRecord record)
|
||||
/// <summary>
|
||||
/// Sets the Technical Record flags for the <see cref="pk"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="value">Value to set for the record.</param>
|
||||
/// <param name="max">Max record to set.</param>
|
||||
public static void SetRecordFlags(this ITechRecord8 pk, bool value, int max = 100)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the Technical Record flags for the <see cref="record"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to set for the record.</param>
|
||||
/// <param name="max">Max record to set.</param>
|
||||
public void SetRecordFlagsAll(bool value, int max)
|
||||
for (int i = 0; i < max; i++)
|
||||
pk.SetMoveRecordFlag(i, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the Technical Record flags for the <see cref="pk"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
public static void ClearRecordFlags(this ITechRecord8 pk) => pk.SetRecordFlags(false, 112);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Technical Record flags for the <see cref="pk"/> based on the current moves.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="moves">Moves to set flags for. If a move is not a Technical Record, it is skipped.</param>
|
||||
public static void SetRecordFlags(this ITechRecord8 pk, ReadOnlySpan<ushort> moves)
|
||||
{
|
||||
var permit = pk.TechRecordPermitFlags;
|
||||
var moveIDs = pk.TechRecordPermitIndexes;
|
||||
if (permit.Length != moveIDs.Length)
|
||||
return;
|
||||
|
||||
foreach (var m in moves)
|
||||
{
|
||||
for (int i = 0; i < max; i++)
|
||||
record.SetMoveRecordFlag(i, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the Technical Record flags for the <see cref="record"/>.
|
||||
/// </summary>
|
||||
public void ClearRecordFlags() => record.SetRecordFlagsAll(false, record.Permit.RecordCountTotal);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Technical Record flags for the <see cref="record"/> based on the current moves.
|
||||
/// </summary>
|
||||
/// <param name="moves">Moves to set flags for. If a move is not a Technical Record, it is skipped.</param>
|
||||
public void SetRecordFlags(ReadOnlySpan<ushort> moves)
|
||||
{
|
||||
var permit = record.Permit;
|
||||
record.SetRecordFlags(moves, permit);
|
||||
}
|
||||
|
||||
private void SetRecordFlags<TTable, TInfo>(ReadOnlySpan<ushort> moves, ReadOnlySpan<EvoCriteria> evos, TTable pt)
|
||||
where TTable : IPersonalTable<TInfo> where TInfo : IPersonalInfo, IPermitRecord
|
||||
{
|
||||
foreach (var evo in evos)
|
||||
record.SetRecordFlags(moves, pt[evo.Species, evo.Form]);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetRecordFlagsAll(ITechRecord)"/>
|
||||
private void SetRecordFlagsAll<TTable, TInfo>(ReadOnlySpan<EvoCriteria> evos, TTable pt)
|
||||
where TTable : IPersonalTable<TInfo> where TInfo : IPersonalInfo, IPermitRecord
|
||||
{
|
||||
foreach (var evo in evos)
|
||||
record.SetRecordFlagsAll(pt[evo.Species, evo.Form]);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetRecordFlags(ITechRecord, ReadOnlySpan{ushort})"/>
|
||||
public void SetRecordFlags(ReadOnlySpan<ushort> moves, ReadOnlySpan<EvoCriteria> evos)
|
||||
{
|
||||
if (record is PK9 pk9)
|
||||
pk9.SetRecordFlags<PersonalTable9SV, PersonalInfo9SV>(moves, evos, PersonalTable.SV);
|
||||
else if (record is PA9 pa9)
|
||||
pa9.SetRecordFlags<PersonalTable9ZA, PersonalInfo9ZA>(moves, evos, PersonalTable.ZA);
|
||||
else if (record is PK8 pk8)
|
||||
pk8.SetRecordFlags<PersonalTable8SWSH, PersonalInfo8SWSH>(moves, evos, PersonalTable.SWSH);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetRecordFlagsAll(ITechRecord)"/>
|
||||
public void SetRecordFlagsAll(ReadOnlySpan<EvoCriteria> evos)
|
||||
{
|
||||
if (record is PK9 pk9)
|
||||
pk9.SetRecordFlagsAll<PersonalTable9SV, PersonalInfo9SV>(evos, PersonalTable.SV);
|
||||
else if (record is PA9 pa9)
|
||||
pa9.SetRecordFlagsAll<PersonalTable9ZA, PersonalInfo9ZA>(evos, PersonalTable.ZA);
|
||||
else if (record is PK8 pk8)
|
||||
pk8.SetRecordFlagsAll<PersonalTable8SWSH, PersonalInfo8SWSH>(evos, PersonalTable.SWSH);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IPermitRecord.IsRecordPermitted"/>
|
||||
public bool IsRecordPermitted(ReadOnlySpan<EvoCriteria> evos, int index) => record switch
|
||||
{
|
||||
PK9 => IsRecordPermitted<PersonalTable9SV, PersonalInfo9SV>(evos, PersonalTable.SV, index),
|
||||
PA9 => IsRecordPermitted<PersonalTable9ZA, PersonalInfo9ZA>(evos, PersonalTable.ZA, index),
|
||||
PK8 => IsRecordPermitted<PersonalTable8SWSH, PersonalInfo8SWSH>(evos, PersonalTable.SWSH, index),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <inheritdoc cref="SetRecordFlags(ITechRecord, PKM, TechnicalRecordApplicatorOption, LegalityAnalysis)"/>
|
||||
public void SetRecordFlags(PKM pk, TechnicalRecordApplicatorOption option)
|
||||
{
|
||||
record.ClearRecordFlags();
|
||||
if (option is None)
|
||||
return;
|
||||
if (option is ForceAll)
|
||||
{
|
||||
record.SetRecordFlagsAll(true, record.Permit.RecordCountUsed);
|
||||
return;
|
||||
}
|
||||
var la = new LegalityAnalysis(pk);
|
||||
SetRecordFlagsInternal(record, pk, option, la);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the Technical Record flags based on the <see cref="option"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Object to apply to, but base type for other logic.</param>
|
||||
/// <param name="option">Option to apply.</param>
|
||||
/// <param name="la">Legality analysis to use for the option.</param>
|
||||
public void SetRecordFlags(PKM pk, TechnicalRecordApplicatorOption option, LegalityAnalysis la)
|
||||
{
|
||||
record.ClearRecordFlags();
|
||||
if (option is None)
|
||||
return;
|
||||
if (option is ForceAll)
|
||||
{
|
||||
record.SetRecordFlagsAll(true, record.Permit.RecordCountUsed);
|
||||
return;
|
||||
}
|
||||
SetRecordFlagsInternal(record, pk, option, la);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all the Technical Record flags for the <see cref="record"/> if they are permitted to be learned in-game.
|
||||
/// </summary>
|
||||
public void SetRecordFlagsAll()
|
||||
{
|
||||
var permit = record.Permit;
|
||||
record.SetRecordFlagsAll(permit);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetRecordFlagsAll(PKHeX.Core.ITechRecord)"/>"/>
|
||||
public void SetRecordFlagsAll(IPermitRecord permit)
|
||||
{
|
||||
for (int i = 0; i < permit.RecordCountUsed; i++)
|
||||
{
|
||||
if (permit.IsRecordPermitted(i))
|
||||
record.SetMoveRecordFlag(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetRecordFlags(ITechRecord, ReadOnlySpan{ushort})"/>
|
||||
public void SetRecordFlags(ReadOnlySpan<ushort> moves, IPermitRecord permit)
|
||||
{
|
||||
var moveIDs = permit.RecordPermitIndexes;
|
||||
|
||||
foreach (var m in moves)
|
||||
{
|
||||
var index = moveIDs.IndexOf(m);
|
||||
if (index == -1)
|
||||
continue;
|
||||
if (permit.IsRecordPermitted(index))
|
||||
record.SetMoveRecordFlag(index);
|
||||
}
|
||||
var index = moveIDs.IndexOf(m);
|
||||
if (index == -1)
|
||||
continue;
|
||||
if (permit[index])
|
||||
pk.SetMoveRecordFlag(index, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IPermitRecord.IsRecordPermitted"/>
|
||||
private static bool IsRecordPermitted<TTable, TInfo>(ReadOnlySpan<EvoCriteria> evos, TTable pt, int index)
|
||||
where TTable : IPersonalTable<TInfo> where TInfo : IPersonalInfo, IPermitRecord
|
||||
/// <summary>
|
||||
/// Sets all the Technical Record flags for the <see cref="pk"/> if they are permitted to be learned in-game.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
public static void SetRecordFlags(this ITechRecord8 pk)
|
||||
{
|
||||
foreach (var evo in evos)
|
||||
var permit = pk.TechRecordPermitFlags;
|
||||
for (int i = 0; i < permit.Length; i++)
|
||||
{
|
||||
if (pt[evo.Species, evo.Form].IsRecordPermitted(index))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetRecordFlags(ITechRecord, PKM, TechnicalRecordApplicatorOption, LegalityAnalysis)"/>
|
||||
public static void SetRecordFlags<T>(this T pk, TechnicalRecordApplicatorOption option)
|
||||
where T : PKM, ITechRecord
|
||||
=> pk.SetRecordFlags(pk, option);
|
||||
|
||||
private static void SetRecordFlagsInternal(ITechRecord record, PKM pk, TechnicalRecordApplicatorOption option, LegalityAnalysis la)
|
||||
{
|
||||
if (option is LegalCurrent)
|
||||
{
|
||||
Span<ushort> moves = stackalloc ushort[4];
|
||||
pk.GetMoves(moves);
|
||||
var evos = la.Info.EvoChainsAllGens.Get(pk.Context);
|
||||
record.SetRecordFlags(moves, evos);
|
||||
}
|
||||
else if (option is LegalAll)
|
||||
{
|
||||
var evos = la.Info.EvoChainsAllGens.Get(pk.Context);
|
||||
record.SetRecordFlagsAll(evos);
|
||||
if (permit[i])
|
||||
pk.SetMoveRecordFlag(i, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for applying Technical Record flags.
|
||||
/// </summary>
|
||||
public enum TechnicalRecordApplicatorOption
|
||||
{
|
||||
/// <summary>
|
||||
/// Do not apply any flags. Clear all flags.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Apply all flags, regardless of legality.
|
||||
/// </summary>
|
||||
ForceAll,
|
||||
|
||||
/// <summary>
|
||||
/// Apply legal flags based on the current moves.
|
||||
/// </summary>
|
||||
LegalCurrent,
|
||||
|
||||
/// <summary>
|
||||
/// Apply legal flags based on all moves able to learn in the game it resides in.
|
||||
/// </summary>
|
||||
LegalAll,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,280 +0,0 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Grammar and prefix/suffix tokens for <see cref="IBattleTemplate"/> localization.
|
||||
/// </summary>
|
||||
public sealed record BattleTemplateConfig
|
||||
{
|
||||
public sealed record BattleTemplateTuple(BattleTemplateToken Token, string Text);
|
||||
|
||||
/// <summary> Prefix tokens - e.g. Friendship: {100} </summary>
|
||||
public required BattleTemplateTuple[] Left { get; init; }
|
||||
|
||||
/// <summary> Suffix tokens - e.g. {Timid} Nature </summary>
|
||||
public required BattleTemplateTuple[] Right { get; init; }
|
||||
|
||||
/// <summary> Tokens that always display the same text, with no value - e.g. Shiny: Yes </summary>
|
||||
public required BattleTemplateTuple[] Center { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Stat names, ordered with speed in the middle (not last).
|
||||
/// </summary>
|
||||
public required StatDisplayConfig StatNames { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Stat names, ordered with speed in the middle (not last).
|
||||
/// </summary>
|
||||
public required StatDisplayConfig StatNamesFull { get; init; }
|
||||
|
||||
public required string Male { get; init; }
|
||||
public required string Female { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stat names in the requested format.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public StatDisplayConfig GetStatDisplay(StatDisplayStyle style = StatDisplayStyle.Abbreviated) => style switch
|
||||
{
|
||||
StatDisplayStyle.Abbreviated => StatNames,
|
||||
StatDisplayStyle.Full => StatNamesFull,
|
||||
StatDisplayStyle.HABCDS => StatDisplayConfig.HABCDS,
|
||||
StatDisplayStyle.Raw => StatDisplayConfig.Raw,
|
||||
StatDisplayStyle.Raw00 => StatDisplayConfig.Raw00,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(style), style, null),
|
||||
};
|
||||
|
||||
public static ReadOnlySpan<char> GetMoveDisplay(MoveDisplayStyle style = MoveDisplayStyle.Fill) => style switch
|
||||
{
|
||||
MoveDisplayStyle.Fill => "----",
|
||||
MoveDisplayStyle.Directional => "↑→←↓",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(style), style, null),
|
||||
};
|
||||
|
||||
public static bool IsMovePrefix(char c) => c is '-' or '–' or '↑' or '←' or '↓' or '→';
|
||||
|
||||
public static ReadOnlySpan<BattleTemplateToken> CommunityStandard =>
|
||||
[
|
||||
BattleTemplateToken.FirstLine,
|
||||
BattleTemplateToken.Ability,
|
||||
BattleTemplateToken.Level,
|
||||
BattleTemplateToken.Shiny,
|
||||
BattleTemplateToken.Friendship,
|
||||
BattleTemplateToken.DynamaxLevel,
|
||||
BattleTemplateToken.Gigantamax,
|
||||
BattleTemplateToken.TeraType,
|
||||
BattleTemplateToken.EVs,
|
||||
BattleTemplateToken.Nature,
|
||||
BattleTemplateToken.IVs,
|
||||
BattleTemplateToken.Moves,
|
||||
];
|
||||
|
||||
public static ReadOnlySpan<BattleTemplateToken> Showdown => CommunityStandard;
|
||||
|
||||
public static ReadOnlySpan<BattleTemplateToken> ShowdownNew =>
|
||||
[
|
||||
BattleTemplateToken.FirstLine,
|
||||
BattleTemplateToken.AbilityHeldItem,
|
||||
BattleTemplateToken.Moves,
|
||||
BattleTemplateToken.EVsAppendNature,
|
||||
BattleTemplateToken.IVs,
|
||||
BattleTemplateToken.Level,
|
||||
BattleTemplateToken.Shiny,
|
||||
BattleTemplateToken.Friendship,
|
||||
BattleTemplateToken.DynamaxLevel,
|
||||
BattleTemplateToken.Gigantamax,
|
||||
BattleTemplateToken.TeraType,
|
||||
];
|
||||
|
||||
public static ReadOnlySpan<BattleTemplateToken> DefaultHover =>
|
||||
[
|
||||
// First line is handled manually.
|
||||
BattleTemplateToken.HeldItem,
|
||||
BattleTemplateToken.Ability,
|
||||
BattleTemplateToken.Level,
|
||||
BattleTemplateToken.Shiny,
|
||||
BattleTemplateToken.DynamaxLevel,
|
||||
BattleTemplateToken.Gigantamax,
|
||||
BattleTemplateToken.TeraType,
|
||||
BattleTemplateToken.EVs,
|
||||
BattleTemplateToken.IVs,
|
||||
BattleTemplateToken.Nature,
|
||||
BattleTemplateToken.Moves,
|
||||
|
||||
// Other tokens are handled manually (Ganbaru, Awakening) as they are not stored by the battle template interface, only entity objects.
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse the line for a token and value, if applicable.
|
||||
/// </summary>
|
||||
/// <param name="line">Line to parse</param>
|
||||
/// <param name="value">Value for the token, if applicable</param>
|
||||
/// <returns>Token type that was found</returns>
|
||||
public BattleTemplateToken TryParse(ReadOnlySpan<char> line, out ReadOnlySpan<char> value)
|
||||
{
|
||||
value = default;
|
||||
if (line.Length == 0)
|
||||
return BattleTemplateToken.None;
|
||||
foreach (var tuple in Left)
|
||||
{
|
||||
if (!line.StartsWith(tuple.Text, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
value = line[tuple.Text.Length..];
|
||||
return tuple.Token;
|
||||
}
|
||||
foreach (var tuple in Right)
|
||||
{
|
||||
if (!line.EndsWith(tuple.Text, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
value = line[..^tuple.Text.Length];
|
||||
return tuple.Token;
|
||||
}
|
||||
foreach (var tuple in Center)
|
||||
{
|
||||
if (!line.Equals(tuple.Text, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
return tuple.Token;
|
||||
}
|
||||
return BattleTemplateToken.None;
|
||||
}
|
||||
|
||||
private string GetToken(BattleTemplateToken token, out bool isLeft)
|
||||
{
|
||||
foreach (var tuple in Left)
|
||||
{
|
||||
if (tuple.Token != token)
|
||||
continue;
|
||||
isLeft = true;
|
||||
return tuple.Text;
|
||||
}
|
||||
foreach (var tuple in Right)
|
||||
{
|
||||
if (tuple.Token != token)
|
||||
continue;
|
||||
isLeft = false;
|
||||
return tuple.Text;
|
||||
}
|
||||
foreach (var tuple in Center)
|
||||
{
|
||||
if (tuple.Token != token)
|
||||
continue;
|
||||
isLeft = false;
|
||||
return tuple.Text;
|
||||
}
|
||||
throw new ArgumentException($"Token {token} not found in config");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string representation of the token. No value is combined with it.
|
||||
/// </summary>
|
||||
public string Push(BattleTemplateToken token) => GetToken(token, out _);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string representation of the token, and combines the value with it.
|
||||
/// </summary>
|
||||
public string Push<T>(BattleTemplateToken token, T value)
|
||||
{
|
||||
var str = GetToken(token, out var isLeft);
|
||||
if (isLeft)
|
||||
return $"{str}{value}";
|
||||
return $"{value}{str}";
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Push{T}(BattleTemplateToken,T)"/>
|
||||
public void Push<T>(BattleTemplateToken token, T value, StringBuilder sb)
|
||||
{
|
||||
var str = GetToken(token, out var isLeft);
|
||||
if (isLeft)
|
||||
sb.Append(str).Append(value);
|
||||
else
|
||||
sb.Append(value).Append(str);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks all representations of the stat name for a match.
|
||||
/// </summary>
|
||||
/// <param name="stat">Stat name</param>
|
||||
/// <returns>-1 if not found, otherwise the index of the stat</returns>
|
||||
public int GetStatIndex(ReadOnlySpan<char> stat)
|
||||
{
|
||||
var index = StatNames.GetStatIndex(stat);
|
||||
if (index != -1)
|
||||
return index;
|
||||
index = StatNamesFull.GetStatIndex(stat);
|
||||
if (index != -1)
|
||||
return index;
|
||||
|
||||
foreach (var set in StatDisplayConfig.Custom)
|
||||
{
|
||||
index = set.GetStatIndex(stat);
|
||||
if (index != -1)
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public StatParseResult TryParseStats(ReadOnlySpan<char> message, Span<int> bestResult)
|
||||
{
|
||||
var result = ParseInternal(message, bestResult);
|
||||
ReorderSpeedNotLast(bestResult);
|
||||
return result;
|
||||
}
|
||||
|
||||
private StatParseResult ParseInternal(ReadOnlySpan<char> message, Span<int> bestResult)
|
||||
{
|
||||
Span<int> original = stackalloc int[bestResult.Length];
|
||||
bestResult.CopyTo(original);
|
||||
|
||||
var result = StatNames.TryParse(message, bestResult);
|
||||
if (result.IsParseClean)
|
||||
return result;
|
||||
|
||||
// Check if the others get a better result
|
||||
int bestCount = result.CountParsed;
|
||||
Span<int> tmp = stackalloc int[bestResult.Length];
|
||||
// Check Long Stat names
|
||||
{
|
||||
original.CopyTo(tmp); // restore original defaults
|
||||
var other = StatNamesFull.TryParse(message, tmp);
|
||||
if (other.IsParseClean)
|
||||
{
|
||||
tmp.CopyTo(bestResult);
|
||||
return other;
|
||||
}
|
||||
if (other.CountParsed > bestCount)
|
||||
{
|
||||
bestCount = other.CountParsed;
|
||||
tmp.CopyTo(bestResult);
|
||||
}
|
||||
}
|
||||
// Check custom parsers
|
||||
foreach (var set in StatDisplayConfig.Custom)
|
||||
{
|
||||
original.CopyTo(tmp); // restore original defaults
|
||||
var other = set.TryParse(message, tmp);
|
||||
if (other.IsParseClean)
|
||||
{
|
||||
tmp.CopyTo(bestResult);
|
||||
return other;
|
||||
}
|
||||
if (other.CountParsed > bestCount)
|
||||
{
|
||||
bestCount = other.CountParsed;
|
||||
tmp.CopyTo(bestResult);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void ReorderSpeedNotLast<T>(Span<T> arr)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(arr.Length, 6);
|
||||
var speed = arr[5];
|
||||
arr[5] = arr[4];
|
||||
arr[4] = arr[3];
|
||||
arr[3] = speed;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Token order for displaying the battle template.
|
||||
/// </summary>
|
||||
public enum BattleTemplateDisplayStyle : sbyte
|
||||
{
|
||||
Custom = -1,
|
||||
Showdown = 0, // default
|
||||
Legacy,
|
||||
Brief, // default preview hover style
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Settings for exporting a battle template.
|
||||
/// </summary>
|
||||
public readonly ref struct BattleTemplateExportSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Order of the tokens in the export.
|
||||
/// </summary>
|
||||
public ReadOnlySpan<BattleTemplateToken> Order { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Localization for the battle template.
|
||||
/// </summary>
|
||||
public BattleTemplateLocalization Localization { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Display style for the EVs.
|
||||
/// </summary>
|
||||
public StatDisplayStyle StatsEVs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Display style for the IVs.
|
||||
/// </summary>
|
||||
public StatDisplayStyle StatsIVs { get; init; }
|
||||
|
||||
public StatDisplayStyle StatsOther { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Display style for the moves.
|
||||
/// </summary>
|
||||
public MoveDisplayStyle Moves { get; init; }
|
||||
|
||||
public static BattleTemplateExportSettings Showdown => new(BattleTemplateConfig.Showdown);
|
||||
public static BattleTemplateExportSettings CommunityStandard => new(BattleTemplateConfig.CommunityStandard);
|
||||
|
||||
public BattleTemplateExportSettings(string language) : this(BattleTemplateConfig.Showdown, language) { }
|
||||
public BattleTemplateExportSettings(LanguageID language) : this(BattleTemplateConfig.Showdown, language) { }
|
||||
|
||||
public BattleTemplateExportSettings(ReadOnlySpan<BattleTemplateToken> order, string language = BattleTemplateLocalization.DefaultLanguage)
|
||||
{
|
||||
Localization = BattleTemplateLocalization.GetLocalization(language);
|
||||
Order = order;
|
||||
}
|
||||
|
||||
public BattleTemplateExportSettings(ReadOnlySpan<BattleTemplateToken> order, LanguageID language)
|
||||
{
|
||||
Localization = BattleTemplateLocalization.GetLocalization(language);
|
||||
Order = order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the token is in the export.
|
||||
/// </summary>
|
||||
public bool IsTokenInExport(BattleTemplateToken token)
|
||||
{
|
||||
foreach (var t in Order)
|
||||
{
|
||||
if (t == token)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the token in the export.
|
||||
/// </summary>
|
||||
public int GetTokenIndex(BattleTemplateToken token)
|
||||
{
|
||||
for (int i = 0; i < Order.Length; i++)
|
||||
{
|
||||
if (Order[i] == token)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the token is in the export.
|
||||
/// </summary>
|
||||
/// <remarks>Should be a static method, but is not because it feels better this way.</remarks>
|
||||
/// <param name="token">Token to check</param>
|
||||
/// <param name="tokens">Tokens to check against</param>
|
||||
public bool IsTokenInExport(BattleTemplateToken token, ReadOnlySpan<BattleTemplateToken> tokens)
|
||||
{
|
||||
foreach (var t in tokens)
|
||||
{
|
||||
if (t == token)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides information for localizing <see cref="IBattleTemplate"/> sets.
|
||||
/// </summary>
|
||||
/// <param name="Strings">In-game strings</param>
|
||||
/// <param name="Config">Grammar and prefix/suffix tokens</param>
|
||||
public sealed record BattleTemplateLocalization(GameStrings Strings, BattleTemplateConfig Config)
|
||||
{
|
||||
public const string DefaultLanguage = GameLanguage.DefaultLanguage; // English
|
||||
|
||||
private static readonly Dictionary<string, BattleTemplateLocalization> Cache = new(1);
|
||||
private static readonly BattleTemplateConfigContext Context = new(LocalizationStorage<BattleTemplateConfig>.Options);
|
||||
public static readonly LocalizationStorage<BattleTemplateConfig> ConfigCache = new("battle", Context.BattleTemplateConfig);
|
||||
public static readonly BattleTemplateLocalization Default = GetLocalization(DefaultLanguage);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localization for the requested language.
|
||||
/// </summary>
|
||||
/// <param name="language">Language code</param>
|
||||
public static BattleTemplateConfig GetConfig(string language) => ConfigCache.Get(language);
|
||||
|
||||
/// <param name="language"><see cref="LanguageID"/> index</param>
|
||||
/// <inheritdoc cref="GetLocalization(string)"/>
|
||||
public static BattleTemplateLocalization GetLocalization(LanguageID language) =>
|
||||
GetLocalization(language.GetLanguageCode());
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localization for the requested language.
|
||||
/// </summary>
|
||||
/// <param name="language">Language code</param>
|
||||
public static BattleTemplateLocalization GetLocalization(string language)
|
||||
{
|
||||
if (Cache.TryGetValue(language, out var result))
|
||||
return result;
|
||||
|
||||
var strings = GameInfo.GetStrings(language);
|
||||
var cfg = GetConfig(language);
|
||||
result = new BattleTemplateLocalization(strings, cfg);
|
||||
Cache[language] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force loads all localizations.
|
||||
/// </summary>
|
||||
public static bool ForceLoadAll()
|
||||
{
|
||||
bool anyLoaded = false;
|
||||
foreach (var lang in GameLanguage.AllSupportedLanguages)
|
||||
{
|
||||
if (Cache.ContainsKey(lang))
|
||||
continue;
|
||||
_ = GetLocalization(lang);
|
||||
anyLoaded = true;
|
||||
}
|
||||
return anyLoaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all localizations.
|
||||
/// </summary>
|
||||
public static IReadOnlyDictionary<string, BattleTemplateLocalization> GetAll()
|
||||
{
|
||||
_ = ForceLoadAll();
|
||||
return Cache;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(BattleTemplateConfig))]
|
||||
public sealed partial class BattleTemplateConfigContext : JsonSerializerContext;
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Enum for the different tokens used in battle templates.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each token represents a specific aspect of a Pokémon's battle template.
|
||||
/// One token per line. Each token can have specific grammar rules depending on the language.
|
||||
/// </remarks>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<BattleTemplateToken>))]
|
||||
public enum BattleTemplateToken : byte
|
||||
{
|
||||
None = 0, // invalid, used as a magic value to signal that a token is not recognized
|
||||
|
||||
// Standard tokens
|
||||
Shiny,
|
||||
Ability,
|
||||
Nature,
|
||||
Friendship,
|
||||
EVs,
|
||||
IVs,
|
||||
Level,
|
||||
DynamaxLevel,
|
||||
Gigantamax,
|
||||
TeraType,
|
||||
|
||||
// Tokens that can appear multiple times
|
||||
Moves,
|
||||
|
||||
// When present, first line will not contain values for these tokens (instead outputting on separate token line)
|
||||
// Not part of the standard export format, but can be recognized/optionally used in the program
|
||||
HeldItem,
|
||||
Nickname,
|
||||
Gender,
|
||||
|
||||
// Manually appended, not stored or recognized on import
|
||||
AVs,
|
||||
GVs,
|
||||
|
||||
// Future Showdown propositions
|
||||
AbilityHeldItem, // [Ability] Item
|
||||
EVsWithNature, // +/-
|
||||
EVsAppendNature, // +/- and .. (Nature)
|
||||
|
||||
// Omitting the first line (species) shouldn't be done unless it is manually added in the presentation/export.
|
||||
FirstLine = byte.MaxValue,
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class BattleTemplateSettings
|
||||
{
|
||||
[LocalizedDescription("Settings for showing details when hovering a slot.")]
|
||||
public BattleTemplateTypeSetting Hover { get; set; } = new(BattleTemplateDisplayStyle.Brief, LanguageID.None, MoveDisplayStyle.Directional);
|
||||
|
||||
[LocalizedDescription("Settings for showing details when exporting a slot.")]
|
||||
public BattleTemplateTypeSetting Export { get; set; } = new(BattleTemplateDisplayStyle.Showdown, LanguageID.English);
|
||||
}
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
public sealed class BattleTemplateTypeSetting
|
||||
{
|
||||
[LocalizedDescription("Language to use when exporting a battle template. If not specified in settings, will use current language.")]
|
||||
public LanguageID Language { get; set; }
|
||||
public StatDisplayStyle StyleStatEVs { get; set; }
|
||||
public StatDisplayStyle StyleStatIVs { get; set; }
|
||||
public StatDisplayStyle StyleStatOther { get; set; }
|
||||
public MoveDisplayStyle StyleMove { get; set; }
|
||||
|
||||
[LocalizedDescription("Custom stat labels and grammar.")]
|
||||
public StatDisplayConfig StatsCustom { get; set; } = StatDisplayConfig.HABCDS;
|
||||
|
||||
[LocalizedDescription("Display format to use when exporting a battle template from the program.")]
|
||||
public BattleTemplateDisplayStyle TokenOrder { get; set; }
|
||||
|
||||
[LocalizedDescription("Custom ordering for exporting a set, if chosen via export display style.")]
|
||||
public BattleTemplateToken[] TokenOrderCustom { get; set; } = BattleTemplateConfig.Showdown.ToArray();
|
||||
|
||||
public BattleTemplateTypeSetting() { }
|
||||
public BattleTemplateTypeSetting(BattleTemplateDisplayStyle style, LanguageID lang, MoveDisplayStyle move = MoveDisplayStyle.Fill)
|
||||
{
|
||||
TokenOrder = style;
|
||||
Language = lang;
|
||||
StyleMove = move;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{TokenOrder} {Language}";
|
||||
|
||||
private LanguageID GetLanguageExport(LanguageID program) => GetLanguage(Language, program);
|
||||
|
||||
public BattleTemplateExportSettings GetSettings(LanguageID programLanguage, EntityContext context) => new(GetOrder(TokenOrder, TokenOrderCustom), GetLanguageExport(programLanguage))
|
||||
{
|
||||
StatsEVs = StyleStatEVs,
|
||||
StatsIVs = StyleStatIVs,
|
||||
StatsOther = StyleStatOther,
|
||||
Moves = GetMoveDisplayStyle(StyleMove, context),
|
||||
};
|
||||
|
||||
private static LanguageID GetLanguage(LanguageID choice, LanguageID program)
|
||||
{
|
||||
if (choice != LanguageID.None)
|
||||
return choice;
|
||||
if (program == LanguageID.None)
|
||||
return LanguageID.English;
|
||||
return program;
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<BattleTemplateToken> GetOrder(BattleTemplateDisplayStyle style, ReadOnlySpan<BattleTemplateToken> custom) => style switch
|
||||
{
|
||||
BattleTemplateDisplayStyle.Legacy => BattleTemplateConfig.CommunityStandard,
|
||||
BattleTemplateDisplayStyle.Brief => BattleTemplateConfig.DefaultHover,
|
||||
BattleTemplateDisplayStyle.Custom => custom,
|
||||
_ => BattleTemplateConfig.Showdown,
|
||||
};
|
||||
|
||||
private static MoveDisplayStyle GetMoveDisplayStyle(MoveDisplayStyle style, EntityContext context) => style switch
|
||||
{
|
||||
MoveDisplayStyle.Directional when context is EntityContext.Gen9a => MoveDisplayStyle.Directional,
|
||||
_ => MoveDisplayStyle.Fill,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
public readonly record struct BattleTemplateParseError(BattleTemplateParseErrorType Type, string Value)
|
||||
{
|
||||
public string Humanize(BattleTemplateParseErrorLocalization localization) => Type.Humanize(localization, Value);
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Localized strings for <see cref="BattleTemplateParseErrorType"/> values.
|
||||
/// Each enum member maps 1:1 to a property for JSON (de)serialization.
|
||||
/// </summary>
|
||||
public sealed class BattleTemplateParseErrorLocalization
|
||||
{
|
||||
private static readonly BattleTemplateParseErrorLocalizationContext Context = new(LocalizationStorage<BattleTemplateParseErrorLocalization>.Options);
|
||||
public static readonly LocalizationStorage<BattleTemplateParseErrorLocalization> Cache = new("setparse", Context.BattleTemplateParseErrorLocalization);
|
||||
public static BattleTemplateParseErrorLocalization Get(string language = GameLanguage.DefaultLanguage) => Cache.Get(language);
|
||||
public static BattleTemplateParseErrorLocalization Get(LanguageID language) => Cache.Get(language.GetLanguageCode());
|
||||
|
||||
// General / structural
|
||||
public required string LineLength { get; init; } = "Line exceeded the maximum supported length: {0}";
|
||||
|
||||
// Token issues
|
||||
public required string TokenUnknown { get; init; } = "Unrecognized: {0}";
|
||||
public required string TokenFailParse { get; init; } = "Token could not be parsed: {0}";
|
||||
|
||||
// Move issues
|
||||
public required string MoveCountTooMany { get; init; } = "Too many moves specified: {0}";
|
||||
public required string MoveSlotAlreadyUsed { get; init; } = "Move slot already used: {0}";
|
||||
public required string MoveDuplicate { get; init; } = "Duplicate move specified: {0}";
|
||||
public required string MoveUnrecognized { get; init; } = "Move not recognized: {0}";
|
||||
|
||||
// Item
|
||||
public required string ItemUnrecognized { get; init; } = "Held item not recognized: {0}";
|
||||
|
||||
// Ability
|
||||
public required string AbilityDeclaration { get; init; } = "Ability already declared: {0}";
|
||||
public required string AbilityUnrecognized { get; init; } = "Ability not recognized: {0}";
|
||||
public required string AbilityAlreadySpecified { get; init; } = "Ability already specified: {0}";
|
||||
|
||||
// Nature
|
||||
public required string NatureUnrecognized { get; init; } = "Nature not recognized: {0}";
|
||||
public required string NatureAlreadySpecified { get; init; } = "Nature already specified: {0}";
|
||||
|
||||
// Hidden Power
|
||||
public required string HiddenPowerUnknownType { get; init; } = "Hidden Power type not recognized: {0}";
|
||||
public required string HiddenPowerIncompatibleIVs { get; init; } = "Hidden Power type incompatible with IVs: {0}";
|
||||
|
||||
// EffortValue Nature Amp (Stat modifiers with + / - )
|
||||
public required string NatureEffortAmpDeclaration { get; init; } = "Nature / effort amp already declared: {0}";
|
||||
public required string NatureEffortAmpUnknown { get; init; } = "Unknown nature effort amp token: {0}";
|
||||
public required string NatureEffortAmpAlreadySpecified { get; init; } = "Nature effort amp already specified: {0}";
|
||||
public required string NatureEffortAmpConflictNature { get; init; } = "Declared effort amp conflicts with previously specified nature.";
|
||||
public required string NatureAmpNoPlus { get; init; } = "Missing '+' nature amp token.";
|
||||
public required string NatureAmpNoMinus { get; init; } = "Missing '-' nature amp token.";
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(BattleTemplateParseErrorLocalization))]
|
||||
public sealed partial class BattleTemplateParseErrorLocalizationContext : JsonSerializerContext;
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
using System;
|
||||
using static PKHeX.Core.BattleTemplateParseErrorType;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public enum BattleTemplateParseErrorType : byte
|
||||
{
|
||||
None = 0,
|
||||
LineLength,
|
||||
TokenUnknown,
|
||||
TokenFailParse,
|
||||
MoveCountTooMany,
|
||||
MoveSlotAlreadyUsed,
|
||||
MoveDuplicate,
|
||||
MoveUnrecognized,
|
||||
|
||||
ItemUnrecognized,
|
||||
AbilityDeclaration,
|
||||
AbilityUnrecognized,
|
||||
AbilityAlreadySpecified,
|
||||
NatureUnrecognized,
|
||||
NatureAlreadySpecified,
|
||||
|
||||
HiddenPowerUnknownType,
|
||||
HiddenPowerIncompatibleIVs,
|
||||
|
||||
NatureEffortAmpDeclaration,
|
||||
NatureEffortAmpUnknown,
|
||||
NatureEffortAmpAlreadySpecified,
|
||||
NatureEffortAmpConflictNature,
|
||||
NatureAmpNoPlus,
|
||||
NatureAmpNoMinus,
|
||||
}
|
||||
|
||||
public static class BattleTemplateParseErrorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the localized string for the provided <paramref name="type"/>.
|
||||
/// Falls back to the enum name if no mapping exists.
|
||||
/// </summary>
|
||||
public static string Humanize(this BattleTemplateParseErrorType type, BattleTemplateParseErrorLocalization localization, string value)
|
||||
{
|
||||
var template = GetTemplate(type, localization);
|
||||
if (value.Length == 0)
|
||||
return template;
|
||||
return string.Format(template, value);
|
||||
}
|
||||
|
||||
private static string GetTemplate(BattleTemplateParseErrorType type, BattleTemplateParseErrorLocalization localization) => type switch
|
||||
{
|
||||
None => "",
|
||||
LineLength => localization.LineLength,
|
||||
TokenUnknown => localization.TokenUnknown,
|
||||
TokenFailParse => localization.TokenFailParse,
|
||||
MoveCountTooMany => localization.MoveCountTooMany,
|
||||
MoveSlotAlreadyUsed => localization.MoveSlotAlreadyUsed,
|
||||
MoveDuplicate => localization.MoveDuplicate,
|
||||
MoveUnrecognized => localization.MoveUnrecognized,
|
||||
ItemUnrecognized => localization.ItemUnrecognized,
|
||||
AbilityDeclaration => localization.AbilityDeclaration,
|
||||
AbilityUnrecognized => localization.AbilityUnrecognized,
|
||||
AbilityAlreadySpecified => localization.AbilityAlreadySpecified,
|
||||
NatureUnrecognized => localization.NatureUnrecognized,
|
||||
NatureAlreadySpecified => localization.NatureAlreadySpecified,
|
||||
HiddenPowerUnknownType => localization.HiddenPowerUnknownType,
|
||||
HiddenPowerIncompatibleIVs => localization.HiddenPowerIncompatibleIVs,
|
||||
NatureEffortAmpDeclaration => localization.NatureEffortAmpDeclaration,
|
||||
NatureEffortAmpUnknown => localization.NatureEffortAmpUnknown,
|
||||
NatureEffortAmpAlreadySpecified => localization.NatureEffortAmpAlreadySpecified,
|
||||
NatureEffortAmpConflictNature => localization.NatureEffortAmpConflictNature,
|
||||
NatureAmpNoPlus => localization.NatureAmpNoPlus,
|
||||
NatureAmpNoMinus => localization.NatureAmpNoMinus,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Style to display moves.
|
||||
/// </summary>
|
||||
public enum MoveDisplayStyle : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Moves are slots 1-4, with no empty slots, and correspond to the rectangular grid without empty spaces.
|
||||
/// </summary>
|
||||
Fill,
|
||||
|
||||
/// <summary>
|
||||
/// Move slots are assigned to the directional pad, and unused directional slots are not displayed.
|
||||
/// </summary>
|
||||
Directional,
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for retrieving teams from URLs.
|
||||
/// </summary>
|
||||
public static class BattleTemplateTeams
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to check if the input text is a valid URL for a team, and if so, retrieves the team data.
|
||||
/// </summary>
|
||||
/// <param name="text">The input text to check.</param>
|
||||
/// <param name="content">When the method returns, contains the retrieved team data if the text is a valid URL; otherwise, null.</param>
|
||||
/// <returns><see langword="true"/> if the text is a valid URL and the team data was successfully retrieved; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool TryGetSetLines(string text, [NotNullWhen(true)] out string? content)
|
||||
{
|
||||
if (ShowdownTeam.IsURL(text, out var url))
|
||||
return ShowdownTeam.TryGetSets(url, out content);
|
||||
if (PokepasteTeam.IsURL(text, out url))
|
||||
return PokepasteTeam.TryGetSets(url, out content);
|
||||
content = text;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve sets from the provided text. If the text is a valid URL, it retrieves the team data from the URL.
|
||||
/// </summary>
|
||||
/// <param name="text">The input text to check.</param>
|
||||
/// <returns>An enumerable collection of <see cref="ShowdownSet"/> objects representing the sets.</returns>
|
||||
public static IEnumerable<ShowdownSet> TryGetSets(string text)
|
||||
{
|
||||
var ingest = TryGetSetLines(text, out var many) ? many : text;
|
||||
return ShowdownParsing.GetShowdownSets(ingest);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for retrieving Showdown teams from URLs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://pokepast.es/"/>
|
||||
/// </remarks>
|
||||
public static class PokepasteTeam
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates the raw URL for retrieving a team based on the supplied team identifier.
|
||||
/// </summary>
|
||||
/// <param name="team">The numeric identifier of the team.</param>
|
||||
/// <returns>A string containing the full URL to access the team data.</returns>
|
||||
public static string GetURL(ulong team) => $"https://pokepast.es/{team:x16}/raw";
|
||||
|
||||
/// <inheritdoc cref="GetURL"/>
|
||||
/// <remarks>For legacy team indexes (first 255 or so), shouldn't ever be triggered non-test team indexes.</remarks>
|
||||
public static string GetURLOld(int team) => $"https://pokepast.es/{team}/raw";
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the Showdown team data from a specified URL, and reformats it.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL to retrieve the team data from.</param>
|
||||
/// <param name="content">When the method returns, contains the processed team data if retrieval and formatting succeed; otherwise, null.</param>
|
||||
/// <returns><see langword="true"/> if the team data is successfully retrieved and reformatted; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool TryGetSets(string url, [NotNullWhen(true)] out string? content)
|
||||
{
|
||||
content = null;
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out var uriResult) || (uriResult.Scheme != Uri.UriSchemeHttp && uriResult.Scheme != Uri.UriSchemeHttps))
|
||||
return false;
|
||||
|
||||
content = NetUtil.GetStringFromURL(uriResult);
|
||||
return content != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the provided text is a valid Showdown team URL. If valid, returns a normalized API URL.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to evaluate.</param>
|
||||
/// <param name="url">When the method returns, contains the normalized API URL if the text represents a valid Showdown team URL; otherwise, null.</param>
|
||||
/// <returns><see langword="true"/> if the text is a valid Showdown team URL; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool IsURL(ReadOnlySpan<char> text, [NotNullWhen(true)] out string? url)
|
||||
{
|
||||
text = text.Trim();
|
||||
url = null;
|
||||
if (!text.StartsWith("https://pokepast.es/")) // short link
|
||||
return false;
|
||||
|
||||
return TryCheckWeb(text, out url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to extract the team identifier from a Showdown web URL and converts it to a standard API URL.
|
||||
/// </summary>
|
||||
/// <param name="text">The Showdown web URL as a read-only span of characters.</param>
|
||||
/// <param name="url">When the method returns, contains the standardized API URL if extraction is successful; otherwise, null.</param>
|
||||
/// <returns><see langword="true"/> if the team index is successfully extracted and converted; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool TryCheckWeb(ReadOnlySpan<char> text, [NotNullWhen(true)] out string? url)
|
||||
{
|
||||
// if ends with `/`, remove.
|
||||
if (text.EndsWith('/'))
|
||||
text = text[..^1]; // remove trailing slash
|
||||
// if ends with `/raw`, remove.
|
||||
if (text.EndsWith("/raw"))
|
||||
text = text[..^4]; // remove trailing /raw
|
||||
|
||||
url = null;
|
||||
|
||||
// seek back to `/`
|
||||
int start = text.LastIndexOf('/'); // seek back to /
|
||||
if (start == -1)
|
||||
return false;
|
||||
|
||||
// get the substring after
|
||||
var number = text[(start + 1)..];
|
||||
switch (number.Length)
|
||||
{
|
||||
case 16 when ulong.TryParse(number, NumberStyles.HexNumber, null, out var hash):
|
||||
url = GetURL(hash);
|
||||
return true;
|
||||
case <= 8 when int.TryParse(number, out var team):
|
||||
url = GetURLOld(team);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,476 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using static PKHeX.Core.Species;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for parsing details for <see cref="ShowdownSet"/> objects.
|
||||
/// </summary>
|
||||
public static class ShowdownParsing
|
||||
{
|
||||
private static readonly string[] genderForms = ["", "F", ""];
|
||||
|
||||
/// <inheritdoc cref="ShowdownSet.DefaultListAllocation"/>
|
||||
private const int DefaultListAllocation = ShowdownSet.DefaultListAllocation;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Form ID from the input <see cref="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">Form name to find the form index of</param>
|
||||
/// <param name="strings">Localized string source to fetch with</param>
|
||||
/// <param name="species">Species ID the form belongs to</param>
|
||||
/// <param name="context">Format the form name should appear in</param>
|
||||
/// <returns>Zero (base form) if no form matches the input string.</returns>
|
||||
public static byte GetFormFromString(ReadOnlySpan<char> name, GameStrings strings, ushort species, EntityContext context)
|
||||
{
|
||||
if (name.Length == 0)
|
||||
return 0;
|
||||
|
||||
var forms = FormConverter.GetFormList(species, strings.Types, strings.forms, genderForms, context);
|
||||
if (forms.Length < 1)
|
||||
return 0;
|
||||
|
||||
// Find first matching index that matches any case, ignoring dashes interchanged with spaces.
|
||||
for (byte i = 0; i < forms.Length; i++)
|
||||
{
|
||||
if (IsFormEquivalent(forms[i], name))
|
||||
return i;
|
||||
}
|
||||
|
||||
// No match, assume default 0 form.
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static bool IsFormEquivalent(ReadOnlySpan<char> reference, ReadOnlySpan<char> input)
|
||||
{
|
||||
if (input.Length != reference.Length)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
var c1 = input[i];
|
||||
var c2 = reference[i];
|
||||
if (char.ToUpperInvariant(c1) == char.ToUpperInvariant(c2))
|
||||
continue;
|
||||
if (c1 is ' ' or '-' && c2 is ' ' or '-')
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Form ID to string.
|
||||
/// </summary>
|
||||
/// <param name="form">Form to get the form name of</param>
|
||||
/// <param name="strings">Localized string source to fetch with</param>
|
||||
/// <param name="species">Species ID the form belongs to</param>
|
||||
/// <param name="context">Format the form name should appear in</param>
|
||||
public static string GetStringFromForm(byte form, GameStrings strings, ushort species, EntityContext context)
|
||||
{
|
||||
if (form == 0)
|
||||
return string.Empty;
|
||||
|
||||
var result = FormConverter.GetStringFromForm(species, form, strings, genderForms, context);
|
||||
if (result.Length == 0)
|
||||
return string.Empty;
|
||||
|
||||
// Showdown uses a non-standard representation for some forms, and uses interstitial dashes instead of spaces.
|
||||
if (strings.Language != LanguageID.English)
|
||||
return result;
|
||||
return GetShowdownFormName(species, result);
|
||||
}
|
||||
|
||||
private const string MiniorFormName = "Meteor";
|
||||
|
||||
/// <summary>
|
||||
/// Converts the PKHeX standard form name to Showdown's form name.
|
||||
/// </summary>
|
||||
/// <param name="species">Species ID</param>
|
||||
/// <param name="form">PKHeX form name</param>
|
||||
public static string GetShowdownFormName(ushort species, string form)
|
||||
{
|
||||
if (form.Length == 0)
|
||||
{
|
||||
return species switch
|
||||
{
|
||||
(int)Minior => MiniorFormName,
|
||||
_ => form,
|
||||
};
|
||||
}
|
||||
|
||||
return species switch
|
||||
{
|
||||
(int)Basculin when form is "Blue" => "Blue-Striped",
|
||||
(int)Vivillon when form is "Poké Ball" => "Pokeball",
|
||||
(int)Zygarde => form.Replace("-C", string.Empty).Replace("50%", string.Empty),
|
||||
(int)Minior when form.StartsWith("M-", StringComparison.OrdinalIgnoreCase) => MiniorFormName,
|
||||
(int)Minior => form.Replace("C-", string.Empty),
|
||||
(int)Necrozma when form is "Dusk" => $"{form}-Mane",
|
||||
(int)Necrozma when form is "Dawn" => $"{form}-Wings",
|
||||
(int)Polteageist or (int)Sinistea => form == "Antique" ? form : string.Empty,
|
||||
(int)Maushold when form is "Family of Four" => "Four",
|
||||
|
||||
(int)Greninja or (int)Rockruff or (int)Koraidon or (int)Miraidon => string.Empty,
|
||||
|
||||
_ => FormInfo.HasTotemForm(species) && form == "Large"
|
||||
? species is (int)Raticate or (int)Marowak ? "Alola-Totem" : "Totem"
|
||||
: form.Replace(' ', '-'),
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsTotemForm(ReadOnlySpan<char> formName) =>
|
||||
formName.Equals("Totem", StringComparison.OrdinalIgnoreCase) ||
|
||||
formName.Equals("Alola-Totem", StringComparison.OrdinalIgnoreCase) ||
|
||||
formName.Equals("Large", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool IsCosplayPikachu(ReadOnlySpan<char> formName, ReadOnlySpan<string> formNames)
|
||||
=> FormConverter.IsCosplayPikachu(formName, formNames);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the Showdown form name to PKHeX's form name.
|
||||
/// </summary>
|
||||
/// <param name="species">Species ID</param>
|
||||
/// <param name="form">Showdown form name</param>
|
||||
/// <param name="ability">Showdown ability ID</param>
|
||||
public static string GetFormNameFromShowdownFormName(ushort species, string form, int ability)
|
||||
{
|
||||
if (form.Length != 0)
|
||||
form = form.Replace(' ', '-'); // inconsistencies are great
|
||||
|
||||
return species switch
|
||||
{
|
||||
(int)Basculin when form is "Blue-Striped" => "Blue",
|
||||
(int)Vivillon when form is "Pokeball" => "Poké Ball",
|
||||
(int)Necrozma when form is "Dusk-Mane" => "Dusk",
|
||||
(int)Necrozma when form is "Dawn-Wings" => "Dawn",
|
||||
(int)Toxtricity when form is "Low-Key" => "Low Key",
|
||||
(int)Darmanitan when form is "Galar-Zen" => "Galar Zen",
|
||||
(int)Minior when form is not MiniorFormName => $"C-{form}",
|
||||
(int)Zygarde when form is "Complete" => form,
|
||||
(int)Zygarde when ability == 211 => $"{(form.Contains("10%") ? "10%" : "50%")}-C",
|
||||
(int)Greninja when ability == 210 => "Ash", // Battle Bond
|
||||
(int)Rockruff when ability == 020 => "Dusk", // Rockruff-1
|
||||
(int)Maushold when form is "Four" => "Family of Four",
|
||||
(int)Urshifu or (int)Pikachu or (int)Alcremie => form.Replace('-', ' '), // Strike and Cosplay
|
||||
|
||||
(int)Pumpkaboo or (int)Gourgeist when form is "Average" => "Medium",
|
||||
(int)Pumpkaboo or (int)Gourgeist when form is "Super" => "Jumbo",
|
||||
|
||||
_ => FormInfo.HasTotemForm(species) && form.EndsWith("Totem", StringComparison.OrdinalIgnoreCase) ? "Large" : form,
|
||||
};
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="localization">Localization data for the set.</param>
|
||||
/// <returns><see cref="ShowdownSet"/> objects until <see cref="lines"/> is consumed.</returns>
|
||||
public static IEnumerable<ShowdownSet> GetShowdownSets(IEnumerable<string> lines, BattleTemplateLocalization localization)
|
||||
{
|
||||
// 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
|
||||
var setLines = new List<string>(DefaultListAllocation);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
setLines.Add(line);
|
||||
continue;
|
||||
}
|
||||
if (setLines.Count == 0)
|
||||
continue;
|
||||
yield return new ShowdownSet(setLines, localization);
|
||||
setLines.Clear();
|
||||
}
|
||||
if (setLines.Count != 0)
|
||||
yield return new ShowdownSet(setLines, localization);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetShowdownSets(IEnumerable{string},BattleTemplateLocalization)"/>
|
||||
public static IEnumerable<ShowdownSet> GetShowdownSets(IEnumerable<string> lines)
|
||||
{
|
||||
var setLines = new List<string>(DefaultListAllocation);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
setLines.Add(line);
|
||||
continue;
|
||||
}
|
||||
if (setLines.Count == 0)
|
||||
continue;
|
||||
yield return TryParseAnyLanguage(setLines, out var set) ? set : new ShowdownSet(setLines);
|
||||
setLines.Clear();
|
||||
}
|
||||
if (setLines.Count != 0)
|
||||
yield return TryParseAnyLanguage(setLines, out var set) ? set : new ShowdownSet(setLines);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetShowdownSets(IEnumerable{string},BattleTemplateLocalization)"/>
|
||||
public static IEnumerable<ShowdownSet> GetShowdownSets(ReadOnlyMemory<char> text, BattleTemplateLocalization localization)
|
||||
{
|
||||
int start = 0;
|
||||
do
|
||||
{
|
||||
var span = text.Span;
|
||||
var slice = span[start..];
|
||||
var set = GetShowdownSet(slice, localization, out int length);
|
||||
if (set.Species == 0)
|
||||
break;
|
||||
yield return set;
|
||||
start += length;
|
||||
}
|
||||
while (start < text.Length);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetShowdownSets(IEnumerable{string},BattleTemplateLocalization)"/>
|
||||
/// <summary>
|
||||
/// Language-unknown version of <see cref="GetShowdownSets(IEnumerable{string},BattleTemplateLocalization)"/>.
|
||||
/// </summary>
|
||||
public static IEnumerable<ShowdownSet> GetShowdownSets(ReadOnlyMemory<char> text)
|
||||
{
|
||||
int start = 0;
|
||||
do
|
||||
{
|
||||
var span = text.Span;
|
||||
var slice = span[start..];
|
||||
var set = GetShowdownSet(slice, out int length);
|
||||
if (set.Species == 0)
|
||||
break;
|
||||
yield return set;
|
||||
start += length;
|
||||
}
|
||||
while (start < text.Length);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetShowdownSets(ReadOnlyMemory{char},BattleTemplateLocalization)"/>
|
||||
public static IEnumerable<ShowdownSet> GetShowdownSets(string text, BattleTemplateLocalization localization) => GetShowdownSets(text.AsMemory(), localization);
|
||||
|
||||
private static int GetLength(ReadOnlySpan<char> text)
|
||||
{
|
||||
// Find the end of the Showdown Set lines.
|
||||
// The end is implied when:
|
||||
// - we see a complete whitespace or empty line
|
||||
int length = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var newline = text.IndexOf('\n');
|
||||
if (newline == -1)
|
||||
return length + text.Length;
|
||||
|
||||
var slice = text[..newline];
|
||||
var used = newline + 1;
|
||||
length += used;
|
||||
|
||||
if (slice.IsWhiteSpace())
|
||||
return length;
|
||||
text = text[used..];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse the input <see cref="text"/> into a <see cref="ShowdownSet"/> object.
|
||||
/// </summary>
|
||||
/// <param name="text">Input string to parse.</param>
|
||||
/// <param name="localization">Input localization to use.</param>
|
||||
/// <param name="length">Amount of characters consumed from the input string.</param>
|
||||
/// <returns>Parsed <see cref="ShowdownSet"/> object if successful, otherwise might be a best-match with some/all unparsed lines.</returns>
|
||||
public static ShowdownSet GetShowdownSet(ReadOnlySpan<char> text, BattleTemplateLocalization localization, out int length)
|
||||
{
|
||||
length = GetLength(text);
|
||||
var slice = text[..length];
|
||||
var set = new ShowdownSet(slice, localization);
|
||||
while (length < text.Length && text[length] is '\r' or '\n' or ' ')
|
||||
length++;
|
||||
return set;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetShowdownSet(ReadOnlySpan{char},BattleTemplateLocalization,out int)"/>
|
||||
public static ShowdownSet GetShowdownSet(ReadOnlySpan<char> text, out int length)
|
||||
{
|
||||
length = GetLength(text);
|
||||
var slice = text[..length];
|
||||
if (!TryParseAnyLanguage(slice, out var set))
|
||||
set = new ShowdownSet(slice); // should never fall back
|
||||
while (length < text.Length && text[length] is '\r' or '\n' or ' ')
|
||||
length++;
|
||||
return set;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetShowdownSets(ReadOnlyMemory{char},BattleTemplateLocalization)"/>
|
||||
public static IEnumerable<ShowdownSet> GetShowdownSets(string text) => GetShowdownSets(text.AsMemory());
|
||||
|
||||
/// <inheritdoc cref="GetShowdownText(PKM, in BattleTemplateExportSettings)"/>
|
||||
public static string GetShowdownText(PKM pk) => GetShowdownText(pk, BattleTemplateExportSettings.Showdown);
|
||||
|
||||
/// <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>
|
||||
/// <param name="settings">Import localization/style setting</param>
|
||||
/// <returns>Multi line set data</returns>
|
||||
public static string GetShowdownText(PKM pk, in BattleTemplateExportSettings settings)
|
||||
{
|
||||
if (pk.Species == 0)
|
||||
return string.Empty;
|
||||
var set = new ShowdownSet(pk, settings.Localization);
|
||||
set.InterpretAsPreview(pk);
|
||||
return set.GetText(settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches ShowdownSet lines from the input <see cref="PKM"/> data.
|
||||
/// </summary>
|
||||
/// <param name="data">Pokémon data to summarize.</param>
|
||||
/// <param name="settings">Export localization/style setting</param>
|
||||
/// <returns>Consumable list of <see cref="ShowdownSet.Text"/> lines.</returns>
|
||||
public static IEnumerable<string> GetShowdownText(IEnumerable<PKM> data, in BattleTemplateExportSettings settings)
|
||||
{
|
||||
List<string> result = new(1);
|
||||
var sets = GetShowdownSets(data);
|
||||
foreach (var set in sets)
|
||||
result.Add(set.GetText(settings));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches ShowdownSet lines from the input <see cref="PKM"/> data.
|
||||
/// </summary>
|
||||
/// <param name="data">Pokémon data to summarize.</param>
|
||||
/// <returns>Consumable list of <see cref="ShowdownSet.Text"/> lines.</returns>
|
||||
public static IEnumerable<ShowdownSet> GetShowdownSets(IEnumerable<PKM> data)
|
||||
{
|
||||
foreach (var pk in data)
|
||||
{
|
||||
if (pk.Species == 0)
|
||||
continue;
|
||||
yield return new ShowdownSet(pk);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetShowdownSets(IEnumerable{string},BattleTemplateLocalization)"/>
|
||||
public static string GetShowdownSets(IEnumerable<PKM> data, string separator) => string.Join(separator, GetShowdownText(data, BattleTemplateExportSettings.Showdown));
|
||||
|
||||
/// <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>
|
||||
/// <param name="settings">Import localization/style setting</param>
|
||||
/// <returns>Single string containing all <see cref="ShowdownSet.Text"/> lines.</returns>
|
||||
public static string GetShowdownSets(IEnumerable<PKM> data, string separator, in BattleTemplateExportSettings settings) => string.Join(separator, GetShowdownText(data, settings));
|
||||
|
||||
/// <summary>
|
||||
/// Gets a localized string preview of the provided <see cref="pk"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon data</param>
|
||||
/// <param name="settings">Export settings</param>
|
||||
/// <returns>Multi-line string</returns>
|
||||
public static string GetLocalizedPreviewText(PKM pk, in BattleTemplateExportSettings settings)
|
||||
{
|
||||
var set = new ShowdownSet(pk, settings.Localization);
|
||||
set.InterpretAsPreview(pk);
|
||||
return set.GetText(settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse the input string into a <see cref="ShowdownSet"/> object.
|
||||
/// </summary>
|
||||
/// <param name="message">Input string to parse.</param>
|
||||
/// <param name="set">Parsed <see cref="ShowdownSet"/> object if successful, otherwise might be a best-match with some unparsed lines.</param>
|
||||
/// <returns>True if the input was parsed successfully, false otherwise.</returns>
|
||||
public static bool TryParseAnyLanguage(ReadOnlySpan<char> message, [NotNullWhen(true)] out ShowdownSet? set)
|
||||
{
|
||||
set = null;
|
||||
if (message.Length == 0)
|
||||
return false;
|
||||
|
||||
var invalid = int.MaxValue;
|
||||
var all = BattleTemplateLocalization.GetAll();
|
||||
foreach (var lang in all)
|
||||
{
|
||||
var local = lang.Value;
|
||||
var tmp = new ShowdownSet(message, local);
|
||||
var bad = tmp.InvalidLines.Count;
|
||||
if (bad == 0)
|
||||
{
|
||||
set = tmp;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for invalid lines
|
||||
if (bad >= invalid)
|
||||
continue;
|
||||
|
||||
// Best so far.
|
||||
invalid = bad;
|
||||
set = tmp;
|
||||
}
|
||||
if (set is null)
|
||||
return false;
|
||||
return set.Species != 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TryParseAnyLanguage(ReadOnlySpan{char}, out ShowdownSet?)"/>
|
||||
public static bool TryParseAnyLanguage(IReadOnlyList<string> setLines, [NotNullWhen(true)] out ShowdownSet? set)
|
||||
{
|
||||
set = null;
|
||||
if (setLines.Count == 0)
|
||||
return false;
|
||||
|
||||
var invalid = int.MaxValue;
|
||||
var all = BattleTemplateLocalization.GetAll();
|
||||
foreach (var lang in all)
|
||||
{
|
||||
var local = lang.Value;
|
||||
var tmp = new ShowdownSet(setLines, local);
|
||||
var bad = tmp.InvalidLines.Count;
|
||||
if (bad == 0)
|
||||
{
|
||||
set = tmp;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for invalid lines
|
||||
if (bad >= invalid)
|
||||
continue;
|
||||
|
||||
// Best so far.
|
||||
invalid = bad;
|
||||
set = tmp;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to translate the input battle template <see cref="message"/> into a localized string.
|
||||
/// </summary>
|
||||
/// <param name="message">Input string to parse.</param>
|
||||
/// <param name="outputSettings">Export settings</param>
|
||||
/// <param name="translated">Translated string if successful.</param>
|
||||
/// <returns><see langword="true"/> if the input was translated successfully, <see langword="false"/> otherwise.</returns>
|
||||
public static bool TryTranslate(ReadOnlySpan<char> message, BattleTemplateExportSettings outputSettings, [NotNullWhen(true)] out string? translated)
|
||||
{
|
||||
translated = null;
|
||||
if (!TryParseAnyLanguage(message, out var set))
|
||||
return false;
|
||||
translated = set.GetText(outputSettings);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TryTranslate(ReadOnlySpan{char}, BattleTemplateExportSettings, out string?)"/>
|
||||
public static bool TryTranslate(IReadOnlyList<string> message, BattleTemplateExportSettings outputSettings, [NotNullWhen(true)] out string? translated)
|
||||
{
|
||||
translated = null;
|
||||
if (!TryParseAnyLanguage(message, out var set))
|
||||
return false;
|
||||
translated = set.GetText(outputSettings);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,175 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for retrieving Showdown teams from URLs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see href="https://play.pokemonshowdown.com/"/>
|
||||
/// </remarks>
|
||||
public static class ShowdownTeam
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates the API URL for retrieving a Showdown team based on the supplied team identifier.
|
||||
/// </summary>
|
||||
/// <param name="team">The numeric identifier of the team.</param>
|
||||
/// <returns>A string containing the full URL to access the team data via the API.</returns>
|
||||
public static string GetURL(int team) => $"https://play.pokemonshowdown.com/api/getteam?teamid={team}&raw=1";
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the Showdown team data from a specified URL, and reformats it.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL to retrieve the team data from.</param>
|
||||
/// <param name="content">When the method returns, contains the processed team data if retrieval and formatting succeed; otherwise, null.</param>
|
||||
/// <returns><see langword="true"/> if the team data is successfully retrieved and reformatted; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool TryGetSets(string url, [NotNullWhen(true)] out string? content)
|
||||
{
|
||||
content = null;
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out var uriResult) || (uriResult.Scheme != Uri.UriSchemeHttp && uriResult.Scheme != Uri.UriSchemeHttps))
|
||||
return false;
|
||||
|
||||
content = NetUtil.GetStringFromURL(uriResult);
|
||||
if (content == null)
|
||||
return false;
|
||||
|
||||
return GetFromReply(ref content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the team data from the API reply and reformats it by replacing escaped newline
|
||||
/// characters with system-specific line breaks.
|
||||
/// </summary>
|
||||
/// <param name="content">
|
||||
/// A reference to the API response string. On successful extraction, the value is replaced
|
||||
/// with the reformatted team data; otherwise, it remains unchanged.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if the team data is successfully extracted and reformatted; otherwise, <see langword="false"/>.
|
||||
/// </returns>
|
||||
public static bool GetFromReply(ref string content)
|
||||
{
|
||||
// reformat
|
||||
const string startText = """
|
||||
"team":"
|
||||
""";
|
||||
var start = content.IndexOf(startText, StringComparison.Ordinal);
|
||||
if (start == -1)
|
||||
return false;
|
||||
start += startText.Length; // skip to the start of the team
|
||||
|
||||
var end = content.LastIndexOf("\\n", StringComparison.Ordinal);
|
||||
if (end == -1)
|
||||
return false;
|
||||
var length = end - start;
|
||||
if (length < 5) // arbitrary length check
|
||||
return false;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(content, start, length);
|
||||
sb.Replace("\\n", Environment.NewLine);
|
||||
content = sb.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the provided text is a valid Showdown team URL. If valid, returns a normalized API URL.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to evaluate.</param>
|
||||
/// <param name="url">When the method returns, contains the normalized API URL if the text represents a valid Showdown team URL; otherwise, null.</param>
|
||||
/// <returns><see langword="true"/> if the text is a valid Showdown team URL; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool IsURL(ReadOnlySpan<char> text, [NotNullWhen(true)] out string? url)
|
||||
{
|
||||
text = text.Trim();
|
||||
if (text.StartsWith("https://psim.us/t/") || // short link
|
||||
text.StartsWith("https://teams.pokemonshowdown.com/"))
|
||||
{
|
||||
return TryCheckWeb(text, out url);
|
||||
}
|
||||
|
||||
if (text.StartsWith("https://play.pokemonshowdown.com/api/getteam?teamid="))
|
||||
return TryCheckAPI(text, out url);
|
||||
|
||||
url = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to extract the team identifier from a Showdown web URL and converts it to a standard API URL.
|
||||
/// </summary>
|
||||
/// <param name="text">The Showdown web URL as a read-only span of characters.</param>
|
||||
/// <param name="url">When the method returns, contains the standardized API URL if extraction is successful; otherwise, null.</param>
|
||||
/// <returns><see langword="true"/> if the team index is successfully extracted and converted; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool TryCheckWeb(ReadOnlySpan<char> text, [NotNullWhen(true)] out string? url)
|
||||
{
|
||||
url = null;
|
||||
if (!TryGetIndexWeb(text, out var team))
|
||||
return false;
|
||||
url = GetURL(team);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to extract the team identifier from a Showdown API URL and returns a standardized API URL.
|
||||
/// </summary>
|
||||
/// <param name="text">The Showdown API URL as a read-only span of characters.</param>
|
||||
/// <param name="url">When the method returns, contains the standardized API URL if extraction is successful; otherwise, null.</param>
|
||||
/// <returns><see langword="true"/> if the team index is successfully extracted and the URL normalized; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool TryCheckAPI(ReadOnlySpan<char> text, [NotNullWhen(true)] out string? url)
|
||||
{
|
||||
url = null;
|
||||
if (!TryGetIndexAPI(text, out var team))
|
||||
return false;
|
||||
url = GetURL(team);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the team identifier from a Showdown web URL.
|
||||
/// </summary>
|
||||
/// <param name="text">The Showdown web URL provided as a read-only span of characters.</param>
|
||||
/// <param name="team">When the method returns, contains the extracted team identifier if successful; otherwise, zero.</param>
|
||||
/// <returns><see langword="true"/> if the team identifier is successfully extracted; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool TryGetIndexWeb(ReadOnlySpan<char> text, out int team)
|
||||
{
|
||||
team = 0;
|
||||
if (text.EndsWith('/'))
|
||||
text = text[..^1]; // remove trailing slash
|
||||
if (text.EndsWith("/raw"))
|
||||
text = text[..^4]; // remove trailing /raw
|
||||
|
||||
int start = text.LastIndexOf('/'); // seek back to =
|
||||
if (start == -1)
|
||||
return false;
|
||||
|
||||
var number = text[(start + 1)..];
|
||||
if (!int.TryParse(number, out team))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the team identifier from a Showdown API URL.
|
||||
/// </summary>
|
||||
/// <param name="text">The Showdown API URL as a read-only span of characters.</param>
|
||||
/// <param name="team">When the method returns, contains the extracted team identifier if successful; otherwise, zero.</param>
|
||||
/// <returns><see langword="true"/> if the team identifier is successfully extracted; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool TryGetIndexAPI(ReadOnlySpan<char> text, out int team)
|
||||
{
|
||||
team = 0;
|
||||
if (!text.EndsWith("&raw=1"))
|
||||
return false;
|
||||
|
||||
text = text[..^6];
|
||||
int start = text.LastIndexOf('='); // seek back to =
|
||||
if (start == -1)
|
||||
return false;
|
||||
|
||||
var number = text[(start + 1)..];
|
||||
if (!int.TryParse(number, out team))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,411 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for displaying stats.
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
public sealed class StatDisplayConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Stat names are displayed without localization; H:X A:X B:X C:X D:X S:X
|
||||
/// </summary>
|
||||
public static readonly StatDisplayConfig HABCDS = new()
|
||||
{
|
||||
Names = ["H", "A", "B", "C", "D", "S"],
|
||||
Separator = " ",
|
||||
ValueGap = ":",
|
||||
IsLeft = true,
|
||||
AlwaysShow = true,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Stat names are displayed without localization; X/X/X/X/X/X
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Same as <see cref="Raw00"/> but with no leading zeroes.
|
||||
/// </remarks>
|
||||
public static readonly StatDisplayConfig Raw = new()
|
||||
{
|
||||
Names = [],
|
||||
Separator = "/",
|
||||
ValueGap = string.Empty,
|
||||
AlwaysShow = true,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Stat names are displayed without localization; XX/XX/XX/XX/XX/XX
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Same as <see cref="Raw"/> but with 2 digits (leading zeroes).
|
||||
/// </remarks>
|
||||
public static readonly StatDisplayConfig Raw00 = new()
|
||||
{
|
||||
Names = [],
|
||||
Separator = "/",
|
||||
ValueGap = string.Empty,
|
||||
AlwaysShow = true,
|
||||
MinimumDigits = 2,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// List of stat display styles that are commonly used and not specific to a localization.
|
||||
/// </summary>
|
||||
public static List<StatDisplayConfig> Custom { get; } = [HABCDS, Raw]; // Raw00 parses equivalent to Raw
|
||||
|
||||
/// <summary>List of stat names to display</summary>
|
||||
public required string[] Names { get; init; }
|
||||
|
||||
/// <summary>Separator between each stat+value declaration</summary>
|
||||
public string Separator { get; init; } = " / ";
|
||||
|
||||
/// <summary>Separator between the stat name and value</summary>
|
||||
public string ValueGap { get; init; } = " ";
|
||||
|
||||
/// <summary><see langword="true"/> if the text is displayed on the left side of the value</summary>
|
||||
public bool IsLeft { get; init; }
|
||||
|
||||
/// <summary><see langword="true"/> if the stat is always shown, even if the value is default</summary>
|
||||
public bool AlwaysShow { get; init; }
|
||||
|
||||
/// <summary>Minimum number of digits to show for the stat value.</summary>
|
||||
public int MinimumDigits { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the displayed stat name (in visual order) via a case-insensitive search.
|
||||
/// </summary>
|
||||
/// <param name="stat">Stat name, trimmed.</param>
|
||||
/// <returns>-1 if not found, otherwise the index of the stat name.</returns>
|
||||
public int GetStatIndex(ReadOnlySpan<char> stat)
|
||||
{
|
||||
for (int i = 0; i < Names.Length; i++)
|
||||
{
|
||||
if (stat.Equals(Names[i], StringComparison.OrdinalIgnoreCase))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public override string ToString() => string.Join(Separator, Names);
|
||||
|
||||
/// <summary>
|
||||
/// Formats a stat value into a string builder.
|
||||
/// </summary>
|
||||
/// <param name="sb">Result string builder</param>
|
||||
/// <param name="statIndex">Display index of the stat</param>
|
||||
/// <param name="statValue">Stat value</param>
|
||||
/// <param name="valueSuffix">Optional suffix for the value, to display a stat amplification request</param>
|
||||
/// <param name="skipValue"><see langword="true"/> to skip the value, only displaying the stat name and amplification (if provided)</param>
|
||||
public void Format<T>(StringBuilder sb, int statIndex, T statValue, ReadOnlySpan<char> valueSuffix = default, bool skipValue = false)
|
||||
{
|
||||
var statName = statIndex < Names.Length ? Names[statIndex] : "";
|
||||
var length = GetStatSize(statName, statValue, valueSuffix, skipValue);
|
||||
|
||||
if (sb.Length + length > sb.Capacity)
|
||||
sb.EnsureCapacity(sb.Length + length);
|
||||
Append(sb, statName, statValue, valueSuffix, skipValue);
|
||||
}
|
||||
|
||||
private void Append<T>(StringBuilder sb, ReadOnlySpan<char> statName, T statValue, ReadOnlySpan<char> valueSuffix, bool skipValue)
|
||||
{
|
||||
int start = sb.Length;
|
||||
|
||||
if (!skipValue)
|
||||
{
|
||||
sb.Append(statValue);
|
||||
var length = sb.Length - start;
|
||||
if (length < MinimumDigits)
|
||||
sb.Insert(start, "0", MinimumDigits - length);
|
||||
}
|
||||
sb.Append(valueSuffix);
|
||||
|
||||
if (IsLeft)
|
||||
{
|
||||
sb.Insert(start, ValueGap);
|
||||
sb.Insert(start, statName);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(ValueGap);
|
||||
sb.Append(statName);
|
||||
}
|
||||
}
|
||||
|
||||
private int GetStatSize<T>(ReadOnlySpan<char> statName, T statValue, ReadOnlySpan<char> valueSuffix, bool skipValue)
|
||||
{
|
||||
var length = statName.Length + ValueGap.Length + valueSuffix.Length;
|
||||
if (!skipValue)
|
||||
length += (int)Math.Max(MinimumDigits, Math.Floor(Math.Log10(Convert.ToDouble(statValue)) + 1));
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the separator character used for parsing.
|
||||
/// </summary>
|
||||
private char GetSeparatorParse() => GetSeparatorParse(Separator);
|
||||
|
||||
private static char GetSeparatorParse(ReadOnlySpan<char> sep) => sep.Length switch
|
||||
{
|
||||
0 => ' ',
|
||||
1 => sep[0],
|
||||
_ => sep.Trim()[0]
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Imports a list of stats from a string.
|
||||
/// </summary>
|
||||
/// <param name="message">Input string</param>
|
||||
/// <param name="result">Result storage</param>
|
||||
/// <returns>Parse result</returns>
|
||||
public StatParseResult TryParse(ReadOnlySpan<char> message, Span<int> result)
|
||||
{
|
||||
var separator = GetSeparatorParse();
|
||||
var gap = ValueGap.AsSpan().Trim();
|
||||
// If stats are not labeled, parse with the straightforward parser.
|
||||
if (Names.Length == 0)
|
||||
return TryParseRaw(message, result, separator);
|
||||
else if (IsLeft)
|
||||
return TryParseIsLeft(message, result, separator, gap);
|
||||
else
|
||||
return TryParseRight(message, result, separator, gap);
|
||||
}
|
||||
|
||||
private StatParseResult TryParseIsLeft(ReadOnlySpan<char> message, Span<int> result, char separator, ReadOnlySpan<char> valueGap)
|
||||
{
|
||||
// Parse left-to-right by splitting on separator, then identifying which stat each segment contains.
|
||||
// Format: "StatName Value / StatName Value / ..."
|
||||
var rec = new StatParseResult();
|
||||
|
||||
while (message.Length != 0)
|
||||
{
|
||||
// Get the next segment
|
||||
ReadOnlySpan<char> segment;
|
||||
var indexSeparator = message.IndexOf(separator);
|
||||
if (indexSeparator != -1)
|
||||
{
|
||||
segment = message[..indexSeparator].Trim();
|
||||
message = message[(indexSeparator + 1)..].TrimStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
segment = message.Trim();
|
||||
message = default;
|
||||
}
|
||||
|
||||
if (segment.Length == 0)
|
||||
{
|
||||
rec.MarkDirty(); // empty segment
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find which stat name this segment contains (should be at the start for IsLeft)
|
||||
var statIndex = TryFindStatNameAtStart(segment, out var statNameLength);
|
||||
if (statIndex == -1)
|
||||
{
|
||||
rec.MarkDirty(); // unrecognized stat
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract the value after the stat name
|
||||
var value = segment[statNameLength..].TrimStart();
|
||||
if (valueGap.Length > 0 && value.StartsWith(valueGap))
|
||||
value = value[valueGap.Length..].TrimStart();
|
||||
|
||||
if (value.Length != 0)
|
||||
{
|
||||
var amped = TryPeekAmp(ref value, ref rec, statIndex);
|
||||
if (amped && value.Length == 0)
|
||||
rec.MarkParsed(statIndex);
|
||||
else
|
||||
TryParse(result, ref rec, value, statIndex);
|
||||
}
|
||||
else if (rec.WasParsed(statIndex))
|
||||
{
|
||||
rec.MarkDirty(); // duplicate stat
|
||||
}
|
||||
}
|
||||
|
||||
rec.FinishParse(Names.Length);
|
||||
return rec;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find a stat name at the start of the segment.
|
||||
/// </summary>
|
||||
/// <param name="segment">Segment to search</param>
|
||||
/// <param name="length">Length of the matched stat name</param>
|
||||
/// <returns>Stat index if found, -1 otherwise</returns>
|
||||
private int TryFindStatNameAtStart(ReadOnlySpan<char> segment, out int length)
|
||||
{
|
||||
for (int i = 0; i < Names.Length; i++)
|
||||
{
|
||||
var name = Names[i];
|
||||
if (segment.StartsWith(name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
length = name.Length;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
length = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find a stat name at the end of the segment.
|
||||
/// </summary>
|
||||
/// <param name="segment">Segment to search</param>
|
||||
/// <param name="length">Length of the matched stat name</param>
|
||||
/// <returns>Stat index if found, -1 otherwise</returns>
|
||||
private int TryFindStatNameAtEnd(ReadOnlySpan<char> segment, out int length)
|
||||
{
|
||||
for (int i = 0; i < Names.Length; i++)
|
||||
{
|
||||
var name = Names[i];
|
||||
if (segment.EndsWith(name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
length = name.Length;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
length = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
private StatParseResult TryParseRight(ReadOnlySpan<char> message, Span<int> result, char separator, ReadOnlySpan<char> valueGap)
|
||||
{
|
||||
// Parse left-to-right by splitting on separator, then identifying which stat each segment contains.
|
||||
// Format: "Value StatName / Value StatName / ..."
|
||||
var rec = new StatParseResult();
|
||||
|
||||
while (message.Length != 0)
|
||||
{
|
||||
// Get the next segment
|
||||
ReadOnlySpan<char> segment;
|
||||
var indexSeparator = message.IndexOf(separator);
|
||||
if (indexSeparator != -1)
|
||||
{
|
||||
segment = message[..indexSeparator].Trim();
|
||||
message = message[(indexSeparator + 1)..].TrimStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
segment = message.Trim();
|
||||
message = default;
|
||||
}
|
||||
|
||||
if (segment.Length == 0)
|
||||
{
|
||||
rec.MarkDirty(); // empty segment
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find which stat name this segment contains (should be at the end for Right/English style)
|
||||
var statIndex = TryFindStatNameAtEnd(segment, out var statNameLength);
|
||||
if (statIndex == -1)
|
||||
{
|
||||
rec.MarkDirty(); // unrecognized stat
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract the value before the stat name
|
||||
var value = segment[..^statNameLength].TrimEnd();
|
||||
if (valueGap.Length > 0 && value.EndsWith(valueGap))
|
||||
value = value[..^valueGap.Length].TrimEnd();
|
||||
|
||||
if (value.Length != 0)
|
||||
{
|
||||
var amped = TryPeekAmp(ref value, ref rec, statIndex);
|
||||
if (amped && value.Length == 0)
|
||||
rec.MarkParsed(statIndex);
|
||||
else
|
||||
TryParse(result, ref rec, value, statIndex);
|
||||
}
|
||||
else if (rec.WasParsed(statIndex))
|
||||
{
|
||||
rec.MarkDirty(); // duplicate stat
|
||||
}
|
||||
}
|
||||
|
||||
rec.FinishParse(Names.Length);
|
||||
return rec;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a raw stat string.
|
||||
/// </summary>
|
||||
/// <param name="message">Input string</param>
|
||||
/// <param name="result">Output storage</param>
|
||||
/// <param name="separator">Separator character</param>
|
||||
public static StatParseResult TryParseRaw(ReadOnlySpan<char> message, Span<int> result, char separator)
|
||||
{
|
||||
var rec = new StatParseResult();
|
||||
|
||||
// Expect the message to contain all entries of `result` separated by the separator and an arbitrary amount of spaces permitted.
|
||||
// The message is split by the separator, and each part is trimmed of whitespace.
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
var index = message.IndexOf(separator);
|
||||
ReadOnlySpan<char> value;
|
||||
if (index != -1)
|
||||
{
|
||||
value = message[..index].TrimEnd();
|
||||
message = message[(index + 1)..].TrimStart();
|
||||
}
|
||||
else // no further iterations to be done
|
||||
{
|
||||
value = message;
|
||||
message = default;
|
||||
}
|
||||
|
||||
if (value.Length == 0)
|
||||
{
|
||||
rec.MarkDirty(); // Something is wrong with the message, as we have an empty stat.
|
||||
continue; // Maybe it's a duplicate separator; keep parsing and hope that the required amount are parsed.
|
||||
}
|
||||
|
||||
var amped = TryPeekAmp(ref value, ref rec, i);
|
||||
if (amped && value.Length == 0)
|
||||
rec.MarkParsed(index);
|
||||
else
|
||||
TryParse(result, ref rec, value, i);
|
||||
}
|
||||
|
||||
if (!message.IsWhiteSpace()) // shouldn't be anything left in the message to parse
|
||||
rec.MarkDirty();
|
||||
rec.FinishParseOnly(result.Length);
|
||||
return rec;
|
||||
}
|
||||
|
||||
private static void TryParse(Span<int> result, ref StatParseResult rec, ReadOnlySpan<char> value, int statIndex)
|
||||
{
|
||||
if (!int.TryParse(value, out var stat) || stat < 0)
|
||||
{
|
||||
rec.MarkDirty();
|
||||
return;
|
||||
}
|
||||
result[statIndex] = stat;
|
||||
rec.MarkParsed(statIndex);
|
||||
}
|
||||
|
||||
private static bool TryPeekAmp(ref ReadOnlySpan<char> value, ref StatParseResult rec, int statIndex)
|
||||
{
|
||||
var last = value[^1];
|
||||
if (last == '+')
|
||||
{
|
||||
rec.Plus = (sbyte)statIndex;
|
||||
value = value[..^1].TrimEnd();
|
||||
return true;
|
||||
}
|
||||
if (last == '-')
|
||||
{
|
||||
rec.Minus = (sbyte)statIndex;
|
||||
value = value[..^1].TrimEnd();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Style to display stat names.
|
||||
/// </summary>
|
||||
public enum StatDisplayStyle : sbyte
|
||||
{
|
||||
Custom = -1,
|
||||
|
||||
/// <summary>
|
||||
/// Stat names are displayed in abbreviated (2-3 characters) localized text.
|
||||
/// </summary>
|
||||
Abbreviated,
|
||||
|
||||
/// <summary>
|
||||
/// Stat names are displayed in full localized text.
|
||||
/// </summary>
|
||||
Full,
|
||||
|
||||
/// <summary>
|
||||
/// Stat names are displayed as a single character.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the typical format used by the Japanese community; HABCDS.
|
||||
/// </remarks>
|
||||
HABCDS,
|
||||
|
||||
/// <summary>
|
||||
/// Stat names are displayed without localization; X/X/X/X/X/X
|
||||
/// </summary>
|
||||
Raw,
|
||||
|
||||
/// <summary>
|
||||
/// Stat names are displayed without localization; XX/XX/XX/XX/XX/XX
|
||||
/// </summary>
|
||||
Raw00,
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Value result object of parsing a stat string.
|
||||
/// </summary>
|
||||
public record struct StatParseResult()
|
||||
{
|
||||
private const uint MaxStatCount = 6; // Number of stats in the game
|
||||
public const sbyte NoStatAmp = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Count of parsed stats.
|
||||
/// </summary>
|
||||
public byte CountParsed { get; private set; } = 0; // could potentially make this a computed value (popcnt), but it's not worth it
|
||||
|
||||
/// <summary>
|
||||
/// Bitflag indexes of parsed stats, indexed in visual order.
|
||||
/// </summary>
|
||||
public byte IndexesParsed { get; private set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Stat index of increased stat, indexed in visual order.
|
||||
/// </summary>
|
||||
public sbyte Plus { get; set; } = NoStatAmp;
|
||||
|
||||
/// <summary>
|
||||
/// Stat index of decreased stat, indexed in visual order.
|
||||
/// </summary>
|
||||
public sbyte Minus { get; set; } = NoStatAmp;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the parsing was clean (no un-parsed text).
|
||||
/// </summary>
|
||||
public bool IsParseClean { get; private set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if all stat indexes available were parsed.
|
||||
/// </summary>
|
||||
public bool IsParsedAllStats { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Marks the stat index as parsed, and updates the count of parsed stats.
|
||||
/// </summary>
|
||||
/// <param name="statIndex">Visual index of the stat to mark as parsed.</param>
|
||||
/// <returns>True if the stat had not been parsed before, false if it was already parsed.</returns>
|
||||
public bool MarkParsed(int statIndex)
|
||||
{
|
||||
// Check if the stat index is valid (0-5)
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)statIndex, MaxStatCount);
|
||||
if (WasParsed(statIndex))
|
||||
return false;
|
||||
// Mark the stat index as parsed
|
||||
IndexesParsed |= (byte)(1 << statIndex);
|
||||
++CountParsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the stat index was parsed.
|
||||
/// </summary>
|
||||
/// <param name="statIndex">Visual index of the stat to check.</param>
|
||||
/// <returns>True if the stat was parsed, false otherwise.</returns>
|
||||
public readonly bool WasParsed(int statIndex)
|
||||
{
|
||||
// Check if the stat index is valid (0-5)
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)statIndex, MaxStatCount);
|
||||
return (IndexesParsed & (1 << statIndex)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the parsing as finished, and updates the internal state to indicate if all stats were parsed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used when not all stats are required to be parsed.
|
||||
/// </remarks>
|
||||
/// <param name="expect"></param>
|
||||
public void FinishParse(int expect)
|
||||
{
|
||||
if (CountParsed == 0 && !HasAmps)
|
||||
MarkDirty();
|
||||
IsParsedAllStats = CountParsed == expect || IsParseClean;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the parsing as finished, and updates the internal state to indicate if all stats were parsed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used when a specific number of stats is expected.
|
||||
/// </remarks>
|
||||
/// <param name="expect"></param>
|
||||
public void FinishParseOnly(int expect) => IsParsedAllStats = CountParsed == expect;
|
||||
|
||||
/// <summary>
|
||||
/// Marks the parsing as dirty, indicating that the string was not a clean input string (user modified or the syntax doesn't match the spec).
|
||||
/// </summary>
|
||||
public void MarkDirty() => IsParseClean = false;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if any stat has any amplified (+/-) requested, indicative of nature.
|
||||
/// </summary>
|
||||
public readonly bool HasAmps => Plus != NoStatAmp || Minus != NoStatAmp;
|
||||
|
||||
/// <summary>
|
||||
/// Reorders the speed stat to be in the middle of the stats.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Speed is visually represented as the last stat in the list, but it is actually the 3rd stat stored.
|
||||
/// </remarks>
|
||||
public void TreatAmpsAsSpeedNotLast()
|
||||
{
|
||||
Plus = GetSpeedMiddleIndex(Plus);
|
||||
Minus = GetSpeedMiddleIndex(Minus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts stat indexes from visual to stored, and ignoring HP's index.
|
||||
/// </summary>
|
||||
/// <param name="amp">Visual index of the stat to get the adjusted value for.</param>
|
||||
/// <returns>Stored index of the stat.</returns>
|
||||
private static sbyte GetSpeedMiddleIndex(sbyte amp) => amp switch
|
||||
{
|
||||
// 0 => NoStatAmp -- handle via default case
|
||||
1 => 0, // Atk
|
||||
2 => 1, // Def
|
||||
3 => 3, // SpA
|
||||
4 => 4, // SpD
|
||||
5 => 2, // Spe
|
||||
_ => NoStatAmp,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,535 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
|
||||
using static PKHeX.Core.BatchEditingUtil;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Base logic for editing entities with user provided <see cref="StringInstruction"/> list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Caches reflection results for the provided types, and provides utility methods for fetching properties and applying instructions.
|
||||
/// </remarks>
|
||||
public abstract class BatchEditingBase<TObject, TMeta> : IBatchEditor<TObject> where TObject : notnull
|
||||
{
|
||||
private readonly Type[] _types;
|
||||
private readonly string[] _customProperties;
|
||||
private readonly Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] _props;
|
||||
private readonly Lazy<string[][]> _properties;
|
||||
|
||||
protected BatchEditingBase(Type[] types, string[] customProperties, int expectedMax)
|
||||
{
|
||||
_types = types;
|
||||
_customProperties = customProperties;
|
||||
_props = GetPropertyDictionaries(types, expectedMax);
|
||||
_properties = new Lazy<string[][]>(() => GetPropArray(_props, customProperties));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Property names, indexed by <see cref="Types"/>.
|
||||
/// </summary>
|
||||
public string[][] Properties => _properties.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of supported entity types.
|
||||
/// </summary>
|
||||
public IReadOnlyList<Type> Types => _types;
|
||||
|
||||
protected abstract TMeta CreateMeta(TObject entity);
|
||||
|
||||
protected abstract bool ShouldModify(TObject entity);
|
||||
|
||||
protected abstract bool TryHandleSetOperation(StringInstruction cmd, TMeta info, TObject entity, out ModifyResult result);
|
||||
|
||||
protected abstract bool TryHandleFilter(StringInstruction cmd, TMeta info, TObject entity, out bool isMatch);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to fetch the entity property from the cache of available properties.
|
||||
/// </summary>
|
||||
public bool TryGetHasProperty(TObject entity, ReadOnlySpan<char> name, [NotNullWhen(true)] out PropertyInfo? pi)
|
||||
=> TryGetHasProperty(entity.GetType(), name, out pi);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to fetch the entity property from the cache of available properties.
|
||||
/// </summary>
|
||||
public bool TryGetHasProperty(Type type, ReadOnlySpan<char> name, [NotNullWhen(true)] out PropertyInfo? pi)
|
||||
{
|
||||
var index = _types.IndexOf(type);
|
||||
if (index < 0)
|
||||
{
|
||||
pi = null;
|
||||
return false;
|
||||
}
|
||||
var localProps = _props[index];
|
||||
return localProps.TryGetValue(name, out pi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of entity types that implement the requested property.
|
||||
/// </summary>
|
||||
public IEnumerable<string> GetTypesImplementing(string property)
|
||||
{
|
||||
for (int i = 0; i < _types.Length; i++)
|
||||
{
|
||||
var type = _types[i];
|
||||
var localProps = _props[i];
|
||||
if (!localProps.TryGetValue(property, out var pi))
|
||||
continue;
|
||||
yield return $"{type.Name}: {pi.PropertyType.Name}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the entity property using the saved cache of properties.
|
||||
/// </summary>
|
||||
public bool TryGetPropertyType(string propertyName, [NotNullWhen(true)] out string? result, int typeIndex = 0)
|
||||
{
|
||||
if (_customProperties.Contains(propertyName))
|
||||
{
|
||||
result = "Custom";
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
if (typeIndex == 0)
|
||||
{
|
||||
foreach (var p in _props)
|
||||
{
|
||||
if (!p.TryGetValue(propertyName, out var pi))
|
||||
continue;
|
||||
result = pi.PropertyType.Name;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = typeIndex - 1;
|
||||
if ((uint)index >= _props.Length)
|
||||
index = 0;
|
||||
var pr = _props[index];
|
||||
if (!pr.TryGetValue(propertyName, out var info))
|
||||
return false;
|
||||
result = info.PropertyType.Name;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the entity is filtered by the provided filters.
|
||||
/// </summary>
|
||||
public bool IsFilterMatch(IEnumerable<StringInstruction> filters, TObject entity)
|
||||
{
|
||||
var info = CreateMeta(entity);
|
||||
var localProps = GetProps(entity);
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
if (!IsFilterMatch(filter, info, entity, localProps))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to modify the entity.
|
||||
/// </summary>
|
||||
public bool TryModifyIsSuccess(TObject entity, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<TObject, bool>? modifier = null)
|
||||
=> TryModify(entity, filters, modifications, modifier) is ModifyResult.Modified;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to modify the entity using instructions and a custom modifier delegate.
|
||||
/// </summary>
|
||||
public ModifyResult TryModify(TObject entity, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<TObject, bool>? modifier = null)
|
||||
{
|
||||
if (!ShouldModify(entity))
|
||||
return ModifyResult.Skipped;
|
||||
|
||||
var info = CreateMeta(entity);
|
||||
var localProps = GetProps(entity);
|
||||
|
||||
foreach (var cmd in filters)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsFilterMatch(cmd, info, entity, localProps))
|
||||
return ModifyResult.Filtered;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
return ModifyResult.Error;
|
||||
}
|
||||
}
|
||||
|
||||
var error = false;
|
||||
var result = ModifyResult.Skipped;
|
||||
|
||||
if (modifier is { } func)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!func(entity))
|
||||
return ModifyResult.Skipped;
|
||||
result = ModifyResult.Modified;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
return ModifyResult.Error;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var cmd in modifications)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tmp = SetProperty(cmd, entity, info, localProps);
|
||||
if (tmp == ModifyResult.Error)
|
||||
error = true;
|
||||
else if (tmp != ModifyResult.Skipped)
|
||||
result = tmp;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
if (error)
|
||||
result |= ModifyResult.Error;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] GetPropertyDictionaries(ReadOnlySpan<Type> types, int expectedMax)
|
||||
{
|
||||
var result = new Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[types.Length];
|
||||
for (int i = 0; i < types.Length; i++)
|
||||
result[i] = GetPropertyDictionary(types[i], ReflectUtil.GetAllPropertyInfoPublic, expectedMax).GetAlternateLookup<ReadOnlySpan<char>>();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Dictionary<string, PropertyInfo> GetPropertyDictionary(Type type, Func<Type, IEnumerable<PropertyInfo>> selector, int expectedMax)
|
||||
{
|
||||
var dict = new Dictionary<string, PropertyInfo>(expectedMax);
|
||||
var localProps = selector(type);
|
||||
foreach (var p in localProps)
|
||||
dict.TryAdd(p.Name, p);
|
||||
return dict;
|
||||
}
|
||||
|
||||
private static string[][] GetPropArray<T>(Dictionary<string, T>.AlternateLookup<ReadOnlySpan<char>>[] types, ReadOnlySpan<string> extra)
|
||||
{
|
||||
var result = new string[types.Length + 2][];
|
||||
var p = result.AsSpan(1, types.Length);
|
||||
|
||||
for (int i = 0; i < p.Length; i++)
|
||||
{
|
||||
var type = types[i].Dictionary;
|
||||
string[] combine = [..type.Keys, ..extra];
|
||||
combine.Sort();
|
||||
p[i] = combine;
|
||||
}
|
||||
|
||||
var first = p[0];
|
||||
var any = new HashSet<string>(first);
|
||||
var all = new HashSet<string>(first);
|
||||
foreach (var set in p[1..])
|
||||
{
|
||||
any.UnionWith(set);
|
||||
all.IntersectWith(set);
|
||||
}
|
||||
|
||||
var arrAny = any.ToArray();
|
||||
arrAny.Sort();
|
||||
result[0] = arrAny;
|
||||
|
||||
var arrAll = all.ToArray();
|
||||
arrAll.Sort();
|
||||
result[^1] = arrAll;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> GetProps(TObject entity)
|
||||
{
|
||||
var type = entity.GetType();
|
||||
var typeIndex = _types.IndexOf(type);
|
||||
return _props[typeIndex];
|
||||
}
|
||||
|
||||
private bool IsFilterMatch(StringInstruction cmd, TMeta info, TObject entity, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> localProps)
|
||||
{
|
||||
if (TryHandleFilter(cmd, info, entity, out var isMatch))
|
||||
return isMatch;
|
||||
return IsPropertyFiltered(cmd, entity, localProps);
|
||||
}
|
||||
|
||||
private static bool IsPropertyFiltered(StringInstruction cmd, TObject entity, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> localProps)
|
||||
{
|
||||
if (!localProps.TryGetValue(cmd.PropertyName, out var pi))
|
||||
return false;
|
||||
if (!pi.CanRead)
|
||||
return false;
|
||||
|
||||
var val = cmd.PropertyValue;
|
||||
if (val.StartsWith(PointerToken) && localProps.TryGetValue(val.AsSpan(1), out var opi))
|
||||
{
|
||||
var result = opi.GetValue(entity) ?? throw new NullReferenceException();
|
||||
return cmd.Comparer.IsCompareOperator(pi.CompareTo(entity, result));
|
||||
}
|
||||
return cmd.Comparer.IsCompareOperator(pi.CompareTo(entity, val));
|
||||
}
|
||||
|
||||
private ModifyResult SetProperty(StringInstruction cmd, TObject entity, TMeta info, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> localProps)
|
||||
{
|
||||
if (cmd.Operation == InstructionOperation.Set && TryHandleSetOperation(cmd, info, entity, out var result))
|
||||
return result;
|
||||
|
||||
if (!localProps.TryGetValue(cmd.PropertyName, out var pi))
|
||||
return ModifyResult.Error;
|
||||
|
||||
if (!pi.CanWrite)
|
||||
return ModifyResult.Error;
|
||||
|
||||
if (cmd.Operation != InstructionOperation.Set)
|
||||
return ApplyNumericOperation(entity, cmd, pi, localProps);
|
||||
|
||||
if (!TryResolveOperandValue(cmd, entity, localProps, out var value))
|
||||
return ModifyResult.Error;
|
||||
|
||||
ReflectUtil.SetValue(pi, entity, value);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
private static ModifyResult ApplyNumericOperation(TObject entity, StringInstruction cmd, PropertyInfo pi, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> localProps)
|
||||
{
|
||||
if (!pi.CanRead)
|
||||
return ModifyResult.Error;
|
||||
|
||||
if (!TryGetNumericType(pi.PropertyType, out var numericType))
|
||||
return ModifyResult.Error;
|
||||
|
||||
var currentValue = pi.GetValue(entity);
|
||||
if (currentValue is null)
|
||||
return ModifyResult.Error;
|
||||
|
||||
if (!TryResolveOperandValue(cmd, entity, localProps, out var operandValue))
|
||||
return ModifyResult.Error;
|
||||
|
||||
if (!TryApplyNumericOperation(numericType, cmd.Operation, currentValue, operandValue, out var value))
|
||||
return ModifyResult.Error;
|
||||
|
||||
ReflectUtil.SetValue(pi, entity, value);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
private static bool TryResolveOperandValue(StringInstruction cmd, TObject entity, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> localProps, [NotNullWhen(true)] out object? value)
|
||||
{
|
||||
if (cmd.Random)
|
||||
{
|
||||
value = cmd.RandomValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
var propertyValue = cmd.PropertyValue;
|
||||
if (propertyValue.StartsWith(PointerToken) && localProps.TryGetValue(propertyValue.AsSpan(1), out var opi))
|
||||
{
|
||||
value = opi.GetValue(entity);
|
||||
return value is not null;
|
||||
}
|
||||
|
||||
value = propertyValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryGetNumericType(Type type, out Type numericType)
|
||||
{
|
||||
numericType = Nullable.GetUnderlyingType(type) ?? type;
|
||||
// bool isNullable = type != numericType;
|
||||
return numericType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(INumber<>));
|
||||
}
|
||||
|
||||
private static bool TryApplyNumericOperation(Type numericType, InstructionOperation operation, object currentValue, object operandValue, [NotNullWhen(true)] out object? result)
|
||||
{
|
||||
result = null;
|
||||
if (numericType == typeof(byte))
|
||||
return ApplyBinaryInteger<byte>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(sbyte))
|
||||
return ApplyBinaryInteger<sbyte>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(short))
|
||||
return ApplyBinaryInteger<short>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(ushort))
|
||||
return ApplyBinaryInteger<ushort>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(int))
|
||||
return ApplyBinaryInteger<int>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(uint))
|
||||
return ApplyBinaryInteger<uint>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(long))
|
||||
return ApplyBinaryInteger<long>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(ulong))
|
||||
return ApplyBinaryInteger<ulong>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(nint))
|
||||
return ApplyBinaryInteger<nint>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(nuint))
|
||||
return ApplyBinaryInteger<nuint>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(BigInteger))
|
||||
return ApplyBinaryInteger<BigInteger>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(float))
|
||||
return ApplyNumeric<float>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(double))
|
||||
return ApplyNumeric<double>(currentValue, operandValue, operation, out result);
|
||||
if (numericType == typeof(decimal))
|
||||
return ApplyNumeric<decimal>(currentValue, operandValue, operation, out result);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ApplyNumeric<T>(object currentValue, object operandValue, InstructionOperation operation, [NotNullWhen(true)] out object? result)
|
||||
where T : INumber<T>
|
||||
{
|
||||
if (operation.IsBitwise)
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var success = TryApplyNumericOperationCore<T>(operation, currentValue, operandValue, out var typed);
|
||||
result = typed;
|
||||
return success;
|
||||
}
|
||||
|
||||
private static bool ApplyBinaryInteger<T>(object currentValue, object operandValue, InstructionOperation operation, [NotNullWhen(true)] out object? result)
|
||||
where T : IBinaryInteger<T>
|
||||
{
|
||||
var success = operation.IsBitwise
|
||||
? TryApplyBinaryIntegerOperationCore<T>(operation, currentValue, operandValue, out var typed)
|
||||
: TryApplyNumericOperationCore(operation, currentValue, operandValue, out typed);
|
||||
result = typed;
|
||||
return success;
|
||||
}
|
||||
|
||||
private static bool TryApplyNumericOperationCore<T>(InstructionOperation operation, object currentValue, object operandValue, [NotNullWhen(true)] out T? result)
|
||||
where T : INumber<T>
|
||||
{
|
||||
if (!TryConvertNumeric<T>(currentValue, out var left) || !TryConvertNumeric<T>(operandValue, out var right))
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryApplyNumericOperationCore(operation, left, right, out result);
|
||||
}
|
||||
|
||||
private static bool TryApplyNumericOperationCore<T>(InstructionOperation operation, T left, T right, [NotNullWhen(true)] out T? result)
|
||||
where T : INumber<T>
|
||||
{
|
||||
try
|
||||
{
|
||||
result = operation switch
|
||||
{
|
||||
InstructionOperation.Add => left + right,
|
||||
InstructionOperation.Subtract => left - right,
|
||||
InstructionOperation.Multiply => left * right,
|
||||
InstructionOperation.Divide => left / right,
|
||||
InstructionOperation.Modulo => left % right,
|
||||
_ => right,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
catch (DivideByZeroException)
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryApplyBinaryIntegerOperationCore<T>(InstructionOperation operation, object currentValue, object operandValue, [NotNullWhen(true)] out T? result)
|
||||
where T : IBinaryInteger<T>
|
||||
{
|
||||
if (!TryConvertNumeric<T>(currentValue, out var left) || !TryConvertNumeric<T>(operandValue, out var right))
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryApplyBinaryIntegerOperationCore(operation, left, right, out result);
|
||||
}
|
||||
|
||||
private static bool TryApplyBinaryIntegerOperationCore<T>(InstructionOperation operation, T left, T right, [NotNullWhen(true)] out T? result)
|
||||
where T : IBinaryInteger<T>
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (operation)
|
||||
{
|
||||
case InstructionOperation.BitwiseAnd:
|
||||
result = left & right;
|
||||
return true;
|
||||
case InstructionOperation.BitwiseOr:
|
||||
result = left | right;
|
||||
return true;
|
||||
case InstructionOperation.BitwiseXor:
|
||||
result = left ^ right;
|
||||
return true;
|
||||
case InstructionOperation.BitwiseShiftLeft:
|
||||
result = left << int.CreateChecked(right);
|
||||
return true;
|
||||
case InstructionOperation.BitwiseShiftRight:
|
||||
result = left >> int.CreateChecked(right);
|
||||
return true;
|
||||
default:
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryConvertNumeric<T>(object value, [NotNullWhen(true)] out T? result) where T : INumber<T>
|
||||
{
|
||||
if (value is T typed)
|
||||
{
|
||||
result = typed;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value is string text)
|
||||
{
|
||||
if (T.TryParse(text, CultureInfo.InvariantCulture, out var parsed))
|
||||
{
|
||||
result = parsed;
|
||||
return true;
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value is IConvertible)
|
||||
{
|
||||
try
|
||||
{
|
||||
var converted = Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture);
|
||||
if (converted is T convertedValue)
|
||||
{
|
||||
result = convertedValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public static class BatchEditingUtil
|
||||
{
|
||||
public const string PROP_TYPENAME = "ObjectType";
|
||||
public const char PointerToken = '*';
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the object is filtered by the provided <see cref="filters"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not use cached reflection; less performant than a cached <see cref="BatchEditingBase{TObject,TMeta}"/> implementation.
|
||||
/// </remarks>
|
||||
/// <param name="filters">Filters which must be satisfied.</param>
|
||||
/// <param name="obj">Object to check.</param>
|
||||
/// <returns>True if <see cref="obj"/> matches all filters.</returns>
|
||||
public static bool IsFilterMatch<T>(IEnumerable<StringInstruction> filters, T obj) where T : notnull
|
||||
{
|
||||
foreach (var cmd in filters)
|
||||
{
|
||||
var name = cmd.PropertyName;
|
||||
var value = cmd.PropertyValue;
|
||||
if (name is PROP_TYPENAME)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
var typeName = type.Name;
|
||||
if (!cmd.Comparer.IsCompareEquivalence(value == typeName))
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ReflectUtil.HasProperty(obj, name, out var pi))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
if (cmd.Comparer.IsCompareOperator(pi.CompareTo(obj, value)))
|
||||
continue;
|
||||
}
|
||||
// User provided inputs can mismatch the type's required value format, and fail to be compared.
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine($"Unable to compare {name} to {value}.");
|
||||
Debug.WriteLine(e.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides batch editing helpers for an entity type.
|
||||
/// </summary>
|
||||
public interface IBatchEditor<TObject> where TObject : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the list of supported entity types.
|
||||
/// </summary>
|
||||
IReadOnlyList<Type> Types { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property names, indexed by <see cref="Types"/>.
|
||||
/// </summary>
|
||||
string[][] Properties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tries to fetch the entity property from the cache of available properties.
|
||||
/// </summary>
|
||||
bool TryGetHasProperty(TObject entity, ReadOnlySpan<char> name, [NotNullWhen(true)] out PropertyInfo? pi);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to fetch the entity property from the cache of available properties.
|
||||
/// </summary>
|
||||
bool TryGetHasProperty(Type type, ReadOnlySpan<char> name, [NotNullWhen(true)] out PropertyInfo? pi);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of entity types that implement the requested property.
|
||||
/// </summary>
|
||||
IEnumerable<string> GetTypesImplementing(string property);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the entity property using the saved cache of properties.
|
||||
/// </summary>
|
||||
bool TryGetPropertyType(string propertyName, [NotNullWhen(true)] out string? result, int typeIndex = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the entity is filtered by the provided filters.
|
||||
/// </summary>
|
||||
bool IsFilterMatch(IEnumerable<StringInstruction> filters, TObject entity);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to modify the entity.
|
||||
/// </summary>
|
||||
bool TryModifyIsSuccess(TObject entity, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<TObject, bool>? modifier = null);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to modify the entity using instructions and a custom modifier delegate.
|
||||
/// </summary>
|
||||
ModifyResult TryModify(TObject entity, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<TObject, bool>? modifier = null);
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
using System;
|
||||
using static PKHeX.Core.InstructionComparer;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Value comparison type
|
||||
/// </summary>
|
||||
public enum InstructionComparer : byte
|
||||
{
|
||||
None,
|
||||
IsEqual,
|
||||
IsNotEqual,
|
||||
IsGreaterThan,
|
||||
IsGreaterThanOrEqual,
|
||||
IsLessThan,
|
||||
IsLessThanOrEqual,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="InstructionComparer"/>
|
||||
/// </summary>
|
||||
public static class InstructionComparerExtensions
|
||||
{
|
||||
extension(InstructionComparer comparer)
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates if the <see cref="comparer"/> is supported by the logic.
|
||||
/// </summary>
|
||||
/// <returns>True if supported, false if unsupported.</returns>
|
||||
public bool IsSupported => comparer switch
|
||||
{
|
||||
IsEqual => true,
|
||||
IsNotEqual => true,
|
||||
IsGreaterThan => true,
|
||||
IsGreaterThanOrEqual => true,
|
||||
IsLessThan => true,
|
||||
IsLessThanOrEqual => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the compare operator is satisfied by a boolean comparison result.
|
||||
/// </summary>
|
||||
/// <param name="compareResult">Result from Equals comparison</param>
|
||||
/// <returns>True if satisfied</returns>
|
||||
/// <remarks>Only use this method if the comparison is boolean only. Use the <see cref="IsCompareOperator"/> otherwise.</remarks>
|
||||
public bool IsCompareEquivalence(bool compareResult) => comparer switch
|
||||
{
|
||||
IsEqual => compareResult,
|
||||
IsNotEqual => !compareResult,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the compare operator is satisfied by the <see cref="IComparable{T}.CompareTo"/> result.
|
||||
/// </summary>
|
||||
/// <param name="compareResult">Result from CompareTo</param>
|
||||
/// <returns>True if satisfied</returns>
|
||||
public bool IsCompareOperator(int compareResult) => comparer switch
|
||||
{
|
||||
IsEqual => compareResult is 0,
|
||||
IsNotEqual => compareResult is not 0,
|
||||
IsGreaterThan => compareResult > 0,
|
||||
IsGreaterThanOrEqual => compareResult >= 0,
|
||||
IsLessThan => compareResult < 0,
|
||||
IsLessThanOrEqual => compareResult <= 0,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
using static PKHeX.Core.InstructionOperation;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Operation type for applying a modification.
|
||||
/// </summary>
|
||||
public enum InstructionOperation : byte
|
||||
{
|
||||
Set,
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Modulo,
|
||||
BitwiseAnd,
|
||||
BitwiseOr,
|
||||
BitwiseXor,
|
||||
BitwiseShiftRight,
|
||||
BitwiseShiftLeft,
|
||||
}
|
||||
|
||||
public static class InstructionOperationExtensions
|
||||
{
|
||||
extension(InstructionOperation operation)
|
||||
{
|
||||
public bool IsBitwise => operation switch
|
||||
{
|
||||
BitwiseAnd => true,
|
||||
BitwiseOr => true,
|
||||
BitwiseXor => true,
|
||||
BitwiseShiftRight => true,
|
||||
BitwiseShiftLeft => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Batch Editor Modification result for an individual processing operation.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ModifyResult
|
||||
{
|
||||
/// <summary>
|
||||
/// No modifications were performed as a filter excluded it.
|
||||
/// </summary>
|
||||
Filtered,
|
||||
|
||||
/// <summary>
|
||||
/// Not a suitable candidate for modification.
|
||||
/// </summary>
|
||||
Skipped,
|
||||
|
||||
/// <summary>
|
||||
/// One or more modifications was successfully applied.
|
||||
/// </summary>
|
||||
Modified,
|
||||
|
||||
/// <summary>
|
||||
/// An error was occurred while attempting modifications.
|
||||
/// </summary>
|
||||
Error = 0x80,
|
||||
}
|
||||
|
|
@ -1,380 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using static PKHeX.Core.InstructionComparer;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Batch Editing instruction
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Can be a filter (skip), or a modification instruction (modify)
|
||||
/// </remarks>
|
||||
/// <see cref="FilterNotEqual"/>
|
||||
/// <see cref="FilterEqual"/>
|
||||
/// <see cref="Apply"/>
|
||||
/// <param name="PropertyName">Property to modify.</param>
|
||||
/// <param name="PropertyValue">Value to set to the property.</param>
|
||||
/// <param name="Comparer">Filter Comparison Type</param>
|
||||
public sealed record StringInstruction(string PropertyName, string PropertyValue, InstructionComparer Comparer, InstructionOperation Operation = InstructionOperation.Set)
|
||||
{
|
||||
public string PropertyValue { get; private set; } = PropertyValue;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PropertyValue"/> to the index of the value in the input <see cref="arr"/>, if it exists.
|
||||
/// </summary>
|
||||
/// <param name="arr">List of values to search for the <see cref="PropertyValue"/>.</param>
|
||||
/// <returns>True if the value was found and set, false otherwise.</returns>
|
||||
public bool SetScreenedValue(ReadOnlySpan<string> arr)
|
||||
{
|
||||
int index = arr.IndexOf(PropertyValue);
|
||||
if ((uint)index >= arr.Length)
|
||||
return false;
|
||||
PropertyValue = index.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Valid prefixes that are recognized for <see cref="InstructionComparer"/> value comparison types.
|
||||
/// </summary>
|
||||
public static ReadOnlySpan<char> Prefixes =>
|
||||
[
|
||||
Apply,
|
||||
FilterEqual, FilterNotEqual, FilterGreaterThan, FilterGreaterThanOrEqual, FilterLessThan, FilterLessThanOrEqual,
|
||||
ApplyAdd, ApplySubtract, ApplyMultiply, ApplyDivide, ApplyModulo,
|
||||
ApplyBitwiseAnd, ApplyBitwiseOr, ApplyBitwiseXor, ApplyBitwiseShiftRight, ApplyBitwiseShiftLeft,
|
||||
];
|
||||
|
||||
public static bool IsFilterInstruction(char c) => c switch
|
||||
{
|
||||
FilterEqual => true,
|
||||
FilterNotEqual => true,
|
||||
FilterGreaterThan => true,
|
||||
FilterLessThan => true,
|
||||
FilterGreaterThanOrEqual => true,
|
||||
FilterLessThanOrEqual => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool IsMutationInstruction(char c) => !IsFilterInstruction(c);
|
||||
|
||||
private const char Apply = '.';
|
||||
private const char ApplyAdd = '+';
|
||||
private const char ApplySubtract = '-';
|
||||
private const char ApplyMultiply = '*';
|
||||
private const char ApplyDivide = '/';
|
||||
private const char ApplyModulo = '%';
|
||||
private const char ApplyBitwiseAnd = '&';
|
||||
private const char ApplyBitwiseOr = '|';
|
||||
private const char ApplyBitwiseXor = '^';
|
||||
private const char ApplyBitwiseShiftRight = '»';
|
||||
private const char ApplyBitwiseShiftLeft = '«';
|
||||
|
||||
private const char SplitRange = ',';
|
||||
|
||||
private const char FilterEqual = '=';
|
||||
private const char FilterNotEqual = '!';
|
||||
private const char FilterGreaterThan = '>';
|
||||
private const char FilterLessThan = '<';
|
||||
private const char FilterGreaterThanOrEqual = '≥';
|
||||
private const char FilterLessThanOrEqual = '≤';
|
||||
|
||||
/// <summary>
|
||||
/// Character which divides a property and a value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example:
|
||||
/// =Species=1
|
||||
/// The second = is the split.
|
||||
/// </remarks>
|
||||
public const char SplitInstruction = '=';
|
||||
|
||||
// Extra Functionality
|
||||
private int RandomMinimum, RandomMaximum;
|
||||
|
||||
/// <summary>
|
||||
/// Apply a <see cref="RandomValue"/> instead of fixed value, based on the <see cref="RandomMinimum"/> and <see cref="RandomMaximum"/> values.
|
||||
/// </summary>
|
||||
public bool Random { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Random"/> value, based on the <see cref="RandomMinimum"/> and <see cref="RandomMaximum"/> values.
|
||||
/// </summary>
|
||||
public int RandomValue => Util.Rand.Next(RandomMinimum, RandomMaximum + 1);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the input <see cref="str"/> is a valid "random range" specification.
|
||||
/// </summary>
|
||||
public static bool IsRandomRange(ReadOnlySpan<char> str)
|
||||
{
|
||||
// Need at least one character on either side of the splitter char.
|
||||
int index = str.IndexOf(SplitRange);
|
||||
return index > 0 && index < str.Length - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a "random range" specification to the instruction.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">When the splitter is not present.</exception>
|
||||
public void SetRandomRange(ReadOnlySpan<char> str)
|
||||
{
|
||||
var index = str.IndexOf(SplitRange);
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(index);
|
||||
|
||||
var min = str[..index];
|
||||
var max = str[(index + 1)..];
|
||||
_ = int.TryParse(min, out RandomMinimum);
|
||||
_ = int.TryParse(max, out RandomMaximum);
|
||||
|
||||
if (RandomMinimum == RandomMaximum)
|
||||
{
|
||||
PropertyValue = RandomMinimum.ToString();
|
||||
Debug.WriteLine($"{PropertyName} randomization range Min/Max same?");
|
||||
}
|
||||
else
|
||||
{
|
||||
Random = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="StringInstruction"/>s from the input <see cref="text"/>.
|
||||
/// </summary>
|
||||
public static List<StringInstruction> GetFilters(ReadOnlySpan<char> text) => GetFilters(text.EnumerateLines());
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="StringInstruction"/> filters from the input <see cref="lines"/>.
|
||||
/// </summary>
|
||||
public static List<StringInstruction> GetFilters(ReadOnlySpan<string> lines)
|
||||
{
|
||||
var result = new List<StringInstruction>(lines.Length);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (TryParseFilter(line, out var entry))
|
||||
result.Add(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="StringInstruction"/> filters from the input <see cref="lines"/>.
|
||||
/// </summary>
|
||||
public static List<StringInstruction> GetFilters(SpanLineEnumerator lines)
|
||||
{
|
||||
var result = new List<StringInstruction>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (TryParseFilter(line, out var entry))
|
||||
result.Add(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="StringInstruction"/> filters from the input <see cref="lines"/>.
|
||||
/// </summary>
|
||||
public static List<StringInstruction> GetFilters(IReadOnlyList<string> lines)
|
||||
{
|
||||
var result = new List<StringInstruction>(lines.Count);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (TryParseFilter(line, out var entry))
|
||||
result.Add(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="StringInstruction"/> filters from the input <see cref="lines"/>.
|
||||
/// </summary>
|
||||
public static List<StringInstruction> GetFilters(IEnumerable<string> lines)
|
||||
{
|
||||
var result = new List<StringInstruction>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (TryParseFilter(line, out var entry))
|
||||
result.Add(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="text"/>.
|
||||
/// </summary>
|
||||
public static List<StringInstruction> GetInstructions(ReadOnlySpan<char> text) => GetInstructions(text.EnumerateLines());
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="lines"/>.
|
||||
/// </summary>
|
||||
public static List<StringInstruction> GetInstructions(ReadOnlySpan<string> lines)
|
||||
{
|
||||
var result = new List<StringInstruction>(lines.Length);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (TryParseInstruction(line, out var entry))
|
||||
result.Add(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="lines"/>.
|
||||
/// </summary>
|
||||
public static List<StringInstruction> GetInstructions(SpanLineEnumerator lines)
|
||||
{
|
||||
var result = new List<StringInstruction>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (TryParseInstruction(line, out var entry))
|
||||
result.Add(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="lines"/>.
|
||||
/// </summary>
|
||||
public static List<StringInstruction> GetInstructions(IReadOnlyList<string> lines)
|
||||
{
|
||||
var result = new List<StringInstruction>(lines.Count);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (TryParseInstruction(line, out var entry))
|
||||
result.Add(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="lines"/>.
|
||||
/// </summary>
|
||||
public static List<StringInstruction> GetInstructions(IEnumerable<string> lines)
|
||||
{
|
||||
var result = new List<StringInstruction>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (TryParseInstruction(line, out var entry))
|
||||
result.Add(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse a <see cref="StringInstruction"/> filter from the input <see cref="line"/>.
|
||||
/// </summary>
|
||||
public static bool TryParseFilter(ReadOnlySpan<char> line, [NotNullWhen(true)] out StringInstruction? entry)
|
||||
{
|
||||
entry = null;
|
||||
if (line.Length is 0)
|
||||
return false;
|
||||
var comparer = GetComparer(line[0]);
|
||||
if (!comparer.IsSupported)
|
||||
return false;
|
||||
return TryParseSplitTuple(line[1..], ref entry, comparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse a <see cref="StringInstruction"/> instruction from the input <see cref="line"/>.
|
||||
/// </summary>
|
||||
public static bool TryParseInstruction(ReadOnlySpan<char> line, [NotNullWhen(true)] out StringInstruction? entry)
|
||||
{
|
||||
entry = null;
|
||||
if (line.Length is 0 || !TryGetOperation(line[0], out var operation))
|
||||
return false;
|
||||
return TryParseSplitTuple(line[1..], ref entry, default, operation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to split a <see cref="StringInstruction"/> tuple from the input <see cref="tuple"/>.
|
||||
/// </summary>
|
||||
public static bool TryParseSplitTuple(ReadOnlySpan<char> tuple, [NotNullWhen(true)] ref StringInstruction? entry, InstructionComparer eval = default, InstructionOperation operation = InstructionOperation.Set)
|
||||
{
|
||||
if (!TryParseSplitTuple(tuple, out var name, out var value))
|
||||
return false;
|
||||
entry = new StringInstruction(name.ToString(), value.ToString(), eval, operation);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to split a <see cref="StringInstruction"/> tuple from the input <see cref="tuple"/>.
|
||||
/// </summary>
|
||||
public static bool TryParseSplitTuple(ReadOnlySpan<char> tuple, out ReadOnlySpan<char> name, out ReadOnlySpan<char> value)
|
||||
{
|
||||
name = default;
|
||||
value = default;
|
||||
var splitIndex = tuple.IndexOf(SplitInstruction);
|
||||
if (splitIndex <= 0)
|
||||
return false;
|
||||
|
||||
name = tuple[..splitIndex];
|
||||
if (name.IsWhiteSpace())
|
||||
return false;
|
||||
|
||||
value = tuple[(splitIndex + 1)..];
|
||||
var noExtra = value.IndexOf(SplitInstruction);
|
||||
return noExtra == -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="InstructionComparer"/> from the input <see cref="opCode"/>.
|
||||
/// </summary>
|
||||
public static InstructionComparer GetComparer(char opCode) => opCode switch
|
||||
{
|
||||
FilterEqual => IsEqual,
|
||||
FilterNotEqual => IsNotEqual,
|
||||
FilterGreaterThan => IsGreaterThan,
|
||||
FilterLessThan => IsLessThan,
|
||||
FilterGreaterThanOrEqual => IsGreaterThanOrEqual,
|
||||
FilterLessThanOrEqual => IsLessThanOrEqual,
|
||||
_ => None,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="InstructionOperation"/> from the input <see cref="opCode"/>.
|
||||
/// </summary>
|
||||
public static bool TryGetOperation(char opCode, out InstructionOperation operation)
|
||||
{
|
||||
switch (opCode)
|
||||
{
|
||||
case Apply:
|
||||
operation = InstructionOperation.Set;
|
||||
return true;
|
||||
case ApplyAdd:
|
||||
operation = InstructionOperation.Add;
|
||||
return true;
|
||||
case ApplySubtract:
|
||||
operation = InstructionOperation.Subtract;
|
||||
return true;
|
||||
case ApplyMultiply:
|
||||
operation = InstructionOperation.Multiply;
|
||||
return true;
|
||||
case ApplyDivide:
|
||||
operation = InstructionOperation.Divide;
|
||||
return true;
|
||||
case ApplyModulo:
|
||||
operation = InstructionOperation.Modulo;
|
||||
return true;
|
||||
case ApplyBitwiseAnd:
|
||||
operation = InstructionOperation.BitwiseAnd;
|
||||
return true;
|
||||
case ApplyBitwiseOr:
|
||||
operation = InstructionOperation.BitwiseOr;
|
||||
return true;
|
||||
case ApplyBitwiseXor:
|
||||
operation = InstructionOperation.BitwiseXor;
|
||||
return true;
|
||||
case ApplyBitwiseShiftRight:
|
||||
operation = InstructionOperation.BitwiseShiftRight;
|
||||
return true;
|
||||
case ApplyBitwiseShiftLeft:
|
||||
operation = InstructionOperation.BitwiseShiftLeft;
|
||||
return true;
|
||||
default:
|
||||
operation = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Processes input of strings into a list of valid Filters and Instructions.
|
||||
/// </summary>
|
||||
public sealed class StringInstructionSet
|
||||
{
|
||||
/// <summary>
|
||||
/// Filters to check if the object should be modified.
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<StringInstruction> Filters;
|
||||
|
||||
/// <summary>
|
||||
/// Instructions to modify the object.
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<StringInstruction> Instructions;
|
||||
|
||||
private const char SetSeparatorChar = ';';
|
||||
|
||||
public StringInstructionSet(IReadOnlyList<StringInstruction> filters, IReadOnlyList<StringInstruction> instructions)
|
||||
{
|
||||
Filters = filters;
|
||||
Instructions = instructions;
|
||||
}
|
||||
|
||||
public StringInstructionSet(ReadOnlySpan<char> text)
|
||||
{
|
||||
var set = text.EnumerateLines();
|
||||
Filters = StringInstruction.GetFilters(set);
|
||||
Instructions = StringInstruction.GetInstructions(set);
|
||||
}
|
||||
|
||||
public StringInstructionSet(SpanLineEnumerator set)
|
||||
{
|
||||
Filters = StringInstruction.GetFilters(set);
|
||||
Instructions = StringInstruction.GetInstructions(set);
|
||||
}
|
||||
|
||||
public StringInstructionSet(ReadOnlySpan<string> set)
|
||||
{
|
||||
Filters = StringInstruction.GetFilters(set);
|
||||
Instructions = StringInstruction.GetInstructions(set);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the input <see cref="text"/> is potentially formatted incorrectly.
|
||||
/// </summary>
|
||||
/// <remarks>Normally, no blank lines should be present in the input.</remarks>
|
||||
/// <returns>True if a blank line is found in the input.</returns>
|
||||
public static bool HasEmptyLine(ReadOnlySpan<char> text) => HasEmptyLine(text.EnumerateLines());
|
||||
|
||||
/// <inheritdoc cref="HasEmptyLine(ReadOnlySpan{char})"/>
|
||||
public static bool HasEmptyLine(SpanLineEnumerator lines)
|
||||
{
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.IsEmpty || line.IsWhiteSpace())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="StringInstructionSet"/>s from the input <see cref="lines"/>.
|
||||
/// </summary>
|
||||
public static StringInstructionSet[] GetBatchSets(ReadOnlySpan<string> lines)
|
||||
{
|
||||
int ctr = 0;
|
||||
int start = 0;
|
||||
while (start < lines.Length)
|
||||
{
|
||||
var slice = lines[start..];
|
||||
var count = GetInstructionSetLength(slice);
|
||||
ctr++;
|
||||
start += count + 1;
|
||||
}
|
||||
|
||||
var result = new StringInstructionSet[ctr];
|
||||
ctr = 0;
|
||||
start = 0;
|
||||
while (start < lines.Length)
|
||||
{
|
||||
var slice = lines[start..];
|
||||
var count = GetInstructionSetLength(slice);
|
||||
var set = slice[..count];
|
||||
result[ctr++] = new StringInstructionSet(set);
|
||||
start += count + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="StringInstructionSet"/>s from the input <see cref="text"/>.
|
||||
/// </summary>
|
||||
public static StringInstructionSet[] GetBatchSets(ReadOnlySpan<char> text)
|
||||
{
|
||||
int ctr = 0;
|
||||
int start = 0;
|
||||
while (start < text.Length)
|
||||
{
|
||||
var slice = text[start..];
|
||||
var count = GetInstructionSetLength(slice);
|
||||
ctr++;
|
||||
start += count + 1;
|
||||
}
|
||||
|
||||
var result = new StringInstructionSet[ctr];
|
||||
ctr = 0;
|
||||
start = 0;
|
||||
while (start < text.Length)
|
||||
{
|
||||
var slice = text[start..];
|
||||
var count = GetInstructionSetLength(slice);
|
||||
var set = slice[..count];
|
||||
result[ctr++] = new StringInstructionSet(set);
|
||||
start += count + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scans through the <see cref="text"/> to count the amount of characters to consume.
|
||||
/// </summary>
|
||||
/// <param name="text">Multi line string</param>
|
||||
/// <returns>Amount of characters comprising a set of instructions</returns>
|
||||
public static int GetInstructionSetLength(ReadOnlySpan<char> text)
|
||||
{
|
||||
int start = 0;
|
||||
while (start < text.Length)
|
||||
{
|
||||
var line = text[start..];
|
||||
if (line.Length != 0 && line[0] == SetSeparatorChar)
|
||||
return start;
|
||||
var next = line.IndexOf('\n');
|
||||
if (next == -1)
|
||||
return text.Length;
|
||||
start += next + 1;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scans through the <see cref="lines"/> to count the amount of valid lines to consume.
|
||||
/// </summary>
|
||||
/// <returns>Amount of lines comprising a set of instructions.</returns>
|
||||
public static int GetInstructionSetLength(ReadOnlySpan<string> lines)
|
||||
{
|
||||
int start = 0;
|
||||
while (start < lines.Length)
|
||||
{
|
||||
var line = lines[start++];
|
||||
if (line.StartsWith(SetSeparatorChar))
|
||||
return start;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
}
|
||||
505
PKHeX.Core/Editing/Bulk/BatchEditing.cs
Normal file
505
PKHeX.Core/Editing/Bulk/BatchEditing.cs
Normal file
|
|
@ -0,0 +1,505 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using static PKHeX.Core.MessageStrings;
|
||||
using static PKHeX.Core.BatchModifications;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for editing many <see cref="PKM"/> with user provided <see cref="StringInstruction"/> list.
|
||||
/// </summary>
|
||||
public static class BatchEditing
|
||||
{
|
||||
public static readonly Type[] Types =
|
||||
{
|
||||
typeof (PK8), typeof (PA8), typeof (PB8),
|
||||
typeof (PB7),
|
||||
typeof (PK7), typeof (PK6), typeof (PK5), typeof (PK4), typeof(BK4),
|
||||
typeof (PK3), typeof (XK3), typeof (CK3),
|
||||
typeof (PK2), typeof (SK2), typeof (PK1),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Extra properties to show in the list of selectable properties (GUI)
|
||||
/// </summary>
|
||||
public static readonly List<string> CustomProperties = new()
|
||||
{
|
||||
PROP_LEGAL, PROP_TYPENAME, PROP_RIBBONS, PROP_CONTESTSTATS, PROP_MOVEMASTERY,
|
||||
IdentifierContains, nameof(ISlotInfo.Slot), nameof(SlotInfoBox.Box),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Property names, indexed by <see cref="Types"/>.
|
||||
/// </summary>
|
||||
public static string[][] Properties => GetProperties.Value;
|
||||
|
||||
private static readonly Lazy<string[][]> GetProperties = new(() => GetPropArray(Types, CustomProperties));
|
||||
|
||||
private static readonly Dictionary<string, PropertyInfo>[] Props = GetPropertyDictionaries(Types);
|
||||
|
||||
private static Dictionary<string, PropertyInfo>[] GetPropertyDictionaries(IReadOnlyList<Type> types)
|
||||
{
|
||||
var result = new Dictionary<string, PropertyInfo>[types.Count];
|
||||
for (int i = 0; i < types.Count; i++)
|
||||
result[i] = GetPropertyDictionary(types[i], ReflectUtil.GetAllPropertyInfoPublic);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Dictionary<string, PropertyInfo> GetPropertyDictionary(Type type, Func<Type, IEnumerable<PropertyInfo>> selector)
|
||||
{
|
||||
const int expectedMax = 0x200; // currently 0x160 as of 2022
|
||||
var dict = new Dictionary<string, PropertyInfo>(expectedMax);
|
||||
var props = selector(type);
|
||||
foreach (var p in props)
|
||||
{
|
||||
if (!dict.ContainsKey(p.Name))
|
||||
dict.Add(p.Name, p);
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
internal const string CONST_RAND = "$rand";
|
||||
internal const string CONST_SHINY = "$shiny";
|
||||
internal const string CONST_SUGGEST = "$suggest";
|
||||
private const string CONST_BYTES = "$[]";
|
||||
private const char CONST_POINTER = '*';
|
||||
internal const char CONST_SPECIAL = '$';
|
||||
|
||||
internal const string PROP_LEGAL = "Legal";
|
||||
internal const string PROP_TYPENAME = "ObjectType";
|
||||
internal const string PROP_RIBBONS = "Ribbons";
|
||||
internal const string PROP_EVS = "EVs";
|
||||
internal const string PROP_CONTESTSTATS = "ContestStats";
|
||||
internal const string PROP_MOVEMASTERY = "MoveMastery";
|
||||
internal const string IdentifierContains = nameof(IdentifierContains);
|
||||
|
||||
private static string[][] GetPropArray(IReadOnlyList<Type> types, IReadOnlyList<string> extra)
|
||||
{
|
||||
var result = new string[types.Count + 2][];
|
||||
var p = result.AsSpan(1, types.Count);
|
||||
|
||||
for (int i = 0; i < p.Length; i++)
|
||||
{
|
||||
var props = ReflectUtil.GetPropertiesPublic(types[i]);
|
||||
p[i] = props.Concat(extra).OrderBy(a => a).ToArray();
|
||||
}
|
||||
|
||||
// Properties for any PKM
|
||||
// Properties shared by all PKM
|
||||
var first = p[0];
|
||||
var any = new HashSet<string>(first);
|
||||
var all = new HashSet<string>(first);
|
||||
for (int i = 1; i < p.Length; i++)
|
||||
{
|
||||
any.UnionWith(p[i]);
|
||||
all.IntersectWith(p[i]);
|
||||
}
|
||||
|
||||
result[0] = any.OrderBy(z => z).ToArray();
|
||||
result[^1] = all.OrderBy(z => z).ToArray();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to fetch the <see cref="PKM"/> property from the cache of available properties.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to check</param>
|
||||
/// <param name="name">Property Name to check</param>
|
||||
/// <param name="pi">Property Info retrieved (if any).</param>
|
||||
/// <returns>True if has property, false if does not.</returns>
|
||||
public static bool TryGetHasProperty(PKM pk, string name, [NotNullWhen(true)] out PropertyInfo? pi)
|
||||
{
|
||||
var type = pk.GetType();
|
||||
return TryGetHasProperty(type, name, out pi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to fetch the <see cref="PKM"/> property from the cache of available properties.
|
||||
/// </summary>
|
||||
/// <param name="type">Type to check</param>
|
||||
/// <param name="name">Property Name to check</param>
|
||||
/// <param name="pi">Property Info retrieved (if any).</param>
|
||||
/// <returns>True if has property, false if does not.</returns>
|
||||
public static bool TryGetHasProperty(Type type, string name, [NotNullWhen(true)] out PropertyInfo? pi)
|
||||
{
|
||||
var index = Array.IndexOf(Types, type);
|
||||
if (index < 0)
|
||||
{
|
||||
pi = null;
|
||||
return false;
|
||||
}
|
||||
var props = Props[index];
|
||||
return props.TryGetValue(name, out pi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="PKM"/> types that implement the requested <see cref="property"/>.
|
||||
/// </summary>
|
||||
public static IEnumerable<string> GetTypesImplementing(string property)
|
||||
{
|
||||
for (int i = 0; i < Types.Length; i++)
|
||||
{
|
||||
var type = Types[i];
|
||||
var props = Props[i];
|
||||
if (!props.TryGetValue(property, out var pi))
|
||||
continue;
|
||||
yield return $"{type.Name}: {pi.PropertyType.Name}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the <see cref="PKM"/> property using the saved cache of properties.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Property Name to fetch the type for</param>
|
||||
/// <param name="typeIndex">Type index (within <see cref="Types"/>. Leave empty (0) for a nonspecific format.</param>
|
||||
/// <returns>Short name of the property's type.</returns>
|
||||
public static string? GetPropertyType(string propertyName, int typeIndex = 0)
|
||||
{
|
||||
if (CustomProperties.Contains(propertyName))
|
||||
return "Custom";
|
||||
|
||||
if (typeIndex == 0) // Any
|
||||
{
|
||||
foreach (var p in Props)
|
||||
{
|
||||
if (p.TryGetValue(propertyName, out var pi))
|
||||
return pi.PropertyType.Name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
int index = typeIndex - 1 >= Props.Length ? 0 : typeIndex - 1; // All vs Specific
|
||||
var pr = Props[index];
|
||||
if (!pr.TryGetValue(propertyName, out var info))
|
||||
return null;
|
||||
return info.PropertyType.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="StringInstruction"/> list with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
|
||||
/// </summary>
|
||||
/// <param name="il">Instructions to initialize.</param>
|
||||
public static void ScreenStrings(IEnumerable<StringInstruction> il)
|
||||
{
|
||||
foreach (var i in il.Where(i => !i.PropertyValue.All(char.IsDigit)))
|
||||
{
|
||||
string pv = i.PropertyValue;
|
||||
if (pv.StartsWith(CONST_SPECIAL) && !pv.StartsWith(CONST_BYTES, StringComparison.Ordinal) && pv.Contains(','))
|
||||
i.SetRandRange(pv);
|
||||
|
||||
SetInstructionScreenedValue(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="StringInstruction"/> with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
|
||||
/// </summary>
|
||||
/// <param name="i">Instruction to initialize.</param>
|
||||
private static void SetInstructionScreenedValue(StringInstruction i)
|
||||
{
|
||||
switch (i.PropertyName)
|
||||
{
|
||||
case nameof(PKM.Species): i.SetScreenedValue(GameInfo.Strings.specieslist); return;
|
||||
case nameof(PKM.HeldItem): i.SetScreenedValue(GameInfo.Strings.itemlist); return;
|
||||
case nameof(PKM.Ability): i.SetScreenedValue(GameInfo.Strings.abilitylist); return;
|
||||
case nameof(PKM.Nature): i.SetScreenedValue(GameInfo.Strings.natures); return;
|
||||
case nameof(PKM.Ball): i.SetScreenedValue(GameInfo.Strings.balllist); return;
|
||||
|
||||
case nameof(PKM.Move1) or nameof(PKM.Move2) or nameof(PKM.Move3) or nameof(PKM.Move4):
|
||||
case nameof(PKM.RelearnMove1) or nameof(PKM.RelearnMove2) or nameof(PKM.RelearnMove3) or nameof(PKM.RelearnMove4):
|
||||
i.SetScreenedValue(GameInfo.Strings.movelist); return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the object is filtered by the provided <see cref="filters"/>.
|
||||
/// </summary>
|
||||
/// <param name="filters">Filters which must be satisfied.</param>
|
||||
/// <param name="pk">Object to check.</param>
|
||||
/// <returns>True if <see cref="pk"/> matches all filters.</returns>
|
||||
public static bool IsFilterMatch(IEnumerable<StringInstruction> filters, PKM pk) => filters.All(z => IsFilterMatch(z, pk, Props[Array.IndexOf(Types, pk.GetType())]));
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the object is filtered by the provided <see cref="filters"/>.
|
||||
/// </summary>
|
||||
/// <param name="filters">Filters which must be satisfied.</param>
|
||||
/// <param name="pk">Object to check.</param>
|
||||
/// <returns>True if <see cref="pk"/> matches all filters.</returns>
|
||||
public static bool IsFilterMatchMeta(IEnumerable<StringInstruction> filters, SlotCache pk)
|
||||
{
|
||||
foreach (var i in filters)
|
||||
{
|
||||
foreach (var filter in BatchFilters.FilterMeta)
|
||||
{
|
||||
if (!filter.IsMatch(i.PropertyName))
|
||||
continue;
|
||||
|
||||
if (!filter.IsFiltered(pk, i))
|
||||
return false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the object is filtered by the provided <see cref="filters"/>.
|
||||
/// </summary>
|
||||
/// <param name="filters">Filters which must be satisfied.</param>
|
||||
/// <param name="obj">Object to check.</param>
|
||||
/// <returns>True if <see cref="obj"/> matches all filters.</returns>
|
||||
public static bool IsFilterMatch(IEnumerable<StringInstruction> filters, object obj)
|
||||
{
|
||||
foreach (var cmd in filters)
|
||||
{
|
||||
if (cmd.PropertyName is PROP_TYPENAME)
|
||||
{
|
||||
if ((obj.GetType().Name == cmd.PropertyValue) != cmd.Evaluator)
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ReflectUtil.HasProperty(obj, cmd.PropertyName, out var pi))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
if (pi.IsValueEqual(obj, cmd.PropertyValue) == cmd.Evaluator)
|
||||
continue;
|
||||
}
|
||||
// User provided inputs can mismatch the type's required value format, and fail to be compared.
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine($"Unable to compare {cmd.PropertyName} to {cmd.PropertyValue}.");
|
||||
Debug.WriteLine(e.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to modify the <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Object to modify.</param>
|
||||
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
|
||||
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
|
||||
/// <returns>Result of the attempted modification.</returns>
|
||||
public static bool TryModify(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
|
||||
{
|
||||
var result = TryModifyPKM(pk, filters, modifications);
|
||||
return result == ModifyResult.Modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to modify the <see cref="BatchInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Command Filter</param>
|
||||
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
|
||||
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
|
||||
/// <returns>Result of the attempted modification.</returns>
|
||||
internal static ModifyResult TryModifyPKM(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
|
||||
{
|
||||
if (!pk.ChecksumValid || pk.Species == 0)
|
||||
return ModifyResult.Invalid;
|
||||
|
||||
var info = new BatchInfo(pk);
|
||||
var pi = Props[Array.IndexOf(Types, pk.GetType())];
|
||||
foreach (var cmd in filters)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsFilterMatch(cmd, info, pi))
|
||||
return ModifyResult.Filtered;
|
||||
}
|
||||
// Swallow any error because this can be malformed user input.
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(MsgBEModifyFailCompare + " " + ex.Message, cmd.PropertyName, cmd.PropertyValue);
|
||||
return ModifyResult.Error;
|
||||
}
|
||||
}
|
||||
|
||||
ModifyResult result = ModifyResult.Modified;
|
||||
foreach (var cmd in modifications)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tmp = SetPKMProperty(cmd, info, pi);
|
||||
if (tmp != ModifyResult.Modified)
|
||||
result = tmp;
|
||||
}
|
||||
// Swallow any error because this can be malformed user input.
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(MsgBEModifyFail + " " + ex.Message, cmd.PropertyName, cmd.PropertyValue);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the if the <see cref="BatchInfo"/> should be filtered due to the <see cref="StringInstruction"/> provided.
|
||||
/// </summary>
|
||||
/// <param name="cmd">Command Filter</param>
|
||||
/// <param name="info">Pokémon to check.</param>
|
||||
/// <param name="props">PropertyInfo cache (optional)</param>
|
||||
/// <returns>True if filtered, else false.</returns>
|
||||
private static ModifyResult SetPKMProperty(StringInstruction cmd, BatchInfo info, IReadOnlyDictionary<string, PropertyInfo> props)
|
||||
{
|
||||
var pk = info.Entity;
|
||||
if (cmd.PropertyValue.StartsWith(CONST_BYTES, StringComparison.Ordinal))
|
||||
return SetByteArrayProperty(pk, cmd);
|
||||
|
||||
if (cmd.PropertyValue.StartsWith(CONST_SUGGEST, StringComparison.OrdinalIgnoreCase))
|
||||
return SetSuggestedPKMProperty(cmd.PropertyName, info, cmd.PropertyValue);
|
||||
if (cmd.PropertyValue == CONST_RAND && cmd.PropertyName == nameof(PKM.Moves))
|
||||
return SetMoves(pk, info.Legality.GetMoveSet(true));
|
||||
|
||||
if (SetComplexProperty(pk, cmd))
|
||||
return ModifyResult.Modified;
|
||||
|
||||
if (!props.TryGetValue(cmd.PropertyName, out var pi))
|
||||
return ModifyResult.Error;
|
||||
|
||||
if (!pi.CanWrite)
|
||||
return ModifyResult.Error;
|
||||
|
||||
object val;
|
||||
if (cmd.Random)
|
||||
val = cmd.RandomValue;
|
||||
else if (cmd.PropertyValue.StartsWith(CONST_POINTER) && props.TryGetValue(cmd.PropertyValue[1..], out var opi))
|
||||
val = opi.GetValue(pk);
|
||||
else
|
||||
val = cmd.PropertyValue;
|
||||
|
||||
ReflectUtil.SetValue(pi, pk, val);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the <see cref="BatchInfo"/> should be filtered due to the <see cref="StringInstruction"/> provided.
|
||||
/// </summary>
|
||||
/// <param name="cmd">Command Filter</param>
|
||||
/// <param name="info">Pokémon to check.</param>
|
||||
/// <param name="props">PropertyInfo cache (optional)</param>
|
||||
/// <returns>True if filter matches, else false.</returns>
|
||||
private static bool IsFilterMatch(StringInstruction cmd, BatchInfo info, IReadOnlyDictionary<string, PropertyInfo> props)
|
||||
{
|
||||
var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
|
||||
if (match != null)
|
||||
return match.IsFiltered(info, cmd);
|
||||
return IsPropertyFiltered(cmd, info.Entity, props);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the <see cref="PKM"/> should be filtered due to the <see cref="StringInstruction"/> provided.
|
||||
/// </summary>
|
||||
/// <param name="cmd">Command Filter</param>
|
||||
/// <param name="pk">Pokémon to check.</param>
|
||||
/// <param name="props">PropertyInfo cache (optional)</param>
|
||||
/// <returns>True if filter matches, else false.</returns>
|
||||
private static bool IsFilterMatch(StringInstruction cmd, PKM pk, IReadOnlyDictionary<string, PropertyInfo> props)
|
||||
{
|
||||
var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
|
||||
if (match != null)
|
||||
return match.IsFiltered(pk, cmd);
|
||||
return IsPropertyFiltered(cmd, pk, props);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the <see cref="PKM"/> should be filtered due to the <see cref="StringInstruction"/> provided.
|
||||
/// </summary>
|
||||
/// <param name="cmd">Command Filter</param>
|
||||
/// <param name="pk">Pokémon to check.</param>
|
||||
/// <param name="props">PropertyInfo cache</param>
|
||||
/// <returns>True if filtered, else false.</returns>
|
||||
private static bool IsPropertyFiltered(StringInstruction cmd, PKM pk, IReadOnlyDictionary<string, PropertyInfo> props)
|
||||
{
|
||||
if (!props.TryGetValue(cmd.PropertyName, out var pi))
|
||||
return false;
|
||||
if (!pi.CanRead)
|
||||
return false;
|
||||
return pi.IsValueEqual(pk, cmd.PropertyValue) == cmd.Evaluator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM"/> data with a suggested value based on its <see cref="LegalityAnalysis"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">Property to modify.</param>
|
||||
/// <param name="info">Cached info storing Legal data.</param>
|
||||
/// <param name="propValue">Suggestion string which starts with <see cref="CONST_SUGGEST"/></param>
|
||||
private static ModifyResult SetSuggestedPKMProperty(string name, BatchInfo info, string propValue)
|
||||
{
|
||||
var first = BatchMods.SuggestionMods.Find(z => z.IsMatch(name, propValue, info));
|
||||
if (first != null)
|
||||
return first.Modify(name, propValue, info);
|
||||
return ModifyResult.Error;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM"/> byte array property to a specified value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="cmd">Modification</param>
|
||||
private static ModifyResult SetByteArrayProperty(PKM pk, StringInstruction cmd)
|
||||
{
|
||||
switch (cmd.PropertyName)
|
||||
{
|
||||
case nameof(PKM.Nickname_Trash): ConvertToBytes(cmd.PropertyValue).CopyTo(pk.Nickname_Trash); return ModifyResult.Modified;
|
||||
case nameof(PKM.OT_Trash): ConvertToBytes(cmd.PropertyValue).CopyTo(pk.OT_Trash); return ModifyResult.Modified;
|
||||
case nameof(PKM.HT_Trash): ConvertToBytes(cmd.PropertyValue).CopyTo(pk.HT_Trash); return ModifyResult.Modified;
|
||||
default:
|
||||
return ModifyResult.Error;
|
||||
}
|
||||
static byte[] ConvertToBytes(string str)
|
||||
{
|
||||
var arr = str[CONST_BYTES.Length..].Split(',');
|
||||
return Array.ConvertAll(arr, z => Convert.ToByte(z.Trim(), 16));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM"/> property to a non-specific smart value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="cmd">Modification</param>
|
||||
/// <returns>True if modified, false if no modifications done.</returns>
|
||||
private static bool SetComplexProperty(PKM pk, StringInstruction cmd)
|
||||
{
|
||||
if (cmd.PropertyName.StartsWith("IV", StringComparison.Ordinal) && cmd.PropertyValue == CONST_RAND)
|
||||
{
|
||||
SetRandomIVs(pk, cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
var match = BatchMods.ComplexMods.Find(z => z.IsMatch(cmd.PropertyName, cmd.PropertyValue));
|
||||
if (match == null)
|
||||
return false;
|
||||
|
||||
match.Modify(pk, cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM"/> IV(s) to a random value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="cmd">Modification</param>
|
||||
private static void SetRandomIVs(PKM pk, StringInstruction cmd)
|
||||
{
|
||||
if (cmd.PropertyName == nameof(PKM.IVs))
|
||||
{
|
||||
pk.SetRandomIVs();
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryGetHasProperty(pk, cmd.PropertyName, out var pi))
|
||||
ReflectUtil.SetValue(pi, pk, Util.Rand.Next(pk.MaxIV + 1));
|
||||
}
|
||||
}
|
||||
85
PKHeX.Core/Editing/Bulk/BatchEditor.cs
Normal file
85
PKHeX.Core/Editing/Bulk/BatchEditor.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.MessageStrings;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Carries out a batch edit and contains information summarizing the results.
|
||||
/// </summary>
|
||||
public sealed class BatchEditor
|
||||
{
|
||||
private int Modified { get; set; }
|
||||
private int Iterated { get; set; }
|
||||
private int Failed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tries to modify the <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Object to modify.</param>
|
||||
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
|
||||
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
|
||||
/// <returns>Result of the attempted modification.</returns>
|
||||
public bool Process(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
|
||||
{
|
||||
if (pk.Species == 0)
|
||||
return false;
|
||||
if (!pk.Valid)
|
||||
{
|
||||
Iterated++;
|
||||
const string reason = "Not Valid.";
|
||||
Debug.WriteLine($"{MsgBEModifyFailBlocked} {reason}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = BatchEditing.TryModifyPKM(pk, filters, modifications);
|
||||
if (result != ModifyResult.Invalid)
|
||||
Iterated++;
|
||||
if (result == ModifyResult.Error)
|
||||
Failed++;
|
||||
if (result != ModifyResult.Modified)
|
||||
return false;
|
||||
|
||||
pk.RefreshChecksum();
|
||||
Modified++;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a message indicating the overall result of all modifications performed across multiple Batch Edit jobs.
|
||||
/// </summary>
|
||||
/// <param name="sets">Collection of modifications.</param>
|
||||
/// <returns>Friendly (multi-line) string indicating the result of the batch edits.</returns>
|
||||
public string GetEditorResults(ICollection<StringInstructionSet> sets)
|
||||
{
|
||||
if (sets.Count == 0)
|
||||
return MsgBEInstructionNone;
|
||||
int ctr = Modified / sets.Count;
|
||||
int len = Iterated / sets.Count;
|
||||
string maybe = sets.Count == 1 ? string.Empty : "~";
|
||||
string result = string.Format(MsgBEModifySuccess, maybe, ctr, len);
|
||||
if (Failed > 0)
|
||||
result += Environment.NewLine + maybe + string.Format(MsgBEModifyFailError, Failed);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static BatchEditor Execute(IList<string> lines, IEnumerable<PKM> data)
|
||||
{
|
||||
var editor = new BatchEditor();
|
||||
var sets = StringInstructionSet.GetBatchSets(lines).ToArray();
|
||||
foreach (var pk in data)
|
||||
{
|
||||
foreach (var set in sets)
|
||||
editor.Process(pk, set.Filters, set.Instructions);
|
||||
}
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
public void AddSkipped()
|
||||
{
|
||||
++Iterated;
|
||||
}
|
||||
}
|
||||
33
PKHeX.Core/Editing/Bulk/BatchFilters.cs
Normal file
33
PKHeX.Core/Editing/Bulk/BatchFilters.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using System.Collections.Generic;
|
||||
using static PKHeX.Core.BatchEditing;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Filters for Batch Editing
|
||||
/// </summary>
|
||||
public static class BatchFilters
|
||||
{
|
||||
public static readonly List<IComplexFilter> FilterMods = new()
|
||||
{
|
||||
new ComplexFilter(PROP_LEGAL,
|
||||
(pk, cmd) => bool.TryParse(cmd.PropertyValue, out var b) && (b == new LegalityAnalysis(pk).Valid) == cmd.Evaluator,
|
||||
(info, cmd) => bool.TryParse(cmd.PropertyValue, out var b) && (b == info.Legality.Valid) == cmd.Evaluator),
|
||||
|
||||
new ComplexFilter(PROP_TYPENAME,
|
||||
(pk, cmd) => (pk.GetType().Name == cmd.PropertyValue) == cmd.Evaluator,
|
||||
(info, cmd) => (info.Entity.GetType().Name == cmd.PropertyValue) == cmd.Evaluator),
|
||||
};
|
||||
|
||||
public static readonly List<IComplexFilterMeta> FilterMeta = new()
|
||||
{
|
||||
new MetaFilter(IdentifierContains,
|
||||
(obj, cmd) => obj is SlotCache s && s.Identify().Contains(cmd.PropertyValue) == cmd.Evaluator),
|
||||
|
||||
new MetaFilter(nameof(SlotInfoBox.Box),
|
||||
(obj, cmd) => obj is SlotCache { Source: SlotInfoBox b } && int.TryParse(cmd.PropertyValue, out var box) && b.Box + 1 == box),
|
||||
|
||||
new MetaFilter(nameof(ISlotInfo.Slot),
|
||||
(obj, cmd) => obj is SlotCache s && int.TryParse(cmd.PropertyValue, out var slot) && s.Source.Slot + 1 == slot),
|
||||
};
|
||||
}
|
||||
18
PKHeX.Core/Editing/Bulk/BatchInfo.cs
Normal file
18
PKHeX.Core/Editing/Bulk/BatchInfo.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Information wrapper used for Batch Editing to apply suggested values.
|
||||
/// </summary>
|
||||
public sealed class BatchInfo
|
||||
{
|
||||
internal PKM Entity { get; }
|
||||
internal BatchInfo(PKM pk) => Entity = pk;
|
||||
|
||||
private LegalityAnalysis? la;
|
||||
internal LegalityAnalysis Legality => la ??= new LegalityAnalysis(Entity);
|
||||
|
||||
public bool Legal => Legality.Valid;
|
||||
internal IReadOnlyList<ushort> SuggestedRelearn => Legality.GetSuggestedRelearnMoves();
|
||||
}
|
||||
99
PKHeX.Core/Editing/Bulk/BatchMods.cs
Normal file
99
PKHeX.Core/Editing/Bulk/BatchMods.cs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using static PKHeX.Core.BatchEditing;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Modifications for Batch Editing
|
||||
/// </summary>
|
||||
public static class BatchMods
|
||||
{
|
||||
public static readonly List<ISuggestModification> SuggestionMods = new()
|
||||
{
|
||||
// Interface Specific
|
||||
new TypeSuggestion<ICombatPower>(nameof(ICombatPower.Stat_CP), p => p.ResetCP()),
|
||||
new TypeSuggestion<IScaledSizeValue>(nameof(IScaledSizeValue.HeightAbsolute), p => p.ResetHeight()),
|
||||
new TypeSuggestion<IScaledSizeValue>(nameof(IScaledSizeValue.WeightAbsolute), p => p.ResetWeight()),
|
||||
new TypeSuggestion<IHyperTrain>(nameof(Extensions.HyperTrainClear), p => p.HyperTrainClear()),
|
||||
new TypeSuggestion<IGeoTrack>(nameof(Extensions.ClearGeoLocationData), p => p.ClearGeoLocationData()),
|
||||
new TypeSuggestion<IAwakened>(nameof(AwakeningUtil.AwakeningClear), p => p.AwakeningClear()),
|
||||
new TypeSuggestion<IAwakened>(nameof(AwakeningUtil.AwakeningMax), p => p.AwakeningMax()),
|
||||
new TypeSuggestion<IGanbaru>(nameof(GanbaruExtensions.ClearGanbaruValues), p => p.ClearGanbaruValues()),
|
||||
new TypeSuggestion<IGanbaru>(nameof(GanbaruExtensions.SetSuggestedGanbaruValues), p => p.SetSuggestedGanbaruValues((PKM)p)),
|
||||
|
||||
// Date Copy
|
||||
new TypeSuggestion<PKM>(nameof(PKM.EggMetDate), p => p.EggMetDate = p.MetDate),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.MetDate), p => p.MetDate = p.EggMetDate),
|
||||
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Nature), p => p.Format >= 8, p => p.Nature = p.StatNature),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.StatNature), p => p.Format >= 8, p => p.StatNature = p.Nature),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Stats), p => p.ResetPartyStats()),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Ball), p => BallApplicator.ApplyBallLegalByColor(p)),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Heal), p => p.Heal()),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.HealPP), p => p.HealPP()),
|
||||
new TypeSuggestion<PKM>(nameof(IHyperTrain.HyperTrainFlags), p => p.SetSuggestedHyperTrainingData()),
|
||||
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Move1_PP), p => p.SetSuggestedMovePP(0)),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Move2_PP), p => p.SetSuggestedMovePP(1)),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Move3_PP), p => p.SetSuggestedMovePP(2)),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Move4_PP), p => p.SetSuggestedMovePP(3)),
|
||||
|
||||
new ComplexSuggestion(nameof(PKM.Moves), (_, _, info) => BatchModifications.SetMoves(info.Entity, info.Legality.GetMoveSet())),
|
||||
new ComplexSuggestion(PROP_EVS, (_, _, info) => BatchModifications.SetEVs(info.Entity)),
|
||||
new ComplexSuggestion(nameof(PKM.RelearnMoves), (_, value, info) => BatchModifications.SetSuggestedRelearnData(info, value)),
|
||||
new ComplexSuggestion(PROP_RIBBONS, (_, value, info) => BatchModifications.SetSuggestedRibbons(info, value)),
|
||||
new ComplexSuggestion(nameof(PKM.Met_Location), (_, _, info) => BatchModifications.SetSuggestedMetData(info)),
|
||||
new ComplexSuggestion(nameof(PKM.CurrentLevel), (_, _, info) => BatchModifications.SetMinimumCurrentLevel(info)),
|
||||
new ComplexSuggestion(PROP_CONTESTSTATS, p => p is IContestStatsMutable, (_, value, info) => BatchModifications.SetContestStats(info.Entity, info.Legality, value)),
|
||||
new ComplexSuggestion(PROP_MOVEMASTERY, (_, value, info) => BatchModifications.SetSuggestedMasteryData(info, value)),
|
||||
};
|
||||
|
||||
private static DateTime ParseDate(string val) => DateTime.ParseExact(val, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None);
|
||||
|
||||
public static readonly List<IComplexSet> ComplexMods = new()
|
||||
{
|
||||
// Date
|
||||
new ComplexSet(nameof(PKM.MetDate), (pk, cmd) => pk.MetDate = ParseDate(cmd.PropertyValue)),
|
||||
new ComplexSet(nameof(PKM.EggMetDate), (pk, cmd) => pk.EggMetDate = ParseDate(cmd.PropertyValue)),
|
||||
|
||||
// Value Swap
|
||||
new ComplexSet(nameof(PKM.EncryptionConstant), value => value == nameof(PKM.PID), (pk, _) => pk.EncryptionConstant = pk.PID),
|
||||
new ComplexSet(nameof(PKM.PID), value => value == nameof(PKM.EncryptionConstant), (pk, _) => pk.PID = pk.EncryptionConstant),
|
||||
|
||||
// Realign to Derived Value
|
||||
new ComplexSet(nameof(PKM.Ability), value => value.StartsWith(CONST_SPECIAL), (pk, cmd) => pk.RefreshAbility(Convert.ToInt16(cmd.PropertyValue[1]) - 0x30)),
|
||||
new ComplexSet(nameof(PKM.AbilityNumber), value => value.StartsWith(CONST_SPECIAL), (pk, cmd) => pk.RefreshAbility(Convert.ToInt16(cmd.PropertyValue[1]) - 0x30)),
|
||||
|
||||
// Random
|
||||
new ComplexSet(nameof(PKM.EncryptionConstant), value => value == CONST_RAND, (pk, _) => pk.EncryptionConstant = Util.Rand32()),
|
||||
new ComplexSet(nameof(PKM.PID), value => value == CONST_RAND, (pk, _) => pk.PID = Util.Rand32()),
|
||||
new ComplexSet(nameof(PKM.Gender), value => value == CONST_RAND, (pk, _) => pk.SetPIDGender(pk.Gender)),
|
||||
new ComplexSet(PROP_EVS, value => value == CONST_RAND, (pk, _) => SetRandomEVs(pk)),
|
||||
|
||||
// Shiny
|
||||
new ComplexSet(nameof(PKM.PID),
|
||||
value => value.StartsWith(CONST_SHINY, true, CultureInfo.CurrentCulture),
|
||||
(pk, cmd) => CommonEdits.SetShiny(pk, GetRequestedShinyState(cmd.PropertyValue))),
|
||||
|
||||
new ComplexSet(nameof(PKM.Species), value => value == "0", (pk, _) => Array.Clear(pk.Data, 0, pk.Data.Length)),
|
||||
new ComplexSet(nameof(PKM.IsNicknamed), value => string.Equals(value, "false", StringComparison.OrdinalIgnoreCase), (pk, _) => pk.SetDefaultNickname()),
|
||||
};
|
||||
|
||||
private static void SetRandomEVs(PKM pk)
|
||||
{
|
||||
Span<int> evs = stackalloc int[6];
|
||||
EffortValues.SetRandom(evs, pk.Format);
|
||||
pk.SetEVs(evs);
|
||||
}
|
||||
|
||||
private static Shiny GetRequestedShinyState(string text) => text.Length == 0 ? Shiny.Random : GetRequestedShinyState(text[^1]);
|
||||
|
||||
private static Shiny GetRequestedShinyState(char last) => last switch
|
||||
{
|
||||
'0' => Shiny.AlwaysSquare,
|
||||
'1' => Shiny.AlwaysStar,
|
||||
_ => Shiny.Random,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Default property provider that uses an <see cref="IBatchEditor{TObject}"/> for reflection.
|
||||
/// </summary>
|
||||
public class BatchPropertyProvider<TEditor, TObject>(TEditor editor) : IPropertyProvider<TObject> where TObject : notnull where TEditor : IBatchEditor<TObject>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BatchPropertyProvider{TEditor, TObject}"/> class with the specified editor.
|
||||
/// </summary>
|
||||
public bool TryGetProperty(TObject obj, string prop, [NotNullWhen(true)] out string? result)
|
||||
{
|
||||
result = null;
|
||||
if (!editor.TryGetHasProperty(obj, prop, out var pi))
|
||||
return false;
|
||||
|
||||
var value = pi.GetValue(obj);
|
||||
result = value?.ToString();
|
||||
return result is not null;
|
||||
}
|
||||
}
|
||||
25
PKHeX.Core/Editing/Bulk/ComplexSet/ComplexFilter.cs
Normal file
25
PKHeX.Core/Editing/Bulk/ComplexSet/ComplexFilter.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <inheritdoc cref="IComplexFilter"/>
|
||||
public sealed class ComplexFilter : IComplexFilter
|
||||
{
|
||||
private readonly string Property;
|
||||
private readonly Func<PKM, StringInstruction, bool> FilterPKM;
|
||||
private readonly Func<BatchInfo, StringInstruction, bool> FilterBulk;
|
||||
|
||||
public ComplexFilter(
|
||||
string property,
|
||||
Func<PKM, StringInstruction, bool> filterPkm,
|
||||
Func<BatchInfo, StringInstruction, bool> filterBulk)
|
||||
{
|
||||
Property = property;
|
||||
FilterPKM = filterPkm;
|
||||
FilterBulk = filterBulk;
|
||||
}
|
||||
|
||||
public bool IsMatch(string prop) => prop == Property;
|
||||
public bool IsFiltered(PKM pk, StringInstruction value) => FilterPKM(pk, value);
|
||||
public bool IsFiltered(BatchInfo info, StringInstruction value) => FilterBulk(info, value);
|
||||
}
|
||||
23
PKHeX.Core/Editing/Bulk/ComplexSet/ComplexSet.cs
Normal file
23
PKHeX.Core/Editing/Bulk/ComplexSet/ComplexSet.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <inheritdoc cref="IComplexSet"/>
|
||||
public sealed class ComplexSet : IComplexSet
|
||||
{
|
||||
public readonly string PropertyName;
|
||||
public readonly Func<string, bool> IsValueCompatible = _ => true;
|
||||
private readonly Action<PKM, StringInstruction> Action;
|
||||
|
||||
public ComplexSet(string propertyName, Action<PKM, StringInstruction> modify)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
Action = modify;
|
||||
}
|
||||
|
||||
public ComplexSet(string propertyName, Func<string, bool> criteria, Action<PKM, StringInstruction> modify) : this(propertyName, modify) => IsValueCompatible = criteria;
|
||||
|
||||
public bool IsMatch(string name, string value) => name == PropertyName && IsValueCompatible(value);
|
||||
|
||||
public void Modify(PKM pk, StringInstruction instr) => Action(pk, instr);
|
||||
}
|
||||
11
PKHeX.Core/Editing/Bulk/ComplexSet/IComplexFilter.cs
Normal file
11
PKHeX.Core/Editing/Bulk/ComplexSet/IComplexFilter.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Complex filter of data based on a string value.
|
||||
/// </summary>
|
||||
public interface IComplexFilter
|
||||
{
|
||||
bool IsMatch(string prop);
|
||||
bool IsFiltered(PKM pk, StringInstruction value);
|
||||
bool IsFiltered(BatchInfo info, StringInstruction value);
|
||||
}
|
||||
10
PKHeX.Core/Editing/Bulk/ComplexSet/IComplexFilterMeta.cs
Normal file
10
PKHeX.Core/Editing/Bulk/ComplexSet/IComplexFilterMeta.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Complex filter of data based on a string value.
|
||||
/// </summary>
|
||||
public interface IComplexFilterMeta
|
||||
{
|
||||
bool IsMatch(string prop);
|
||||
bool IsFiltered(object cache, StringInstruction value);
|
||||
}
|
||||
10
PKHeX.Core/Editing/Bulk/ComplexSet/IComplexSet.cs
Normal file
10
PKHeX.Core/Editing/Bulk/ComplexSet/IComplexSet.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Complex modification of data to a string value.
|
||||
/// </summary>
|
||||
public interface IComplexSet
|
||||
{
|
||||
bool IsMatch(string name, string value);
|
||||
void Modify(PKM pk, StringInstruction instr);
|
||||
}
|
||||
21
PKHeX.Core/Editing/Bulk/ComplexSet/MetaFilter.cs
Normal file
21
PKHeX.Core/Editing/Bulk/ComplexSet/MetaFilter.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <inheritdoc cref="IComplexFilter"/>
|
||||
public sealed class MetaFilter : IComplexFilterMeta
|
||||
{
|
||||
private readonly string Property;
|
||||
private readonly Func<object, StringInstruction, bool> FilterPKM;
|
||||
|
||||
public MetaFilter(
|
||||
string property,
|
||||
Func<object, StringInstruction, bool> filterPkm)
|
||||
{
|
||||
Property = property;
|
||||
FilterPKM = filterPkm;
|
||||
}
|
||||
|
||||
public bool IsMatch(string prop) => prop == Property;
|
||||
public bool IsFiltered(object pk, StringInstruction value) => FilterPKM(pk, value);
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using static PKHeX.Core.EntityBatchEditor;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Filters for Batch Editing
|
||||
/// </summary>
|
||||
public static class BatchFilters
|
||||
{
|
||||
/// <summary>
|
||||
/// Filters to use for <see cref="EntityBatchEditor"/> that are derived from the <see cref="PKM"/> data.
|
||||
/// </summary>
|
||||
public static readonly List<IComplexFilter> FilterMods =
|
||||
[
|
||||
new ComplexFilter(PROP_LEGAL,
|
||||
(pk, cmd) => bool.TryParse(cmd.PropertyValue, out var b) && cmd.Comparer.IsCompareEquivalence(b == new LegalityAnalysis(pk).Valid),
|
||||
(info, cmd) => bool.TryParse(cmd.PropertyValue, out var b) && cmd.Comparer.IsCompareEquivalence(b == info.Legality.Valid)),
|
||||
|
||||
new ComplexFilter(BatchEditingUtil.PROP_TYPENAME,
|
||||
(pk, cmd) => cmd.Comparer.IsCompareEquivalence(pk.GetType().Name == cmd.PropertyValue),
|
||||
(info, cmd) => cmd.Comparer.IsCompareEquivalence(info.Entity.GetType().Name == cmd.PropertyValue)),
|
||||
|
||||
new ComplexFilter(PROP_TYPE1,
|
||||
(pk, cmd) => byte.TryParse(cmd.PropertyValue, out var b) && cmd.Comparer.IsCompareEquivalence(b == pk.PersonalInfo.Type1),
|
||||
(info, cmd) => byte.TryParse(cmd.PropertyValue, out var b) && cmd.Comparer.IsCompareEquivalence(b == info.Entity.PersonalInfo.Type1)),
|
||||
|
||||
new ComplexFilter(PROP_TYPE2,
|
||||
(pk, cmd) => byte.TryParse(cmd.PropertyValue, out var b) && cmd.Comparer.IsCompareEquivalence(b == pk.PersonalInfo.Type2),
|
||||
(info, cmd) => byte.TryParse(cmd.PropertyValue, out var b) && cmd.Comparer.IsCompareEquivalence(b == info.Entity.PersonalInfo.Type2)),
|
||||
|
||||
new ComplexFilter(PROP_TYPEEITHER,
|
||||
(pk, cmd) => byte.TryParse(cmd.PropertyValue, out var b) && cmd.Comparer.IsCompareEquivalence(pk.PersonalInfo.IsType(b)),
|
||||
(info, cmd) => byte.TryParse(cmd.PropertyValue, out var b) && cmd.Comparer.IsCompareEquivalence(info.Entity.PersonalInfo.IsType(b))),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Filters to use for <see cref="EntityBatchEditor"/> that are derived from the <see cref="PKM"/> source.
|
||||
/// </summary>
|
||||
public static readonly List<IComplexFilterMeta> FilterMeta =
|
||||
[
|
||||
new MetaFilter(IdentifierContains,
|
||||
(obj, cmd) => obj is SlotCache s && cmd.Comparer.IsCompareEquivalence(s.Identify().Contains(cmd.PropertyValue))),
|
||||
|
||||
new MetaFilter(nameof(SlotInfoBox.Box),
|
||||
(obj, cmd) => obj is SlotCache { Source: SlotInfoBox b } && int.TryParse(cmd.PropertyValue, out var box) && cmd.Comparer.IsCompareOperator((b.Box + 1).CompareTo(box))),
|
||||
|
||||
new MetaFilter(nameof(ISlotInfo.Slot),
|
||||
(obj, cmd) => obj is SlotCache s && int.TryParse(cmd.PropertyValue, out var slot) && cmd.Comparer.IsCompareOperator((s.Source.Slot + 1).CompareTo(slot))),
|
||||
];
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Information wrapper used for Batch Editing to apply suggested values.
|
||||
/// </summary>
|
||||
/// <param name="Entity"> Entity to be modified. </param>
|
||||
public sealed record BatchInfo(PKM Entity)
|
||||
{
|
||||
/// <summary>
|
||||
/// Legality analysis of the entity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Eagerly evaluate on ctor, so that the initial state is remembered before any modifications may disturb matching.
|
||||
/// </remarks>
|
||||
public readonly LegalityAnalysis Legality = new(Entity);
|
||||
|
||||
/// <inheritdoc cref="LegalityAnalysis.Valid"/>
|
||||
public bool Legal => Legality.Valid;
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using static PKHeX.Core.EntityBatchEditor;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Modifications for Batch Editing
|
||||
/// </summary>
|
||||
public static class BatchMods
|
||||
{
|
||||
public static readonly List<ISuggestModification> SuggestionMods =
|
||||
[
|
||||
// Interface Specific
|
||||
new TypeSuggestion<ICombatPower>(nameof(ICombatPower.Stat_CP), p => p.ResetCP()),
|
||||
new TypeSuggestion<IScaledSizeValue>(nameof(IScaledSizeValue.HeightAbsolute), p => p.ResetHeight()),
|
||||
new TypeSuggestion<IScaledSizeValue>(nameof(IScaledSizeValue.WeightAbsolute), p => p.ResetWeight()),
|
||||
new TypeSuggestion<IHyperTrain>(nameof(Extensions.HyperTrainClear), p => p.HyperTrainClear()),
|
||||
new TypeSuggestion<IGeoTrack>(nameof(Extensions.ClearGeoLocationData), p => p.ClearGeoLocationData()),
|
||||
new TypeSuggestion<IAwakened>(nameof(AwakeningUtil.AwakeningClear), p => p.AwakeningClear()),
|
||||
new TypeSuggestion<IAwakened>(nameof(AwakeningUtil.AwakeningMinimum), p => p.AwakeningMinimum()),
|
||||
new TypeSuggestion<IAwakened>(nameof(AwakeningUtil.AwakeningMaximize), p => p.AwakeningMaximize()),
|
||||
new TypeSuggestion<IGanbaru>(nameof(GanbaruExtensions.ClearGanbaruValues), p => p.ClearGanbaruValues()),
|
||||
new TypeSuggestion<IGanbaru>(nameof(GanbaruExtensions.SetSuggestedGanbaruValues), p => p.SetSuggestedGanbaruValues((PKM)p)),
|
||||
|
||||
// Date Copy
|
||||
new TypeSuggestion<PKM>(nameof(PKM.EggMetDate), p => p.EggMetDate = p.MetDate),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.MetDate), p => p.MetDate = p.EggMetDate),
|
||||
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Nature), p => p.Format >= 8, p => p.Nature = p.StatNature),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.StatNature), p => p.Format >= 8, p => p.StatNature = p.Nature),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Stats), p => p.ResetPartyStats()),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Ball), p => BallApplicator.ApplyBallLegalByColor(p)),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Heal), p => p.Heal()),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.HealPP), p => p.HealPP()),
|
||||
new TypeSuggestion<PKM>(nameof(IHyperTrain.HyperTrainFlags), p => p.SetSuggestedHyperTrainingData()),
|
||||
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Move1_PP), p => p.HealPPIndex(0)),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Move2_PP), p => p.HealPPIndex(1)),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Move3_PP), p => p.HealPPIndex(2)),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Move4_PP), p => p.HealPPIndex(3)),
|
||||
|
||||
new ComplexSuggestion(nameof(PKM.CurrentFriendship), (_, _, info) => BatchModifications.SetSuggestedCurrentFriendship(info)),
|
||||
new ComplexSuggestion(nameof(PKM.OriginalTrainerFriendship), (_, _, info) => BatchModifications.SetSuggestedOriginalTrainerFriendship(info)),
|
||||
new ComplexSuggestion(nameof(PKM.HandlingTrainerFriendship), (_, _, info) => BatchModifications.SetSuggestedHandlingTrainerFriendship(info)),
|
||||
new ComplexSuggestion(nameof(PKM.Moves), (_, _, info) => BatchModifications.SetSuggestedMoveset(info)),
|
||||
new ComplexSuggestion(PROP_EVS, (_, _, info) => BatchModifications.SetEVs(info.Entity)),
|
||||
new ComplexSuggestion(nameof(PKM.RelearnMoves), (_, value, info) => BatchModifications.SetSuggestedRelearnData(info, value)),
|
||||
new ComplexSuggestion(PROP_RIBBONS, (_, value, info) => BatchModifications.SetSuggestedRibbons(info, value)),
|
||||
new ComplexSuggestion(nameof(PKM.MetLocation), (_, _, info) => BatchModifications.SetSuggestedMetData(info)),
|
||||
new ComplexSuggestion(nameof(PKM.CurrentLevel), (_, _, info) => BatchModifications.SetMinimumCurrentLevel(info)),
|
||||
new ComplexSuggestion(PROP_CONTESTSTATS, p => p is IContestStats, (_, value, info) => BatchModifications.SetContestStats(info.Entity, info.Legality, value)),
|
||||
new ComplexSuggestion(PROP_MOVEMASTERY, (_, value, info) => BatchModifications.SetSuggestedMasteryData(info, value)),
|
||||
new ComplexSuggestion(PROP_MOVEPLUS, (_, value, info) => BatchModifications.SetSuggestedMovePlusData(info, value)),
|
||||
];
|
||||
|
||||
private static DateOnly ParseDate(ReadOnlySpan<char> val) => DateOnly.ParseExact(val, "yyyyMMdd", CultureInfo.InvariantCulture);
|
||||
|
||||
public static readonly List<IComplexSet> ComplexMods =
|
||||
[
|
||||
new ComplexSet(nameof(PKM.MetDate), (pk, cmd) => pk.MetDate = ParseDate(cmd.PropertyValue)),
|
||||
new ComplexSet(nameof(PKM.EggMetDate), (pk, cmd) => pk.EggMetDate = ParseDate(cmd.PropertyValue)),
|
||||
|
||||
// Realign to Derived Value
|
||||
new ComplexSet(nameof(PKM.Ability), value => value.Length == 2 && value.StartsWith(CONST_SPECIAL), (pk, cmd) => pk.RefreshAbility(cmd.PropertyValue[1] - 0x30)),
|
||||
new ComplexSet(nameof(PKM.AbilityNumber), value => value.Length == 2 && value.StartsWith(CONST_SPECIAL), (pk, cmd) => pk.RefreshAbility(cmd.PropertyValue[1] - 0x30)),
|
||||
|
||||
// Random
|
||||
new ComplexSet(nameof(PKM.PID), value => value is CONST_RAND, (pk, _) => pk.PID = Util.Rand32()),
|
||||
new ComplexSet(nameof(PKM.Gender), value => value is CONST_RAND, (pk, _) => pk.SetPIDGender(pk.Gender)),
|
||||
new ComplexSet(PROP_EVS, value => value is CONST_RAND, (pk, _) => SetRandomEVs(pk)),
|
||||
new ComplexSet(nameof(ITeraType.TeraTypeOverride), value => value is CONST_RAND, (pk, _) => SetRandomTeraType(pk)),
|
||||
|
||||
// Shiny
|
||||
new ComplexSet(nameof(PKM.PID),
|
||||
value => value.StartsWith(CONST_SHINY),
|
||||
(pk, cmd) => pk.SetShiny(GetRequestedShinyState(cmd.PropertyValue))),
|
||||
|
||||
new ComplexSet(nameof(PKM.Species), value => value is "0", (pk, _) => pk.Data.Clear()),
|
||||
new ComplexSet(nameof(PKM.IsNicknamed), value => value.Equals("false", StringComparison.OrdinalIgnoreCase), (pk, _) => pk.SetDefaultNickname()),
|
||||
|
||||
// Complicated
|
||||
new ComplexSet(nameof(PKM.EncryptionConstant), value => value.StartsWith(CONST_RAND), (pk, cmd) => pk.EncryptionConstant = CommonEdits.GetComplicatedEC(pk, option: GetOptionSuffix(cmd.PropertyValue, CONST_RAND))),
|
||||
];
|
||||
|
||||
private static char GetOptionSuffix(ReadOnlySpan<char> str, ReadOnlySpan<char> prefix)
|
||||
=> str.Length == prefix.Length ? CommonEdits.OptionNone : str[^1];
|
||||
|
||||
private static void SetRandomTeraType(PKM pk)
|
||||
{
|
||||
if (pk is ITeraType t)
|
||||
t.TeraTypeOverride = (MoveType)Util.Rand.Next(0, TeraTypeUtil.MaxType + 1);
|
||||
}
|
||||
|
||||
private static void SetRandomEVs(PKM pk)
|
||||
{
|
||||
Span<int> evs = stackalloc int[6];
|
||||
EffortValues.SetRandom(evs, pk.Format);
|
||||
pk.SetEVs(evs);
|
||||
}
|
||||
|
||||
private static Shiny GetRequestedShinyState(ReadOnlySpan<char> text) => text.Length == 0 ? Shiny.Random : GetRequestedShinyState(text[^1]);
|
||||
|
||||
private static Shiny GetRequestedShinyState(char last) => last switch
|
||||
{
|
||||
'0' => Shiny.AlwaysSquare,
|
||||
'1' => Shiny.AlwaysStar,
|
||||
_ => Shiny.Random,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <inheritdoc cref="IComplexFilter"/>
|
||||
public sealed class ComplexFilter(
|
||||
[ConstantExpected] string Property,
|
||||
Func<PKM, StringInstruction, bool> FilterPKM,
|
||||
Func<BatchInfo, StringInstruction, bool> FilterBulk)
|
||||
: IComplexFilter
|
||||
{
|
||||
public bool IsMatch(ReadOnlySpan<char> prop) => prop.SequenceEqual(Property);
|
||||
public bool IsFiltered(PKM pk, StringInstruction value) => FilterPKM(pk, value);
|
||||
public bool IsFiltered(BatchInfo info, StringInstruction value) => FilterBulk(info, value);
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <inheritdoc cref="IComplexSet"/>
|
||||
public sealed class ComplexSet([ConstantExpected] string PropertyName, Action<PKM, StringInstruction> Action) : IComplexSet
|
||||
{
|
||||
public readonly string PropertyName = PropertyName;
|
||||
public readonly Func<ReadOnlySpan<char>, bool> IsValueCompatible = _ => true;
|
||||
|
||||
public ComplexSet([ConstantExpected] string PropertyName, Func<ReadOnlySpan<char>, bool> criteria, Action<PKM, StringInstruction> Action)
|
||||
: this(PropertyName, Action) => IsValueCompatible = criteria;
|
||||
|
||||
public bool IsMatch(ReadOnlySpan<char> name, ReadOnlySpan<char> value)
|
||||
=> name.SequenceEqual(PropertyName) && IsValueCompatible(value);
|
||||
|
||||
public void Modify(PKM pk, StringInstruction instr) => Action(pk, instr);
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Complex filter of data based on a string value.
|
||||
/// </summary>
|
||||
public interface IComplexFilter
|
||||
{
|
||||
bool IsMatch(ReadOnlySpan<char> prop);
|
||||
bool IsFiltered(PKM pk, StringInstruction value);
|
||||
bool IsFiltered(BatchInfo info, StringInstruction value);
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Complex filter of data based on a string value.
|
||||
/// </summary>
|
||||
public interface IComplexFilterMeta
|
||||
{
|
||||
bool IsMatch(ReadOnlySpan<char> prop);
|
||||
bool IsFiltered(object cache, StringInstruction value);
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Complex modification of data to a string value.
|
||||
/// </summary>
|
||||
public interface IComplexSet
|
||||
{
|
||||
bool IsMatch(ReadOnlySpan<char> name, ReadOnlySpan<char> value);
|
||||
void Modify(PKM pk, StringInstruction instr);
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <inheritdoc cref="IComplexFilter"/>
|
||||
public sealed class MetaFilter(
|
||||
[ConstantExpected] string Property,
|
||||
Func<object, StringInstruction, bool> FilterPKM)
|
||||
: IComplexFilterMeta
|
||||
{
|
||||
public bool IsMatch(ReadOnlySpan<char> prop) => prop.SequenceEqual(Property);
|
||||
public bool IsFiltered(object pk, StringInstruction value) => FilterPKM(pk, value);
|
||||
}
|
||||
|
|
@ -1,304 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.BatchModifications;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for editing many <see cref="PKM"/> with user provided <see cref="StringInstruction"/> list.
|
||||
/// </summary>
|
||||
public sealed class EntityBatchEditor() : BatchEditingBase<PKM, BatchInfo>(EntityTypes, EntityCustomProperties, expectedMax: 0x200)
|
||||
{
|
||||
private static readonly Type[] EntityTypes =
|
||||
[
|
||||
typeof (PK9), typeof (PA9),
|
||||
typeof (PK8), typeof (PA8), typeof (PB8),
|
||||
typeof (PB7),
|
||||
typeof (PK7), typeof (PK6), typeof (PK5), typeof (PK4), typeof(BK4), typeof(RK4),
|
||||
typeof (PK3), typeof (XK3), typeof (CK3),
|
||||
typeof (PK2), typeof (SK2), typeof (PK1),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Extra properties to show in the list of selectable properties (GUI) with special handling.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These are not necessarily properties of the <see cref="PKM"/> themselves,
|
||||
/// but can be any context-sensitive value related to the <see cref="PKM"/> or its legality,
|
||||
/// such as "Legal" or "HasType". The handling of these properties must be implemented in the <see cref="TryHandleSetOperation"/> and <see cref="TryHandleFilter"/> methods.
|
||||
/// </remarks>
|
||||
private static readonly string[] EntityCustomProperties =
|
||||
[
|
||||
// General
|
||||
BatchEditingUtil.PROP_TYPENAME,
|
||||
|
||||
// Entity/PersonalInfo
|
||||
PROP_LEGAL, PROP_RIBBONS, PROP_EVS, PROP_CONTESTSTATS, PROP_MOVEMASTERY, PROP_MOVEPLUS,
|
||||
PROP_TYPE1, PROP_TYPE2, PROP_TYPEEITHER,
|
||||
|
||||
// SlotCache
|
||||
IdentifierContains, nameof(ISlotInfo.Slot), nameof(SlotInfoBox.Box),
|
||||
];
|
||||
|
||||
public static EntityBatchEditor Instance { get; } = new();
|
||||
|
||||
// Custom Identifiers for special handling.
|
||||
private const string CONST_BYTES = "$[]"; // Define a byte array with separated hex byte values, e.g. "$[]FF,02,03" or "$[]A0 02 0A FF"
|
||||
|
||||
// Custom Values to apply.
|
||||
internal const string CONST_RAND = "$rand";
|
||||
internal const string CONST_SHINY = "$shiny";
|
||||
internal const string CONST_SUGGEST = "$suggest";
|
||||
internal const char CONST_SPECIAL = '$';
|
||||
|
||||
// Custom Properties to change.
|
||||
internal const string PROP_LEGAL = "Legal";
|
||||
internal const string PROP_TYPEEITHER = "HasType";
|
||||
internal const string PROP_TYPE1 = "PersonalType1";
|
||||
internal const string PROP_TYPE2 = "PersonalType2";
|
||||
internal const string PROP_RIBBONS = "Ribbons";
|
||||
internal const string PROP_EVS = "EVs";
|
||||
internal const string PROP_CONTESTSTATS = "ContestStats";
|
||||
internal const string PROP_MOVEMASTERY = "MoveMastery";
|
||||
internal const string PROP_MOVEPLUS = "PlusMoves";
|
||||
internal const string IdentifierContains = nameof(IdentifierContains);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="StringInstruction"/> list with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
|
||||
/// </summary>
|
||||
/// <param name="il">Instructions to initialize.</param>
|
||||
public static void ScreenStrings(IEnumerable<StringInstruction> il)
|
||||
{
|
||||
foreach (var i in il)
|
||||
{
|
||||
var pv = i.PropertyValue;
|
||||
if (pv.All(char.IsDigit))
|
||||
continue;
|
||||
|
||||
if (pv.StartsWith(CONST_SPECIAL) && !pv.StartsWith(CONST_BYTES, StringComparison.Ordinal))
|
||||
{
|
||||
var str = pv.AsSpan(1);
|
||||
if (StringInstruction.IsRandomRange(str))
|
||||
{
|
||||
i.SetRandomRange(str);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
SetInstructionScreenedValue(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="StringInstruction"/> with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
|
||||
/// </summary>
|
||||
/// <param name="i">Instruction to initialize.</param>
|
||||
private static void SetInstructionScreenedValue(StringInstruction i)
|
||||
{
|
||||
ReadOnlySpan<string> set;
|
||||
switch (i.PropertyName)
|
||||
{
|
||||
case nameof(PKM.Species): set = GameInfo.Strings.specieslist; break;
|
||||
case nameof(PKM.HeldItem): set = GameInfo.Strings.itemlist; break;
|
||||
case nameof(PKM.Ability): set = GameInfo.Strings.abilitylist; break;
|
||||
case nameof(PKM.Nature): set = GameInfo.Strings.natures; break;
|
||||
case nameof(PKM.Ball): set = GameInfo.Strings.balllist; break;
|
||||
|
||||
case nameof(PKM.Move1) or nameof(PKM.Move2) or nameof(PKM.Move3) or nameof(PKM.Move4):
|
||||
case nameof(PKM.RelearnMove1) or nameof(PKM.RelearnMove2) or nameof(PKM.RelearnMove3) or nameof(PKM.RelearnMove4):
|
||||
set = GameInfo.Strings.movelist; break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
i.SetScreenedValue(set);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the object is filtered by the provided <see cref="filters"/>.
|
||||
/// </summary>
|
||||
/// <param name="filters">Filters which must be satisfied.</param>
|
||||
/// <param name="pk">Object to check.</param>
|
||||
/// <returns>True if <see cref="pk"/> matches all filters.</returns>
|
||||
public static bool IsFilterMatchMeta(IEnumerable<StringInstruction> filters, SlotCache pk)
|
||||
{
|
||||
foreach (var i in filters)
|
||||
{
|
||||
foreach (var filter in BatchFilters.FilterMeta)
|
||||
{
|
||||
if (!filter.IsMatch(i.PropertyName))
|
||||
continue;
|
||||
|
||||
if (!filter.IsFiltered(pk, i))
|
||||
return false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override BatchInfo CreateMeta(PKM entity) => new(entity);
|
||||
|
||||
protected override bool ShouldModify(PKM entity) => entity.ChecksumValid && entity.Species != 0;
|
||||
|
||||
protected override bool TryHandleSetOperation(StringInstruction cmd, BatchInfo info, PKM entity, out ModifyResult result)
|
||||
{
|
||||
if (cmd.PropertyValue.StartsWith(CONST_BYTES, StringComparison.Ordinal))
|
||||
{
|
||||
result = SetByteArrayProperty(entity, cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cmd.PropertyValue.StartsWith(CONST_SUGGEST, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result = SetSuggestedProperty(cmd.PropertyName, info, cmd.PropertyValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cmd is { PropertyValue: CONST_RAND, PropertyName: nameof(PKM.Moves) })
|
||||
{
|
||||
result = SetSuggestedMoveset(info, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SetComplexProperty(info, cmd))
|
||||
{
|
||||
result = ModifyResult.Modified;
|
||||
return true;
|
||||
}
|
||||
|
||||
result = ModifyResult.Skipped;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool TryHandleFilter(StringInstruction cmd, BatchInfo info, PKM entity, out bool isMatch)
|
||||
{
|
||||
var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
|
||||
if (match is null)
|
||||
{
|
||||
isMatch = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
isMatch = match.IsFiltered(info, cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM"/> data with a suggested value based on its <see cref="LegalityAnalysis"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">Property to modify.</param>
|
||||
/// <param name="info">Cached info storing Legal data.</param>
|
||||
/// <param name="propValue">Suggestion string which starts with <see cref="CONST_SUGGEST"/></param>
|
||||
private static ModifyResult SetSuggestedProperty(ReadOnlySpan<char> name, BatchInfo info, ReadOnlySpan<char> propValue)
|
||||
{
|
||||
foreach (var mod in BatchMods.SuggestionMods)
|
||||
{
|
||||
if (mod.IsMatch(name, propValue, info))
|
||||
return mod.Modify(name, propValue, info);
|
||||
}
|
||||
return ModifyResult.Error;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM"/> byte array property to a specified value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="cmd">Modification</param>
|
||||
private static ModifyResult SetByteArrayProperty(PKM pk, StringInstruction cmd)
|
||||
{
|
||||
Span<byte> dest;
|
||||
switch (cmd.PropertyName)
|
||||
{
|
||||
case nameof(PKM.NicknameTrash) or nameof(PKM.Nickname): dest = pk.NicknameTrash; break;
|
||||
case nameof(PKM.OriginalTrainerTrash): dest = pk.OriginalTrainerTrash; break;
|
||||
case nameof(PKM.HandlingTrainerTrash): dest = pk.HandlingTrainerTrash; break;
|
||||
default:
|
||||
return ModifyResult.Error;
|
||||
}
|
||||
var src = cmd.PropertyValue.AsSpan(CONST_BYTES.Length); // skip prefix
|
||||
StringUtil.LoadHexBytesTo(src, dest, 3);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM"/> property to a non-specific smart value.
|
||||
/// </summary>
|
||||
/// <param name="info">Pokémon to modify.</param>
|
||||
/// <param name="cmd">Modification</param>
|
||||
/// <returns>True if modified, false if no modifications done.</returns>
|
||||
private bool SetComplexProperty(BatchInfo info, StringInstruction cmd)
|
||||
{
|
||||
ReadOnlySpan<char> name = cmd.PropertyName;
|
||||
ReadOnlySpan<char> value = cmd.PropertyValue;
|
||||
|
||||
if (name.StartsWith("IV") && value is CONST_RAND)
|
||||
{
|
||||
SetRandomIVs(info, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var mod in BatchMods.ComplexMods)
|
||||
{
|
||||
if (!mod.IsMatch(name, value))
|
||||
continue;
|
||||
mod.Modify(info.Entity, cmd);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM"/> IV(s) to a random value.
|
||||
/// </summary>
|
||||
/// <param name="info">Pokémon to modify.</param>
|
||||
/// <param name="propertyName">Property to modify</param>
|
||||
private void SetRandomIVs(BatchInfo info, ReadOnlySpan<char> propertyName)
|
||||
{
|
||||
var pk = info.Entity;
|
||||
if (propertyName is nameof(PKM.IVs))
|
||||
{
|
||||
var la = info.Legality;
|
||||
var enc = la.EncounterMatch;
|
||||
if (enc is IFlawlessIVCount { FlawlessIVCount: not 0 } fc)
|
||||
pk.SetRandomIVs(fc.FlawlessIVCount);
|
||||
else if (enc is IFixedIVSet { IVs: {IsSpecified: true} iv})
|
||||
pk.SetRandomIVs(iv);
|
||||
else if (enc is IFlawlessIVCountConditional c && c.GetFlawlessIVCount(pk) is { Max: not 0 } x)
|
||||
pk.SetRandomIVs(Util.Rand.Next(x.Min, x.Max + 1));
|
||||
else
|
||||
pk.SetRandomIVs();
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryGetHasProperty(pk, propertyName, out var pi))
|
||||
{
|
||||
const string IV32 = nameof(PK9.IV32);
|
||||
if (propertyName is IV32)
|
||||
{
|
||||
var value = (uint)Util.Rand.Next(0x3FFFFFFF + 1);
|
||||
if (pk is BK4 bk) // Big Endian, reverse IV ordering
|
||||
{
|
||||
value <<= 2; // flags are the lowest bits, and our random value is still fine.
|
||||
value |= bk.IV32 & 3; // preserve the flags
|
||||
bk.IV32 = value;
|
||||
return;
|
||||
}
|
||||
|
||||
var exist = ReflectUtil.GetValue(pk, IV32);
|
||||
value |= exist switch
|
||||
{
|
||||
uint iv => iv & (3u << 30), // preserve the flags
|
||||
_ => 0,
|
||||
};
|
||||
ReflectUtil.SetValue(pi, pk, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = Util.Rand.Next(pk.MaxIV + 1);
|
||||
ReflectUtil.SetValue(pi, pk, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using static PKHeX.Core.MessageStrings;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Carries out a batch edit and contains information summarizing the results.
|
||||
/// </summary>
|
||||
public sealed class EntityBatchProcessor
|
||||
{
|
||||
private int Modified { get; set; }
|
||||
private int Iterated { get; set; }
|
||||
private int Failed { get; set; }
|
||||
|
||||
private static EntityBatchEditor Editor => EntityBatchEditor.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to modify the <see cref="PKM"/> using instructions and a custom modifier delegate.
|
||||
/// </summary>
|
||||
/// <param name="pk">Object to modify.</param>
|
||||
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
|
||||
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
|
||||
/// <param name="modifier">Custom modifier delegate.</param>
|
||||
/// <returns>Result of the attempted modification.</returns>
|
||||
public bool Process(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<PKM, bool>? modifier = null)
|
||||
{
|
||||
if (pk.Species == 0)
|
||||
return false;
|
||||
if (!pk.Valid)
|
||||
{
|
||||
Iterated++;
|
||||
const string reason = "Not Valid.";
|
||||
Debug.WriteLine($"{MsgBEModifyFailBlocked} {reason}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = Editor.TryModify(pk, filters, modifications, modifier);
|
||||
if (result != ModifyResult.Skipped)
|
||||
Iterated++;
|
||||
if (result.HasFlag(ModifyResult.Error))
|
||||
{
|
||||
Failed++;
|
||||
result &= ~ModifyResult.Error;
|
||||
}
|
||||
if (result != ModifyResult.Modified)
|
||||
return false;
|
||||
|
||||
pk.RefreshChecksum();
|
||||
Modified++;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a message indicating the overall result of all modifications performed across multiple Batch Edit jobs.
|
||||
/// </summary>
|
||||
/// <param name="sets">Collection of modifications.</param>
|
||||
/// <returns>Friendly (multi-line) string indicating the result of the batch edits.</returns>
|
||||
public string GetEditorResults(IReadOnlyCollection<StringInstructionSet> sets)
|
||||
{
|
||||
if (sets.Count == 0)
|
||||
return MsgBEInstructionNone;
|
||||
int ctr = Modified / sets.Count;
|
||||
int len = Iterated / sets.Count;
|
||||
string maybe = sets.Count == 1 ? string.Empty : "~";
|
||||
string result = string.Format(MsgBEModifySuccess, maybe, ctr, len);
|
||||
if (Failed > 0)
|
||||
result += Environment.NewLine + maybe + string.Format(MsgBEModifyFailError, Failed);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the batch instruction <see cref="lines"/> on the input <see cref="data"/>
|
||||
/// </summary>
|
||||
/// <param name="lines">Batch instruction line(s)</param>
|
||||
/// <param name="data">Entities to modify</param>
|
||||
/// <param name="modifier">Custom modifier delegate.</param>
|
||||
/// <returns>Editor object if follow-up modifications are desired.</returns>
|
||||
public static EntityBatchProcessor Execute(ReadOnlySpan<string> lines, IEnumerable<PKM> data, Func<PKM, bool>? modifier = null)
|
||||
{
|
||||
var editor = new EntityBatchProcessor();
|
||||
var sets = StringInstructionSet.GetBatchSets(lines);
|
||||
foreach (var pk in data)
|
||||
{
|
||||
foreach (var set in sets)
|
||||
editor.Process(pk, set.Filters, set.Instructions, modifier);
|
||||
}
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
public void AddSkipped()
|
||||
{
|
||||
++Iterated;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Modifications using <see cref="BatchInfo"/> legality.
|
||||
/// </summary>
|
||||
internal static class BatchModifications
|
||||
{
|
||||
private static bool IsAll(ReadOnlySpan<char> p) => p.EndsWith("All", StringComparison.OrdinalIgnoreCase);
|
||||
private static bool IsNone(ReadOnlySpan<char> p) => p.EndsWith("None", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a suggested legal moveset for the Entity.
|
||||
/// </summary>
|
||||
public static ModifyResult SetSuggestedMoveset(BatchInfo info, bool random = false)
|
||||
{
|
||||
Span<ushort> moves = stackalloc ushort[4];
|
||||
info.Legality.GetMoveSet(moves, random);
|
||||
return SetMoves(info.Entity, moves);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a suggested legal relearn moveset for the Entity.
|
||||
/// </summary>
|
||||
public static ModifyResult SetSuggestedRelearnData(BatchInfo info, ReadOnlySpan<char> propValue)
|
||||
{
|
||||
var pk = info.Entity;
|
||||
if (pk is ITechRecord t)
|
||||
{
|
||||
if (IsNone(propValue))
|
||||
t.SetRecordFlags(pk, TechnicalRecordApplicatorOption.None);
|
||||
else if (IsAll(propValue))
|
||||
t.SetRecordFlags(pk, TechnicalRecordApplicatorOption.LegalAll, info.Legality);
|
||||
else
|
||||
t.SetRecordFlags(pk, TechnicalRecordApplicatorOption.LegalCurrent, info.Legality);
|
||||
}
|
||||
|
||||
pk.SetRelearnMoves(info.Legality);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all legal Move Mastery flag data for the Entity.
|
||||
/// </summary>
|
||||
/// <remarks>Only applicable for <see cref="IMoveShop8Mastery"/>.</remarks>
|
||||
public static ModifyResult SetSuggestedMasteryData(BatchInfo info, ReadOnlySpan<char> propValue)
|
||||
{
|
||||
var pk = info.Entity;
|
||||
if (pk is not IMoveShop8Mastery t)
|
||||
return ModifyResult.Skipped;
|
||||
|
||||
t.ClearMoveShopFlags();
|
||||
if (IsNone(propValue))
|
||||
return ModifyResult.Modified;
|
||||
|
||||
var enc = info.Legality.EncounterMatch;
|
||||
if (enc is IMasteryInitialMoveShop8 shop)
|
||||
shop.SetInitialMastery(pk, enc);
|
||||
if (IsAll(propValue))
|
||||
{
|
||||
t.SetPurchasedFlagsAll(pk);
|
||||
t.SetMoveShopFlagsAll(pk);
|
||||
}
|
||||
else
|
||||
{
|
||||
t.SetMoveShopFlags(pk);
|
||||
}
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all legal Plus Move flag data for the Entity.
|
||||
/// </summary>
|
||||
/// <remarks>Only applicable for <see cref="IPlusRecord"/>.</remarks>
|
||||
public static ModifyResult SetSuggestedMovePlusData(BatchInfo info, ReadOnlySpan<char> value)
|
||||
{
|
||||
var pk = info.Entity;
|
||||
if (pk is not IPlusRecord t || pk.PersonalInfo is not IPermitPlus p)
|
||||
return ModifyResult.Skipped;
|
||||
|
||||
PlusRecordApplicatorOption option;
|
||||
if (IsNone(value))
|
||||
option = PlusRecordApplicatorOption.None;
|
||||
else if (IsAll(value))
|
||||
option = PlusRecordApplicatorOption.LegalSeedTM;
|
||||
else
|
||||
option = PlusRecordApplicatorOption.LegalCurrent;
|
||||
|
||||
t.SetPlusFlags(p, option, info.Legality);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets suggested ribbon data for the Entity.
|
||||
/// </summary>
|
||||
/// <remarks>If None, removes all ribbons possible.</remarks>
|
||||
public static ModifyResult SetSuggestedRibbons(BatchInfo info, ReadOnlySpan<char> value)
|
||||
{
|
||||
if (IsNone(value))
|
||||
RibbonApplicator.RemoveAllValidRibbons(info.Legality);
|
||||
else if (IsAll(value))
|
||||
RibbonApplicator.SetAllValidRibbons(info.Legality);
|
||||
else // Only for current context
|
||||
RibbonApplicator.SetAllValidRibbons(info.Entity, info.Legality.EncounterMatch, info.Legality.Info.EvoChainsAllGens.AsSingle(info.Entity.Context));
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets suggested met data for the Entity.
|
||||
/// </summary>
|
||||
public static ModifyResult SetSuggestedMetData(BatchInfo info)
|
||||
{
|
||||
var pk = info.Entity;
|
||||
var encounter = EncounterSuggestion.GetSuggestedMetInfo(pk);
|
||||
if (encounter is null)
|
||||
return ModifyResult.Error;
|
||||
|
||||
var location = encounter.Location;
|
||||
var level = encounter.LevelMin;
|
||||
var minimumLevel = EncounterSuggestion.GetLowestLevel(pk, level);
|
||||
var current = Math.Max(minimumLevel, level);
|
||||
|
||||
if (pk.MetLevel == level && pk.MetLocation == location && pk.CurrentLevel == current)
|
||||
return ModifyResult.Skipped;
|
||||
|
||||
pk.MetLevel = level;
|
||||
pk.MetLocation = location;
|
||||
pk.CurrentLevel = current;
|
||||
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the lowest current level for the Entity.
|
||||
/// </summary>
|
||||
public static ModifyResult SetMinimumCurrentLevel(BatchInfo info)
|
||||
{
|
||||
var result = EncounterSuggestion.IterateMinimumCurrentLevel(info.Entity, info.Legal);
|
||||
return result ? ModifyResult.Modified : ModifyResult.Skipped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the provided moves in a random order.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="moves">Moves to apply.</param>
|
||||
public static ModifyResult SetMoves(PKM pk, ReadOnlySpan<ushort> moves)
|
||||
{
|
||||
Span<ushort> current = stackalloc ushort[4];
|
||||
pk.GetMoves(current);
|
||||
if (current.SequenceEqual(moves))
|
||||
return ModifyResult.Skipped;
|
||||
pk.SetMoves(moves);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
public static ModifyResult SetEVs(PKM pk)
|
||||
{
|
||||
Span<int> evs = stackalloc int[6];
|
||||
EffortValues.SetMax(evs, pk);
|
||||
Span<int> current = stackalloc int[6];
|
||||
|
||||
pk.GetEVs(current);
|
||||
if (current.SequenceEqual(evs))
|
||||
return ModifyResult.Skipped;
|
||||
pk.SetEVs(evs);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the contests stats as requested.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="la">Legality Information matched to.</param>
|
||||
/// <param name="option">Option to apply with</param>
|
||||
public static ModifyResult SetContestStats(PKM pk, LegalityAnalysis la, ReadOnlySpan<char> option)
|
||||
{
|
||||
if (option.Length != 0 && option[EntityBatchEditor.CONST_SUGGEST.Length..] is not "0")
|
||||
pk.SetMaxContestStats(la.EncounterMatch, la.Info.EvoChainsAllGens);
|
||||
else
|
||||
pk.SetSuggestedContestStats(la.EncounterMatch, la.Info.EvoChainsAllGens);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
public static ModifyResult SetSuggestedCurrentFriendship(BatchInfo info)
|
||||
{
|
||||
var pk = info.Entity;
|
||||
var value = HistoryVerifier.GetSuggestedFriendshipCurrent(pk, info.Legality.EncounterMatch);
|
||||
if (pk.CurrentFriendship == value)
|
||||
return ModifyResult.Skipped;
|
||||
|
||||
pk.CurrentFriendship = value;
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
public static ModifyResult SetSuggestedOriginalTrainerFriendship(BatchInfo info)
|
||||
{
|
||||
var pk = info.Entity;
|
||||
var value = HistoryVerifier.GetSuggestedFriendshipOT(pk, info.Legality.EncounterMatch);
|
||||
if (pk.OriginalTrainerFriendship == value)
|
||||
return ModifyResult.Skipped;
|
||||
|
||||
pk.OriginalTrainerFriendship = value;
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
public static ModifyResult SetSuggestedHandlingTrainerFriendship(BatchInfo info)
|
||||
{
|
||||
var pk = info.Entity;
|
||||
var value = HistoryVerifier.GetSuggestedFriendshipHT(pk);
|
||||
if (pk.HandlingTrainerFriendship == value)
|
||||
return ModifyResult.Skipped;
|
||||
|
||||
pk.HandlingTrainerFriendship = value;
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <inheritdoc cref="ISuggestModification"/>
|
||||
public sealed class ComplexSuggestion(
|
||||
[ConstantExpected] string Keyword,
|
||||
Func<ReadOnlySpan<char>, ReadOnlySpan<char>, BatchInfo, ModifyResult> Action)
|
||||
: ISuggestModification
|
||||
{
|
||||
public readonly string Keyword = Keyword;
|
||||
public readonly Func<PKM, bool> Criteria = _ => true;
|
||||
public readonly Func<ReadOnlySpan<char>, ReadOnlySpan<char>, BatchInfo, ModifyResult> Action = Action;
|
||||
|
||||
public ComplexSuggestion(
|
||||
[ConstantExpected] string Keyword,
|
||||
Func<PKM, bool> criteria,
|
||||
Func<ReadOnlySpan<char>, ReadOnlySpan<char>, BatchInfo, ModifyResult> action) : this(Keyword, action)
|
||||
{
|
||||
Criteria = criteria;
|
||||
}
|
||||
|
||||
public bool IsMatch(ReadOnlySpan<char> name, ReadOnlySpan<char> value, BatchInfo info)
|
||||
{
|
||||
return name.SequenceEqual(Keyword) && Criteria(info.Entity);
|
||||
}
|
||||
|
||||
public ModifyResult Modify(ReadOnlySpan<char> name, ReadOnlySpan<char> value, BatchInfo info)
|
||||
{
|
||||
return Action(name, value, info);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Modifies a property to have a "correct" value based on derived legality.
|
||||
/// </summary>
|
||||
public interface ISuggestModification
|
||||
{
|
||||
public bool IsMatch(ReadOnlySpan<char> name, ReadOnlySpan<char> value, BatchInfo info);
|
||||
public ModifyResult Modify(ReadOnlySpan<char> name, ReadOnlySpan<char> value, BatchInfo info);
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <inheritdoc cref="ISuggestModification"/>
|
||||
/// <typeparam name="T">Specific (or not) type</typeparam>
|
||||
public sealed class TypeSuggestion<T>([ConstantExpected] string Keyword, Action<T> Action) : ISuggestModification
|
||||
{
|
||||
public readonly string Keyword = Keyword;
|
||||
public readonly Action<T, ReadOnlySpan<char>> Action = (pk, _) => Action(pk);
|
||||
public readonly Func<T, bool> Criteria = _ => true;
|
||||
|
||||
public TypeSuggestion([ConstantExpected] string Keyword, Func<T, bool> criteria, Action<T> action) : this(Keyword, action)
|
||||
{
|
||||
Criteria = criteria;
|
||||
}
|
||||
|
||||
public bool IsMatch(ReadOnlySpan<char> name, ReadOnlySpan<char> value, BatchInfo info)
|
||||
{
|
||||
return name.SequenceEqual(Keyword) && info.Entity is T;
|
||||
}
|
||||
|
||||
public ModifyResult Modify(ReadOnlySpan<char> name, ReadOnlySpan<char> value, BatchInfo info)
|
||||
{
|
||||
var pk = info.Entity;
|
||||
if (pk is not T x)
|
||||
return ModifyResult.Skipped;
|
||||
if (!Criteria(x))
|
||||
return ModifyResult.Skipped;
|
||||
Action(x, value);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for retrieving properties from a <see cref="T"/>.
|
||||
/// </summary>
|
||||
public interface IPropertyProvider<in T> where T : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to retrieve a property's value (as string) from an entity instance.
|
||||
/// </summary>
|
||||
/// <param name="obj">Entity to retrieve the property from.</param>
|
||||
/// <param name="prop">Property name to retrieve.</param>
|
||||
/// <param name="result">Property value as string.</param>
|
||||
/// <returns><see langword="true"/> if the property was found and retrieved successfully; otherwise, <see langword="false"/>.</returns>
|
||||
bool TryGetProperty(T obj, string prop, [NotNullWhen(true)] out string? result);
|
||||
}
|
||||
27
PKHeX.Core/Editing/Bulk/ModifyResult.cs
Normal file
27
PKHeX.Core/Editing/Bulk/ModifyResult.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Batch Editor Modification result for an individual <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
public enum ModifyResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="PKM"/> has invalid data and is not a suitable candidate for modification.
|
||||
/// </summary>
|
||||
Invalid,
|
||||
|
||||
/// <summary>
|
||||
/// An error was occurred while iterating modifications for this <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
Error,
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="PKM"/> was skipped due to a matching Filter.
|
||||
/// </summary>
|
||||
Filtered,
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="PKM"/> was modified.
|
||||
/// </summary>
|
||||
Modified,
|
||||
}
|
||||
123
PKHeX.Core/Editing/Bulk/StringInstruction.cs
Normal file
123
PKHeX.Core/Editing/Bulk/StringInstruction.cs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Batch Editing instruction
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Can be a filter (skip), or a modification instruction (modify)
|
||||
/// </remarks>
|
||||
/// <see cref="Exclude"/>
|
||||
/// <see cref="Require"/>
|
||||
/// <see cref="Apply"/>
|
||||
public sealed class StringInstruction
|
||||
{
|
||||
public string PropertyName { get; }
|
||||
public string PropertyValue { get; private set; }
|
||||
|
||||
/// <summary> True if ==, false if != </summary>
|
||||
public bool Evaluator { get; private init; }
|
||||
|
||||
public StringInstruction(string name, string value)
|
||||
{
|
||||
PropertyName = name;
|
||||
PropertyValue = value;
|
||||
}
|
||||
|
||||
public void SetScreenedValue(string[] arr)
|
||||
{
|
||||
int index = Array.IndexOf(arr, PropertyValue);
|
||||
PropertyValue = index > -1 ? index.ToString() : PropertyValue;
|
||||
}
|
||||
|
||||
public static readonly IReadOnlyList<char> Prefixes = new[] { Apply, Require, Exclude };
|
||||
private const char Exclude = '!';
|
||||
private const char Require = '=';
|
||||
private const char Apply = '.';
|
||||
private const char SplitRange = ',';
|
||||
|
||||
/// <summary>
|
||||
/// Character which divides a property and a value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example:
|
||||
/// =Species=1
|
||||
/// The second = is the split.
|
||||
/// </remarks>
|
||||
public const char SplitInstruction = '=';
|
||||
|
||||
// Extra Functionality
|
||||
private int RandomMinimum, RandomMaximum;
|
||||
public bool Random { get; private set; }
|
||||
public int RandomValue => Util.Rand.Next(RandomMinimum, RandomMaximum + 1);
|
||||
|
||||
public void SetRandRange(string pv)
|
||||
{
|
||||
string str = pv[1..];
|
||||
var split = str.Split(SplitRange);
|
||||
int.TryParse(split[0], out RandomMinimum);
|
||||
int.TryParse(split[1], out RandomMaximum);
|
||||
|
||||
if (RandomMinimum == RandomMaximum)
|
||||
{
|
||||
PropertyValue = RandomMinimum.ToString();
|
||||
Debug.WriteLine($"{PropertyName} randomization range Min/Max same?");
|
||||
}
|
||||
else
|
||||
{
|
||||
Random = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<StringInstruction> GetFilters(IEnumerable<string> lines)
|
||||
{
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.Length is 0 || line[0] is not (Exclude or Require))
|
||||
continue;
|
||||
|
||||
const int start = 1;
|
||||
var splitIndex = line.IndexOf(SplitInstruction, start);
|
||||
if (splitIndex == -1)
|
||||
continue;
|
||||
var noExtra = line.IndexOf(SplitInstruction, splitIndex + 1);
|
||||
if (noExtra != -1)
|
||||
continue;
|
||||
|
||||
var name = line.AsSpan(start, splitIndex - start);
|
||||
if (name.IsWhiteSpace())
|
||||
continue;
|
||||
|
||||
bool eval = line[0] == Require;
|
||||
var value = line[(splitIndex + 1)..];
|
||||
yield return new StringInstruction(name.ToString(), value) { Evaluator = eval };
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<StringInstruction> GetInstructions(IEnumerable<string> lines)
|
||||
{
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.Length is 0 || line[0] is not Apply)
|
||||
continue;
|
||||
|
||||
const int start = 1;
|
||||
var splitIndex = line.IndexOf(SplitInstruction, start);
|
||||
if (splitIndex == -1)
|
||||
continue;
|
||||
var noExtra = line.IndexOf(SplitInstruction, splitIndex + 1);
|
||||
if (noExtra != -1)
|
||||
continue;
|
||||
|
||||
var name = line.AsSpan(start, splitIndex - start);
|
||||
if (name.IsWhiteSpace())
|
||||
continue;
|
||||
|
||||
var value = line[(splitIndex + 1)..];
|
||||
yield return new StringInstruction(name.ToString(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
PKHeX.Core/Editing/Bulk/StringInstructionSet.cs
Normal file
38
PKHeX.Core/Editing/Bulk/StringInstructionSet.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Processes input of strings into a list of valid Filters and Instructions.
|
||||
/// </summary>
|
||||
public sealed class StringInstructionSet
|
||||
{
|
||||
public readonly IReadOnlyList<StringInstruction> Filters;
|
||||
public readonly IReadOnlyList<StringInstruction> Instructions;
|
||||
|
||||
private const string SetSeparator = ";";
|
||||
|
||||
public StringInstructionSet(IReadOnlyList<StringInstruction> filters, IReadOnlyList<StringInstruction> instructions)
|
||||
{
|
||||
Filters = filters;
|
||||
Instructions = instructions;
|
||||
}
|
||||
|
||||
public StringInstructionSet(ICollection<string> set)
|
||||
{
|
||||
Filters = StringInstruction.GetFilters(set).ToList();
|
||||
Instructions = StringInstruction.GetInstructions(set).ToList();
|
||||
}
|
||||
|
||||
public static IEnumerable<StringInstructionSet> GetBatchSets(IList<string> lines)
|
||||
{
|
||||
int start = 0;
|
||||
while (start < lines.Count)
|
||||
{
|
||||
var list = lines.Skip(start).TakeWhile(_ => !lines[start++].StartsWith(SetSeparator, StringComparison.Ordinal)).ToList();
|
||||
yield return new StringInstructionSet(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
122
PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs
Normal file
122
PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Modifications using <see cref="BatchInfo"/> legality.
|
||||
/// </summary>
|
||||
internal static class BatchModifications
|
||||
{
|
||||
private static bool IsAll(string p) => p.EndsWith("All", StringComparison.OrdinalIgnoreCase);
|
||||
private static bool IsNone(string p) => p.EndsWith("None", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static ModifyResult SetSuggestedRelearnData(BatchInfo info, string propValue)
|
||||
{
|
||||
var pk = info.Entity;
|
||||
if (pk is ITechRecord8 t)
|
||||
{
|
||||
t.ClearRecordFlags();
|
||||
if (IsAll(propValue))
|
||||
{
|
||||
t.SetRecordFlags(); // all
|
||||
}
|
||||
else if (!IsNone(propValue))
|
||||
{
|
||||
Span<ushort> moves = stackalloc ushort[4];
|
||||
pk.GetMoves(moves);
|
||||
t.SetRecordFlags(moves); // whatever fit the current moves
|
||||
}
|
||||
}
|
||||
|
||||
pk.SetRelearnMoves(info.SuggestedRelearn);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
public static ModifyResult SetSuggestedMasteryData(BatchInfo info, string propValue)
|
||||
{
|
||||
var pk = info.Entity;
|
||||
if (pk is not IMoveShop8Mastery t)
|
||||
return ModifyResult.Invalid;
|
||||
|
||||
t.ClearMoveShopFlags();
|
||||
if (IsNone(propValue))
|
||||
return ModifyResult.Modified;
|
||||
|
||||
var e = info.Legality.EncounterMatch;
|
||||
if (e is IMasteryInitialMoveShop8 enc)
|
||||
enc.SetInitialMastery(pk);
|
||||
if (IsAll(propValue))
|
||||
t.SetMoveShopFlagsAll(pk);
|
||||
else
|
||||
t.SetMoveShopFlags(pk);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
public static ModifyResult SetSuggestedRibbons(BatchInfo info, string value)
|
||||
{
|
||||
if (IsNone(value))
|
||||
RibbonApplicator.RemoveAllValidRibbons(info.Legality);
|
||||
else // All
|
||||
RibbonApplicator.SetAllValidRibbons(info.Legality);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
public static ModifyResult SetSuggestedMetData(BatchInfo info)
|
||||
{
|
||||
var pk = info.Entity;
|
||||
var encounter = EncounterSuggestion.GetSuggestedMetInfo(pk);
|
||||
if (encounter == null)
|
||||
return ModifyResult.Error;
|
||||
|
||||
int level = encounter.LevelMin;
|
||||
int location = encounter.Location;
|
||||
int minimumLevel = EncounterSuggestion.GetLowestLevel(pk, encounter.LevelMin);
|
||||
|
||||
pk.Met_Level = level;
|
||||
pk.Met_Location = location;
|
||||
pk.CurrentLevel = Math.Max(minimumLevel, level);
|
||||
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
public static ModifyResult SetMinimumCurrentLevel(BatchInfo info)
|
||||
{
|
||||
var result = EncounterSuggestion.IterateMinimumCurrentLevel(info.Entity, info.Legal);
|
||||
return result ? ModifyResult.Modified : ModifyResult.Filtered;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the provided moves in a random order.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="moves">Moves to apply.</param>
|
||||
public static ModifyResult SetMoves(PKM pk, ReadOnlySpan<ushort> moves)
|
||||
{
|
||||
pk.SetMoves(moves);
|
||||
pk.HealPP();
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
public static ModifyResult SetEVs(PKM pk)
|
||||
{
|
||||
Span<int> evs = stackalloc int[6];
|
||||
EffortValues.SetMax(evs, pk);
|
||||
pk.SetEVs(evs);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the contests stats as requested.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="la">Legality Information matched to.</param>
|
||||
/// <param name="option">Option to apply with</param>
|
||||
public static ModifyResult SetContestStats(PKM pk, LegalityAnalysis la, string option)
|
||||
{
|
||||
if (option.Length != 0 && option[BatchEditing.CONST_SUGGEST.Length..] is not "0")
|
||||
pk.SetMaxContestStats(la.EncounterMatch, la.Info.EvoChainsAllGens);
|
||||
else
|
||||
pk.SetSuggestedContestStats(la.EncounterMatch, la.Info.EvoChainsAllGens);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
}
|
||||
37
PKHeX.Core/Editing/Bulk/Suggestion/ComplexSuggestion.cs
Normal file
37
PKHeX.Core/Editing/Bulk/Suggestion/ComplexSuggestion.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <inheritdoc cref="ISuggestModification"/>
|
||||
public sealed class ComplexSuggestion : ISuggestModification
|
||||
{
|
||||
public readonly string Keyword;
|
||||
public readonly Func<PKM, bool> Criteria = _ => true;
|
||||
public readonly Func<string, string, BatchInfo, ModifyResult> Action;
|
||||
|
||||
public ComplexSuggestion(
|
||||
string keyword,
|
||||
Func<PKM, bool> criteria,
|
||||
Func<string, string, BatchInfo, ModifyResult> action) : this(keyword, action)
|
||||
{
|
||||
Criteria = criteria;
|
||||
}
|
||||
|
||||
public ComplexSuggestion(
|
||||
string keyword,
|
||||
Func<string, string, BatchInfo, ModifyResult> action)
|
||||
{
|
||||
Keyword = keyword;
|
||||
Action = action;
|
||||
}
|
||||
|
||||
public bool IsMatch(string name, string value, BatchInfo info)
|
||||
{
|
||||
return name == Keyword && Criteria(info.Entity);
|
||||
}
|
||||
|
||||
public ModifyResult Modify(string name, string value, BatchInfo info)
|
||||
{
|
||||
return Action(name, value, info);
|
||||
}
|
||||
}
|
||||
10
PKHeX.Core/Editing/Bulk/Suggestion/ISuggestModification.cs
Normal file
10
PKHeX.Core/Editing/Bulk/Suggestion/ISuggestModification.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Modifies a property to have a "correct" value based on derived legality.
|
||||
/// </summary>
|
||||
public interface ISuggestModification
|
||||
{
|
||||
public bool IsMatch(string name, string value, BatchInfo info);
|
||||
public ModifyResult Modify(string name, string value, BatchInfo info);
|
||||
}
|
||||
39
PKHeX.Core/Editing/Bulk/Suggestion/TypeSuggestion.cs
Normal file
39
PKHeX.Core/Editing/Bulk/Suggestion/TypeSuggestion.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <inheritdoc cref="ISuggestModification"/>
|
||||
/// <typeparam name="T">Specific (or not) type</typeparam>
|
||||
public sealed class TypeSuggestion<T> : ISuggestModification
|
||||
{
|
||||
public readonly string Keyword;
|
||||
public readonly Action<T, string> Action;
|
||||
public readonly Func<T, bool> Criteria = _ => true;
|
||||
|
||||
public TypeSuggestion(string keyword, Action<T> action)
|
||||
{
|
||||
Keyword = keyword;
|
||||
Action = (pk, _) => action(pk);
|
||||
}
|
||||
|
||||
public TypeSuggestion(string keyword, Func<T, bool> criteria, Action<T> action) : this(keyword, action)
|
||||
{
|
||||
Criteria = criteria;
|
||||
}
|
||||
|
||||
public bool IsMatch(string name, string value, BatchInfo info)
|
||||
{
|
||||
return name == Keyword && info.Entity is T;
|
||||
}
|
||||
|
||||
public ModifyResult Modify(string name, string value, BatchInfo info)
|
||||
{
|
||||
var pk = info.Entity;
|
||||
if (pk is not T x)
|
||||
return ModifyResult.Invalid;
|
||||
if (!Criteria(x))
|
||||
return ModifyResult.Invalid;
|
||||
Action(x, value);
|
||||
return ModifyResult.Modified;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ namespace PKHeX.Core;
|
|||
public static class CommonEdits
|
||||
{
|
||||
/// <summary>
|
||||
/// Setting which enables/disables automatic manipulation of <see cref="IAppliedMarkings"/> when importing from a <see cref="IBattleTemplate"/>.
|
||||
/// Setting which enables/disables automatic manipulation of <see cref="PKM.MarkValue"/> when importing from a <see cref="IBattleTemplate"/>.
|
||||
/// </summary>
|
||||
public static bool ShowdownSetIVMarkings { get; set; } = true;
|
||||
|
||||
|
|
@ -17,478 +17,431 @@ public static class CommonEdits
|
|||
/// </summary>
|
||||
public static bool ShowdownSetBehaviorNature { get; set; }
|
||||
|
||||
extension(PKM pk)
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Nickname"/> to the provided value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="nick"><see cref="PKM.Nickname"/> to set. If no nickname is provided, the <see cref="PKM.Nickname"/> is set to the default value for its current language and format.</param>
|
||||
public static void SetNickname(this PKM pk, string nick)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Nickname"/> to the provided value.
|
||||
/// </summary>
|
||||
/// <param name="nick"><see cref="PKM.Nickname"/> to set. If no nickname is provided, the <see cref="PKM.Nickname"/> is set to the default value for its current language and format.</param>
|
||||
public void SetNickname(string nick)
|
||||
if (nick.Length == 0)
|
||||
{
|
||||
if (nick.Length == 0)
|
||||
{
|
||||
pk.ClearNickname();
|
||||
return;
|
||||
}
|
||||
|
||||
pk.PrepareNickname();
|
||||
pk.Nickname = nick;
|
||||
pk.IsNicknamed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Nickname"/> to the default value of the current species and language.
|
||||
/// </summary>
|
||||
/// <returns>Default nickname for the current species and language.</returns>
|
||||
public string ClearNickname()
|
||||
{
|
||||
pk.IsNicknamed = false;
|
||||
string nick = SpeciesName.GetSpeciesNameGeneration(pk.Species, pk.Language, pk.Format);
|
||||
pk.SetString(pk.NicknameTrash, nick, nick.Length, StringConverterOption.None);
|
||||
if (pk is GBPKM pk12)
|
||||
pk12.SetNotNicknamed();
|
||||
return nick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Ability"/> value by sanity checking the provided <see cref="PKM.Ability"/> against the possible pool of abilities.
|
||||
/// </summary>
|
||||
/// <param name="abilityID">Desired <see cref="Ability"/> value to set.</param>
|
||||
public void SetAbility(int abilityID)
|
||||
{
|
||||
if (abilityID < 0)
|
||||
return;
|
||||
var index = pk.PersonalInfo.GetIndexOfAbility(abilityID);
|
||||
if (index < 0)
|
||||
return; // leave original value
|
||||
pk.SetAbilityIndex(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Ability"/> value based on the provided ability index (0-2)
|
||||
/// </summary>
|
||||
/// <param name="index">Desired <see cref="PKM.AbilityNumber"/> (shifted by 1) to set.</param>
|
||||
public void SetAbilityIndex(int index)
|
||||
{
|
||||
if (pk is PK5 pk5 && index == 2)
|
||||
pk5.HiddenAbility = true;
|
||||
else if (pk.Format <= 5)
|
||||
pk.PID = EntityPID.GetRandomPID(Util.Rand, pk.Species, pk.Gender, pk.Version, pk.Nature, pk.Form, (uint)(index * 0x10001));
|
||||
pk.RefreshAbility(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a Random <see cref="PKM.EncryptionConstant"/> value. The <see cref="PKM.EncryptionConstant"/> is not updated if the value should match the <see cref="PKM.PID"/> instead.
|
||||
/// </summary>
|
||||
/// <remarks>Accounts for Wurmple evolutions.</remarks>
|
||||
public void SetRandomEC()
|
||||
{
|
||||
var gen = pk.Generation;
|
||||
if (gen is 3 or 4 or 5)
|
||||
{
|
||||
pk.EncryptionConstant = pk.PID;
|
||||
return;
|
||||
}
|
||||
pk.EncryptionConstant = GetComplicatedEC(pk);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.IsShiny"/> derived value.
|
||||
/// </summary>
|
||||
/// <param name="shiny">Desired <see cref="PKM.IsShiny"/> state to set.</param>
|
||||
public bool SetIsShiny(bool shiny) => shiny ? SetShiny(pk) : pk.SetUnshiny();
|
||||
|
||||
/// <summary>
|
||||
/// Makes a <see cref="PKM"/> shiny.
|
||||
/// </summary>
|
||||
/// <param name="type">Shiny type to force. Only use Always* or Random</param>
|
||||
/// <returns>Returns true if the <see cref="PKM"/> data was modified.</returns>
|
||||
public bool SetShiny(Shiny type = Shiny.Random)
|
||||
{
|
||||
if (pk.IsShiny && type.IsValid(pk))
|
||||
return false;
|
||||
|
||||
if (type == Shiny.Random || pk.FatefulEncounter || pk.Version == GameVersion.GO || pk.Format <= 2)
|
||||
{
|
||||
pk.SetShiny();
|
||||
return true;
|
||||
}
|
||||
|
||||
do { pk.SetShiny(); }
|
||||
while (!type.IsValid(pk));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes a <see cref="PKM"/> not-shiny.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the <see cref="PKM"/> data was modified.</returns>
|
||||
public bool SetUnshiny()
|
||||
{
|
||||
if (!pk.IsShiny)
|
||||
return false;
|
||||
|
||||
pk.SetPIDGender(pk.Gender);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Nature"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Nature"/> value.
|
||||
/// </summary>
|
||||
/// <param name="nature">Desired <see cref="PKM.Nature"/> value to set.</param>
|
||||
public void SetNature(Nature nature)
|
||||
{
|
||||
if (!nature.IsFixed)
|
||||
nature = 0; // default valid
|
||||
|
||||
var format = pk.Format;
|
||||
if (format >= 8)
|
||||
pk.StatNature = nature;
|
||||
else if (format is 3 or 4)
|
||||
pk.SetPIDNature(nature);
|
||||
else
|
||||
pk.Nature = nature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies <see cref="IBattleTemplate"/> details to the <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
/// <param name="set"><see cref="IBattleTemplate"/> details to copy from.</param>
|
||||
public void ApplySetDetails(IBattleTemplate set)
|
||||
{
|
||||
pk.Species = Math.Min(pk.MaxSpeciesID, set.Species);
|
||||
pk.Form = set.Form;
|
||||
|
||||
ReadOnlySpan<ushort> moves = set.Moves;
|
||||
if (moves[0] != 0)
|
||||
pk.SetMoves(moves, true);
|
||||
if (Legal.IsPPUpAvailable(pk))
|
||||
pk.SetMaximumPPUps(moves);
|
||||
|
||||
pk.ApplyHeldItem(set.HeldItem, set.Context);
|
||||
pk.CurrentLevel = set.Level;
|
||||
pk.CurrentFriendship = set.Friendship;
|
||||
|
||||
ReadOnlySpan<int> ivs = set.IVs;
|
||||
ReadOnlySpan<int> evs = set.EVs;
|
||||
pk.SetIVs(ivs);
|
||||
|
||||
if (pk is GBPKM gb)
|
||||
{
|
||||
// In Generation 1/2 Format sets, when IVs are not specified with a Hidden Power set, we might not have the hidden power type.
|
||||
// Under this scenario, just force the Hidden Power type.
|
||||
if (moves.Contains((ushort)Move.HiddenPower) && gb.HPType != set.HiddenPowerType)
|
||||
{
|
||||
if (ivs.ContainsAny(30, 31))
|
||||
gb.SetHiddenPower(set.HiddenPowerType);
|
||||
}
|
||||
|
||||
// In Generation 1/2 Format sets, when EVs are not specified at all, it implies maximum EVs instead!
|
||||
// Under this scenario, just apply maximum EVs (65535).
|
||||
if (!evs.ContainsAnyExcept(0))
|
||||
gb.MaxEVs();
|
||||
else if (evs.ContainsAnyExceptInRange(0, 252)) // Any specified above 252
|
||||
gb.SetEVs(evs);
|
||||
else
|
||||
gb.SetSqrtEVs(evs);
|
||||
}
|
||||
else
|
||||
{
|
||||
pk.SetEVs(evs);
|
||||
}
|
||||
|
||||
// IVs have no side effects such as hidden power type in gen 8
|
||||
// therefore all specified IVs are deliberate and should not be Hyper Trained for Pokémon met in gen 8
|
||||
if (pk.Generation < 8)
|
||||
pk.SetSuggestedHyperTrainingData(ivs);
|
||||
|
||||
if (ShowdownSetIVMarkings)
|
||||
pk.SetMarkings();
|
||||
|
||||
pk.SetNickname(set.Nickname);
|
||||
pk.SetSaneGender(set.Gender);
|
||||
|
||||
if (pk.Format >= 3)
|
||||
{
|
||||
pk.SetAbility(set.Ability);
|
||||
pk.SetNature(set.Nature);
|
||||
}
|
||||
|
||||
pk.SetIsShiny(set.Shiny);
|
||||
pk.SetRandomEC();
|
||||
|
||||
if (pk is IAwakened a)
|
||||
{
|
||||
a.SetSuggestedAwakenedValues(pk);
|
||||
if (pk is PB7 b)
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
b.SetEV(i, 0);
|
||||
b.ResetCalculatedValues();
|
||||
}
|
||||
}
|
||||
if (pk is IGanbaru g)
|
||||
g.SetSuggestedGanbaruValues(pk);
|
||||
|
||||
if (pk is IGigantamax c)
|
||||
c.CanGigantamax = set.CanGigantamax;
|
||||
if (pk is IDynamaxLevel d)
|
||||
d.DynamaxLevel = d.GetSuggestedDynamaxLevel(pk, requested: set.DynamaxLevel);
|
||||
if (pk is ITeraType tera)
|
||||
{
|
||||
var type = set.TeraType == MoveType.Any ? (MoveType)pk.PersonalInfo.Type1 : set.TeraType;
|
||||
tera.SetTeraType(type);
|
||||
}
|
||||
|
||||
if (pk is IMoveShop8Mastery s)
|
||||
s.SetMoveShopFlags(set.Moves, pk);
|
||||
|
||||
if (ShowdownSetBehaviorNature && pk.Format >= 8)
|
||||
pk.Nature = pk.StatNature;
|
||||
|
||||
var legal = new LegalityAnalysis(pk);
|
||||
if (pk is ITechRecord t)
|
||||
{
|
||||
t.ClearRecordFlags();
|
||||
t.SetRecordFlags(set.Moves, legal.Info.EvoChainsAllGens.Get(pk.Context));
|
||||
}
|
||||
|
||||
if (pk is IPlusRecord plus && pk.PersonalInfo is IPermitPlus permit)
|
||||
{
|
||||
plus.ClearPlusFlags(permit.PlusCountTotal);
|
||||
plus.SetPlusFlags(permit, legal, true, true);
|
||||
}
|
||||
|
||||
if (legal.Parsed && !MoveResult.AllValid(legal.Info.Relearn))
|
||||
pk.SetRelearnMoves(legal);
|
||||
pk.ResetPartyStats();
|
||||
pk.RefreshChecksum();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.HeldItem"/> value depending on the current format and the provided item index & format.
|
||||
/// </summary>
|
||||
/// <param name="item">Held Item to apply</param>
|
||||
/// <param name="context">Format required for importing</param>
|
||||
public void ApplyHeldItem(int item, EntityContext context)
|
||||
{
|
||||
item = ItemConverter.GetItemForFormat(item, context, pk.Context);
|
||||
pk.HeldItem = ((uint)item > pk.MaxItemID) ? 0 : item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets one of the <see cref="EffortValues"/> based on its index within the array.
|
||||
/// </summary>
|
||||
/// <param name="index">Index to set to</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
public int SetEV(int index, int value) => index switch
|
||||
{
|
||||
0 => pk.EV_HP = value,
|
||||
1 => pk.EV_ATK = value,
|
||||
2 => pk.EV_DEF = value,
|
||||
3 => pk.EV_SPE = value,
|
||||
4 => pk.EV_SPA = value,
|
||||
5 => pk.EV_SPD = value,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Sets one of the <see cref="PKM.IVs"/> based on its index within the array.
|
||||
/// </summary>
|
||||
/// <param name="index">Index to set to</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
public int SetIV(int index, int value) => index switch
|
||||
{
|
||||
0 => pk.IV_HP = value,
|
||||
1 => pk.IV_ATK = value,
|
||||
2 => pk.IV_DEF = value,
|
||||
3 => pk.IV_SPE = value,
|
||||
4 => pk.IV_SPA = value,
|
||||
5 => pk.IV_SPD = value,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the highest value the provided <see cref="EffortValues"/> index can be while considering others.
|
||||
/// </summary>
|
||||
/// <param name="index">Index to fetch for</param>
|
||||
/// <returns>Highest value the value can be.</returns>
|
||||
public int GetMaximumEV(int index)
|
||||
{
|
||||
if (pk.Format < 3)
|
||||
return EffortValues.Max12;
|
||||
|
||||
var sum = pk.EVTotal - pk.GetEV(index);
|
||||
int remaining = EffortValues.Max510 - sum;
|
||||
return Math.Clamp(remaining, 0, EffortValues.Max252);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the highest value the provided <see cref="PKM.IVs"/>.
|
||||
/// </summary>
|
||||
/// <param name="index">Index to fetch for</param>
|
||||
/// <param name="allow30">Causes the returned value to be dropped down -1 if the value is already at a maximum.</param>
|
||||
/// <returns>Highest value the value can be.</returns>
|
||||
public int GetMaximumIV(int index, bool allow30 = false)
|
||||
{
|
||||
if (pk.GetIV(index) == pk.MaxIV && allow30)
|
||||
return pk.MaxIV - 1;
|
||||
return pk.MaxIV;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game.
|
||||
/// </summary>
|
||||
/// <param name="tr">Trainer to force hatch with if Version is not currently set.</param>
|
||||
/// <param name="reHatch">Re-hatch already hatched <see cref="PKM"/> inputs</param>
|
||||
public void ForceHatchPKM(ITrainerInfo? tr = null, bool reHatch = false)
|
||||
{
|
||||
if (!pk.IsEgg && !reHatch)
|
||||
return;
|
||||
pk.IsEgg = false;
|
||||
pk.ClearNickname();
|
||||
pk.OriginalTrainerFriendship = Math.Min(pk.OriginalTrainerFriendship, EggStateLegality.GetEggHatchFriendship(pk.Context));
|
||||
if (pk.IsTradedEgg)
|
||||
pk.EggLocation = pk.MetLocation;
|
||||
if (pk.Version == 0)
|
||||
pk.Version = EggStateLegality.GetEggHatchVersion(pk, tr?.Version ?? RecentTrainerCache.Version);
|
||||
var loc = EncounterSuggestion.GetSuggestedEggMetLocation(pk);
|
||||
if (loc != EncounterSuggestion.LocationNone)
|
||||
pk.MetLocation = loc;
|
||||
if (pk.Format >= 4)
|
||||
pk.MetDate = EncounterDate.GetDate(pk.Context.Console);
|
||||
if (pk.Gen6)
|
||||
pk.SetHatchMemory6();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game.
|
||||
/// </summary>
|
||||
/// <param name="origin">Game the egg originated from</param>
|
||||
/// <param name="dest">Game the egg is currently present on</param>
|
||||
public void SetEggMetData(GameVersion origin, GameVersion dest)
|
||||
{
|
||||
if (pk.Format < 4)
|
||||
return;
|
||||
|
||||
var console = pk.Context.Console;
|
||||
var date = EncounterDate.GetDate(console);
|
||||
var today = pk.MetDate = date;
|
||||
bool traded = origin != dest;
|
||||
pk.EggLocation = EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(pk.Generation, origin, traded);
|
||||
pk.EggMetDate = today;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximizes the <see cref="PKM.CurrentFriendship"/>. If the <see cref="PKM.IsEgg"/>, the hatch counter is set to 1.
|
||||
/// </summary>
|
||||
public void MaximizeFriendship()
|
||||
{
|
||||
if (pk.IsEgg)
|
||||
pk.OriginalTrainerFriendship = 1;
|
||||
else
|
||||
pk.CurrentFriendship = byte.MaxValue;
|
||||
if (pk is ICombatPower pb)
|
||||
pb.ResetCP();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximizes the <see cref="PKM.CurrentLevel"/>. If the <see cref="PKM.IsEgg"/>, the <see cref="PKM"/> is ignored.
|
||||
/// </summary>
|
||||
public void MaximizeLevel()
|
||||
{
|
||||
if (pk.IsEgg)
|
||||
return;
|
||||
pk.CurrentLevel = Experience.MaxLevel;
|
||||
if (pk is ICombatPower pb)
|
||||
pb.ResetCP();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Nickname"/> to its default value.
|
||||
/// </summary>
|
||||
/// <param name="la">Precomputed optional</param>
|
||||
public void SetDefaultNickname(LegalityAnalysis la)
|
||||
{
|
||||
if (la is { Parsed: true, EncounterOriginal: IFixedNickname {IsFixedNickname: true} t })
|
||||
pk.SetNickname(t.GetNickname(pk.Language));
|
||||
else
|
||||
pk.ClearNickname();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Nickname"/> to its default value.
|
||||
/// </summary>
|
||||
public void SetDefaultNickname() => pk.SetDefaultNickname(new LegalityAnalysis(pk));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Location Name for the <see cref="PKM"/>
|
||||
/// </summary>
|
||||
/// <param name="eggmet">Location requested is the egg obtained location, not met location.</param>
|
||||
/// <returns>Location string</returns>
|
||||
public string GetLocationString(bool eggmet)
|
||||
{
|
||||
if (pk.Format < 2)
|
||||
return string.Empty;
|
||||
|
||||
ushort location = eggmet ? pk.EggLocation : pk.MetLocation;
|
||||
return GameInfo.GetLocationName(eggmet, location, pk.Format, pk.Generation, pk.Version);
|
||||
return;
|
||||
}
|
||||
pk.IsNicknamed = true;
|
||||
pk.Nickname = nick;
|
||||
}
|
||||
|
||||
// Extensions
|
||||
|
||||
public const char OptionNone = '\0';
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="PKM.EncryptionConstant"/> to match the requested option.
|
||||
/// Clears the <see cref="PKM.Nickname"/> to the default value.
|
||||
/// </summary>
|
||||
public static uint GetComplicatedEC(ISpeciesForm pk, char option = OptionNone)
|
||||
/// <param name="pk"></param>
|
||||
public static string ClearNickname(this PKM pk)
|
||||
{
|
||||
var species = pk.Species;
|
||||
var form = pk.Form;
|
||||
return GetComplicatedEC(species, form, option);
|
||||
pk.IsNicknamed = false;
|
||||
string nick = SpeciesName.GetSpeciesNameGeneration(pk.Species, pk.Language, pk.Format);
|
||||
pk.Nickname = nick;
|
||||
if (pk is GBPKM pk12)
|
||||
pk12.SetNotNicknamed();
|
||||
return nick;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetComplicatedEC(ISpeciesForm,char)"/>
|
||||
public static uint GetComplicatedEC(ushort species, byte form, char option = OptionNone)
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Ability"/> value by sanity checking the provided <see cref="PKM.Ability"/> against the possible pool of abilities.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="abil">Desired <see cref="PKM.Ability"/> to set.</param>
|
||||
public static void SetAbility(this PKM pk, int abil)
|
||||
{
|
||||
var rng = Util.Rand;
|
||||
uint rand = rng.Rand32();
|
||||
uint mod, noise;
|
||||
if (species is >= (int)Species.Wurmple and <= (int)Species.Dustox)
|
||||
{
|
||||
mod = 10;
|
||||
bool lower = option is '0' or 'B' or 'S' || WurmpleUtil.GetWurmpleEvoGroup(species) == 0;
|
||||
noise = (lower ? 0u : 5u) + (uint)rng.Next(0, 5);
|
||||
}
|
||||
else if (species is (int)Species.Dunsparce or (int)Species.Dudunsparce or (int)Species.Tandemaus or (int)Species.Maushold)
|
||||
{
|
||||
mod = 100;
|
||||
noise = species switch
|
||||
{
|
||||
// Retain requisite correlation to allow for evolving into this species too.
|
||||
(int)Species.Dudunsparce => form == 1 ? 0 : (uint)rng.Next(1, 100), // 3 Segment
|
||||
(int)Species.Maushold => form == 0 ? 0 : (uint)rng.Next(1, 100), // Family of 3
|
||||
if (abil < 0)
|
||||
return;
|
||||
var index = pk.PersonalInfo.GetIndexOfAbility(abil);
|
||||
index = Math.Max(0, index);
|
||||
pk.SetAbilityIndex(index);
|
||||
}
|
||||
|
||||
// Otherwise, check if one is preferred, and if not, just make it the more common outcome.
|
||||
_ => option switch
|
||||
{
|
||||
'0' or '3' => 0u,
|
||||
_ => (uint)rng.Next(1, 100),
|
||||
},
|
||||
};
|
||||
}
|
||||
else if (option is >= '0' and <= '5')
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Ability"/> value based on the provided ability index (0-2)
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="index">Desired <see cref="PKM.AbilityNumber"/> (shifted by 1) to set.</param>
|
||||
public static void SetAbilityIndex(this PKM pk, int index)
|
||||
{
|
||||
if (pk is PK5 pk5 && index == 2)
|
||||
pk5.HiddenAbility = true;
|
||||
else if (pk.Format <= 5)
|
||||
pk.PID = EntityPID.GetRandomPID(Util.Rand, pk.Species, pk.Gender, pk.Version, pk.Nature, pk.Form, (uint)(index * 0x10001));
|
||||
pk.RefreshAbility(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a Random <see cref="PKM.EncryptionConstant"/> value. The <see cref="PKM.EncryptionConstant"/> is not updated if the value should match the <see cref="PKM.PID"/> instead.
|
||||
/// </summary>
|
||||
/// <remarks>Accounts for Wurmple evolutions.</remarks>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
public static void SetRandomEC(this PKM pk)
|
||||
{
|
||||
int gen = pk.Generation;
|
||||
if (gen is 3 or 4 or 5)
|
||||
{
|
||||
mod = 6;
|
||||
noise = (uint)(option - '0');
|
||||
pk.EncryptionConstant = pk.PID;
|
||||
return;
|
||||
}
|
||||
|
||||
int wIndex = WurmpleUtil.GetWurmpleEvoGroup(pk.Species);
|
||||
if (wIndex != -1)
|
||||
{
|
||||
pk.EncryptionConstant = WurmpleUtil.GetWurmpleEncryptionConstant(wIndex);
|
||||
return;
|
||||
}
|
||||
pk.EncryptionConstant = Util.Rand32();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.IsShiny"/> derived value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="shiny">Desired <see cref="PKM.IsShiny"/> state to set.</param>
|
||||
public static bool SetIsShiny(this PKM pk, bool shiny) => shiny ? SetShiny(pk) : pk.SetUnshiny();
|
||||
|
||||
/// <summary>
|
||||
/// Makes a <see cref="PKM"/> shiny.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="type">Shiny type to force. Only use Always* or Random</param>
|
||||
/// <returns>Returns true if the <see cref="PKM"/> data was modified.</returns>
|
||||
public static bool SetShiny(PKM pk, Shiny type = Shiny.Random)
|
||||
{
|
||||
if (pk.IsShiny && type.IsValid(pk))
|
||||
return false;
|
||||
|
||||
if (type == Shiny.Random || pk.FatefulEncounter || pk.Version == (int)GameVersion.GO || pk.Format <= 2)
|
||||
{
|
||||
pk.SetShiny();
|
||||
return true;
|
||||
}
|
||||
|
||||
do { pk.SetShiny(); }
|
||||
while (!type.IsValid(pk));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes a <see cref="PKM"/> not-shiny.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <returns>Returns true if the <see cref="PKM"/> data was modified.</returns>
|
||||
public static bool SetUnshiny(this PKM pk)
|
||||
{
|
||||
if (!pk.IsShiny)
|
||||
return false;
|
||||
|
||||
pk.SetPIDGender(pk.Gender);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Nature"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Nature"/> value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="nature">Desired <see cref="PKM.Nature"/> value to set.</param>
|
||||
public static void SetNature(this PKM pk, int nature)
|
||||
{
|
||||
var value = Math.Min((int)Nature.Quirky, Math.Max((int)Nature.Hardy, nature));
|
||||
var format = pk.Format;
|
||||
if (format >= 8)
|
||||
pk.StatNature = value;
|
||||
else if (format is 3 or 4)
|
||||
pk.SetPIDNature(value);
|
||||
else
|
||||
pk.Nature = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies <see cref="IBattleTemplate"/> details to the <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="Set"><see cref="IBattleTemplate"/> details to copy from.</param>
|
||||
public static void ApplySetDetails(this PKM pk, IBattleTemplate Set)
|
||||
{
|
||||
pk.Species = Math.Min(pk.MaxSpeciesID, Set.Species);
|
||||
pk.Form = Set.Form;
|
||||
if (Set.Moves[0] != 0)
|
||||
pk.SetMoves(Set.Moves, true);
|
||||
pk.ApplyHeldItem(Set.HeldItem, Set.Context);
|
||||
pk.CurrentLevel = Set.Level;
|
||||
pk.CurrentFriendship = Set.Friendship;
|
||||
pk.SetIVs(Set.IVs);
|
||||
|
||||
if (pk is GBPKM gb)
|
||||
{
|
||||
// In Generation 1/2 Format sets, when IVs are not specified with a Hidden Power set, we might not have the hidden power type.
|
||||
// Under this scenario, just force the Hidden Power type.
|
||||
if (Array.IndexOf(Set.Moves, (ushort)Move.HiddenPower) != -1 && pk.HPType != Set.HiddenPowerType)
|
||||
{
|
||||
if (Array.Exists(Set.IVs, static iv => iv >= 30))
|
||||
pk.SetHiddenPower(Set.HiddenPowerType);
|
||||
}
|
||||
|
||||
// In Generation 1/2 Format sets, when EVs are not specified at all, it implies maximum EVs instead!
|
||||
// Under this scenario, just apply maximum EVs (65535).
|
||||
if (Array.TrueForAll(Set.EVs, static ev => ev == 0))
|
||||
gb.MaxEVs();
|
||||
else
|
||||
pk.SetEVs(Set.EVs);
|
||||
}
|
||||
else
|
||||
{
|
||||
return rand;
|
||||
pk.SetEVs(Set.EVs);
|
||||
}
|
||||
return unchecked(rand - (rand % mod) + noise);
|
||||
|
||||
// IVs have no side effects such as hidden power type in gen 8
|
||||
// therefore all specified IVs are deliberate and should not be Hyper Trained for pokemon met in gen 8
|
||||
if (!pk.Gen8)
|
||||
pk.SetSuggestedHyperTrainingData(Set.IVs);
|
||||
|
||||
if (ShowdownSetIVMarkings)
|
||||
pk.SetMarkings();
|
||||
|
||||
pk.SetNickname(Set.Nickname);
|
||||
pk.SetSaneGender(Set.Gender);
|
||||
|
||||
if (Legal.IsPPUpAvailable(pk))
|
||||
pk.SetMaximumPPUps(Set.Moves);
|
||||
|
||||
if (pk.Format >= 3)
|
||||
{
|
||||
pk.SetAbility(Set.Ability);
|
||||
pk.SetNature(Set.Nature);
|
||||
}
|
||||
|
||||
pk.SetIsShiny(Set.Shiny);
|
||||
pk.SetRandomEC();
|
||||
|
||||
if (pk is IAwakened a)
|
||||
{
|
||||
a.SetSuggestedAwakenedValues(pk);
|
||||
if (pk is PB7 b)
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
b.SetEV(i, 0);
|
||||
b.ResetCalculatedValues();
|
||||
}
|
||||
}
|
||||
if (pk is IGanbaru g)
|
||||
g.SetSuggestedGanbaruValues(pk);
|
||||
|
||||
if (pk is IGigantamax c)
|
||||
c.CanGigantamax = Set.CanGigantamax;
|
||||
if (pk is IDynamaxLevel d)
|
||||
d.DynamaxLevel = d.GetSuggestedDynamaxLevel(pk, requested: Set.DynamaxLevel);
|
||||
|
||||
if (pk is ITechRecord8 t)
|
||||
{
|
||||
t.ClearRecordFlags();
|
||||
t.SetRecordFlags(Set.Moves);
|
||||
}
|
||||
if (pk is IMoveShop8Mastery s)
|
||||
s.SetMoveShopFlags(Set.Moves, pk);
|
||||
|
||||
if (ShowdownSetBehaviorNature && pk.Format >= 8)
|
||||
pk.Nature = pk.StatNature;
|
||||
|
||||
var legal = new LegalityAnalysis(pk);
|
||||
if (legal.Parsed && !MoveResult.AllValid(legal.Info.Relearn))
|
||||
pk.SetRelearnMoves(legal.GetSuggestedRelearnMoves());
|
||||
pk.ResetPartyStats();
|
||||
pk.RefreshChecksum();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.HeldItem"/> value depending on the current format and the provided item index & format.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="item">Held Item to apply</param>
|
||||
/// <param name="context">Format required for importing</param>
|
||||
public static void ApplyHeldItem(this PKM pk, int item, EntityContext context)
|
||||
{
|
||||
item = ItemConverter.GetItemForFormat(item, context, pk.Context);
|
||||
pk.HeldItem = ((uint)item > pk.MaxItemID) ? 0 : item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets one of the <see cref="EffortValues"/> based on its index within the array.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="index">Index to set to</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
public static int SetEV(this PKM pk, int index, int value) => index switch
|
||||
{
|
||||
0 => pk.EV_HP = value,
|
||||
1 => pk.EV_ATK = value,
|
||||
2 => pk.EV_DEF = value,
|
||||
3 => pk.EV_SPE = value,
|
||||
4 => pk.EV_SPA = value,
|
||||
5 => pk.EV_SPD = value,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Sets one of the <see cref="PKM.IVs"/> based on its index within the array.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="index">Index to set to</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
public static int SetIV(this PKM pk, int index, int value) => index switch
|
||||
{
|
||||
0 => pk.IV_HP = value,
|
||||
1 => pk.IV_ATK = value,
|
||||
2 => pk.IV_DEF = value,
|
||||
3 => pk.IV_SPE = value,
|
||||
4 => pk.IV_SPA = value,
|
||||
5 => pk.IV_SPD = value,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the highest value the provided <see cref="EffortValues"/> index can be while considering others.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="index">Index to fetch for</param>
|
||||
/// <returns>Highest value the value can be.</returns>
|
||||
public static int GetMaximumEV(this PKM pk, int index)
|
||||
{
|
||||
if (pk.Format < 3)
|
||||
return ushort.MaxValue;
|
||||
|
||||
var sum = pk.EVTotal - pk.GetEV(index);
|
||||
int remaining = 510 - sum;
|
||||
return Math.Min(Math.Max(remaining, 0), 252);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the highest value the provided <see cref="PKM.IVs"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="index">Index to fetch for</param>
|
||||
/// <param name="allow30">Causes the returned value to be dropped down -1 if the value is already at a maximum.</param>
|
||||
/// <returns>Highest value the value can be.</returns>
|
||||
public static int GetMaximumIV(this PKM pk, int index, bool allow30 = false)
|
||||
{
|
||||
if (pk.GetIV(index) == pk.MaxIV && allow30)
|
||||
return pk.MaxIV - 1;
|
||||
return pk.MaxIV;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game.
|
||||
/// </summary>
|
||||
/// <param name="pk">PKM to apply hatch details to</param>
|
||||
/// <param name="reHatch">Re-hatch already hatched <see cref="PKM"/> inputs</param>
|
||||
public static void ForceHatchPKM(this PKM pk, bool reHatch = false)
|
||||
{
|
||||
if (!pk.IsEgg && !reHatch)
|
||||
return;
|
||||
pk.IsEgg = false;
|
||||
pk.ClearNickname();
|
||||
pk.CurrentFriendship = pk.PersonalInfo.BaseFriendship;
|
||||
if (pk.IsTradedEgg)
|
||||
pk.Egg_Location = pk.Met_Location;
|
||||
var loc = EncounterSuggestion.GetSuggestedEggMetLocation(pk);
|
||||
if (loc >= 0)
|
||||
pk.Met_Location = loc;
|
||||
pk.MetDate = DateTime.Today;
|
||||
if (pk.Gen6)
|
||||
pk.SetHatchMemory6();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game.
|
||||
/// </summary>
|
||||
/// <param name="pk">PKM to apply hatch details to</param>
|
||||
/// <param name="origin">Game the egg originated from</param>
|
||||
/// <param name="dest">Game the egg is currently present on</param>
|
||||
public static void SetEggMetData(this PKM pk, GameVersion origin, GameVersion dest)
|
||||
{
|
||||
bool traded = origin != dest;
|
||||
var today = pk.MetDate = DateTime.Today;
|
||||
pk.Egg_Location = EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(pk.Generation, origin, traded);
|
||||
pk.EggMetDate = today;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximizes the <see cref="PKM.CurrentFriendship"/>. If the <see cref="PKM.IsEgg"/>, the hatch counter is set to 1.
|
||||
/// </summary>
|
||||
/// <param name="pk">PKM to apply hatch details to</param>
|
||||
public static void MaximizeFriendship(this PKM pk)
|
||||
{
|
||||
if (pk.IsEgg)
|
||||
pk.OT_Friendship = 1;
|
||||
else
|
||||
pk.CurrentFriendship = byte.MaxValue;
|
||||
if (pk is ICombatPower pb)
|
||||
pb.ResetCP();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximizes the <see cref="PKM.CurrentLevel"/>. If the <see cref="PKM.IsEgg"/>, the <see cref="PKM"/> is ignored.
|
||||
/// </summary>
|
||||
/// <param name="pk">PKM to apply hatch details to</param>
|
||||
public static void MaximizeLevel(this PKM pk)
|
||||
{
|
||||
if (pk.IsEgg)
|
||||
return;
|
||||
pk.CurrentLevel = 100;
|
||||
if (pk is ICombatPower pb)
|
||||
pb.ResetCP();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Nickname"/> to its default value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="la">Precomputed optional</param>
|
||||
public static void SetDefaultNickname(this PKM pk, LegalityAnalysis la)
|
||||
{
|
||||
if (la.Parsed && la.EncounterOriginal is EncounterTrade {HasNickname: true} t)
|
||||
pk.SetNickname(t.GetNickname(pk.Language));
|
||||
else
|
||||
pk.ClearNickname();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM.Nickname"/> to its default value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
public static void SetDefaultNickname(this PKM pk) => pk.SetDefaultNickname(new LegalityAnalysis(pk));
|
||||
|
||||
private static readonly string[] PotentialUnicode = { "★☆☆☆", "★★☆☆", "★★★☆", "★★★★" };
|
||||
private static readonly string[] PotentialNoUnicode = { "+", "++", "+++", "++++" };
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Potential evaluation of the input <see cref="pk"/>.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to analyze.</param>
|
||||
/// <param name="unicode">Returned value is unicode or not</param>
|
||||
/// <returns>Potential string</returns>
|
||||
public static string GetPotentialString(this PKM pk, bool unicode = true)
|
||||
{
|
||||
var arr = unicode ? PotentialUnicode : PotentialNoUnicode;
|
||||
return arr[pk.PotentialRating];
|
||||
}
|
||||
|
||||
// Extensions
|
||||
/// <summary>
|
||||
/// Gets the Location Name for the <see cref="PKM"/>
|
||||
/// </summary>
|
||||
/// <param name="pk">PKM to fetch data for</param>
|
||||
/// <param name="eggmet">Location requested is the egg obtained location, not met location.</param>
|
||||
/// <returns>Location string</returns>
|
||||
public static string GetLocationString(this PKM pk, bool eggmet)
|
||||
{
|
||||
if (pk.Format < 2)
|
||||
return string.Empty;
|
||||
|
||||
int location = eggmet ? pk.Egg_Location : pk.Met_Location;
|
||||
return GameInfo.GetLocationName(eggmet, location, pk.Format, pk.Generation, (GameVersion)pk.Version);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
@ -10,34 +8,7 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
public sealed class TrainerDatabase
|
||||
{
|
||||
private readonly Dictionary<GameVersion, List<ITrainerInfo>> Database = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of unique versions in the database.
|
||||
/// </summary>
|
||||
public int CountVersions => Database.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of trainers in the database.
|
||||
/// </summary>
|
||||
public int CountTrainers => Database.Sum(z => z.Value.Count);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the database contains any trainers for the specified <see cref="version"/>.
|
||||
/// </summary>
|
||||
/// <param name="version"></param>
|
||||
public bool HasVersion(GameVersion version) => Database.ContainsKey(version);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all trainers from the database for the specified saved <see cref="version"/>.
|
||||
/// </summary>
|
||||
/// <param name="version">Saved Version to fetch trainers for</param>
|
||||
public ReadOnlySpan<ITrainerInfo> GetTrainers(GameVersion version)
|
||||
{
|
||||
if (Database.TryGetValue(version, out var list))
|
||||
return CollectionsMarshal.AsSpan(list);
|
||||
return default;
|
||||
}
|
||||
private readonly Dictionary<GameVersion, List<ITrainerInfo>> Database = new();
|
||||
|
||||
/// <summary>
|
||||
/// Fetches an appropriate trainer based on the requested <see cref="version"/>.
|
||||
|
|
@ -45,35 +16,45 @@ public ReadOnlySpan<ITrainerInfo> GetTrainers(GameVersion version)
|
|||
/// <param name="version">Version the trainer should originate from</param>
|
||||
/// <param name="language">Language to request for</param>
|
||||
/// <returns>Null if no trainer found for this version.</returns>
|
||||
public ITrainerInfo? GetTrainer(GameVersion version, LanguageID? language = null)
|
||||
public ITrainerInfo? GetTrainer(int version, LanguageID? language = null) => GetTrainer((GameVersion)version, language);
|
||||
|
||||
/// <summary>
|
||||
/// Fetches an appropriate trainer based on the requested <see cref="ver"/>.
|
||||
/// </summary>
|
||||
/// <param name="ver">Version the trainer should originate from</param>
|
||||
/// <param name="language">Language to request for</param>
|
||||
/// <returns>Null if no trainer found for this version.</returns>
|
||||
public ITrainerInfo? GetTrainer(GameVersion ver, LanguageID? language = null)
|
||||
{
|
||||
if (version <= 0)
|
||||
if (ver <= 0)
|
||||
return null;
|
||||
|
||||
if (!version.IsValidSavedVersion())
|
||||
return GetTrainerFromGroup(version, language);
|
||||
if (!ver.IsValidSavedVersion())
|
||||
return GetTrainerFromGroup(ver, language);
|
||||
|
||||
if (Database.TryGetValue(version, out var list))
|
||||
return list[GetRandomIndex(list.Count)];
|
||||
if (Database.TryGetValue(ver, out var list))
|
||||
return GetRandomChoice(list);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int GetRandomIndex(int count) => count == 1 ? 0 : Util.Rand.Next(count);
|
||||
private static T GetRandomChoice<T>(IReadOnlyList<T> list)
|
||||
{
|
||||
if (list.Count == 1)
|
||||
return list[0];
|
||||
return list[Util.Rand.Next(list.Count)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches an appropriate trainer based on the requested <see cref="version"/> group.
|
||||
/// Fetches an appropriate trainer based on the requested <see cref="ver"/> group.
|
||||
/// </summary>
|
||||
/// <param name="version">Version the trainer should originate from</param>
|
||||
/// <param name="ver">Version the trainer should originate from</param>
|
||||
/// <param name="lang">Language to request for</param>
|
||||
/// <returns>Null if no trainer found for this version.</returns>
|
||||
private ITrainerInfo? GetTrainerFromGroup(GameVersion version, LanguageID? lang = null)
|
||||
private ITrainerInfo? GetTrainerFromGroup(GameVersion ver, LanguageID? lang = null)
|
||||
{
|
||||
var possible = Database.Where(z => version.Contains(z.Key)).ToList();
|
||||
if (possible.Count == 0)
|
||||
return null;
|
||||
|
||||
if (lang is not null)
|
||||
var possible = Database.Where(z => ver.Contains(z.Key)).ToList();
|
||||
if (lang != null)
|
||||
{
|
||||
possible = possible.Select(z =>
|
||||
{
|
||||
|
|
@ -81,23 +62,19 @@ public ReadOnlySpan<ITrainerInfo> GetTrainers(GameVersion version)
|
|||
return new KeyValuePair<GameVersion, List<ITrainerInfo>>(z.Key, filtered);
|
||||
}).Where(z => z.Value.Count != 0).ToList();
|
||||
}
|
||||
var span = CollectionsMarshal.AsSpan(possible);
|
||||
return GetRandomTrainer(span);
|
||||
return GetRandomTrainer(possible);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches an appropriate trainer based on the requested <see cref="context"/>.
|
||||
/// Fetches an appropriate trainer based on the requested <see cref="generation"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">Generation the trainer should inhabit</param>
|
||||
/// <param name="generation">Generation the trainer should inhabit</param>
|
||||
/// <param name="lang">Language to request for</param>
|
||||
/// <returns>Null if no trainer found for this version.</returns>
|
||||
public ITrainerInfo? GetTrainerFromContext(EntityContext context, LanguageID? lang = null)
|
||||
public ITrainerInfo? GetTrainerFromGen(int generation, LanguageID? lang = null)
|
||||
{
|
||||
var possible = Database.Where(z => z.Key.Context == context).ToList();
|
||||
if (possible.Count == 0)
|
||||
return null;
|
||||
|
||||
if (lang is not null)
|
||||
var possible = Database.Where(z => z.Key.GetGeneration() == generation).ToList();
|
||||
if (lang != null)
|
||||
{
|
||||
possible = possible.Select(z =>
|
||||
{
|
||||
|
|
@ -105,17 +82,15 @@ public ReadOnlySpan<ITrainerInfo> GetTrainers(GameVersion version)
|
|||
return new KeyValuePair<GameVersion, List<ITrainerInfo>>(z.Key, filtered);
|
||||
}).Where(z => z.Value.Count != 0).ToList();
|
||||
}
|
||||
var span = CollectionsMarshal.AsSpan(possible);
|
||||
return GetRandomTrainer(span);
|
||||
return GetRandomTrainer(possible);
|
||||
}
|
||||
|
||||
private static ITrainerInfo? GetRandomTrainer(ReadOnlySpan<KeyValuePair<GameVersion, List<ITrainerInfo>>> possible)
|
||||
private static ITrainerInfo? GetRandomTrainer(IReadOnlyList<KeyValuePair<GameVersion, List<ITrainerInfo>>> possible)
|
||||
{
|
||||
if (possible.Length == 0)
|
||||
if (possible.Count == 0)
|
||||
return null;
|
||||
var group = possible[GetRandomIndex(possible.Length)];
|
||||
var span = group.Value;
|
||||
return span[GetRandomIndex(span.Count)];
|
||||
var group = GetRandomChoice(possible);
|
||||
return GetRandomChoice(group.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -124,10 +99,12 @@ public ReadOnlySpan<ITrainerInfo> GetTrainers(GameVersion version)
|
|||
/// <param name="trainer">Trainer details to add.</param>
|
||||
public void Register(ITrainerInfo trainer)
|
||||
{
|
||||
var version = trainer.Version;
|
||||
if (!Database.TryGetValue(version, out var list))
|
||||
var ver = (GameVersion)trainer.Game;
|
||||
if (ver <= 0 && trainer is SaveFile s)
|
||||
ver = s.Version;
|
||||
if (!Database.TryGetValue(ver, out var list))
|
||||
{
|
||||
Database.Add(version, [trainer]);
|
||||
Database.Add(ver, new List<ITrainerInfo> { trainer });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -150,36 +127,21 @@ public void Register(ITrainerInfo trainer)
|
|||
/// <remarks>A copy of the object will be made to prevent modifications, just in case.</remarks>
|
||||
public void RegisterCopy(ITrainerInfo info) => Register(new SimpleTrainerInfo(info));
|
||||
|
||||
private static SimpleTrainerInfo GetTrainerReference(PKM pk)
|
||||
private static ITrainerInfo GetTrainerReference(PKM pk)
|
||||
{
|
||||
var (cr, c, r) = GetRegion3DS(pk);
|
||||
return GetTrainerReference(pk, cr, c, r);
|
||||
}
|
||||
|
||||
private static SimpleTrainerInfo GetTrainerReference(PKM pk, byte cr, byte c, byte r) => new(pk.Version)
|
||||
{
|
||||
TID16 = pk.TID16,
|
||||
SID16 = pk.SID16,
|
||||
OT = pk.OriginalTrainerName,
|
||||
Gender = pk.OriginalTrainerGender,
|
||||
Language = pk.Language,
|
||||
Generation = pk.Generation,
|
||||
ConsoleRegion = cr,
|
||||
Country = c,
|
||||
Region = r,
|
||||
};
|
||||
|
||||
private static (byte ConsoleRegion, byte Country, byte Region) GetRegion3DS(PKM pk)
|
||||
{
|
||||
if (pk is IRegionOriginReadOnly x)
|
||||
return (x.ConsoleRegion, x.Country, x.Region);
|
||||
if (pk.Version.IsGen6() || pk.Version.IsGen7())
|
||||
var result = new SimpleTrainerInfo((GameVersion)pk.Version)
|
||||
{
|
||||
if (pk.Language == (int)LanguageID.Japanese)
|
||||
return (0, 1, 0);
|
||||
return (1, 7, 49);
|
||||
}
|
||||
return default;
|
||||
TID = pk.TID, SID = pk.SID, OT = pk.OT_Name, Gender = pk.OT_Gender,
|
||||
Language = pk.Language,
|
||||
Generation = pk.Generation,
|
||||
};
|
||||
|
||||
if (pk is IRegionOrigin r)
|
||||
r.CopyRegionOrigin(result);
|
||||
else
|
||||
result.SetDefaultRegionOrigins();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
@ -15,7 +16,7 @@ public static class HiddenPower
|
|||
/// <param name="context">Generation format</param>
|
||||
public static int GetType(ReadOnlySpan<int> IVs, EntityContext context)
|
||||
{
|
||||
if (context.IsEraGameBoy)
|
||||
if (context.Generation() <= 2)
|
||||
return GetTypeGB(IVs);
|
||||
return GetType(IVs);
|
||||
}
|
||||
|
|
@ -30,84 +31,11 @@ public static int GetType(ReadOnlySpan<int> IVs)
|
|||
int hp = 0;
|
||||
for (int i = 0; i < 6; i++)
|
||||
hp |= (IVs[i] & 1) << i;
|
||||
return SixBitType[hp];
|
||||
hp *= 0xF;
|
||||
hp /= 0x3F;
|
||||
return hp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Hidden Power Type of the input IVs for Generations 3+
|
||||
/// </summary>
|
||||
/// <param name="u32">32-bit value of the IVs</param>
|
||||
/// <returns>Hidden Power Type of the IVs</returns>
|
||||
public static int GetType(uint u32)
|
||||
{
|
||||
uint hp = 0;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
hp |= (u32 & 1) << i;
|
||||
u32 >>= 5;
|
||||
}
|
||||
return SixBitType[(int)hp];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Hidden Power Type of the input IVs for Generations 3+
|
||||
/// </summary>
|
||||
/// <param name="u32">32-bit value of the IVs</param>
|
||||
/// <remarks>IVs are stored in reverse order in the 32-bit value</remarks>
|
||||
/// <returns>Hidden Power Type of the IVs</returns>
|
||||
public static int GetTypeBigEndian(uint u32)
|
||||
{
|
||||
uint hp = 0;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
hp |= (u32 & 1) << (5 - i);
|
||||
u32 >>= 5;
|
||||
}
|
||||
return SixBitType[(int)hp];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Count of unique Hidden Power Types
|
||||
/// </summary>
|
||||
public const int TypeCount = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the input Hidden Power Type is not one of the 15 valid types.
|
||||
/// </summary>
|
||||
/// <param name="type">Hidden Power Type</param>
|
||||
/// <returns><see langword="true"/> if the input Hidden Power Type is not one of the 15 valid types.</returns>
|
||||
public static bool IsInvalidType(int type) => (uint)type >= TypeCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Type Name index of the input Hidden Power Type
|
||||
/// </summary>
|
||||
/// <param name="type">Fetched Hidden Power Type</param>
|
||||
/// <param name="index">Type Name index</param>
|
||||
/// <returns>True if the input Hidden Power Type is valid</returns>
|
||||
public static bool TryGetTypeIndex(int type, out byte index)
|
||||
{
|
||||
if (IsInvalidType(type))
|
||||
{
|
||||
index = 0;
|
||||
return false;
|
||||
}
|
||||
index = (byte)(type + 1); // Normal type is not a valid Hidden Power type
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<byte> SixBitType =>
|
||||
[
|
||||
// (low-bit mash) * 15 / 63
|
||||
00, 00, 00, 00, 00, 01, 01, 01,
|
||||
01, 02, 02, 02, 02, 03, 03, 03,
|
||||
03, 04, 04, 04, 04, 05, 05, 05,
|
||||
05, 05, 06, 06, 06, 06, 07, 07,
|
||||
07, 07, 08, 08, 08, 08, 09, 09,
|
||||
09, 09, 10, 10, 10, 10, 10, 11,
|
||||
11, 11, 11, 12, 12, 12, 12, 13,
|
||||
13, 13, 13, 14, 14, 14, 14, 15,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Hidden Power Type of the input <see cref="IVs"/> for Generations 1 & 2
|
||||
/// </summary>
|
||||
|
|
@ -120,9 +48,6 @@ public static int GetTypeGB(ReadOnlySpan<int> IVs)
|
|||
return ((atk & 3) << 2) | (def & 3);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetTypeGB(ReadOnlySpan{int})"/>
|
||||
public static int GetTypeGB(ushort u16) => ((u16 >> 10) & 0b1100) | ((u16 >> 8) & 0b11);
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the provided <see cref="IVs"/> to have the requested <see cref="hiddenPowerType"/> for Generations 1 & 2
|
||||
/// </summary>
|
||||
|
|
@ -131,19 +56,11 @@ public static int GetTypeGB(ReadOnlySpan<int> IVs)
|
|||
/// <returns>True if the Hidden Power of the <see cref="IVs"/> is obtained, with or without modifications</returns>
|
||||
public static bool SetTypeGB(int hiddenPowerType, Span<int> IVs)
|
||||
{
|
||||
IVs[1] = (IVs[1] & 0b1100) | (hiddenPowerType >> 2);
|
||||
IVs[2] = (IVs[2] & 0b1100) | (hiddenPowerType & 3);
|
||||
IVs[1] = (IVs[1] & ~3) | (hiddenPowerType >> 2);
|
||||
IVs[2] = (IVs[2] & ~3) | (hiddenPowerType & 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetTypeGB(int, Span{int})"/>
|
||||
public static ushort SetTypeGB(int hiddenPowerType, ushort current)
|
||||
{
|
||||
// Extract bits from ATK and DEF.
|
||||
var u16 = ((hiddenPowerType & 0b1100) << 10) | ((hiddenPowerType & 0b11) << 8);
|
||||
return (ushort)((current & 0b1100_1100_1111_1111) | u16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the provided <see cref="IVs"/> to have the requested <see cref="hiddenPowerType"/>.
|
||||
/// </summary>
|
||||
|
|
@ -153,7 +70,7 @@ public static ushort SetTypeGB(int hiddenPowerType, ushort current)
|
|||
/// <returns>True if the Hidden Power of the <see cref="IVs"/> is obtained, with or without modifications</returns>
|
||||
public static bool SetIVsForType(int hiddenPowerType, Span<int> IVs, EntityContext context)
|
||||
{
|
||||
if (context.IsEraGameBoy)
|
||||
if (context.Generation() <= 2)
|
||||
return SetTypeGB(hiddenPowerType, IVs);
|
||||
return SetIVsForType(hiddenPowerType, IVs);
|
||||
}
|
||||
|
|
@ -166,10 +83,6 @@ public static bool SetIVsForType(int hiddenPowerType, Span<int> IVs, EntityConte
|
|||
/// <returns>True if the Hidden Power of the <see cref="IVs"/> is obtained, with or without modifications</returns>
|
||||
public static bool SetIVsForType(int hpVal, Span<int> IVs)
|
||||
{
|
||||
int current = GetType(IVs);
|
||||
if (current == hpVal)
|
||||
return true; // no mods necessary
|
||||
|
||||
int flawlessCount = IVs.Count(31);
|
||||
if (flawlessCount == 0)
|
||||
return false;
|
||||
|
|
@ -180,116 +93,101 @@ public static bool SetIVsForType(int hpVal, Span<int> IVs)
|
|||
return true;
|
||||
}
|
||||
|
||||
int current = GetType(IVs);
|
||||
if (current == hpVal)
|
||||
return true; // no mods necessary
|
||||
|
||||
// Required HP type doesn't match IVs. Make currently-flawless IVs flawed.
|
||||
var bits = GetSuggestedHiddenPowerIVs(hpVal, IVs);
|
||||
if (bits == NoResult)
|
||||
Span<int> scratch = stackalloc int[IVs.Length];
|
||||
Span<int> result = stackalloc int[IVs.Length];
|
||||
var success = GetSuggestedHiddenPowerIVs(hpVal, IVs, scratch, result);
|
||||
if (!success)
|
||||
return false; // can't force hidden power?
|
||||
|
||||
// set IVs back to array
|
||||
ForceLowBits(IVs, bits);
|
||||
result.CopyTo(IVs);
|
||||
return true;
|
||||
}
|
||||
|
||||
private const byte NoResult = byte.MaxValue;
|
||||
|
||||
private static byte GetSuggestedHiddenPowerIVs(int hpVal, ReadOnlySpan<int> IVs)
|
||||
{
|
||||
// Iterate through all bit combinations that yield our Hidden Power Type.
|
||||
// There's at most 5 we need to check, so brute force is fine.
|
||||
// Prefer the least amount of IVs changed (31 -> 30).
|
||||
|
||||
// Get the starting index from our 64 possible bit states.
|
||||
int index = SixBitType.IndexOf((byte)hpVal);
|
||||
if (index == -1)
|
||||
return NoResult;
|
||||
|
||||
var bestIndex = NoResult;
|
||||
var bestIndexFlaws = 6;
|
||||
do
|
||||
{
|
||||
var flaws = GetFlawedBitCount(IVs, index);
|
||||
if (flaws >= bestIndexFlaws)
|
||||
continue;
|
||||
bestIndex = (byte)index;
|
||||
bestIndexFlaws = flaws;
|
||||
} while (++index < SixBitType.Length && SixBitType[index] == hpVal);
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
private static int GetFlawedBitCount(ReadOnlySpan<int> ivs, int bitValue)
|
||||
// Non-recursive https://en.wikipedia.org/wiki/Heap%27s_algorithm
|
||||
private static bool GetSuggestedHiddenPowerIVs(int hpVal, ReadOnlySpan<int> original, Span<int> ivs, Span<int> best)
|
||||
{
|
||||
const int max = 31;
|
||||
int flaws = 0;
|
||||
for (int i = 0; i < ivs.Length; i++)
|
||||
|
||||
// Get a list of indexes that can be mutated
|
||||
Span<int> indexes = stackalloc int[original.Length];
|
||||
int flaw = 0;
|
||||
for (int i = 0; i < original.Length; i++)
|
||||
{
|
||||
var iv = ivs[i];
|
||||
if ((iv & 1) == (bitValue & (1 << i)))
|
||||
continue; // ok
|
||||
if (iv != max)
|
||||
return NoResult;
|
||||
flaws++;
|
||||
if (original[i] == max)
|
||||
indexes[flaw++] = i;
|
||||
}
|
||||
return flaws;
|
||||
indexes = indexes[..flaw];
|
||||
Span<int> c = stackalloc int[indexes.Length];
|
||||
|
||||
int mutated = c.Length + 1; // result tracking
|
||||
for (int i = 1; i < c.Length;)
|
||||
{
|
||||
ref int ci = ref c[i];
|
||||
if (i <= ci) // Reset the state and simulate popping the stack by incrementing the pointer.
|
||||
{
|
||||
ci = 0;
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
var x = (i & 1) * ci; // if lowest bit set, ci : 0 (branch-less)
|
||||
Swap(ref indexes[i], ref indexes[x]);
|
||||
|
||||
// Inlined continuance check
|
||||
original.CopyTo(ivs);
|
||||
var q = Math.Min(indexes.Length, mutated);
|
||||
for (var j = 0; j < q; j++)
|
||||
{
|
||||
ivs[indexes[j]] ^= 1;
|
||||
if (hpVal != GetType(ivs))
|
||||
continue;
|
||||
|
||||
var ct = j + 1;
|
||||
if (ct >= mutated)
|
||||
break; // any further flaws are always worse
|
||||
|
||||
mutated = ct;
|
||||
ivs.CopyTo(best);
|
||||
if (j == 0) // nothing will be better than only 1 flaw
|
||||
return true;
|
||||
break; // any further flaws are always worse
|
||||
}
|
||||
|
||||
ci++;
|
||||
i = 1;
|
||||
}
|
||||
|
||||
return mutated <= c.Length; // did we actually find a suitable result?
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Swap<T>(ref T a, ref T b) => (a, b) = (b, a);
|
||||
|
||||
/// <summary>Calculate the Hidden Power Type of the entered IVs.</summary>
|
||||
/// <param name="type">Hidden Power Type</param>
|
||||
/// <param name="ivs">Individual Values (H/A/B/S/C/D)</param>
|
||||
/// <param name="context">Generation specific format</param>
|
||||
public static void SetIVs(int type, Span<int> ivs, EntityContext context = Latest.Context)
|
||||
public static void SetIVs(int type, Span<int> ivs, EntityContext context = PKX.Context)
|
||||
{
|
||||
if (context.IsEraGameBoy)
|
||||
if (context.Generation() <= 2)
|
||||
{
|
||||
ivs[1] = (ivs[1] & 0b1100) | (type >> 2);
|
||||
ivs[2] = (ivs[2] & 0b1100) | (type & 3);
|
||||
ivs[1] = (ivs[1] & ~3) | (type >> 2);
|
||||
ivs[2] = (ivs[2] & ~3) | (type & 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
ForceLowBits(ivs, DefaultLowBits[type]);
|
||||
var bits = DefaultLowBits[type];
|
||||
for (int i = 0; i < 6; i++)
|
||||
ivs[i] = (ivs[i] & 0x1E) + ((bits >> i) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ForceLowBits(Span<int> ivs, byte bits)
|
||||
{
|
||||
for (int i = 0; i < ivs.Length; i++)
|
||||
ivs[i] = (ivs[i] & 0b11110) | ((bits >> i) & 1);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetIVs(int,Span{int},EntityContext)"/>
|
||||
public static uint SetIVs(int type, uint ivs)
|
||||
{
|
||||
var bits = DefaultLowBits[type];
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
var bit = (bits >> i) & 1;
|
||||
var bitIndex = i * 5;
|
||||
var mask = (1u << bitIndex);
|
||||
if (bit == 0)
|
||||
ivs &= ~mask;
|
||||
else
|
||||
ivs |= mask;
|
||||
}
|
||||
return ivs;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetIVs(int,uint)"/>
|
||||
/// <remarks>IVs are stored in reverse order in the 32-bit value</remarks>
|
||||
public static uint SetIVsBigEndian(int type, uint ivs)
|
||||
{
|
||||
var bits = DefaultLowBits[type];
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
var bit = (bits >> i) & 1;
|
||||
var bitIndex = (5 - i) * 5;
|
||||
var mask = (1u << bitIndex);
|
||||
if (bit == 0)
|
||||
ivs &= ~mask;
|
||||
else
|
||||
ivs |= mask;
|
||||
}
|
||||
return ivs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hidden Power IV values (even or odd) to achieve a specified Hidden Power Type
|
||||
/// </summary>
|
||||
|
|
@ -298,8 +196,8 @@ public static uint SetIVsBigEndian(int type, uint ivs)
|
|||
/// These are just precomputed for fast modification.
|
||||
/// Individual Values (H/A/B/S/C/D)
|
||||
/// </remarks>
|
||||
public static ReadOnlySpan<byte> DefaultLowBits =>
|
||||
[
|
||||
public static readonly byte[] DefaultLowBits =
|
||||
{
|
||||
0b000011, // Fighting
|
||||
0b001000, // Flying
|
||||
0b001011, // Poison
|
||||
|
|
@ -316,14 +214,5 @@ public static uint SetIVsBigEndian(int type, uint ivs)
|
|||
0b111001, // Ice
|
||||
0b111101, // Dragon
|
||||
0b111111, // Dark
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the suggested low-bits for the input Hidden Power Type
|
||||
/// </summary>
|
||||
public static byte GetLowBits(int type)
|
||||
{
|
||||
var arr = DefaultLowBits;
|
||||
return (uint)type < arr.Length ? arr[type] : (byte)0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Interface containing details relevant for battling.
|
||||
/// </summary>
|
||||
public interface IBattleTemplate : ISpeciesForm, IGigantamaxReadOnly, IDynamaxLevelReadOnly, INatureReadOnly, ITeraTypeReadOnly
|
||||
public interface IBattleTemplate : ISpeciesForm, IGigantamax, IDynamaxLevel, INature
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="PKM.Context"/> of the Set entity it is specific to.
|
||||
|
|
@ -18,12 +18,11 @@ public interface IBattleTemplate : ISpeciesForm, IGigantamaxReadOnly, IDynamaxLe
|
|||
/// <summary>
|
||||
/// <see cref="PKM.Gender"/> name of the Set entity.
|
||||
/// </summary>
|
||||
byte? Gender { get; }
|
||||
int Gender { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="PKM.HeldItem"/> of the Set entity.
|
||||
/// </summary>
|
||||
/// <remarks>Depends on <see cref="Context"/> for context-specific item lists.</remarks>
|
||||
int HeldItem { get; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -34,7 +33,7 @@ public interface IBattleTemplate : ISpeciesForm, IGigantamaxReadOnly, IDynamaxLe
|
|||
/// <summary>
|
||||
/// <see cref="PKM.CurrentLevel"/> of the Set entity.
|
||||
/// </summary>
|
||||
byte Level { get; }
|
||||
int Level { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="PKM.CurrentLevel"/> of the Set entity.
|
||||
|
|
@ -44,7 +43,7 @@ public interface IBattleTemplate : ISpeciesForm, IGigantamaxReadOnly, IDynamaxLe
|
|||
/// <summary>
|
||||
/// <see cref="PKM.CurrentFriendship"/> of the Set entity.
|
||||
/// </summary>
|
||||
byte Friendship { get; }
|
||||
int Friendship { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="PKM.Form"/> name of the Set entity, stored in PKHeX style (instead of Showdown's)
|
||||
|
|
@ -54,7 +53,7 @@ public interface IBattleTemplate : ISpeciesForm, IGigantamaxReadOnly, IDynamaxLe
|
|||
/// <summary>
|
||||
/// <see cref="PKM.HPType"/> of the Set entity.
|
||||
/// </summary>
|
||||
sbyte HiddenPowerType { get; }
|
||||
int HiddenPowerType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="EffortValues"/> of the Set entity.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace PKHeX.Core;
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Simple interface representing a <see cref="PKM"/> viewer.
|
||||
|
|
@ -44,10 +44,4 @@ public interface IPKMView
|
|||
/// <param name="focus">Cause the viewer to give focus to itself.</param>
|
||||
/// <param name="skipConversionCheck">Cause the viewer to skip converting the data. Faster if it is known that the format is the same as the previous format.</param>
|
||||
void PopulateFields(PKM pk, bool focus = true, bool skipConversionCheck = false);
|
||||
|
||||
/// <summary>
|
||||
/// Messages back that the entity has been saved.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon data that was saved.</param>
|
||||
void NotifyWasExported(PKM pk);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
namespace PKHeX.Core;
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin interface used by an editor to notify third-party code providers.
|
||||
|
|
@ -26,13 +26,6 @@ public interface IPlugin
|
|||
/// </summary>
|
||||
void NotifySaveLoaded();
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the plugin that the display language has changed.
|
||||
/// </summary>
|
||||
/// <param name="language">Short code for language name</param>
|
||||
/// <remarks>Useful to translate controls if any added.</remarks>
|
||||
void NotifyDisplayLanguageChanged(string language) { }
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load a file using the plugin.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -9,14 +9,16 @@ public interface ISpriteBuilder<T>
|
|||
/// <summary>
|
||||
/// Gets a sprite using the requested parameters.
|
||||
/// </summary>
|
||||
T GetSprite(ushort species, byte form, byte gender, uint formarg, int heldItem, bool isEgg, Shiny shiny,
|
||||
EntityContext context = EntityContext.None);
|
||||
T GetSprite(ushort species, byte form, int gender, uint formarg, int heldItem, bool isEgg, Shiny shiny,
|
||||
int generation = -1,
|
||||
SpriteBuilderTweak tweak = SpriteBuilderTweak.None);
|
||||
|
||||
/// <summary>
|
||||
/// Revises the sprite using the requested parameters.
|
||||
/// </summary>
|
||||
T GetSprite(T baseSprite, ushort species, int heldItem, bool isEgg, Shiny shiny,
|
||||
EntityContext context = EntityContext.None);
|
||||
int generation = -1,
|
||||
SpriteBuilderTweak tweak = SpriteBuilderTweak.None);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the implementation with the context details from the <see cref="sav"/>.
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ public static class LocationEdits
|
|||
/// <summary>
|
||||
/// Gets the "None" location index for a specific <see cref="PKM"/> context.
|
||||
/// </summary>
|
||||
public static ushort GetNoneLocation(PKM pk) => GetNoneLocation(pk.Context);
|
||||
public static int GetNoneLocation(PKM pk) => GetNoneLocation(pk.Context);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the "None" location index for a specific <see cref="PKM"/> context.
|
||||
/// </summary>
|
||||
public static ushort GetNoneLocation(EntityContext context) => context switch
|
||||
public static int GetNoneLocation(EntityContext context) => context switch
|
||||
{
|
||||
EntityContext.Gen8b => Locations.Default8bNone,
|
||||
_ => 0,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using static PKHeX.Core.NatureAmpRequest;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
|
@ -8,92 +8,43 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
public static class NatureAmp
|
||||
{
|
||||
extension(NatureAmpRequest type)
|
||||
/// <summary>
|
||||
/// Mutate the nature amp indexes to match the request
|
||||
/// </summary>
|
||||
/// <param name="type">Request type to modify the provided <see cref="statIndex"/></param>
|
||||
/// <param name="statIndex">Stat Index to mutate</param>
|
||||
/// <param name="currentNature">Current nature to derive the current amps from</param>
|
||||
/// <returns>New nature value</returns>
|
||||
public static int GetNewNature(this NatureAmpRequest type, int statIndex, int currentNature)
|
||||
{
|
||||
/// <summary>
|
||||
/// Mutate the nature amp indexes to match the request
|
||||
/// </summary>
|
||||
/// <param name="statIndex">Stat Index to mutate</param>
|
||||
/// <param name="currentNature">Current nature to derive the current amps from</param>
|
||||
/// <returns>New nature value</returns>
|
||||
public Nature GetNewNature(int statIndex, Nature currentNature)
|
||||
{
|
||||
if ((uint)currentNature >= NatureCount)
|
||||
return Nature.Random;
|
||||
if (currentNature > (int)Nature.Quirky)
|
||||
return -1;
|
||||
|
||||
var (up, dn) = currentNature.GetNatureModification();
|
||||
var (up, dn) = GetNatureModification(currentNature);
|
||||
|
||||
return type.GetNewNature(statIndex, up, dn);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetNewNature(NatureAmpRequest,int,Nature)"/>
|
||||
public Nature GetNewNature(int statIndex, int up, int dn)
|
||||
{
|
||||
//
|
||||
switch (type)
|
||||
{
|
||||
case Increase when up != statIndex:
|
||||
up = statIndex;
|
||||
break;
|
||||
case Decrease when dn != statIndex:
|
||||
dn = statIndex;
|
||||
break;
|
||||
case Neutral when up != statIndex && dn != statIndex:
|
||||
up = dn = statIndex;
|
||||
break;
|
||||
default:
|
||||
return Nature.Random; // failure
|
||||
}
|
||||
|
||||
return CreateNatureFromAmps(up, dn);
|
||||
}
|
||||
return GetNewNature(type, statIndex, up, dn);
|
||||
}
|
||||
|
||||
extension(Nature nature)
|
||||
/// <inheritdoc cref="GetNewNature(PKHeX.Core.NatureAmpRequest,int,int)"/>
|
||||
public static int GetNewNature(NatureAmpRequest type, int statIndex, int up, int dn)
|
||||
{
|
||||
/// <summary>
|
||||
/// Decompose the nature to the two stat indexes that are modified
|
||||
/// </summary>
|
||||
public (int up, int dn) GetNatureModification()
|
||||
//
|
||||
switch (type)
|
||||
{
|
||||
var up = ((byte)nature / 5);
|
||||
var dn = ((byte)nature % 5);
|
||||
return (up, dn);
|
||||
case Increase when up != statIndex:
|
||||
up = statIndex;
|
||||
break;
|
||||
case Decrease when dn != statIndex:
|
||||
dn = statIndex;
|
||||
break;
|
||||
case Neutral when up != statIndex && dn != statIndex:
|
||||
up = dn = statIndex;
|
||||
break;
|
||||
default:
|
||||
return -1; // failure
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IsNeutralOrInvalid(Nature, int, int)"/>
|
||||
public bool IsNeutralOrInvalid()
|
||||
{
|
||||
var (up, dn) = nature.GetNatureModification();
|
||||
return nature.IsNeutralOrInvalid(up, dn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the nature is out of range or the stat amplifications are not neutral.
|
||||
/// </summary>
|
||||
/// <param name="up">Increased stat</param>
|
||||
/// <param name="dn">Decreased stat</param>
|
||||
/// <returns>True if nature modification values are equal or the Nature is out of range.</returns>
|
||||
public bool IsNeutralOrInvalid(int up, int dn)
|
||||
{
|
||||
return up == dn || (byte)nature >= 25; // invalid
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates stats according to the specified nature.
|
||||
/// </summary>
|
||||
/// <param name="stats">Current stats to amplify if appropriate</param>
|
||||
public void ModifyStatsForNature(Span<ushort> stats)
|
||||
{
|
||||
var (up, dn) = nature.GetNatureModification();
|
||||
if (nature.IsNeutralOrInvalid(up, dn))
|
||||
return;
|
||||
|
||||
ref var upStat = ref stats[up + 1];
|
||||
ref var dnStat = ref stats[dn + 1];
|
||||
upStat = (ushort)((upStat * 11) / 10);
|
||||
dnStat = (ushort)((dnStat * 9) / 10);
|
||||
}
|
||||
return CreateNatureFromAmps(up, dn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -102,69 +53,54 @@ public void ModifyStatsForNature(Span<ushort> stats)
|
|||
/// <param name="up">Increased stat</param>
|
||||
/// <param name="dn">Decreased stat</param>
|
||||
/// <returns>Nature</returns>
|
||||
public static Nature CreateNatureFromAmps(int up, int dn)
|
||||
public static int CreateNatureFromAmps(int up, int dn)
|
||||
{
|
||||
if ((uint)up > 5 || (uint)dn > 5)
|
||||
return Nature.Random;
|
||||
return (Nature)((up * 5) + dn);
|
||||
return -1;
|
||||
return (up * 5) + dn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nature Amplification Table
|
||||
/// Decompose the nature to the two stat indexes that are modified
|
||||
/// </summary>
|
||||
/// <remarks>-1 is 90%, 0 is 100%, 1 is 110%.</remarks>
|
||||
public static ReadOnlySpan<sbyte> Table =>
|
||||
[
|
||||
0, 0, 0, 0, 0, // Hardy
|
||||
1,-1, 0, 0, 0, // Lonely
|
||||
1, 0, 0, 0,-1, // Brave
|
||||
1, 0,-1, 0, 0, // Adamant
|
||||
1, 0, 0,-1, 0, // Naughty
|
||||
-1, 1, 0, 0, 0, // Bold
|
||||
0, 0, 0, 0, 0, // Docile
|
||||
0, 1, 0, 0,-1, // Relaxed
|
||||
0, 1,-1, 0, 0, // Impish
|
||||
0, 1, 0,-1, 0, // Lax
|
||||
-1, 0, 0, 0, 1, // Timid
|
||||
0,-1, 0, 0, 1, // Hasty
|
||||
0, 0, 0, 0, 0, // Serious
|
||||
0, 0,-1, 0, 1, // Jolly
|
||||
0, 0, 0,-1, 1, // Naive
|
||||
-1, 0, 1, 0, 0, // Modest
|
||||
0,-1, 1, 0, 0, // Mild
|
||||
0, 0, 1, 0,-1, // Quiet
|
||||
0, 0, 0, 0, 0, // Bashful
|
||||
0, 0, 1,-1, 0, // Rash
|
||||
-1, 0, 0, 1, 0, // Calm
|
||||
0,-1, 0, 1, 0, // Gentle
|
||||
0, 0, 0, 1,-1, // Sassy
|
||||
0, 0,-1, 1, 0, // Careful
|
||||
0, 0, 0, 0, 0, // Quirky
|
||||
];
|
||||
|
||||
private const byte NatureCount = 25;
|
||||
private const int AmpWidth = 5;
|
||||
|
||||
public static int AmplifyStat(Nature nature, int index, int initial) => GetNatureAmp(nature, index) switch
|
||||
public static (int up, int dn) GetNatureModification(int nature)
|
||||
{
|
||||
1 => 110 * initial / 100, // 110%
|
||||
-1 => 90 * initial / 100, // 90%
|
||||
_ => initial,
|
||||
};
|
||||
|
||||
private static sbyte GetNatureAmp(Nature nature, int index)
|
||||
{
|
||||
if ((uint)nature >= NatureCount)
|
||||
return -1;
|
||||
var amps = GetAmps(nature);
|
||||
return amps[index];
|
||||
var up = (nature / 5) + 1;
|
||||
var dn = (nature % 5) + 1;
|
||||
return (up, dn);
|
||||
}
|
||||
|
||||
public static ReadOnlySpan<sbyte> GetAmps(Nature nature)
|
||||
/// <summary>
|
||||
/// Checks if the nature is out of range or the stat amplifications are not neutral.
|
||||
/// </summary>
|
||||
/// <param name="nature">Nature</param>
|
||||
/// <param name="up">Increased stat</param>
|
||||
/// <param name="dn">Decreased stat</param>
|
||||
/// <returns>True if nature modification values are equal or the Nature is out of range.</returns>
|
||||
public static bool IsNeutralOrInvalid(int nature, int up, int dn)
|
||||
{
|
||||
if ((uint)nature >= NatureCount)
|
||||
nature = 0;
|
||||
return Table.Slice(AmpWidth * (byte)nature, AmpWidth);
|
||||
return up == dn || nature >= 25; // invalid
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IsNeutralOrInvalid(int, int, int)"/>
|
||||
public static bool IsNeutralOrInvalid(int nature)
|
||||
{
|
||||
var (up, dn) = GetNatureModification(nature);
|
||||
return IsNeutralOrInvalid(nature, up, dn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates stats according to the specified nature.
|
||||
/// </summary>
|
||||
/// <param name="stats">Current stats to amplify if appropriate</param>
|
||||
/// <param name="nature">Nature</param>
|
||||
public static void ModifyStatsForNature(Span<ushort> stats, int nature)
|
||||
{
|
||||
var (up, dn) = GetNatureModification(nature);
|
||||
if (IsNeutralOrInvalid(nature, up, dn))
|
||||
return;
|
||||
stats[up] *= 11; stats[up] /= 10;
|
||||
stats[dn] *= 9; stats[dn] /= 10;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static PKHeX.Core.MessageStrings;
|
||||
|
||||
|
|
@ -9,32 +9,16 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
public static class EntitySuggestionUtil
|
||||
{
|
||||
public static List<string> GetMetLocationSuggestionMessage(PKM pk, byte level, ushort location, int minimumLevel, IEncounterable? enc)
|
||||
public static List<string> GetMetLocationSuggestionMessage(PKM pk, int level, int location, int minimumLevel)
|
||||
{
|
||||
var suggestion = new List<string> { MsgPKMSuggestionStart };
|
||||
if (pk.Format >= 3)
|
||||
{
|
||||
var metList = GameInfo.GetLocationList(pk.Version, pk.Context, egg: false);
|
||||
var metList = GameInfo.GetLocationList((GameVersion)pk.Version, pk.Context, egg: false);
|
||||
var locationName = metList.First(loc => loc.Value == location).Text;
|
||||
suggestion.Add($"{MsgPKMSuggestionMetLocation} {locationName}");
|
||||
suggestion.Add($"{MsgPKMSuggestionMetLevel} {level}");
|
||||
}
|
||||
else if (pk is ICaughtData2)
|
||||
{
|
||||
var metList = GameInfo.GetLocationList(GameVersion.C, pk.Context);
|
||||
string locationName;
|
||||
if (enc?.Version.Contains(GameVersion.C) == true)
|
||||
{
|
||||
locationName = metList.First(loc => loc.Value == location).Text;
|
||||
}
|
||||
else
|
||||
{
|
||||
locationName = metList[0].Text;
|
||||
level = 0;
|
||||
}
|
||||
suggestion.Add($"{MsgPKMSuggestionMetLocation} {locationName}");
|
||||
suggestion.Add($"{MsgPKMSuggestionMetLevel} {level}");
|
||||
}
|
||||
if (pk.CurrentLevel < minimumLevel)
|
||||
suggestion.Add($"{MsgPKMSuggestionLevel} {minimumLevel}");
|
||||
return suggestion;
|
||||
|
|
|
|||
|
|
@ -1,122 +1,117 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Bindable summary object that can fetch strings that summarize a <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class EntitySummary : IFatefulEncounterReadOnly // do NOT seal, allow inheritance
|
||||
public class EntitySummary // do NOT seal, allow inheritance
|
||||
{
|
||||
private static readonly IReadOnlyList<string> GenderSymbols = GameInfo.GenderSymbolASCII;
|
||||
|
||||
private readonly GameStrings Strings;
|
||||
private readonly ushort[] Stats;
|
||||
public readonly PKM Entity; // protected for children generating extra properties
|
||||
protected readonly PKM pk; // protected for children generating extra properties
|
||||
|
||||
public virtual string Position => "???";
|
||||
public string Nickname => Entity.Nickname;
|
||||
public string Species => Get(Strings.specieslist, Entity.Species);
|
||||
public string Nature => Get(Strings.natures, (byte)Entity.StatNature);
|
||||
public string Gender => Get(GenderSymbols, Entity.Gender);
|
||||
public string ESV => Entity.PSV.ToString("0000");
|
||||
public string HP_Type => GetSpan(Strings.HiddenPowerTypes, Entity.HPType);
|
||||
public string Ability => Get(Strings.abilitylist, Entity.Ability);
|
||||
public string Move1 => Get(Strings.movelist, Entity.Move1);
|
||||
public string Move2 => Get(Strings.movelist, Entity.Move2);
|
||||
public string Move3 => Get(Strings.movelist, Entity.Move3);
|
||||
public string Move4 => Get(Strings.movelist, Entity.Move4);
|
||||
public string HeldItem => GetSpan(Strings.GetItemStrings(Entity.Context), Entity.HeldItem);
|
||||
public string Nickname => pk.Nickname;
|
||||
public string Species => Get(Strings.specieslist, pk.Species);
|
||||
public string Nature => Get(Strings.natures, pk.StatNature);
|
||||
public string Gender => Get(GenderSymbols, pk.Gender);
|
||||
public string ESV => pk.PSV.ToString("0000");
|
||||
public string HP_Type => Get(Strings.types, pk.HPType + 1);
|
||||
public string Ability => Get(Strings.abilitylist, pk.Ability);
|
||||
public string Move1 => Get(Strings.movelist, pk.Move1);
|
||||
public string Move2 => Get(Strings.movelist, pk.Move2);
|
||||
public string Move3 => Get(Strings.movelist, pk.Move3);
|
||||
public string Move4 => Get(Strings.movelist, pk.Move4);
|
||||
public string HeldItem => GetSpan(Strings.GetItemStrings(pk.Context), pk.HeldItem);
|
||||
public string HP => Stats[0].ToString();
|
||||
public string ATK => Stats[1].ToString();
|
||||
public string DEF => Stats[2].ToString();
|
||||
public string SPA => Stats[4].ToString();
|
||||
public string SPD => Stats[5].ToString();
|
||||
public string SPE => Stats[3].ToString();
|
||||
public string MetLoc => Entity.GetLocationString(eggmet: false);
|
||||
public string EggLoc => Entity.GetLocationString(eggmet: true);
|
||||
public string Ball => Get(Strings.balllist, Entity.Ball);
|
||||
public string OT => Entity.OriginalTrainerName;
|
||||
public string Version => Get(Strings.gamelist, (int)Entity.Version);
|
||||
public string OTLang => ((LanguageID)Entity.Language).ToString();
|
||||
public string Legal => Legality.Parsed ? Legality.Valid.ToString() : "-";
|
||||
public string EncounterType => Legality.EncounterMatch.LongName;
|
||||
|
||||
private LegalityAnalysis Legality { get; }
|
||||
public string MetLoc => pk.GetLocationString(eggmet: false);
|
||||
public string EggLoc => pk.GetLocationString(eggmet: true);
|
||||
public string Ball => Get(Strings.balllist, pk.Ball);
|
||||
public string OT => pk.OT_Name;
|
||||
public string Version => Get(Strings.gamelist, pk.Version);
|
||||
public string OTLang => ((LanguageID)pk.Language).ToString();
|
||||
public string Legal { get { var la = new LegalityAnalysis(pk); return la.Parsed ? la.Valid.ToString() : "-"; } }
|
||||
|
||||
#region Extraneous
|
||||
public string EC => Entity.EncryptionConstant.ToString("X8");
|
||||
public string PID => Entity.PID.ToString("X8");
|
||||
public int IV_HP => Entity.IV_HP;
|
||||
public int IV_ATK => Entity.IV_ATK;
|
||||
public int IV_DEF => Entity.IV_DEF;
|
||||
public int IV_SPA => Entity.IV_SPA;
|
||||
public int IV_SPD => Entity.IV_SPD;
|
||||
public int IV_SPE => Entity.IV_SPE;
|
||||
public uint EXP => Entity.EXP;
|
||||
public byte Level => Entity.CurrentLevel;
|
||||
public int EV_HP => Entity.EV_HP;
|
||||
public int EV_ATK => Entity.EV_ATK;
|
||||
public int EV_DEF => Entity.EV_DEF;
|
||||
public int EV_SPA => Entity.EV_SPA;
|
||||
public int EV_SPD => Entity.EV_SPD;
|
||||
public int EV_SPE => Entity.EV_SPE;
|
||||
public int Cool => Entity is IContestStatsReadOnly s ? s.ContestCool : 0;
|
||||
public int Beauty => Entity is IContestStatsReadOnly s ? s.ContestBeauty : 0;
|
||||
public int Cute => Entity is IContestStatsReadOnly s ? s.ContestCute : 0;
|
||||
public int Smart => Entity is IContestStatsReadOnly s ? s.ContestSmart : 0;
|
||||
public int Tough => Entity is IContestStatsReadOnly s ? s.ContestTough : 0;
|
||||
public int Sheen => Entity is IContestStatsReadOnly s ? s.ContestSheen : 0;
|
||||
public string EC => pk.EncryptionConstant.ToString("X8");
|
||||
public string PID => pk.PID.ToString("X8");
|
||||
public int HP_IV => pk.IV_HP;
|
||||
public int ATK_IV => pk.IV_ATK;
|
||||
public int DEF_IV => pk.IV_DEF;
|
||||
public int SPA_IV => pk.IV_SPA;
|
||||
public int SPD_IV => pk.IV_SPD;
|
||||
public int SPE_IV => pk.IV_SPE;
|
||||
public uint EXP => pk.EXP;
|
||||
public int Level => pk.CurrentLevel;
|
||||
public int HP_EV => pk.EV_HP;
|
||||
public int ATK_EV => pk.EV_ATK;
|
||||
public int DEF_EV => pk.EV_DEF;
|
||||
public int SPA_EV => pk.EV_SPA;
|
||||
public int SPD_EV => pk.EV_SPD;
|
||||
public int SPE_EV => pk.EV_SPE;
|
||||
public int Cool => pk is IContestStats s ? s.CNT_Cool : 0;
|
||||
public int Beauty => pk is IContestStats s ? s.CNT_Beauty : 0;
|
||||
public int Cute => pk is IContestStats s ? s.CNT_Cute : 0;
|
||||
public int Smart => pk is IContestStats s ? s.CNT_Smart : 0;
|
||||
public int Tough => pk is IContestStats s ? s.CNT_Tough : 0;
|
||||
public int Sheen => pk is IContestStats s ? s.CNT_Sheen : 0;
|
||||
public int Markings => pk.MarkValue;
|
||||
|
||||
public string NotOT => Entity.Format > 5 ? Entity.HandlingTrainerName : "N/A";
|
||||
public string NotOT => pk.Format > 5 ? pk.HT_Name : "N/A";
|
||||
|
||||
public int AbilityNum => Entity.Format > 5 ? Entity.AbilityNumber : -1;
|
||||
public byte GenderFlag => Entity.Gender;
|
||||
public byte Form => Entity.Form;
|
||||
public int PokerusStrain => Entity.PokerusStrain;
|
||||
public int PokerusDays => Entity.PokerusDays;
|
||||
public byte MetLevel => Entity.MetLevel;
|
||||
public byte OriginalTrainerGender => Entity.OriginalTrainerGender;
|
||||
public int AbilityNum => pk.Format > 5 ? pk.AbilityNumber : -1;
|
||||
public int GenderFlag => pk.Gender;
|
||||
public byte Form => pk.Form;
|
||||
public int PKRS_Strain => pk.PKRS_Strain;
|
||||
public int PKRS_Days => pk.PKRS_Days;
|
||||
public int MetLevel => pk.Met_Level;
|
||||
public int OT_Gender => pk.OT_Gender;
|
||||
|
||||
public bool FatefulEncounter => Entity.FatefulEncounter;
|
||||
public bool IsEgg => Entity.IsEgg;
|
||||
public bool IsNicknamed => Entity.IsNicknamed;
|
||||
public bool IsShiny => Entity.IsShiny;
|
||||
public bool FatefulFlag => pk.FatefulEncounter;
|
||||
public bool IsEgg => pk.IsEgg;
|
||||
public bool IsNicknamed => pk.IsNicknamed;
|
||||
public bool IsShiny => pk.IsShiny;
|
||||
|
||||
public ushort TID16 => Entity.TID16;
|
||||
public ushort SID16 => Entity.SID16;
|
||||
public uint TSV => Entity.TSV;
|
||||
public int Move1_PP => Entity.Move1_PP;
|
||||
public int Move2_PP => Entity.Move2_PP;
|
||||
public int Move3_PP => Entity.Move3_PP;
|
||||
public int Move4_PP => Entity.Move4_PP;
|
||||
public int Move1_PPUp => Entity.Move1_PPUps;
|
||||
public int Move2_PPUp => Entity.Move2_PPUps;
|
||||
public int Move3_PPUp => Entity.Move3_PPUps;
|
||||
public int Move4_PPUp => Entity.Move4_PPUps;
|
||||
public string Relearn1 => Get(Strings.movelist, Entity.RelearnMove1);
|
||||
public string Relearn2 => Get(Strings.movelist, Entity.RelearnMove2);
|
||||
public string Relearn3 => Get(Strings.movelist, Entity.RelearnMove3);
|
||||
public string Relearn4 => Get(Strings.movelist, Entity.RelearnMove4);
|
||||
public ushort Checksum => Entity is ISanityChecksum s ? s.Checksum : Checksums.CRC16_CCITT(Entity.Data[Entity.SIZE_STORED..]);
|
||||
public int Friendship => Entity.OriginalTrainerFriendship;
|
||||
public int EggYear => Entity.EggMetDate.GetValueOrDefault().Year;
|
||||
public int EggMonth => Entity.EggMetDate.GetValueOrDefault().Month;
|
||||
public int EggDay => Entity.EggMetDate.GetValueOrDefault().Day;
|
||||
public int MetYear => Entity.MetDate.GetValueOrDefault().Year;
|
||||
public int MetMonth => Entity.MetDate.GetValueOrDefault().Month;
|
||||
public int MetDay => Entity.MetDate.GetValueOrDefault().Day;
|
||||
public int TID => pk.DisplayTID;
|
||||
public int SID => pk.DisplaySID;
|
||||
public int TSV => pk.TSV;
|
||||
public int Move1_PP => pk.Move1_PP;
|
||||
public int Move2_PP => pk.Move2_PP;
|
||||
public int Move3_PP => pk.Move3_PP;
|
||||
public int Move4_PP => pk.Move4_PP;
|
||||
public int Move1_PPUp => pk.Move1_PPUps;
|
||||
public int Move2_PPUp => pk.Move2_PPUps;
|
||||
public int Move3_PPUp => pk.Move3_PPUps;
|
||||
public int Move4_PPUp => pk.Move4_PPUps;
|
||||
public string Relearn1 => Get(Strings.movelist, pk.RelearnMove1);
|
||||
public string Relearn2 => Get(Strings.movelist, pk.RelearnMove2);
|
||||
public string Relearn3 => Get(Strings.movelist, pk.RelearnMove3);
|
||||
public string Relearn4 => Get(Strings.movelist, pk.RelearnMove4);
|
||||
public ushort Checksum => pk is ISanityChecksum s ? s.Checksum : Checksums.CRC16_CCITT(pk.Data.AsSpan(pk.SIZE_STORED));
|
||||
public int Friendship => pk.OT_Friendship;
|
||||
public int Egg_Year => pk.EggMetDate.GetValueOrDefault().Year;
|
||||
public int Egg_Month => pk.EggMetDate.GetValueOrDefault().Month;
|
||||
public int Egg_Day => pk.EggMetDate.GetValueOrDefault().Day;
|
||||
public int Met_Year => pk.MetDate.GetValueOrDefault().Year;
|
||||
public int Met_Month => pk.MetDate.GetValueOrDefault().Month;
|
||||
public int Met_Day => pk.MetDate.GetValueOrDefault().Day;
|
||||
|
||||
#endregion
|
||||
|
||||
protected EntitySummary(PKM pk, GameStrings strings)
|
||||
protected EntitySummary(PKM p, GameStrings strings)
|
||||
{
|
||||
Entity = pk;
|
||||
pk = p;
|
||||
Strings = strings;
|
||||
Stats = Entity.GetStats(Entity.PersonalInfo);
|
||||
Legality = new LegalityAnalysis(Entity);
|
||||
Stats = pk.GetStats(pk.PersonalInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -15,20 +17,22 @@ public static void TemplateFields(PKM pk, ITrainerInfo tr)
|
|||
pk.Move1 = (int)Move.Pound;
|
||||
pk.HealPP();
|
||||
pk.Ball = 4;
|
||||
if (pk.Format >= 4)
|
||||
pk.MetDate = EncounterDate.GetDate(pk.Context.Console);
|
||||
pk.MetDate = DateTime.Today;
|
||||
|
||||
if (tr.Game >= 0)
|
||||
pk.Version = tr.Game;
|
||||
|
||||
pk.Version = GetTemplateVersion(tr);
|
||||
pk.Species = GetTemplateSpecies(pk, tr);
|
||||
pk.Language = GetTemplateLanguage(tr);
|
||||
pk.Gender = pk.GetSaneGender();
|
||||
|
||||
pk.ClearNickname();
|
||||
|
||||
pk.OriginalTrainerName = tr.OT;
|
||||
pk.OriginalTrainerGender = tr.Gender;
|
||||
pk.ID32 = tr.ID32;
|
||||
if (tr is IRegionOriginReadOnly o && pk is IRegionOrigin gt)
|
||||
pk.OT_Name = tr.OT;
|
||||
pk.OT_Gender = tr.Gender;
|
||||
pk.TID = tr.TID;
|
||||
pk.SID = tr.SID;
|
||||
if (tr is IRegionOrigin o && pk is IRegionOrigin gt)
|
||||
{
|
||||
gt.ConsoleRegion = o.ConsoleRegion;
|
||||
gt.Country = o.Country;
|
||||
|
|
@ -39,17 +43,6 @@ public static void TemplateFields(PKM pk, ITrainerInfo tr)
|
|||
pk.RefreshChecksum();
|
||||
}
|
||||
|
||||
private static GameVersion GetTemplateVersion(ITrainerInfo tr)
|
||||
{
|
||||
var version = tr.Version;
|
||||
if (version.IsValidSavedVersion())
|
||||
return version;
|
||||
version = version.GetSingleVersion();
|
||||
if (version.IsValidSavedVersion())
|
||||
return version;
|
||||
return default; // 0
|
||||
}
|
||||
|
||||
private static void ApplyTrashBytes(PKM pk, ITrainerInfo tr)
|
||||
{
|
||||
// Copy OT trash bytes for sensitive games (Gen1/2)
|
||||
|
|
@ -58,17 +51,17 @@ private static void ApplyTrashBytes(PKM pk, ITrainerInfo tr)
|
|||
switch (tr)
|
||||
{
|
||||
case SAV1 s1:
|
||||
s1.OriginalTrainerTrash.CopyTo(pk12.OriginalTrainerTrash);
|
||||
s1.OT_Trash.CopyTo(pk12.OT_Trash);
|
||||
break;
|
||||
case SAV2 s2:
|
||||
s2.OriginalTrainerTrash.CopyTo(pk12.OriginalTrainerTrash);
|
||||
s2.OT_Trash.CopyTo(pk12.OT_Trash);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static ushort GetTemplateSpecies(PKM pk, ITrainerInfo tr)
|
||||
{
|
||||
ushort species = tr is IGameValueLimit s ? s.MaxSpeciesID : pk.Version.GetMaxSpeciesID();
|
||||
ushort species = tr is IGameValueLimit s ? s.MaxSpeciesID : ((GameVersion)pk.Version).GetMaxSpeciesID();
|
||||
if (species == 0)
|
||||
species = pk.MaxSpeciesID;
|
||||
return species;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
@ -9,16 +10,16 @@ namespace PKHeX.Core;
|
|||
public sealed class LegalMoveComboSource : ILegalMoveDisplaySource<ComboItem>
|
||||
{
|
||||
private readonly bool[] IsMoveBoxOrdered = new bool[4];
|
||||
private ComboItem[] MoveDataAllowed = [];
|
||||
private ComboItem[] MoveDataAllowed = Array.Empty<ComboItem>();
|
||||
|
||||
public IReadOnlyList<ComboItem> DataSource => [.. MoveDataAllowed]; // copy
|
||||
public IReadOnlyList<ComboItem> DataSource => (ComboItem[])MoveDataAllowed.Clone();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the <see cref="MoveDataAllowed"/> data source with an updated collection.
|
||||
/// </summary>
|
||||
public void ReloadMoves(IReadOnlyList<ComboItem> moves)
|
||||
{
|
||||
MoveDataAllowed = [.. moves]; // copy
|
||||
MoveDataAllowed = moves.ToArray();
|
||||
ClearUpdateCheck();
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +35,7 @@ public void ReloadMoves(LegalMoveInfo info)
|
|||
private void SortMoves(LegalMoveInfo info) => Array.Sort(MoveDataAllowed, (i1, i2) => Compare(i1, i2, info.CanLearn));
|
||||
|
||||
// defer re-population until dropdown is opened; handled by dropdown event
|
||||
private void ClearUpdateCheck() => IsMoveBoxOrdered.AsSpan().Clear();
|
||||
private void ClearUpdateCheck() => Array.Clear(IsMoveBoxOrdered, 0, IsMoveBoxOrdered.Length);
|
||||
|
||||
private static int Compare(ComboItem i1, ComboItem i2, Func<ushort, bool> check)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using static PKHeX.Core.IndicatedSourceType;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
@ -9,16 +8,16 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
public sealed class LegalMoveInfo
|
||||
{
|
||||
// Use a byte array instead of a HashSet; we have a limited range of moves.
|
||||
// Use a bool array instead of a HashSet; we have a limited range of moves.
|
||||
// This implementation is faster (no hashcode or bucket search) with lower memory overhead (1 byte per move ID).
|
||||
private readonly IndicatedSourceType[] AllowedMoves = new IndicatedSourceType[(int)Move.MAX_COUNT + 1];
|
||||
private readonly bool[] AllowedMoves = new bool[(int)Move.MAX_COUNT + 1];
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the requested <see cref="move"/> is legally able to be learned.
|
||||
/// </summary>
|
||||
/// <param name="move">Move to check if it can be learned</param>
|
||||
/// <returns>True if it can learn the move</returns>
|
||||
public bool CanLearn(ushort move) => AllowedMoves[move] != None;
|
||||
/// <param name="move">Move to check if can be learned</param>
|
||||
/// <returns>True if can learn the move</returns>
|
||||
public bool CanLearn(ushort move) => AllowedMoves[move];
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the legality sources to permit the provided legal info.
|
||||
|
|
@ -26,89 +25,16 @@ public sealed class LegalMoveInfo
|
|||
/// <param name="la">Details of analysis, moves to allow</param>
|
||||
public bool ReloadMoves(LegalityAnalysis la)
|
||||
{
|
||||
var rentLearn = ArrayPool<bool>.Shared.Rent(AllowedMoves.Length);
|
||||
var spanLearn = rentLearn.AsSpan(0, AllowedMoves.Length);
|
||||
var rentEval = ArrayPool<IndicatedSourceType>.Shared.Rent(spanLearn.Length);
|
||||
var spanEval = rentEval.AsSpan(0, spanLearn.Length);
|
||||
try
|
||||
{
|
||||
LearnPossible.Get(la.Entity, la.EncounterOriginal, la.Info.EvoChainsAllGens, spanLearn);
|
||||
ComputeEval(spanEval, spanLearn, la);
|
||||
if (spanEval.SequenceEqual(AllowedMoves))
|
||||
return false;
|
||||
spanEval.CopyTo(AllowedMoves);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (Array.TrueForAll(AllowedMoves, z => z == None))
|
||||
return false;
|
||||
AllowedMoves.AsSpan().Clear();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
spanLearn.Clear();
|
||||
spanEval.Clear();
|
||||
ArrayPool<IndicatedSourceType>.Shared.Return(rentEval);
|
||||
ArrayPool<bool>.Shared.Return(rentLearn);
|
||||
}
|
||||
}
|
||||
var rent = ArrayPool<bool>.Shared.Rent(AllowedMoves.Length);
|
||||
var span = rent.AsSpan(0, AllowedMoves.Length);
|
||||
LearnPossible.Get(la.Entity, la.EncounterOriginal, la.Info.EvoChainsAllGens, span);
|
||||
|
||||
private static void ComputeEval(Span<IndicatedSourceType> type, ReadOnlySpan<bool> learn, LegalityAnalysis la)
|
||||
{
|
||||
for (int i = 0; i < type.Length; i++)
|
||||
type[i] = learn[i] ? Learn : None;
|
||||
|
||||
if (!la.Entity.IsOriginalMovesetDeleted())
|
||||
AddEncounterMoves(type, la.EncounterOriginal);
|
||||
|
||||
type[0] = None; // Move ID 0 is always None
|
||||
}
|
||||
|
||||
private static void AddEncounterMoves(Span<IndicatedSourceType> type, IEncounterTemplate enc)
|
||||
{
|
||||
if (enc is IEncounterEgg egg)
|
||||
{
|
||||
var moves = egg.Learn.GetEggMoves(enc.Species, enc.Form);
|
||||
foreach (var move in moves)
|
||||
type[move] = Egg;
|
||||
}
|
||||
else if (enc is IMoveset {Moves: {HasMoves: true} set})
|
||||
{
|
||||
foreach (var move in set.AsSpan())
|
||||
{
|
||||
if (type[move] == None)
|
||||
type[move] = Encounter;
|
||||
}
|
||||
}
|
||||
else if (enc is ISingleMoveBonus single)
|
||||
{
|
||||
var moves = single.GetMoveBonusPossible();
|
||||
foreach (var move in moves)
|
||||
{
|
||||
if (type[move] == None)
|
||||
type[move] = EncounterSingle;
|
||||
}
|
||||
}
|
||||
|
||||
if (enc is IRelearn { Relearn: {HasMoves: true} relearn})
|
||||
{
|
||||
foreach (var move in relearn.AsSpan())
|
||||
{
|
||||
if (type[move] == None)
|
||||
type[move] = Relearn;
|
||||
}
|
||||
}
|
||||
// check prior move-pool to not needlessly refresh the data set
|
||||
bool diff = !span.SequenceEqual(AllowedMoves);
|
||||
if (diff) // keep
|
||||
span.CopyTo(AllowedMoves);
|
||||
span.Clear();
|
||||
ArrayPool<bool>.Shared.Return(rent);
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
|
||||
public enum IndicatedSourceType : byte
|
||||
{
|
||||
None = 0,
|
||||
Learn,
|
||||
Egg,
|
||||
Encounter,
|
||||
EncounterSingle,
|
||||
Relearn,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Legal Move information for a single <see cref="PKM"/>, for indicating if a move is legal or not.
|
||||
/// </summary>
|
||||
public sealed class LegalMoveSource<T>(ILegalMoveDisplaySource<T> Display)
|
||||
public sealed class LegalMoveSource<T>
|
||||
{
|
||||
public LegalMoveInfo Info { get; } = new();
|
||||
public readonly ILegalMoveDisplaySource<T> Display = Display;
|
||||
public readonly ILegalMoveDisplaySource<T> Display;
|
||||
|
||||
public LegalMoveSource(ILegalMoveDisplaySource<T> display) => Display = display;
|
||||
|
||||
public void ReloadMoves(LegalityAnalysis source)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
@ -12,8 +11,8 @@ public static class QRMessageUtil
|
|||
private const string QR6PathBad = "null/#";
|
||||
private const string QR6Path = "http://lunarcookies.github.io/b1s1.html#";
|
||||
private const string QR6PathWC = "http://lunarcookies.github.io/wc.html#";
|
||||
private static string GetExploitURLPrefixPKM(byte format) => format == 6 ? QR6Path : QR6PathBad;
|
||||
private static string GetExploitURLPrefixWC(byte format) => format == 6 ? QR6PathWC : QR6PathBad;
|
||||
private static string GetExploitURLPrefixPKM(int format) => format == 6 ? QR6Path : QR6PathBad;
|
||||
private static string GetExploitURLPrefixWC(int format) => format == 6 ? QR6PathWC : QR6PathBad;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="PKM"/> data from the message that is encoded in a QR.
|
||||
|
|
@ -21,10 +20,10 @@ public static class QRMessageUtil
|
|||
/// <param name="message">QR Message</param>
|
||||
/// <param name="context">Preferred <see cref="PKM.Context"/> to expect.</param>
|
||||
/// <returns>Decoded <see cref="PKM"/> object, null if invalid.</returns>
|
||||
public static PKM? GetPKM(ReadOnlySpan<char> message, EntityContext context)
|
||||
public static PKM? GetPKM(string message, EntityContext context)
|
||||
{
|
||||
var data = DecodeMessagePKM(message);
|
||||
if (data is null)
|
||||
if (data == null)
|
||||
return null;
|
||||
return EntityFormat.GetFromBytes(data, context);
|
||||
}
|
||||
|
|
@ -37,21 +36,16 @@ public static class QRMessageUtil
|
|||
public static string GetMessage(PKM pk)
|
||||
{
|
||||
if (pk is PK7 pk7)
|
||||
return GetMessage(pk7);
|
||||
{
|
||||
byte[] payload = QR7.GenerateQRData(pk7);
|
||||
return GetMessage(payload);
|
||||
}
|
||||
|
||||
var server = GetExploitURLPrefixPKM(pk.Format);
|
||||
var data = pk.EncryptedBoxData;
|
||||
return GetMessageBase64(data, server);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetMessage(PKM)"/>
|
||||
public static string GetMessage(PK7 pk7, int box = 0, int slot = 0, int num_copies = 1)
|
||||
{
|
||||
Span<byte> data = stackalloc byte[QR7.SIZE];
|
||||
QR7.SetQRData(pk7, data, box, slot, num_copies);
|
||||
return GetMessage(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a QR Message from the input <see cref="byte"/> data.
|
||||
/// </summary>
|
||||
|
|
@ -59,10 +53,10 @@ public static string GetMessage(PK7 pk7, int box = 0, int slot = 0, int num_copi
|
|||
/// <returns>QR Message</returns>
|
||||
public static string GetMessage(ReadOnlySpan<byte> payload)
|
||||
{
|
||||
Span<char> result = stackalloc char[payload.Length];
|
||||
for (int i = 0; i < payload.Length; i++)
|
||||
result[i] = (char)payload[i];
|
||||
return new string(result);
|
||||
var sb = new StringBuilder(payload.Length);
|
||||
foreach (var b in payload)
|
||||
sb.Append((char)b);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -77,19 +71,13 @@ public static string GetMessage(DataMysteryGift mg)
|
|||
return GetMessageBase64(data, server);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a QR Message from the input <see cref="byte"/> data.
|
||||
/// </summary>
|
||||
/// <param name="data">Data to include in the payload</param>
|
||||
/// <param name="server">Website URL that sources the payload</param>
|
||||
/// <returns>QR Message</returns>
|
||||
public static string GetMessageBase64(ReadOnlySpan<byte> data, string server)
|
||||
public static string GetMessageBase64(byte[] data, string server)
|
||||
{
|
||||
string payload = Convert.ToBase64String(data);
|
||||
return server + payload;
|
||||
}
|
||||
|
||||
private static byte[]? DecodeMessagePKM(ReadOnlySpan<char> message)
|
||||
private static byte[]? DecodeMessagePKM(string message)
|
||||
{
|
||||
if (message.Length < 32) // arbitrary length check; everything should be greater than this
|
||||
return null;
|
||||
|
|
@ -98,40 +86,37 @@ public static string GetMessageBase64(ReadOnlySpan<byte> data, string server)
|
|||
if (message.StartsWith("http", StringComparison.Ordinal)) // inject url
|
||||
return DecodeMessageDataBase64(message);
|
||||
|
||||
const int g7size = PokeCrypto.SIZE_6STORED; // 0xE8;
|
||||
const int g7size = 0xE8;
|
||||
const int g7intro = 0x30;
|
||||
if (message.StartsWith("POKE", StringComparison.Ordinal) && message.Length > g7intro + g7size) // G7 data
|
||||
return GetBytesFromMessage(message[g7intro..], g7size);
|
||||
return GetBytesFromMessage(message.AsSpan(g7intro), g7size);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static byte[]? DecodeMessageDataBase64(ReadOnlySpan<char> url)
|
||||
private static byte[]? DecodeMessageDataBase64(string url)
|
||||
{
|
||||
int payloadBegin = url.IndexOf('#');
|
||||
if (payloadBegin == -1)
|
||||
return null; // bad URL, need the payload separator
|
||||
if (payloadBegin == url.Length - 1)
|
||||
return null; // no payload
|
||||
if (url.Length == 0 || url[^1] == '#')
|
||||
return null;
|
||||
|
||||
url = url[(payloadBegin + 1)..]; // Trim URL to right after #
|
||||
// Decode unicode string -- size might be pretty big (user input), so just rent instead of stackalloc
|
||||
var tmp = ArrayPool<byte>.Shared.Rent(url.Length);
|
||||
var result = Convert.TryFromBase64Chars(url, tmp, out int bytesWritten) ? tmp[..bytesWritten] : null;
|
||||
ArrayPool<byte>.Shared.Return(tmp, true);
|
||||
return result;
|
||||
try
|
||||
{
|
||||
int payloadBegin = url.IndexOf('#');
|
||||
if (payloadBegin < 0) // bad URL, need the payload separator
|
||||
return null;
|
||||
url = url[(payloadBegin + 1)..]; // Trim URL to right after #
|
||||
return Convert.FromBase64String(url);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] GetBytesFromMessage(ReadOnlySpan<char> input, int count)
|
||||
{
|
||||
byte[] result = new byte[count];
|
||||
GetBytesFromMessage(input, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void GetBytesFromMessage(ReadOnlySpan<char> input, Span<byte> output)
|
||||
{
|
||||
Debug.Assert(input.Length >= output.Length);
|
||||
for (int i = 0; i < output.Length; i++)
|
||||
output[i] = (byte)input[i];
|
||||
byte[] data = new byte[count];
|
||||
for (int i = data.Length - 1; i >= 0; i--)
|
||||
data[i] = (byte)input[i];
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user