Revise Overworld8a application of PIDIV

Instantiating from template now follows group seed -> spawn 1 correlation, including alpha move.
Differentiates static encounters that don't follow the ow8a correlation, scrambles EC to disassociate.
Adds rand64 to get initial seeds
Set correct level range to match slotSeed; not respecting the slot roll being valid, but whatever.
This commit is contained in:
Kurt 2022-04-22 21:11:11 -07:00
parent ad249dbb76
commit ff2e890e69
10 changed files with 245 additions and 118 deletions

View file

@ -1,6 +1,7 @@
using static PKHeX.Core.EncounterUtil;
using static PKHeX.Core.Shiny;
using static PKHeX.Core.GameVersion;
using static PKHeX.Core.EncounterStatic8aCorrelation;
namespace PKHeX.Core;
@ -15,13 +16,13 @@ internal static class Encounters8a
internal static readonly EncounterStatic8a[] StaticLA =
{
// Gifts
new(722,000,05,M,M) { Location = 006, Gift = true, Ball = (int)Ball.LAPoke }, // Rowlet
new(155,000,05,M,M) { Location = 006, Gift = true, Ball = (int)Ball.LAPoke }, // Cyndaquil
new(501,000,05,M,M) { Location = 006, Gift = true, Ball = (int)Ball.LAPoke }, // Oshawott
new(037,001,40,M,M) { Location = 088, Gift = true, Ball = (int)Ball.LAPoke }, // Vulpix-1
new(483,000,65,M,M) { Location = 109, FlawlessIVCount = 3, Gift = true, Ball = (int)Ball.LAOrigin }, // Dialga
new(484,000,65,M,M) { Location = 109, FlawlessIVCount = 3, Gift = true, Ball = (int)Ball.LAOrigin }, // Palkia
new(493,000,75,M,M) { Location = 109, FlawlessIVCount = 3, Gift = true, Ball = (int)Ball.LAPoke, Fateful = true }, // Arceus
new(722,000,05,M,M) { Location = 006, Gift = true, Method = Fixed, Ball = (int)Ball.LAPoke }, // Rowlet
new(155,000,05,M,M) { Location = 006, Gift = true, Method = Fixed, Ball = (int)Ball.LAPoke }, // Cyndaquil
new(501,000,05,M,M) { Location = 006, Gift = true, Method = Fixed, Ball = (int)Ball.LAPoke }, // Oshawott
new(037,001,40,M,M) { Location = 088, Gift = true, Method = Fixed, Ball = (int)Ball.LAPoke }, // Vulpix-1
new(483,000,65,M,M) { Location = 109, FlawlessIVCount = 3, Gift = true, Method = Fixed, Ball = (int)Ball.LAOrigin }, // Dialga
new(484,000,65,M,M) { Location = 109, FlawlessIVCount = 3, Gift = true, Method = Fixed, Ball = (int)Ball.LAOrigin }, // Palkia
new(493,000,75,M,M) { Location = 109, FlawlessIVCount = 3, Gift = true, Method = Fixed, Ball = (int)Ball.LAPoke, Fateful = true }, // Arceus
// Static Encounters - Scripted Table Slots
new(480,000,70,M,M) { Location = 111, FlawlessIVCount = 3 }, // Uxie
@ -50,61 +51,66 @@ internal static class Encounters8a
new(492,000,70,M,M) { Location = 026, FlawlessIVCount = 3, Fateful = true }, // Shaymin
// Unown Notes
new(201,000,25,U) { Location = 040 }, // Unown A
new(201,001,25,U) { Location = 056 }, // Unown B
new(201,002,25,U) { Location = 081 }, // Unown C
new(201,003,25,U) { Location = 008 }, // Unown D
new(201,004,25,U) { Location = 022 }, // Unown E
new(201,005,25,U) { Location = 010 }, // Unown F
new(201,006,25,U) { Location = 017 }, // Unown G
new(201,007,25,U) { Location = 006 }, // Unown H
new(201,008,25,U) { Location = 023 }, // Unown I
new(201,009,25,U) { Location = 072 }, // Unown J
new(201,010,25,U) { Location = 043 }, // Unown K
new(201,011,25,U) { Location = 086 }, // Unown L
new(201,012,25,U) { Location = 037 }, // Unown M
new(201,013,25,U) { Location = 009 }, // Unown N
new(201,014,25,U) { Location = 102 }, // Unown O
new(201,015,25,U) { Location = 075 }, // Unown P
new(201,016,25,U) { Location = 058 }, // Unown Q
new(201,017,25,U) { Location = 059 }, // Unown R
new(201,018,25,U) { Location = 025 }, // Unown S
new(201,019,25,U) { Location = 092 }, // Unown T
new(201,020,25,U) { Location = 011 }, // Unown U
new(201,021,25,U) { Location = 038 }, // Unown V
new(201,022,25,U) { Location = 006 }, // Unown W
new(201,023,25,U) { Location = 021 }, // Unown X
new(201,024,25,U) { Location = 097 }, // Unown Y
new(201,025,25,U) { Location = 051 }, // Unown Z
new(201,026,25,U) { Location = 142 }, // Unown ! at Snowfall Hot Spring
new(201,027,25,U) { Location = 006 }, // Unown ?
new(201,000,25,U) { Location = 040, Method = Fixed }, // Unown A
new(201,001,25,U) { Location = 056, Method = Fixed }, // Unown B
new(201,002,25,U) { Location = 081, Method = Fixed }, // Unown C
new(201,003,25,U) { Location = 008, Method = Fixed }, // Unown D
new(201,004,25,U) { Location = 022, Method = Fixed }, // Unown E
new(201,005,25,U) { Location = 010, Method = Fixed }, // Unown F
new(201,006,25,U) { Location = 017, Method = Fixed }, // Unown G
new(201,007,25,U) { Location = 006, Method = Fixed }, // Unown H
new(201,008,25,U) { Location = 023, Method = Fixed }, // Unown I
new(201,009,25,U) { Location = 072, Method = Fixed }, // Unown J
new(201,010,25,U) { Location = 043, Method = Fixed }, // Unown K
new(201,011,25,U) { Location = 086, Method = Fixed }, // Unown L
new(201,012,25,U) { Location = 037, Method = Fixed }, // Unown M
new(201,013,25,U) { Location = 009, Method = Fixed }, // Unown N
new(201,014,25,U) { Location = 102, Method = Fixed }, // Unown O
new(201,015,25,U) { Location = 075, Method = Fixed }, // Unown P
new(201,016,25,U) { Location = 058, Method = Fixed }, // Unown Q
new(201,017,25,U) { Location = 059, Method = Fixed }, // Unown R
new(201,018,25,U) { Location = 025, Method = Fixed }, // Unown S
new(201,019,25,U) { Location = 092, Method = Fixed }, // Unown T
new(201,020,25,U) { Location = 011, Method = Fixed }, // Unown U
new(201,021,25,U) { Location = 038, Method = Fixed }, // Unown V
new(201,022,25,U) { Location = 006, Method = Fixed }, // Unown W
new(201,023,25,U) { Location = 021, Method = Fixed }, // Unown X
new(201,024,25,U) { Location = 097, Method = Fixed }, // Unown Y
new(201,025,25,U) { Location = 051, Method = Fixed }, // Unown Z
new(201,026,25,U) { Location = 142, Method = Fixed }, // Unown ! at Snowfall Hot Spring
new(201,027,25,U) { Location = 006, Method = Fixed }, // Unown ?
// Future updates will handle crossovers better.
new(201,017,25,U) { Location = 009 }, // Unown R (Cobalt Coastlands)
new(201,026,25,U) { Location = 099 }, // Unown ! (Arenas Approach)
new(201,026,25,U) { Location = 141 }, // Unown ! (Icepeak Arena)
new(201,023,25,U) { Location = 007 }, // Unown X
new(201,024,25,U) { Location = 097 }, // Unown Y
new(201,006,25,U) { Location = 007 }, // Unown G
new(201,017,25,U) { Location = 009, Method = Fixed }, // Unown R (Cobalt Coastlands)
new(201,026,25,U) { Location = 099, Method = Fixed }, // Unown ! (Arenas Approach)
new(201,026,25,U) { Location = 141, Method = Fixed }, // Unown ! (Icepeak Arena)
new(201,023,25,U) { Location = 007, Method = Fixed }, // Unown X
new(201,024,25,U) { Location = 097, Method = Fixed }, // Unown Y
new(201,006,25,U) { Location = 007, Method = Fixed }, // Unown G
new(642,000,70,M,M) { Location = 059, FlawlessIVCount = 3 }, // Thundurus (Lunkers Lair)
new(642,000,70,M,M) { Location = 129, FlawlessIVCount = 3 }, // Thundurus (Sands Reach)
new(488,000,70,M,M) { Location = 010, FlawlessIVCount = 3 }, // Cresselia (Coronet Highlands)
new(491,000,70,M,M) { Location = 074, FlawlessIVCount = 3, Fateful = true }, // Darkrai (Lonely Spring)
// Static Encounters
new(046,000,50,M,M) { Location = 019 }, // paras01: Paras
new(390,000,12,M,M) { Location = 007 }, // hikozaru_01: Chimchar
new(434,000,20,M,M) { Location = 008 }, // skunpuu01: Stunky
new(441,000,34,M,M) { Location = 129 }, // perap01: Chatot
new(450,000,34,M,M) { Location = 036, Gender = 0 }, // kabaldon01: Hippowdon
new(459,000,50,M,M) { Location = 101, Gender = 1 }, // yukikaburi01: Snover
new(046,000,50,M,M) { Location = 019, Method = Fixed }, // paras01: Paras
new(390,000,12,M,M) { Location = 007, Method = Fixed }, // hikozaru_01: Chimchar
new(434,000,20,M,M) { Location = 008, Method = Fixed }, // skunpuu01: Stunky
new(441,000,34,M,M) { Location = 129, Method = Fixed }, // perap01: Chatot
new(450,000,34,M,M) { Location = 036, Method = Fixed, Gender = 0 }, // kabaldon01: Hippowdon
new(459,000,50,M,M) { Location = 101, Method = Fixed, Gender = 1 }, // yukikaburi01: Snover
new(483,000,65,M,M) { Location = 109, FlawlessIVCount = 3 }, // dialga01: Dialga
new(484,000,65,M,M) { Location = 109, FlawlessIVCount = 3 }, // palkia01: Palkia
new(486,000,70,M,M) { Location = 095, FlawlessIVCount = 3 }, // regigigas01: Regigigas
new(487,001,70,M,M) { Location = 067, FlawlessIVCount = 3 }, // giratina02: Giratina-1
new(483,000,65,M,M) { Location = 109, Method = Fixed, FlawlessIVCount = 3 }, // dialga01: Dialga
new(484,000,65,M,M) { Location = 109, Method = Fixed, FlawlessIVCount = 3 }, // palkia01: Palkia
new(486,000,70,M,M) { Location = 095, Method = Fixed, FlawlessIVCount = 3 }, // regigigas01: Regigigas
new(487,001,70,M,M) { Location = 067, Method = Fixed, FlawlessIVCount = 3 }, // giratina02: Giratina-1
new(362,000,64,A,A) { Location = 011, IsAlpha = true, Moves = new[] {442,059,556,242}, Mastery = new[] {true,true,true, true } }, // onigohri01: Glalie
new(402,000,12,A,A) { Location = 007, IsAlpha = true, Gender = 0, Moves = new[] {206,071,033,332}, Mastery = new[] {true,true,false,false} }, // mev002: Kricketune
new(416,000,60,A,A) { Location = 022, IsAlpha = true, Gender = 1, FlawlessIVCount = 3, Moves = new[] {188,403,408,405}, Mastery = new[] {true,true,true ,true } }, // beequen01: Vespiquen
new(571,001,58,M,M) { Location = 111, IsAlpha = true, Moves = new[] {555,421,841,417}, Mastery = new[] {true,true,true ,true } }, // zoroark01: Zoroark-1
new(706,001,58,M,M) { Location = 104, IsAlpha = true, Moves = new[] {231,406,842,056}, Mastery = new[] {true,true,true ,true } }, // numelgon01: Goodra-1
new(904,000,58,M,M) { Location = 105, IsAlpha = true, Moves = new[] {301,398,401,038}, Mastery = new[] {true,true,true ,false} }, // harysen01: Overqwil
new(362,000,64,A,A) { Location = 011, Method = Fixed, IsAlpha = true, Moves = new[] {442,059,556,242}, Mastery = new[] {true,true,true, true } }, // onigohri01: Glalie
new(402,000,12,A,A) { Location = 007, Method = Fixed, IsAlpha = true, Gender = 0, Moves = new[] {206,071,033,332}, Mastery = new[] {true,true,false,false} }, // mev002: Kricketune
new(416,000,60,A,A) { Location = 022, Method = Fixed, IsAlpha = true, Gender = 1, FlawlessIVCount = 3, Moves = new[] {188,403,408,405}, Mastery = new[] {true,true,true ,true } }, // beequen01: Vespiquen
new(571,001,58,M,M) { Location = 111, Method = Fixed, IsAlpha = true, Moves = new[] {555,421,841,417}, Mastery = new[] {true,true,true ,true } }, // zoroark01: Zoroark-1
new(706,001,58,M,M) { Location = 104, Method = Fixed, IsAlpha = true, Moves = new[] {231,406,842,056}, Mastery = new[] {true,true,true ,true } }, // numelgon01: Goodra-1
new(904,000,58,M,M) { Location = 105, Method = Fixed, IsAlpha = true, Moves = new[] {301,398,401,038}, Mastery = new[] {true,true,true ,false} }, // harysen01: Overqwil
};
}

