From 2f9512e33bf70b333531e38aec45a61eeb36d16e Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 15 Apr 2026 21:49:07 -0500 Subject: [PATCH] Misc tweaks Fix box export (individual files) not including party data in the bin. Previous behavior would have 00'd party data if not force-calculated. Just calculate party stats if not present for the format. Log Database entity as party format, just to avoid using the Stored format size. Revise gen2 odd egg declarations to group shiny eggs together, and enforce the Shiny property for object filtering. If IVs are specified, then Shininess property needs to be provided (rather than "random" which is untrue). Simplify some logic paths for trade1/trade2. Extract some of the nuance of transferred Hiragana Dugtrio so the unit test is xref'd nicely. --- .../Saves/Slots/Exporting/BoxExport.cs | 12 +++++-- .../Encounters/Data/Gen2/Encounters2.cs | 36 ++++++++++--------- .../Templates/Gen1/EncounterTrade1.cs | 36 +++++++++---------- .../Templates/Gen2/EncounterTrade2.cs | 6 ++-- PKHeX.WinForms/Subforms/SAV_Database.cs | 4 +-- Tests/PKHeX.Core.Tests/PKM/StringTests.cs | 2 +- 6 files changed, 52 insertions(+), 44 deletions(-) diff --git a/PKHeX.Core/Editing/Saves/Slots/Exporting/BoxExport.cs b/PKHeX.Core/Editing/Saves/Slots/Exporting/BoxExport.cs index 1f65df3c5..2cb97cbdb 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Exporting/BoxExport.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Exporting/BoxExport.cs @@ -86,8 +86,9 @@ public static int Export(SaveFile sav, string destPath, IFileNamer namer, B int count = GetSlotCountForBox(boxSlotCount, box, total); int ctr = 0; - // Export each slot in the box. - Span data = stackalloc byte[sav.SIZE_STORED]; + // Export each slot in the box with party stats, to be nice to any external analysis. + bool isPartyFormat = sav.SIZE_BOXSLOT == sav.SIZE_PARTY; + Span data = stackalloc byte[sav.SIZE_PARTY]; for (int slot = 0; slot < count; slot++) { var pk = sav.GetBoxSlotAtIndex(box, slot); @@ -99,7 +100,12 @@ public static int Export(SaveFile sav, string destPath, IFileNamer namer, B var fileName = GetFileName(pk, settings.FileIndexPrefix, namer, box, slot, boxSlotCount); var fn = Path.Combine(destPath, fileName); - pk.WriteDecryptedDataStored(data); + + // Assume that all PKM read for the loop all are the same shape; the if-else will always travel one path. + // We don't have to worry about lingering party data from a previous loop iteration. + if (!isPartyFormat) + pk.ForcePartyData(); // Rather than export all-zero party stats, calculate what they would be. + pk.WriteDecryptedDataParty(data); File.WriteAllBytes(fn, data); ctr++; } diff --git a/PKHeX.Core/Legality/Encounters/Data/Gen2/Encounters2.cs b/PKHeX.Core/Legality/Encounters/Data/Gen2/Encounters2.cs index ec82daafa..1737bf923 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Gen2/Encounters2.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Gen2/Encounters2.cs @@ -95,22 +95,26 @@ internal static class Encounters2 new(202, 15, C) { Location = 016 }, // Wobbuffet @ Goldenrod City (Game Corner) ]; + private static IndividualValueSet AllZero => new(00, 00, 00, 00, 00, 00); + private static IndividualValueSet Shiny2 => new(00, 02, 10, 10, 10, 10); + public static readonly EncounterStatic2[] StaticOddEggC = [ - new(172, 05, C) { IsEgg = true, Gender = 1, IVs = new(00, 00, 00, 00, 00, 00), Moves = new((int)Move.ThunderShock,(int)Move.Charm, (int)Move.DizzyPunch)}, // Pichu - new(172, 05, C) { IsEgg = true, Gender = 1, IVs = new(00, 02, 10, 10, 10, 10), Moves = new((int)Move.ThunderShock,(int)Move.Charm, (int)Move.DizzyPunch)}, // Shiny Pichu - new(173, 05, C) { IsEgg = true, Gender = 1, IVs = new(00, 00, 00, 00, 00, 00), Moves = new((int)Move.Pound, (int)Move.Charm, (int)Move.DizzyPunch)}, // Cleffa - new(173, 05, C) { IsEgg = true, Gender = 1, IVs = new(00, 02, 10, 10, 10, 10), Moves = new((int)Move.Pound, (int)Move.Charm, (int)Move.DizzyPunch)}, // Shiny Cleffa - new(174, 05, C) { IsEgg = true, Gender = 1, IVs = new(00, 00, 00, 00, 00, 00), Moves = new((int)Move.Sing, (int)Move.Charm, (int)Move.DizzyPunch)}, // Igglybuff - new(174, 05, C) { IsEgg = true, Gender = 1, IVs = new(00, 02, 10, 10, 10, 10), Moves = new((int)Move.Sing, (int)Move.Charm, (int)Move.DizzyPunch)}, // Shiny Igglybuff - new(236, 05, C) { IsEgg = true, IVs = new(00, 00, 00, 00, 00, 00), Moves = new((int)Move.Tackle, (int)Move.DizzyPunch)}, // Tyrogue - new(236, 05, C) { IsEgg = true, IVs = new(00, 02, 10, 10, 10, 10), Moves = new((int)Move.Tackle, (int)Move.DizzyPunch)}, // Shiny Tyrogue - new(238, 05, C) { IsEgg = true, Gender = 1, IVs = new(00, 00, 00, 00, 00, 00), Moves = new((int)Move.Pound, (int)Move.Lick, (int)Move.DizzyPunch)}, // Smoochum - new(238, 05, C) { IsEgg = true, Gender = 1, IVs = new(00, 02, 10, 10, 10, 10), Moves = new((int)Move.Pound, (int)Move.Lick, (int)Move.DizzyPunch)}, // Shiny Smoochum - new(239, 05, C) { IsEgg = true, Gender = 1, IVs = new(00, 00, 00, 00, 00, 00), Moves = new((int)Move.QuickAttack, (int)Move.Leer, (int)Move.DizzyPunch)}, // Elekid - new(239, 05, C) { IsEgg = true, Gender = 1, IVs = new(00, 02, 10, 10, 10, 10), Moves = new((int)Move.QuickAttack, (int)Move.Leer, (int)Move.DizzyPunch)}, // Shiny Elekid - new(240, 05, C) { IsEgg = true, Gender = 1, IVs = new(00, 00, 00, 00, 00, 00), Moves = new((int)Move.Ember, (int)Move.DizzyPunch)}, // Magby - new(240, 05, C) { IsEgg = true, Gender = 1, IVs = new(00, 02, 10, 10, 10, 10), Moves = new((int)Move.Ember, (int)Move.DizzyPunch)}, // Shiny Magby + new(172, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.ThunderShock,(int)Move.Charm, (int)Move.DizzyPunch)}, // Pichu + new(173, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Pound, (int)Move.Charm, (int)Move.DizzyPunch)}, // Cleffa + new(174, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Sing, (int)Move.Charm, (int)Move.DizzyPunch)}, // Igglybuff + new(236, 05, C) { IsEgg = true, Gender = 0, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Tackle, (int)Move.DizzyPunch)}, // Tyrogue + new(238, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Pound, (int)Move.Lick, (int)Move.DizzyPunch)}, // Smoochum + new(239, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.QuickAttack, (int)Move.Leer, (int)Move.DizzyPunch)}, // Elekid + new(240, 05, C) { IsEgg = true, Gender = 1, IVs = AllZero, Shiny = Shiny.Never, Moves = new((int)Move.Ember, (int)Move.DizzyPunch)}, // Magby + + new(172, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.ThunderShock,(int)Move.Charm, (int)Move.DizzyPunch)}, // Shiny Pichu + new(173, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Pound, (int)Move.Charm, (int)Move.DizzyPunch)}, // Shiny Cleffa + new(174, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Sing, (int)Move.Charm, (int)Move.DizzyPunch)}, // Shiny Igglybuff + new(236, 05, C) { IsEgg = true, Gender = 0, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Tackle, (int)Move.DizzyPunch)}, // Shiny Tyrogue + new(238, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Pound, (int)Move.Lick, (int)Move.DizzyPunch)}, // Shiny Smoochum + new(239, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.QuickAttack, (int)Move.Leer, (int)Move.DizzyPunch)}, // Shiny Elekid + new(240, 05, C) { IsEgg = true, Gender = 1, IVs = Shiny2, Shiny = Shiny.Always, Moves = new((int)Move.Ember, (int)Move.DizzyPunch)}, // Shiny Magby ]; internal static readonly EncounterStatic2 CelebiVC = new(251, 30, C) { Location = 014 }; // Celebi @ Ilex Forest (VC) @@ -128,7 +132,7 @@ internal static class Encounters2 new(TradeNames, 7, 178, 15, 15616) { Gender = 0, IVs = new(08, 09, 06, 08, 06, 06) }, // Xatu @ Pewter City for Haunter [wild] new(TradeNames, 8, 082, 05, 50082) { Gender = 2, IVs = new(08, 09, 06, 06, 06, 06) }, // Magneton @ Power Plant for Dugtrio [traded for Lickitung] - new(TradeNames, 9, 021, 10, 01001), // Spearow @ Goldenrod City for free - new(TradeNames, 10, 213, 15, 00518), // Shuckle @ Cianwood City for free + new(TradeNames, 9, 021, 10, 01001) { Shiny = Shiny.Random }, // Spearow @ Goldenrod City for free + new(TradeNames, 10, 213, 15, 00518) { Shiny = Shiny.Random }, // Shuckle @ Cianwood City for free ]; } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterTrade1.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterTrade1.cs index 32884c25f..890a95d86 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterTrade1.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen1/EncounterTrade1.cs @@ -48,27 +48,25 @@ public EncounterTrade1(ReadOnlySpan names, byte index, ushort species, LevelMinGSC = levelMinGSC; } + /// + /// When transferred to Gen7+ via Bank, the nickname for a Japanese Dugtrio in Hiragana changes from "ぐリお" to "ぐりお". + /// + public const string HiraganaDugtrio7 = "ぐりお"; + private bool IsNicknameValid(PKM pk, ReadOnlySpan nick) { - if (pk.Format <= 2) - return IsNicknameAnyMatch(nick); - - // Converted string 1/2->7 to language specific value // Nicknames can be from any of the languages it can trade between. - int lang = pk.Language; - if (lang == 1) - { - // Special consideration for Hiragana strings that are transferred - if (Version == GameVersion.YW && Species == (int)Core.Species.Dugtrio) - return nick is "ぐりお"; - return nick.SequenceEqual(Nicknames.Span[(int)LanguageID.Japanese]); - } + if (!pk.Japanese) + return DetectLanguage(nick, Nicknames.Span, 2) >= 2; - return GetNicknameIndex(nick) >= 2; + // Converted Japanese strings 1/2->7 can mutate from an exact match. + // Special consideration for Hiragana strings that are transferred: only Dugtrio's nickname changes when transferred to Gen7+. + if (pk.Format > 2 && Version == GameVersion.YW && Species == (int)Core.Species.Dugtrio) + return nick is HiraganaDugtrio7; + // Otherwise, must match the Japanese nickname exactly. + return Nicknames.Span[(int)LanguageID.Japanese].SequenceEqual(nick); } - private bool IsNicknameAnyMatch(ReadOnlySpan current) => GetNicknameIndex(current) >= 0; - private static bool IsTrainerNameValid(PKM pk) { if (pk.Format <= 2) @@ -83,12 +81,12 @@ private static bool IsTrainerNameValid(PKM pk) return trainer.SequenceEqual(expect); } - private int GetNicknameIndex(ReadOnlySpan nickname) => GetIndex(nickname, Nicknames.Span); - - private static int GetIndex(ReadOnlySpan name, ReadOnlySpan arr) + private static int DetectLanguage(ReadOnlySpan name, ReadOnlySpan arr, int start = 1) { - for (int i = 0; i < arr.Length; i++) + for (int i = start; i < arr.Length; i++) { + if (i == (int)LanguageID.UNUSED_6) + continue; if (name.SequenceEqual(arr[i])) return i; } diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterTrade2.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterTrade2.cs index c6d07fca3..52d02df67 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterTrade2.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterTrade2.cs @@ -16,7 +16,6 @@ public sealed record EncounterTrade2 : IEncounterable, IEncounterMatch, IEncount public bool IsEgg => false; public Ball FixedBall => Ball.Poke; public AbilityPermission Ability => AbilityPermission.OnlyHidden; - public Shiny Shiny => Shiny.Random; public bool IsShiny => false; public ushort EggLocation => 0; public bool IsFixedTrainer => true; @@ -33,6 +32,7 @@ public sealed record EncounterTrade2 : IEncounterable, IEncounterMatch, IEncount private readonly ReadOnlyMemory TrainerNames; private readonly ReadOnlyMemory Nicknames; + public Shiny Shiny { get; init; } = Shiny.Never; public byte Gender { get; init; } public byte OTGender { get; init; } public IndividualValueSet IVs { get; init; } @@ -168,8 +168,8 @@ private int DetectLanguage(PKM pk, ReadOnlySpan trainer, ReadOnlySpan data = stackalloc byte[pk.SIZE_STORED]; - pk.WriteDecryptedDataStored(data); + Span data = stackalloc byte[pk.SIZE_PARTY]; + pk.WriteDecryptedDataParty(data); File.WriteAllBytes(path, data); var info = new SlotInfoFileSingle(path); diff --git a/Tests/PKHeX.Core.Tests/PKM/StringTests.cs b/Tests/PKHeX.Core.Tests/PKM/StringTests.cs index d20d827b6..83851361a 100644 --- a/Tests/PKHeX.Core.Tests/PKM/StringTests.cs +++ b/Tests/PKHeX.Core.Tests/PKM/StringTests.cs @@ -75,7 +75,7 @@ public static void Encode45(ushort g4, char g5) } [Theory] - [InlineData("ぐリお", "ぐりお")] + [InlineData("ぐリお", EncounterTrade1.HiraganaDugtrio7)] public static void ConvertStringVC(string g12, string g7) { Span b12 = stackalloc byte[g12.Length];