encdb->pa8: source shiny roll count from save file

This commit is contained in:
Kurt
2026-07-28 21:54:00 -05:00
parent b916b06c03
commit 757a297f14
9 changed files with 69 additions and 18 deletions

View File

@@ -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();

View File

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

View File

@@ -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)
{

View File

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

View File

@@ -9,5 +9,5 @@ public interface IGenerateSeed64
/// Generates the <see cref="PKM"/> from the seed.
/// </summary>
/// <remarks>Be sure the <see cref="pk"/> is un-converted and matches its original type.</remarks>
void GenerateSeed64(PKM pk, ulong seed);
void GenerateSeed64(PKM pk, ITrainerInfo tr, ulong seed);
}

View File

@@ -284,7 +284,7 @@ private static bool TryGetMatch<T>(in SearchContext<T> ctx, uint seed, byte natu
}
/// <summary>
/// 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.
/// </summary>
private const Nature LeadSyncAllowed = Nature.Random;

View File

@@ -137,3 +137,15 @@ private static bool IsMatchVersion(ITrainerInfo tr, PKM pk)
return false;
}
}
/// <summary>
/// Save File specific interface for trainer objects to expose information about how many shiny rolls a species may be generated with.
/// </summary>
/// <remarks>
/// Currently only useful for <see cref="GameVersion.PLA"/> 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.
/// </remarks>
public interface ITrainerInfo8a
{
byte GetShinyRolls(ushort species);
}

View File

@@ -6,7 +6,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 8 <see cref="SaveFile"/> object for <see cref="GameVersion.PLA"/> games.
/// </summary>
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<byte> 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<byte, uint>(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;

View File

@@ -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<byte> data) => new()
public void Write(Span<byte> data)
{
// Index is not saved.
// Favorite is saved separately.
WriteUInt16LittleEndian(data, (ushort)Index);
WriteUInt16LittleEndian(data[2..], (ushort)Count);
}