View file

@ -23,25 +23,25 @@ public sealed record EncounterSlot8a : EncounterSlot, IAlpha
Gender = gender;
}
public bool HasAlphaMove => IsAlpha && Type is not SlotType.Landmark;
protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk)
{
base.ApplyDetails(sav, criteria, pk);
if (Gender != Gender.Random)
pk.Gender = (int)Gender;
var para = GetParams(criteria);
Overworld8aRNG.ApplyDetails(pk, criteria, para);
var para = GetParams();
var (_, slotSeed) = Overworld8aRNG.ApplyDetails(pk, criteria, para, HasAlphaMove);
if (LevelMin != LevelMax)
pk.CurrentLevel = pk.Met_Level = Overworld8aRNG.GetRandomLevel(slotSeed, LevelMin, LevelMax);
if (IsAlpha)
{
if (pk is IAlpha a)
a.IsAlpha = true;
if (Type is not SlotType.Landmark && pk is PA8 pa)
{
var extra = pa.AlphaMove = pa.GetRandomAlphaMove();
pa.SetMasteryFlagMove(extra);
pk.PushMove(extra);
}
if (pk is PA8 { AlphaMove: not 0 } pa)
pk.PushMove(pa.AlphaMove);
}
if (pk is PA8 pa8)
@ -115,7 +115,7 @@ public sealed record EncounterSlot8a : EncounterSlot, IAlpha
return EncounterMatchRating.Match;
}
private OverworldParam8a GetParams(EncounterCriteria criteria)
private OverworldParam8a GetParams()
{
var pt = PersonalTable.LA;
var entry = pt.GetFormEntry(Species, Form);
@ -125,8 +125,18 @@ public sealed record EncounterSlot8a : EncounterSlot, IAlpha
IsAlpha = IsAlpha,
FlawlessIVs = FlawlessIVCount,
Shiny = Shiny,
RollCount = criteria.Shiny.IsShiny() ? Type is SlotType.Swarm ? (byte)32 : (byte)7 : (byte)1,
RollCount = GetRollCount(Type),
GenderRatio = gender,
};
}
// hardcoded 7 to assume max dex progress + shiny charm.
private const int MaxRollCount = 7;
private static byte GetRollCount(SlotType type) => (byte)(MaxRollCount + type switch
{
SlotType.OverworldMMO => 10,
SlotType.OverworldMass => 25,
_ => 0,
});
}

