Generator: Gen3/4 slots w/ IVs & not-min level

Previously would only generate with minimum levels when IVs are provided
Now that it generates not-min levels, need to set the correct level.

Might be possible in the future to have IV-spec also respect a Level request, but for now it's only forceMin which nobody currently uses (no usage of the setter).
This commit is contained in:
Kurt 2024-11-18 18:56:18 -06:00
parent 5c338f5f74
commit 52351bf0e7
7 changed files with 135 additions and 41 deletions

View file

@ -48,7 +48,7 @@ public static class GenerateMethodH
if (!criteria.IsGenderSatisfied(gender))
break; // try again
pk.MetLevel = pk.CurrentLevel = (byte)((lv % (enc.LevelMax - enc.LevelMin + 1)) + enc.LevelMin);
pk.MetLevel = pk.CurrentLevel = (byte)MethodH.GetRandomLevel(enc, lv, LeadRequired.None);
SetPIDIVSequential(pk, pid, seed);
return;
}
@ -120,10 +120,18 @@ public static class GenerateMethodH
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (!criteria.IsGenderSatisfied(gender))
continue;
var lead = MethodH.GetSeed(enc, seed, enc, pk.E, gender, 3);
var lead = MethodH.GetSeed(enc, seed, pk.E, gender, criteria.ForceMinLevelRange);
if (!lead.IsValid()) // Verifies the slot, (min) level, and nature loop; if it passes, apply the details.
continue;
// always level rand
{
var lv = MethodH.SkipToLevelRand(enc, lead.Seed) >> 16;
var actual = MethodH.GetRandomLevel(enc, lv, lead.Lead);
if (pk.MetLevel != actual)
pk.MetLevel = pk.CurrentLevel = (byte)actual;
}
pk.PID = pid;
pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF);
pk.RefreshAbility((int)(pid & 1));
@ -147,10 +155,18 @@ public static class GenerateMethodH
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (!criteria.IsGenderSatisfied(gender))
continue;
var lead = MethodH.GetSeed(enc, seed, enc, pk.E, gender, 3);
var lead = MethodH.GetSeed(enc, seed, pk.E, gender, criteria.ForceMinLevelRange);
if (!lead.IsValid()) // Verifies the slot and nature loop; if it passes, apply the details.
continue;
// always level rand
{
var lv = MethodH.SkipToLevelRand(enc, lead.Seed) >> 16;
var actual = MethodH.GetRandomLevel(enc, lv, lead.Lead);
if (pk.MetLevel != actual)
pk.MetLevel = pk.CurrentLevel = (byte)actual;
}
pk.PID = pid;
pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF);
pk.RefreshAbility((int)(pid & 1));
@ -187,10 +203,11 @@ public static class GenerateMethodH
continue;
seed = LCRNG.Prev(seed);
}
var lead = MethodH.GetSeed(enc, seed, enc, false, 2, 3);
var lead = MethodH.GetSeed(enc, seed, false, 2, criteria.ForceMinLevelRange);
if (!lead.IsValid()) // Verifies the slot and form loop; if it passes, apply the details.
continue;
// Level is always 25, and no need to consider ability (always slot 0, not dual ability).
pk.PID = pid;
pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF);
return true;
@ -212,10 +229,11 @@ public static class GenerateMethodH
var form = EntityPID.GetUnownForm3(pid);
if (form != enc.Form)
continue;
var lead = MethodH.GetSeed(enc, seed, enc, false, 2, 3);
var lead = MethodH.GetSeed(enc, seed, false, 2, criteria.ForceMinLevelRange);
if (!lead.IsValid()) // Verifies the slot and form loop; if it passes, apply the details.
continue;
// Level is always 25, and no need to consider ability (always slot 0, not dual ability).
pk.PID = pid;
pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF);
return true;

View file

