mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-03-28 21:15:36 -05:00
Compare commits
120 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aad2839736 | ||
|
|
49505a4a8c | ||
|
|
ecfd8f6748 | ||
|
|
5bf1e2cf45 | ||
|
|
793525875f | ||
|
|
492aea166e | ||
|
|
83071ca7c2 | ||
|
|
6df330e62f | ||
|
|
66df00d038 | ||
|
|
4784e2de82 | ||
|
|
a43b6a5d32 | ||
|
|
48938c5e14 | ||
|
|
0e097b1fc6 | ||
|
|
56ba92b68b | ||
|
|
3ad376be44 | ||
|
|
ca6fbf024c | ||
|
|
3c1e7bdc6c | ||
|
|
b1dbc6a82b | ||
|
|
94ad477703 | ||
|
|
42496def98 | ||
|
|
8950613422 | ||
|
|
2faa1b10b1 | ||
|
|
ff69f82234 | ||
|
|
3317a8bfda | ||
|
|
94f3937f2f | ||
|
|
a3e0ed29b4 | ||
|
|
d3a4ed6ad3 | ||
|
|
bb363a7a3d | ||
|
|
2f95536b13 | ||
|
|
301a1e7664 | ||
|
|
662c3db7dc | ||
|
|
5b42ff746d | ||
|
|
c7f02bcc20 | ||
|
|
7617f6dfa7 | ||
|
|
8b08f263e5 | ||
|
|
d827cec5a7 | ||
|
|
e5e7cc914c | ||
|
|
ff72af52ad | ||
|
|
d24d227df4 | ||
|
|
5a75fe4b89 | ||
|
|
49d9467d3c | ||
|
|
065d329546 | ||
|
|
93b9481393 | ||
|
|
244b34b8d3 | ||
|
|
3e33f0fc2e | ||
|
|
3e33521796 | ||
|
|
79a08822ea | ||
|
|
85ad6495e6 | ||
|
|
04c2063791 | ||
|
|
dd0d1fc07a | ||
|
|
c64bc65359 | ||
|
|
8587a88723 | ||
|
|
e56226f046 | ||
|
|
6e48856bec | ||
|
|
51a012ff78 | ||
|
|
bd9f64b07e | ||
|
|
b1dd981537 | ||
|
|
553f154657 | ||
|
|
b6eb0745a3 | ||
|
|
f382291de4 | ||
|
|
df39aff5e9 | ||
|
|
20a92f533b | ||
|
|
b93d57cc9a | ||
|
|
2939bfae48 | ||
|
|
0d96d75b7a | ||
|
|
8f0672b8c5 | ||
|
|
9420dcf44d | ||
|
|
bde5729883 | ||
|
|
fa0ac2a9ab | ||
|
|
c037829b29 | ||
|
|
5c4d27f7e4 | ||
|
|
5c9f97b7fd | ||
|
|
ea36292994 | ||
|
|
7fad9a0c47 | ||
|
|
2f456fceb3 | ||
|
|
306c1329ed | ||
|
|
07a826292c | ||
|
|
3371c791ef | ||
|
|
2559f96439 | ||
|
|
2210068013 | ||
|
|
31b7b7f723 | ||
|
|
364e014848 | ||
|
|
3fc2971df1 | ||
|
|
d690f1c5d3 | ||
|
|
1aedb012ac | ||
|
|
395bc1b1e9 | ||
|
|
11c4fe446e | ||
|
|
ddba4dae44 | ||
|
|
2efa19e5e3 | ||
|
|
0b42f57534 | ||
|
|
0757ca3a5d | ||
|
|
6f9daaed04 | ||
|
|
f14cfaf08d | ||
|
|
b6ae27e4e8 | ||
|
|
13fc0cdfeb | ||
|
|
aab826ef13 | ||
|
|
a9f449ff01 | ||
|
|
29a08bf988 | ||
|
|
53c684a223 | ||
|
|
f6bae2b8d7 | ||
|
|
1d25b78a19 | ||
|
|
ceb420a2a1 | ||
|
|
9792455f34 | ||
|
|
387c254aa4 | ||
|
|
1e5663433a | ||
|
|
1ec0b22a0a | ||
|
|
782ee643d6 | ||
|
|
7f51c125bd | ||
|
|
4ecd51e826 | ||
|
|
3fa6ab9c23 | ||
|
|
20905cbe67 | ||
|
|
0f8321cda4 | ||
|
|
09d7fd9e31 | ||
|
|
dd1b55cb6a | ||
|
|
51711bb659 | ||
|
|
0982e54dac | ||
|
|
3ef46268e9 | ||
|
|
c5b7eb4c7d | ||
|
|
916521f906 | ||
|
|
91ac18dd34 |
48
.github/README-fr.md
vendored
48
.github/README-fr.md
vendored
|
|
@ -1,48 +1,48 @@
|
|||
PKHeX
|
||||
=====
|
||||

|
||||

|
||||
|
||||
Éditeur de sauvegarde de la série de base Pokémon, programmé en [C#](https://fr.wikipedia.org/wiki/C_sharp).
|
||||
Éditeur de sauvegarde des jeux principaux de la série Pokémon, programmé en [C#](https://fr.wikipedia.org/wiki/C_sharp).
|
||||
|
||||
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.
|
||||
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\*) 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.
|
||||
* 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.
|
||||
|
||||
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 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 ensembles Pokémon Showdown et les QR codes peuvent être importés/exportés pour faciliter le partage.
|
||||
Les sets Pokémon Showdown! et les QR codes peuvent être importés/exportés pour faciliter le partage.
|
||||
|
||||
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) ou [JKSM](https://github.com/J-D-K/JKSM)).
|
||||
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).
|
||||
|
||||
**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.**
|
||||
**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.**
|
||||
|
||||
## Captures d'écran
|
||||
## Capture d'écran
|
||||
|
||||

|
||||

