Misc enc template tweaks

Skip string fetch for Trade1
Set gender for Trade8
Add record to wrap Gen8 raid generate params, return & use bool for filtering results, fallback to unrestricted if none succeed
Fix gender comparison for Entree5 slot
Simplify gen2 ResetKey fetch (byte sum of Money/ID/OT(used)
Closes #3970 by filtering gender inside the method like Encounter9RNG with an early return
Adds IV filtering inside the method with an early return like Encounter9RNG
This commit is contained in:
Kurt 2023-08-29 22:22:23 -07:00
parent 5222fdc6ad
commit 978bcfa56f
10 changed files with 98 additions and 88 deletions

View file

@ -75,7 +75,7 @@ public sealed record EncounterTrade1 : IEncounterable, IEncounterMatch, IFixedTr
private static bool IsTrainerNameValid(PKM pk)
{
if (pk.Format <= 2)
return pk.OT_Name == StringConverter12.G1TradeOTStr;
return pk.OT_Trash is [StringConverter12.G1TradeOTCode, StringConverter12.G1TerminatorCode, _];
return pk.Language switch
{
1 => GetIndex(pk.OT_Name, TrainerNames) == 1,
@ -89,7 +89,7 @@ public sealed record EncounterTrade1 : IEncounterable, IEncounterMatch, IFixedTr
{
for (int i = 0; i < arr.Count; i++)
{
if (arr[i].AsSpan().SequenceEqual(name))
if (name.SequenceEqual(arr[i]))
return i;
}
@ -124,12 +124,12 @@ public sealed record EncounterTrade1 : IEncounterable, IEncounterMatch, IFixedTr
Catch_Rate = EncounterUtil1.GetWildCatchRate(Version, Species),
DV16 = EncounterUtil1.GetRandomDVs(Util.Rand),
OT_Name = StringConverter12.G1TradeOTStr,
Nickname = Nicknames[lang],
TID16 = tr.TID16,
Type1 = pi.Type1,
Type2 = pi.Type2,
};
pk.OT_Trash[0] = StringConverter12.G1TradeOTCode;
EncounterUtil1.SetEncounterMoves(pk, Version, level);

View file

@ -90,32 +90,37 @@ public abstract record EncounterStatic8Nest<T>(GameVersion Version)
{
bool requestShiny = criteria.Shiny.IsShiny();
bool checkShiny = requestShiny && Shiny != Shiny.Never;
var ratio = RemapGenderToParam(Gender, pi);
var abil = RemapAbilityToParam(Ability);
Span<int> iv = stackalloc int[6];
int ctr = 0;
var rand = new Xoroshiro128Plus(Util.Rand.Rand64());
var param = GetParam(pi);
ulong seed;
const int max = 100_000;
do
{
seed = rand.Next();
ApplyDetailsTo(pk, seed, iv, abil, ratio);
if (criteria.IV_ATK != 31 && pk.IV_ATK != criteria.IV_ATK)
continue;
if (criteria.IV_SPE != 31 && pk.IV_SPE != criteria.IV_SPE)
if (!TryApply(pk, seed, iv, param, criteria))
continue;
if (checkShiny && pk.IsShiny != requestShiny)
continue;
break;
} while (ctr++ < 100_000);
} while (ctr++ < max);
if (ctr == max) // fail
TryApply(pk, rand.Next(), iv, param, EncounterCriteria.Unrestricted);
FinishCorrelation(pk, seed);
if ((byte)criteria.Nature != pk.Nature && criteria.Nature.IsMint())
pk.StatNature = (byte)criteria.Nature;
}
private GenerateParam8 GetParam(PersonalInfo8SWSH pi)
{
var ratio = RemapGenderToParam(Gender, pi);
return new GenerateParam8(Species, ratio, FlawlessIVCount, Ability, Shiny, Nature.Random, IVs);
}
protected virtual void FinishCorrelation(PK8 pk, ulong seed) { }
#endregion
@ -239,28 +244,14 @@ public abstract record EncounterStatic8Nest<T>(GameVersion Version)
/// <returns>True if the seed is valid for the criteria.</returns>
public bool Verify(PKM pk, ulong seed, bool forceNoShiny = false)
{
var ratio = RemapGenderToParam(Gender, Info);
var abil = RemapAbilityToParam(Ability);
var param = GetParam(PersonalTable.SWSH.GetFormEntry(Species, Form));
Span<int> iv = stackalloc int[6];
LoadIVs(iv);
return RaidRNG.Verify(pk, seed, iv, Species, FlawlessIVCount, abil, ratio, forceNoShiny: forceNoShiny);
return RaidRNG.Verify(pk, seed, iv, param, forceNoShiny: forceNoShiny);
}
private void ApplyDetailsTo(PK8 pk, ulong seed, Span<int> iv, byte abil, byte ratio)
private static bool TryApply(PK8 pk, ulong seed, Span<int> iv, GenerateParam8 param, EncounterCriteria criteria)
{
LoadIVs(iv);
RaidRNG.ApplyDetailsTo(pk, seed, iv, Species, FlawlessIVCount, abil, ratio);
}
private void LoadIVs(Span<int> span)
{
// Template stores with speed in middle (standard), convert for generator purpose.
var ivs = IVs;
if (ivs.IsSpecified)
ivs.CopyToSpeedLast(span);
else
span.Fill(-1);
return RaidRNG.TryApply(pk, seed, iv, param, criteria);
}
private static byte RemapGenderToParam(byte gender, PersonalInfo8SWSH pi) => gender switch
@ -271,13 +262,6 @@ public abstract record EncounterStatic8Nest<T>(GameVersion Version)
_ => pi.Gender,
};
private static byte RemapAbilityToParam(AbilityPermission a) => a switch
{
Any12H => 254,
Any12 => 255,
_ => a.GetSingleValue(),
};
private bool IsMatchCorrelation(PKM pk)
{
if (pk.IsShiny)

View file

@ -109,6 +109,7 @@ public sealed record EncounterTrade8 : IEncounterable, IEncounterMatch, IFixedTr
Met_Level = Level,
MetDate = EncounterDate.GetDateSwitch(),
Ball = (byte)FixedBall,
Gender = Gender,
ID32 = ID32,
Version = (byte)version,

View file

@ -0,0 +1,15 @@
namespace PKHeX.Core;
/// <summary>
/// Parameters used to generate data for an encounter.
/// </summary>
/// <param name="Species">Species to generate.</param>
/// <param name="GenderRatio">Gender ratio byte.</param>
/// <param name="FlawlessIVs">Count of IVs that are perfect.</param>
/// <param name="Ability">Ability type to generate.</param>
/// <param name="Shiny">PID generation type.</param>
/// <param name="Nature">Nature specification.</param>
/// <param name="IVs">IV specification.</param>
public readonly record struct GenerateParam8(ushort Species, byte GenderRatio, byte FlawlessIVs,
AbilityPermission Ability = AbilityPermission.Any12, Shiny Shiny = Shiny.Random,
Nature Nature = Nature.Random, IndividualValueSet IVs = default);

View file

@ -14,14 +14,10 @@ public static class RaidRNG
/// <param name="pk">Entity to verify against</param>
/// <param name="seed">Seed that generated the entity</param>
/// <param name="ivs">Buffer of IVs (potentially with already specified values)</param>
/// <param name="species">Species of the entity</param>
/// <param name="iv_count">Number of flawless IVs to generate</param>
/// <param name="ability_param">Ability to generate</param>
/// <param name="gender_ratio">Gender distribution to generate</param>
/// <param name="nature_param">Nature to generate</param>
/// <param name="param">Parameters to generate with</param>
/// <param name="forceNoShiny">Force the entity to be non-shiny via special handling</param>
/// <returns>True if the seed matches the entity</returns>
public static bool Verify(PKM pk, ulong seed, Span<int> ivs, ushort species, byte iv_count, byte ability_param, byte gender_ratio, sbyte nature_param = -1, bool forceNoShiny = false)
public static bool Verify(PKM pk, ulong seed, Span<int> ivs, in GenerateParam8 param, bool forceNoShiny = false)
{
var rng = new Xoroshiro128Plus(seed);
var ec = (uint)rng.NextInt();
@ -49,7 +45,12 @@ public static class RaidRNG
const int UNSET = -1;
const int MAX = 31;
for (int i = ivs.Count(MAX); i < iv_count; i++)
if (param.IVs.IsSpecified)
param.IVs.CopyToSpeedLast(ivs);
else
ivs.Fill(UNSET);
for (int i = ivs.Count(MAX); i < param.FlawlessIVs; i++)
{
int index = (int)rng.NextInt(6);
while (ivs[index] != UNSET)
@ -76,11 +77,11 @@ public static class RaidRNG
if (pk.IV_SPE != ivs[5])
return false;
int abil = ability_param switch
int abil = param.Ability switch
{
254 => (int)rng.NextInt(3),
255 => (int)rng.NextInt(2),
_ => ability_param,
AbilityPermission.Any12H => (int)rng.NextInt(3),
AbilityPermission.Any12 => (int)rng.NextInt(2),
_ => param.Ability.GetSingleValue(),
};
abil <<= 1; // 1/2/4
@ -92,7 +93,7 @@ public static class RaidRNG
}
// else, for things that were made Hidden Ability, defer to Ability Checks (Ability Patch)
switch (gender_ratio)
switch (param.GenderRatio)
{
case PersonalInfo.RatioMagicGenderless:
if (pk.Gender != 2)
@ -107,14 +108,14 @@ public static class RaidRNG
return false;
break;
default:
var gender = (int)rng.NextInt(253) + 1 < gender_ratio ? 1 : 0;
var gender = (int)rng.NextInt(253) + 1 < param.GenderRatio ? 1 : 0;
if (pk.Gender != gender && pk.Gender != 2) // allow Nincada(0/1)->Shedinja(2)
return false;
break;
}
int nature = nature_param != -1 ? nature_param
: species == (int)Species.Toxtricity
int nature = param.Nature != Nature.Random ? (int)param.Nature
: param.Species == (int)Species.Toxtricity
? ToxtricityUtil.GetRandomNature(ref rng, pk.Form)
: (byte)rng.NextInt(25);
if (pk.Nature != nature)
@ -164,14 +165,10 @@ public static class RaidRNG
/// <param name="pk">Entity to verify against</param>
/// <param name="seed">Seed that generated the entity</param>
/// <param name="ivs">Buffer of IVs (potentially with already specified values)</param>
/// <param name="species">Species of the entity</param>
/// <param name="iv_count">Number of flawless IVs to generate</param>
/// <param name="ability_param">Ability to generate</param>
/// <param name="gender_ratio">Gender distribution to generate</param>
/// <param name="nature_param">Nature to generate</param>
/// <param name="shiny">Shiny state to generate</param>
/// <param name="param">Parameters to generate with</param>
/// <param name="criteria">Criteria to generate with</param>
/// <returns>True if the seed matches the entity</returns>
public static bool ApplyDetailsTo(PK8 pk, ulong seed, Span<int> ivs, ushort species, byte iv_count, byte ability_param, byte gender_ratio, sbyte nature_param = -1, Shiny shiny = Shiny.Random)
public static bool TryApply(PK8 pk, ulong seed, Span<int> ivs, in GenerateParam8 param, EncounterCriteria criteria)
{
var rng = new Xoroshiro128Plus(seed);
pk.EncryptionConstant = (uint)rng.NextInt();
@ -183,7 +180,7 @@ public static class RaidRNG
pid = (uint)rng.NextInt();
var xor = GetShinyXor(pid, trID);
isShiny = xor < 16;
if (isShiny && shiny == Shiny.Never)
if (isShiny && param.Shiny == Shiny.Never)
{
ForceShinyState(false, ref pid, trID);
isShiny = false;
@ -205,13 +202,21 @@ public static class RaidRNG
const int UNSET = -1;
const int MAX = 31;
for (int i = ivs.Count(MAX); i < iv_count; i++)
if (param.IVs.IsSpecified)
{
int index = (int)rng.NextInt(6);
while (ivs[index] != UNSET)
index = (int)rng.NextInt(6);
param.IVs.CopyToSpeedLast(ivs);
}
else
{
ivs.Fill(UNSET);
for (int i = ivs.Count(MAX); i < param.FlawlessIVs; i++)
{
int index;
do { index = (int)rng.NextInt(6); }
while (ivs[index] != UNSET);
ivs[index] = MAX;
}
}
for (int i = 0; i < 6; i++)
{
@ -219,6 +224,9 @@ public static class RaidRNG
ivs[i] = (int)rng.NextInt(32);
}
if (!param.IVs.IsSpecified && !criteria.IsIVsCompatible(ivs, 8))
return false;
pk.IV_HP = ivs[0];
pk.IV_ATK = ivs[1];
pk.IV_DEF = ivs[2];
@ -226,24 +234,27 @@ public static class RaidRNG
pk.IV_SPD = ivs[4];
pk.IV_SPE = ivs[5];
int abil = ability_param switch
int abil = param.Ability switch
{
254 => (int)rng.NextInt(3),
255 => (int)rng.NextInt(2),
_ => ability_param,
AbilityPermission.Any12H => (int)rng.NextInt(3),
AbilityPermission.Any12 => (int)rng.NextInt(2),
_ => param.Ability.GetSingleValue(),
};
pk.RefreshAbility(abil);
pk.Gender = gender_ratio switch
var gender = param.GenderRatio switch
{
PersonalInfo.RatioMagicGenderless => 2,
PersonalInfo.RatioMagicFemale => 1,
PersonalInfo.RatioMagicMale => 0,
_ => (int) rng.NextInt(253) + 1 < gender_ratio ? 1 : 0,
_ => (int) rng.NextInt(253) + 1 < param.GenderRatio ? 1 : 0,
};
if (criteria.Gender != FixedGenderUtil.GenderRandom && gender != criteria.Gender)
return false;
pk.Gender = gender;
int nature = nature_param != -1 ? nature_param
: species == (int)Species.Toxtricity
int nature = param.Nature != Nature.Random ? (byte)param.Nature
: param.Species == (int)Species.Toxtricity
? ToxtricityUtil.GetRandomNature(ref rng, pk.Form)
: (byte)rng.NextInt(25);

View file

@ -229,8 +229,8 @@ public sealed class PK1 : GBPKML, IPersonalType
private string GetTransferTrainerName(int lang)
{
if (OT_Trash[0] == StringConverter12.G1TradeOTCode)
return StringConverter12.G1TradeOTName[lang]; // In-game Trade
if (OT_Trash[0] == StringConverter12.G1TradeOTCode) // In-game Trade
return StringConverter12.G1TradeOTName[lang];
return StringConverter12Transporter.GetString(OT_Trash, Japanese);
}
}

View file

@ -39,7 +39,6 @@ public static class StringConverter12
public const char G1Terminator = '\0';
public const byte G1TradeOTCode = 0x5D;
public const char G1TradeOT = '*';
public const string G1TradeOTStr = "*";
public const byte G1SpaceCode = 0x7F;
public static readonly IReadOnlyList<string> G1TradeOTName = new []
@ -111,7 +110,7 @@ public static class StringConverter12
/// <returns>Character count loaded.</returns>
public static int LoadString(ReadOnlySpan<byte> data, Span<char> result, bool jp)
{
if (data[0] == G1TradeOTCode)
if (data[0] == G1TradeOTCode) // In-game Trade
{
result[0] = G1TradeOT;
return 1;

View file

@ -50,7 +50,7 @@ public static class StringConverter2KOR
/// <returns>Character count loaded.</returns>
public static int LoadString(ReadOnlySpan<byte> data, Span<char> result)
{
if (data[0] == G1TradeOTCode)
if (data[0] == G1TradeOTCode) // In-game Trade
{
result[0] = G1TradeOT;
return 1;

View file

@ -720,16 +720,16 @@ public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
private ushort GetResetKey()
{
var value = (TID16 >> 8) + (TID16 & 0xFF) + ((Money >> 16) & 0xFF) + ((Money >> 8) & 0xFF) + (Money & 0xFF);
var ot = Data.AsSpan(Offsets.Trainer1 + 2, 5);
var sum = 0;
foreach (var b in ot)
{
if (b == StringConverter12.G1TerminatorCode)
break;
sum += b;
}
return (ushort)(value + sum);
ushort result = 0;
foreach (var b in Data.AsSpan(Offsets.Money, 3))
result += b;
var tr = Data.AsSpan(Offsets.Trainer1, 7);
var end = tr[2..].IndexOf(StringConverter12.G1TerminatorCode);
if (end >= 0)
tr = tr[..(end + 2)];
foreach (var b in tr)
result += b;
return result;
}
/// <summary>

View file

@ -620,7 +620,7 @@ public partial class SAV_Misc5 : Form
source.Remove(slot);
s.Species = slot.Species;
s.Form = slot.Form;
s.Gender = slot.Gender == -1 ? PersonalTable.B2W2[slot.Species].RandomGender() : slot.Gender;
s.Gender = slot.Gender == FixedGenderUtil.GenderRandom ? PersonalTable.B2W2[slot.Species].RandomGender() : slot.Gender;
slot.Moves.CopyTo(moves);
var count = moves.Length - moves.Count((ushort)0);