using System.Collections.Generic; namespace PKHeX.Core { /// /// Contains various Colosseum/XD 'wait for value' logic related to PKM generation. /// /// /// When generating a Trainer Team, the game must generate PID values that match the programmed team specifications. /// 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 of the current 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. /// locks require a certain , which is derived from the . /// locks require a certain gender value, which is derived from the and ratio. /// public sealed class TeamLockResult { public readonly uint OriginSeed; public readonly bool Valid; internal readonly TeamLock Specifications; private int OriginFrame; private int RCSV = NOT_FORCED; private readonly int TSV; private readonly Stack Locks; private readonly FrameCache Cache; private readonly Stack Team; private const int NOT_FORCED = -1; internal TeamLockResult(TeamLock teamSpec, uint originSeed, int tsv) { Locks = new Stack((Specifications = teamSpec).Locks); Team = new Stack(); Cache = new FrameCache(RNG.XDRNG.Reverse(originSeed, 2), RNG.XDRNG.Prev); TSV = tsv; Valid = FindLockSeed(); if (Valid) OriginSeed = Cache.GetSeed(OriginFrame); } // Recursively iterates to visit possible locks until all locks (or none) are satisfied. private bool FindLockSeed(int frame = 0, NPCLock prior = null) { 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; } private IEnumerable GetPossibleLocks(int ctr, NPCLock current, NPCLock prior) { if (prior?.Shadow != false) return GetSingleLock(ctr, current); return GetAllLocks(ctr, current, prior); } private IEnumerable GetSingleLock(int ctr, NPCLock current) { uint pid = Cache[ctr + 1] << 16 | Cache[ctr]; if (current.MatchesLock(pid)) yield return new SeedFrame { FrameID = ctr + (current.Seen ? 5 : 7), PID = pid }; } private IEnumerable GetAllLocks(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. int start = ctr; bool forcedOT = false; 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; if (l.Shadow && sv == TSV) // shiny shadow mon, only ever true for XD. { // 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]; if (l.MatchesLock(pid)) yield return new SeedFrame { FrameID = ctr + (l.Seen ? 5 : 7), PID = pid }; ctr += 2; } } 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; bool shadow = Specificiations.Locks[pos].Shadow; if (!shadow && psv == CPUSV) return false; if (psv == TSV) // always false for Colo, which doesn't set the TSV field. return false; pos--; } OriginFrame = ctr + 2; return true; } } }