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 sealed record EncounterGift3Colo : IEncounterable, IEncounterMatch, IEnco 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 @@ public sealed record EncounterGift3Colo : IEncounterable, IEncounterMatch, IEnco 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 @@ public sealed record EncounterGift3Colo : IEncounterable, IEncounterMatch, IEnco } #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 @@ public sealed record EncounterShadow3Colo(byte ID, short Gauge, ReadOnlyMemory 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 sealed record EncounterStatic3XD(ushort Species, byte Level) 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 @@ public sealed record EncounterStatic3XD(ushort Species, byte Level) 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); + } +}