@ -18,7 +18,7 @@ public static class MethodH
/// <param name="evo">Level range constraints for the capture, if known.</param>
/// <param name="emerald">Version encountered in (either Emerald or not)</param>
/// <param name="gender">Gender encountered as</param>
/// <param name="format">Current format (different from 3)</param>
/// <param name="format">Current format (different from 3 will use level range instead of exact)</param>
public static LeadSeed GetSeed<TEnc, TEvo>(TEnc enc, uint seed, TEvo evo, bool emerald, byte gender, byte format)
where TEnc : IEncounterSlot3
where TEvo : ILevelRange
@ -32,9 +32,10 @@ public static class MethodH
return GetOriginSeed(info, enc, seed, nature, evo.LevelMin, evo.LevelMax, format);
}
/// <remarks>Used when generating or ignoring level ranges.</remarks>
/// <inheritdoc cref="GetSeed{TEnc,TEvo}"/>
public static LeadSeed GetSeed<TEnc>(TEnc enc, uint seed, bool emerald, byte gender, byte format)
where TEnc : IEncounterSlot3 => GetSeed(enc, seed, enc, emerald, gender, format);
public static LeadSeed GetSeed<TEnc>(TEnc enc, uint seed, bool emerald, byte gender, bool minLevel = false)
where TEnc : IEncounterSlot3 => GetSeed(enc, seed, enc, emerald, gender, minLevel ? (byte)3 : (byte)0);
// Summary of Random Determinations:
// Nature: rand() % 25 == nature
@ -203,6 +204,18 @@ public static class MethodH
public static bool IsEncounterCheckApplicable(SlotType3 type) => type is Rock_Smash; // Fishing can use Sticky/Suction along with Friendship boost.
/// <inheritdoc cref="MethodK.SkipToLevelRand{T}"/>
public static uint SkipToLevelRand<T>(T enc, uint seed)
where T : IEncounterSlot3
{
if (enc.Type is Rock_Smash)
return LCRNG.Next3(seed); // Proc, ESV, level.
if (enc.Type.IsFishingRodType())
return LCRNG.Next2(seed); // ESV, level.
// Can sweet scent trigger.
return LCRNG.Next2(seed); // ESV, level.
}
public static bool CheckEncounterActivation<T>(T enc, ref LeadSeed result)
where T : IEncounterSlot3
{
@ -570,7 +583,13 @@ public static class MethodH
private static bool IsLevelValid<T>(T enc, byte min, byte max, byte format, uint u16LevelRand) where T : ILevelRange
{
var level = GetExpectedLevel(enc, u16LevelRand);
var level = GetRandomLevel(enc, u16LevelRand);
return IsOriginalLevelValid(min, max, format, level);
}
private static bool IsLevelValidMinus1<T>(T enc, byte min, byte max, byte format, uint u16LevelRand) where T : ILevelRange
{
var level = GetRandomLevelMinus1(enc, u16LevelRand);
return IsOriginalLevelValid(min, max, format, level);
}
@ -581,20 +600,21 @@ public static class MethodH
return LevelRangeExtensions.IsLevelWithinRange((int)level, min, max);
}
private static uint GetExpectedLevel<T>(T enc, uint u16LevelRand) where T : ILevelRange
public static uint GetRandomLevel<T>(T enc, uint u16LevelRand, LeadRequired lead) where T : ILevelRange => lead switch
{
PressureHustleSpiritFail => GetRandomLevelMinus1(enc, u16LevelRand),
PressureHustleSpirit => enc.LevelMax,
_ => GetRandomLevel(enc, u16LevelRand),
};
private static uint GetRandomLevel<T>(T enc, uint u16LevelRand) where T : ILevelRange
{
var min = enc.LevelMin;
uint mod = 1u + enc.LevelMax - min;
return (u16LevelRand % mod) + min;
}
private static bool IsLevelValidMinus1<T>(T enc, byte min, byte max, byte format, uint u16LevelRand) where T : ILevelRange
{
var level = GetExpectedLevelMinus1(enc, u16LevelRand);
return IsOriginalLevelValid(min, max, format, level);
}
private static uint GetExpectedLevelMinus1<T>(T enc, uint u16LevelRand) where T : ILevelRange
private static uint GetRandomLevelMinus1<T>(T enc, uint u16LevelRand) where T : ILevelRange
{
var min = enc.LevelMin;
uint mod = 1u + enc.LevelMax - min;

View file

@ -59,12 +59,7 @@ public static class GenerateMethodJ
break; // try again
if (randLevel)
{
var lvl = enc.Type is SlotType4.HoneyTree
? MethodJ.GetHoneyTreeLevel(lv)
: ((lv % (enc.LevelMax - enc.LevelMin + 1)) + enc.LevelMin);
pk.MetLevel = pk.CurrentLevel = (byte)lvl;
}
pk.MetLevel = pk.CurrentLevel = (byte)MethodJ.GetRandomLevel(enc, lv, LeadRequired.None);
pk.PID = pid;
var iv1 = LCRNG.Next16(ref seed);
var iv2 = LCRNG.Next16(ref seed);
@ -107,10 +102,18 @@ public static class GenerateMethodJ
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (!criteria.IsGenderSatisfied(gender))
continue;
var lead = MethodJ.GetSeed(enc, seed, enc, 4);
var lead = MethodJ.GetSeed(enc, seed, criteria.ForceMinLevelRange);
if (!lead.IsValid()) // Verifies the slot, (min) level, and nature loop; if it passes, apply the details.
continue;
if (MethodJ.IsLevelRand(enc))
{
var lv = MethodJ.SkipToLevelRand(enc, lead.Seed) >> 16;
var actual = MethodJ.GetRandomLevel(enc, lv, lead.Lead);
if (pk.MetLevel != actual)
pk.MetLevel = pk.CurrentLevel = (byte)actual;
}
pk.PID = pid;
pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF);
pk.Gender = gender;

