Handle radar slot bypassing / chain shiny

This commit is contained in:
Kurt 2024-03-09 18:13:09 -06:00
parent 90cf6cff84
commit 30d85c95a0
9 changed files with 153 additions and 47 deletions

View file

@ -147,10 +147,13 @@ public static class LegalityFormatting
{
if (lead is LeadRequired.Invalid)
return "❌";
var (ability, isFail) = lead.GetDisplayAbility();
var (ability, isFail, condition) = lead.GetDisplayAbility();
var abilities = GameInfo.Strings.Ability;
var name = abilities[(int)ability];
return isFail ? string.Format(L_F0_1, name, "❌") : name;
var result = isFail ? string.Format(L_F0_1, name, "❌") : name;
if (condition != EncounterTriggerCondition.None)
result += $"-{condition}";
return result;
}
public static string GetEncounterName(this IEncounterable enc)

View file

@ -179,7 +179,7 @@ public static class MethodH
return IsRockSmashPossible(enc.AreaRate, ref result.Seed);
if (enc.Type.IsFishingRodType())
return true; // can just wait and trigger after hooking.
// Can sweet scent trigger.
return true;
}

View file

@ -148,7 +148,7 @@ public static class MethodJ
/// <summary>
/// Attempts to find a matching seed for the given encounter and constraints for Cute Charm buffered PIDs.
/// </summary>
public static bool TryGetMatchCuteCharm<T>(T enc, ReadOnlySpan<uint> seeds, byte nature, byte levelMin, byte levelMax, byte format, out uint result)
public static bool TryGetMatchCuteCharm<T>(T enc, ReadOnlySpan<uint> seeds, byte nature, byte levelMin, byte levelMax, byte format, out LeadSeed result)
where T : IEncounterSlot4
{
foreach (uint seed in seeds)
@ -158,15 +158,35 @@ public static class MethodJ
if (!reg)
continue;
var ctx = new FrameCheckDetails<T>(enc, seed, levelMin, levelMax, format);
if (!TryGetMatchCuteCharm(ctx, out result))
if (!TryGetMatchCuteCharm(ctx, out var s))
continue;
if (!CheckEncounterActivation(enc, ref result))
if (!CheckEncounterActivation(enc, ref s))
continue;
result = new(s, CuteCharm);
return true;
}
if (CanRadar(enc))
{
foreach (uint seed in seeds)
{
var p0 = seed >> 16; // 0
var reg = GetNature(p0) == nature;
if (!reg)
continue;
var ctx = new FrameCheckDetails<T>(enc, seed, levelMin, levelMax, format);
if (IsCuteCharmFail(ctx.Prev1))
continue;
result = new(ctx.Seed2, CuteCharmRadar);
return true;
}
}
result = default; return false;
}
private static bool CanRadar<T>(T enc) where T : IEncounterSlot4 => enc is EncounterSlot4 { Type: Grass, CanUseRadar: true };
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryGetMatch<T>(T enc, byte levelMin, byte levelMax, uint seed, byte nature, byte format, out LeadSeed result)
where T : IEncounterSlot4
@ -187,6 +207,11 @@ public static class MethodJ
result = new(seed, Synchronize);
return true;
}
if (CanRadar(enc))
{
result = new(ctx.Seed1, SynchronizeRadar);
return true;
}
}
result = default;
return false;
@ -261,6 +286,9 @@ public static class MethodJ
if (IsSlotValidIntimidate(ctx, out seed))
{ result = new(seed, IntimidateKeenEyeFail); return true; }
if (CanRadar(ctx.Encounter))
{ result = new(ctx.Seed1, Radar); return true; }
result = default; return false;
}

View file

@ -134,7 +134,7 @@ public static class MethodK
/// <summary>
/// Attempts to find a matching seed for the given encounter and constraints for Cute Charm buffered PIDs.
/// </summary>
public static bool TryGetMatchCuteCharm<T>(T enc, ReadOnlySpan<uint> seeds, byte nature, byte levelMin, byte levelMax, byte format, out uint result)
public static bool TryGetMatchCuteCharm<T>(T enc, ReadOnlySpan<uint> seeds, byte nature, byte levelMin, byte levelMax, byte format, out LeadSeed result)
where T : IEncounterSlot4
{
foreach (uint seed in seeds)
@ -144,10 +144,13 @@ public static class MethodK
if (!reg)
continue;
var ctx = new FrameCheckDetails<T>(enc, seed, levelMin, levelMax, format);
if (!TryGetMatchCuteCharm(ctx, out result))
if (!TryGetMatchCuteCharm(ctx, out var s))
continue;
if (CheckEncounterActivationCuteCharm(enc, ref result))
return true;
if (!CheckEncounterActivationCuteCharm(enc, ref s))
continue;
result = new(s, CuteCharm);
return true;
}
result = default; return false;
}

