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.
This commit is contained in:
Kurt
2026-04-15 21:49:07 -05:00
parent 234f402beb
commit 2f9512e33b
6 changed files with 52 additions and 44 deletions

View File

@@ -86,8 +86,9 @@ 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];
// 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<byte> 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<PKM> 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++;
}

View File

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

View File

@@ -48,27 +48,25 @@ public EncounterTrade1(ReadOnlySpan<string[]> names, byte index, ushort species,
LevelMinGSC = levelMinGSC;
}
/// <summary>
/// When transferred to Gen7+ via Bank, the nickname for a Japanese Dugtrio in Hiragana changes from "ぐリお" to "ぐりお".
/// </summary>
public const string HiraganaDugtrio7 = "ぐりお";
private bool IsNicknameValid(PKM pk, ReadOnlySpan<char> 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<char> 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<char> nickname) => GetIndex(nickname, Nicknames.Span);
private static int GetIndex(ReadOnlySpan<char> name, ReadOnlySpan<string> arr)
private static int DetectLanguage(ReadOnlySpan<char> name, ReadOnlySpan<string> 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;
}

View File

@@ -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<string> TrainerNames;
private readonly ReadOnlyMemory<string> 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<char> trainer, ReadOnlySpan<char
return -1;
return (int)LanguageID.Korean;
}
for (int i = 2; i < TrainerNames.Length; i++)
// Skip languages that are not-transferable to International games.
for (int i = 2; i < (int)LanguageID.Korean; i++)
{
if (i == (int)LanguageID.UNUSED_6)
continue;

View File

@@ -263,8 +263,8 @@ private void ClickSet(object sender, EventArgs e)
return;
}
Span<byte> data = stackalloc byte[pk.SIZE_STORED];
pk.WriteDecryptedDataStored(data);
Span<byte> data = stackalloc byte[pk.SIZE_PARTY];
pk.WriteDecryptedDataParty(data);
File.WriteAllBytes(path, data);
var info = new SlotInfoFileSingle(path);

View File

@@ -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<byte> b12 = stackalloc byte[g12.Length];