View file

@ -14,6 +14,7 @@ public sealed record EncounterStatic8a(GameVersion Version) : EncounterStatic(Ve
public byte HeightScalar { get; }
public byte WeightScalar { get; }
public bool IsAlpha { get; set; }
public EncounterStatic8aCorrelation Method { get; init; }
public bool HasFixedHeight => HeightScalar != NoScalar;
public bool HasFixedWeight => WeightScalar != NoScalar;
@ -50,21 +51,25 @@ public sealed record EncounterStatic8a(GameVersion Version) : EncounterStatic(Ve
if (pk is PA8 pa)
{
if (pa.AlphaMove != 0)
pk.PushMove(pa.AlphaMove);
pa.SetMasteryFlags();
pa.HeightScalarCopy = pa.HeightScalar;
if (IsAlpha)
{
var extra = pa.AlphaMove = pa.GetRandomAlphaMove();
pa.SetMasteryFlagMove(extra);
pk.PushMove(extra);
}
}
}
protected override void SetPINGA(PKM pk, EncounterCriteria criteria)
{
var para = GetParams();
Overworld8aRNG.ApplyDetails(pk, criteria, para);
var (_, slotSeed) = Overworld8aRNG.ApplyDetails(pk, criteria, para, IsAlpha);
// We don't override LevelMin, so just handle the two species cases.
if (Species == (int)Core.Species.Zorua)
pk.CurrentLevel = pk.Met_Level = Overworld8aRNG.GetRandomLevel(slotSeed, 26, 28);
else if (Species == (int)Core.Species.Phione)
pk.CurrentLevel = pk.Met_Level = Overworld8aRNG.GetRandomLevel(slotSeed, 33, 36);
if (Method == EncounterStatic8aCorrelation.Fixed)
pk.EncryptionConstant = Util.Rand32();
}
protected override void ApplyDetailsBall(PKM pk) => pk.Ball = Gift ? Ball : (int)Core.Ball.LAPoke;
@ -159,3 +164,9 @@ public sealed record EncounterStatic8a(GameVersion Version) : EncounterStatic(Ve
};
}
}
public enum EncounterStatic8aCorrelation : byte
{
WildGroup,
Fixed,
}

View file

@ -8,21 +8,6 @@ namespace PKHeX.Core;
/// </summary>
public static class Overworld8aRNG
{
public static uint AdaptPID(PKM pk, Shiny shiny, uint pid)
{
if (shiny == Shiny.Never)
{
if (GetIsShiny(pk.TID, pk.SID, pid))
pid ^= 0x1000_0000;
}
else if (shiny != Shiny.Random)
{
if (!GetIsShiny(pk.TID, pk.SID, pid))
pid = GetShinyPID(pk.TID, pk.SID, pid, 0);
}
return pid;
}
private static uint GetShinyPID(int tid, int sid, uint pid, int type)
{
return (uint)(((tid ^ sid ^ (pid & 0xFFFF) ^ type) << 16) | (pid & 0xFFFF));
@ -41,30 +26,84 @@ public static class Overworld8aRNG
private const int UNSET = -1;
public static void ApplyDetails(PKM pk, EncounterCriteria criteria, in OverworldParam8a para)
public static (ulong GroupSeed, ulong SlotSeed) ApplyDetails(PKM pk, EncounterCriteria criteria, in OverworldParam8a para, bool giveAlphaMove)
{
int ctr = 0;
const int maxAttempts = 50_000;
var rnd = Util.Rand;
var fakeRand = new Xoroshiro128Plus(Util.Rand.Rand64());
Xoroshiro128Plus groupRand;
ulong groupSeed;
ulong slotSeed;
do
{
ulong s0 = Util.Rand32(rnd) | (ulong)Util.Rand32(rnd) << 32;
ulong s1 = Util.Rand32(rnd) | (ulong)Util.Rand32(rnd) << 32;
var rand = new Xoroshiro128Plus(s0, s1);
if (TryApplyFromSeed(pk, criteria, para, rand))
return;
groupSeed = fakeRand.Next();
groupRand = new Xoroshiro128Plus(groupSeed);
slotSeed = groupRand.Next();
var slotRand = new Xoroshiro128Plus(slotSeed);
_ = slotRand.Next();
var entitySeed = slotRand.Next();
var result = TryApplyFromSeed(pk, criteria, para, entitySeed);
if (result)
break;
} while (++ctr != maxAttempts);
// Failed, fall back to Unrestricted and just put whatever.
if (ctr >= maxAttempts)
{
ulong s0 = Util.Rand32(rnd) | (ulong)Util.Rand32(rnd) << 32;
ulong s1 = Util.Rand32(rnd) | (ulong)Util.Rand32(rnd) << 32;
var rand = new Xoroshiro128Plus(s0, s1);
TryApplyFromSeed(pk, EncounterCriteria.Unrestricted, para, rand);
groupSeed = fakeRand.Next();
groupRand = new Xoroshiro128Plus(groupSeed);
var slotRand = new Xoroshiro128Plus(slotSeed);
_ = slotRand.Next();
var entitySeed = slotRand.Next();
TryApplyFromSeed(pk, EncounterCriteria.Unrestricted, para, entitySeed);
}
if (giveAlphaMove)
ApplyRandomAlphaMove(pk, groupRand.Next());
return (groupSeed, slotSeed);
}
public static bool TryApplyFromSeed(PKM pk, EncounterCriteria criteria, in OverworldParam8a para, Xoroshiro128Plus rand)
public static (ulong EntitySeed, ulong SlotRand) ApplyDetails(PKM pk, in OverworldParam8a para, bool giveAlphaMove, ref Xoroshiro128Plus groupRand)
{
var slotSeed = groupRand.Next();
var alphaSeed = groupRand.Next();
var slotRand = new Xoroshiro128Plus(slotSeed);
var slotRoll = slotRand.Next();
var entitySeed = slotRand.Next();
TryApplyFromSeed(pk, EncounterCriteria.Unrestricted, para, entitySeed);
if (giveAlphaMove)
ApplyRandomAlphaMove(pk, alphaSeed);
return (entitySeed, slotRoll);
}
private static void ApplyRandomAlphaMove(PKM pk, ulong seed)
{
var pi = (PersonalInfoLA)PersonalTable.LA.GetFormEntry(pk.Species, pk.Form);
var count = pi.GetMoveShopCount();
if (count == 0 || pk is not PA8 pa8)
return;
var index = GetRandomAlphaMoveIndex(seed, count);
var alphaIndex = pi.GetMoveShopIndex(index);
var alphaMove = Legal.MoveShop8_LA[alphaIndex];
pa8.SetMasteryFlagMove(pa8.AlphaMove = alphaMove);
}
private static int GetRandomAlphaMoveIndex(ulong alphaSeed, int count)
{
var alphaRand = new Xoroshiro128Plus(alphaSeed);
return (int)alphaRand.NextInt((uint)count);
}
public static bool TryApplyFromSeed(PKM pk, EncounterCriteria criteria, in OverworldParam8a para, ulong seed)
{
var rand = new Xoroshiro128Plus(seed);
// Encryption Constant
pk.EncryptionConstant = (uint)rand.NextInt();
@ -281,6 +320,16 @@ public static class Overworld8aRNG
pid ^= 0x1000_0000;
}
}
public static int GetRandomLevel(ulong slotSeed, byte LevelMin, byte LevelMax)
{
var delta = LevelMax - LevelMin;
var xoro = new Xoroshiro128Plus(slotSeed);
xoro.Next();
xoro.Next(); // slot, entitySeed
var amp = (int)xoro.NextInt((ulong)delta + 1);
return LevelMin + amp;
}
}
public readonly record struct OverworldParam8a(bool IsAlpha, byte GenderRatio, byte FlawlessIVs, byte RollCount, Shiny Shiny = Shiny.Random);

