using System.Collections.Generic; namespace PKHeX.Core { public static class FrameFinder { /// /// Checks a to see if any encounter frames can generate the spread. Requires further filtering against matched Encounter Slots and generation patterns. /// /// Matched containing info and . /// object containing various accessible information required for the encounter. /// to yield possible encounter details for further filtering public static IEnumerable GetFrames(PIDIV pidiv, PKM pk) { if (pidiv.RNG == null) yield break; FrameGenerator info = new FrameGenerator(pidiv, pk); if (info.FrameType == FrameType.None) yield break; info.Nature = pk.EncryptionConstant % 25; // gather possible nature determination seeds until a same-nature PID breaks the unrolling var seeds = pk.Species == 201 && pk.FRLG // reversed await case ? SeedInfo.GetSeedsUntilUnownForm(pidiv, info, pk.AltForm) : SeedInfo.GetSeedsUntilNature(pidiv, info); var frames = pidiv.Type == PIDType.CuteCharm ? FilterCuteCharm(seeds, pidiv, info) : FilterNatureSync(seeds, pidiv, info); var refined = RefineFrames(frames, info); foreach (var z in refined) yield return z; } private static IEnumerable RefineFrames(IEnumerable frames, FrameGenerator info) { return info.FrameType == FrameType.MethodH ? RefineFrames3(frames, info) : RefineFrames4(frames, info); } private static IEnumerable RefineFrames3(IEnumerable frames, FrameGenerator info) { // ESV // Level // Nature // Current Seed of the frame is the Level Calc (frame before nature) var list = new List(); foreach (var f in frames) { bool noLead = !info.AllowLeads && f.Lead != LeadRequired.None; if (noLead) continue; var prev = info.RNG.Prev(f.Seed); // ESV var rand = prev >> 16; f.ESV = rand; yield return f; // Generate frames for other slots after the regular slots if (info.AllowLeads && (f.Lead == LeadRequired.CuteCharm || f.Lead == LeadRequired.None)) list.Add(f); } foreach (var f in list) { var leadframes = GenerateLeadSpecificFrames3(f, info); foreach (var frame in leadframes) yield return frame; } } private static IEnumerable GenerateLeadSpecificFrames3(Frame f, FrameGenerator info) { // Check leads -- none in list if leads are not allowed // Certain leads inject a RNG call // 3 different rand places LeadRequired lead; var prev0 = f.Seed; // 0 var prev1 = info.RNG.Prev(f.Seed); // -1 var prev2 = info.RNG.Prev(prev1); // -2 // Rand call raw values var p0 = prev0 >> 16; var p1 = prev1 >> 16; var p2 = prev2 >> 16; // Cute Charm // -2 ESV // -1 Level // 0 CC Proc (Random() % 3 != 0) // 1 Nature if (info.Gendered) { bool cc = p0 % 3 != 0; if (f.Lead == LeadRequired.CuteCharm) // 100% required for frame base { if (cc) yield return info.GetFrame(prev2, LeadRequired.CuteCharm, p2); yield break; } lead = cc ? LeadRequired.CuteCharm : LeadRequired.CuteCharmFail; yield return info.GetFrame(prev2, lead, p2); } // Pressure, Hustle, Vital Spirit = Force Maximum Level from slot // -2 ESV // -1 Level // 0 LevelMax proc (Random() & 1) // 1 Nature bool max = p0 % 2 == 1; lead = max ? LeadRequired.PressureHustleSpirit : LeadRequired.PressureHustleSpiritFail; yield return info.GetFrame(prev2, lead, p2); // Keen Eye, Intimidate // -2 ESV // -1 Level // 0 Level Adequate Check !(Random() % 2 == 1) rejects -- rand%2==1 is adequate // 1 Nature // Note: if this check fails, the encounter generation routine is aborted. if (max) // same result as above, no need to recalculate { lead = LeadRequired.IntimidateKeenEye; yield return info.GetFrame(prev2, lead, p2); } // Static or Magnet Pull // -2 SlotProc (Random % 2 == 0) // -1 ESV (select slot) // 0 Level // 1 Nature bool force = p2 % 2 == 0; if (force) { // Since a failed proc is indistinguishable from the default frame calls, only generate if it succeeds. lead = LeadRequired.StaticMagnet; yield return info.GetFrame(prev2, lead, p1); } } private static IEnumerable RefineFrames4(IEnumerable frames, FrameGenerator info) { var list = new List(); foreach (var f in frames) { // Current Seed of the frame is the ESV. var rand = f.Seed >> 16; { f.ESV = rand; yield return f; } if (f.Lead != LeadRequired.None) continue; // Generate frames for other slots after the regular slots list.Add(f); } foreach (var f in list) { // Level Modifiers between ESV and Nature var prev = info.RNG.Prev(f.Seed); var p16 = prev >> 16; yield return info.GetFrame(prev, LeadRequired.IntimidateKeenEye, p16); yield return info.GetFrame(prev, LeadRequired.PressureHustleSpirit, p16); // Slot Modifiers before ESV var force = (info.DPPt ? p16 >> 15 : p16 & 1) == 1; if (!force) continue; var rand = f.Seed >> 16; yield return info.GetFrame(prev, LeadRequired.StaticMagnet, rand); } } /// /// Filters the input according to a Nature Lock frame generation pattern. /// /// Seed Information for the frame /// PIDIV Info for the frame /// Search Info for the frame /// Possible matches to the Nature Lock frame generation pattern private static IEnumerable FilterNatureSync(IEnumerable seeds, PIDIV pidiv, FrameGenerator info) { foreach (var seed in seeds) { var s = seed.Seed; if (info.Safari3) { // successful pokeblock activation bool result = IsValidPokeBlockNature(s, info.Nature, out uint blockSeed); if (result) yield return info.GetFrame(blockSeed, seed.Charm3 ? LeadRequired.CuteCharm : LeadRequired.None); // if no pokeblocks present (or failed call), fall out of the safari specific code and generate via the other scenarios } var rand = s >> 16; bool sync = info.AllowLeads && !seed.Charm3 && (info.DPPt ? rand >> 15 : rand & 1) == 0; bool reg = (info.DPPt ? rand / 0xA3E : rand % 25) == info.Nature; if (!sync && !reg) // doesn't generate nature frame continue; uint prev = pidiv.RNG.Prev(s); if (info.AllowLeads && reg) // check for failed sync { var failsync = (info.DPPt ? prev >> 31 : (prev >> 16) & 1) != 1; if (failsync) yield return info.GetFrame(pidiv.RNG.Prev(prev), LeadRequired.SynchronizeFail); } if (sync) yield return info.GetFrame(prev, LeadRequired.Synchronize); if (reg) { if (seed.Charm3) yield return info.GetFrame(prev, LeadRequired.CuteCharm); else { if (info.Safari3) prev = pidiv.RNG.Prev(prev); // wasted RNG call yield return info.GetFrame(prev, LeadRequired.None); } } } } private static bool IsValidPokeBlockNature(uint seed, uint nature, out uint natureOrigin) { if (nature % 6 == 0) // neutral { natureOrigin = 0; return false; } // unroll the RNG to a stack of seeds var stack = new Stack(); for (uint i = 0; i < 25; i++) for (uint j = 1 + i; j < 25; j++) stack.Push(seed = RNG.LCRNG.Prev(seed)); natureOrigin = RNG.LCRNG.Prev(stack.Peek()); if (natureOrigin >> 16 % 100 >= 80) // failed proc return false; // init natures uint[] natures = new uint[25]; for (uint i = 0; i < 25; i++) natures[i] = i; // shuffle nature list for (uint i = 0; i < 25; i++) for (uint j = 1 + i; j < 25; j++) { var s = stack.Pop(); if ((s >> 16 & 1) == 0) continue; // only swap if 1 var temp = natures[i]; natures[i] = natures[j]; natures[j] = temp; } var likes = Pokeblock.GetLikedBlockFlavor(nature); // best case scenario is a perfect flavored pokeblock for the nature. // has liked flavor, and all other non-disliked flavors are present. // is it possible to skip this step? for (int i = 0; i < 25; i++) { var n = natures[i]; if (n == nature) break; var nl = Pokeblock.GetLikedBlockFlavor(natures[i]); if (nl == likes) // next random nature likes the same block as the desired nature return false; // current nature is chosen instead, fail! } // unroll once more to hit the level calc (origin with respect for beginning the nature calcs) natureOrigin = RNG.LCRNG.Prev(natureOrigin); return true; } /// /// Filters the input according to a Cute Charm frame generation pattern. /// /// Seed Information for the frame /// PIDIV Info for the frame /// Search Info for the frame /// Possible matches to the Cute Charm frame generation pattern private static IEnumerable FilterCuteCharm(IEnumerable seeds, PIDIV pidiv, FrameGenerator info) { foreach (var seed in seeds) { var s = seed.Seed; var rand = s >> 16; var nature = info.DPPt ? rand / 0xA3E : rand % 25; if (nature != info.Nature) continue; var prev = pidiv.RNG.Prev(s); var proc = prev >> 16; bool charmProc = (info.DPPt ? proc / 0x5556 : proc % 3) != 0; // 2/3 odds if (!charmProc) continue; yield return info.GetFrame(prev, LeadRequired.CuteCharm); } } } }