Refactor to use Context over Generation

Generation was always more weak; am I paranoid about potential VC3? maybe
Better indicates the move source for LGPE exclusive moves, etc.
This commit is contained in:
Kurt 2026-02-15 02:15:50 -06:00
parent 387c254aa4
commit 9792455f34
52 changed files with 306 additions and 201 deletions

View File

@ -74,7 +74,7 @@ public static string GetStringFromForm(byte form, GameStrings strings, ushort sp
if (form == 0)
return string.Empty;
var result = FormConverter.GetStringFromForm(form, strings, species, genderForms, context);
var result = FormConverter.GetStringFromForm(species, form, strings, genderForms, context);
if (result.Length == 0)
return string.Empty;

View File

@ -561,7 +561,7 @@ private void PushToken(BattleTemplateToken token, List<string> result, in Battle
result.Add(cfg.Push(token, Friendship));
break;
case BattleTemplateToken.IVs:
var maxIV = Context.Generation < 3 ? 15 : 31;
var maxIV = Context.IsEraGameBoy ? 15 : 31;
if (!IVs.ContainsAnyExcept(maxIV))
break; // skip if all IVs are maxed
var nameIVs = cfg.GetStatDisplay(settings.StatsIVs);
@ -1028,7 +1028,7 @@ private ReadOnlySpan<char> ParseLineMove(ReadOnlySpan<char> line, GameStrings st
return hiddenPowerName;
HiddenPowerType = (sbyte)hpVal;
var maxIV = Context.Generation < 3 ? 15 : 31;
var maxIV = Context.IsEraGameBoy ? 15 : 31;
if (IVs.ContainsAnyExcept(maxIV))
{
if (!HiddenPower.SetIVsForType(hpVal, IVs, Context))

View File

@ -86,14 +86,14 @@ public ReadOnlySpan<ITrainerInfo> GetTrainers(GameVersion version)
}
/// <summary>
/// Fetches an appropriate trainer based on the requested <see cref="generation"/>.
/// Fetches an appropriate trainer based on the requested <see cref="context"/>.
/// </summary>
/// <param name="generation">Generation the trainer should inhabit</param>
/// <param name="context">Generation the trainer should inhabit</param>
/// <param name="lang">Language to request for</param>
/// <returns>Null if no trainer found for this version.</returns>
public ITrainerInfo? GetTrainerFromGen(byte generation, LanguageID? lang = null)
public ITrainerInfo? GetTrainerFromContext(EntityContext context, LanguageID? lang = null)
{
var possible = Database.Where(z => z.Key.Generation == generation).ToList();
var possible = Database.Where(z => z.Key.Context == context).ToList();
if (possible.Count == 0)
return null;

View File

@ -15,7 +15,7 @@ public static class HiddenPower
/// <param name="context">Generation format</param>
public static int GetType(ReadOnlySpan<int> IVs, EntityContext context)
{
if (context.Generation <= 2)
if (context.IsEraGameBoy)
return GetTypeGB(IVs);
return GetType(IVs);
}
@ -153,7 +153,7 @@ public static ushort SetTypeGB(int hiddenPowerType, ushort current)
/// <returns>True if the Hidden Power of the <see cref="IVs"/> is obtained, with or without modifications</returns>
public static bool SetIVsForType(int hiddenPowerType, Span<int> IVs, EntityContext context)
{
if (context.Generation <= 2)
if (context.IsEraGameBoy)
return SetTypeGB(hiddenPowerType, IVs);
return SetIVsForType(hiddenPowerType, IVs);
}
@ -238,7 +238,7 @@ private static int GetFlawedBitCount(ReadOnlySpan<int> ivs, int bitValue)
/// <param name="context">Generation specific format</param>
public static void SetIVs(int type, Span<int> ivs, EntityContext context = Latest.Context)
{
if (context.Generation <= 2)
if (context.IsEraGameBoy)
{
ivs[1] = (ivs[1] & 0b1100) | (type >> 2);
ivs[2] = (ivs[2] & 0b1100) | (type & 3);

View File

@ -28,7 +28,7 @@ public FilteredGameDataSource(SaveFile sav, GameDataSource source, bool HaX = fa
Items = [];
}
var gamelist = GameUtil.GetVersionsWithinRange(sav, sav.Generation).ToList();
var gamelist = GameUtil.GetVersionsWithinRange(sav, sav.Context).ToList();
Games = Source.VersionDataSource.Where(g => gamelist.Contains((GameVersion)g.Value) || g.Value == 0).ToList();
Languages = Source.LanguageDataSource(sav.Generation, sav.Context);

View File

@ -252,23 +252,23 @@ public bool Contains(GameVersion g2)
}
/// <summary>
/// List of possible <see cref="GameVersion"/> values within the provided <see cref="generation"/>.
/// List of possible <see cref="GameVersion"/> values within the provided <see cref="context"/>.
/// </summary>
/// <param name="generation">Generation to look within</param>
/// <param name="context">Generation to look within</param>
/// <param name="version">Entity version</param>
public static GameVersion[] GetVersionsInGeneration(byte generation, GameVersion version)
public static GameVersion[] GetVersionsInGeneration(EntityContext context, GameVersion version)
{
if (Gen7b.Contains(version))
if (context is EntityContext.Gen7b)
return [GO, GP, GE];
return Array.FindAll(GameVersions, z => z.Generation == generation);
return Array.FindAll(GameVersions, z => z.Context == context);
}
/// <summary>
/// List of possible <see cref="GameVersion"/> values within the provided <see cref="IGameValueLimit"/> criteria.
/// </summary>
/// <param name="obj">Criteria for retrieving versions</param>
/// <param name="generation">Generation format minimum (necessary for the CXD/Gen4 swap etc.)</param>
public static IEnumerable<GameVersion> GetVersionsWithinRange(IGameValueLimit obj, byte generation = 0)
/// <param name="context">Generation format minimum (necessary for the CXD/Gen4 swap etc.)</param>
public static IEnumerable<GameVersion> GetVersionsWithinRange(IGameValueLimit obj, EntityContext context = 0)
{
var max = obj.MaxGameID;
if (max == Legal.MaxGameID_7b) // edge case
@ -277,14 +277,14 @@ public static IEnumerable<GameVersion> GetVersionsWithinRange(IGameValueLimit ob
.Where(version => obj.MinGameID <= version && version <= max);
if (max != BATREV)
versions = versions.Where(static version => version != BATREV);
if (generation == 0)
if (context == 0)
return versions;
if (max == Legal.MaxGameID_7 && generation == 7)
if (max == Legal.MaxGameID_7 && context == EntityContext.Gen7)
versions = versions.Where(static version => version != GO);
// HOME allows up-reach to Gen9
if (generation >= 8)
generation = 9;
return versions.Where(version => version.Generation <= generation);
if (context.IsEraHOME)
return versions;
return versions.Where(version => version.Generation <= context.Generation);
}
}

View File

@ -141,7 +141,7 @@ private static bool VerifySecondaryChecks(PKM pk, LegalInfo info, PeekEnumerator
}
else if (pk is PK1 pk1)
{
var hasGen2 = MoveInfo.IsAnyFromGeneration(2, info.Moves);
var hasGen2 = MoveInfo.IsAnyFromGeneration(EntityContext.Gen2, info.Moves);
if (hasGen2)
{
if (!ParseSettings.AllowGen1Tradeback)

View File

@ -34,10 +34,9 @@ public static class EncounterMutationUtil
/// <param name="level">Destination level</param>
public static EncounterMutation GetSuggested(EntityContext targetContext, byte level)
{
var gen = targetContext.Generation;
if (gen < 6)
if (targetContext.IsEraPre3DS)
return EncounterMutation.None;
if (gen < 8)
if (targetContext.IsEraPreSwitch)
{
if (targetContext is EntityContext.Gen7b)
return level != 100 ? EncounterMutation.None : EncounterMutation.CanMaxIndividualStat;

View File

@ -37,7 +37,7 @@ public static IEnumerable<IEncounterable> GenerateEncounters(PKM pk, ITrainerInf
yield break;
OptimizeCriteria(pk, info);
var vers = versions.Length >= 1 ? versions : GameUtil.GetVersionsWithinRange(pk, pk.Format);
var vers = versions.Length >= 1 ? versions : GameUtil.GetVersionsWithinRange(pk, pk.Context);
foreach (var version in vers)
{
var encounters = GenerateVersionEncounters(pk, moves, version);
@ -63,12 +63,12 @@ public static void OptimizeCriteria(PKM pk, ITrainerID32ReadOnly info)
/// Gets possible encounters that allow all moves requested to be learned.
/// </summary>
/// <param name="pk">Rough Pokémon data which contains the requested species, gender, and form.</param>
/// <param name="generation">Specific generation to iterate versions for.</param>
/// <param name="context">Specific generation to iterate versions for.</param>
/// <param name="moves">Moves that the resulting <see cref="IEncounterable"/> must be able to learn.</param>
/// <returns>A consumable <see cref="IEncounterable"/> list of possible encounters.</returns>
public static IEnumerable<IEncounterable> GenerateEncounter(PKM pk, byte generation, ReadOnlyMemory<ushort> moves)
public static IEnumerable<IEncounterable> GenerateEncounter(PKM pk, EntityContext context, ReadOnlyMemory<ushort> moves)
{
var vers = GameUtil.GetVersionsInGeneration(generation, pk.Version);
var vers = GameUtil.GetVersionsInGeneration(context, pk.Version);
return GenerateEncounters(pk, moves, vers);
}
@ -84,7 +84,7 @@ public static IEnumerable<IEncounterable> GenerateEncounters(PKM pk, ReadOnlyMem
if (!IsSane(pk, moves.Span))
yield break;
var vers = versions.Length != 0 ? versions : GameUtil.GetVersionsWithinRange(pk, pk.Format);
var vers = versions.Length != 0 ? versions : GameUtil.GetVersionsWithinRange(pk, pk.Context);
foreach (var version in vers)
{
foreach (var enc in GenerateVersionEncounters(pk, moves, version))
@ -382,7 +382,7 @@ private static bool IsSane(ReadOnlySpan<EvoCriteria> chain, IEncounterTemplate e
return true;
if (enc is IEncounterFormRandom { IsRandomUnspecificForm: true } or { Species: (ushort)Species.Unown })
return true;
if (enc is EncounterStatic7 {IsTotem: true} && evo.Form == 0 && current.Generation > 7) // totems get form wiped
if (enc is EncounterStatic7 {IsTotem: true} && evo.Form == 0 && current != EntityContext.Gen7) // totems get form wiped
return true;
break;
}

View File

@ -43,7 +43,7 @@ private static void GetLegalityReportLines(in LegalityLocalizationContext la, Li
var info = l.Info;
var pk = l.Entity;
LegalityFormatting.AddMoves(la, info.Moves, lines, pk.Format, false);
LegalityFormatting.AddMoves(la, info.Moves, lines, pk.Context, false);
if (pk.Format >= 6)
LegalityFormatting.AddRelearn(la, info.Relearn, lines, false);
LegalityFormatting.AddSecondaryChecksInvalid(la, l.Results, lines);
@ -66,10 +66,9 @@ private static List<string> GetVerboseLegalityReportLines(in LegalityLocalizatio
var pk = l.Entity;
int initialCount = lines.Count;
var format = pk.Format;
LegalityFormatting.AddMoves(la, info.Moves, lines, format, true);
LegalityFormatting.AddMoves(la, info.Moves, lines, pk.Context, true);
if (format >= 6)
if (pk.Format >= 6)
LegalityFormatting.AddRelearn(la, info.Relearn, lines, true);
if (lines.Count != initialCount) // move info added, break for next section

View File

@ -66,7 +66,7 @@ public static void AddRelearn(LegalityLocalizationContext la, ReadOnlySpan<MoveR
}
}
public static void AddMoves(LegalityLocalizationContext la, ReadOnlySpan<MoveResult> moves, List<string> lines, in byte currentFormat, bool state)
public static void AddMoves(LegalityLocalizationContext la, ReadOnlySpan<MoveResult> moves, List<string> lines, in EntityContext currentFormat, bool state)
{
for (int i = 0; i < moves.Length; i++)
{

View File

@ -9,6 +9,7 @@ public sealed class LearnGroup1 : ILearnGroup
{
public static readonly LearnGroup1 Instance = new();
private const byte Generation = 1;
private const EntityContext Context = EntityContext.Gen1;
public ushort MaxMoveID => Legal.MaxMoveID_1;
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => pk.Context switch
@ -45,7 +46,7 @@ private static void FlagEvolutionSlots(Span<MoveResult> result, ReadOnlySpan<ush
continue;
var detail = result[i];
if (!detail.Valid || detail.Generation is not (1 or 2))
if (!detail.Valid || detail.Context is not (EntityContext.Gen1 or EntityContext.Gen2))
continue;
var info = detail.Info;
@ -53,7 +54,7 @@ private static void FlagEvolutionSlots(Span<MoveResult> result, ReadOnlySpan<ush
{
var level = info.Argument;
var stage = detail.EvoStage;
var chain = detail.Generation is 1 ? history.Gen1 : history.Gen2;
var chain = detail.Context is EntityContext.Gen1 ? history.Gen1 : history.Gen2;
var species = chain[stage].Species;
if (IsAnyOtherResultALowerEvolutionStageAndHigherLevel(result, i, history, level, species))
result[i] = MoveResult.Unobtainable();
@ -69,7 +70,7 @@ private static bool IsAnyOtherResultALowerEvolutionStageAndHigherLevel(Span<Move
if (i == index)
continue;
var detail = result[i];
if (!detail.Valid || detail.Generation is not (1 or 2))
if (!detail.Valid || detail.Context is not (EntityContext.Gen1 or EntityContext.Gen2))
continue;
(var method, _, byte level2) = detail.Info;
@ -77,7 +78,7 @@ private static bool IsAnyOtherResultALowerEvolutionStageAndHigherLevel(Span<Move
continue;
var stage = detail.EvoStage;
var chain = detail.Generation is 1 ? history.Gen1 : history.Gen2;
var chain = detail.Context is EntityContext.Gen1 ? history.Gen1 : history.Gen2;
var species2 = chain[stage].Species;
if (level2 > level && species2 < species)
return true;
@ -170,19 +171,19 @@ private static void Check(Span<MoveResult> result, ReadOnlySpan<ushort> current,
for (int i = result.Length - 1; i >= 0; i--)
{
ref var entry = ref result[i];
if (entry is { Valid: true, Generation: > 2 })
if (entry is { Valid: true, Context: not (EntityContext.Gen1 or EntityContext.Gen2) })
continue;
var move = current[i];
var chk = yw.GetCanLearn(pk, yp, evo, move, types);
if (chk != default && GetIsPreferable(entry, chk, stage))
{
entry = new(chk, (byte)stage, Generation);
entry = new(chk, (byte)stage, Context);
continue;
}
chk = rb.GetCanLearn(pk, rp, evo, move, types);
if (chk != default && GetIsPreferable(entry, chk, stage))
entry = new(chk, (byte)stage, Generation);
entry = new(chk, (byte)stage, Context);
}
}

View File

@ -8,14 +8,14 @@ namespace PKHeX.Core;
public sealed class LearnGroup2 : ILearnGroup
{
public static readonly LearnGroup2 Instance = new();
private const byte Generation = 2;
private const EntityContext Context = EntityContext.Gen2;
public ushort MaxMoveID => Legal.MaxMoveID_2;
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => pk.Context switch
{
EntityContext.Gen2 when enc.Generation == 1 => LearnGroup1.Instance,
EntityContext.Gen2 when enc.Context == EntityContext.Gen1 => LearnGroup1.Instance,
EntityContext.Gen1 => null,
_ => enc.Generation != 1 && !pk.Korean && history.HasVisitedGen1 ? LearnGroup1.Instance : null,
_ => enc.Context != EntityContext.Gen1 && !pk.Korean && history.HasVisitedGen1 ? LearnGroup1.Instance : null,
};
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedGen2;
@ -23,7 +23,7 @@ public sealed class LearnGroup2 : ILearnGroup
public bool Check(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvolutionHistory history, IEncounterTemplate enc,
MoveSourceType types = MoveSourceType.All, LearnOption option = LearnOption.Current)
{
if (enc.Generation == Generation && types.HasFlag(MoveSourceType.Encounter))
if (enc.Context == Context && types.HasFlag(MoveSourceType.Encounter))
CheckEncounterMoves(pk, result, current, enc);
var evos = history.Gen2;
@ -55,7 +55,7 @@ public sealed class LearnGroup2 : ILearnGroup
if (!move.IsParsed)
continue;
var method = move.Info.Method;
if ((vc1 && move.Generation == 2) || method is LearnMethod.Initial || method.IsEggSource)
if ((vc1 && move.Context == EntityContext.Gen2) || method is LearnMethod.Initial || method.IsEggSource)
result[i] = MoveResult.Unobtainable();
}
@ -116,14 +116,14 @@ private static void Check(Span<MoveResult> result, ReadOnlySpan<ushort> current,
for (int i = result.Length - 1; i >= 0; i--)
{
ref var entry = ref result[i];
if (entry is { Valid: true, Generation: > 2 })
if (entry is { Valid: true, Context: not (EntityContext.Gen1 or EntityContext.Gen2) })
continue;
var move = current[i];
var chk = gs.GetCanLearn(pk, gp, evo, move, types);
if (chk != default && GetIsPreferable(entry, chk, stage))
{
entry = new(chk, (byte)stage, Generation);
entry = new(chk, (byte)stage, Context);
continue;
}
@ -132,13 +132,13 @@ private static void Check(Span<MoveResult> result, ReadOnlySpan<ushort> current,
chk = c.GetCanLearn(pk, cp, evo, move, types);
if (chk != default && GetIsPreferable(entry, chk, stage))
entry = new(chk, (byte)stage, Generation);
entry = new(chk, (byte)stage, Context);
if (stad2)
{
chk = LearnSource2Stadium.Instance.GetCanRelearn(evo, move, types);
if (chk != default && GetIsPreferable(entry, chk, stage))
entry = new(chk, (byte)stage, Generation);
entry = new(chk, (byte)stage, Context);
}
}
}
@ -168,7 +168,7 @@ private static bool GetIsPreferable(in MoveResult entry, in MoveLearnInfo chk, i
public void GetAllMoves(Span<bool> result, PKM pk, EvolutionHistory history, IEncounterTemplate enc, MoveSourceType types = MoveSourceType.All, LearnOption option = LearnOption.Current)
{
if (types.HasFlag(MoveSourceType.Encounter) && enc.Generation == Generation)
if (types.HasFlag(MoveSourceType.Encounter) && enc.Context == Context)
FlagEncounterMoves(pk, enc, result);
foreach (var evo in history.Gen2)

View File

@ -8,7 +8,7 @@ namespace PKHeX.Core;
public sealed class LearnGroup3 : ILearnGroup
{
public static readonly LearnGroup3 Instance = new();
private const byte Generation = 3;
private const EntityContext Context = EntityContext.Gen3;
public ushort MaxMoveID => Legal.MaxMoveID_3;
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => null; // Gen3 is the end of the line!
@ -21,7 +21,7 @@ public sealed class LearnGroup3 : ILearnGroup
for (var i = 0; i < evos.Length; i++)
Check(result, current, pk, evos[i], i, types);
if (types.HasFlag(MoveSourceType.Encounter) && enc is EncounterEgg3 { Generation: Generation } egg)
if (types.HasFlag(MoveSourceType.Encounter) && enc is EncounterEgg3 { Context: Context } egg)
CheckEncounterMoves(result, current, egg);
if (types.HasFlag(MoveSourceType.LevelUp) && enc.Species is (int)Species.Nincada && evos is [{ Species: (int)Species.Shedinja }, _])
@ -61,7 +61,7 @@ private static void CheckNincadaMoves(Span<MoveResult> result, ReadOnlySpan<usho
continue;
var info = new MoveLearnInfo(LearnMethod.ShedinjaEvo, LearnEnvironment.E, level);
result[i] = new MoveResult(info, 0, Generation);
result[i] = new MoveResult(info, 0, Context);
break; // Can only have one Ninjask move.
}
}
@ -116,30 +116,30 @@ private static void Check(Span<MoveResult> result, ReadOnlySpan<ushort> current,
var chk = e.GetCanLearn(pk, ep, evo, move, types);
if (chk != default)
{
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
continue;
}
chk = rs.GetCanLearn(pk, rp, evo, move, types & (MoveSourceType.LevelUp | MoveSourceType.AllTutors));
if (chk != default)
{
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
continue;
}
chk = fr.GetCanLearn(pk, fp, evo, move, types & (MoveSourceType.LevelUp | MoveSourceType.AllTutors));
if (chk != default)
{
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
continue;
}
chk = lg.GetCanLearn(pk, lp, evo, move, types & MoveSourceType.LevelUp); // Tutors same as FR
if (chk != default)
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
}
}
public void GetAllMoves(Span<bool> result, PKM pk, EvolutionHistory history, IEncounterTemplate enc, MoveSourceType types = MoveSourceType.All, LearnOption option = LearnOption.Current)
{
if (types.HasFlag(MoveSourceType.Encounter) && enc.Generation == Generation)
if (types.HasFlag(MoveSourceType.Encounter) && enc.Context == Context)
FlagEncounterMoves(enc, result);
var evos = history.Gen3;

View File

@ -8,10 +8,10 @@ namespace PKHeX.Core;
public sealed class LearnGroup4 : ILearnGroup
{
public static readonly LearnGroup4 Instance = new();
private const byte Generation = 4;
private const EntityContext Context = EntityContext.Gen4;
public ushort MaxMoveID => Legal.MaxMoveID_4;
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => enc.Generation is Generation ? null : LearnGroup3.Instance;
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => enc.Context is EntityContext.Gen4 ? null : LearnGroup3.Instance;
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedGen4;
public bool Check(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvolutionHistory history,
@ -53,7 +53,7 @@ private static void CheckNincadaMoves(Span<MoveResult> result, ReadOnlySpan<usho
continue;
var info = new MoveLearnInfo(LearnMethod.ShedinjaEvo, LearnEnvironment.Pt, level);
result[i] = new MoveResult(info, 0, Generation);
result[i] = new MoveResult(info, 0, Context);
break; // Can only have one Ninjask move.
}
}
@ -125,27 +125,27 @@ private static void CheckInternal(Span<MoveResult> result, ReadOnlySpan<ushort>
var chk = hgss.GetCanLearn(pk, hgss_pi, evo, move);
if (chk != default)
{
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
continue;
}
chk = pt.GetCanLearn(pk, pt_pi, evo, move, MoveSourceType.LevelUp | MoveSourceType.AllMachines);
if (chk != default)
{
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
continue;
}
chk = dp.GetCanLearn(pk, pt_pi, evo, move, MoveSourceType.LevelUp);
if (chk != default)
{
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
}
}
}
public void GetAllMoves(Span<bool> result, PKM pk, EvolutionHistory history, IEncounterTemplate enc, MoveSourceType types = MoveSourceType.All, LearnOption option = LearnOption.Current)
{
if (types.HasFlag(MoveSourceType.Encounter) && enc.Generation == Generation)
if (types.HasFlag(MoveSourceType.Encounter) && enc.Context == Context)
FlagEncounterMoves(enc, result);
var evos = history.Gen4;

View File

@ -8,10 +8,10 @@ namespace PKHeX.Core;
public sealed class LearnGroup5 : ILearnGroup
{
public static readonly LearnGroup5 Instance = new();
private const byte Generation = 5;
private const EntityContext Context = EntityContext.Gen5;
public ushort MaxMoveID => Legal.MaxMoveID_5;
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => enc.Generation is Generation ? null : LearnGroup4.Instance;
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => enc.Context is EntityContext.Gen5 ? null : LearnGroup4.Instance;
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedGen5;
public bool Check(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvolutionHistory history,
@ -86,7 +86,7 @@ private static void CheckInternal(Span<MoveResult> result, ReadOnlySpan<ushort>
var move = current[i];
var chk = b2w2.GetCanLearn(pk, b2w2_pi, evo, move, types, option);
if (chk != default)
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
if (bw_pi is null)
continue;
@ -94,13 +94,13 @@ private static void CheckInternal(Span<MoveResult> result, ReadOnlySpan<ushort>
// B2/W2 is the same besides some level up moves.
chk = LearnSource5BW.Instance.GetCanLearn(pk, bw_pi, evo, move, types & MoveSourceType.LevelUp, option);
if (chk != default)
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
}
}
public void GetAllMoves(Span<bool> result, PKM pk, EvolutionHistory history, IEncounterTemplate enc, MoveSourceType types = MoveSourceType.All, LearnOption option = LearnOption.Current)
{
if (types.HasFlag(MoveSourceType.Encounter) && enc.Generation == Generation)
if (types.HasFlag(MoveSourceType.Encounter) && enc.Context == Context)
FlagEncounterMoves(enc, result);
foreach (var evo in history.Gen5)

View File

@ -9,9 +9,10 @@ public sealed class LearnGroup6 : ILearnGroup
{
public static readonly LearnGroup6 Instance = new();
private const byte Generation = 6;
private const EntityContext Context = EntityContext.Gen6;
public ushort MaxMoveID => Legal.MaxMoveID_6_AO;
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => enc.Generation is Generation ? null : LearnGroup5.Instance;
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => enc.Context is EntityContext.Gen6 ? null : LearnGroup5.Instance;
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedGen6;
public bool Check(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvolutionHistory history, IEncounterTemplate enc,
@ -64,7 +65,7 @@ private static void CheckDexNavMoves(Span<MoveResult> result, ReadOnlySpan<ushor
var move = current[i];
if (!dexnav.IsMoveBonus(move))
continue;
result[i] = new(new(LearnMethod.Encounter, LearnEnvironment.ORAS), Generation: Generation);
result[i] = new(new(LearnMethod.Encounter, LearnEnvironment.ORAS), Context: Context);
break;
}
}
@ -137,13 +138,13 @@ private static void CheckBoth(Span<MoveResult> result, ReadOnlySpan<ushort> curr
var chk = ao.GetCanLearn(pk, ao_pi, evo, move, types, option);
if (chk != default)
{
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
continue;
}
chk = xy.GetCanLearn(pk, xy_pi, evo, move, types & MoveSourceType.LevelUp, option);
if (chk != default)
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
}
}
@ -163,13 +164,13 @@ private static void CheckBoth(Span<MoveResult> result, ReadOnlySpan<ushort> curr
var move = current[i];
var chk = game.GetCanLearn(pk, pi, evo, move, types, option);
if (chk != default)
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
}
}
public void GetAllMoves(Span<bool> result, PKM pk, EvolutionHistory history, IEncounterTemplate enc, MoveSourceType types = MoveSourceType.All, LearnOption option = LearnOption.Current)
{
if (types.HasFlag(MoveSourceType.Encounter) && enc.Generation == Generation)
if (types.HasFlag(MoveSourceType.Encounter) && enc.Context == Context)
FlagEncounterMoves(enc, result);
var mode = GetCheckMode(enc, pk);

View File

@ -1,4 +1,5 @@
using System;
using static PKHeX.Core.EntityContext;
namespace PKHeX.Core;
@ -8,14 +9,15 @@ namespace PKHeX.Core;
public sealed class LearnGroup7 : ILearnGroup
{
public static readonly LearnGroup7 Instance = new();
private const EntityContext Context = Gen7;
private const byte Generation = 7;
public ushort MaxMoveID => Legal.MaxMoveID_7_USUM;
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => enc.Generation switch
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => enc.Context switch
{
1 => LearnGroup1.Instance,
2 => LearnGroup2.Instance,
(3 or 4 or 5 or 6) => LearnGroup6.Instance,
Gen1 => LearnGroup1.Instance,
Gen2 => LearnGroup2.Instance,
(Gen3 or Gen4 or Gen5 or Gen6) => LearnGroup6.Instance,
_ => null,
};
@ -58,7 +60,7 @@ private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<us
private static CheckMode GetCheckMode(IEncounterTemplate enc, PKM pk)
{
// We can check if it has visited specific sources. We won't check the games it hasn't visited.
if (enc.Context != EntityContext.Gen7 || !pk.IsUntraded)
if (enc.Context != Gen7 || !pk.IsUntraded)
return CheckMode.Both;
if (pk.USUM)
return CheckMode.USUM;
@ -119,7 +121,7 @@ private static void CheckBoth(Span<MoveResult> result, ReadOnlySpan<ushort> curr
var chk = uu.GetCanLearn(pk, uu_pi, evo, move, types, option);
if (chk != default)
{
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
continue;
}
@ -127,7 +129,7 @@ private static void CheckBoth(Span<MoveResult> result, ReadOnlySpan<ushort> curr
continue;
chk = sm.GetCanLearn(pk, uu_pi, evo, move, types & MoveSourceType.LevelUp, option);
if (chk != default)
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
}
}
@ -145,13 +147,13 @@ private static void CheckSingle(Span<MoveResult> result, ReadOnlySpan<ushort> cu
var move = current[i];
var chk = game.GetCanLearn(pk, pi, evo, move, types, option);
if (chk != default)
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
}
}
public void GetAllMoves(Span<bool> result, PKM pk, EvolutionHistory history, IEncounterTemplate enc, MoveSourceType types = MoveSourceType.All, LearnOption option = LearnOption.Current)
{
if (types.HasFlag(MoveSourceType.Encounter) && enc.Generation == Generation)
if (types.HasFlag(MoveSourceType.Encounter) && enc.Context == Context)
FlagEncounterMoves(enc, result);
var mode = GetCheckMode(enc, pk);

View File

@ -8,7 +8,7 @@ namespace PKHeX.Core;
public sealed class LearnGroup7b : ILearnGroup
{
public static readonly LearnGroup7b Instance = new();
private const byte Generation = 7;
private const EntityContext Context = EntityContext.Gen7b;
public ushort MaxMoveID => Legal.MaxMoveID_7b;
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => null;
@ -38,13 +38,13 @@ private static void Check(Span<MoveResult> result, ReadOnlySpan<ushort> current,
var move = current[i];
var chk = game.GetCanLearn(pk, pi, evo, move);
if (chk != default)
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
}
}
public void GetAllMoves(Span<bool> result, PKM pk, EvolutionHistory history, IEncounterTemplate enc, MoveSourceType types = MoveSourceType.All, LearnOption option = LearnOption.Current)
{
if (types.HasFlag(MoveSourceType.Encounter) && enc.Generation == Generation)
if (types.HasFlag(MoveSourceType.Encounter) && enc.Context == Context)
FlagEncounterMoves(enc, result);
foreach (var evo in history.Gen7b)

View File

@ -128,7 +128,7 @@ private static void CheckInternal(Span<MoveResult> result, ReadOnlySpan<ushort>
var move = current[i];
var chk = game.GetCanLearn(pk, pi, evo, move, type, option);
if (chk != default)
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
}
}

View File

@ -45,7 +45,7 @@ private static void Check(Span<MoveResult> result, ReadOnlySpan<ushort> current,
var move = current[i];
var chk = game.GetCanLearn(pk, pi, evo, move);
if (chk != default)
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
}
}

View File

@ -87,7 +87,7 @@ private static void CheckInternal(Span<MoveResult> result, ReadOnlySpan<ushort>
var move = current[i];
var chk = game.GetCanLearn(pk, pi, evo, move, type, option);
if (chk != default)
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
}
}

View File

@ -109,7 +109,7 @@ private static void CheckInternal(Span<MoveResult> result, ReadOnlySpan<ushort>
var move = current[i];
var chk = game.GetCanLearn(pk, pi, evo, move, type, option);
if (chk != default)
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
}
}

View File

@ -66,7 +66,7 @@ private static void CheckInternal(Span<MoveResult> result, ReadOnlySpan<ushort>
var move = current[i];
var chk = game.GetCanLearn(pk, pi, evo, move, type, option);
if (chk != default)
result[i] = new(chk, (byte)stage, Generation);
result[i] = new(chk, (byte)stage, Context);
}
}

View File

@ -123,7 +123,7 @@ private static bool CleanPurge(Span<MoveResult> result, ReadOnlySpan<ushort> cur
for (int i = 0; i < result.Length; i++)
{
ref var r = ref result[i];
if (!r.Valid || r.Generation == 0)
if (!r.Valid || r.Context == 0)
continue;
if (r.Info.Environment == local.Environment)
@ -146,7 +146,7 @@ private static bool CleanPurge(Span<MoveResult> result, ReadOnlySpan<ushort> cur
// Most games do not have a Learn Source for Volt Tackle besides it being specially inserted for Egg Encounters.
if (!valid && move is not (ushort)Move.VoltTackle)
{
if (r.Generation >= 8 || local is not LearnSource8SWSH)
if (r.Context.IsEraHOME || local is not LearnSource8SWSH)
r = default;
}
}

View File

@ -2,6 +2,7 @@
namespace PKHeX.Core;
using static LearnEnvironment;
using static EntityContext;
/// <summary>
/// Indicates the group of game(s) that the move was learned in.
@ -36,9 +37,9 @@ public static class LearnEnvironmentExtensions
extension(LearnEnvironment value)
{
/// <summary>
/// Indicates whether the <see cref="LearnEnvironment"/> is specified (not <see cref="None"/>), and thus worth indicating.
/// Indicates whether the <see cref="LearnEnvironment"/> is specified (not <see cref="LearnEnvironment.None"/>), and thus worth indicating.
/// </summary>
public bool IsSpecified => value is not None;
public bool IsSpecified => value is not LearnEnvironment.None;
/// <summary>
/// Gets the generation number [1-n] for the given <see cref="LearnEnvironment"/>.
@ -57,6 +58,24 @@ public static class LearnEnvironmentExtensions
_ => 0,
};
public EntityContext Context => value switch
{
RB or YW => Gen1,
GS or C or Stadium2 => Gen2,
RS or E or FR or LG => Gen3,
DP or Pt or HGSS => Gen4,
BW or B2W2 => Gen5,
XY or ORAS => Gen6,
SM or USUM => Gen7,
GG => Gen7b,
SWSH => Gen8,
PLA => Gen8a,
BDSP => Gen8b,
SV => Gen9,
ZA => Gen9a,
_ => 0,
};
/// <summary>
/// Retrieves the evolution criteria for the given <see cref="LearnEnvironment"/> from the provided <see cref="EvolutionHistory"/>.
/// </summary>

View File

@ -8,26 +8,32 @@ namespace PKHeX.Core;
/// </summary>
/// <param name="Info">Info about the game it was learned in.</param>
/// <param name="EvoStage">Evolution stage index within the <see cref="EntityContext"/> evolution list it existed in.</param>
/// <param name="Generation">Rough indicator of generation the <see cref="MoveLearnInfo.Environment"/> was.</param>
/// <param name="Context">Rough indicator of generation the <see cref="MoveLearnInfo.Environment"/> was.</param>
/// <param name="Expect">Optional value used when the move is not legal, to indicate that another move ID should have been in that move slot instead.</param>
public readonly record struct MoveResult(MoveLearnInfo Info, byte EvoStage = 0, byte Generation = 0, ushort Expect = 0)
public readonly record struct MoveResult(MoveLearnInfo Info, byte EvoStage = 0, EntityContext Context = 0, ushort Expect = 0)
{
public bool IsParsed => this != default;
public bool Valid => Info.Method.IsValid;
internal MoveResult(LearnMethod method, LearnEnvironment game) : this(new MoveLearnInfo(method, game), Generation: game.Generation) { }
internal MoveResult(LearnMethod method, LearnEnvironment game) : this(new MoveLearnInfo(method, game), Context: game.Context) { }
private MoveResult(LearnMethod method) : this(new MoveLearnInfo(method, LearnEnvironment.None)) { }
public string Summary(in LegalityLocalizationContext ctx)
{
var sb = new StringBuilder(48);
Append(ctx, sb);
return sb.ToString();
}
public void Append(in LegalityLocalizationContext ctx, StringBuilder sb)
{
Info.Summarize(sb, ctx.Settings.Moves);
if (Info.Method.HasExpectedMove())
{
var name = ctx.GetMoveName(Expect);
var str = ctx.Settings.Lines.MoveFExpectSingle_0;
sb.Append(' ').AppendFormat(str, name);
return sb.ToString();
return;
}
var la = ctx.Analysis;
@ -36,14 +42,13 @@ public string Summary(in LegalityLocalizationContext ctx)
var detail = GetDetail(history);
if (detail.Species == 0)
return sb.ToString();
return;
if (detail.Species == current.Species && detail.Form == current.Form)
return sb.ToString();
return;
sb.Append(' ').Append(ctx.GetSpeciesName(detail.Species));
if (detail.Form != current.Form)
sb.Append('-').Append(detail.Form);
return sb.ToString();
sb.Append('-').Append(ctx.GetFormName(detail.Species, detail.Form, Info.Environment.Context));
}
private EvoCriteria GetDetail(EvolutionHistory history)

View File

@ -199,7 +199,7 @@ private void ParsePK1()
Level.Verify(this);
Level.VerifyG1(this);
Trainer.VerifyOTGB(this);
MiscValues.VerifyMiscG1(this);
MiscValues.VerifyMiscG12(this);
MovePP.Verify(this);
if (Entity.Format == 2)
Item.Verify(this);
@ -332,7 +332,6 @@ private void UpdateChecks()
return;
HyperTraining.Verify(this);
MiscValues.VerifyVersionEvolution(this);
Trash.Verify(this);
if (format < 8)

View File

@ -42,6 +42,7 @@ public static LegalityLocalizationContext Create(LegalityAnalysis la, string lan
public string GetConsoleRegion3DS(int index) => GetSafe(Strings.console3ds, index);
public string GetRibbonName(RibbonIndex index) => Strings.Ribbons.GetNameSafe($"Ribbon{index}", out var result) ? result : index.ToString();
public string GetLanguageName(int index) => GetSafe(Strings.languageNames, index);
public string GetFormName(ushort species, byte form, EntityContext context) => FormConverter.GetStringFromForm(species, form, Strings, context);
private static string GetSafe(ReadOnlySpan<string> arr, int index)
{
@ -142,12 +143,12 @@ private string GetMemory(CheckResult chk, string template, LegalityCheckResultCo
>= MAX => throw new ArgumentOutOfRangeException(nameof(code), code, null),
};
public string FormatMove(in MoveResult move, int index, byte currentFormat)
public string FormatMove(in MoveResult move, int index, EntityContext current)
{
var result = Format(move, index, Settings.Moves.FormatMove);
var gen = move.Generation;
if (currentFormat != gen && gen != 0)
result += $" [Gen{gen}]";
var original = move.Context;
if (current != original && original != 0)
result += $" [{original}]";
return result;
}

View File

@ -198,7 +198,7 @@ public static TimeCapsuleEvaluation IsTimeCapsuleTransferred(PK1 pk, ReadOnlySpa
if (rate == 0)
return Transferred12;
if (MoveInfo.IsAnyFromGeneration(2, moves))
if (MoveInfo.IsAnyFromGeneration(EntityContext.Gen2, moves))
{
if (pk is {CatchRate: not 0} && !ItemConverter.IsCatchRateHeldItem(pk.CatchRate))
return BadCatchRate;

View File

@ -33,9 +33,9 @@ public static MemoryArgType GetMemoryArgType(byte memory, int memoryGen)
return (MemoryArgType)type;
}
public static MemoryContext GetContext(EntityContext context) => context.Generation switch
public static MemoryContext GetContext(EntityContext context) => context switch
{
<=7 => MemoryContext6.Instance,
EntityContext.Gen6 or EntityContext.Gen7 => MemoryContext6.Instance,
_ => MemoryContext8.Instance,
};

View File

@ -128,9 +128,9 @@ private static bool IsFilteredLookBack(ReadOnlySpan<char> message, EntityContext
return true;
}
var generation = original.Generation;
if (generation > 7 || original is EntityContext.Gen7b)
if (original.IsEraHOME || original is EntityContext.Gen7b)
{
// Already checked above -- done.
type = WordFilterType.None;
return false;
}
@ -141,7 +141,7 @@ private static bool IsFilteredLookBack(ReadOnlySpan<char> message, EntityContext
return true;
}
if (generation == 5 && WordFilter5.IsFiltered(message, out regMatch))
if (original == EntityContext.Gen5 && WordFilter5.IsFiltered(message, out regMatch))
{
type = WordFilterType.Gen5;
return true;

View File

@ -42,10 +42,10 @@ public static class WordFilterTypeExtensions
public static WordFilterType GetName(EntityContext type) => type.Console switch
{
GameConsole.NX => WordFilterType.NintendoSwitch,
_ => type.Generation switch
_ => type switch
{
5 => WordFilterType.Gen5,
6 or 7 => WordFilterType.Nintendo3DS,
EntityContext.Gen5 => WordFilterType.Gen5,
EntityContext.Gen6 or EntityContext.Gen7 => WordFilterType.Nintendo3DS,
_ => WordFilterType.None,
},
};

View File

@ -145,7 +145,7 @@ public static bool IsFormChangeable(ushort species, byte oldForm, byte newForm,
{
if (origin == EntityContext.Gen5)
return true; // B/W or B2/W2 change via seasons
if (current.Generation >= 8)
if (current.IsEraHOME)
return true; // Via S/V change via in-game province on startup.
}
return false;

View File

@ -0,0 +1,77 @@
using static PKHeX.Core.LegalityCheckResultCode;
using static PKHeX.Core.CheckIdentifier;
namespace PKHeX.Core;
internal sealed class EggVerifier : Verifier
{
protected override CheckIdentifier Identifier => Misc;
public override void Verify(LegalityAnalysis data)
{
var pk = data.Entity;
if (pk.IsEgg)
Verify(data, pk);
}
internal void Verify(LegalityAnalysis data, PKM pk)
{
VerifyCommon(data, pk);
// No egg have contest stats from the encounter.
if (pk is IContestStatsReadOnly s && s.HasContestStats())
data.AddLine(GetInvalid(Egg, EggContest));
// Cannot transfer eggs across contexts (must be hatched).
var e = data.EncounterOriginal;
if (e.Context != pk.Context)
data.AddLine(GetInvalid(Egg, TransferEggVersion));
switch (pk)
{
// Side Game: No Eggs
case SK2 or CK3 or XK3 or BK4 or RK4 when e.Context == pk.Context:
data.AddLine(GetInvalid(Egg, TransferEggVersion));
break;
// All Eggs are Japanese and flagged specially for localized string
case PK3 when pk.Language != 1:
data.AddLine(GetInvalid(Egg, OTLanguageShouldBe_0, (byte)LanguageID.Japanese));
break;
}
if (pk is IHomeTrack { HasTracker: true })
data.AddLine(GetInvalid(TransferTrackerShouldBeZero));
}
internal void VerifyCommon(LegalityAnalysis data, PKM pk)
{
var enc = data.EncounterMatch;
if (!EggStateLegality.GetIsEggHatchCyclesValid(pk, enc))
data.AddLine(GetInvalid(Egg, EggHatchCycles));
if (pk.Format >= 6 && enc is IEncounterEgg && !MovesMatchRelearn(pk))
data.AddLine(GetInvalid(Egg, MovesShouldMatchRelearnMoves));
if (pk is ITechRecord record)
{
if (record.GetMoveRecordFlagAny())
data.AddLine(GetInvalid(Egg, EggRelearnFlags));
if (pk.StatNature != pk.Nature)
data.AddLine(GetInvalid(Egg, EggNature));
}
}
private static bool MovesMatchRelearn(PKM pk)
{
if (pk.Move1 != pk.RelearnMove1)
return false;
if (pk.Move2 != pk.RelearnMove2)
return false;
if (pk.Move3 != pk.RelearnMove3)
return false;
if (pk.Move4 != pk.RelearnMove4)
return false;
return true;
}
}

View File

@ -124,9 +124,9 @@ private static bool IsTradeEvolutionRequired(LegalityAnalysis data, IEncounterTe
var moves = data.Info.Moves;
// Gen2 stuff can be traded between Gen2 games holding an Everstone, assuming it hasn't been transferred to Gen1 for special moves.
if (enc.Generation == 2)
return MoveInfo.IsAnyFromGeneration(1, moves);
return MoveInfo.IsAnyFromGeneration(EntityContext.Gen1, moves);
// Gen1 stuff can only be un-evolved if it was never traded from the OT.
if (MoveInfo.IsAnyFromGeneration(2, moves))
if (MoveInfo.IsAnyFromGeneration(EntityContext.Gen2, moves))
return true; // traded to Gen2 for special moves
if (pk.Format != 1)
return true; // traded to Gen2 (current state)

View File

@ -200,9 +200,9 @@ public void VerifyVCEncounter(PKM pk, IEncounterTemplate original, EncounterTran
// Flag Moves that cannot be transferred
if (original is EncounterStatic2 { IsDizzyPunchEgg: true }) // Dizzy Punch Gifts
FlagIncompatibleTransferMove(pk, data.Info.Moves, 146, 2); // can't have Dizzy Punch at all
FlagIncompatibleTransferMove(pk, data.Info.Moves, 146, EntityContext.Gen2); // can't have Dizzy Punch at all
bool checkShiny = pk.VC2 || original.Generation == 2 || MoveInfo.IsAnyFromGeneration(2, data.Info.Moves);
bool checkShiny = pk.VC2 || original.Generation == 2 || MoveInfo.IsAnyFromGeneration(EntityContext.Gen2, data.Info.Moves);
if (!checkShiny)
return;
@ -220,13 +220,13 @@ public void VerifyVCEncounter(PKM pk, IEncounterTemplate original, EncounterTran
}
}
private static void FlagIncompatibleTransferMove(PKM pk, Span<MoveResult> parse, ushort move, byte generation)
private static void FlagIncompatibleTransferMove(PKM pk, Span<MoveResult> parse, ushort move, EntityContext context)
{
int index = pk.GetMoveIndex(move);
if (index < 0)
return; // doesn't have move
if (parse[index].Generation == generation) // not obtained from a future gen
if (parse[index].Context == context) // not obtained from a future gen
parse[index] = MoveResult.Unobtainable(0);
}
}

View File

@ -243,11 +243,11 @@ private static byte GetType(ushort move, ReadOnlySpan<byte> types)
return types[move];
}
public static bool IsAnyFromGeneration(byte generation, ReadOnlySpan<MoveResult> moves)
public static bool IsAnyFromGeneration(EntityContext context, ReadOnlySpan<MoveResult> moves)
{
foreach (var move in moves)
{
if (move.Generation == generation)
if (move.Context == context)
return true;
}
return false;

View File

@ -61,7 +61,7 @@ public string GetTitleFromIndex(GameStrings strings)
{
args[0] = strings.Species[gift.Species];
// 1: category (e.g. "Victory Pokémon" for Victini)
args[2] = FormConverter.GetStringFromForm(gift.Form, strings, gift.Species, GameInfo.GenderSymbolASCII, gift.Context);
args[2] = FormConverter.GetStringFromForm(gift.Species, gift.Form, strings, GameInfo.GenderSymbolASCII, gift.Context);
args[3] = gift.OriginalTrainerName;
args[4] = strings.Move[gift.Moves.Move1];
args[5] = strings.Move[gift.Moves.Move2];

View File

@ -114,7 +114,7 @@ public static void ChangeFormArgument(this IFormArgument f, ushort species, byte
var max = GetFormArgumentMax(species, form, context);
f.FormArgumentRemain = (byte)value;
if (value == max || (value == 0 && species is (int)Hoopa && form == 1 && context.Generation >= 8))
if (value == max || (value == 0 && species is (int)Hoopa && form == 1 && context.IsEraHOME))
{
f.FormArgumentElapsed = f.FormArgumentMaximum = 0;
return;
@ -132,31 +132,27 @@ public static void ChangeFormArgument(this IFormArgument f, ushort species, byte
/// <param name="species">Entity Species</param>
/// <param name="form">Entity Form</param>
/// <param name="context">Context to check with.</param>
public static uint GetFormArgumentMax(ushort species, byte form, EntityContext context)
public static uint GetFormArgumentMax(ushort species, byte form, EntityContext context) => species switch
{
var gen = context.Generation;
return species switch
{
(int)Furfrou when form != 0 => 5,
(int)Hoopa when form == 1 => 3,
(int)Yamask when form == 1 => 9999,
(int)Runerigus when form == 0 => 9999,
(int)Alcremie => (uint)AlcremieDecoration.Ribbon,
(int)Qwilfish when form == 1 && gen >= 8 => 9999,
(int)Overqwil => 9999, // 20
(int)Stantler or (int)Wyrdeer when gen >= 8 => 9999,
(int)Basculin when form == 2 => 9999, // 294
(int)Basculegion => 9999, // 294
(int)Primeape or (int)Annihilape when gen >= 8 => 9999,
(int)Bisharp or (int)Kingambit when gen >= 8 => 9999,
(int)Gimmighoul => 998,
(int)Gholdengo => 999,
(int)Koraidon or (int)Miraidon => 1,
(int)Farfetchd when form == 1 && gen >= 8 => 9999,
(int)Sirfetchd when gen >= 8 => 9999,
_ => 0,
};
}
(int)Furfrou when form != 0 => 5,
(int)Hoopa when form == 1 => 3,
(int)Yamask when form == 1 => 9999,
(int)Runerigus when form == 0 => 9999,
(int)Alcremie => (uint)AlcremieDecoration.Ribbon,
(int)Qwilfish when form == 1 && context.IsEraHOME => 9999,
(int)Overqwil => 9999, // 20
(int)Stantler or (int)Wyrdeer when context.IsEraHOME => 9999,
(int)Basculin when form == 2 => 9999, // 294
(int)Basculegion => 9999, // 294
(int)Primeape or (int)Annihilape when context.IsEraHOME => 9999,
(int)Bisharp or (int)Kingambit when context.IsEraHOME => 9999,
(int)Gimmighoul => 998,
(int)Gholdengo => 999,
(int)Koraidon or (int)Miraidon => 1,
(int)Farfetchd when form == 1 && context.IsEraHOME => 9999,
(int)Sirfetchd when context.IsEraHOME => 9999,
_ => 0,
};
/// <summary>
/// Gets the minimum value the <see cref="IFormArgument.FormArgument"/> value can be to satisfy an evolution requirement.

View File

@ -225,16 +225,16 @@ private bool FilterResultEgg(PKM pk)
public ReadOnlyMemory<GameVersion> GetVersions(SaveFile sav, GameVersion fallback)
{
if (Version > 0)
if (Version.IsValidSavedVersion())
return new[] {Version};
return Generation switch
return Context switch
{
1 when !ParseSettings.AllowGen1Tradeback => [RD, BU, GN, YW],
2 when sav is SAV2 {Korean: true} => [GD, SI],
1 or 2 => [RD, BU, GN, YW, /* */ GD, SI, C],
EntityContext.Gen1 when !ParseSettings.AllowGen1Tradeback => [RD, BU, GN, YW],
EntityContext.Gen2 when sav is SAV2 {Korean: true} => [GD, SI],
EntityContext.Gen1 or EntityContext.Gen2 => [RD, BU, GN, YW, /* */ GD, SI, C],
_ when fallback.Generation == Generation => GameUtil.GetVersionsWithinRange(sav, Generation).ToArray(),
_ when fallback.Context == Context => GameUtil.GetVersionsWithinRange(sav, Context).ToArray(),
_ => GameUtil.GameVersions,
};
}

View File

@ -30,10 +30,9 @@ public static class SearchUtil
private static bool CanReachContext(PKM pk, EntityContext context)
{
var generation = context.Generation;
if (generation <= 2)
if (context.IsEraGameBoy)
return pk.Format <= 2; // 1-2 can reach 1-2
if (generation <= 6)
if (context.IsEraPre3DS)
return pk.Format >= 3; // 3-6 can reach 3-6
return true; // 7+ can reach all contexts
}

View File

@ -11,6 +11,10 @@ namespace PKHeX.Core;
/// </summary>
public static class FormConverter
{
/// <inheritdoc cref="GetFormList(ushort, IReadOnlyList{string}, IReadOnlyList{string}, IReadOnlyList{string}, EntityContext)"/>
public static string[] GetFormList(ushort species, IReadOnlyList<string> types, IReadOnlyList<string> forms, EntityContext context)
=> GetFormList(species, types, forms, GameInfo.GenderSymbolUnicode, context);
/// <summary>
/// Gets a list of forms that the species can have.
/// </summary>
@ -1122,19 +1126,22 @@ public static bool IsCosplayPikachu(ReadOnlySpan<char> formName, ReadOnlySpan<st
/// <summary>
/// Converts a Form ID to string.
/// </summary>
/// <param name="species">Species ID the form belongs to</param>
/// <param name="form">Form to get the form name of</param>
/// <param name="strings">Localized string source to fetch with</param>
/// <param name="species">Species ID the form belongs to</param>
/// <param name="genders">List of genders names</param>
/// <param name="context">Format the form name should appear in</param>
public static string GetStringFromForm(byte form, GameStrings strings, ushort species, IReadOnlyList<string> genders, EntityContext context)
public static string GetStringFromForm(ushort species, byte form, GameStrings strings, IReadOnlyList<string> genders, EntityContext context)
{
var forms = GetFormList(species, strings.Types, strings.forms, genders, context);
var result = form >= forms.Length ? string.Empty : forms[form];
return result;
}
}
/// <inheritdoc cref="GetStringFromForm(ushort, byte, GameStrings, IReadOnlyList{string}, EntityContext)"/>
public static string GetStringFromForm(ushort species, byte form, GameStrings strings, EntityContext context)
=> GetStringFromForm(species, form, strings, GameInfo.GenderSymbolUnicode, context);
}
public sealed record MegaFormNames
{
public required string Regular { get; init; }

View File

@ -104,6 +104,12 @@ public static class EntityContextExtensions
_ => throw new ArgumentOutOfRangeException(nameof(value), value, null),
};
public bool IsEraGameBoy => value is Gen1 or Gen2;
public bool IsEraPre3DS => value.Generation is (>= 1 and <= 5);
public bool IsEraPreSwitch => value.Generation is (>= 1 and <= 7);
public bool IsEraHOME => value.Generation >= 8;
public bool IsSquareShinyDifferentiated => value is Gen8;
/// <summary>
/// Determines whether Mega Pokémon forms exist in the specified <see cref="EntityContext"/>.
/// </summary>

View File

@ -95,8 +95,6 @@ private static int GetFormatFromExtension(char last, EntityContext prefer)
{
if (last is >= '1' and <= '9')
return last - '0';
if (prefer.Generation <= 7 && last == 'x')
return 6;
return (int)prefer;
}

View File

@ -1,5 +1,3 @@
using System;
namespace PKHeX.Core;
/// <summary>

View File

@ -65,7 +65,7 @@ private void SetRegionFlag(int region, int index, bool value)
public static string[] GetFormNames4Dex(ushort species)
{
string[] formNames = FormConverter.GetFormList(species, GameInfo.Strings.types, GameInfo.Strings.forms, [], EntityContext.Gen4);
string[] formNames = FormConverter.GetFormList(species, GameInfo.Strings.types, GameInfo.Strings.forms, EntityContext.Gen4);
if (species == (int)Species.Pichu)
formNames = [MALE, FEMALE, formNames[1]]; // Spiky
return formNames;

View File

@ -114,7 +114,5 @@ public static class SaveFileTypeExtensions
Bulk7 => EntityContext.Gen7,
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
};
public byte Generation => type.Context.Generation;
}
}

View File

@ -70,32 +70,32 @@ public static string GetWallpaperResourceName(GameVersion version, int index)
SaveFileType.LGPE => string.Empty,
_ => type.Generation switch
_ => type.Context switch
{
3 => "rs",
4 => "dp",
5 => "bw",
6 or 7 => "xy",
EntityContext.Gen3 => "rs",
EntityContext.Gen4 => "dp",
EntityContext.Gen5 => "bw",
EntityContext.Gen6 or EntityContext.Gen7 => "xy", // roughly equivalent, only use X/Y's because they don't force checker-boxes.
_ => string.Empty,
},
};
private static string GetResourceSuffix(GameVersion version, int index) => version.Generation switch
private static string GetResourceSuffix(GameVersion version, int index) => version.Context switch
{
3 when version == E => "e",
3 when FRLG.Contains(version) && index > 12 => "frlg",
3 => "rs",
EntityContext.Gen3 when version == E => "e",
EntityContext.Gen3 when FRLG.Contains(version) && index > 12 => "frlg",
EntityContext.Gen3 => "rs",
4 when index <= 16 => "dp",
4 when version == Pt => "pt",
4 when HGSS.Contains(version) => "hgss",
EntityContext.Gen4 when index <= 16 => "dp",
EntityContext.Gen4 when version == Pt => "pt",
EntityContext.Gen4 when HGSS.Contains(version) => "hgss",
5 => B2W2.Contains(version) && index > 16 ? "b2w2" : "bw",
6 => ORAS.Contains(version) && index > 16 ? "ao" : "xy",
7 when !GG.Contains(version) => "xy",
8 when !SWSH.Contains(version) => "bdsp",
8 => "swsh",
9 => "sv",
EntityContext.Gen5 => B2W2.Contains(version) && index > 16 ? "b2w2" : "bw",
EntityContext.Gen6 => ORAS.Contains(version) && index > 16 ? "ao" : "xy",
EntityContext.Gen7 => "xy", // roughly equivalent, only use X/Y's because they don't force checker-boxes.
EntityContext.Gen8b => "bdsp",
EntityContext.Gen8 => "swsh",
EntityContext.Gen9 => "sv",
_ => string.Empty,
};
}

View File

@ -64,7 +64,7 @@ public void ToggleInterface(object o, EntityContext context)
}
Visible = TabStop = true;
bool smart = context.Generation < 6;
bool smart = context.IsEraPre3DS;
Label_Smart.Visible = smart; // show "Smart" for Gen3-5
Label_Clever.Visible = !smart; // show "Clever" for Gen6+
}

View File

@ -178,7 +178,7 @@ public static string AppendLegalityHint(in LegalityLocalizationContext la, strin
var chk = analysis.Info.Moves[i];
if (chk.Valid)
continue;
var hint = la.FormatMove(chk, i, la.Analysis.Info.Entity.Format);
var hint = la.FormatMove(chk, i, la.Analysis.Info.Entity.Context);
return Join(line, hint);
}
@ -187,7 +187,7 @@ public static string AppendLegalityHint(in LegalityLocalizationContext la, strin
var chk = analysis.Info.Relearn[i];
if (chk.Valid)
continue;
var hint = la.FormatMove(chk, i, la.Analysis.Info.Entity.Format);
var hint = la.FormatMove(chk, i, la.Analysis.Info.Entity.Context);
return Join(line, hint);
}

View File

@ -229,7 +229,7 @@ private static NumericUpDown GetNUD(byte min, byte max, bool hex) => new()
{
EntityContext.Gen5 => SpecialCharsGen5,
EntityContext.Gen6 or EntityContext.Gen7 or EntityContext.Gen7b => SpecialCharsGen67,
_ when context.Generation >= 8 => SpecialCharsGen8,
_ when !context.IsEraPreSwitch => SpecialCharsGen8,
_ => [], // Undocumented
};