View file

@ -831,14 +831,6 @@ public sealed class PA8 : PKM, ISanityChecksum,
return (byte)Math.Min(255, unsigned);
}
public ushort GetRandomAlphaMove()
{
var index = MoveShopPermitFlags.IndexOf(true);
if (index == -1)
return 0;
return MoveShopPermitIndexes[index];
}
public void SetMasteryFlags()
{
for (int i = 0; i < 4; i++)

View file

@ -110,4 +110,32 @@ public sealed class PersonalInfoLA : PersonalInfo
public int DexIndexLocal3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x66)); set => WriteUInt16LittleEndian(Data.AsSpan(0x66), (ushort)value); }
public int DexIndexLocal4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x68)); set => WriteUInt16LittleEndian(Data.AsSpan(0x68), (ushort)value); }
public int DexIndexLocal5 { get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), (ushort)value); }
public int GetMoveShopCount()
{
// Return a count of true indexes from Tutors
var arr = SpecialTutors[0];
int count = 0;
foreach (var index in arr)
{
if (index)
count++;
}
return count;
}
public int GetMoveShopIndex(int randIndexFromCount)
{
// Return a count of true indexes from Tutors
var arr = SpecialTutors[0];
for (var i = 0; i < arr.Length; i++)
{
var index = arr[i];
if (!index)
continue;
if (randIndexFromCount-- == 0)
return i;
}
throw new ArgumentOutOfRangeException(nameof(randIndexFromCount));
}
}