|
||||
|
||||
## Construction
|
||||
## Compilation
|
||||
|
||||
PKHeX est une application Windows Forms qui nécessite [.NET 10.0](https://dotnet.microsoft.com/download/dotnet/10.0).
|
||||
PKHeX est une application Windows Forms qui nécessite [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0).
|
||||
|
||||
L'exécutable peut être construit avec n'importe quel compilateur prenant en charge C# 14.
|
||||
L'exécutable peut être compilé avec n'importe quel compilateur prenant en charge C# 14.
|
||||
|
||||
### Construire les configurations
|
||||
### Configurations de la compilation
|
||||
|
||||
Utilisez les configurations Debug ou Release lors de la construction. Il n'y a pas de code spécifique à la plate-forme à craindre!
|
||||
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 !
|
||||
|
||||
## Dépendances
|
||||
|
||||
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).
|
||||
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).
|
||||
|
||||
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 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).
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## IDE
|
||||
|
||||
PKHeX peut être ouvert avec des IDE tels que [Visual Studio](https://visualstudio.microsoft.com/fr/downloads/) en ouvrant le fichier .sln ou .csproj.
|
||||
PKHeX peut être ouvert avec des IDEs tels que [Visual Studio](https://visualstudio.microsoft.com/fr/downloads/) en ouvrant le fichier .sln ou .csproj.
|
||||
|
|
|
|||
2
.github/README-it.md
vendored
2
.github/README-it.md
vendored
|
|
@ -9,7 +9,7 @@ Supporta i seguenti tipi di file:
|
|||
* 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 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.
|
||||
|
||||
|
|
|
|||
2
.github/README-zh-Hans.md
vendored
2
.github/README-zh-Hans.md
vendored
|
|
@ -9,7 +9,7 @@ PKHeX
|
|||
* GameCube 宝可梦游戏存档包含 GameCube 记忆存档 (\*.raw, \*.bin)
|
||||
* 单个宝可梦实体文件 (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4)
|
||||
* 神秘礼物文件 (\*.pgt, \*.pcd, \*.pgf, .wc\*) 并转换为 .pk\*
|
||||
* 导入 Go Park存档 (\*.gp1) 并转换为 .pb7
|
||||
* 导入 GO Park存档 (\*.gp1) 并转换为 .pb7
|
||||
* 从已破解的 3DS 对战视频中导入队伍
|
||||
* 支持宝可梦在不同世代的间转移,并转换文件格式
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>26.01.31</Version>
|
||||
<Version>26.03.20</Version>
|
||||
<LangVersion>14</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
|
|
|
|||
|
|
@ -107,21 +107,29 @@ public void SetMasteredFlag(Learnset learn, Learnset mastery, byte level, int in
|
|||
if (shop.GetMasteredRecordFlag(index))
|
||||
return;
|
||||
|
||||
if (learn.TryGetLevelLearnMove(move, out var learnLevel) && level < learnLevel) // Can't learn it yet; must purchase.
|
||||
if (learn.TryGetLevelLearnMove(move, out var learnLevel))
|
||||
{
|
||||
shop.SetPurchasedRecordFlag(index, true);
|
||||
shop.SetMasteredRecordFlag(index, true);
|
||||
return;
|
||||
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);
|
||||
}
|
||||
|
||||
if (mastery.TryGetLevelLearnMove(move, out var masterLevel) && level < masterLevel) // Can't master it yet; must Seed of Mastery
|
||||
shop.SetMasteredRecordFlag(index, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the "mastered" move shop flag for the encounter.
|
||||
/// </summary>
|
||||
public void SetEncounterMasteryFlags(ReadOnlySpan<ushort> moves, Learnset mastery, byte level)
|
||||
public void SetEncounterMasteryFlags(ReadOnlySpan<ushort> moves, Learnset mastery, byte metLevel, ushort alphaMove)
|
||||
{
|
||||
var permit = shop.Permit;
|
||||
var possible = permit.RecordPermitIndexes;
|
||||
|
|
@ -137,7 +145,14 @@ public void SetEncounterMasteryFlags(ReadOnlySpan<ushort> moves, Learnset master
|
|||
// 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) || level >= masteryLevel)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -145,14 +160,28 @@ public void SetEncounterMasteryFlags(ReadOnlySpan<ushort> moves, Learnset master
|
|||
/// <summary>
|
||||
/// Sets the "purchased" move shop flag for all possible moves.
|
||||
/// </summary>
|
||||
public void SetPurchasedFlagsAll()
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ public static string GetStringFromForm(byte form, GameStrings strings, ushort sp
|
|||
if (form == 0)
|
||||
return string.Empty;
|
||||
|
||||
var result = FormConverter.GetStringFromForm(form, strings, species, genderForms, context);
|
||||
var result = FormConverter.GetStringFromForm(species, form, strings, genderForms, context);
|
||||
if (result.Length == 0)
|
||||
return string.Empty;
|
||||
|
||||
|
|
|
|||
|
|
@ -299,35 +299,35 @@ private void ParseLineAbilityBracket(ReadOnlySpan<char> line, GameStrings locali
|
|||
Ability = abilityIndex;
|
||||
}
|
||||
|
||||
private bool ParseEntry(BattleTemplateToken token, ReadOnlySpan<char> value, BattleTemplateLocalization localization) => token switch
|
||||
private bool ParseEntry(BattleTemplateToken token, ReadOnlySpan<char> input, BattleTemplateLocalization localization) => token switch
|
||||
{
|
||||
BattleTemplateToken.Ability => ParseLineAbility(value, localization.Strings.abilitylist),
|
||||
BattleTemplateToken.Nature => ParseLineNature(value, localization.Strings.natures),
|
||||
BattleTemplateToken.Ability => ParseLineAbility(input, localization.Strings.abilitylist),
|
||||
BattleTemplateToken.Nature => ParseLineNature(input, localization.Strings.natures),
|
||||
BattleTemplateToken.Shiny => Shiny = true,
|
||||
BattleTemplateToken.Gigantamax => CanGigantamax = true,
|
||||
BattleTemplateToken.HeldItem => ParseItemName(value, localization.Strings),
|
||||
BattleTemplateToken.Nickname => ParseNickname(value),
|
||||
BattleTemplateToken.Gender => ParseGender(value, localization.Config),
|
||||
BattleTemplateToken.Friendship => ParseFriendship(value),
|
||||
BattleTemplateToken.EVs => ParseLineEVs(value, localization),
|
||||
BattleTemplateToken.IVs => ParseLineIVs(value, localization.Config),
|
||||
BattleTemplateToken.Level => ParseLevel(value),
|
||||
BattleTemplateToken.DynamaxLevel => ParseDynamax(value),
|
||||
BattleTemplateToken.TeraType => ParseTeraType(value, localization.Strings.types),
|
||||
BattleTemplateToken.HeldItem => ParseItemName(input, localization.Strings),
|
||||
BattleTemplateToken.Nickname => ParseNickname(input),
|
||||
BattleTemplateToken.Gender => ParseGender(input, localization.Config),
|
||||
BattleTemplateToken.Friendship => ParseFriendship(input),
|
||||
BattleTemplateToken.EVs => ParseLineEVs(input, localization),
|
||||
BattleTemplateToken.IVs => ParseLineIVs(input, localization.Config),
|
||||
BattleTemplateToken.Level => ParseLevel(input),
|
||||
BattleTemplateToken.DynamaxLevel => ParseDynamax(input),
|
||||
BattleTemplateToken.TeraType => ParseTeraType(input, localization.Strings.types),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
private bool ParseLineAbility(ReadOnlySpan<char> value, ReadOnlySpan<string> abilityNames)
|
||||
private bool ParseLineAbility(ReadOnlySpan<char> input, ReadOnlySpan<string> abilityNames)
|
||||
{
|
||||
var index = StringUtil.FindIndexIgnoreCase(abilityNames, value);
|
||||
var index = StringUtil.FindIndexIgnoreCase(abilityNames, input);
|
||||
if (index < 0)
|
||||
{
|
||||
LogError(AbilityUnrecognized, value);
|
||||
LogError(AbilityUnrecognized, input);
|
||||
return false;
|
||||
}
|
||||
if (Ability != -1 && Ability != index)
|
||||
{
|
||||
LogError(AbilityAlreadySpecified, value);
|
||||
LogError(AbilityAlreadySpecified, input);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -335,21 +335,21 @@ private bool ParseLineAbility(ReadOnlySpan<char> value, ReadOnlySpan<string> abi
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool ParseLineNature(ReadOnlySpan<char> value, ReadOnlySpan<string> natureNames)
|
||||
private bool ParseLineNature(ReadOnlySpan<char> input, ReadOnlySpan<string> natureNames)
|
||||
{
|
||||
var index = StringUtil.FindIndexIgnoreCase(natureNames, value);
|
||||
var index = StringUtil.FindIndexIgnoreCase(natureNames, input);
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
var nature = (Nature)index;
|
||||
if (!nature.IsFixed())
|
||||
if (!nature.IsFixed)
|
||||
{
|
||||
LogError(NatureUnrecognized, value);
|
||||
LogError(NatureUnrecognized, input);
|
||||
return false;
|
||||
}
|
||||
if (Nature != Nature.Random && Nature != nature)
|
||||
if (Nature.IsFixed && Nature != nature)
|
||||
{
|
||||
LogError(NatureAlreadySpecified, value);
|
||||
LogError(NatureAlreadySpecified, input);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -357,23 +357,23 @@ private bool ParseLineNature(ReadOnlySpan<char> value, ReadOnlySpan<string> natu
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool ParseNickname(ReadOnlySpan<char> value)
|
||||
private bool ParseNickname(ReadOnlySpan<char> input)
|
||||
{
|
||||
if (value.Length == 0)
|
||||
if (input.Length == 0)
|
||||
return false;
|
||||
// ignore length, but generally should be <= the Context's max length
|
||||
Nickname = value.ToString();
|
||||
Nickname = input.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ParseGender(ReadOnlySpan<char> value, BattleTemplateConfig cfg)
|
||||
private bool ParseGender(ReadOnlySpan<char> input, BattleTemplateConfig cfg)
|
||||
{
|
||||
if (value.Equals(cfg.Male, StringComparison.OrdinalIgnoreCase))
|
||||
if (input.Equals(cfg.Male, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Gender = EntityGender.Male;
|
||||
return true;
|
||||
}
|
||||
if (value.Equals(cfg.Female, StringComparison.OrdinalIgnoreCase))
|
||||
if (input.Equals(cfg.Female, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Gender = EntityGender.Female;
|
||||
return true;
|
||||
|
|
@ -381,43 +381,43 @@ private bool ParseGender(ReadOnlySpan<char> value, BattleTemplateConfig cfg)
|
|||
return false;
|
||||
}
|
||||
|
||||
private bool ParseLevel(ReadOnlySpan<char> value)
|
||||
private bool ParseLevel(ReadOnlySpan<char> input)
|
||||
{
|
||||
if (!byte.TryParse(value.Trim(), out var val))
|
||||
if (!byte.TryParse(input.Trim(), out var value))
|
||||
return false;
|
||||
if ((uint)val is 0 or > Experience.MaxLevel)
|
||||
if ((uint)value is 0 or > Experience.MaxLevel)
|
||||
return false;
|
||||
Level = val;
|
||||
Level = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ParseFriendship(ReadOnlySpan<char> value)
|
||||
private bool ParseFriendship(ReadOnlySpan<char> input)
|
||||
{
|
||||
if (!byte.TryParse(value.Trim(), out var val))
|
||||
if (!byte.TryParse(input.Trim(), out var value))
|
||||
return false;
|
||||
Friendship = val;
|
||||
Friendship = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ParseDynamax(ReadOnlySpan<char> value)
|
||||
private bool ParseDynamax(ReadOnlySpan<char> input)
|
||||
{
|
||||
Context = EntityContext.Gen8;
|
||||
var val = Util.ToInt32(value);
|
||||
if ((uint)val > 10)
|
||||
var value = Util.ToInt32(input);
|
||||
if ((uint)value > 10)
|
||||
return false;
|
||||
DynamaxLevel = (byte)val;
|
||||
DynamaxLevel = (byte)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ParseTeraType(ReadOnlySpan<char> value, ReadOnlySpan<string> types)
|
||||
private bool ParseTeraType(ReadOnlySpan<char> input, ReadOnlySpan<string> types)
|
||||
{
|
||||
Context = EntityContext.Gen9;
|
||||
var val = StringUtil.FindIndexIgnoreCase(types, value);
|
||||
if (val < 0)
|
||||
var value = StringUtil.FindIndexIgnoreCase(types, input);
|
||||
if (value < 0)
|
||||
return false;
|
||||
if (val == TeraTypeUtil.StellarTypeDisplayStringIndex)
|
||||
val = TeraTypeUtil.Stellar;
|
||||
TeraType = (MoveType)val;
|
||||
if (value == TeraTypeUtil.StellarTypeDisplayStringIndex)
|
||||
value = TeraTypeUtil.Stellar;
|
||||
TeraType = (MoveType)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -561,7 +561,7 @@ private void PushToken(BattleTemplateToken token, List<string> result, in Battle
|
|||
result.Add(cfg.Push(token, Friendship));
|
||||
break;
|
||||
case BattleTemplateToken.IVs:
|
||||
var maxIV = Context.Generation < 3 ? 15 : 31;
|
||||
var maxIV = Context.IsEraGameBoy ? 15 : 31;
|
||||
if (!IVs.ContainsAnyExcept(maxIV))
|
||||
break; // skip if all IVs are maxed
|
||||
var nameIVs = cfg.GetStatDisplay(settings.StatsIVs);
|
||||
|
|
@ -627,7 +627,7 @@ private void AddEVs(List<string> result, in BattleTemplateExportSettings setting
|
|||
BattleTemplateToken.EVsAppendNature => GetStringStatsNatureAmp(EVs, 0, nameEVs, Nature),
|
||||
_ => GetStringStats(EVs, 0, nameEVs),
|
||||
};
|
||||
if (token is BattleTemplateToken.EVsAppendNature && Nature.IsFixed())
|
||||
if (token is BattleTemplateToken.EVsAppendNature && Nature.IsFixed)
|
||||
line += $" ({settings.Localization.Strings.natures[(int)Nature]})";
|
||||
result.Add(cfg.Push(BattleTemplateToken.EVs, line));
|
||||
}
|
||||
|
|
@ -1028,7 +1028,7 @@ private ReadOnlySpan<char> ParseLineMove(ReadOnlySpan<char> line, GameStrings st
|
|||
return hiddenPowerName;
|
||||
|
||||
HiddenPowerType = (sbyte)hpVal;
|
||||
var maxIV = Context.Generation < 3 ? 15 : 31;
|
||||
var maxIV = Context.IsEraGameBoy ? 15 : 31;
|
||||
if (IVs.ContainsAnyExcept(maxIV))
|
||||
{
|
||||
if (!HiddenPower.SetIVsForType(hpVal, IVs, Context))
|
||||
|
|
@ -1081,7 +1081,7 @@ private bool ParseLineEVs(ReadOnlySpan<char> line, BattleTemplateLocalization lo
|
|||
return false; // invalid line
|
||||
}
|
||||
|
||||
if (Nature != Nature.Random) // specified in a separate Nature line
|
||||
if (Nature.IsFixed) // specified in a separate Nature line
|
||||
LogError(NatureEffortAmpAlreadySpecified, natureName);
|
||||
else
|
||||
Nature = (Nature)natureIndex;
|
||||
|
|
@ -1100,7 +1100,7 @@ private bool ParseLineEVs(ReadOnlySpan<char> line, BattleTemplateLocalization lo
|
|||
result.TreatAmpsAsSpeedNotLast();
|
||||
var ampNature = AdjustNature(result.Plus, result.Minus);
|
||||
success &= ampNature;
|
||||
if (ampNature && currentNature != Nature.Random && currentNature != Nature)
|
||||
if (ampNature && currentNature.IsFixed && currentNature != Nature)
|
||||
{
|
||||
LogError(NatureEffortAmpConflictNature);
|
||||
Nature = currentNature; // revert to original
|
||||
|
|
|
|||
535
PKHeX.Core/Editing/Bulk/Base/BatchEditingBase.cs
Normal file
535
PKHeX.Core/Editing/Bulk/Base/BatchEditingBase.cs
Normal file
|
|
@ -0,0 +1,535 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
53
PKHeX.Core/Editing/Bulk/Base/BatchEditingUtil.cs
Normal file
53
PKHeX.Core/Editing/Bulk/Base/BatchEditingUtil.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
57
PKHeX.Core/Editing/Bulk/Base/IBatchEditor.cs
Normal file
57
PKHeX.Core/Editing/Bulk/Base/IBatchEditor.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
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);
|
||||
}
|
||||
71
PKHeX.Core/Editing/Bulk/Base/InstructionComparer.cs
Normal file
71
PKHeX.Core/Editing/Bulk/Base/InstructionComparer.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
37
PKHeX.Core/Editing/Bulk/Base/InstructionOperation.cs
Normal file
37
PKHeX.Core/Editing/Bulk/Base/InstructionOperation.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ namespace PKHeX.Core;
|
|||
/// <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)
|
||||
public sealed record StringInstruction(string PropertyName, string PropertyValue, InstructionComparer Comparer, InstructionOperation Operation = InstructionOperation.Set)
|
||||
{
|
||||
public string PropertyValue { get; private set; } = PropertyValue;
|
||||
|
||||
|
|
@ -44,9 +44,35 @@ public bool SetScreenedValue(ReadOnlySpan<string> arr)
|
|||
[
|
||||
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 = '=';
|
||||
|
|
@ -256,19 +282,19 @@ public static bool TryParseFilter(ReadOnlySpan<char> line, [NotNullWhen(true)] o
|
|||
public static bool TryParseInstruction(ReadOnlySpan<char> line, [NotNullWhen(true)] out StringInstruction? entry)
|
||||
{
|
||||
entry = null;
|
||||
if (line.Length is 0 || line[0] is not Apply)
|
||||
if (line.Length is 0 || !TryGetOperation(line[0], out var operation))
|
||||
return false;
|
||||
return TryParseSplitTuple(line[1..], ref entry);
|
||||
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)
|
||||
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);
|
||||
entry = new StringInstruction(name.ToString(), value.ToString(), eval, operation);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -305,71 +331,50 @@ public static bool TryParseSplitTuple(ReadOnlySpan<char> tuple, out ReadOnlySpan
|
|||
FilterLessThanOrEqual => IsLessThanOrEqual,
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Gets the <see cref="InstructionOperation"/> from the input <see cref="opCode"/>.
|
||||
/// </summary>
|
||||
public static bool TryGetOperation(char opCode, out InstructionOperation operation)
|
||||
{
|
||||
/// <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
|
||||
switch (opCode)
|
||||
{
|
||||
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.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,
|
||||
};
|
||||
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,612 +0,0 @@
|
|||
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 (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)
|
||||
/// </summary>
|
||||
private static readonly string[] CustomProperties =
|
||||
[
|
||||
PROP_LEGAL, PROP_TYPENAME, PROP_RIBBONS, PROP_EVS, PROP_CONTESTSTATS, PROP_MOVEMASTERY, PROP_MOVEPLUS,
|
||||
PROP_TYPE1, PROP_TYPE2, PROP_TYPEEITHER,
|
||||
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 Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] Props = GetPropertyDictionaries(Types);
|
||||
private static readonly Lazy<string[][]> GetProperties = new(() => GetPropArray(Props, CustomProperties));
|
||||
|
||||
private static Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] GetPropertyDictionaries(IReadOnlyList<Type> types)
|
||||
{
|
||||
var result = new Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[types.Count];
|
||||
for (int i = 0; i < types.Count; i++)
|
||||
result[i] = GetPropertyDictionary(types[i], ReflectUtil.GetAllPropertyInfoPublic).GetAlternateLookup<ReadOnlySpan<char>>();
|
||||
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)
|
||||
dict.TryAdd(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_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);
|
||||
|
||||
private static string[][] GetPropArray<T>(Dictionary<string, T>.AlternateLookup<ReadOnlySpan<char>>[] types, ReadOnlySpan<string> extra)
|
||||
{
|
||||
// Create a list for all types, [inAny, ..types, inAll]
|
||||
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];
|
||||
Array.Sort(combine);
|
||||
p[i] = combine;
|
||||
}
|
||||
|
||||
// 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);
|
||||
foreach (var set in p[1..])
|
||||
{
|
||||
any.UnionWith(set);
|
||||
all.IntersectWith(set);
|
||||
}
|
||||
|
||||
var arrAny = any.ToArray();
|
||||
Array.Sort(arrAny);
|
||||
result[0] = arrAny;
|
||||
|
||||
var arrAll = all.ToArray();
|
||||
Array.Sort(arrAll);
|
||||
result[^1] = arrAll;
|
||||
|
||||
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 it has property, false if it does not.</returns>
|
||||
public static bool TryGetHasProperty(PKM pk, ReadOnlySpan<char> 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 it has property, false if it does not.</returns>
|
||||
public static 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 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="result">Type name of the property</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 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) // Any
|
||||
{
|
||||
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; // All vs Specific
|
||||
var pr = Props[index];
|
||||
if (!pr.TryGetValue(propertyName, out var info))
|
||||
return false;
|
||||
result = info.PropertyType.Name;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
private static Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> GetProps(PKM pk)
|
||||
{
|
||||
var type = pk.GetType();
|
||||
var typeIndex = Types.IndexOf(type);
|
||||
return Props[typeIndex];
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var props = GetProps(pk);
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
if (!IsFilterMatch(filter, pk, props))
|
||||
return false;
|
||||
}
|
||||
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="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)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
var typeName = type.Name;
|
||||
if (!cmd.Comparer.IsCompareEquivalence(cmd.PropertyValue == typeName))
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ReflectUtil.HasProperty(obj, cmd.PropertyName, out var pi))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
if (cmd.Comparer.IsCompareOperator(pi.CompareTo(obj, cmd.PropertyValue)))
|
||||
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.Skipped;
|
||||
|
||||
var info = new BatchInfo(pk);
|
||||
var props = GetProps(pk);
|
||||
foreach (var cmd in filters)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsFilterMatch(cmd, info, props))
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
var error = false;
|
||||
var result = ModifyResult.Skipped;
|
||||
foreach (var cmd in modifications)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tmp = SetPKMProperty(cmd, info, props);
|
||||
if (tmp == ModifyResult.Error)
|
||||
error = true;
|
||||
else if (tmp != ModifyResult.Skipped)
|
||||
result = tmp;
|
||||
}
|
||||
// Swallow any error because this can be malformed user input.
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(MsgBEModifyFail + " " + ex.Message, cmd.PropertyName, cmd.PropertyValue);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
if (error)
|
||||
result |= ModifyResult.Error;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the property 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, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> 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 is { PropertyValue: CONST_RAND, PropertyName: nameof(PKM.Moves) })
|
||||
return SetSuggestedMoveset(info, 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.AsSpan(1), out var opi))
|
||||
val = opi.GetValue(pk) ?? throw new NullReferenceException();
|
||||
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, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> props)
|
||||
{
|
||||
var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
|
||||
if (match is not 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, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> props)
|
||||
{
|
||||
var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
|
||||
if (match is not 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, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> props)
|
||||
{
|
||||
if (!props.TryGetValue(cmd.PropertyName, out var pi))
|
||||
return false;
|
||||
if (!pi.CanRead)
|
||||
return false;
|
||||
|
||||
var val = cmd.PropertyValue;
|
||||
if (val.StartsWith(CONST_POINTER) && props.TryGetValue(val.AsSpan(1), out var opi))
|
||||
{
|
||||
var result = opi.GetValue(pk) ?? throw new NullReferenceException();
|
||||
return cmd.Comparer.IsCompareOperator(pi.CompareTo(pk, result));
|
||||
}
|
||||
return cmd.Comparer.IsCompareOperator(pi.CompareTo(pk, val));
|
||||
}
|
||||
|
||||
/// <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(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="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)
|
||||
{
|
||||
ReadOnlySpan<char> name = cmd.PropertyName;
|
||||
ReadOnlySpan<char> value = cmd.PropertyValue;
|
||||
|
||||
if (name.StartsWith("IV") && value is CONST_RAND)
|
||||
{
|
||||
SetRandomIVs(pk, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var mod in BatchMods.ComplexMods)
|
||||
{
|
||||
if (!mod.IsMatch(name, value))
|
||||
continue;
|
||||
mod.Modify(pk, cmd);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="PKM"/> IV(s) to a random value.
|
||||
/// </summary>
|
||||
/// <param name="pk">Pokémon to modify.</param>
|
||||
/// <param name="propertyName">Property to modify</param>
|
||||
private static void SetRandomIVs(PKM pk, ReadOnlySpan<char> propertyName)
|
||||
{
|
||||
if (propertyName is nameof(PKM.IVs))
|
||||
{
|
||||
var la = new LegalityAnalysis(pk);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
PKHeX.Core/Editing/Bulk/BatchPropertyProvider.cs
Normal file
23
PKHeX.Core/Editing/Bulk/BatchPropertyProvider.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using static PKHeX.Core.BatchEditing;
|
||||
using static PKHeX.Core.EntityBatchEditor;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ namespace PKHeX.Core;
|
|||
public static class BatchFilters
|
||||
{
|
||||
/// <summary>
|
||||
/// Filters to use for <see cref="BatchEditing"/> that are derived from the <see cref="PKM"/> data.
|
||||
/// Filters to use for <see cref="EntityBatchEditor"/> that are derived from the <see cref="PKM"/> data.
|
||||
/// </summary>
|
||||
public static readonly List<IComplexFilter> FilterMods =
|
||||
[
|
||||
|
|
@ -17,7 +17,7 @@ public static class BatchFilters
|
|||
(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(PROP_TYPENAME,
|
||||
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)),
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ public static class BatchFilters
|
|||
];
|
||||
|
||||
/// <summary>
|
||||
/// Filters to use for <see cref="BatchEditing"/> that are derived from the <see cref="PKM"/> source.
|
||||
/// Filters to use for <see cref="EntityBatchEditor"/> that are derived from the <see cref="PKM"/> source.
|
||||
/// </summary>
|
||||
public static readonly List<IComplexFilterMeta> FilterMeta =
|
||||
[
|
||||
|
|
@ -6,12 +6,13 @@ namespace PKHeX.Core;
|
|||
/// <param name="Entity"> Entity to be modified. </param>
|
||||
public sealed record BatchInfo(PKM Entity)
|
||||
{
|
||||
private LegalityAnalysis? la; // c# 14 replace with get-field
|
||||
|
||||
/// <summary>
|
||||
/// Legality analysis of the entity.
|
||||
/// </summary>
|
||||
public LegalityAnalysis Legality => la ??= new LegalityAnalysis(Entity);
|
||||
/// <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,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using static PKHeX.Core.BatchEditing;
|
||||
using static PKHeX.Core.EntityBatchEditor;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
304
PKHeX.Core/Editing/Bulk/Entity/EntityBatchEditor.cs
Normal file
304
PKHeX.Core/Editing/Bulk/Entity/EntityBatchEditor.cs
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,20 +8,23 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Carries out a batch edit and contains information summarizing the results.
|
||||
/// </summary>
|
||||
public sealed class BatchEditor
|
||||
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"/>.
|
||||
/// 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)
|
||||
public bool Process(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<PKM, bool>? modifier = null)
|
||||
{
|
||||
if (pk.Species == 0)
|
||||
return false;
|
||||
|
|
@ -33,13 +36,12 @@ public bool Process(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<
|
|||
return false;
|
||||
}
|
||||
|
||||
var result = BatchEditing.TryModifyPKM(pk, filters, modifications);
|
||||
var result = Editor.TryModify(pk, filters, modifications, modifier);
|
||||
if (result != ModifyResult.Skipped)
|
||||
Iterated++;
|
||||
if (result.HasFlag(ModifyResult.Error))
|
||||
{
|
||||
Failed++;
|
||||
// Still need to fix checksum if another modification was successful.
|
||||
result &= ~ModifyResult.Error;
|
||||
}
|
||||
if (result != ModifyResult.Modified)
|
||||
|
|
@ -73,15 +75,16 @@ public string GetEditorResults(IReadOnlyCollection<StringInstructionSet> sets)
|
|||
/// </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 BatchEditor Execute(ReadOnlySpan<string> lines, IEnumerable<PKM> data)
|
||||
public static EntityBatchProcessor Execute(ReadOnlySpan<string> lines, IEnumerable<PKM> data, Func<PKM, bool>? modifier = null)
|
||||
{
|
||||
var editor = new BatchEditor();
|
||||
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);
|
||||
editor.Process(pk, set.Filters, set.Instructions, modifier);
|
||||
}
|
||||
|
||||
return editor;
|
||||
|
|
@ -54,12 +54,12 @@ public static ModifyResult SetSuggestedMasteryData(BatchInfo info, ReadOnlySpan<
|
|||
if (IsNone(propValue))
|
||||
return ModifyResult.Modified;
|
||||
|
||||
var e = info.Legality.EncounterMatch;
|
||||
if (e is IMasteryInitialMoveShop8 enc)
|
||||
enc.SetInitialMastery(pk);
|
||||
var enc = info.Legality.EncounterMatch;
|
||||
if (enc is IMasteryInitialMoveShop8 shop)
|
||||
shop.SetInitialMastery(pk, enc);
|
||||
if (IsAll(propValue))
|
||||
{
|
||||
t.SetPurchasedFlagsAll();
|
||||
t.SetPurchasedFlagsAll(pk);
|
||||
t.SetMoveShopFlagsAll(pk);
|
||||
}
|
||||
else
|
||||
|
|
@ -176,7 +176,7 @@ public static ModifyResult SetEVs(PKM pk)
|
|||
/// <param name="option">Option to apply with</param>
|
||||
public static ModifyResult SetContestStats(PKM pk, LegalityAnalysis la, ReadOnlySpan<char> option)
|
||||
{
|
||||
if (option.Length != 0 && option[BatchEditing.CONST_SUGGEST.Length..] is not "0")
|
||||
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);
|
||||
|
|
@ -3,38 +3,16 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for retrieving properties from a <see cref="PKM"/>.
|
||||
/// Interface for retrieving properties from a <see cref="T"/>.
|
||||
/// </summary>
|
||||
public interface IPropertyProvider
|
||||
public interface IPropertyProvider<in T> where T : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to retrieve a property's value (as string) from a <see cref="PKM"/> instance.
|
||||
/// Attempts to retrieve a property's value (as string) from an entity instance.
|
||||
/// </summary>
|
||||
/// <param name="pk">Entity to retrieve the property from.</param>
|
||||
/// <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(PKM pk, string prop, [NotNullWhen(true)] out string? result);
|
||||
}
|
||||
|
||||
public sealed class DefaultPropertyProvider : IPropertyProvider
|
||||
{
|
||||
public static readonly DefaultPropertyProvider Instance = new();
|
||||
|
||||
public bool TryGetProperty(PKM pk, string prop, [NotNullWhen(true)] out string? result)
|
||||
{
|
||||
result = null;
|
||||
if (!BatchEditing.TryGetHasProperty(pk, prop, out var pi))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
var value = pi.GetValue(pk);
|
||||
result = value?.ToString();
|
||||
return result is not null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool TryGetProperty(T obj, string prop, [NotNullWhen(true)] out string? result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,10 @@ public void SetNickname(string nick)
|
|||
pk.ClearNickname();
|
||||
return;
|
||||
}
|
||||
pk.IsNicknamed = true;
|
||||
|
||||
pk.PrepareNickname();
|
||||
pk.Nickname = nick;
|
||||
pk.IsNicknamed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -137,7 +139,7 @@ public bool SetUnshiny()
|
|||
/// <param name="nature">Desired <see cref="PKM.Nature"/> value to set.</param>
|
||||
public void SetNature(Nature nature)
|
||||
{
|
||||
if (!nature.IsFixed())
|
||||
if (!nature.IsFixed)
|
||||
nature = 0; // default valid
|
||||
|
||||
var format = pk.Format;
|
||||
|
|
|
|||
|
|
@ -86,14 +86,14 @@ public ReadOnlySpan<ITrainerInfo> GetTrainers(GameVersion version)
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches an appropriate trainer based on the requested <see cref="generation"/>.
|
||||
/// Fetches an appropriate trainer based on the requested <see cref="context"/>.
|
||||
/// </summary>
|
||||
/// <param name="generation">Generation the trainer should inhabit</param>
|
||||
/// <param name="context">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? GetTrainerFromGen(byte generation, LanguageID? lang = null)
|
||||
public ITrainerInfo? GetTrainerFromContext(EntityContext context, LanguageID? lang = null)
|
||||
{
|
||||
var possible = Database.Where(z => z.Key.Generation == generation).ToList();
|
||||
var possible = Database.Where(z => z.Key.Context == context).ToList();
|
||||
if (possible.Count == 0)
|
||||
return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public static class HiddenPower
|
|||
/// <param name="context">Generation format</param>
|
||||
public static int GetType(ReadOnlySpan<int> IVs, EntityContext context)
|
||||
{
|
||||
if (context.Generation <= 2)
|
||||
if (context.IsEraGameBoy)
|
||||
return GetTypeGB(IVs);
|
||||
return GetType(IVs);
|
||||
}
|
||||
|
|
@ -153,7 +153,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.Generation <= 2)
|
||||
if (context.IsEraGameBoy)
|
||||
return SetTypeGB(hiddenPowerType, IVs);
|
||||
return SetIVsForType(hiddenPowerType, IVs);
|
||||
}
|
||||
|
|
@ -238,7 +238,7 @@ private static int GetFlawedBitCount(ReadOnlySpan<int> ivs, int bitValue)
|
|||
/// <param name="context">Generation specific format</param>
|
||||
public static void SetIVs(int type, Span<int> ivs, EntityContext context = Latest.Context)
|
||||
{
|
||||
if (context.Generation <= 2)
|
||||
if (context.IsEraGameBoy)
|
||||
{
|
||||
ivs[1] = (ivs[1] & 0b1100) | (type >> 2);
|
||||
ivs[2] = (ivs[2] & 0b1100) | (type & 3);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
namespace PKHeX.Core;
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Simple interface representing a <see cref="PKM"/> viewer.
|
||||
|
|
@ -44,4 +44,10 @@ 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,13 @@ public class EntitySummary : IFatefulEncounterReadOnly // do NOT seal, allow inh
|
|||
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 ushort Checksum => Entity switch
|
||||
{
|
||||
ISanityChecksum s => s.Checksum,
|
||||
PK1 gb => gb.GetSingleListChecksum(),
|
||||
PK2 gb => gb.GetSingleListChecksum(),
|
||||
_ => 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;
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ public static string GetMessage(PKM pk)
|
|||
return GetMessage(pk7);
|
||||
|
||||
var server = GetExploitURLPrefixPKM(pk.Format);
|
||||
var data = pk.EncryptedBoxData;
|
||||
Span<byte> data = stackalloc byte[pk.SIZE_STORED];
|
||||
pk.WriteEncryptedDataStored(data);
|
||||
return GetMessageBase64(data, server);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,15 @@ namespace PKHeX.Core;
|
|||
|
||||
public sealed class AdvancedSettings
|
||||
{
|
||||
[LocalizedDescription("Skip the Overwrite prompt when exporting a save file, to always Save As...")]
|
||||
public bool SaveExportForceSaveAs { get; set; }
|
||||
|
||||
[LocalizedDescription("Check if the Pokémon in the editor has unsaved changes before exporting the save file.")]
|
||||
public bool SaveExportCheckUnsavedEntity { get; set; } = true;
|
||||
|
||||
[LocalizedDescription("Folder path that contains dump(s) of block hash-names. If a specific dump file does not exist, only names defined within the program's code will be loaded.")]
|
||||
public string PathBlockKeyList { get; set; } = string.Empty;
|
||||
|
||||
[LocalizedDescription("Hide event variables below this event type value. Removes event values from the GUI that the user doesn't care to view.")]
|
||||
public NamedEventType HideEventTypeBelow { get; set; }
|
||||
|
||||
[LocalizedDescription("Hide event variable names for that contain any of the comma-separated substrings below. Removes event values from the GUI that the user doesn't care to view.")]
|
||||
public string HideEvent8Contains { get; set; } = string.Empty;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,12 @@
|
|||
using System;
|
||||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for defining a manipulation of box data.
|
||||
/// </summary>
|
||||
public abstract class BoxManipBase : IBoxManip
|
||||
public abstract record BoxManipBase(BoxManipType Type, Func<SaveFile, bool> Usable) : IBoxManip
|
||||
{
|
||||
public BoxManipType Type { get; }
|
||||
public Func<SaveFile, bool> Usable { get; }
|
||||
|
||||
protected BoxManipBase(BoxManipType type, Func<SaveFile, bool> usable)
|
||||
{
|
||||
Type = type;
|
||||
Usable = usable;
|
||||
}
|
||||
|
||||
public abstract string GetPrompt(bool all);
|
||||
public abstract string GetFail(bool all);
|
||||
public abstract string GetSuccess(bool all);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Clears contents of boxes by deleting all that satisfy a criteria.
|
||||
/// </summary>
|
||||
public sealed class BoxManipClear(BoxManipType Type, Func<PKM, bool> criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
|
||||
public sealed record BoxManipClear(BoxManipType Type, Func<PKM, bool> Criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
|
||||
{
|
||||
public BoxManipClear(BoxManipType Type, Func<PKM, bool> Criteria) : this(Type, Criteria, _ => true) { }
|
||||
|
||||
|
|
@ -18,6 +18,6 @@ public override int Execute(SaveFile sav, BoxManipParam param)
|
|||
var (start, stop, reverse) = param;
|
||||
return sav.ClearBoxes(start, stop, Method);
|
||||
|
||||
bool Method(PKM p) => reverse ^ criteria(p);
|
||||
bool Method(PKM p) => reverse ^ Criteria(p);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Clears contents of boxes by deleting all that satisfy a criteria based on a <see cref="SaveFile"/>.
|
||||
/// </summary>
|
||||
public sealed class BoxManipClearComplex(BoxManipType Type, Func<PKM, SaveFile, bool> criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
|
||||
public sealed record BoxManipClearComplex(BoxManipType Type, Func<PKM, SaveFile, bool> Criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
|
||||
{
|
||||
public BoxManipClearComplex(BoxManipType Type, Func<PKM, SaveFile, bool> Criteria) : this(Type, Criteria, _ => true) { }
|
||||
|
||||
|
|
@ -18,6 +18,6 @@ public override int Execute(SaveFile sav, BoxManipParam param)
|
|||
var (start, stop, reverse) = param;
|
||||
return sav.ClearBoxes(start, stop, Method);
|
||||
|
||||
bool Method(PKM p) => reverse ^ criteria(p, sav);
|
||||
bool Method(PKM p) => reverse ^ Criteria(p, sav);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace PKHeX.Core;
|
|||
/// Clears contents of boxes by deleting all but the first duplicate detected.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Base type of the "is duplicate" hash for the duplicate detection.</typeparam>
|
||||
public sealed class BoxManipClearDuplicate<T> : BoxManipBase
|
||||
public sealed record BoxManipClearDuplicate<T> : BoxManipBase
|
||||
{
|
||||
private readonly HashSet<T> HashSet = [];
|
||||
private readonly Func<PKM, bool> Criteria;
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Modifies contents of boxes by using an <see cref="Action"/> to change data.
|
||||
/// </summary>
|
||||
public sealed class BoxManipModify(BoxManipType type, Action<PKM> Action, Func<SaveFile, bool> Usable)
|
||||
: BoxManipBase(type, Usable)
|
||||
public sealed record BoxManipModify(BoxManipType Type, Action<PKM> Action, Func<SaveFile, bool> Usable)
|
||||
: BoxManipBase(Type, Usable)
|
||||
{
|
||||
public BoxManipModify(BoxManipType type, Action<PKM> Action) : this(type, Action, _ => true) { }
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Modifies contents of boxes by using an <see cref="Action"/> (referencing a Save File) to change data.
|
||||
/// </summary>
|
||||
public sealed class BoxManipModifyComplex(BoxManipType Type, Action<PKM, SaveFile> Action, Func<SaveFile, bool> Usable)
|
||||
public sealed record BoxManipModifyComplex(BoxManipType Type, Action<PKM, SaveFile> Action, Func<SaveFile, bool> Usable)
|
||||
: BoxManipBase(Type, Usable)
|
||||
{
|
||||
public BoxManipModifyComplex(BoxManipType Type, Action<PKM, SaveFile> Action) : this(Type, Action, _ => true) { }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Sorts contents of boxes by using a Sorter to determine the order.
|
||||
/// </summary>
|
||||
public sealed class BoxManipSort(BoxManipType Type, Func<IEnumerable<PKM>, IEnumerable<PKM>> Sorter, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
|
||||
public sealed record BoxManipSort(BoxManipType Type, Func<IEnumerable<PKM>, IEnumerable<PKM>> Sorter, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
|
||||
{
|
||||
public BoxManipSort(BoxManipType Type, Func<IEnumerable<PKM>, IEnumerable<PKM>> Sorter) : this(Type, Sorter, _ => true) { }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
|
@ -6,7 +6,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Sorts contents of boxes by using a <see cref="Sorter"/> (referencing a Save File) to determine the order.
|
||||
/// </summary>
|
||||
public sealed class BoxManipSortComplex : BoxManipBase
|
||||
public sealed record BoxManipSortComplex : BoxManipBase
|
||||
{
|
||||
private readonly Func<IEnumerable<PKM>, SaveFile, int, IEnumerable<PKM>> Sorter;
|
||||
public BoxManipSortComplex(BoxManipType type, Func<IEnumerable<PKM>, SaveFile, IEnumerable<PKM>> sorter) : this(type, sorter, _ => true) { }
|
||||
|
|
|
|||
|
|
@ -13,4 +13,5 @@ public sealed class FakePKMEditor(PKM template) : IPKMView
|
|||
|
||||
public PKM PreparePKM(bool click = true) => Data;
|
||||
public void PopulateFields(PKM pk, bool focus = true, bool skipConversionCheck = false) => Data = pk;
|
||||
public void NotifyWasExported(PKM pk) { }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,10 +47,10 @@ public sealed class FakeSaveFile : SaveFile
|
|||
protected override void SetChecksums() { }
|
||||
public override GameVersion Version { get => GameVersion.R; set { } }
|
||||
public override Type PKMType => typeof(PK3);
|
||||
protected override PK3 GetPKM(byte[] data) => BlankPKM;
|
||||
protected override byte[] DecryptPKM(byte[] data) => data;
|
||||
protected override PK3 GetPKM(Memory<byte> data) => BlankPKM;
|
||||
protected override void DecryptPKM(Span<byte> data) { }
|
||||
public override PK3 BlankPKM => new();
|
||||
public override EntityContext Context => EntityContext.Gen3;
|
||||
protected override int SIZE_STORED => 0;
|
||||
protected override int SIZE_PARTY => 0;
|
||||
public override int SIZE_STORED => 0;
|
||||
public override int SIZE_PARTY => 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ public static int Export(SaveFile sav, string destPath, IFileNamer<PKM> namer, B
|
|||
int count = GetSlotCountForBox(boxSlotCount, box, total);
|
||||
int ctr = 0;
|
||||
// Export each slot in the box.
|
||||
Span<byte> data = stackalloc byte[sav.SIZE_STORED];
|
||||
for (int slot = 0; slot < count; slot++)
|
||||
{
|
||||
var pk = sav.GetBoxSlotAtIndex(box, slot);
|
||||
|
|
@ -98,7 +99,8 @@ public static int Export(SaveFile sav, string destPath, IFileNamer<PKM> namer, B
|
|||
|
||||
var fileName = GetFileName(pk, settings.FileIndexPrefix, namer, box, slot, boxSlotCount);
|
||||
var fn = Path.Combine(destPath, fileName);
|
||||
File.WriteAllBytes(fn, pk.DecryptedPartyData);
|
||||
pk.WriteDecryptedDataStored(data);
|
||||
File.WriteAllBytes(fn, data);
|
||||
ctr++;
|
||||
}
|
||||
return ctr;
|
||||
|
|
|
|||
|
|
@ -61,11 +61,11 @@ private static List<SlotInfoMisc> GetExtraSlots2(SAV2 sav)
|
|||
|
||||
private static List<SlotInfoMisc> GetExtraSlots3(SAV3 sav)
|
||||
{
|
||||
if (sav is not SAV3FRLG)
|
||||
if (sav is not SAV3FRLG frlg)
|
||||
return None;
|
||||
return
|
||||
[
|
||||
new(sav.LargeBuffer[0x3C98..], 0) {Type = StorageSlotType.Daycare},
|
||||
new(frlg.LargeBlock.SingleDaycareRoute5, 0) {Type = StorageSlotType.Daycare},
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ private static List<SlotInfoMisc> GetExtraSlots4(SAV4 sav)
|
|||
if (sav.GTS > 0)
|
||||
list.Add(new SlotInfoMisc(sav.GeneralBuffer[sav.GTS..], 0) { Type = StorageSlotType.GTS });
|
||||
if (sav is SAV4HGSS hgss)
|
||||
list.Add(new SlotInfoMisc(hgss.GeneralBuffer[SAV4HGSS.WalkerPair..], 1) {Type = StorageSlotType.Misc});
|
||||
list.Add(new SlotInfoMisc(hgss.GeneralBuffer[SAV4HGSS.WalkerPair..], 1) {Type = StorageSlotType.Pokéwalker});
|
||||
return list;
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ private static List<SlotInfoMisc> GetExtraSlots5(SAV5 sav)
|
|||
var list = new List<SlotInfoMisc>
|
||||
{
|
||||
new(sav.GTS.Upload, 0) {Type = StorageSlotType.GTS},
|
||||
new(sav.GlobalLink.Upload, 0) { Type = StorageSlotType.Misc },
|
||||
new(sav.GlobalLink.Upload, 0) { Type = StorageSlotType.PGL },
|
||||
|
||||
new(sav.BattleBox[0], 0) {Type = StorageSlotType.BattleBox},
|
||||
new(sav.BattleBox[1], 1) {Type = StorageSlotType.BattleBox},
|
||||
|
|
@ -106,7 +106,7 @@ private static List<SlotInfoMisc> GetExtraSlots6XY(SAV6XY sav)
|
|||
[
|
||||
new(sav.GTS.Upload, 0) {Type = StorageSlotType.GTS},
|
||||
new(sav.Fused[0], 0) {Type = StorageSlotType.FusedKyurem},
|
||||
new(sav.SUBE.GiveSlot, 0, Mutable: true) {Type = StorageSlotType.Misc}, // Old Man
|
||||
new(sav.SUBE.GiveSlot, 0, Mutable: true) {Type = StorageSlotType.Scripted}, // Old Man
|
||||
|
||||
new(sav.BattleBox[0], 0) {Type = StorageSlotType.BattleBox},
|
||||
new(sav.BattleBox[1], 1) {Type = StorageSlotType.BattleBox},
|
||||
|
|
@ -123,7 +123,7 @@ private static List<SlotInfoMisc> GetExtraSlots6AO(SAV6AO sav)
|
|||
[
|
||||
new(sav.GTS.Upload, 0) { Type = StorageSlotType.GTS },
|
||||
new(sav.Fused[0], 0) { Type = StorageSlotType.FusedKyurem },
|
||||
new(sav.SUBE.GiveSlot, 0) {Type = StorageSlotType.Misc},
|
||||
new(sav.SUBE.GiveSlot, 0) {Type = StorageSlotType.Scripted},
|
||||
|
||||
new(sav.BattleBox[0], 0) {Type = StorageSlotType.BattleBox},
|
||||
new(sav.BattleBox[1], 1) {Type = StorageSlotType.BattleBox},
|
||||
|
|
@ -150,9 +150,9 @@ private static List<SlotInfoMisc> GetExtraSlots7(SAV7 sav, bool all)
|
|||
]);
|
||||
list.AddRange(
|
||||
[
|
||||
new SlotInfoMisc(uu.BattleAgency[0], 0) {Type = StorageSlotType.Misc},
|
||||
new SlotInfoMisc(uu.BattleAgency[1], 1) {Type = StorageSlotType.Misc},
|
||||
new SlotInfoMisc(uu.BattleAgency[2], 2) {Type = StorageSlotType.Misc},
|
||||
new SlotInfoMisc(uu.BattleAgency[0], 0) {Type = StorageSlotType.BattleAgency},
|
||||
new SlotInfoMisc(uu.BattleAgency[1], 1) {Type = StorageSlotType.BattleAgency},
|
||||
new SlotInfoMisc(uu.BattleAgency[2], 2) {Type = StorageSlotType.BattleAgency},
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -202,15 +202,15 @@ private static List<SlotInfoMisc> GetExtraSlots8b(SAV8BS sav)
|
|||
{
|
||||
return
|
||||
[
|
||||
new(sav.UgSaveData[0], 0, true) { Type = StorageSlotType.Misc, HideLegality = true },
|
||||
new(sav.UgSaveData[1], 1, true) { Type = StorageSlotType.Misc, HideLegality = true },
|
||||
new(sav.UgSaveData[2], 2, true) { Type = StorageSlotType.Misc, HideLegality = true },
|
||||
new(sav.UgSaveData[3], 3, true) { Type = StorageSlotType.Misc, HideLegality = true },
|
||||
new(sav.UgSaveData[4], 4, true) { Type = StorageSlotType.Misc, HideLegality = true },
|
||||
new(sav.UgSaveData[5], 5, true) { Type = StorageSlotType.Misc, HideLegality = true },
|
||||
new(sav.UgSaveData[6], 6, true) { Type = StorageSlotType.Misc, HideLegality = true },
|
||||
new(sav.UgSaveData[7], 7, true) { Type = StorageSlotType.Misc, HideLegality = true },
|
||||
new(sav.UgSaveData[8], 8, true) { Type = StorageSlotType.Misc, HideLegality = true },
|
||||
new(sav.UgSaveData[0], 0, true) { Type = StorageSlotType.Underground, HideLegality = true },
|
||||
new(sav.UgSaveData[1], 1, true) { Type = StorageSlotType.Underground, HideLegality = true },
|
||||
new(sav.UgSaveData[2], 2, true) { Type = StorageSlotType.Underground, HideLegality = true },
|
||||
new(sav.UgSaveData[3], 3, true) { Type = StorageSlotType.Underground, HideLegality = true },
|
||||
new(sav.UgSaveData[4], 4, true) { Type = StorageSlotType.Underground, HideLegality = true },
|
||||
new(sav.UgSaveData[5], 5, true) { Type = StorageSlotType.Underground, HideLegality = true },
|
||||
new(sav.UgSaveData[6], 6, true) { Type = StorageSlotType.Underground, HideLegality = true },
|
||||
new(sav.UgSaveData[7], 7, true) { Type = StorageSlotType.Underground, HideLegality = true },
|
||||
new(sav.UgSaveData[8], 8, true) { Type = StorageSlotType.Underground, HideLegality = true },
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -239,8 +239,8 @@ private static List<SlotInfoMisc> GetExtraSlots9(SAV9SV sav)
|
|||
|
||||
if (sav.Blocks.TryGetBlock(SaveBlockAccessor9SV.KSurpriseTrade, out var surprise))
|
||||
{
|
||||
list.Add(new(surprise.Raw[0x198..], 0) { Type = StorageSlotType.Misc }); // my upload
|
||||
list.Add(new(surprise.Raw[0x02C..], 1) { Type = StorageSlotType.Misc }); // received from others
|
||||
list.Add(new(surprise.Raw[0x198..], 0) { Type = StorageSlotType.SurpriseTrade }); // my upload
|
||||
list.Add(new(surprise.Raw[0x02C..], 1) { Type = StorageSlotType.SurpriseTrade }); // received from others
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
|
@ -254,7 +254,7 @@ private static List<SlotInfoMisc> GetExtraSlots9a(SAV9ZA sav)
|
|||
{
|
||||
const int size = 0x1F0;
|
||||
var ofs = (i * size) + 8;
|
||||
var entry = shinyCache.Raw.Slice(ofs, PokeCrypto.SIZE_9PARTY);
|
||||
var entry = shinyCache.Raw.Slice(ofs, PokeCrypto.SIZE_8PARTY);
|
||||
if (EntityDetection.IsPresent(entry.Span))
|
||||
list.Add(new(entry, i, true) { Type = StorageSlotType.Shiny, HideLegality = true }); // no OT info
|
||||
else
|
||||
|
|
@ -266,9 +266,9 @@ private static List<SlotInfoMisc> GetExtraSlots9a(SAV9ZA sav)
|
|||
{
|
||||
const int size = 0x1A8;
|
||||
var ofs = (i * size) + 8;
|
||||
var entry = giveAway.Raw.Slice(ofs, PokeCrypto.SIZE_9PARTY);
|
||||
var entry = giveAway.Raw.Slice(ofs, PokeCrypto.SIZE_8PARTY);
|
||||
if (EntityDetection.IsPresent(entry.Span))
|
||||
list.Add(new(entry, i, true, Mutable: true) { Type = StorageSlotType.Misc });
|
||||
list.Add(new(entry, i, true, Mutable: true) { Type = StorageSlotType.Scripted });
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
|
@ -48,4 +49,11 @@ public interface ISlotViewer<T>
|
|||
/// Save data the <see cref="ISlotViewer{T}"/> is showing data from.
|
||||
/// </summary>
|
||||
SaveFile SAV { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Instructs the viewer to cache the provided filter and apply it to all slots, showing only those that match the filter.
|
||||
/// </summary>
|
||||
/// <param name="filter">Filter function to apply to the viewer's slots. Only slots for which this function returns true will be shown in the viewer.</param>
|
||||
/// <param name="reload">Trigger a reload of the viewer after applying the new filter. This is required to update the viewer's display after changing the filter.</param>
|
||||
void ApplyNewFilter(Func<PKM, bool>? filter, bool reload = true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
|
@ -11,6 +12,7 @@ public sealed class SlotPublisher<T>
|
|||
/// All <see cref="ISlotViewer{T}"/> instances that provide a view on individual <see cref="ISlotInfo"/> content.
|
||||
/// </summary>
|
||||
private List<ISlotViewer<T>> Subscribers { get; } = [];
|
||||
private Func<PKM, bool>? Filter { get; set; }
|
||||
|
||||
public ISlotInfo? Previous { get; private set; }
|
||||
public SlotTouchType PreviousType { get; private set; } = SlotTouchType.None;
|
||||
|
|
@ -49,4 +51,11 @@ public void ResetView(ISlotViewer<T> sub)
|
|||
|
||||
public void Subscribe(ISlotViewer<T> sub) => Subscribers.Add(sub);
|
||||
public bool Unsubscribe(ISlotViewer<T> sub) => Subscribers.Remove(sub);
|
||||
|
||||
public void UpdateFilter(Func<PKM, bool>? searchFilter, bool reload = true)
|
||||
{
|
||||
Filter = searchFilter;
|
||||
foreach (var sub in Subscribers)
|
||||
sub.ApplyNewFilter(Filter, reload);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
26
PKHeX.Core/Editing/Saves/Slots/SlotVisibilityType.cs
Normal file
26
PKHeX.Core/Editing/Saves/Slots/SlotVisibilityType.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the visibility options for a slot when it is displayed in the user interface.
|
||||
/// </summary>
|
||||
/// <remarks>This enumeration supports bitwise combination of its values to allow multiple visibility behaviors to be applied simultaneously.</remarks>
|
||||
[Flags]
|
||||
public enum SlotVisibilityType
|
||||
{
|
||||
/// <summary>
|
||||
/// No special visibility handling.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Check the legality of the slot when displaying it.
|
||||
/// </summary>
|
||||
CheckLegalityIndicate = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Fade-out the slot if it does not match the current filter.
|
||||
/// </summary>
|
||||
FilterMismatch = 1 << 1,
|
||||
}
|
||||
|
|
@ -7,28 +7,106 @@ public enum StorageSlotType : byte
|
|||
{
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Originated from Box
|
||||
/// </summary>
|
||||
Box,
|
||||
|
||||
/// <summary>
|
||||
/// Originated from Party
|
||||
/// </summary>
|
||||
Party,
|
||||
|
||||
/// <summary> Battle Box </summary>
|
||||
/// <summary>
|
||||
/// Battle Box
|
||||
/// </summary>
|
||||
BattleBox,
|
||||
/// <summary> Daycare </summary>
|
||||
|
||||
/// <summary>
|
||||
/// Daycare
|
||||
/// </summary>
|
||||
Daycare,
|
||||
/// <summary> Global Trade Station (GTS) </summary>
|
||||
|
||||
/// <summary>
|
||||
/// Miscellaneous Origin (usually in-game scripted event recollection)
|
||||
/// </summary>
|
||||
Scripted,
|
||||
|
||||
/// <summary>
|
||||
/// Global Trade Station (GTS)
|
||||
/// </summary>
|
||||
GTS,
|
||||
/// <summary> Shiny Overworld Cache </summary>
|
||||
|
||||
/// <summary>
|
||||
/// Pokémon Global Link (PGL)
|
||||
/// </summary>
|
||||
PGL,
|
||||
|
||||
/// <summary>
|
||||
/// Surprise Trade Upload/Download
|
||||
/// </summary>
|
||||
SurpriseTrade,
|
||||
|
||||
/// <summary>
|
||||
/// Shiny Overworld Cache
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="GameVersion.ZA"/>
|
||||
/// </remarks>
|
||||
Shiny,
|
||||
|
||||
/// <summary> Fused Legendary Storage </summary>
|
||||
/// <summary>
|
||||
/// Underground area wild Pokémon cache
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="GameVersion.BD"/>
|
||||
/// <see cref="GameVersion.SP"/>
|
||||
/// </remarks>
|
||||
Underground,
|
||||
|
||||
/// <summary>
|
||||
/// Fused Legendary Storage
|
||||
/// </summary>
|
||||
Fused,
|
||||
|
||||
/// <summary>
|
||||
/// Sub-tag for <see cref="Species.Kyurem"/> differentiation.
|
||||
/// </summary>
|
||||
FusedKyurem,
|
||||
/// <summary>
|
||||
/// Sub-tag for <see cref="Species.Solgaleo"/> differentiation.
|
||||
/// </summary>
|
||||
FusedNecrozmaS,
|
||||
/// <summary>
|
||||
/// Sub-tag for <see cref="Species.Lunala"/> differentiation.
|
||||
/// </summary>
|
||||
FusedNecrozmaM,
|
||||
/// <summary>
|
||||
/// Sub-tag for <see cref="Species.Calyrex"/> differentiation.
|
||||
/// </summary>
|
||||
FusedCalyrex,
|
||||
|
||||
/// <summary> Miscellaneous </summary>
|
||||
Misc,
|
||||
/// <summary> Poké Pelago (Gen7) </summary>
|
||||
/// <summary>
|
||||
/// Poké Pelago (Gen7)
|
||||
/// </summary>
|
||||
Resort,
|
||||
/// <summary> Ride Legendary Slot (S/V) </summary>
|
||||
|
||||
/// <summary>
|
||||
/// Ride Legendary Slot (S/V)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="GameVersion.SL"/>
|
||||
/// <see cref="GameVersion.VL"/>
|
||||
/// </remarks>
|
||||
Ride,
|
||||
|
||||
/// <summary>
|
||||
/// Battle Agency (Gen7)
|
||||
/// </summary>
|
||||
BattleAgency,
|
||||
|
||||
/// <summary>
|
||||
/// Gen4 HeartGold/SoulSilver pedometer accessory upload
|
||||
/// </summary>
|
||||
Pokéwalker,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,6 +238,11 @@ public enum GameVersion : byte
|
|||
/// Pokémon Legends: (Z-A) (NX)
|
||||
/// </summary>
|
||||
ZA = 52,
|
||||
|
||||
/// <summary>
|
||||
/// Pokémon Champions (NX)
|
||||
/// </summary>
|
||||
CP = 53,
|
||||
#endregion
|
||||
|
||||
// The following values are not actually stored values in pk data,
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public static class NatureUtil
|
|||
/// Checks if the provided <see cref="value"/> is a valid stored <see cref="Nature"/> value.
|
||||
/// </summary>
|
||||
/// <returns>True if value is an actual nature.</returns>
|
||||
public bool IsFixed() => value < Nature.Random;
|
||||
public bool IsFixed => value != Nature.Random;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the provided <see cref="value"/> is a possible mint nature.
|
||||
|
|
@ -63,12 +63,12 @@ public static class NatureUtil
|
|||
/// <remarks>
|
||||
/// The only valid mint natures are those which have a stat amp applied, or neutral nature being Serious.
|
||||
/// </remarks>
|
||||
public bool IsMint() => (value.IsFixed() && (byte)value % 6 != 0) || value == Nature.Serious;
|
||||
public bool IsMint => (value.IsFixed && (byte)value % 6 != 0) || value == Nature.Serious;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the provided <see cref="value"/> is a neutral nature which has no stat amps applied.
|
||||
/// </summary>
|
||||
public bool IsNeutral() => value.IsFixed() && (byte)value % 6 == 0;
|
||||
public bool IsNeutral => value.IsFixed && (byte)value % 6 == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Converts the provided <see cref="value"/> to a neutral nature.
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public FilteredGameDataSource(SaveFile sav, GameDataSource source, bool HaX = fa
|
|||
Items = [];
|
||||
}
|
||||
|
||||
var gamelist = GameUtil.GetVersionsWithinRange(sav, sav.Generation).ToList();
|
||||
var gamelist = GameUtil.GetVersionsWithinRange(sav, sav.Context).ToList();
|
||||
Games = Source.VersionDataSource.Where(g => gamelist.Contains((GameVersion)g.Value) || g.Value == 0).ToList();
|
||||
|
||||
Languages = Source.LanguageDataSource(sav.Generation, sav.Context);
|
||||
|
|
|
|||
|
|
@ -65,13 +65,11 @@ public static int GetLanguageIndex(string lang)
|
|||
/// </summary>
|
||||
public static string[] GetStrings(string ident, string lang, [ConstantExpected] string type = "text")
|
||||
{
|
||||
#pragma warning disable CA1857
|
||||
string[] data = Util.GetStringList(ident, lang, type);
|
||||
if (data.Length == 0)
|
||||
data = Util.GetStringList(ident, DefaultLanguage, type);
|
||||
|
||||
return data;
|
||||
#pragma warning restore CA1857
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ private static GameVersion[] GetValidGameVersions()
|
|||
SL or VL => SV,
|
||||
ZA => ZA,
|
||||
|
||||
CP => CP, // TODO: Champions
|
||||
|
||||
_ => Invalid,
|
||||
};
|
||||
|
||||
|
|
@ -168,6 +170,7 @@ private EntityContext GetContextInternal()
|
|||
SW or SH => Legal.MaxSpeciesID_8,
|
||||
SL or VL => Legal.MaxSpeciesID_9,
|
||||
ZA => Legal.MaxSpeciesID_9a,
|
||||
CP => Legal.MaxSpeciesID_9, // TODO: Champions
|
||||
_ => 0
|
||||
};
|
||||
|
||||
|
|
@ -252,23 +255,23 @@ public bool Contains(GameVersion g2)
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of possible <see cref="GameVersion"/> values within the provided <see cref="generation"/>.
|
||||
/// List of possible <see cref="GameVersion"/> values within the provided <see cref="context"/>.
|
||||
/// </summary>
|
||||
/// <param name="generation">Generation to look within</param>
|
||||
/// <param name="context">Generation to look within</param>
|
||||
/// <param name="version">Entity version</param>
|
||||
public static GameVersion[] GetVersionsInGeneration(byte generation, GameVersion version)
|
||||
public static GameVersion[] GetVersionsInGeneration(EntityContext context, GameVersion version)
|
||||
{
|
||||
if (Gen7b.Contains(version))
|
||||
if (context is EntityContext.Gen7b)
|
||||
return [GO, GP, GE];
|
||||
return Array.FindAll(GameVersions, z => z.Generation == generation);
|
||||
return Array.FindAll(GameVersions, z => z.Context == context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of possible <see cref="GameVersion"/> values within the provided <see cref="IGameValueLimit"/> criteria.
|
||||
/// </summary>
|
||||
/// <param name="obj">Criteria for retrieving versions</param>
|
||||
/// <param name="generation">Generation format minimum (necessary for the CXD/Gen4 swap etc.)</param>
|
||||
public static IEnumerable<GameVersion> GetVersionsWithinRange(IGameValueLimit obj, byte generation = 0)
|
||||
/// <param name="context">Generation format minimum (necessary for the CXD/Gen4 swap etc.)</param>
|
||||
public static IEnumerable<GameVersion> GetVersionsWithinRange(IGameValueLimit obj, EntityContext context = 0)
|
||||
{
|
||||
var max = obj.MaxGameID;
|
||||
if (max == Legal.MaxGameID_7b) // edge case
|
||||
|
|
@ -277,14 +280,14 @@ public static IEnumerable<GameVersion> GetVersionsWithinRange(IGameValueLimit ob
|
|||
.Where(version => obj.MinGameID <= version && version <= max);
|
||||
if (max != BATREV)
|
||||
versions = versions.Where(static version => version != BATREV);
|
||||
if (generation == 0)
|
||||
if (context == 0)
|
||||
return versions;
|
||||
if (max == Legal.MaxGameID_7 && generation == 7)
|
||||
if (max == Legal.MaxGameID_7 && context == EntityContext.Gen7)
|
||||
versions = versions.Where(static version => version != GO);
|
||||
|
||||
// HOME allows up-reach to Gen9
|
||||
if (generation >= 8)
|
||||
generation = 9;
|
||||
return versions.Where(version => version.Generation <= generation);
|
||||
if (context.IsEraHOME)
|
||||
return versions;
|
||||
return versions.Where(version => version.Generation <= context.Generation);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,16 +48,32 @@ public int Clamp(InventoryType type, int itemIndex, int requestVal)
|
|||
/// <summary>
|
||||
/// Validates and clamps an item count for the specified pouch.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true"/> if the count is valid after clamping; otherwise, <see langword="false"/>.</returns>
|
||||
public bool IsQuantitySane(InventoryType type, int itemIndex, ref int count, bool hasNew, bool HaX = false)
|
||||
{
|
||||
if (HaX)
|
||||
{
|
||||
// Only clamp to max storable quantity.
|
||||
count = Math.Clamp(count, 0, MaxQuantityHaX);
|
||||
return true;
|
||||
}
|
||||
if (itemIndex == 0)
|
||||
{
|
||||
// No item, no count.
|
||||
count = 0;
|
||||
return true;
|
||||
}
|
||||
if (count <= 0)
|
||||
return count == 0 && hasNew;
|
||||
{
|
||||
// No count, ensure positive quantity.
|
||||
// Only allow an ItemID if game supports "new" item remembering.
|
||||
// Otherwise, it's safe to ignore the item.
|
||||
// Note: a Zero value might not be legal for certain items based on game progress, but we aren't really validating that.
|
||||
count = 0;
|
||||
return hasNew;
|
||||
}
|
||||
|
||||
// Clamp non-zero value to pouch/item rules.
|
||||
count = Clamp(type, itemIndex, count);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ namespace PKHeX.Core;
|
|||
|
||||
public sealed class PlayerBag3E : PlayerBag, IPlayerBag3
|
||||
{
|
||||
private const int BaseOffset = 0x0498;
|
||||
|
||||
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(ItemStorage3E.Instance);
|
||||
public override ItemStorage3E Info => ItemStorage3E.Instance;
|
||||
|
||||
|
|
@ -21,7 +19,7 @@ public sealed class PlayerBag3E : PlayerBag, IPlayerBag3
|
|||
new(0x000, 50, 999, info, PCItems),
|
||||
];
|
||||
|
||||
public PlayerBag3E(SAV3E sav) : this(sav.Large[BaseOffset..], sav.SecurityKey) { }
|
||||
public PlayerBag3E(SAV3E sav) : this(sav.LargeBlock.Inventory, sav.SmallBlock.SecurityKey) { }
|
||||
public PlayerBag3E(ReadOnlySpan<byte> data, uint security)
|
||||
{
|
||||
UpdateSecurityKey(security);
|
||||
|
|
@ -29,7 +27,7 @@ public PlayerBag3E(ReadOnlySpan<byte> data, uint security)
|
|||
}
|
||||
|
||||
public override void CopyTo(SaveFile sav) => CopyTo((SAV3E)sav);
|
||||
public void CopyTo(SAV3E sav) => CopyTo(sav.Large[BaseOffset..]);
|
||||
public void CopyTo(SAV3E sav) => CopyTo(sav.LargeBlock.Inventory);
|
||||
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
|
||||
|
||||
public override int GetMaxCount(InventoryType type, int itemIndex)
|
||||
|
|
|
|||
|
|
@ -4,14 +4,13 @@
|
|||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class PlayerBag3FRLG : PlayerBag, IPlayerBag3
|
||||
public sealed class PlayerBag3FRLG(bool VC) : PlayerBag, IPlayerBag3
|
||||
{
|
||||
private const int BaseOffset = 0x0298;
|
||||
public override IItemStorage Info => GetInfo(VC);
|
||||
private static IItemStorage GetInfo(bool vc) => vc ? ItemStorage3FRLG_VC.Instance : ItemStorage3FRLG.Instance;
|
||||
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(GetInfo(VC));
|
||||
|
||||
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(ItemStorage3FRLG.Instance);
|
||||
public override ItemStorage3FRLG Info => ItemStorage3FRLG.Instance;
|
||||
|
||||
private static InventoryPouch3[] GetPouches(ItemStorage3FRLG info) =>
|
||||
private static InventoryPouch3[] GetPouches(IItemStorage info) =>
|
||||
[
|
||||
new(0x078, 42, 999, info, Items),
|
||||
new(0x120, 30, 001, info, KeyItems),
|
||||
|
|
@ -21,15 +20,15 @@ public sealed class PlayerBag3FRLG : PlayerBag, IPlayerBag3
|
|||
new(0x000, 30, 999, info, PCItems),
|
||||
];
|
||||
|
||||
public PlayerBag3FRLG(SAV3FRLG sav) : this(sav.Large[BaseOffset..], sav.SecurityKey) { }
|
||||
public PlayerBag3FRLG(ReadOnlySpan<byte> data, uint security)
|
||||
public PlayerBag3FRLG(SAV3FRLG sav) : this(sav.LargeBlock.Inventory, sav.SmallBlock.SecurityKey, sav.IsVirtualConsole) { }
|
||||
public PlayerBag3FRLG(ReadOnlySpan<byte> data, uint security, bool vc) : this(vc)
|
||||
{
|
||||
UpdateSecurityKey(security);
|
||||
Pouches.LoadAll(data);
|
||||
}
|
||||
|
||||
public override void CopyTo(SaveFile sav) => CopyTo((SAV3FRLG)sav);
|
||||
public void CopyTo(SAV3FRLG sav) => CopyTo(sav.Large[BaseOffset..]);
|
||||
public void CopyTo(SAV3FRLG sav) => CopyTo(sav.LargeBlock.Inventory);
|
||||
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
|
||||
|
||||
public override int GetMaxCount(InventoryType type, int itemIndex)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ namespace PKHeX.Core;
|
|||
|
||||
public sealed class PlayerBag3RS : PlayerBag
|
||||
{
|
||||
private const int BaseOffset = 0x0498;
|
||||
|
||||
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(ItemStorage3RS.Instance);
|
||||
public override ItemStorage3RS Info => ItemStorage3RS.Instance;
|
||||
|
||||
|
|
@ -21,11 +19,11 @@ public sealed class PlayerBag3RS : PlayerBag
|
|||
new(0x000, 50, 999, info, PCItems),
|
||||
];
|
||||
|
||||
public PlayerBag3RS(SAV3RS sav) : this(sav.Large[BaseOffset..]) { }
|
||||
public PlayerBag3RS(SAV3RS sav) : this(sav.LargeBlock.Inventory) { }
|
||||
public PlayerBag3RS(ReadOnlySpan<byte> data) => Pouches.LoadAll(data);
|
||||
|
||||
public override void CopyTo(SaveFile sav) => CopyTo((SAV3RS)sav);
|
||||
public void CopyTo(SAV3RS sav) => CopyTo(sav.Large[BaseOffset..]);
|
||||
public void CopyTo(SAV3RS sav) => CopyTo(sav.LargeBlock.Inventory);
|
||||
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
|
||||
|
||||
public override int GetMaxCount(InventoryType type, int itemIndex)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public sealed class ItemStorage3Colo : IItemStorage
|
|||
540, 541, 542, 546, 547,
|
||||
];
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
|
||||
|
||||
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,14 +13,16 @@ public sealed class ItemStorage3E : IItemStorage
|
|||
public static ReadOnlySpan<ushort> Key =>
|
||||
[
|
||||
// R/S
|
||||
259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
|
||||
259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
|
||||
// FR/LG
|
||||
349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374,
|
||||
370, 371, 372,
|
||||
// E
|
||||
375, 376,
|
||||
];
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
|
||||
private static readonly ushort[] PCItems = [.. General, .. Berry, .. Balls, .. Machine];
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
|
||||
|
||||
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
|
||||
{
|
||||
|
|
@ -29,7 +31,7 @@ public sealed class ItemStorage3E : IItemStorage
|
|||
InventoryType.Balls => Balls,
|
||||
InventoryType.TMHMs => Machine,
|
||||
InventoryType.Berries => Berry,
|
||||
InventoryType.PCItems => General,
|
||||
InventoryType.PCItems => PCItems,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ public sealed class ItemStorage3FRLG : IItemStorage
|
|||
public static ReadOnlySpan<ushort> Key =>
|
||||
[
|
||||
// R/S
|
||||
259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
|
||||
260, 261, 262, 263, 264, 265,
|
||||
// FR/LG
|
||||
349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374,
|
||||
];
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
|
||||
|
||||
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
|
||||
{
|
||||
|
|
@ -31,3 +31,58 @@ public sealed class ItemStorage3FRLG : IItemStorage
|
|||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Item storage for <see cref="GameVersion.FR"/> and <see cref="GameVersion.LG"/> on <see cref="GameConsole.NX"/>.
|
||||
/// </summary>
|
||||
public sealed class ItemStorage3FRLG_VC : IItemStorage // TODO VC RSE: delete me and any usages as RSE gives the remainder of items.
|
||||
{
|
||||
public static readonly ItemStorage3FRLG_VC Instance = new();
|
||||
public ReadOnlySpan<ushort> GetItems(InventoryType type) => ItemStorage3FRLG.Instance.GetItems(type);
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !IsUnreleasedHeld(itemIndex); // use VC unreleased list
|
||||
|
||||
public static bool IsUnreleasedHeld(int itemIndex) => Unreleased.Contains((ushort)itemIndex);
|
||||
|
||||
private static ReadOnlySpan<ushort> Unreleased =>
|
||||
[
|
||||
// Unobtainable
|
||||
005, // Safari
|
||||
|
||||
// TODO RSE VC: Remove these
|
||||
007, 012, // Dive Ball, Premier Ball (Unobtainable without trading from R/S/E)
|
||||
039, 041, 042, 043, // Flutes (Yellow is obtainable via Coins)
|
||||
|
||||
// Unobtainable
|
||||
044, // Berry Juice
|
||||
|
||||
// TODO RSE VC: Remove these
|
||||
046, 047, // Shoal Salt, Shoal Shell
|
||||
048, 049, 050, 051, // Shards
|
||||
081, // Fluffy Tail
|
||||
121, 122, 123, 124, 125, 126, 127, 128, 129, // Mail
|
||||
168, // Liechi Berry (Mirage Island)
|
||||
|
||||
// Event Berries (Unobtainable)
|
||||
169, // Ganlon Berry (Event)
|
||||
170, // Salac Berry (Event)
|
||||
171, // Petaya Berry (Event)
|
||||
172, // Apicot Berry (Event)
|
||||
173, // Lansat Berry (Event)
|
||||
174, // Starf Berry (Event)
|
||||
175, // Enigma Berry (Event)
|
||||
|
||||
// TODO RSE VC: Remove these
|
||||
179, // BrightPowder
|
||||
180, // White Herb
|
||||
185, // Mental Herb
|
||||
186, // Choice Band
|
||||
191, // Soul Dew
|
||||
192, // DeepSeaTooth
|
||||
193, // DeepSeaScale
|
||||
198, // Scope Lens
|
||||
202, // Light Ball
|
||||
219, // Shell Bell
|
||||
254, 255, 256, 257, 258, 259, // Scarves
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,13 +54,13 @@ public sealed class ItemStorage3RS : IItemStorage
|
|||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
|
||||
];
|
||||
|
||||
internal static ReadOnlySpan<ushort> Unreleased => [005]; // Safari Ball
|
||||
internal static ReadOnlySpan<ushort> Unreleased => [005, 044]; // Safari Ball, Berry Juice
|
||||
|
||||
public static ushort[] GetAllHeld() => [..General, ..Balls, ..Berry, ..MachineOnlyTM];
|
||||
|
||||
private static readonly ushort[] PCItems = [..General, ..Key, .. Berry, ..Balls, ..Machine];
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
|
||||
|
||||
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public sealed class ItemStorage3XD : IItemStorage
|
|||
590, 591, 592, 593,
|
||||
];
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
|
||||
|
||||
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public sealed class ItemStorage4DP : ItemStorage4, IItemStorage
|
|||
|
||||
public static ushort[] GetAllHeld() => [..GeneralDP, ..Mail, ..Medicine, ..Berry, ..BallsDPPt, ..Battle, ..Machine[..^8]];
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
|
||||
|
||||
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public sealed class ItemStorage4HGSS : ItemStorage4, IItemStorage
|
|||
492, 493, 494, 495, 496, 497, 498, 499, 500, // Apricorn Balls
|
||||
];
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
|
||||
|
||||
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public sealed class ItemStorage4Pt : ItemStorage4, IItemStorage
|
|||
|
||||
public static ushort[] GetAllHeld() => [..GeneralPt, ..Mail, ..Medicine, ..Berry, ..BallsDPPt, ..Battle, ..Machine[..^8]];
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
|
||||
|
||||
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public sealed class ItemStorage5B2W2 : ItemStorage5, IItemStorage
|
|||
616, 617, 621, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638,
|
||||
];
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
|
||||
|
||||
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public sealed class ItemStorage5BW : ItemStorage5, IItemStorage
|
|||
616, 617, 621, 623, 624, 625, 626,
|
||||
];
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => true;
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => !Unreleased.Contains((ushort)itemIndex);
|
||||
|
||||
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -122,10 +122,6 @@ public sealed class ItemStorage9ZA : IItemStorage
|
|||
public static ReadOnlySpan<ushort> Unreleased =>
|
||||
[
|
||||
0016, // Cherish Ball
|
||||
|
||||
0664, // Blazikenite
|
||||
|
||||
2640, // Garchompite Z
|
||||
];
|
||||
|
||||
public int GetMax(InventoryType type) => type switch
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
#pragma warning disable CA1857
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
|
|||
|
|
@ -71,24 +71,19 @@ private static EncounterArea3[] GetRegular([ConstantExpected] string resource, [
|
|||
new(147, 18, FR) { FixedBall = Ball.Poke, Location = 94 }, // Dratini
|
||||
new(137, 26, FR) { FixedBall = Ball.Poke, Location = 94 }, // Porygon
|
||||
|
||||
new(386, 30, FR ) { Location = 187, FatefulEncounter = true, Form = 1 }, // Deoxys @ Birth Island
|
||||
new(386, 30, FR) { Location = 187, FatefulEncounter = true, Form = 1 }, // Deoxys @ Birth Island
|
||||
];
|
||||
|
||||
public static readonly EncounterStatic3[] StaticLG =
|
||||
[
|
||||
// Celadon City Game Corner
|
||||
new(063, 09, FR) { FixedBall = Ball.Poke, Location = 94 }, // Abra
|
||||
new(035, 08, FR) { FixedBall = Ball.Poke, Location = 94 }, // Clefairy
|
||||
new(123, 25, FR) { FixedBall = Ball.Poke, Location = 94 }, // Scyther
|
||||
new(147, 18, FR) { FixedBall = Ball.Poke, Location = 94 }, // Dratini
|
||||
new(137, 26, FR) { FixedBall = Ball.Poke, Location = 94 }, // Porygon
|
||||
|
||||
new(063, 07, LG) { FixedBall = Ball.Poke, Location = 94 }, // Abra
|
||||
new(035, 12, LG) { FixedBall = Ball.Poke, Location = 94 }, // Clefairy
|
||||
new(127, 18, LG) { FixedBall = Ball.Poke, Location = 94 }, // Pinsir
|
||||
new(147, 24, LG) { FixedBall = Ball.Poke, Location = 94 }, // Dratini
|
||||
new(137, 18, LG) { FixedBall = Ball.Poke, Location = 94 }, // Porygon
|
||||
new(386, 30, LG) { Location = 187, FatefulEncounter = true, Form = 2 }, // Deoxys @ Birth Island
|
||||
|
||||
new(386, 30, LG) { Location = 187, FatefulEncounter = true, Form = 2 }, // Deoxys @ Birth Island
|
||||
];
|
||||
|
||||
private static ReadOnlySpan<byte> TradeContest_Cool => [ 30, 05, 05, 05, 05, 10 ];
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using static PKHeX.Core.EncounterUtil;
|
||||
using static PKHeX.Core.GameVersion;
|
||||
using static PKHeX.Core.AbilityPermission;
|
||||
#pragma warning disable CA1857
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ public static class Encounters5B2W2
|
|||
|
||||
public static readonly EncounterStatic5N[] Encounter_B2W2_N =
|
||||
[
|
||||
// N's Pokemon
|
||||
// N's Pokémon
|
||||
new(0xFF01007F) { Species = 509, Level = 07, Location = 015, Ability = OnlySecond, Nature = Nature.Timid }, // Purloin @ Route 2
|
||||
new(0xFF01007F) { Species = 519, Level = 13, Location = 033, Ability = OnlySecond, Nature = Nature.Sassy }, // Pidove @ Pinwheel Forest
|
||||
new(0xFF00003F) { Species = 532, Level = 13, Location = 033, Ability = OnlyFirst, Nature = Nature.Rash }, // Timburr @ Pinwheel Forest
|
||||
|
|
|
|||
|
|
@ -236,6 +236,7 @@ public static class EncounterServerDate
|
|||
{1540, new(2025, 09, 25, 2025, 10, 25)}, // Shiny Miraidon / Koraidon Gift
|
||||
{0070, new(2025, 10, 31, 2027, 02, 01)}, // PokéCenter Fidough Birthday Gift
|
||||
{0526, new(2025, 11, 21, 2025, 12, 01)}, // LAIC 2026 Federico Camporesi’s Whimsicott
|
||||
{0527, new(2026, 02, 12, 2026, 02, 21)}, // EUIC 2026 Yuma Kinugawa's Hisuian Typhlosion
|
||||
|
||||
{9021, HOME3_ML}, // Hidden Ability Sprigatito
|
||||
{9022, HOME3_ML}, // Hidden Ability Fuecoco
|
||||
|
|
|
|||
|
|
@ -127,8 +127,8 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, Le
|
|||
|
||||
private static bool IsBallCompatible(IFixedBall e, PKM pk) => e.FixedBall switch
|
||||
{
|
||||
Ball.Safari when pk.Ball is (byte)Ball.Safari => true,
|
||||
Ball.Sport when pk.Ball is (byte)Ball.Sport => true,
|
||||
Ball.Safari => pk.Ball is (byte)Ball.Safari,
|
||||
Ball.Sport => pk.Ball is (byte)Ball.Sport && pk is not BK4 || pk is BK4 { BallDPPt: (byte)Ball.Poke }, // side transfer forgetting ball
|
||||
_ => pk.Ball is not ((byte)Ball.Safari or (byte)Ball.Sport),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ public EncounterCriteria()
|
|||
/// Determines whether a specific Nature is specified in the criteria or if complex nature mutations are allowed.
|
||||
/// </summary>
|
||||
/// <returns>><see langword="true"/> if a Nature is specified or complex nature mutations are allowed; otherwise, <see langword="false"/>.</returns>
|
||||
public bool IsSpecifiedNature() => Nature != Nature.Random || Mutations.IsComplexNature();
|
||||
public bool IsSpecifiedNature() => Nature.IsFixed || Mutations.IsComplexNature();
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a level range is specified in the criteria.
|
||||
|
|
@ -126,6 +126,12 @@ public EncounterCriteria()
|
|||
/// <returns>><see langword="true"/> if an Ability is specified; otherwise, <see langword="false"/>.</returns>
|
||||
public bool IsSpecifiedAbility() => Ability != Any12H;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the shiny value is explicitly specified rather than set to random.
|
||||
/// </summary>
|
||||
/// <returns>><see langword="true"/> if a Shiny is specified; otherwise, <see langword="false"/>.</returns>
|
||||
public bool IsSpecifiedShiny() => Shiny != Shiny.Random;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether all IVs are specified in the criteria.
|
||||
/// </summary>
|
||||
|
|
@ -183,6 +189,20 @@ public int GetCountSpecifiedIVs() => Convert.ToInt32(IV_HP != RandomIV)
|
|||
_ => throw new ArgumentOutOfRangeException(nameof(ability), ability, null),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified shiny properties satisfy the shiny criteria based on the current <see cref="Shiny"/> setting.
|
||||
/// </summary>
|
||||
/// <returns>><see langword="true"/> if the index satisfies the shiny criteria; otherwise, <see langword="false"/>.</returns>
|
||||
public bool IsSatisfiedShiny(uint xor, uint cmp) => Shiny switch
|
||||
{
|
||||
Shiny.Random => true,
|
||||
Shiny.Never => xor > cmp, // not shiny
|
||||
Shiny.AlwaysSquare => xor == 0, // square shiny
|
||||
Shiny.AlwaysStar => xor < cmp && xor != 0, // star shiny
|
||||
Shiny.Always => xor < cmp, // shiny
|
||||
_ => false, // shouldn't be set
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Nature satisfies the criteria.
|
||||
/// </summary>
|
||||
|
|
@ -191,7 +211,7 @@ public int GetCountSpecifiedIVs() => Convert.ToInt32(IV_HP != RandomIV)
|
|||
public bool IsSatisfiedNature(Nature nature)
|
||||
{
|
||||
if (Mutations.HasFlag(AllowOnlyNeutralNature))
|
||||
return nature.IsNeutral();
|
||||
return nature.IsNeutral;
|
||||
if (Nature == Nature.Random)
|
||||
return true;
|
||||
return nature == Nature || Mutations.HasFlag(CanMintNature);
|
||||
|
|
@ -300,7 +320,7 @@ public Nature GetNature(Nature encValue)
|
|||
/// </summary>
|
||||
public Nature GetNature()
|
||||
{
|
||||
if (Nature != Nature.Random)
|
||||
if (Nature.IsFixed)
|
||||
return Nature;
|
||||
var result = (Nature)Util.Rand.Next(25);
|
||||
if (Mutations.HasFlag(AllowOnlyNeutralNature))
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ private static bool VerifySecondaryChecks(PKM pk, LegalInfo info, PeekEnumerator
|
|||
}
|
||||
else if (pk is PK1 pk1)
|
||||
{
|
||||
var hasGen2 = MoveInfo.IsAnyFromGeneration(2, info.Moves);
|
||||
var hasGen2 = MoveInfo.IsAnyFromGeneration(EntityContext.Gen2, info.Moves);
|
||||
if (hasGen2)
|
||||
{
|
||||
if (!ParseSettings.AllowGen1Tradeback)
|
||||
|
|
|
|||
|
|
@ -34,10 +34,9 @@ public static class EncounterMutationUtil
|
|||
/// <param name="level">Destination level</param>
|
||||
public static EncounterMutation GetSuggested(EntityContext targetContext, byte level)
|
||||
{
|
||||
var gen = targetContext.Generation;
|
||||
if (gen < 6)
|
||||
if (targetContext.IsEraPre3DS)
|
||||
return EncounterMutation.None;
|
||||
if (gen < 8)
|
||||
if (targetContext.IsEraPreSwitch)
|
||||
{
|
||||
if (targetContext is EntityContext.Gen7b)
|
||||
return level != 100 ? EncounterMutation.None : EncounterMutation.CanMaxIndividualStat;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public static IEnumerable<IEncounterable> GenerateEncounters(PKM pk, ITrainerInf
|
|||
yield break;
|
||||
|
||||
OptimizeCriteria(pk, info);
|
||||
var vers = versions.Length >= 1 ? versions : GameUtil.GetVersionsWithinRange(pk, pk.Format);
|
||||
var vers = versions.Length >= 1 ? versions : GameUtil.GetVersionsWithinRange(pk, pk.Context);
|
||||
foreach (var version in vers)
|
||||
{
|
||||
var encounters = GenerateVersionEncounters(pk, moves, version);
|
||||
|
|
@ -63,12 +63,12 @@ public static void OptimizeCriteria(PKM pk, ITrainerID32ReadOnly info)
|
|||
/// Gets possible encounters that allow all moves requested to be learned.
|
||||
/// </summary>
|
||||
/// <param name="pk">Rough Pokémon data which contains the requested species, gender, and form.</param>
|
||||
/// <param name="generation">Specific generation to iterate versions for.</param>
|
||||
/// <param name="context">Specific generation to iterate versions for.</param>
|
||||
/// <param name="moves">Moves that the resulting <see cref="IEncounterable"/> must be able to learn.</param>
|
||||
/// <returns>A consumable <see cref="IEncounterable"/> list of possible encounters.</returns>
|
||||
public static IEnumerable<IEncounterable> GenerateEncounter(PKM pk, byte generation, ReadOnlyMemory<ushort> moves)
|
||||
public static IEnumerable<IEncounterable> GenerateEncounter(PKM pk, EntityContext context, ReadOnlyMemory<ushort> moves)
|
||||
{
|
||||
var vers = GameUtil.GetVersionsInGeneration(generation, pk.Version);
|
||||
var vers = GameUtil.GetVersionsInGeneration(context, pk.Version);
|
||||
return GenerateEncounters(pk, moves, vers);
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ public static IEnumerable<IEncounterable> GenerateEncounters(PKM pk, ReadOnlyMem
|
|||
if (!IsSane(pk, moves.Span))
|
||||
yield break;
|
||||
|
||||
var vers = versions.Length != 0 ? versions : GameUtil.GetVersionsWithinRange(pk, pk.Format);
|
||||
var vers = versions.Length != 0 ? versions : GameUtil.GetVersionsWithinRange(pk, pk.Context);
|
||||
foreach (var version in vers)
|
||||
{
|
||||
foreach (var enc in GenerateVersionEncounters(pk, moves, version))
|
||||
|
|
@ -382,7 +382,7 @@ private static bool IsSane(ReadOnlySpan<EvoCriteria> chain, IEncounterTemplate e
|
|||
return true;
|
||||
if (enc is IEncounterFormRandom { IsRandomUnspecificForm: true } or { Species: (ushort)Species.Unown })
|
||||
return true;
|
||||
if (enc is EncounterStatic7 {IsTotem: true} && evo.Form == 0 && current.Generation > 7) // totems get form wiped
|
||||
if (enc is EncounterStatic7 {IsTotem: true} && evo.Form == 0 && current != EntityContext.Gen7) // totems get form wiped
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public static Shiny GetType(PKM pk)
|
|||
if (!shiny)
|
||||
return Shiny.Never;
|
||||
|
||||
if (IsSquareShinyExist(pk))
|
||||
if (IsSquareShinyExist(pk) && pk.Context.IsSquareShinyDifferentiated)
|
||||
return Shiny.AlwaysSquare;
|
||||
return Shiny.AlwaysStar;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ public enum PogoType : byte
|
|||
|
||||
// Pokémon captured in the wild.
|
||||
Wild,
|
||||
WildLevel20,
|
||||
WildLevel25,
|
||||
|
||||
// Pokémon hatched from Eggs.
|
||||
Egg,
|
||||
|
|
@ -137,6 +139,8 @@ public static class PogoTypeExtensions
|
|||
public byte LevelMin => encounterType switch
|
||||
{
|
||||
Wild => 1,
|
||||
WildLevel20 => 20,
|
||||
WildLevel25 => 25,
|
||||
Egg => 1,
|
||||
Egg12km => 8,
|
||||
Raid => 20,
|
||||
|
|
@ -204,6 +208,8 @@ public static class PogoTypeExtensions
|
|||
public int MinimumIV => encounterType switch
|
||||
{
|
||||
Wild => 0,
|
||||
WildLevel20 => 0,
|
||||
WildLevel25 => 0,
|
||||
RaidMythical => 10,
|
||||
RaidShadowMythical => 8,
|
||||
RaidShadowMythicalGOWA => 8,
|
||||
|
|
@ -324,6 +330,6 @@ public bool IsBallValid(Ball ball)
|
|||
_ => Ball.None, // Poké, Great, Ultra
|
||||
};
|
||||
|
||||
public bool IsSpecialResearch => encounterType is >= SpecialMythical and < TimedMythical;
|
||||
public bool IsSpecialResearch => encounterType is SpecialResearch or >= SpecialMythical and < TimedMythical;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,12 +58,16 @@ public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
|||
|
||||
// Force Hatch
|
||||
Language = language,
|
||||
OriginalTrainerName = tr.OT,
|
||||
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
|
||||
OriginalTrainerFriendship = 120,
|
||||
MetLevel = 0,
|
||||
MetLocation = Location,
|
||||
};
|
||||
// Copy from SaveFile's OT name. Trash bytes here should be pure, but our OT name might not always source from a PK3/SAV3.
|
||||
// Condition the buffer as if it came from a correct SAV3 named after the OT.
|
||||
var ot = pk.OriginalTrainerTrash;
|
||||
ot[..(language == 1 ? 6 : 7)].Fill(0xFF);
|
||||
pk.OriginalTrainerName = EncounterUtil.GetTrainerName(tr, language);
|
||||
|
||||
SetEncounterMoves(pk);
|
||||
|
||||
|
|
|
|||
|
|
@ -64,11 +64,15 @@ public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
|||
Ball = (byte)GetRequiredBall(Ball.Poke),
|
||||
|
||||
Language = language,
|
||||
OriginalTrainerName = tr.OT,
|
||||
OriginalTrainerGender = tr.Gender,
|
||||
ID32 = tr.ID32,
|
||||
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
|
||||
};
|
||||
// Copy from SaveFile's OT name. Trash bytes here should be pure, but our OT name might not always source from a PK3/SAV3.
|
||||
// Condition the buffer as if it came from a correct SAV3 named after the OT.
|
||||
var ot = pk.OriginalTrainerTrash;
|
||||
ot[..(language == 1 ? 6 : 7)].Fill(0xFF);
|
||||
pk.OriginalTrainerName = EncounterUtil.GetTrainerName(tr, language);
|
||||
|
||||
SetPINGA(pk, criteria, pi);
|
||||
SetEncounterMoves(pk);
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public sealed record EncounterStatic3(ushort Species, byte Level, GameVersion Ve
|
|||
|
||||
public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
||||
{
|
||||
int lang = GetTemplateLanguage(tr);
|
||||
int language = GetTemplateLanguage(tr);
|
||||
var version = this.GetCompatibleVersion(tr.Version);
|
||||
var pi = PersonalTable.E[Species];
|
||||
var pk = new PK3
|
||||
|
|
@ -56,12 +56,16 @@ public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
|||
Ball = (byte)(FixedBall != Ball.None ? FixedBall : Ball.Poke),
|
||||
FatefulEncounter = FatefulEncounter,
|
||||
|
||||
Language = lang,
|
||||
OriginalTrainerName = EncounterUtil.GetTrainerName(tr, lang),
|
||||
Language = language,
|
||||
OriginalTrainerGender = tr.Gender,
|
||||
ID32 = tr.ID32,
|
||||
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation),
|
||||
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
|
||||
};
|
||||
// Copy from SaveFile's OT name. Trash bytes here should be pure, but our OT name might not always source from a PK3/SAV3.
|
||||
// Condition the buffer as if it came from a correct SAV3 named after the OT.
|
||||
var ot = pk.OriginalTrainerTrash;
|
||||
ot[..(language == 1 ? 6 : 7)].Fill(0xFF);
|
||||
pk.OriginalTrainerName = EncounterUtil.GetTrainerName(tr, language);
|
||||
|
||||
if (IsEgg)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -152,6 +152,8 @@ public RandomCorrelationRating IsCompatible(PIDType type, PKM pk)
|
|||
return Match;
|
||||
if (type is PIDType.CXDAnti && FatefulEncounter)
|
||||
return Match;
|
||||
if (type is PIDType.CXD_ColoStarter && pk.Species is (ushort)Core.Species.Umbreon)
|
||||
return Match; // Can be satisfying the Colosseum correlation too (only disqualified by Fateful Encounter later)
|
||||
return Mismatch;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
|
|||
return false;
|
||||
if (Gender != FixedGenderUtil.GenderRandom && Gender != pk.Gender)
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|||
return false;
|
||||
if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk))
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
|
|||
return false;
|
||||
if (Gender != pk.Gender)
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|||
return false;
|
||||
if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk))
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
|
|||
return false;
|
||||
if (Gender != pk.Gender)
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,8 +122,6 @@ protected virtual void SetPINGA(PK8 pk, in EncounterCriteria criteria, PersonalI
|
|||
}
|
||||
|
||||
FinishCorrelation(pk, seed);
|
||||
if (criteria.IsSpecifiedNature() && criteria.Nature != pk.Nature && criteria.Nature.IsMint())
|
||||
pk.StatNature = criteria.Nature;
|
||||
}
|
||||
|
||||
protected GenerateParam8 GetParam() => GetParam(Info);
|
||||
|
|
|
|||
|
|
@ -57,6 +57,10 @@ protected override void SetPINGA(PK8 pk, in EncounterCriteria criteria, Personal
|
|||
{
|
||||
Span<int> iv = stackalloc int[6];
|
||||
|
||||
// Honor a shiny request only at the end; generate as never-shiny to avoid shiny PID rejection in main RNG method.
|
||||
var isShinyRequested = criteria.Shiny.IsShiny();
|
||||
var iterCriteria = criteria with { Shiny = ShinyMethod };
|
||||
|
||||
int ctr = 0;
|
||||
var rand = new Xoroshiro128Plus(Util.Rand.Rand64());
|
||||
var param = GetParam(pi);
|
||||
|
|
@ -64,23 +68,22 @@ protected override void SetPINGA(PK8 pk, in EncounterCriteria criteria, Personal
|
|||
const int max = 100_000;
|
||||
do
|
||||
{
|
||||
if (TryApply(pk, seed = rand.Next(), iv, param, criteria))
|
||||
if (TryApply(pk, seed = rand.Next(), iv, param, iterCriteria))
|
||||
break;
|
||||
} while (++ctr < max);
|
||||
|
||||
if (ctr == max) // fail
|
||||
{
|
||||
if (!TryApply(pk, seed = rand.Next(), iv, param, criteria.WithoutIVs()))
|
||||
iterCriteria = iterCriteria.WithoutIVs();
|
||||
if (!TryApply(pk, seed = rand.Next(), iv, param, iterCriteria))
|
||||
{
|
||||
var tmp = EncounterCriteria.Unrestricted with { Shiny = ShinyMethod };
|
||||
while (!TryApply(pk, seed = rand.Next(), iv, param, tmp)) { }
|
||||
iterCriteria = EncounterCriteria.Unrestricted with { Shiny = ShinyMethod };
|
||||
while (!TryApply(pk, seed = rand.Next(), iv, param, iterCriteria)) { }
|
||||
}
|
||||
}
|
||||
|
||||
FinishCorrelation(pk, seed);
|
||||
if (criteria.IsSpecifiedNature() && criteria.Nature != pk.Nature && criteria.Nature.IsMint())
|
||||
pk.StatNature = criteria.Nature;
|
||||
if (criteria.Shiny.IsShiny())
|
||||
if (isShinyRequested)
|
||||
pk.PID = ShinyUtil.GetShinyPID(pk.TID16, pk.SID16, pk.PID, ShinyXor);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
|
|||
{
|
||||
if (!Shiny.IsValid(pk))
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
if (Gender != FixedGenderUtil.GenderRandom && pk.Gender != Gender)
|
||||
return false;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user