diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterGift3Colo.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterGift3Colo.cs
index adce9e3e6..034a6b16e 100644
--- a/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterGift3Colo.cs
+++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterGift3Colo.cs
@@ -42,8 +42,6 @@ public EncounterGift3Colo(ushort species, byte level, string[] trainers, GameVer
public byte LevelMin => Level;
public byte LevelMax => Level;
- public bool IsColoStarter => Species is (ushort)Core.Species.Espeon or (ushort)Core.Species.Umbreon;
-
#region Generating
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria);
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr);
@@ -90,6 +88,9 @@ private int GetTemplateLanguage(ITrainerInfo tr)
private void SetPINGA(CK3 pk, EncounterCriteria criteria, PersonalInfo3 pi)
{
+ if (criteria.IsSpecifiedIVs() && MethodCXD.SetFromIVsCXD(pk, criteria, pi, Shiny == Shiny.Never))
+ return;
+
var gender = criteria.GetGender(pi);
var nature = criteria.GetNature();
var ability = criteria.GetAbilityFromNumber(Ability);
@@ -156,12 +157,7 @@ private bool IsMatchPartial(PKM pk)
}
#endregion
- public bool IsCompatible(PIDType val, PKM pk)
- {
- if (IsColoStarter)
- return val is PIDType.CXD_ColoStarter;
- return val is PIDType.CXD;
- }
+ public bool IsCompatible(PIDType val, PKM pk) => val is PIDType.CXD;
public PIDType GetSuggestedCorrelation() => PIDType.CXD;
diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterShadow3Colo.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterShadow3Colo.cs
index f29fcc8f0..0c4909fc9 100644
--- a/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterShadow3Colo.cs
+++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterShadow3Colo.cs
@@ -89,6 +89,9 @@ private void SetPINGA(CK3 pk, EncounterCriteria criteria, PersonalInfo3 pi)
private void SetPINGA_Regular(CK3 pk, EncounterCriteria criteria, PersonalInfo3 pi)
{
+ if (criteria.IsSpecifiedIVs() && this.SetFromIVs(pk, criteria, pi))
+ return;
+
var gender = criteria.GetGender(pi);
var nature = criteria.GetNature();
int ability = criteria.GetAbilityFromNumber(Ability);
@@ -107,8 +110,6 @@ private void SetPINGA_Regular(CK3 pk, EncounterCriteria criteria, PersonalInfo3
break;
}
while (++ctr <= max);
-
- System.Diagnostics.Debug.Assert(ctr < max);
}
private void SetPINGA_EReader(CK3 pk)
diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterStatic3Colo.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterStatic3Colo.cs
index 1c2dce0a6..8d82b965c 100644
--- a/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterStatic3Colo.cs
+++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/Colo/EncounterStatic3Colo.cs
@@ -71,6 +71,9 @@ public CK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
private void SetPINGA(CK3 pk, EncounterCriteria criteria, PersonalInfo3 pi)
{
+ if (MethodCXD.SetFromTrainerIDStarter(pk, criteria, pi, pk.TID16, pk.SID16))
+ return;
+
var gender = criteria.GetGender(Gender, pi);
var nature = criteria.GetNature();
var ability = criteria.GetAbilityFromNumber(Ability);
diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterShadow3XD.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterShadow3XD.cs
index e35b35e5d..a5e719023 100644
--- a/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterShadow3XD.cs
+++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterShadow3XD.cs
@@ -79,6 +79,9 @@ public XK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
private void SetPINGA(XK3 pk, EncounterCriteria criteria, PersonalInfo3 pi)
{
+ if (criteria.IsSpecifiedIVs() && this.SetFromIVs(pk, criteria, pi, noShiny: true))
+ return;
+
var gender = criteria.GetGender(pi);
var nature = criteria.GetNature();
int ability = criteria.GetAbilityFromNumber(Ability);
diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterStatic3XD.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterStatic3XD.cs
index eefabb766..d963e4915 100644
--- a/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterStatic3XD.cs
+++ b/PKHeX.Core/Legality/Encounters/Templates/Gen3/XD/EncounterStatic3XD.cs
@@ -29,8 +29,6 @@ public sealed record EncounterStatic3XD(ushort Species, byte Level)
public byte LevelMin => Level;
public byte LevelMax => Level;
- public bool IsColoStarter => Species is (ushort)Core.Species.Espeon or (ushort)Core.Species.Umbreon;
-
#region Generating
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria);
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr);
@@ -71,6 +69,16 @@ public XK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
private void SetPINGA(XK3 pk, EncounterCriteria criteria, PersonalInfo3 pi)
{
+ if (Species == (int)Core.Species.Eevee)
+ {
+ if (MethodCXD.SetFromTrainerIDStarter(pk, criteria, pi, pk.TID16, pk.SID16))
+ return;
+ }
+ else
+ {
+ if (criteria.IsSpecifiedIVs() && MethodCXD.SetFromIVsCXD(pk, criteria, pi, noShiny: true))
+ return;
+ }
var gender = criteria.GetGender(pi);
var nature = criteria.GetNature();
var ability = criteria.GetAbilityFromNumber(Ability);
@@ -137,8 +145,6 @@ private bool IsMatchPartial(PKM pk)
public bool IsCompatible(PIDType val, PKM pk)
{
- if (IsColoStarter)
- return val is PIDType.CXD_ColoStarter;
if (val is PIDType.CXD)
return true;
return val is PIDType.CXDAnti && FatefulEncounter;
diff --git a/PKHeX.Core/Legality/RNG/CXD/MethodCXD.cs b/PKHeX.Core/Legality/RNG/CXD/MethodCXD.cs
new file mode 100644
index 000000000..64ce9b899
--- /dev/null
+++ b/PKHeX.Core/Legality/RNG/CXD/MethodCXD.cs
@@ -0,0 +1,190 @@
+using System;
+
+namespace PKHeX.Core;
+
+///
+/// Generator methods for .
+///
+public static class MethodCXD
+{
+ ///
+ /// Tries to set a valid PID/IV for the requested criteria for an object.
+ ///
+ /// Only called if is true.
+ public static bool SetFromIVs(this T enc, G3PKM pk, EncounterCriteria criteria, PersonalInfo3 pi, bool noShiny = false) where T : IShadow3
+ {
+ var gr = pi.Gender;
+ (uint iv1, uint iv2) = GetCombinedIVs(criteria);
+ Span all = stackalloc uint[XDRNG.MaxCountSeedsIV];
+ var count = XDRNG.GetSeedsIVs(all, iv1 << 16, iv2 << 16);
+ var seeds = all[..count];
+ foreach (var seed in seeds)
+ {
+ // * => IV, IV, ability, PID, PID
+ var s = XDRNG.Next3(seed);
+ uint pid = GetPID(pk, s, noShiny);
+ if (criteria.IsSpecifiedNature() && (Nature)(pid % 25) != criteria.Nature)
+ continue;
+
+ var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
+ if (!criteria.IsGenderSatisfied(gender))
+ continue;
+
+ var origin = XDRNG.Prev(seed);
+ var pidiv = new PIDIV(PIDType.CXD, origin);
+ var result = LockFinder.IsAllShadowLockValid(enc, pidiv, pk);
+ if (!result)
+ continue;
+
+ pk.PID = pid;
+ pk.Ability = ((XDRNG.Next2(seed) >> 16) & 1) == 0 ? pi.Ability1 : pi.Ability2;
+ criteria.SetRandomIVs(pk);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Tries to set a valid PID/IV for the requested criteria for a non-shadow encounter.
+ ///
+ /// Only called if is true.
+ public static bool SetFromIVsCXD(G3PKM pk, EncounterCriteria criteria, PersonalInfo3 pi, bool noShiny = true)
+ {
+ var gr = pi.Gender;
+ (uint iv1, uint iv2) = GetCombinedIVs(criteria);
+ Span all = stackalloc uint[XDRNG.MaxCountSeedsIV];
+ var count = XDRNG.GetSeedsIVs(all, iv1 << 16, iv2 << 16);
+ var seeds = all[..count];
+ foreach (var seed in seeds)
+ {
+ // * => IV, IV, ability, PID, PID
+ var s = XDRNG.Next3(seed);
+ uint pid = GetPID(pk, s, noShiny);
+ if (criteria.IsSpecifiedNature() && (Nature)(pid % 25) != criteria.Nature)
+ continue;
+
+ var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
+ if (!criteria.IsGenderSatisfied(gender))
+ continue;
+
+ pk.PID = pid;
+ pk.Ability = ((XDRNG.Next2(seed) >> 16) & 1) == 0 ? pi.Ability1 : pi.Ability2;
+ criteria.SetRandomIVs(pk);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Tries to set a valid PID/IV for the requested criteria for a Colosseum starter (Umbreon and Espeon), preferring to match Trainer IDs.
+ ///
+ public static bool SetFromTrainerIDStarter(CK3 pk, EncounterCriteria criteria, PersonalInfo3 pi, ushort tid, ushort sid)
+ {
+ // * => TID, SID, fakepid*2, [IVs, ability, PID]
+ Span all = stackalloc uint[XDRNG.MaxCountSeedsPID];
+ var count = XDRNG.GetSeeds(all, (uint)tid << 16, (uint)sid << 16);
+ var seeds = all[..count];
+ foreach (ref var seed in seeds)
+ {
+ var s = XDRNG.Next7(seed);
+ uint pid = GetPIDStarterMale(ref s, pk.ID32);
+ if (pk.Species == (int)Species.Espeon) // After Umbreon
+ {
+ s = XDRNG.Next2(s);
+ pid = GetPIDStarterMale(ref s, pk.ID32);
+ }
+ if (criteria.IsSpecifiedNature() && (Nature)(pid % 25) != criteria.Nature)
+ continue;
+
+ var ivSeed = XDRNG.Next4(seed);
+ var iv1 = XDRNG.Next15(ref ivSeed);
+ var iv2 = XDRNG.Next15(ref ivSeed);
+
+ SetIVs(pk, iv1, iv2);
+ pk.PID = pid;
+ pk.Ability = ((XDRNG.Next2(seed) >> 16) & 1) == 0 ? pi.Ability1 : pi.Ability2;
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Tries to set a valid PID/IV for the requested criteria for the XD starter (Eevee), preferring to match Trainer IDs.
+ ///
+ public static bool SetFromTrainerIDStarter(XK3 pk, EncounterCriteria criteria, PersonalInfo3 pi, ushort tid, ushort sid)
+ {
+ // * => TID, SID, fakepid*2, [IVs, ability, PID]
+ Span all = stackalloc uint[XDRNG.MaxCountSeedsPID];
+ var count = XDRNG.GetSeeds(all, (uint)tid << 16, (uint)sid << 16);
+ var seeds = all[..count];
+ foreach (ref var seed in seeds)
+ {
+ var s = XDRNG.Next7(seed);
+ uint pid = GetPID(s);
+ if (criteria.IsSpecifiedNature() && (Nature)(pid % 25) != criteria.Nature)
+ continue;
+
+ var ivSeed = XDRNG.Next4(seed);
+ var iv1 = XDRNG.Next15(ref ivSeed);
+ var iv2 = XDRNG.Next15(ref ivSeed);
+
+ SetIVs(pk, iv1, iv2);
+ pk.PID = pid;
+ pk.Ability = ((XDRNG.Next2(seed) >> 16) & 1) == 0 ? pi.Ability1 : pi.Ability2;
+ return true;
+ }
+
+ return false;
+ }
+
+ private static uint GetPID(uint seed)
+ {
+ var a = XDRNG.Next16(ref seed);
+ var b = XDRNG.Next16(ref seed);
+ return GetPIDRegular(a, b);
+ }
+
+ private static uint GetPID(G3PKM pk, uint seed, bool noShiny)
+ {
+ while (true)
+ {
+ var a = XDRNG.Next16(ref seed);
+ var b = XDRNG.Next16(ref seed);
+ var pid = GetPIDRegular(a, b);
+ if (!noShiny || !ShinyUtil.GetIsShiny(pk.ID32, pid))
+ return pid;
+ }
+ }
+
+ private static uint GetPIDStarterMale(ref uint seed, uint id32)
+ {
+ const byte ratio = 0x1F; // 12.5% F (can't be female)
+ while (true)
+ {
+ var a = XDRNG.Next16(ref seed);
+ var b = XDRNG.Next16(ref seed);
+ var pid = GetPIDRegular(a, b);
+ if ((pid & 0xFF) >= ratio && !ShinyUtil.GetIsShiny(id32, pid))
+ return pid;
+ }
+ }
+
+ private static uint GetPIDRegular(uint a, uint b) => a << 16 | b;
+
+ private static (uint iv1, uint iv2) GetCombinedIVs(EncounterCriteria criteria)
+ {
+ uint iv1 = (uint)criteria.IV_HP | (uint)criteria.IV_ATK << 5 | (uint)criteria.IV_DEF << 10;
+ uint iv2 = (uint)criteria.IV_SPE | (uint)criteria.IV_SPA << 5 | (uint)criteria.IV_SPD << 10;
+ return (iv1, iv2);
+ }
+
+ private static void SetIVs(G3PKM pk, uint iv1, uint iv2)
+ {
+ Span ivs = stackalloc int[6];
+ MethodFinder.GetIVsInt32(ivs, iv1, iv2);
+ pk.SetIVs(ivs);
+ }
+}