Rework EncounterCriteria to be ability indexed rather than direct ability (#3179)

* Exploration: rework ability criteria to ability numbers desired

* Sync remaining changes

* Update EncounterCriteria.cs

* Add xmldoc

* Improve speed of IsDualGender check

* More xmldoc updates

Should be doing this on main but meh, this branch is gonna get merged later

* Fix typo

* Update WC7.cs

* Update PersonalInfo.cs
This commit is contained in:
Kurt 2021-03-23 17:05:15 -07:00 committed by GitHub
parent f86291cc1c
commit 3822981590
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 172 additions and 44 deletions

View file

@ -132,7 +132,7 @@ namespace PKHeX.Core
var pi = pk.PersonalInfo;
int gender = criteria.GetGender(-1, pi);
int nature = (int)criteria.GetNature(Nature.Random);
var ability = criteria.GetAbilityFromNumber(Ability, pi);
var ability = criteria.GetAbilityFromNumber(Ability);
if (Generation == 3 && Species == (int)Unown)
{

View file

@ -23,7 +23,7 @@ namespace PKHeX.Core
var pi = pk.PersonalInfo;
int gender = criteria.GetGender(-1, pi);
int nature = (int)criteria.GetNature(Nature.Random);
int ability = criteria.GetAbilityFromNumber(0, pi);
int ability = criteria.GetAbilityFromNumber(0);
PIDGenerator.SetRandomPokeSpotPID(pk, nature, gender, ability, SlotNumber);
pk.Gender = gender;
pk.StatNature = nature;

View file

@ -23,7 +23,7 @@ namespace PKHeX.Core
int num = Ability;
if (Area.Type == SlotType.SOS && pk.FlawlessIVCount < 2)
num = 0; // let's fake it as an insufficient chain, no HA possible.
var ability = criteria.GetAbilityFromNumber(num, pi);
var ability = criteria.GetAbilityFromNumber(num);
pk.RefreshAbility(ability);
pk.SetRandomEC();
}

View file

@ -109,7 +109,7 @@ namespace PKHeX.Core
var pi = pk.PersonalInfo;
int gender = criteria.GetGender(Gender, pi);
int nature = (int)criteria.GetNature(Nature);
int ability = criteria.GetAbilityFromNumber(Ability, pi);
int ability = criteria.GetAbilityFromNumber(Ability);
var pidtype = GetPIDType();
PIDGenerator.SetRandomWildPID(pk, pk.Format, nature, ability, gender, pidtype);

View file

@ -81,7 +81,7 @@ namespace PKHeX.Core
var pi = pk.PersonalInfo;
int gender = criteria.GetGender(-1, pi);
int nature = (int)criteria.GetNature(Nature.Random);
int ability = criteria.GetAbilityFromNumber(0, pi);
int ability = criteria.GetAbilityFromNumber(0);
// Ensure that any generated specimen has valid Shadow Locks
// This can be kinda slow, depending on how many locks / how strict they are.

View file

@ -146,7 +146,7 @@ namespace PKHeX.Core
var pi = pk.PersonalInfo;
int gender = criteria.GetGender(Gender, pi);
int nature = (int)criteria.GetNature(Nature);
int ability = criteria.GetAbilityFromNumber(Ability, pi);
int ability = criteria.GetAbilityFromNumber(Ability);
PIDGenerator.SetRandomWildPID(pk, Generation, nature, ability, gender);
pk.Nature = pk.StatNature = nature;

View file

@ -70,7 +70,7 @@ namespace PKHeX.Core
var pi = pk.PersonalInfo;
int gender = criteria.GetGender(PKX.GetGenderFromPID(Species, PID), pi);
int nature = (int)criteria.GetNature(Nature);
int ability = criteria.GetAbilityFromNumber(Ability, pi);
int ability = criteria.GetAbilityFromNumber(Ability);
pk.PID = PID;
pk.Nature = nature;

View file

@ -38,7 +38,7 @@
var pi = pk.PersonalInfo;
int gender = criteria.GetGender(PKX.GetGenderFromPID(Species, PID), pi);
int nature = (int)criteria.GetNature(Nature);
int ability = criteria.GetAbilityFromNumber(Ability, pi);
int ability = criteria.GetAbilityFromNumber(Ability);
pk.PID = PID;
pk.Nature = nature;

View file

@ -1,15 +1,32 @@
namespace PKHeX.Core
using System.Collections.Generic;
using static PKHeX.Core.AbilityPermission;
namespace PKHeX.Core
{
/// <summary>
/// Object that can be fed to a <see cref="IEncounterable"/> converter to ensure that the resulting <see cref="PKM"/> meets rough specifications.
/// Object that can be fed to a <see cref="IEncounterConvertible"/> converter to ensure that the resulting <see cref="PKM"/> meets rough specifications.
/// </summary>
public sealed record EncounterCriteria
{
/// <summary>
/// Default criteria with no restrictions (random) for all fields.
/// </summary>
public static readonly EncounterCriteria Unrestricted = new();
public int Ability { get; init; } = -1;
/// <summary> End result's gender. </summary>
/// <remarks> Leave as -1 to not restrict gender. </remarks>
public int Gender { get; init; } = -1;
/// <summary> End result's ability numbers permitted. </summary>
/// <remarks> Leave as <see cref="Any12H"/> to not restrict ability. </remarks>
public AbilityPermission AbilityNumber { get; init; } = Any12H;
/// <summary> End result's nature. </summary>
/// <remarks> Leave as <see cref="Core.Nature.Random"/> to not restrict nature. </remarks>
public Nature Nature { get; init; } = Nature.Random;
/// <summary> End result's nature. </summary>
/// <remarks> Leave as <see cref="Core.Shiny.Random"/> to not restrict shininess. </remarks>
public Shiny Shiny { get; init; } = Shiny.Random;
public int IV_HP { get; init; } = RandomIV;
@ -19,10 +36,17 @@
public int IV_SPD { get; init; } = RandomIV;
public int IV_SPE { get; init; } = RandomIV;
// unused
public int HPType { get; init; } = -1;
private const int RandomIV = -1;
/// <summary>
/// Checks if the IVs are compatible with the encounter's defined IV restrictions.
/// </summary>
/// <param name="encounterIVs">Encounter template's IV restrictions. Speed is not last.</param>
/// <param name="generation">Destination generation</param>
/// <returns>True if compatible, false if incompatible.</returns>
public bool IsIVsCompatible(int[] encounterIVs, int generation)
{
var IVs = encounterIVs;
@ -43,13 +67,31 @@
return true;
}
public static EncounterCriteria GetCriteria(IBattleTemplate s)
/// <inheritdoc cref="GetCriteria(IBattleTemplate, PersonalTable)"/>
/// <remarks>Uses the latest generation personal table (PKX.Personal); you really should pass the table.</remarks>
public static EncounterCriteria GetCriteria(IBattleTemplate s) => GetCriteria(s, PKX.Personal);
/// <inheritdoc cref="GetCriteria(IBattleTemplate, PersonalInfo)"/>
/// <param name="s">Template data (end result).</param>
/// <param name="t">Personal table the end result will exist with.</param>
public static EncounterCriteria GetCriteria(IBattleTemplate s, PersonalTable t)
{
var pi = t.GetFormEntry(s.Species, s.Form);
return GetCriteria(s, pi);
}
/// <summary>
/// Creates a new <see cref="EncounterCriteria"/> by loading parameters from the provided <see cref="IBattleTemplate"/>.
/// </summary>
/// <param name="s">Template data (end result).</param>
/// <param name="pi">Personal info the end result will exist with.</param>
/// <returns>Initialized criteria data to be passed to generators.</returns>
public static EncounterCriteria GetCriteria(IBattleTemplate s, PersonalInfo pi)
{
int gender = string.IsNullOrWhiteSpace(s.Gender) ? -1 : PKX.GetGenderFromString(s.Gender);
return new EncounterCriteria
{
Gender = gender,
Ability = s.Ability,
IV_HP = s.IVs[0],
IV_ATK = s.IVs[1],
IV_DEF = s.IVs[2],
@ -58,11 +100,35 @@
IV_SPD = s.IVs[5],
HPType = s.HiddenPowerType,
AbilityNumber = GetAbilityNumber(s.Ability, pi),
Nature = NatureUtil.GetNature(s.Nature),
Shiny = s.Shiny ? Shiny.Always : Shiny.Never,
};
}
private static AbilityPermission GetAbilityNumber(int ability, PersonalInfo pi)
{
var abilities = pi.Abilities;
if (abilities.Count < 2)
return 0;
var dual = GetAbilityValueDual(ability, abilities);
if (abilities.Count == 2) // prior to gen5
return dual;
if (abilities[2] == ability)
return dual == 0 ? Any12H : OnlyHidden;
return dual;
}
private static AbilityPermission GetAbilityValueDual(int ability, IReadOnlyList<int> abilities)
{
if (ability == abilities[0])
return ability != abilities[1] ? OnlyFirst : Any12;
return ability == abilities[1] ? OnlySecond : Any12;
}
/// <summary>
/// Gets a random nature to generate, based off an encounter's <see cref="encValue"/>.
/// </summary>
public Nature GetNature(Nature encValue)
{
if ((uint)encValue < 25)
@ -72,6 +138,9 @@
return (Nature)Util.Rand.Next(25);
}
/// <summary>
/// Gets a random gender to generate, based off an encounter's <see cref="gender"/>.
/// </summary>
public int GetGender(int gender, PersonalInfo pkPersonalInfo)
{
if ((uint)gender < 3)
@ -83,32 +152,43 @@
return pkPersonalInfo.RandomGender();
}
public int GetAbilityFromNumber(int num, PersonalInfo pkPersonalInfo)
/// <summary>
/// Gets a random ability index (0/1/2) to generate, based off an encounter's <see cref="num"/>.
/// </summary>
public int GetAbilityFromNumber(int num)
{
if (num > 0) // fixed number
return num >> 1;
var abils = pkPersonalInfo.Abilities;
if (abils.Count > 2 && abils[2] == Ability && num == -1) // hidden allowed
return 2;
if (abils.Count > 0 && abils[0] == Ability)
return 0;
return 1;
bool canBeHidden = num == -1;
return GetAbilityIndexPreference(canBeHidden);
}
public int GetAbilityFromType(int type, PersonalInfo pkPersonalInfo)
/// <summary>
/// Gets a random ability index (0/1/2) to generate, based off an encounter's <see cref="type"/>.
/// </summary>
/// <remarks>This is used for the Mystery Gift ability type arguments.</remarks>
public int GetAbilityFromType(int type)
{
if ((uint)type < 3)
return type;
var abils = pkPersonalInfo.Abilities;
if (type == 4 && abils.Count > 2 && abils[2] == Ability) // hidden allowed
return 2;
if (abils[0] == Ability)
return 0;
return 1;
bool canBeHidden = type == 4;
return GetAbilityIndexPreference(canBeHidden);
}
private int GetAbilityIndexPreference(bool canBeHidden = false) => AbilityNumber switch
{
OnlyFirst => 0,
OnlySecond => 1,
OnlyHidden or Any12H when canBeHidden => 2, // hidden allowed
_ => Util.Rand.Next(2),
};
/// <summary>
/// Applies random IVs without any correlation.
/// </summary>
/// <param name="pk">Entity to mutate.</param>
public void SetRandomIVs(PKM pk)
{
pk.IV_HP = IV_HP != RandomIV ? IV_HP : Util.Rand.Next(32);
@ -119,4 +199,13 @@
pk.IV_SPE = IV_SPE != RandomIV ? IV_SPE : Util.Rand.Next(32);
}
}
public enum AbilityPermission : sbyte
{
Any12H = -1,
Any12 = 0,
OnlyFirst = 1,
OnlySecond = 2,
OnlyHidden = 4,
}
}

View file

@ -5,6 +5,9 @@ using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core
{
/// <summary>
/// Default formatter for Legality Result displays.
/// </summary>
public class BaseLegalityFormatter : ILegalityFormatter
{
public string GetReport(LegalityAnalysis l)

View file

@ -1,8 +1,18 @@
namespace PKHeX.Core
{
/// <summary>
/// Formats legality results into a <see cref="T:System.String"/> for display.
/// </summary>
public interface ILegalityFormatter
{
/// <summary>
/// Gets a small summary of the legality analysis.
/// </summary>
string GetReport(LegalityAnalysis l);
/// <summary>
/// Gets a verbose summary of the legality analysis.
/// </summary>
string GetReportVerbose(LegalityAnalysis l);
}
}

View file

@ -4,6 +4,9 @@ using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core
{
/// <summary>
/// Formatting logic for <see cref="LegalityAnalysis"/> to create a human readable <see cref="T:System.String"/>.
/// </summary>
public static class LegalityFormatting
{
/// <summary>

View file

@ -284,7 +284,7 @@ namespace PKHeX.Core
private int GetAbilityIndex(EncounterCriteria criteria, PersonalInfo pi) => AbilityType switch
{
00 or 01 or 02 => AbilityType, // Fixed 0/1/2
03 or 04 => criteria.GetAbilityFromType(AbilityType, pi), // 0/1 or 0/1/H
03 or 04 => criteria.GetAbilityFromType(AbilityType), // 0/1 or 0/1/H
_ => throw new ArgumentException(nameof(AbilityType)),
};

View file

@ -416,7 +416,7 @@ namespace PKHeX.Core
private int GetAbilityIndex(EncounterCriteria criteria, PersonalInfo pi) => AbilityType switch
{
00 or 01 or 02 => AbilityType, // Fixed 0/1/2
03 or 04 => criteria.GetAbilityFromType(AbilityType, pi), // 0/1 or 0/1/H
03 or 04 => criteria.GetAbilityFromType(AbilityType), // 0/1 or 0/1/H
_ => throw new ArgumentException(nameof(AbilityType)),
};

View file

@ -410,7 +410,7 @@ namespace PKHeX.Core
private int GetAbilityIndex(EncounterCriteria criteria, PersonalInfo pi) => AbilityType switch
{
00 or 01 or 02 => AbilityType, // Fixed 0/1/2
03 or 04 => criteria.GetAbilityFromType(AbilityType, pi), // 0/1 or 0/1/H
03 or 04 => criteria.GetAbilityFromType(AbilityType), // 0/1 or 0/1/H
_ => throw new ArgumentException(nameof(AbilityType)),
};

View file

@ -316,6 +316,9 @@ namespace PKHeX.Core
int currentLevel = Level > 0 ? Level : rnd.Next(1, 101);
int metLevel = MetLevel > 0 ? MetLevel : currentLevel;
var version = OriginGame != 0 ? OriginGame : (int)this.GetCompatibleVersion((GameVersion)sav.Game);
var language = Language != 0 ? Language : (int)Core.Language.GetSafeLanguage(Generation, (LanguageID)sav.Language, (GameVersion)version);
var pi = PersonalTable.USUM.GetFormEntry(Species, Form);
PK7 pk = new()
{
@ -326,8 +329,8 @@ namespace PKHeX.Core
Met_Level = metLevel,
Form = Form,
EncryptionConstant = EncryptionConstant != 0 ? EncryptionConstant : Util.Rand32(),
Version = OriginGame != 0 ? OriginGame : sav.Game,
Language = Language != 0 ? Language : sav.Language,
Version = version,
Language = language,
Ball = Ball,
Move1 = Move1, Move2 = Move2, Move3 = Move3, Move4 = Move4,
RelearnMove1 = RelearnMove1, RelearnMove2 = RelearnMove2,
@ -441,7 +444,7 @@ namespace PKHeX.Core
private int GetAbilityIndex(EncounterCriteria criteria, PersonalInfo pi) => AbilityType switch
{
00 or 01 or 02 => AbilityType, // Fixed 0/1/2
03 or 04 => criteria.GetAbilityFromType(AbilityType, pi), // 0/1 or 0/1/H
03 or 04 => criteria.GetAbilityFromType(AbilityType), // 0/1 or 0/1/H
_ => throw new ArgumentException(nameof(AbilityType)),
};
@ -548,6 +551,12 @@ namespace PKHeX.Core
return PIDType != 0 || pkm.PID == PID;
}
public override GameVersion Version
{
get => CardID == 2046 ? GameVersion.SM : GameVersion.Gen7;
set { }
}
protected override bool IsMatchDeferred(PKM pkm) => Species != pkm.Species;
protected override bool IsMatchPartial(PKM pkm)

View file

@ -497,7 +497,7 @@ namespace PKHeX.Core
private int GetAbilityIndex(EncounterCriteria criteria, PersonalInfo pi) => AbilityType switch
{
00 or 01 or 02 => AbilityType, // Fixed 0/1/2
03 or 04 => criteria.GetAbilityFromType(AbilityType, pi), // 0/1 or 0/1/H
03 or 04 => criteria.GetAbilityFromType(AbilityType), // 0/1 or 0/1/H
_ => throw new ArgumentException(nameof(AbilityType)),
};

View file

@ -2,8 +2,12 @@
{
public interface IRegionOrigin
{
/// <summary> Console hardware region. </summary>
/// <see cref="RegionID"/>
int ConsoleRegion { get; set; }
/// <summary> Console's configured Country via System Settings. </summary>
int Country { get; set; }
/// <summary> Console's configured Region within <see cref="Country"/> via System Settings. </summary>
int Region { get; set; }
}

View file

@ -1,8 +1,14 @@
namespace PKHeX.Core
{
/// <summary>
/// Simple identification values for an Pokémon Entity.
/// </summary>
public interface ISpeciesForm
{
/// <summary> Species ID of the entity (National Dex). </summary>
int Species { get; }
/// <summary> Form ID of the entity. </summary>
int Form { get; }
}
}

View file

@ -279,8 +279,9 @@ namespace PKHeX.Core
return fix >= 0 ? fix : Util.Rand.Next(2);
}
public bool IsDualGender => FixedGender < 0;
/// <summary>
/// Gets a gender value. Returns -1 if the entry <see cref="IsDualGender"/>.
/// </summary>
public int FixedGender
{
get
@ -295,6 +296,11 @@ namespace PKHeX.Core
}
}
/// <summary>
/// Indicates that the entry has two genders.
/// </summary>
public bool IsDualGender => (uint)(Gender - 1) < 253;
/// <summary>
/// Indicates that the entry is exclusively Genderless.
/// </summary>
@ -324,7 +330,6 @@ namespace PKHeX.Core
/// Checks to see if the <see cref="PKM.Form"/> is valid within the <see cref="FormCount"/>
/// </summary>
/// <param name="form"></param>
/// <returns></returns>
public bool IsFormWithinRange(int form)
{
if (form == 0)
@ -335,7 +340,7 @@ namespace PKHeX.Core
/// <summary>
/// Checks to see if the provided Types match the entry's types.
/// </summary>
/// <remarks>Input order matters! If input order does not matter, use <see cref="o:IsType(type1, type2)"/>.</remarks>
/// <remarks>Input order matters! If input order does not matter, use <see cref="IsType(int, int)"/> instead.</remarks>
/// <param name="type1">First type</param>
/// <param name="type2">Second type</param>
/// <returns>Typing is an exact match</returns>

View file

@ -262,7 +262,7 @@ namespace PKHeX.Core
}
/// <summary>
/// Count of entries in the table, which includes default species entries and their separate <see cref="PKM.Form"/> entreis.
/// Count of entries in the table, which includes default species entries and their separate <see cref="PKM.Form"/> entries.
/// </summary>
public int TableLength => Table.Length;

View file

@ -172,7 +172,7 @@ namespace PKHeX.Core
const int av = 3;
pk.Gender = criteria.GetGender(Gender, pi);
pk.Nature = (int)criteria.GetNature(Nature.Random);
pk.RefreshAbility(criteria.GetAbilityFromType(av, pi));
pk.RefreshAbility(criteria.GetAbilityFromType(av));
bool isShiny = pk.IsShiny;
if (IsShiny && !isShiny) // Force Square

View file

@ -355,16 +355,15 @@ namespace PKHeX.WinForms
// Get Gender Threshold
int species = WinFormsUtil.GetIndex(CB_Species);
var pi = SAV.Personal[species];
var fg = pi.FixedGender;
if (fg == -1) // dual gender
if (pi.IsDualGender)
{
fg = PKX.GetGenderFromString(Label_Gender.Text);
var fg = PKX.GetGenderFromString(Label_Gender.Text);
fg = (fg ^ 1) & 1;
Label_Gender.Text = Main.GenderSymbols[fg];
}
else
{
var fg = pi.FixedGender;
Label_Gender.Text = Main.GenderSymbols[fg];
return;
}