View file

@ -60,7 +60,7 @@ public static class GenerateMethodK
break; // try again
if (randLevel)
pk.MetLevel = pk.CurrentLevel = (byte)((lv % (enc.LevelMax - enc.LevelMin + 1)) + enc.LevelMin);
pk.MetLevel = pk.CurrentLevel = (byte)MethodK.GetRandomLevel(enc, lv, LeadRequired.None);
pk.PID = pid;
var iv1 = LCRNG.Next16(ref seed);
var iv2 = LCRNG.Next16(ref seed);
@ -106,10 +106,18 @@ public static class GenerateMethodK
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (!criteria.IsGenderSatisfied(gender))
continue;
var lead = MethodK.GetSeed(enc, seed, enc, 4);
var lead = MethodK.GetSeed(enc, seed, criteria.ForceMinLevelRange);
if (!lead.IsValid()) // Verifies the slot, (min) level, and nature loop; if it passes, apply the details.
continue;
if (MethodK.IsLevelRand(enc))
{
var lv = MethodK.SkipToLevelRand(enc, lead.Seed, lead.Lead) >> 16;
var actual = MethodK.GetRandomLevel(enc, lv, lead.Lead);
if (pk.MetLevel != actual)
pk.MetLevel = pk.CurrentLevel = (byte)actual;
}
pk.PID = pid;
pk.IV32 = ((iv2 & 0x7FFF) << 15) | (iv1 & 0x7FFF);
pk.Gender = gender;

View file

