mirror of
https://github.com/kwsch/PKHeX
synced 2025-02-17 05:48:44 +00:00
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:
parent
d52c1f2def
commit
495eb26740
6 changed files with 136 additions and 33 deletions
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
|
||||
// XDRNG Based
|
||||
CXD,
|
||||
CXD_ColoStarter,
|
||||
Channel,
|
||||
PokeSpot,
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue