Gen3: revise PCJP internal logic

deduplicates a little; renames the PID type label to something less confusing.
This commit is contained in:
Kurt 2025-08-26 12:54:53 -05:00
parent ed419c49bd
commit 79b3bd4f74
5 changed files with 73 additions and 65 deletions

View File

@ -28,7 +28,7 @@ internal static class EncountersWC3
new(263, 05, S) { Moves = new(033,045,039,000), Language = (int)Japanese, Method = BACD_RBCD, ID32 = 21121, Shiny = Always, OriginalTrainerName = "ルビー", OriginalTrainerGender = RandD3_1 }, // Berry Fix Ruby
new(263, 05, S) { Moves = new(033,045,039,000), Language = (int)Japanese, Method = BACD_RBCD, ID32 = 21121, Shiny = Always, OriginalTrainerName = "サファイア", OriginalTrainerGender = RandD3_0 }, // Berry Fix Sapphire
new(385, 05, R) { Moves = new(273,093,156,000), Language = (int)Japanese, Method = BACD_T2, ID32 = 30719, Shiny = Never, OriginalTrainerName = "ネガイボシ", OriginalTrainerGender = Only0 }, // Negai Boshi Jirachi
new(385, 05, R) { Moves = new(273,093,156,000), Language = (int)Japanese, Method = BACD_TA, ID32 = 30719, Shiny = Never, OriginalTrainerName = "ネガイボシ", OriginalTrainerGender = Only0 }, // Negai Boshi Jirachi
new(385, 05, RS){ Moves = new(273,093,156,000), Language = (int)Japanese, Method = BACD_U_AX, ID32 = 30719, Shiny = Never, OriginalTrainerName = "ネガイボシ", OriginalTrainerGender = Recipient }, // Negai Boshi Jirachi (Match Recipient)
new(385, 05, R) { Moves = new(273,093,156,000), Language = (int)Japanese, Method = BACD_R_A, ID32 = 40707, Shiny = Never, OriginalTrainerName = "タナバタ", OriginalTrainerGender = Only1 }, // Tanabata Jirachi (2004)
new(025, 10, R) { Moves = new(019,084,039,086), Language = (int)Japanese, Method = BACD_R_A, ID32 = 41205, Shiny = Never, OriginalTrainerName = "", OriginalTrainerGender = Only0 }, // ANA Pikachu
@ -183,16 +183,16 @@ internal static class EncountersWC3
new(172, 5, Gen3, true) { Moves = new(084,204,057,000), Method = BACD_U, OriginalTrainerName = "", OriginalTrainerGender = Only1 }, // Pichu Egg with Surf
// PCJP - Pokémon Center 5th Anniversary Eggs (April 25 to May 18, 2003)
new(172, 5, R, true) { Moves = new(084,204,298,000), Method = BACD_T3, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0, Shiny = Always }, // Pichu with Teeter Dance
new(172, 5, R, true) { Moves = new(084,204,273,000), Method = BACD_T3, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0, Shiny = Always }, // Pichu with Wish
new(172, 5, R, true) { Moves = new(084,204,298,000), Method = BACD_T2, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Pichu with Teeter Dance
new(172, 5, R, true) { Moves = new(084,204,273,000), Method = BACD_T2, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Pichu with Wish
new(280, 5, R, true) { Moves = new(045,204,000,000), Method = BACD_T2, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Ralts with Charm
new(280, 5, R, true) { Moves = new(045,273,000,000), Method = BACD_T2, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Ralts with Wish
new(359, 5, R, true) { Moves = new(010,043,180,000), Method = BACD_T2, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Absol with Spite
new(359, 5, R, true) { Moves = new(010,043,273,000), Method = BACD_T2, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Absol with Wish
new(371, 5, R, true) { Moves = new(099,044,334,000), Method = BACD_T2, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Bagon with Iron Defense
new(371, 5, R, true) { Moves = new(099,044,273,000), Method = BACD_T2, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Bagon with Wish
new(172, 5, R, true) { Moves = new(084,204,298,000), Method = BACD_TS, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0, Shiny = Always }, // Pichu with Teeter Dance
new(172, 5, R, true) { Moves = new(084,204,273,000), Method = BACD_TS, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0, Shiny = Always }, // Pichu with Wish
new(172, 5, R, true) { Moves = new(084,204,298,000), Method = BACD_TA, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Pichu with Teeter Dance
new(172, 5, R, true) { Moves = new(084,204,273,000), Method = BACD_TA, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Pichu with Wish
new(280, 5, R, true) { Moves = new(045,204,000,000), Method = BACD_TA, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Ralts with Charm
new(280, 5, R, true) { Moves = new(045,273,000,000), Method = BACD_TA, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Ralts with Wish
new(359, 5, R, true) { Moves = new(010,043,180,000), Method = BACD_TA, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Absol with Spite
new(359, 5, R, true) { Moves = new(010,043,273,000), Method = BACD_TA, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Absol with Wish
new(371, 5, R, true) { Moves = new(099,044,334,000), Method = BACD_TA, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Bagon with Iron Defense
new(371, 5, R, true) { Moves = new(099,044,273,000), Method = BACD_TA, OriginalTrainerName = PCJPEggTrainerName, OriginalTrainerGender = Only0 }, // Bagon with Wish
// Distributed event gifts for receipt in the Pokémon Center 2F call the same give egg script as the Hot Springs Wynaut.
// Most often, the game has a VBlank interrupt between PID and IVs (potentially because the script does not lock when starting).

View File

@ -334,11 +334,9 @@ public static void SetValuesFromSeedChannel(PK3 pk, uint seed)
private uint GetSaneSeed(uint seed) => Method switch
{
BACD_RBCD => Math.Clamp(seed, 3, 213), // BCD digit sum
BACD_T2 when Species is (ushort)Core.Species.Jirachi
=> LCRNG.Next2(seed & 0xFFFF),
BACD_T2
=> LCRNG.Next2(PCJPFifthAnniversary.GetSeedForResult(Species, Shiny == Shiny.Always, Moves.Contains((ushort)Move.Wish), seed)),
BACD_T3
BACD_TA when Species is (ushort)Core.Species.Jirachi
=> LCRNG.Next2(seed & 0xFFFF), // table rand, but table result identical
BACD_TA or BACD_TS // shiny depends on encounter
=> LCRNG.Next2(PCJPFifthAnniversary.GetSeedForResult(Species, Shiny == Shiny.Always, Moves.Contains((ushort)Move.Wish), seed)),
BACD_M => MystryMew.GetSeed(seed),
@ -466,8 +464,8 @@ public RandomCorrelationRating IsCompatibleReviseReset(ref PIDIV value, PKM pk)
BACD_R_A => IsRestrictedAnti(ref value, type),
BACD_U_AX => IsUnrestrictedAntiX(ref value, type),
BACD_T2 => IsRestrictedTable2(ref value, type, Species, Moves.Contains((ushort)Move.Wish)),
BACD_T3 => IsRestrictedTable3(ref value, type, Species, Moves.Contains((ushort)Move.Wish)),
BACD_TA => IsRestrictedTable2(ref value, type, Species, Moves.Contains((ushort)Move.Wish)),
BACD_TS => IsRestrictedTable3(ref value, type, Species, Moves.Contains((ushort)Move.Wish)),
BACD_RBCD => IsBerryFixShiny(ref value, type),
BACD_M => IsMystryMew(ref value, type),
Channel => IsChannelJirachi(ref value, type),

View File

@ -18,55 +18,52 @@ public static class CommonEvent3Checker
/// <returns>True if is match; mutation not indicated.</returns>
public static bool IsRestrictedTable2(ref PIDIV value, PIDType observed, ushort species, bool isWish)
{
if (observed is not (BACD or BACD_A or BACD_EA))
// Same method as IsRestrictedTable3, except shiny is disallowed.
if (observed is not (BACD or BACD_A or BACD_EA)) // regular/anti-shiny, or anti-shiny before trade & hatch
return false;
var seed = value.OriginSeed;
var prev2 = LCRNG.Prev2(seed);
if (prev2 > ushort.MaxValue)
return false;
if (species is (ushort)Species.Jirachi)
return true;
var index = PCJPFifthAnniversary.GetIndexPCJP(species);
var combined = WeightedTable3.GetRandom32(prev2);
var result = PCJPFifthAnniversary.GetResultPCJP(combined);
if (result.Shiny)
return false;
if (result.Index != index)
return false;
if (result.Wish != isWish)
return false;
value = value.AsMutated(BACD_R, prev2);
return true;
return IsRestrictedTableMatch(ref value, species, isWish, false);
}
/// <remarks>Force Shiny Table constraint.</remarks>
/// <inheritdoc cref="IsRestrictedTable2"/>
public static bool IsRestrictedTable3(ref PIDIV value, PIDType observed, ushort species, bool isWish)
{
if (observed is not (BACD_S or BACD_ES))
// Same method as IsRestrictedTable2, except it should be shiny.
if (observed is not (BACD_S or BACD_ES)) // shiny or shiny before trade & hatch
return false;
var seed = value.OriginSeed;
var prev3 = LCRNG.Prev2(seed);
if (prev3 > ushort.MaxValue)
return false;
if (species is (ushort)Species.Jirachi)
return true;
return IsRestrictedTableMatch(ref value, species, isWish, true);
}
var index = PCJPFifthAnniversary.GetIndexPCJP(species);
seed = prev3;
var rand1 = LCRNG.Next16(ref seed);
var rand2 = LCRNG.Next16(ref seed);
var combined = (rand1 << 16) | rand2;
var result = PCJPFifthAnniversary.GetResultPCJP(combined);
if (!result.Shiny)
private static bool IsRestrictedTableMatch(ref PIDIV value, ushort species, bool isWish, bool isShiny)
{
var seed = value.OriginSeed;
var origin = LCRNG.Prev2(seed);
if (origin > ushort.MaxValue)
return false;
// Only 2 events distributed with table rand.
// Negai Boshi Jirachi was filled with the same entry.
// PCJP Fifth Anniversary had a few entries, will need to check.
if (species is not (ushort)Species.Jirachi)
{
// Otherwise, check the table to see if it is valid.
if (!IsMatchTableRand(species, isWish, (ushort)origin, isShiny))
return false;
}
value = value.AsMutated(BACD_R, origin);
return true;
}
private static bool IsMatchTableRand(ushort species, bool isWish, ushort seed16, bool isShiny)
{
var index = PCJPFifthAnniversary.GetIndex(species);
var result = PCJPFifthAnniversary.GetResult(seed16);
if (result.Shiny != isShiny)
return false;
if (result.Index != index)
return false;
if (result.Wish != isWish)
return false;
value = value.AsMutated(BACD_R, prev3);
return true;
}

View File

@ -8,6 +8,15 @@ public static class PCJPFifthAnniversary
private const uint MaxTableWeight = 1000;
private const uint EntryWeight = 125;
/// <summary>
/// Determines the result from the table, based on the input 16-bit random seed.
/// </summary>
public static (uint Index, bool Wish, bool Shiny) GetResult(ushort rand)
{
var u32 = WeightedTable3.GetRandom32(rand);
return GetResult(u32);
}
// Table:
// Pichu 125 Teeter Dance (100-124 shiny)
// Pichu 125 Wish (100-124 shiny)
@ -23,8 +32,13 @@ public static class PCJPFifthAnniversary
// Species index: (result / 250)
// Moveset index: (result / 125) % 2
// Shiny: if Pichu, (result % 125) >= 100
public static (uint Index, bool Wish, bool Shiny) GetResultPCJP(uint rand)
/// <summary>
/// Determines the result from the table, based on the input 32-bit random value.
/// </summary>
private static (uint Index, bool Wish, bool Shiny) GetResult(uint rand)
{
// Reduce the weight to a random number in the range of the table weight.
var result = WeightedTable3.GetPeriodicWeight(rand, MaxTableWeight);
var eighth = result / EntryWeight;
var wish = (eighth & 1) == 1;
@ -38,7 +52,7 @@ public static (uint Index, bool Wish, bool Shiny) GetResultPCJP(uint rand)
/// </summary>
/// <param name="species">Species ID</param>
/// <returns>0-3</returns>
public static uint GetIndexPCJP(ushort species)
public static uint GetIndex(ushort species)
{
// Pichu: 172 = 0_10_10_11_00
// Bagon: 371 = 1_01_11_00_11
@ -54,21 +68,20 @@ public static uint GetIndexPCJP(ushort species)
/// </summary>
public static bool IsMatch(ushort species, bool shiny, bool wish, uint rand)
{
var index = GetIndexPCJP(species);
var result = GetResultPCJP(rand);
var index = GetIndex(species);
var result = GetResult(rand);
return index == result.Index && wish == result.Wish && shiny == result.Shiny;
}
/// <summary>
/// Gets a random 16-bit seed that will return the desired table result.
/// </summary>
public static uint GetSeedForResult(ushort species, bool shiny, bool wish, uint seed)
public static ushort GetSeedForResult(ushort species, bool shiny, bool wish, uint seed)
{
while (true)
{
var u16 = seed & 0xFFFF; // restricted
var u32 = WeightedTable3.GetRandom32(u16);
if (IsMatch(species, shiny, wish, u32))
var u16 = (ushort)seed; // restricted
if (IsMatch(species, shiny, wish, u16))
return u16;
seed = LCRNG.Next(seed);
}

View File

@ -83,10 +83,10 @@ public enum PIDType : byte
BACD_R_A,
/// <summary> Event Reversed Order PID restricted to [0,213] Origin Seed, shiny </summary>
BACD_RBCD,
/// <summary> Event Reversed Order PID with Origin Seed restrictions, consuming 2 calls to the RNG before the PID is generated. </summary>
BACD_T2,
/// <summary> Event Reversed Order PID with Origin Seed restrictions, consuming 3 calls to the RNG before the PID is generated. </summary>
BACD_T3,
/// <summary> Event Reversed Order PID restricted to 16bit Origin Seed, consuming 2 calls select the event gift index, anti-shiny. </summary>
BACD_TA,
/// <summary> Event Reversed Order PID restricted to 16bit Origin Seed, consuming 2 calls select the event gift index, force-shiny. </summary>
BACD_TS,
/// <summary> Event Reversed Order PID with Origin Seed restrictions, only using the Mystry Mew table. </summary>
BACD_M,