View file

@ -47,7 +47,7 @@ public readonly ref struct AreaSpawnerSet8a
for (int i = 0; i < count; i++)
{
var spawner = this[i];
if (spawner.Meta.Spawner_01 == key)
if (spawner.Meta.SpawnerHash == key)
return i;
}
return -1;

View file

@ -13,13 +13,13 @@ public readonly ref struct SpawnerMeta8a
public SpawnerMeta8a(Span<byte> data) => Data = data;
public ulong QuantitySeed { get => ReadUInt64LittleEndian(Data); set => WriteUInt64LittleEndian(Data , value); }
public ulong CountSeed { get => ReadUInt64LittleEndian(Data); set => WriteUInt64LittleEndian(Data , value); }
/// <summary> Seed that regenerates seeds for the entries as a group, regenerating multiple or single entries. </summary>
public ulong GroupSeed { get => ReadUInt64LittleEndian(Data[0x08..]); set => WriteUInt64LittleEndian(Data[0x08..], value); }
// flatbuffer PlacementSpawner8a.Field_01 to match
public ulong Spawner_01 { get => ReadUInt64LittleEndian(Data[0x10..]); set => WriteUInt64LittleEndian(Data[0x10..], value); }
public ulong SpawnerHash { get => ReadUInt64LittleEndian(Data[0x10..]); set => WriteUInt64LittleEndian(Data[0x10..], value); }
public int Count { get => ReadInt32LittleEndian (Data[0x18..]); set => WriteInt32LittleEndian (Data[0x18..], value); }
public int Flags { get => ReadInt32LittleEndian (Data[0x1C..]); set => WriteInt32LittleEndian (Data[0x1C..], value); }
@ -58,15 +58,15 @@ public readonly ref struct SpawnerMeta8a
/// <param name="min">Minimum spawn count</param>
/// <param name="max">Maximum spawn count</param>
/// <returns>Count for the cycle.</returns>
/// <remarks>Does not advance the <see cref="QuantitySeed"/> if the input <see cref="min"/> and <see cref="max"/> are equivalent.</remarks>
/// <remarks>Does not advance the <see cref="CountSeed"/> if the input <see cref="min"/> and <see cref="max"/> are equivalent.</remarks>
public int GetNextQuantity(int min, int max)
{
if (min == max)
return min;
var delta = max - min;
var rand = new Xoroshiro128Plus(QuantitySeed);
var rand = new Xoroshiro128Plus(CountSeed);
var result = (int)rand.NextInt((uint)delta + 1);
QuantitySeed = rand.Next();
CountSeed = rand.Next();
return result;
}
}

