mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-29 23:40:27 +00:00
Add Channel Jirachi menu pattern verification
Sitting on my todo-list for far too long see pokefinder for similar implementation PKHeX has a forward & reverse implementation available for 100% documentation purposes :) The seed indicated in hover previews now matches that of the true Origin seed for the encounter (pre menu & accept dialog), similar to Method H/J/K encounters. Co-Authored-By: Admiral-Fish <24730718+Admiral-Fish@users.noreply.github.com>
This commit is contained in:
parent
dbed62b314
commit
2ff2815dbe
4 changed files with 246 additions and 0 deletions
|
@ -48,6 +48,16 @@ public sealed class EncounterGenerator3 : IEncounterGenerator
|
||||||
}
|
}
|
||||||
if (e is not EncounterSlot3 slot)
|
if (e is not EncounterSlot3 slot)
|
||||||
{
|
{
|
||||||
|
if (e is WC3 { TID16: 40122 } channel)
|
||||||
|
{
|
||||||
|
var chk = ChannelJirachi.GetPossible(info.PIDIV.OriginSeed);
|
||||||
|
if (chk.Pattern is not ChannelJirachiRandomResult.None)
|
||||||
|
info.PIDIV = info.PIDIV.AsEncounteredVia(new(chk.Seed, LeadRequired.None));
|
||||||
|
else
|
||||||
|
info.ManualFlag = EncounterYieldFlag.InvalidPIDIV;
|
||||||
|
yield return channel;
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
yield return e;
|
yield return e;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,17 @@ public static class XDRNG
|
||||||
return (seed >> 16) & 0x7FFF;
|
return (seed >> 16) & 0x7FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the previous 16 bits of the previous RNG seed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seed">Seed to advance one step.</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static uint Prev16(ref uint seed)
|
||||||
|
{
|
||||||
|
seed = Prev(seed);
|
||||||
|
return seed >> 16;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Advances the RNG seed to the next state value a specified amount of times.
|
/// Advances the RNG seed to the next state value a specified amount of times.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
224
PKHeX.Core/Legality/RNG/ClassicEra/Gen3/ChannelJirachi.cs
Normal file
224
PKHeX.Core/Legality/RNG/ClassicEra/Gen3/ChannelJirachi.cs
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace PKHeX.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logic for Channel Jirachi RNG seeds prior to PID/IV generation.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>An arbitrary PID/IV seed is valid about 18% of the time.</remarks>
|
||||||
|
public static class ChannelJirachi
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the seed that immediately generates the PID/IV is valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seed">Seed that generates the PID/IV</param>
|
||||||
|
/// <returns>True if valid.</returns>
|
||||||
|
public static bool IsPossible(uint seed) => GetPossible(seed).Pattern != ChannelJirachiRandomResult.None;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any of the 3 Jirachi float patterns are possible.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seed">Seed that generates the PID/IV</param>
|
||||||
|
/// <returns>First possible float pattern and origin seed (pre-menu).</returns>
|
||||||
|
public static (ChannelJirachiRandomResult Pattern, uint Seed) GetPossible(uint seed)
|
||||||
|
{
|
||||||
|
// Jirachi floating into the screen has 3 different patterns to follow
|
||||||
|
// Check each of them
|
||||||
|
for (uint i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
var origin = seed;
|
||||||
|
if (!IsValid(ref origin, i))
|
||||||
|
continue;
|
||||||
|
return ((ChannelJirachiRandomResult)(i + 1), origin);
|
||||||
|
}
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the seed is valid for the Jirachi float pattern type AND menu pattern.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="origin">Seed that generated the PID/IV, to be reversed into the true origin seed.</param>
|
||||||
|
/// <param name="possibleBranch"><see cref="ChannelJirachiRandomResult"/></param>
|
||||||
|
/// <returns>True if valid.</returns>
|
||||||
|
public static bool IsValid(ref uint origin, uint possibleBranch)
|
||||||
|
{
|
||||||
|
if (!IsValidAccept(ref origin, possibleBranch))
|
||||||
|
return false;
|
||||||
|
if (!IsValidMenu(ref origin))
|
||||||
|
return false;
|
||||||
|
origin = XDRNG.Prev(origin); // reverse to the seed before the menu pattern
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the seed is valid for the Accept random chances.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seed">Seed that generated the PID/IV, to be reversed to the post-menu seed.</param>
|
||||||
|
/// <param name="acceptPivot">Float pattern (0-2, not 1-3).</param>
|
||||||
|
public static bool IsValidAccept(ref uint seed, uint acceptPivot)
|
||||||
|
{
|
||||||
|
// The game checks for two random results (25% and 33%).
|
||||||
|
// If either passes, we advance once, otherwise we advance twice.
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(acceptPivot, 3u, nameof(acceptPivot));
|
||||||
|
|
||||||
|
// 71.9% chance of passing at least one of the following branches.
|
||||||
|
// 28.1% chance of failing all branches.
|
||||||
|
|
||||||
|
if (acceptPivot == 0) // 8 advances total
|
||||||
|
{
|
||||||
|
// 2: 25% fails, 33% fails. 2 advances. ~50.25% chance of success.
|
||||||
|
seed = XDRNG.Prev2(seed); // 2
|
||||||
|
if (XDRNG.Prev16(ref seed) <= 0x547A) // 33% should fail
|
||||||
|
return false;
|
||||||
|
if (XDRNG.Prev16(ref seed) <= 0x4000) // 25% should fail
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (acceptPivot == 1) // 6 advances total
|
||||||
|
{
|
||||||
|
// 0: 25% passes, 33% unchecked. 1 advance. 25% chance of success.
|
||||||
|
seed = XDRNG.Prev(seed); // 1
|
||||||
|
if (XDRNG.Prev16(ref seed) > 0x4000) // 25% should pass
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else // 7 advances total
|
||||||
|
{
|
||||||
|
// 25% fails, 33% passes. 1 advance. 24.75% chance of success.
|
||||||
|
seed = XDRNG.Prev(seed); // 1
|
||||||
|
if (XDRNG.Prev16(ref seed) > 0x547A) // 33% should pass
|
||||||
|
return false;
|
||||||
|
if (XDRNG.Prev16(ref seed) <= 0x4000) // 25% should fail
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
seed = XDRNG.Prev4(seed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the seed is valid for the Jirachi menu pattern.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seed">Already reversed to the seed immediately after the menu pattern, to be reversed to the origin seed.</param>
|
||||||
|
/// <returns>True if valid.</returns>
|
||||||
|
public static bool IsValidMenu(ref uint seed)
|
||||||
|
{
|
||||||
|
// 25% chance of being an impossible seed (0 as the final result).
|
||||||
|
// 2/3 chance of being an impossible seed (end value reoccurs before the other two values are seen).
|
||||||
|
// 25% chance of a seed being valid.
|
||||||
|
|
||||||
|
// From the starting seed, the game determines the order of [1,2,3] by looping until all 3 are generated via (seed >> 30).
|
||||||
|
// Track which values we've seen. If we've not yet seen the result, add it to the bitmask.
|
||||||
|
var end = seed >> 30;
|
||||||
|
// Menu can't end on a 0; we expect 1-3.
|
||||||
|
if (end == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Start with the final result.
|
||||||
|
var seen = 1u << (int)end;
|
||||||
|
|
||||||
|
// Iterate backwards until all 3 results are observed.
|
||||||
|
// If we encounter the ending result again, then the seed isn't legal -- a duplicate end value would be impossible to stop on.
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
seed = XDRNG.Prev(seed);
|
||||||
|
var pattern = seed >> 30; // 0-3
|
||||||
|
if (pattern == end)
|
||||||
|
return false; // Duplicate. Input seed cannot be landed on.
|
||||||
|
|
||||||
|
// Ignore skipping 0; add it to the bitmask anyway (reduce branching).
|
||||||
|
seen |= 1u << (int)pattern;
|
||||||
|
if (seen >= BitPattern123) // only true if 1-3 bits set. 0th bit irrelevant.
|
||||||
|
return true; // Seen all! seed is legal.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const byte BitPattern123 = 0b1110; // ignore bit 0; we need to see bits 1-3
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the Menu pattern calculation forward to see which pattern order is generated.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seed">Initial seed (not yet advanced once).</param>
|
||||||
|
/// <returns>Order and final seed.</returns>
|
||||||
|
public static (byte First, byte Second, byte Third, uint EndSeed) GetMenuPattern(uint seed)
|
||||||
|
{
|
||||||
|
Span<byte> pattern = stackalloc byte[3];
|
||||||
|
var result = GetMenuPattern(seed, pattern);
|
||||||
|
return (pattern[0], pattern[1], pattern[2], result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the Menu pattern calculation forward to see which pattern order is generated.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seed">Initial seed (not yet advanced once).</param>
|
||||||
|
/// <param name="pattern">Result to store the order.</param>
|
||||||
|
/// <returns>Final seed.</returns>
|
||||||
|
public static uint GetMenuPattern(uint seed, [Length(3,3)] Span<byte> pattern)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
while (i != pattern.Length)
|
||||||
|
{
|
||||||
|
seed = XDRNG.Next(seed);
|
||||||
|
var p = (byte)(seed >> 30);
|
||||||
|
if (pattern.Contains(p))
|
||||||
|
continue; // 0 will always return true, yay zero initialized arrays!
|
||||||
|
pattern[i++] = p;
|
||||||
|
}
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skips from origin to the PID/IV generation seed.
|
||||||
|
/// </summary>
|
||||||
|
public static uint SkipToPIDIV(uint seed)
|
||||||
|
{
|
||||||
|
seed = SkipMenuPattern(seed);
|
||||||
|
seed = SkipAccept(seed);
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skips the menu pattern calculation to the seed that is used by the Accept step.
|
||||||
|
/// </summary>
|
||||||
|
public static uint SkipMenuPattern(uint seed)
|
||||||
|
{
|
||||||
|
uint seen = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
seed = XDRNG.Next(seed);
|
||||||
|
var p = (byte)(seed >> 30);
|
||||||
|
seen |= 1u << p;
|
||||||
|
if (seen >= BitPattern123) // only true if 1-3 bits set. 0th bit irrelevant.
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skips the 25% and 33% calculation done when accepting the Jirachi to the end seed that then can be used to generate the PID/IV.
|
||||||
|
/// </summary>
|
||||||
|
public static uint SkipAccept(uint seed)
|
||||||
|
{
|
||||||
|
seed = XDRNG.Next4(seed);
|
||||||
|
|
||||||
|
// If we pass either a 25% or 33% check, we advance once.
|
||||||
|
// Each check is independent of the other, using a different random value.
|
||||||
|
var rand = XDRNG.Next16(ref seed);
|
||||||
|
if (rand <= 0x4000) // 25%
|
||||||
|
return XDRNG.Next(seed);
|
||||||
|
|
||||||
|
rand = XDRNG.Next16(ref seed);
|
||||||
|
if (rand <= 0x547A) // 33%
|
||||||
|
return XDRNG.Next(seed);
|
||||||
|
|
||||||
|
// Both failed, advance twice.
|
||||||
|
return XDRNG.Next2(seed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ChannelJirachiRandomResult : byte
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
// Ordered by most likely to occur.
|
||||||
|
BothFail = 1, // 50.25%
|
||||||
|
FirstPass = 2, // 25%
|
||||||
|
SecondPass = 3, // 24.75%
|
||||||
|
}
|
|
@ -210,6 +210,7 @@ public sealed class WC3(GameVersion Version, bool Fateful = false)
|
||||||
{
|
{
|
||||||
PIDType.BACD_R => seed & 0x0000FFFF, // u16
|
PIDType.BACD_R => seed & 0x0000FFFF, // u16
|
||||||
PIDType.BACD_R_S => seed & 0x000000FF, // u8
|
PIDType.BACD_R_S => seed & 0x000000FF, // u8
|
||||||
|
PIDType.Channel => ChannelJirachi.SkipToPIDIV(seed),
|
||||||
_ => seed,
|
_ => seed,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue