2018-10-28 18:15:40 +00:00
using System.Collections.Generic ;
2017-05-19 00:36:43 +00:00
namespace PKHeX.Core
{
2017-11-02 04:12:44 +00:00
/// <summary>
/// Contains various Colosseum/XD 'wait for value' logic related to PKM generation.
/// </summary>
/// <remarks>
/// "Locks" are referring to the <see cref="IEncounterable"/> being "locked" to a certain value, e.g. requiring Nature to be neutral.
/// These locks cause the <see cref="PKM.PID"/> of the current <see cref="PKM"/> to be rerolled until the requisite lock is satisfied.
/// <see cref="PKM.Nature"/> locks require a certain <see cref="Nature"/>, which is derived from the <see cref="PKM.PID"/>.
/// <see cref="PKM.Gender"/> locks require a certain gender value, which is derived from the <see cref="PKM.PID"/> and <see cref="PersonalInfo.Gender"/> ratio.
/// Not sure if Abilities are locked for the encounter, assume not. When this code is eventually utilized, our understanding can be tested!
/// </remarks>
2017-05-19 00:36:43 +00:00
public static class LockFinder
{
2018-02-04 08:06:07 +00:00
public static bool FindLockSeed ( uint originSeed , IEnumerable < NPCLock > lockList , bool XD , out uint origin )
{
var locks = new Stack < NPCLock > ( lockList ) ;
2018-10-29 03:28:56 +00:00
var team = new Stack < SeedFrame > ( ) ;
2018-02-05 00:33:53 +00:00
var cache = new FrameCache ( RNG . XDRNG . Reverse ( originSeed , 2 ) , RNG . XDRNG . Prev ) ;
2018-10-29 03:28:56 +00:00
var result = FindLockSeed ( cache , 0 , locks , null , team , XD , out var originFrame ) ;
2018-02-05 00:33:53 +00:00
origin = cache . GetSeed ( originFrame ) ;
return result ;
2018-02-04 08:06:07 +00:00
}
2017-05-19 00:36:43 +00:00
// Recursively iterates to visit possible locks until all locks (or none) are satisfied.
2018-10-29 03:10:35 +00:00
private static bool FindLockSeed ( FrameCache cache , int ctr , Stack < NPCLock > Locks , NPCLock prior , Stack < SeedFrame > team , bool XD , out int originFrame )
2017-05-19 00:36:43 +00:00
{
if ( Locks . Count = = 0 )
2018-10-29 03:10:35 +00:00
return VerifyNPC ( cache , ctr , team , XD , out originFrame ) ;
2017-05-19 00:36:43 +00:00
var l = Locks . Pop ( ) ;
2018-10-21 02:03:04 +00:00
var frames = FindPossibleLockFrames ( cache , ctr , l , prior ) ;
foreach ( var poss in frames )
2017-05-19 00:36:43 +00:00
{
2018-10-29 03:10:35 +00:00
team . Push ( poss ) ; // possible match
if ( FindLockSeed ( cache , poss . FrameID , Locks , l , team , XD , out originFrame ) )
2017-05-19 00:36:43 +00:00
return true ; // all locks are satisfied
2018-10-29 03:10:35 +00:00
team . Pop ( ) ; // no match, remove
2017-05-19 00:36:43 +00:00
}
2018-02-05 00:33:53 +00:00
Locks . Push ( l ) ; // return the lock, lock is impossible
originFrame = 0 ;
2017-05-19 00:36:43 +00:00
return false ;
}
2018-02-05 00:33:53 +00:00
private static IEnumerable < SeedFrame > FindPossibleLockFrames ( FrameCache cache , int ctr , NPCLock l , NPCLock prior )
2017-05-19 00:36:43 +00:00
{
2018-10-28 18:15:40 +00:00
if ( prior ? . Shadow ! = false )
2018-02-05 00:33:53 +00:00
return GetSingleLockFrame ( cache , ctr , l ) ;
2018-02-04 08:06:07 +00:00
2018-02-05 00:33:53 +00:00
return GetComplexLockFrame ( cache , ctr , l , prior ) ;
}
2018-09-15 05:37:47 +00:00
2018-02-05 00:33:53 +00:00
private static IEnumerable < SeedFrame > GetSingleLockFrame ( FrameCache cache , int ctr , NPCLock l )
{
uint pid = cache [ ctr + 1 ] < < 16 | cache [ ctr ] ;
2018-10-21 02:03:04 +00:00
if ( l . MatchesLock ( pid ) )
2018-10-28 18:15:40 +00:00
yield return new SeedFrame { FrameID = ctr + ( l . Seen ? 5 : 7 ) , PID = pid } ;
2018-02-05 00:33:53 +00:00
}
2018-09-15 05:37:47 +00:00
2018-02-05 00:33:53 +00:00
private static IEnumerable < SeedFrame > GetComplexLockFrame ( FrameCache cache , int ctr , NPCLock l , NPCLock prior )
{
// Since the prior(next) lock is generated 7+2*n frames after, the worst case break is 7 frames after the PID.
// Continue reversing until a sequential generation case is found.
2018-05-12 15:41:23 +00:00
// Check
2018-02-05 00:33:53 +00:00
int start = ctr ;
2018-05-12 15:41:23 +00:00
while ( true )
2017-05-19 00:36:43 +00:00
{
2018-02-05 00:33:53 +00:00
int p7 = ctr - 7 ;
if ( p7 > start )
{
uint cid = cache [ p7 + 1 ] < < 16 | cache [ p7 ] ;
2018-10-21 02:03:04 +00:00
if ( prior . MatchesLock ( cid ) )
2018-02-05 00:33:53 +00:00
yield break ;
}
uint pid = cache [ ctr + 1 ] < < 16 | cache [ ctr ] ;
2018-10-21 02:03:04 +00:00
if ( l . MatchesLock ( pid ) )
2018-10-29 03:10:35 +00:00
yield return new SeedFrame { FrameID = ctr + ( l . Seen ? 5 : 7 ) , PID = pid } ;
2017-05-19 00:36:43 +00:00
2018-02-05 00:33:53 +00:00
ctr + = 2 ;
2018-05-12 15:41:23 +00:00
}
2017-05-19 00:36:43 +00:00
}
2018-02-05 00:33:53 +00:00
2018-10-29 03:10:35 +00:00
private static bool VerifyNPC ( FrameCache cache , int ctr , IEnumerable < SeedFrame > team , bool XD , out int originFrame )
2017-05-19 00:36:43 +00:00
{
2018-02-05 00:33:53 +00:00
originFrame = ctr + 2 ;
var tid = cache [ ctr + 1 ] ;
var sid = cache [ ctr ] ;
2017-05-19 00:36:43 +00:00
// verify none are shiny
2018-10-29 03:10:35 +00:00
foreach ( var pid in team )
2018-10-28 18:15:40 +00:00
{
2018-10-29 03:10:35 +00:00
if ( IsShiny ( tid , sid , pid . PID ) )
2018-02-04 08:06:07 +00:00
return true ; // todo
2018-10-28 18:15:40 +00:00
}
2017-05-19 00:36:43 +00:00
return true ;
}
// Helpers
2018-02-04 08:06:07 +00:00
private static bool IsShiny ( uint TID , uint SID , uint PID ) = > ( TID ^ SID ^ ( PID > > 16 ) ^ ( PID & 0xFFFF ) ) < 8 ;
2017-05-19 00:36:43 +00:00
private static bool IsShiny ( int TID , int SID , uint PID ) = > ( TID ^ SID ^ ( PID > > 16 ) ^ ( PID & 0xFFFF ) ) < 8 ;
2018-09-15 05:37:47 +00:00
2018-10-22 01:55:02 +00:00
public static bool IsFirstShadowLockValid ( EncounterStaticShadow s , PIDIV pv )
{
return IsFirstShadowLockValid ( pv , s . Locks , s . Version = = GameVersion . XD ) ;
}
2018-10-29 03:28:56 +00:00
public static bool IsAllShadowLockValid ( EncounterStaticShadow s , PIDIV pv )
{
return IsAllShadowLockValid ( pv , s . Locks , s . Version = = GameVersion . XD ) ;
}
public static bool IsAllShadowLockValid ( PIDIV pv , TeamLock [ ] teams , bool XD )
{
if ( teams . Length = = 0 )
return true ;
foreach ( var t in teams )
{
var locks = new Stack < NPCLock > ( t . Locks ) ;
var team = new Stack < SeedFrame > ( ) ;
var originSeed = pv . OriginSeed ;
var cache = new FrameCache ( RNG . XDRNG . Reverse ( originSeed , 2 ) , RNG . XDRNG . Prev ) ;
var result = FindLockSeed ( cache , 0 , locks , null , team , XD , out var _ ) ;
if ( result )
return true ;
}
return false ;
}
2018-10-22 01:55:02 +00:00
public static bool IsFirstShadowLockValid ( PIDIV pv , TeamLock [ ] teams , bool XD )
{
if ( teams . Length = = 0 )
return true ;
foreach ( var t in teams )
{
var locks = new Stack < NPCLock > ( 1 ) ;
locks . Push ( t . Locks [ t . Locks . Length - 1 ] ) ;
2018-10-29 03:28:56 +00:00
2018-10-29 03:10:35 +00:00
var team = new Stack < SeedFrame > ( ) ;
2018-10-22 01:55:02 +00:00
var originSeed = pv . OriginSeed ;
var cache = new FrameCache ( RNG . XDRNG . Reverse ( originSeed , 2 ) , RNG . XDRNG . Prev ) ;
2018-10-29 03:10:35 +00:00
var result = FindLockSeed ( cache , 0 , locks , null , team , XD , out var _ ) ;
2018-10-22 01:55:02 +00:00
if ( result )
return true ;
}
return false ;
}
2017-11-02 04:12:44 +00:00
// Colosseum/XD Starters
public static bool IsXDStarterValid ( uint seed , int TID , int SID )
{
// pidiv reversed 2x yields SID, 3x yields TID. shift by 7 if another PKM is generated prior
var SIDf = RNG . XDRNG . Reverse ( seed , 2 ) ;
var TIDf = RNG . XDRNG . Prev ( SIDf ) ;
return SIDf > > 16 = = SID & & TIDf > > 16 = = TID ;
}
2018-09-15 05:37:47 +00:00
2017-11-02 04:12:44 +00:00
public static bool IsColoStarterValid ( int species , ref uint seed , int TID , int SID , uint pkPID , uint IV1 , uint IV2 )
{
// reverse the seed the bare minimum
int rev = 2 ;
if ( species = = 196 )
rev + = 7 ;
var rng = RNG . XDRNG ;
var SIDf = rng . Reverse ( seed , rev ) ;
int ctr = 0 ;
2018-10-28 18:15:40 +00:00
uint temp ;
2018-05-24 13:00:29 +00:00
while ( ( temp = rng . Prev ( SIDf ) ) > > 16 ! = TID | | SIDf > > 16 ! = SID )
2017-11-02 04:12:44 +00:00
{
2018-05-12 15:41:23 +00:00
SIDf = temp ;
2017-11-02 04:12:44 +00:00
if ( ctr > 32 ) // arbitrary
return false ;
ctr + + ;
}
var next = rng . Next ( SIDf ) ;
// generate Umbreon
var PIDIV = GenerateValidColoStarterPID ( ref next , TID , SID ) ;
if ( species = = 196 ) // need espeon, which is immediately next
PIDIV = GenerateValidColoStarterPID ( ref next , TID , SID ) ;
if ( ! PIDIV . Equals ( pkPID , IV1 , IV2 ) )
return false ;
seed = rng . Reverse ( SIDf , 2 ) ;
return true ;
}
private struct PIDIVGroup
{
public uint PID ;
public uint IV1 ;
public uint IV2 ;
public bool Equals ( uint pid , uint iv1 , uint iv2 ) = > PID = = pid & & IV1 = = iv1 & & IV2 = = iv2 ;
}
private static PIDIVGroup GenerateValidColoStarterPID ( ref uint uSeed , int TID , int SID )
{
var rng = RNG . XDRNG ;
PIDIVGroup group = new PIDIVGroup ( ) ;
uSeed = rng . Advance ( uSeed , 2 ) ; // skip fakePID
group . IV1 = ( uSeed > > 16 ) & 0x7FFF ;
uSeed = rng . Next ( uSeed ) ;
group . IV2 = ( uSeed > > 16 ) & 0x7FFF ;
uSeed = rng . Next ( uSeed ) ;
uSeed = rng . Advance ( uSeed , 1 ) ; // skip ability call
group . PID = GenerateStarterPID ( ref uSeed , TID , SID ) ;
uSeed = rng . Advance ( uSeed , 2 ) ; // PID calls consumed
return group ;
}
2018-09-15 05:37:47 +00:00
2017-11-02 04:12:44 +00:00
private static uint GenerateStarterPID ( ref uint uSeed , int TID , int SID )
{
uint PID ;
const byte ratio = 0x20 ; // 12.5% F (can't be female)
while ( true )
{
var next = RNG . XDRNG . Next ( uSeed ) ;
PID = ( uSeed & 0xFFFF0000 ) | ( next > > 16 ) ;
if ( ( PID & 0xFF ) > ratio & & ! IsShiny ( TID , SID , PID ) )
break ;
uSeed = RNG . XDRNG . Next ( next ) ;
}
return PID ;
}
2017-05-19 00:36:43 +00:00
}
}