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];