mirror of
https://github.com/kwsch/PKHeX
synced 2024-12-23 02:43:13 +00:00
ff2e890e69
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.
335 lines
10 KiB
C#
335 lines
10 KiB
C#
using System;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace PKHeX.Core;
|
|
|
|
/// <summary>
|
|
/// Contains logic for the Generation 8 (Legends: Arceus) overworld spawns that walk around the overworld.
|
|
/// </summary>
|
|
public static class Overworld8aRNG
|
|
{
|
|
private static uint GetShinyPID(int tid, int sid, uint pid, int type)
|
|
{
|
|
return (uint)(((tid ^ sid ^ (pid & 0xFFFF) ^ type) << 16) | (pid & 0xFFFF));
|
|
}
|
|
|
|
private static bool GetIsShiny(int tid, int sid, uint pid)
|
|
{
|
|
return GetShinyXor(pid, (uint)((sid << 16) | tid)) < 16;
|
|
}
|
|
|
|
private static uint GetShinyXor(uint pid, uint oid)
|
|
{
|
|
var xor = pid ^ oid;
|
|
return (xor ^ (xor >> 16)) & 0xFFFF;
|
|
}
|
|
|
|
private const int UNSET = -1;
|
|
|
|
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 fakeRand = new Xoroshiro128Plus(Util.Rand.Rand64());
|
|
|
|
Xoroshiro128Plus groupRand;
|
|
ulong groupSeed;
|
|
ulong slotSeed;
|
|
do
|
|
{
|
|
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)
|
|
{
|
|
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 (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();
|
|
|
|
// PID
|
|
var fakeTID = (uint)rand.NextInt();
|
|
uint pid = (uint)rand.NextInt();
|
|
bool isShiny = false;
|
|
if (para.Shiny == Shiny.Random) // let's decide if it's shiny or not!
|
|
{
|
|
for (int i = 1; i < para.RollCount; i++)
|
|
{
|
|
isShiny = GetShinyXor(pid, fakeTID) < 16;
|
|
if (isShiny)
|
|
break;
|
|
pid = (uint)rand.NextInt();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no need to calculate a fake trainer
|
|
isShiny = para.Shiny == Shiny.Always;
|
|
}
|
|
|
|
ForceShinyState(pk, isShiny, ref pid);
|
|
|
|
if (para.Shiny == Shiny.Never)
|
|
{
|
|
if (GetIsShiny(pk.TID, pk.SID, pid))
|
|
return false;
|
|
}
|
|
else if (para.Shiny != Shiny.Random)
|
|
{
|
|
if (!GetIsShiny(pk.TID, pk.SID, pid))
|
|
return false;
|
|
|
|
if (para.Shiny == Shiny.AlwaysSquare && pk.ShinyXor != 0)
|
|
return false;
|
|
if (para.Shiny == Shiny.AlwaysStar && pk.ShinyXor == 0)
|
|
return false;
|
|
}
|
|
pk.PID = pid;
|
|
|
|
Span<int> ivs = stackalloc[] { UNSET, UNSET, UNSET, UNSET, UNSET, UNSET };
|
|
const int MAX = 31;
|
|
for (int i = 0; i < para.FlawlessIVs; i++)
|
|
{
|
|
int index;
|
|
do { index = (int)rand.NextInt(6); }
|
|
while (ivs[index] != UNSET);
|
|
|
|
ivs[index] = MAX;
|
|
}
|
|
|
|
for (int i = 0; i < ivs.Length; i++)
|
|
{
|
|
if (ivs[i] == UNSET)
|
|
ivs[i] = (int)rand.NextInt(32);
|
|
}
|
|
|
|
if (!criteria.IsIVsCompatible(ivs, 8))
|
|
return false;
|
|
|
|
pk.IV_HP = ivs[0];
|
|
pk.IV_ATK = ivs[1];
|
|
pk.IV_DEF = ivs[2];
|
|
pk.IV_SPA = ivs[3];
|
|
pk.IV_SPD = ivs[4];
|
|
pk.IV_SPE = ivs[5];
|
|
|
|
pk.RefreshAbility((int)rand.NextInt(2));
|
|
|
|
int gender = para.GenderRatio switch
|
|
{
|
|
PersonalInfo.RatioMagicGenderless => 2,
|
|
PersonalInfo.RatioMagicFemale => 1,
|
|
PersonalInfo.RatioMagicMale => 0,
|
|
_ => (int)rand.NextInt(252) + 1 < para.GenderRatio ? 1 : 0,
|
|
};
|
|
if (gender != criteria.Gender && criteria.Gender != -1)
|
|
return false;
|
|
pk.Gender = gender;
|
|
|
|
int nature = (int)rand.NextInt(25);
|
|
pk.StatNature = pk.Nature = nature;
|
|
|
|
var (height, weight) = para.IsAlpha
|
|
? (byte.MaxValue, byte.MaxValue)
|
|
: ((byte)((int)rand.NextInt(0x81) + (int)rand.NextInt(0x80)),
|
|
(byte)((int)rand.NextInt(0x81) + (int)rand.NextInt(0x80)));
|
|
|
|
if (pk is IScaledSize s)
|
|
{
|
|
s.HeightScalar = height;
|
|
s.WeightScalar = weight;
|
|
if (pk is IScaledSizeValue a)
|
|
{
|
|
a.ResetHeight();
|
|
a.ResetWeight();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static bool Verify(PKM pk, ulong seed, in OverworldParam8a para)
|
|
{
|
|
var rand = new Xoroshiro128Plus(seed);
|
|
var ec = (uint)rand.NextInt();
|
|
if (ec != pk.EncryptionConstant)
|
|
return false;
|
|
|
|
var fakeTID = (uint)rand.NextInt();
|
|
uint pid = (uint)rand.NextInt();
|
|
bool isShiny = false;
|
|
if (para.Shiny == Shiny.Random) // let's decide if it's shiny or not!
|
|
{
|
|
for (int i = 1; i < para.RollCount; i++)
|
|
{
|
|
isShiny = GetShinyXor(pid, fakeTID) < 16;
|
|
if (isShiny)
|
|
break;
|
|
pid = (uint)rand.NextInt();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no need to calculate a fake trainer
|
|
isShiny = para.Shiny == Shiny.Always;
|
|
}
|
|
|
|
ForceShinyState(pk, isShiny, ref pid);
|
|
|
|
if (pk.PID != pid)
|
|
return false;
|
|
Span<int> ivs = stackalloc[] { UNSET, UNSET, UNSET, UNSET, UNSET, UNSET };
|
|
const int MAX = 31;
|
|
for (int i = 0; i < para.FlawlessIVs; i++)
|
|
{
|
|
int index;
|
|
do { index = (int)rand.NextInt(6); }
|
|
while (ivs[index] != UNSET);
|
|
|
|
ivs[index] = MAX;
|
|
}
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
if (ivs[i] == UNSET)
|
|
ivs[i] = (int)rand.NextInt(32);
|
|
}
|
|
|
|
if (pk.IV_HP != ivs[0])
|
|
return false;
|
|
if (pk.IV_ATK != ivs[1])
|
|
return false;
|
|
if (pk.IV_DEF != ivs[2])
|
|
return false;
|
|
if (pk.IV_SPA != ivs[3])
|
|
return false;
|
|
if (pk.IV_SPD != ivs[4])
|
|
return false;
|
|
if (pk.IV_SPE != ivs[5])
|
|
return false;
|
|
|
|
int abil = (int)rand.NextInt(2);
|
|
abil <<= 1; // 1/2/4
|
|
|
|
var current = pk.AbilityNumber;
|
|
if (abil == 4 && current != 4)
|
|
return false;
|
|
// else, for things that were made Hidden Ability, defer to Ability Checks (Ability Patch)
|
|
|
|
int gender = para.GenderRatio switch
|
|
{
|
|
PersonalInfo.RatioMagicGenderless => 2,
|
|
PersonalInfo.RatioMagicFemale => 1,
|
|
PersonalInfo.RatioMagicMale => 0,
|
|
_ => (int)rand.NextInt(252) + 1 < para.GenderRatio ? 1 : 0,
|
|
};
|
|
if (pk.Gender != gender)
|
|
return false;
|
|
|
|
var nature = (int)rand.NextInt(25);
|
|
if (pk.Nature != nature)
|
|
return false;
|
|
|
|
var (height, weight) = para.IsAlpha
|
|
? (byte.MaxValue, byte.MaxValue)
|
|
: ((byte)((int)rand.NextInt(0x81) + (int)rand.NextInt(0x80)),
|
|
(byte)((int)rand.NextInt(0x81) + (int)rand.NextInt(0x80)));
|
|
|
|
if (pk is IScaledSize s)
|
|
{
|
|
if (s.HeightScalar != height)
|
|
return false;
|
|
if (s.WeightScalar != weight)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void ForceShinyState(PKM pk, bool isShiny, ref uint pid)
|
|
{
|
|
if (isShiny)
|
|
{
|
|
if (!GetIsShiny(pk.TID, pk.SID, pid))
|
|
pid = GetShinyPID(pk.TID, pk.SID, pid, 0);
|
|
}
|
|
else
|
|
{
|
|
if (GetIsShiny(pk.TID, pk.SID, pid))
|
|
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);
|