Gen3: encounter legality tweaks

Gen3-5: Egg (breeding) requesting shiny now loops to match criteria.
Gen3: Add evolution nickname bypass for trash byte application
Gen3: Fix japanese OT prefill check from not actually working
Gen3: Add missing Voltorb encounter (was superseded by Wild slot, but aggressive checks since added).
This commit is contained in:
Kurt
2026-04-22 23:37:11 -05:00
parent 8a1c364207
commit 3d7a371395
5 changed files with 31 additions and 10 deletions

View File

@@ -61,6 +61,7 @@ private static EncounterArea3[] GetSwarm([ConstantExpected] string resource, [Le
// Stationary
new(352, 30, RSE) { Location = 034 }, // Kecleon @ Route 119
new(352, 30, RSE) { Location = 035 }, // Kecleon @ Route 120
new(100, 25, RSE) { Location = 062 }, // Voltorb @ New Mauville
// Stationary Lengendary
new(377, 40, RSE) { Location = 082 }, // Regirock @ Desert Ruins

View File

@@ -79,14 +79,14 @@ public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
// Get a random PID that matches gender/nature/ability criteria
var pi = PersonalTable.E[Species];
var gr = pi.Gender;
var pid = GetRandomPID(criteria, gr);
var pid = GetRandomPID(criteria, gr, tr.ID32);
pk.PID = pid;
pk.RefreshAbility((int)(pid % 2));
return pk;
}
private uint GetRandomPID(in EncounterCriteria criteria, byte gr)
private uint GetRandomPID(in EncounterCriteria criteria, byte gr, uint id32)
{
var seed = Util.Rand32();
while (true)
@@ -109,6 +109,10 @@ private uint GetRandomPID(in EncounterCriteria criteria, byte gr)
if (!Daycare3.IsValidProcPID(pid, Version))
continue; // 0-value PID is invalid
var shiny = ShinyUtil.GetIsShiny3(id32, pid);
if (criteria.Shiny.IsShiny() != shiny)
continue;
return pid;
}
}

View File

@@ -84,7 +84,7 @@ public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
// Get a random PID that matches gender/nature/ability criteria
var pi = PersonalTable.HGSS[Species];
var gr = pi.Gender;
var pid = GetRandomPID(criteria, gr, out var gender);
var pid = GetRandomPID(criteria, gr, tr.ID32, out var gender);
pk.PID = pid;
pk.Gender = gender;
pk.RefreshAbility((int)(pid & 1));
@@ -92,7 +92,7 @@ public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
return pk;
}
private uint GetRandomPID(in EncounterCriteria criteria, byte gr, out byte gender)
private uint GetRandomPID(in EncounterCriteria criteria, byte gr, uint id32, out byte gender)
{
var seed = Util.Rand32();
while (true)
@@ -115,6 +115,10 @@ private uint GetRandomPID(in EncounterCriteria criteria, byte gr, out byte gende
// PID is rolled forward upon picking up the egg.
// Not worth skipping 0-value PIDs. Too rare to be worth trying again, since it can be a valid PID.
var shiny = ShinyUtil.GetIsShiny3(id32, pid);
if (criteria.Shiny.IsShiny() != shiny)
continue;
return pid;
}
}

View File

@@ -72,7 +72,7 @@ public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
var pi = PersonalTable.B2W2[Species];
var gr = pi.Gender;
var ability = criteria.GetAbilityFromNumber(Ability);
var pid = GetRandomPID(criteria, gr, out var gender);
var pid = GetRandomPID(criteria, gr, tr.ID32, out var gender);
pid = (pid & 0xFFFEFFFFu) | (uint)(ability & 1) << 16; // 0x00000000 or 0x00010000
pk.PID = pid;
pk.Gender = gender;
@@ -81,7 +81,7 @@ public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
return pk;
}
private static uint GetRandomPID(in EncounterCriteria criteria, byte gr, out byte gender)
private static uint GetRandomPID(in EncounterCriteria criteria, byte gr, uint id32, out byte gender)
{
var seed = Util.Rand32();
while (true)
@@ -91,6 +91,9 @@ private static uint GetRandomPID(in EncounterCriteria criteria, byte gr, out byt
gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(gender))
continue;
var shiny = ShinyUtil.GetIsShiny3(id32, pid);
if (criteria.Shiny.IsShiny() != shiny)
continue;
return pid;
}
}

View File

@@ -139,7 +139,11 @@ private static void FlagIsNicknameClean(LegalityAnalysis data, PK3 pk)
// International games are 10 chars (full buffer) max; implicit terminator if full.
var nick = pk.GetNicknamePrefillRegion();
if (!TrashByteRules3.IsTerminatedFF(nick))
data.AddLine(GetInvalid(Trainer, TrashBytesMismatchInitial));
{
// Trade to another language and evolve will treat it like a nickname, without actually filling with FF.
if (!TrashByteRules3.IsTerminatedFFZero(nick) || pk.Species == data.EncounterOriginal.Species) // not evolved
data.AddLine(GetInvalid(Trainer, TrashBytesMismatchInitial));
}
}
}
@@ -192,9 +196,15 @@ public static bool IsTerminatedFF(ReadOnlySpan<byte> data)
var first = TrashBytes8.GetTerminatorIndex(data);
if (first == -1 || first >= data.Length - 1)
return true;
return !data[(first + 1)..].ContainsAnyExcept<byte>(0xFF);
return !data[(first + 1)..].ContainsAnyExcept(Terminator);
}
/// <summary>
/// Checks if the <see cref="data"/> matches the pattern of a pre-filled array with terminators of count <see cref="preFill"/>.
/// </summary>
/// <param name="data"></param>
/// <param name="preFill"></param>
/// <returns></returns>
public static bool IsTerminatedFFZero(ReadOnlySpan<byte> data, int preFill = 0)
{
if (preFill == 0)
@@ -211,8 +221,7 @@ public static bool IsTerminatedFFZero(ReadOnlySpan<byte> data, int preFill = 0)
if (inner.ContainsAnyExcept(Terminator))
return false;
first = preFill;
first++;
if (first >= data.Length - 1)
if (first >= data.Length)
return true;
}
return !data[first..].ContainsAnyExcept<byte>(0);