View file

@ -33,11 +33,33 @@ public static class LeadFinder
where TEvo : ILevelRange
{
var type = pv.Type;
if (type is Method_1)
if (type is ChainShiny)
{
if (pk is { IsShiny: true, HGSS: false } && enc is EncounterSlot4 { CanUseRadar: true })
{
// The Chain Shiny generator can loop for Cute Charm and Synchronize, but all they do is skip until satisfied.
// Any result can be encountered without a lead by triggering on the nearest frame.
return new(pv.OriginSeed, LeadRequired.None);
}
// There's a very-very rare chance that the PID-IV can be from Cute Charm too.
// It may match Method 1, but since we early-return, we don't check for Cute Charm.
// So, we check for Cute Charm here and try checking Cute Charm frames if it matches.
if (MethodFinder.IsCuteCharm(pk, pk.EncryptionConstant))
type = CuteCharm;
}
else if (type is Method_1)
{
if (TryGetLeadInfo4(enc, evo, pk.HGSS, pv.OriginSeed, pk.Format, out var result))
return result;
// Off chance that it is a radar shiny...
if (pk is { IsShiny: true, HGSS: false } && enc is EncounterSlot4 { CanUseRadar: true })
{
if (TryGetMatchRadarShiny(pk, out result))
return result;
}
// There's a very-very rare chance that the PID-IV can be from Cute Charm too.
// It may match Method 1, but since we early-return, we don't check for Cute Charm.
// So, we check for Cute Charm here and try checking Cute Charm frames if it matches.
@ -48,13 +70,33 @@ public static class LeadFinder
{
// Needs to fetch all possible seeds for IVs.
// Kinda sucks to do this every encounter, but it's relatively rare -- still good enough perf.
var result = TryGetMatchCuteCharm4(enc, pk, evo, pk.Format, out var seed);
if (result)
return new(seed, LeadRequired.CuteCharm);
if (TryGetMatchCuteCharm4(enc, pk, evo, pk.Format, out var result))
return result;
}
return default;
}
private static bool TryGetMatchRadarShiny(PKM pk, out LeadSeed result)
{
// Can be one of many seeds. Don't trust the initial assumed seed.
Span<uint> seeds = stackalloc uint[LCRNG.MaxCountSeedsIV];
var ctr = GetSeedsIVs(pk, seeds);
seeds = seeds[..ctr];
var ec = pk.EncryptionConstant;
foreach (var seed in seeds)
{
if (!MethodFinder.IsChainShinyValid(pk, ec, seed, out var s))
continue;
// The Chain Shiny generator can loop for Cute Charm and Synchronize, but all they do is skip until satisfied.
// Any result can be encountered without a lead by triggering on the nearest frame.
result = new(s, LeadRequired.None);
}
result = default;
return false;
}
/// <summary>
/// Tries to get the lead information for a Generation 4 encounter.
/// </summary>
@ -69,7 +111,7 @@ public static class LeadFinder
return result.IsValid();
}
private static bool TryGetMatchCuteCharm4<TEnc, TEvo>(TEnc enc, PKM pk, TEvo evo, byte format, out uint seed)
private static bool TryGetMatchCuteCharm4<TEnc, TEvo>(TEnc enc, PKM pk, TEvo evo, byte format, out LeadSeed result)
where TEnc : IEncounterSlot4
where TEvo : ILevelRange
{
@ -81,8 +123,8 @@ public static class LeadFinder
var nature = (byte)(pk.EncryptionConstant % 25);
var (min, max) = (evo.LevelMin, evo.LevelMax);
return pk.HGSS
? MethodK.TryGetMatchCuteCharm(enc, seeds, nature, min, max, format, out seed)
: MethodJ.TryGetMatchCuteCharm(enc, seeds, nature, min, max, format, out seed);
? MethodK.TryGetMatchCuteCharm(enc, seeds, nature, min, max, format, out result)
: MethodJ.TryGetMatchCuteCharm(enc, seeds, nature, min, max, format, out result);
}
private static int GetSeedsIVs(PKM pk, Span<uint> seeds)

View file

@ -30,5 +30,5 @@ public struct LeadSeed(uint Seed, LeadRequired Lead)
/// <summary>
/// Prefers the lead with the most likely value (lowest value).
/// </summary>
public readonly bool IsBetterThan(in LeadSeed other) => Lead > other.Lead;
public readonly bool IsBetterThan(LeadSeed other) => Lead > other.Lead;
}

View file