@ -17,7 +17,7 @@ public static class MethodJ
/// <param name="enc">Encounter template.</param>
/// <param name="seed">Seed that immediately generates the PID.</param>
/// <param name="evo">Level range constraints for the capture, if known.</param>
/// <param name="format">Current format (different from 4)</param>
/// <param name="format">Current format (different from 4 will use level range instead of exact)</param>
public static LeadSeed GetSeed<TEnc, TEvo>(TEnc enc, uint seed, TEvo evo, byte format)
where TEnc : IEncounterSlot4
where TEvo : ILevelRange
@ -29,9 +29,10 @@ public static class MethodJ
return GetOriginSeed(enc, seed, nature, frames, evo.LevelMin, evo.LevelMax, format);
}
/// <remarks>Used when generating or ignoring level ranges.</remarks>
/// <inheritdoc cref="GetSeed{TEnc,TEvo}"/>
public static LeadSeed GetSeed<TEnc>(TEnc enc, uint seed, byte format)
where TEnc : IEncounterSlot4 => GetSeed(enc, seed, enc, format);
public static LeadSeed GetSeed<TEnc>(TEnc enc, uint seed, bool minLevel = false) where TEnc : IEncounterSlot4
=> GetSeed(enc, seed, enc, minLevel ? (byte)4 : (byte)0);
// Summary of Random Determinations:
// For constant-value rand choices, the games avoid using modulo via:
@ -95,6 +96,19 @@ public static class MethodJ
public static bool IsEncounterCheckApplicable(SlotType4 type) => type is HoneyTree || type.IsFishingRodType();
/// <inheritdoc cref="MethodK.SkipToLevelRand{T}"/>
public static uint SkipToLevelRand<T>(T enc, uint seed)
where T : IEncounterSlot4
{
if (enc.Type is HoneyTree)
return LCRNG.Next(seed); // Honey Tree: No ESV, just the level.
if (!enc.Type.IsFishingRodType())
return LCRNG.Next2(seed); // ESV, level.
if (enc is EncounterSlot4 { Parent.IsCoronetFeebasArea: true })
return LCRNG.Next4(seed); // Proc, Tile, ESV, level.
return LCRNG.Next3(seed); // Proc, ESV, level.
}
public static bool CheckEncounterActivation<T>(T enc, ref LeadSeed result)
where T : IEncounterSlot4
{
@ -142,7 +156,7 @@ public static class MethodJ
return true;
}
private static bool IsValidCoronetB1F(ISpeciesForm s4, ref uint result)
private static bool IsValidCoronetB1F<T>(T s4, ref uint result) where T : ISpeciesForm
{
// The game rolls to check if it might need to replace the slots with Feebas.
// This occurs in Mt. Coronet B1F; if passed, check if the player is on a Feebas tile before replacing.
@ -445,7 +459,7 @@ public static class MethodJ
private static bool IsLevelValid<T>(T enc, byte min, byte max, byte format, uint u16LevelRand) where T : IEncounterSlot4
{
var level = enc.Type is HoneyTree ? GetHoneyTreeLevel(u16LevelRand) : GetExpectedLevel(enc, u16LevelRand);
var level = GetRandomLevel(enc, u16LevelRand);
return IsOriginalLevelValid(min, max, format, level);
}
@ -456,8 +470,17 @@ public static class MethodJ
return LevelRangeExtensions.IsLevelWithinRange((int)level, min, max);
}
private static uint GetExpectedLevel<T>(T enc, uint u16LevelRand) where T : ILevelRange
public static uint GetRandomLevel<T>(T enc, uint seed, LeadRequired lead) where T : IEncounterSlot4
{
if (lead is PressureHustleSpirit)
return enc.PressureLevel;
return GetRandomLevel(enc, seed);
}
private static uint GetRandomLevel<T>(T enc, uint u16LevelRand) where T : IEncounterSlot4
{
if (enc.Type is HoneyTree)
return GetHoneyTreeLevel(u16LevelRand);
var min = enc.LevelMin;
uint mod = 1u + enc.LevelMax - min;
return (u16LevelRand % mod) + min;

View file

@ -16,12 +16,17 @@ public static class MethodK
/// <param name="enc">Encounter template.</param>
/// <param name="seed">Seed that immediately generates the PID.</param>
/// <param name="evo">Level range constraints for the capture, if known.</param>
/// <param name="format">Current format (different from 4)</param>
/// <param name="format">Current format (different from 4 will use level range instead of exact)</param>
public static LeadSeed GetSeed<TEnc, TEvo>(TEnc enc, uint seed, TEvo evo, byte format = Format)
where TEnc : IEncounterSlot4
where TEvo : ILevelRange
=> GetSeed(enc, seed, evo.LevelMin, evo.LevelMax, format);
/// <remarks>Used when generating or ignoring level ranges.</remarks>
/// <inheritdoc cref="GetSeed{TEnc,TEvo}"/>
public static LeadSeed GetSeed<TEnc>(TEnc enc, uint seed, bool minLevel = false) where TEnc : IEncounterSlot4
=> GetSeed(enc, seed, enc, minLevel ? (byte)4 : (byte)0);
public static LeadSeed GetSeed<TEnc>(TEnc enc, uint seed, byte levelMin, byte levelMax, byte format = Format, int depth = 0)
where TEnc : IEncounterSlot4
{
@ -32,10 +37,6 @@ public static class MethodK
return GetOriginSeed(enc, seed, nature, frames, levelMin, levelMax, format, depth);
}
/// <inheritdoc cref="GetSeed{TEnc, TEvo}(TEnc, uint, TEvo, byte)"/>
public static LeadSeed GetSeed<TEnc>(TEnc enc, uint seed)
where TEnc : IEncounterSlot4 => GetSeed(enc, seed, enc);
/// <inheritdoc cref="MethodJ.GetReversalWindow"/>
/// <returns>Count of reverses allowed for no specific lead (not cute charm).</returns>
public static int GetReversalWindow(uint seed, byte nature) => MethodJ.GetReversalWindow(seed, nature);
@ -92,6 +93,20 @@ public static class MethodK
public static bool IsEncounterCheckApplicable(SlotType4 type) => type is Rock_Smash or BugContest || type.IsFishingRodType();
/// <summary>
/// Advances the origin seed to the state that yields the level rand() result.
/// </summary>
/// <remarks> If using the rand() result, be sure to >> 16. </remarks>
public static uint SkipToLevelRand<T>(T enc, uint seed, LeadRequired lead)
where T : IEncounterSlot4
{
if (enc.Type.IsFishingRodType() || enc.Type is Rock_Smash)
return LCRNG.Next3(seed); // Proc, ESV, level.
if (enc.Type is BugContest && !lead.IsAbleToSweetScent())
return LCRNG.Next4(seed); // ProcStep[2], ESV, level.
return LCRNG.Next2(seed); // ESV, level.
}
public static bool CheckEncounterActivation<T>(T enc, ref LeadSeed result)
where T : IEncounterSlot4
{
@ -471,7 +486,7 @@ public static class MethodK
private static bool IsLevelValid<T>(T enc, byte min, byte max, byte format, uint u16LevelRand) where T : ILevelRange
{
var level = GetExpectedLevel(enc, u16LevelRand);
var level = GetRandomLevel(enc, u16LevelRand);
return IsOriginalLevelValid(min, max, format, level);
}
@ -482,7 +497,14 @@ public static class MethodK
return LevelRangeExtensions.IsLevelWithinRange((int)level, min, max);
}
private static uint GetExpectedLevel<T>(T enc, uint u16LevelRand) where T : ILevelRange
public static uint GetRandomLevel<T>(T enc, uint seed, LeadRequired lead) where T : IEncounterSlot4
{
if (lead is PressureHustleSpirit)
return enc.PressureLevel;
return GetRandomLevel(enc, seed);
}
private static uint GetRandomLevel<T>(T enc, uint u16LevelRand) where T : ILevelRange
{
var min = enc.LevelMin;
uint mod = 1u + enc.LevelMax - min;

View file

@ -46,7 +46,7 @@ public static class MethodJChecks
LeadRequired expectLead = LeadRequired.Synchronize)
{
var fake = new MockSlot4(slotIndex);
var (seed, lead) = MethodJ.GetSeed(fake, prePID, 4);
var (seed, lead) = MethodJ.GetSeed(fake, prePID);
lead.Should().Be(expectLead);
seed.Should().BeInRange(0, uint.MaxValue);
}
@ -62,7 +62,7 @@ public static class MethodJChecks
public static void CheckImpossibleJ(uint prePID, byte slotIndex)
{
var fake = new MockSlot4(slotIndex);
var lead = MethodJ.GetSeed(fake, prePID, 4);
var lead = MethodJ.GetSeed(fake, prePID);
lead.IsValid().Should().BeFalse();
lead.Seed.Should().BeInRange(0, uint.MaxValue);
}