mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-10 06:34:19 +00:00
Handle radar slot bypassing / chain shiny
This commit is contained in:
parent
90cf6cff84
commit
30d85c95a0
9 changed files with 153 additions and 47 deletions
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -230,7 +230,6 @@ public partial class SAV_HallOfFame : Form
|
|||
OriginalTrainerGender = (uint)EntityGender.GetFromString(Label_OTGender.Text) & 1,
|
||||
};
|
||||
|
||||
|
||||
offset = index * StructureSize;
|
||||
|
||||
uint vnd = 0;
|
||||
|
|
Loading…
Reference in a new issue