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);
}