2018-10-30 04:28:22 +00:00
using System.Collections.Generic ;
namespace PKHeX.Core
{
2018-10-30 05:49:04 +00:00
/// <summary>
2018-10-31 00:45:21 +00:00
/// Analyzes a <see cref="TeamLock"/> to determine if the provided parameters are a valid end product.
2018-10-30 05:49:04 +00:00
/// </summary>
/// <remarks>
2018-10-31 00:45:21 +00:00
/// When generating a Trainer Team for <see cref="GameVersion.CXD"/>, the game must generate PID values that match the programmed team specifications.
2018-10-30 05:49:04 +00:00
/// The game is 'locked' into a PID generating loop until a valid PID is created, after which the routine is unlocked and proceeds to generate the next member.
/// These locks cause the <see cref="PKM.PID"/> of the current <see cref="PKM"/> to be rerolled until the requisite lock is satisfied.
/// This locking is the same as Method H/J/K and Gen3 Cute Charm by extension, with the additional restriction of not allowing shinies.
/// <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.
2018-10-31 00:45:21 +00:00
/// <see cref="PKM.IsShiny"/> locks require the member to not be shiny. This is enforced for non-shadow members for both games, and for shadow members in <see cref="GameVersion.XD"/>.
2018-10-30 05:49:04 +00:00
/// </remarks>
2018-10-30 04:28:22 +00:00
public sealed class TeamLockResult
{
2018-10-31 00:45:21 +00:00
/// <summary>
/// Seed prior to generating the team per <see cref="Specifications"/>.
/// </summary>
2018-10-30 04:28:22 +00:00
public readonly uint OriginSeed ;
2018-10-31 00:45:21 +00:00
/// <summary>
/// Indicates if the input parameters can be generated by the <see cref="Specifications"/> provided.
/// </summary>
2018-10-30 04:28:22 +00:00
public readonly bool Valid ;
2018-10-31 00:45:21 +00:00
/// <summary>
/// NPC Team data containing a list of <see cref="NPCLock"/> members.
/// </summary>
2018-10-30 16:21:32 +00:00
internal readonly TeamLock Specifications ;
2018-10-30 04:28:22 +00:00
2018-10-31 00:45:21 +00:00
/// <summary>
/// Frame the <see cref="OriginSeed"/> is from, based on frames reversed from the input seed provided.
/// </summary>
2018-10-30 04:28:22 +00:00
private int OriginFrame ;
2018-10-31 00:45:21 +00:00
/// <summary>
/// Required CPU Trainer Shiny Value
/// </summary>
/// <remarks>
/// If this value is >= 0, the CPU Trainer Shiny Value must be equal to this value as a <see cref="SeedFrame"/> skipped over a matching interrupt frame.
/// If this value is <see cref="NOT_FORCED"/>, the CPU Trainer Shiny Value can be anything (except matching any of the <see cref="SeedFrame"/> result members.
/// </remarks>
2018-10-30 05:49:04 +00:00
private int RCSV = NOT_FORCED ;
2018-10-30 04:28:22 +00:00
2018-10-31 00:45:21 +00:00
/// <summary>
/// Player Trainer Shiny Value
/// </summary>
2018-10-31 20:52:09 +00:00
/// <remarks>Only used by <see cref="GameVersion.XD"/> encounters, which disallow shiny shadow members for both player & CPU TSVs.</remarks>
2018-10-30 04:28:22 +00:00
private readonly int TSV ;
2018-10-31 00:45:21 +00:00
2018-10-30 04:28:22 +00:00
private readonly Stack < NPCLock > Locks ;
private readonly FrameCache Cache ;
2018-10-31 00:45:21 +00:00
// only save a list of valid nodes we've visited to save memory while we recursively search for a full team.
2018-10-30 04:28:22 +00:00
private readonly Stack < SeedFrame > Team ;
private const int NOT_FORCED = - 1 ;
internal TeamLockResult ( TeamLock teamSpec , uint originSeed , int tsv )
{
2018-10-30 16:21:32 +00:00
Locks = new Stack < NPCLock > ( ( Specifications = teamSpec ) . Locks ) ;
2021-08-22 00:44:43 +00:00
Team = new Stack < SeedFrame > ( Locks . Count ) ;
2018-10-30 04:28:22 +00:00
Cache = new FrameCache ( RNG . XDRNG . Reverse ( originSeed , 2 ) , RNG . XDRNG . Prev ) ;
TSV = tsv ;
Valid = FindLockSeed ( ) ;
if ( Valid )
OriginSeed = Cache . GetSeed ( OriginFrame ) ;
}
2018-10-31 00:45:21 +00:00
/// <summary>
/// Depth-first search traversal which finds a possible origin for the <see cref="Specifications"/>.
/// </summary>
/// <param name="frame">Frame at which the search starts/continues at.</param>
/// <param name="prior">Prior <see cref="NPCLock"/> data. If this is the last lock in the CPU Team, this is null.</param>
/// <returns>True if the <see cref="Specifications"/> are valid.</returns>
PKHeX.Core Nullable cleanup (#2401)
* Handle some nullable cases
Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data)
Make some classes have explicit constructors instead of { } initialization
* Handle bits more obviously without null
* Make SaveFile.BAK explicitly readonly again
* merge constructor methods to have readonly fields
* Inline some properties
* More nullable handling
* Rearrange box actions
define straightforward classes to not have any null properties
* Make extrabyte reference array immutable
* Move tooltip creation to designer
* Rearrange some logic to reduce nesting
* Cache generated fonts
* Split mystery gift album purpose
* Handle more tooltips
* Disallow null setters
* Don't capture RNG object, only type enum
* Unify learnset objects
Now have readonly properties which are never null
don't new() empty learnsets (>800 Learnset objects no longer created,
total of 2400 objects since we also new() a move & level array)
optimize g1/2 reader for early abort case
* Access rewrite
Initialize blocks in a separate object, and get via that object
removes a couple hundred "might be null" warnings since blocks are now readonly getters
some block references have been relocated, but interfaces should expose all that's needed
put HoF6 controls in a groupbox, and disable
* Readonly personal data
* IVs non nullable for mystery gift
* Explicitly initialize forced encounter moves
* Make shadow objects readonly & non-null
Put murkrow fix in binary data resource, instead of on startup
* Assign dex form fetch on constructor
Fixes legality parsing edge cases
also handle cxd parse for valid; exit before exception is thrown in FrameGenerator
* Remove unnecessary null checks
* Keep empty value until init
SetPouch sets the value to an actual one during load, but whatever
* Readonly team lock data
* Readonly locks
Put locked encounters at bottom (favor unlocked)
* Mail readonly data / offset
Rearrange some call flow and pass defaults
Add fake classes for SaveDataEditor mocking
Always party size, no need to check twice in stat editor
use a fake save file as initial data for savedata editor, and for
gamedata (wow i found a usage)
constrain eventwork editor to struct variable types (uint, int, etc),
thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
private bool FindLockSeed ( int frame = 0 , NPCLock ? prior = null )
2018-10-30 04:28:22 +00:00
{
if ( Locks . Count = = 0 ) // full team reverse-generated
return VerifyNPC ( frame ) ;
var current = Locks . Pop ( ) ;
var locks = GetPossibleLocks ( frame , current , prior ) ;
foreach ( var l in locks )
{
Team . Push ( l ) ; // possible match
if ( FindLockSeed ( l . FrameID , current ) )
return true ; // all locks are satisfied
Team . Pop ( ) ; // no match, remove
}
Locks . Push ( current ) ; // return the lock, lock is impossible
return false ;
}
2018-10-31 00:45:21 +00:00
/// <summary>
/// Generates a list of frames the <see cref="current"/> lock data can be generated at.
/// </summary>
/// <param name="ctr">Starting frame for the traversal.</param>
/// <param name="current">Current lock criteria to satisfy. Used to find valid <see cref="SeedFrame"/> results to yield.</param>
/// <param name="prior">Prior lock criteria. Used for determining when the traversal stops.</param>
/// <returns>List of possible locks for the provided input.</returns>
PKHeX.Core Nullable cleanup (#2401)
* Handle some nullable cases
Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data)
Make some classes have explicit constructors instead of { } initialization
* Handle bits more obviously without null
* Make SaveFile.BAK explicitly readonly again
* merge constructor methods to have readonly fields
* Inline some properties
* More nullable handling
* Rearrange box actions
define straightforward classes to not have any null properties
* Make extrabyte reference array immutable
* Move tooltip creation to designer
* Rearrange some logic to reduce nesting
* Cache generated fonts
* Split mystery gift album purpose
* Handle more tooltips
* Disallow null setters
* Don't capture RNG object, only type enum
* Unify learnset objects
Now have readonly properties which are never null
don't new() empty learnsets (>800 Learnset objects no longer created,
total of 2400 objects since we also new() a move & level array)
optimize g1/2 reader for early abort case
* Access rewrite
Initialize blocks in a separate object, and get via that object
removes a couple hundred "might be null" warnings since blocks are now readonly getters
some block references have been relocated, but interfaces should expose all that's needed
put HoF6 controls in a groupbox, and disable
* Readonly personal data
* IVs non nullable for mystery gift
* Explicitly initialize forced encounter moves
* Make shadow objects readonly & non-null
Put murkrow fix in binary data resource, instead of on startup
* Assign dex form fetch on constructor
Fixes legality parsing edge cases
also handle cxd parse for valid; exit before exception is thrown in FrameGenerator
* Remove unnecessary null checks
* Keep empty value until init
SetPouch sets the value to an actual one during load, but whatever
* Readonly team lock data
* Readonly locks
Put locked encounters at bottom (favor unlocked)
* Mail readonly data / offset
Rearrange some call flow and pass defaults
Add fake classes for SaveDataEditor mocking
Always party size, no need to check twice in stat editor
use a fake save file as initial data for savedata editor, and for
gamedata (wow i found a usage)
constrain eventwork editor to struct variable types (uint, int, etc),
thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
private IEnumerable < SeedFrame > GetPossibleLocks ( int ctr , NPCLock current , NPCLock ? prior )
2018-10-30 04:28:22 +00:00
{
if ( prior ? . Shadow ! = false )
return GetSingleLock ( ctr , current ) ;
2021-08-22 00:44:43 +00:00
return GetAllLocks ( ctr , current , ( NPCLock ) prior ) ;
2018-10-30 04:28:22 +00:00
}
2018-10-31 00:45:21 +00:00
/// <summary>
/// Returns a single <see cref="SeedFrame"/> as the <see cref="current"/> lock must match precisely.
/// </summary>
/// <param name="ctr">Starting frame for the traversal.</param>
/// <param name="current">Current lock criteria to satisfy. Used to find valid <see cref="SeedFrame"/> results to yield.</param>
/// <returns></returns>
2018-10-30 04:28:22 +00:00
private IEnumerable < SeedFrame > GetSingleLock ( int ctr , NPCLock current )
{
uint pid = Cache [ ctr + 1 ] < < 16 | Cache [ ctr ] ;
if ( current . MatchesLock ( pid ) )
2021-08-22 00:44:43 +00:00
yield return new SeedFrame ( pid , ctr + current . FramesConsumed ) ;
2020-05-11 23:52:09 +00:00
else
yield break ;
// Reaching here means the single lock didn't cut it. Maybe the frame before it was an anti-shiny reroll?
// Track if we ever require the CPU Trainer Shiny Value to be a value for a shiny skip.
// We need to un-set this flag if future frames don't pan out.
bool forcedOT = false ;
int start = 2 ;
while ( true )
{
var upper = Cache [ start + 1 ] ;
var lower = Cache [ start ] ;
// uint cid = upper << 16 | lower;
var sv = ( upper ^ lower ) > > 3 ;
if ( sv = = TSV ) // XD shiny checks all opponent PKM, even non-shadow.
{
// Anti-shiny rerolled! This is a possible frame.
}
else if ( RCSV ! = NOT_FORCED ) // CPU shiny value is required for a previous lock
{
2021-05-18 20:29:55 +00:00
if ( sv = = RCSV )
2020-05-11 23:52:09 +00:00
{
2021-05-18 20:29:55 +00:00
// No CPU shiny value forced yet. Lets try to skip this lock by requiring the eventual OT to get this shiny.
RCSV = ( int ) sv ;
2020-05-11 23:52:09 +00:00
forcedOT = true ;
2021-05-18 20:29:55 +00:00
continue ; // don't break
2020-05-11 23:52:09 +00:00
}
2021-05-18 20:29:55 +00:00
if ( forcedOT ) // current call to this method had forced the OT; clear the forced OT before breaking.
RCSV = NOT_FORCED ;
yield break ; // Since we can't skip this interrupt, we're done.
2020-05-11 23:52:09 +00:00
}
// Yield the final rerolled pid instead of the bad anti-shiny (metadata/validation).
2021-08-22 00:44:43 +00:00
yield return new SeedFrame ( pid , start + current . FramesConsumed ) ;
2020-05-11 23:52:09 +00:00
start + = 2 ;
}
2018-10-30 04:28:22 +00:00
}
2018-10-31 00:45:21 +00:00
/// <summary>
/// Generates <see cref="SeedFrame"/> nodes until the traversal is ended by an interrupt frame that matches the <see cref="prior"/>.
/// </summary>
/// <param name="ctr">Starting frame for the traversal.</param>
/// <param name="current">Current lock criteria to satisfy. Used to find valid <see cref="SeedFrame"/> results to yield.</param>
/// <param name="prior">Prior lock criteria. Used for determining when the traversal stops.</param>
/// <returns>List of possible locks for the provided input.</returns>
/// <remarks>
/// An "interrupt" signals the end of the traversal.
/// Any <see cref="SeedFrame"/> afterwards (when generated forward from the CPU Trainer) will use the interrupt rather than the previous <see cref="SeedFrame"/> that was found for the <see cref="prior"/> lock.
/// </remarks>
private IEnumerable < SeedFrame > GetAllLocks ( int ctr , NPCLock current , NPCLock prior )
2018-10-30 04:28:22 +00:00
{
// 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.
int start = ctr ;
2018-10-31 00:45:21 +00:00
// Track if we ever require the CPU Trainer Shiny Value to be a value for a shiny skip.
// We need to un-set this flag if future frames don't pan out.
2018-10-30 04:28:22 +00:00
bool forcedOT = false ;
2018-10-31 00:45:21 +00:00
2018-10-30 04:28:22 +00:00
while ( true )
{
int p7 = ctr - 7 ;
if ( p7 > start )
{
// check for interrupting cpu team cases
var upper = Cache [ p7 + 1 ] ;
var lower = Cache [ p7 ] ;
uint cid = upper < < 16 | lower ;
var sv = ( upper ^ lower ) > > 3 ;
2020-03-12 02:18:58 +00:00
if ( sv = = TSV ) // XD shiny checks all opponent PKM, even non-shadow.
2018-10-30 04:28:22 +00:00
{
// This interrupt is ignored! The result is shiny.
}
else if ( prior . MatchesLock ( cid ) ) // lock matched cpu mon
{
if ( RCSV ! = NOT_FORCED ) // CPU shiny value is required for a previous lock
{
if ( sv ! = RCSV )
{
if ( forcedOT ) // current call to this method had forced the OT; clear the forced OT before breaking.
RCSV = NOT_FORCED ;
yield break ; // Since we can't skip this interrupt, we're done.
}
}
else // No CPU shiny value forced yet. Lets try to skip this lock by requiring the eventual OT to get this shiny.
{
RCSV = ( int ) sv ;
forcedOT = true ;
// don't break
}
}
}
uint pid = Cache [ ctr + 1 ] < < 16 | Cache [ ctr ] ;
2018-10-31 00:45:21 +00:00
if ( current . MatchesLock ( pid ) )
2021-08-22 00:44:43 +00:00
yield return new SeedFrame ( pid , ctr + current . FramesConsumed ) ;
2018-10-30 04:28:22 +00:00
ctr + = 2 ;
}
}
2018-10-31 00:45:21 +00:00
/// <summary>
/// Checks to see if the generated <see cref="Team"/> is compatible with the CPU Trainer Data.
/// </summary>
/// <param name="ctr">Ending frame of the traversal.</param>
/// <returns>True if the <see cref="Specifications"/> are valid.</returns>
2018-10-30 04:28:22 +00:00
private bool VerifyNPC ( int ctr )
{
var TID = Cache [ ctr + 1 ] ;
var SID = Cache [ ctr ] ;
var CPUSV = ( TID ^ SID ) > > 3 ;
if ( RCSV ! = NOT_FORCED & & RCSV ! = CPUSV )
return false ; // required CPU Trainer's shiny value did not match the required value.
int pos = Team . Count - 1 ; // stack can't do a for loop :(
foreach ( var member in Team )
{
var pid = member . PID ;
var psv = ( ( pid & 0xFFFF ) ^ ( pid > > 16 ) ) > > 3 ;
2018-10-31 00:45:21 +00:00
2020-03-12 02:18:58 +00:00
// check for shiny for Trainer -- XD only
// if (psv == TSV) // XD shiny checks all opponent PKM, even non-shadow.
// return false; // no shiny shadow mons
// we already checked this when re-generating the team
2018-10-31 00:45:21 +00:00
// check for shiny for CPU
if ( psv = = CPUSV )
{
if ( ! Specifications . Locks [ pos ] . Shadow )
return false ; // no shiny CPU mons
if ( TSV ! = NOT_FORCED ) // XD
return false ; // no shiny shadow mons
}
2018-10-30 04:28:22 +00:00
pos - - ;
}
OriginFrame = ctr + 2 ;
return true ;
}
}
2018-10-30 16:21:32 +00:00
}