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.
This commit is contained in:
Kurt 2017-11-01 21:12:44 -07:00
parent d52c1f2def
commit 495eb26740
6 changed files with 136 additions and 33 deletions

View file

@ -932,30 +932,18 @@ namespace PKHeX.Core
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()

View file

@ -391,8 +391,9 @@ namespace PKHeX.Core
#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

View file

@ -2,13 +2,27 @@
namespace PKHeX.Core
{
public class NPCLock
/// <summary>
/// Locks associated to a given NPC PKM that appears before a <see cref="EncounterStaticShadow"/>.
/// </summary>
public sealed class NPCLock
{
public int Species;
public uint? Nature = null;
public uint? Gender = null;
public uint? Ability = null;
}
/// <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>
public static class LockFinder
{
// Message Passing
@ -64,7 +78,7 @@ namespace PKHeX.Core
} while (true);
}
private static bool VerifyNPC(uint seed, RNG RNG, Stack<uint> PIDs, bool XD, out uint origin)
private static bool VerifyNPC(uint seed, RNG RNG, IEnumerable<uint> PIDs, bool XD, out uint origin)
{
// todo: get trainer TID/SID/Origin Seed
origin = 0;
@ -72,9 +86,8 @@ namespace PKHeX.Core
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 @@ namespace PKHeX.Core
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;
}
}
}

View file

@ -34,6 +34,8 @@ namespace PKHeX.Core
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 @@ namespace PKHeX.Core
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);
}
/// <summary>
/// Returns false and no <see cref="PIDIV"/>.
@ -660,7 +681,7 @@ namespace PKHeX.Core
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:

View file

@ -64,6 +64,7 @@
// XDRNG Based
CXD,
CXD_ColoStarter,
Channel,
PokeSpot,

View file

@ -27,10 +27,6 @@ namespace PKHeX.Core
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));
}
}