@ -43,10 +43,23 @@ public enum LeadRequired : byte
/// <summary> <see cref="Ability.Synchronize"/> </summary>
Synchronize,
/// <summary> Higher display priority for radar-only encounters. </summary>
CuteCharmRadar,
/// <summary> Higher display priority for radar-only encounters. </summary>
SynchronizeRadar,
/// <summary> Higher display priority for radar-only encounters. </summary>
Radar,
/// <summary> No Lead ability effect is present, or is not checked for this type of frame. </summary>
None = byte.MaxValue,
}
public enum EncounterTriggerCondition : byte
{
None,
Radar,
}
public static class LeadRequiredExtensions
{
public static bool IsFailTuple(this LeadRequired lr) => lr
@ -74,10 +87,13 @@ public static class LeadRequiredExtensions
SuctionCups => Ability.SuctionCups,
Illuminate => Ability.Illuminate,
IntimidateKeenEyeFail => Ability.Intimidate,
SynchronizeRadar => Ability.Synchronize,
CuteCharmRadar => Ability.CuteCharm,
_ => Ability.None,
};
public static (Ability Ability, bool IsFail) GetDisplayAbility(this LeadRequired lr)
public static (Ability Ability, bool IsFail, EncounterTriggerCondition Condition) GetDisplayAbility(this LeadRequired lr)
{
var isFail = false;
if (lr.IsFailTuple())
@ -89,6 +105,14 @@ public static class LeadRequiredExtensions
{
isFail = true;
}
return (lr.GetAbility(), isFail);
var condition = lr switch
{
Radar => EncounterTriggerCondition.Radar,
SynchronizeRadar => EncounterTriggerCondition.Radar,
CuteCharmRadar => EncounterTriggerCondition.Radar,
_ => EncounterTriggerCondition.None,
};
return (lr.GetAbility(), isFail, condition);
}
}

View file

@ -469,7 +469,7 @@ public static class MethodFinder
return false;
}
private static bool GetChainShinyMatch(Span<uint> seeds, PKM pk, uint pid, ReadOnlySpan<uint> IVs, out PIDIV pidiv)
private static bool GetChainShinyMatch(Span<uint> seeds, ITrainerID32 pk, uint pid, ReadOnlySpan<uint> IVs, out PIDIV pidiv)
{
// 13 shiny bits
// PIDH & 7
@ -482,38 +482,45 @@ public static class MethodFinder
var reg = seeds[..count];
foreach (var seed in reg)
{
// check the individual bits
var s = seed;
int i = 15;
do
{
var bit = s >> 16 & 1;
if (bit != (pid >> i & 1))
break;
s = LCRNG.Prev(s);
}
while (--i != 2);
if (i != 2) // bit failed
if (!IsChainShinyValid(pk, pid, seed, out uint s))
continue;
// Shiny Bits of PID validated
var upper = s;
if ((upper >> 16 & 7) != (pid >> 16 & 7))
continue;
var lower = LCRNG.Prev(upper);
if ((lower >> 16 & 7) != (pid & 7))
continue;
var upid = (((pid & 0xFFFF) ^ pk.TID16 ^ pk.SID16) & 0xFFF8) | ((upper >> 16) & 0x7);
if (upid != pid >> 16)
continue;
s = LCRNG.Prev2(lower); // unroll one final time to get the origin seed
pidiv = new PIDIV(ChainShiny, s);
return true;
}
return GetNonMatch(out pidiv);
}
public static bool IsChainShinyValid(ITrainerID32 pk, uint pid, uint seed, out uint s)
{
// check the individual bits
s = seed;
int i = 15;
do
{
var bit = s >> 16 & 1;
if (bit != (pid >> i & 1))
break;
s = LCRNG.Prev(s);
}
while (--i != 2);
if (i != 2) // bit failed
return false;
// Shiny Bits of PID validated
var upper = s;
if ((upper >> 16 & 7) != (pid >> 16 & 7))
return false;
var lower = LCRNG.Prev(upper);
if ((lower >> 16 & 7) != (pid & 7))
return false;
var upid = (((pid & 0xFFFF) ^ pk.TID16 ^ pk.SID16) & 0xFFF8) | ((upper >> 16) & 0x7);
if (upid != pid >> 16)
return false;
s = LCRNG.Prev2(lower); // unroll one final time to get the origin seed
return true;
}
private static bool GetBACDMatch(Span<uint> seeds, PKM pk, uint pid, ReadOnlySpan<uint> IVs, out PIDIV pidiv)
{
var bot = GetIVChunk(IVs[..3]) << 16;

View file

@ -230,7 +230,6 @@ public partial class SAV_HallOfFame : Form
OriginalTrainerGender = (uint)EntityGender.GetFromString(Label_OTGender.Text) & 1,
};
offset = index * StructureSize;
uint vnd = 0;