2022-01-03 05:35:59 +00:00
using System ;
namespace PKHeX.Core
2021-02-14 02:18:14 +00:00
{
2021-03-14 18:28:46 +00:00
/// <summary>
/// Contains logic for the Generation 8 (SW/SH) overworld spawns that walk around the overworld.
/// </summary>
/// <remarks>
/// Entities spawned into the overworld that can be encountered are assigned a 32bit seed, which can be immediately derived from the <see cref="PKM.EncryptionConstant"/>.
/// </remarks>
2021-02-14 02:18:14 +00:00
public static class Overworld8RNG
{
2021-02-14 20:27:14 +00:00
public static void ApplyDetails ( PKM pk , EncounterCriteria criteria , Shiny shiny = Shiny . FixedValue , int flawless = - 1 )
{
if ( shiny = = Shiny . FixedValue )
2021-10-18 00:40:38 +00:00
shiny = criteria . Shiny is Shiny . Random or Shiny . Never ? Shiny . Never : Shiny . Always ;
2021-02-14 20:27:14 +00:00
if ( flawless = = - 1 )
flawless = 0 ;
int ctr = 0 ;
2021-02-15 05:45:39 +00:00
const int maxAttempts = 50_000 ;
2021-08-05 02:58:25 +00:00
var rnd = Util . Rand ;
2021-02-14 20:27:14 +00:00
do
{
2021-08-05 02:58:25 +00:00
var seed = Util . Rand32 ( rnd ) ;
2021-02-14 20:27:14 +00:00
if ( TryApplyFromSeed ( pk , criteria , shiny , flawless , seed ) )
return ;
} while ( + + ctr ! = maxAttempts ) ;
2021-08-05 02:58:25 +00:00
TryApplyFromSeed ( pk , EncounterCriteria . Unrestricted , shiny , flawless , Util . Rand32 ( rnd ) ) ;
2021-02-14 20:27:14 +00:00
}
private static bool TryApplyFromSeed ( PKM pk , EncounterCriteria criteria , Shiny shiny , int flawless , uint seed )
{
var xoro = new Xoroshiro128Plus ( seed ) ;
// Encryption Constant
pk . EncryptionConstant = ( uint ) xoro . NextInt ( uint . MaxValue ) ;
// PID
var pid = ( uint ) xoro . NextInt ( uint . MaxValue ) ;
if ( shiny = = Shiny . Never )
{
if ( GetIsShiny ( pk . TID , pk . SID , pid ) )
pid ^ = 0x1000 _0000 ;
}
2021-02-15 05:45:39 +00:00
else if ( shiny ! = Shiny . Random )
2021-02-14 20:27:14 +00:00
{
if ( ! GetIsShiny ( pk . TID , pk . SID , pid ) )
pid = GetShinyPID ( pk . TID , pk . SID , pid , 0 ) ;
2021-02-15 05:45:39 +00:00
if ( shiny = = Shiny . AlwaysSquare & & pk . ShinyXor ! = 0 )
return false ;
2021-02-15 05:52:34 +00:00
if ( shiny = = Shiny . AlwaysStar & & pk . ShinyXor = = 0 )
2021-02-15 05:45:39 +00:00
return false ;
2021-02-14 20:27:14 +00:00
}
pk . PID = pid ;
// IVs
2022-01-03 05:35:59 +00:00
Span < int > ivs = stackalloc [ ] { UNSET , UNSET , UNSET , UNSET , UNSET , UNSET } ;
2021-02-14 20:27:14 +00:00
const int MAX = 31 ;
for ( int i = 0 ; i < flawless ; i + + )
{
int index ;
do { index = ( int ) xoro . NextInt ( 6 ) ; }
while ( ivs [ index ] ! = UNSET ) ;
ivs [ index ] = MAX ;
}
for ( int i = 0 ; i < ivs . Length ; i + + )
{
if ( ivs [ i ] = = UNSET )
ivs [ i ] = ( int ) xoro . 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 ] ;
// Remainder
var scale = ( IScaledSize ) pk ;
scale . HeightScalar = ( int ) xoro . NextInt ( 0x81 ) + ( int ) xoro . NextInt ( 0x80 ) ;
scale . WeightScalar = ( int ) xoro . NextInt ( 0x81 ) + ( int ) xoro . NextInt ( 0x80 ) ;
return true ;
}
2021-02-14 18:20:35 +00:00
public static bool ValidateOverworldEncounter ( PKM pk , Shiny shiny = Shiny . FixedValue , int flawless = - 1 )
2021-02-14 02:18:14 +00:00
{
var seed = GetOriginalSeed ( pk ) ;
return ValidateOverworldEncounter ( pk , seed , shiny , flawless ) ;
}
2021-02-14 18:20:35 +00:00
public static bool ValidateOverworldEncounter ( PKM pk , uint seed , Shiny shiny = Shiny . FixedValue , int flawless = - 1 )
2021-02-14 02:18:14 +00:00
{
// is the seed Xoroshiro determined, or just truncated state?
if ( seed = = uint . MaxValue )
return false ;
var xoro = new Xoroshiro128Plus ( seed ) ;
2021-02-14 20:27:14 +00:00
var ec = ( uint ) xoro . NextInt ( uint . MaxValue ) ;
2021-02-14 02:18:14 +00:00
if ( ec ! = pk . EncryptionConstant )
return false ;
var pid = ( uint ) xoro . NextInt ( uint . MaxValue ) ;
if ( ! IsPIDValid ( pk , pid , shiny ) )
return false ;
2021-02-14 18:20:35 +00:00
var actualCount = flawless = = - 1 ? GetIsMatchEnd ( pk , xoro ) : GetIsMatchEnd ( pk , xoro , flawless , flawless ) ;
2021-02-14 02:18:14 +00:00
return actualCount ! = NoMatchIVs ;
}
private static bool IsPIDValid ( PKM pk , uint pid , Shiny shiny )
{
if ( shiny = = Shiny . Random )
return pid = = pk . PID ;
if ( pid = = pk . PID )
return true ;
// Check forced shiny
if ( pk . IsShiny )
{
if ( GetIsShiny ( pk . TID , pk . SID , pid ) )
return false ;
pid = GetShinyPID ( pk . TID , pk . SID , pid , 0 ) ;
return pid = = pk . PID ;
}
// Check forced non-shiny
if ( ! GetIsShiny ( pk . TID , pk . SID , pid ) )
return false ;
pid ^ = 0x1000 _0000 ;
return pid = = pk . PID ;
}
private const int NoMatchIVs = - 1 ;
2021-02-14 06:45:07 +00:00
private const int UNSET = - 1 ;
2021-02-14 02:18:14 +00:00
private static int GetIsMatchEnd ( PKM pk , Xoroshiro128Plus xoro , int start = 0 , int end = 3 )
{
2021-02-14 18:20:35 +00:00
bool skip1 = start = = 0 & & end = = 3 ;
2021-02-14 02:18:14 +00:00
for ( int iv_count = start ; iv_count < = end ; iv_count + + )
{
2021-02-14 18:20:35 +00:00
if ( skip1 & & iv_count = = 1 )
continue ;
2021-02-14 02:18:14 +00:00
var copy = xoro ;
2022-01-03 05:35:59 +00:00
Span < int > ivs = stackalloc [ ] { UNSET , UNSET , UNSET , UNSET , UNSET , UNSET } ;
2021-02-14 02:18:14 +00:00
const int MAX = 31 ;
for ( int i = 0 ; i < iv_count ; i + + )
{
int index ;
do { index = ( int ) copy . NextInt ( 6 ) ; } while ( ivs [ index ] ! = UNSET ) ;
ivs [ index ] = MAX ;
}
2022-01-03 05:35:59 +00:00
for ( var i = 0 ; i < ivs . Length ; i + + )
{
if ( ivs [ i ] = = UNSET )
ivs [ i ] = ( int ) copy . NextInt ( 31 + 1 ) ;
}
if ( ivs [ 0 ] ! = pk . IV_HP ) continue ;
if ( ivs [ 1 ] ! = pk . IV_ATK ) continue ;
if ( ivs [ 2 ] ! = pk . IV_DEF ) continue ;
if ( ivs [ 3 ] ! = pk . IV_SPA ) continue ;
if ( ivs [ 4 ] ! = pk . IV_SPD ) continue ;
if ( ivs [ 5 ] ! = pk . IV_SPE ) continue ;
2021-02-14 06:45:07 +00:00
2021-02-14 02:18:14 +00:00
if ( pk is not IScaledSize s )
continue ;
var height = ( int ) copy . NextInt ( 0x81 ) + ( int ) copy . NextInt ( 0x80 ) ;
if ( s . HeightScalar ! = height )
continue ;
var weight = ( int ) copy . NextInt ( 0x81 ) + ( int ) copy . NextInt ( 0x80 ) ;
if ( s . WeightScalar ! = weight )
continue ;
return iv_count ;
}
return NoMatchIVs ;
}
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 ;
}
/// <summary>
/// Obtains the original seed for the Generation 8 overworld wild encounter.
/// </summary>
/// <param name="pk">Entity to check for</param>
/// <returns>Seed</returns>
2021-02-15 00:00:43 +00:00
public static uint GetOriginalSeed ( PKM pk )
2021-02-14 02:18:14 +00:00
{
var seed = pk . EncryptionConstant - unchecked ( ( uint ) Xoroshiro128Plus . XOROSHIRO_CONST ) ;
if ( seed = = 0xD5B9C463 ) // Collision seed with the 0xFFFFFFFF re-roll.
{
var xoro = new Xoroshiro128Plus ( seed ) ;
/* ec */ xoro . NextInt ( uint . MaxValue ) ;
var pid = xoro . NextInt ( uint . MaxValue ) ;
if ( pid ! = pk . PID )
return 0xDD6295A4 ;
}
return seed ;
}
}
}