From 495eb26740a52e78ef4e431a49ae8a2dbf3df48a Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 1 Nov 2017 21:12:44 -0700 Subject: [PATCH] Rework colo starter PIDIV detection force colo starters to be male only use some c# lang features for EncounterType flags add edge case PIDIV detection for starter gender/shiny lock scenario adds a little documentation for those unfamiliar with the NPC PKM generation quirks. Colosseum Starters will now be recognized with a different PIDIV type which is specific to them & them only. --- PKHeX.Core/Legality/Checks.cs | 28 ++--- .../Legality/Encounters/Data/Encounters3.cs | 5 +- PKHeX.Core/Legality/RNG/Locks/LockFinder.cs | 106 +++++++++++++++++- PKHeX.Core/Legality/RNG/MethodFinder.cs | 23 +++- PKHeX.Core/Legality/RNG/PIDType.cs | 1 + .../Legality/Structures/EncounterType.cs | 6 +- 6 files changed, 136 insertions(+), 33 deletions(-) diff --git a/PKHeX.Core/Legality/Checks.cs b/PKHeX.Core/Legality/Checks.cs index a223f3c11..aba8f5985 100644 --- a/PKHeX.Core/Legality/Checks.cs +++ b/PKHeX.Core/Legality/Checks.cs @@ -932,30 +932,18 @@ private void VerifyCXDStarterCorrelation(PIDIV pidiv) if (pidiv.Type != PIDType.CXD) return; - var spec = EncounterMatch.Species; - int rev; // pidiv reversed 2x yields SID, 3x yields TID. shift by 7 if another PKM is generated prior - switch (spec) + bool valid; + switch (EncounterMatch.Species) { - // XD - case 133: // Eevee - rev = 2; - break; - - // Colosseum - case 197: // Umbreon (generated before Espeon) - rev = 2; - break; - case 196: // Espeon (generated after Umbreon) - rev = 2+7; - break; + case 133: + valid = LockFinder.IsXDStarterValid(pidiv.OriginSeed, pkm.TID, pkm.SID); break; + case 196: case 197: + valid = pidiv.Type == PIDType.CXD_ColoStarter; break; default: return; } - var seed = pidiv.OriginSeed; - var SIDf = pidiv.RNG.Reverse(seed, rev); - var TIDf = pidiv.RNG.Prev(SIDf); - if (SIDf >> 16 != pkm.SID || TIDf >> 16 != pkm.TID) - AddLine(Severity.Invalid, V400 + $" {TIDf>>16}/{SIDf>>16}", CheckIdentifier.PID); + if (!valid) + AddLine(Severity.Invalid, V400, CheckIdentifier.PID); } private void VerifyAbility() diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs index f680af810..5373fa336 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs @@ -391,8 +391,9 @@ private static void MarkG3SlotsSafariZones(ref EncounterArea[] Areas, int locati #region Colosseum internal static readonly EncounterStatic[] Encounter_Colo = { - new EncounterStatic { Gift = true, Species = 196, Level = 25, Location = 254 }, // Espeon - new EncounterStatic { Gift = true, Species = 197, Level = 26, Location = 254, Moves = new[] {044} }, // Umbreon (Bite) + // Colosseum Starters: Gender locked to male + new EncounterStatic { Gift = true, Species = 196, Level = 25, Location = 254, Gender = 0 }, // Espeon + new EncounterStatic { Gift = true, Species = 197, Level = 26, Location = 254, Gender = 0, Moves = new[] {044} }, // Umbreon (Bite) new EncounterStaticShadow { Species = 296, Level = 30, Gauge = 03000, Moves = new[] {193,116,233,238}, Location = 005 }, // Makuhita: Miror B.Peon Trudly @ Phenac City diff --git a/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs b/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs index 1d309fb92..8f265ce3a 100644 --- a/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs +++ b/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs @@ -2,13 +2,27 @@ namespace PKHeX.Core { - public class NPCLock + /// + /// Locks associated to a given NPC PKM that appears before a . + /// + public sealed class NPCLock { public int Species; public uint? Nature = null; public uint? Gender = null; public uint? Ability = null; } + + /// + /// Contains various Colosseum/XD 'wait for value' logic related to PKM generation. + /// + /// + /// "Locks" are referring to the being "locked" to a certain value, e.g. requiring Nature to be neutral. + /// These locks cause the of the current to be rerolled until the requisite lock is satisfied. + /// locks require a certain , which is derived from the . + /// locks require a certain gender value, which is derived from the and ratio. + /// Not sure if Abilities are locked for the encounter, assume not. When this code is eventually utilized, our understanding can be tested! + /// public static class LockFinder { // Message Passing @@ -64,7 +78,7 @@ private static IEnumerable FindPossibleLockFrames(uint seed, RNG RNG, N } while (true); } - private static bool VerifyNPC(uint seed, RNG RNG, Stack PIDs, bool XD, out uint origin) + private static bool VerifyNPC(uint seed, RNG RNG, IEnumerable PIDs, bool XD, out uint origin) { // todo: get trainer TID/SID/Origin Seed origin = 0; @@ -72,9 +86,8 @@ private static bool VerifyNPC(uint seed, RNG RNG, Stack PIDs, bool XD, out var sid = 0; // verify none are shiny - var arr = PIDs.ToArray(); - for (int i = 0; i < PIDs.Count; i++) - if (IsShiny(tid, sid, arr[i])) + foreach (var pid in PIDs) + if (IsShiny(tid, sid, pid)) return false; return true; } @@ -91,5 +104,88 @@ private static bool MatchesLock(NPCLock k, uint PID, int Gender, int AbilityNumb return false; return true; } + + // 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; + } + 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; + while (true) + { + if (SIDf >> 16 == SID && rng.Prev(SIDf) >> 16 == TID) + break; + SIDf = rng.Prev(SIDf); + 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; + } + 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; + } } } diff --git a/PKHeX.Core/Legality/RNG/MethodFinder.cs b/PKHeX.Core/Legality/RNG/MethodFinder.cs index bfc5a3dfd..0512df0d3 100644 --- a/PKHeX.Core/Legality/RNG/MethodFinder.cs +++ b/PKHeX.Core/Legality/RNG/MethodFinder.cs @@ -34,6 +34,8 @@ public static PIDIV Analyze(PKM pk) return pidiv; if (pk.Species == 201 && GetLCRNGUnownMatch(top, bot, IVs, out pidiv)) // frlg only return pidiv; + if (GetColoStarterMatch(pk, top, bot, IVs, out pidiv)) + return pidiv; if (GetXDRNGMatch(top, bot, IVs, out pidiv)) return pidiv; @@ -448,6 +450,25 @@ private static bool GetPokewalkerMatch(PKM pk, uint oldpid, out PIDIV pidiv) pidiv = new PIDIV {NoSeed = true, RNG = RNG.LCRNG, Type = PIDType.Pokewalker}; return true; } + private static bool GetColoStarterMatch(PKM pk, uint top, uint bot, uint[] IVs, out PIDIV pidiv) + { + if (pk.Version != 15 || pk.Species != 196 && pk.Species != 197) + return GetNonMatch(out pidiv); + + var iv1 = GetIVChunk(IVs, 0); + var iv2 = GetIVChunk(IVs, 3); + var xdc = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot); + foreach (var seed in xdc) + { + uint origin = seed; + if (!LockFinder.IsColoStarterValid(pk.Species, ref origin, pk.TID, pk.SID, pk.PID, iv1, iv2)) + continue; + + pidiv = new PIDIV { OriginSeed = origin, RNG = RNG.XDRNG, Type = PIDType.CXD_ColoStarter }; + return true; + } + return GetNonMatch(out pidiv); + } /// /// Returns false and no . @@ -660,7 +681,7 @@ public static bool IsCompatible3(this PIDType val, IEncounterable encounter, PKM case EncounterStatic s: switch (pkm.Version) { - case (int)GameVersion.CXD: return val == PIDType.CXD; + case (int)GameVersion.CXD: return val == PIDType.CXD || val == PIDType.CXD_ColoStarter; case (int)GameVersion.E: return val == PIDType.Method_1; // no roamer glitch case (int)GameVersion.FR: diff --git a/PKHeX.Core/Legality/RNG/PIDType.cs b/PKHeX.Core/Legality/RNG/PIDType.cs index a2e6658bb..6f20dafaa 100644 --- a/PKHeX.Core/Legality/RNG/PIDType.cs +++ b/PKHeX.Core/Legality/RNG/PIDType.cs @@ -64,6 +64,7 @@ public enum PIDType // XDRNG Based CXD, + CXD_ColoStarter, Channel, PokeSpot, diff --git a/PKHeX.Core/Legality/Structures/EncounterType.cs b/PKHeX.Core/Legality/Structures/EncounterType.cs index 53a4c4964..1848cb97a 100644 --- a/PKHeX.Core/Legality/Structures/EncounterType.cs +++ b/PKHeX.Core/Legality/Structures/EncounterType.cs @@ -27,10 +27,6 @@ public enum EncounterType public static class EncounterTypeExtension { - public static bool Contains(this EncounterType g1, int g2) - { - var type = (EncounterType)(1 << g2); - return (g1 & type) != 0; - } + public static bool Contains(this EncounterType g1, int g2) => g1.HasFlag((EncounterType)(1 << g2)); } }