View file

@ -18,7 +18,8 @@ namespace PKHeX.Core
});
public static uint Rand32() => Rand32(Rand);
public static uint Rand32(Random rnd) => (uint)rnd.Next(1 << 30) << 2 | (uint)rnd.Next(1 << 2);
public static uint Rand32(this Random rnd) => (uint)rnd.Next(1 << 30) << 2 | (uint)rnd.Next(1 << 2);
public static ulong Rand64(this Random rnd) => rnd.Rand32() | (ulong)rnd.Rand32() << 32;
/// <summary>
/// Shuffles the order of items within a collection of items.

View file

@ -11,7 +11,6 @@ public static class Wild8aRNGTests
PA8 test = new() { Species = (int)Species.Zorua, Form = 1 };
const ulong s0 = 0xDF440DA44EEC4FFB;
var rand = new Xoroshiro128Plus(s0);
var param = new OverworldParam8a
{
FlawlessIVs = 0, IsAlpha = false,
@ -19,7 +18,7 @@ public static class Wild8aRNGTests
GenderRatio = 0x7F,
};
var result = Overworld8aRNG.TryApplyFromSeed(test, EncounterCriteria.Unrestricted, param, rand);
var result = Overworld8aRNG.TryApplyFromSeed(test, EncounterCriteria.Unrestricted, param, s0);
result.Should().BeTrue();
test.IV_HP.Should().Be(10);
@ -34,4 +33,35 @@ public static class Wild8aRNGTests
var verify = Overworld8aRNG.Verify(test, s0, param);
verify.Should().BeTrue();
}
[Fact]
public static void TestMagby()
{
const ulong s0 = 0xE12DDECBDFC64AA1ul;
PA8 test = new() { Species = (int)Species.Magby };
var param = new OverworldParam8a
{
FlawlessIVs = 3,
IsAlpha = true,
Shiny = Shiny.Random,
RollCount = 17,
GenderRatio = 0x7F,
};
var xoro = new Xoroshiro128Plus(s0);
var result = Overworld8aRNG.ApplyDetails(test, param, true, ref xoro);
test.IV_HP.Should().Be(31);
test.IV_ATK.Should().Be(31);
test.IV_DEF.Should().Be(7);
test.IV_SPA.Should().Be(31);
test.IV_SPD.Should().Be(20);
test.IV_SPE.Should().Be(10);
test.AlphaMove.Should().Be((ushort)Move.Flamethrower);
var verify = Overworld8aRNG.Verify(test, result.EntitySeed, param);
verify.Should().BeTrue();
}
}