From 757a297f1463e7a0f4635a7bbf98db84b4e933ed Mon Sep 17 00:00:00 2001 From: Kurt Date: Tue, 28 Jul 2026 21:54:00 -0500 Subject: [PATCH] encdb->pa8: source shiny roll count from save file --- .../Templates/Gen8/EncounterStatic8N.cs | 2 +- .../Templates/Gen8/EncounterStatic8Nest.cs | 2 +- .../Templates/Gen8a/EncounterSlot8a.cs | 32 +++++++++++++------ .../Templates/Gen8a/EncounterStatic8a.cs | 2 +- .../Interfaces/RNG/IGenerateSeed64.cs | 2 +- .../Legality/RNG/ClassicEra/Gen4/MethodK.cs | 2 +- PKHeX.Core/Saves/Abstractions/ITrainerInfo.cs | 12 +++++++ PKHeX.Core/Saves/SAV8LA.cs | 29 ++++++++++++++++- .../Inventory/Item/InventoryItem8a.cs | 4 +-- 9 files changed, 69 insertions(+), 18 deletions(-) diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8N.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8N.cs index 2fcd5d8bc..62e6a73ce 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8N.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8N.cs @@ -160,7 +160,7 @@ private static byte GetInitialDynamaxLevel(Xoroshiro128Plus rand, int rank) return (byte)(baseValue + boost); } - public override void GenerateSeed64(PKM pk, ulong seed) + public override void GenerateSeed64(PKM pk, ITrainerInfo tr, ulong seed) { var (_, noShiny) = IsPossibleSeed(pk, seed, false); var param = GetParam(); diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8Nest.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8Nest.cs index f84692db8..85c64a958 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8Nest.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8Nest.cs @@ -253,7 +253,7 @@ public bool Verify(PKM pk, ulong seed, bool forceNoShiny = false) return RaidRNG.Verify(pk, seed, iv, param, forceNoShiny: forceNoShiny); } - public virtual void GenerateSeed64(PKM pk, ulong seed) + public virtual void GenerateSeed64(PKM pk, ITrainerInfo tr, ulong seed) { var criteria = EncounterCriteria.Unrestricted; var pk8 = (PK8)pk; diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterSlot8a.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterSlot8a.cs index 434a8e1ba..1aa09af52 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterSlot8a.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterSlot8a.cs @@ -56,7 +56,7 @@ public PA8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) OriginalTrainerFriendship = pi.BaseFriendship, Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation), }; - SetPINGA(pk, criteria, pi); + SetPINGA(pk, tr, criteria, pi); pk.Scale = pk.HeightScalar; pk.ResetHeight(); pk.ResetWeight(); @@ -65,9 +65,10 @@ public PA8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) return pk; } - private void SetPINGA(PA8 pk, in EncounterCriteria criteria, PersonalInfo8LA pi) + private void SetPINGA(PA8 pk, ITrainerInfo tr, in EncounterCriteria criteria, PersonalInfo8LA pi) { - var para = GetParams(pi); + var rollCount = GetRollCount(Type, tr); + var para = GetParams(pi, rollCount); bool checkLevel = criteria.IsSpecifiedLevelRange() && this.IsLevelWithinRange(criteria); while (true) { @@ -84,22 +85,23 @@ private void SetPINGA(PA8 pk, in EncounterCriteria criteria, PersonalInfo8LA pi) } } - public void GenerateSeed64(PKM pk, ulong seed) + public void GenerateSeed64(PKM pk, ITrainerInfo tr, ulong seed) { if (pk is not PA8 pa8) throw new ArgumentException($"{nameof(pk)} must be a {nameof(PA8)} instance.", nameof(pk)); var criteria = EncounterCriteria.Unrestricted; var pi = PersonalTable.LA.GetFormEntry(Species, Form); - var para = GetParams(pi); + var rollCount = GetRollCount(Type, tr); + var para = GetParams(pi, rollCount); _ = Overworld8aRNG.ApplyDetails(pa8, criteria, para, HasAlphaMove); } - private OverworldParam8a GetParams(PersonalInfo8LA pi) => new() + private OverworldParam8a GetParams(PersonalInfo8LA pi, byte rollCount) => new() { Shiny = Shiny, IsAlpha = IsAlpha, FlawlessIVs = FlawlessIVCount, - RollCount = GetRollCount(Type), + RollCount = rollCount, GenderRatio = Gender switch { Gender.Male => PersonalInfo.RatioMagicMale, @@ -111,12 +113,21 @@ private OverworldParam8a GetParams(PersonalInfo8LA pi) => new() // hardcoded 7 to assume max dex progress + shiny charm. private const int MaxRollCount = 7; - private static byte GetRollCount(SlotType8a type) => (byte)(MaxRollCount + type switch + private byte GetRollCount(SlotType8a type, ITrainerInfo tr) => (byte)(GetRollCountBase(tr, Species) + GetRollCountBoost(type)); + + private static byte GetRollCountBoost(SlotType8a type) => type switch { SlotType8a.MassOutbreakMassive => 12, SlotType8a.MassOutbreakRegular => 25, _ => 0, - }); + }; + + private int GetRollCountBase(ITrainerInfo tr, ushort species) + { + if (tr is not ITrainerInfo8a la) + return MaxRollCount; + return la.GetShinyRolls(species); + } private void SetEncounterMoves(PA8 pk, byte level) { @@ -246,7 +257,8 @@ public SeedCorrelationResult TryGetSeed(PKM pk, out ulong seed) { // Check if it matches any single-roll seed. var pi = PersonalTable.LA[Species, Form]; - var param = GetParams(pi) with { RollCount = 1 }; + var rollCount = (byte)(1 + GetRollCountBoost(Type)); + var param = GetParams(pi, rollCount); var solver = new XoroMachineSkip(pk.EncryptionConstant, pk.PID); foreach (var s in solver) { diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterStatic8a.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterStatic8a.cs index a298c94d2..7c6594662 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterStatic8a.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8a/EncounterStatic8a.cs @@ -118,7 +118,7 @@ private void Finalize(PA8 pk, ulong slotSeed) SetEncounterMoves(pk, pk.MetLevel); } - public void GenerateSeed64(PKM pk, ulong seed) + public void GenerateSeed64(PKM pk, ITrainerInfo tr, ulong seed) { var pa8 = (PA8)pk; var criteria = EncounterCriteria.Unrestricted; diff --git a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/IGenerateSeed64.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/IGenerateSeed64.cs index 2ccf9b6e6..505af6b18 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/IGenerateSeed64.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/RNG/IGenerateSeed64.cs @@ -9,5 +9,5 @@ public interface IGenerateSeed64 /// Generates the from the seed. /// /// Be sure the is un-converted and matches its original type. - void GenerateSeed64(PKM pk, ulong seed); + void GenerateSeed64(PKM pk, ITrainerInfo tr, ulong seed); } diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs index 385ec176e..48ad82243 100644 --- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs +++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/MethodK.cs @@ -284,7 +284,7 @@ private static bool TryGetMatch(in SearchContext ctx, uint seed, byte natu } /// - /// Represents the magic number that that allows any lead to be used, enabling the generation target to require or not require a synchronize lead. + /// Represents the magic number that allows any lead to be used, enabling the generation target to require or not require a synchronize lead. /// private const Nature LeadSyncAllowed = Nature.Random; diff --git a/PKHeX.Core/Saves/Abstractions/ITrainerInfo.cs b/PKHeX.Core/Saves/Abstractions/ITrainerInfo.cs index 53747dcdb..1bbfa7f65 100644 --- a/PKHeX.Core/Saves/Abstractions/ITrainerInfo.cs +++ b/PKHeX.Core/Saves/Abstractions/ITrainerInfo.cs @@ -137,3 +137,15 @@ private static bool IsMatchVersion(ITrainerInfo tr, PKM pk) return false; } } + +/// +/// Save File specific interface for trainer objects to expose information about how many shiny rolls a species may be generated with. +/// +/// +/// Currently only useful for which ties Pokédex progress to shiny rolls. +/// By implementing this interface, a generated Pokémon can be generated more plausibly, and the save file can quickly inform. +/// +public interface ITrainerInfo8a +{ + byte GetShinyRolls(ushort species); +} diff --git a/PKHeX.Core/Saves/SAV8LA.cs b/PKHeX.Core/Saves/SAV8LA.cs index 2a94528d3..74e364632 100644 --- a/PKHeX.Core/Saves/SAV8LA.cs +++ b/PKHeX.Core/Saves/SAV8LA.cs @@ -6,7 +6,7 @@ namespace PKHeX.Core; /// /// Generation 8 object for games. /// -public sealed class SAV8LA : SaveFile, ISaveBlock8LA, ISCBlockArray, ISaveFileRevision, IBoxDetailName, IBoxDetailWallpaper +public sealed class SAV8LA : SaveFile, ISaveBlock8LA, ISCBlockArray, ISaveFileRevision, IBoxDetailName, IBoxDetailWallpaper, ITrainerInfo8a { protected internal override string ShortSummary => $"{OT} ({Version}) - {LastSaved.DisplayValue}"; public override string Extension => string.Empty; @@ -136,6 +136,33 @@ protected override SAV8LA CloneInternal() protected override Span PartyBuffer => PartyInfo.Data; public override bool HasPokeDex => true; + + public byte GetShinyRolls(ushort species) + { + // Level 10: +1 + // Perfect: +2 + // Shiny Charm: +3 + + var dex = PokedexSave; + byte rolls = HasKeyItem(632) ? (byte)(1 + 3) : (byte)1; + if (!dex.IsComplete(species)) + return rolls; + if (!dex.IsPerfect(species)) + return (byte)(rolls + 1); + return (byte)(rolls + 3); + } + + private bool HasKeyItem(ushort item) + { + var span = Accessor.GetBlock(SaveBlockAccessor8LA.KItemKey).Data; + // Look for (u16 632, u16 1) after reinterpreting as u32, respecting endianness as Little Endian + var cast = System.Runtime.InteropServices.MemoryMarshal.Cast(span); + var seek = (uint)(item | (1 << 16)); // 0x00010278 + if (!BitConverter.IsLittleEndian) + seek = System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(seek); + return cast.Contains(seek); + } + private void Initialize() { Box = 0; diff --git a/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem8a.cs b/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem8a.cs index d3f642de8..445d54d1e 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem8a.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem8a.cs @@ -8,7 +8,7 @@ public sealed record InventoryItem8a : InventoryItem, IItemFavorite public const int SIZE = 0x10; public override string ToString() => $"{Index:000} x{Count}"; - public bool IsFavorite { get; set; } + public bool IsFavorite { get; set; } // favorites stored separately, this is just a fake property mirror public override void Clear() { @@ -23,7 +23,7 @@ public static InventoryItem8a Read(ReadOnlySpan data) => new() public void Write(Span data) { - // Index is not saved. + // Favorite is saved separately. WriteUInt16LittleEndian(data, (ushort)Index); WriteUInt16LittleEndian(data[2..], (ushort)Count); }