mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-09-11 12:07:30 -05:00
Merge branch 'kwsch:master' into master
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>26.04.11</Version>
|
||||
<Version>26.05.05</Version>
|
||||
<LangVersion>14</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
|
||||
@@ -623,8 +623,8 @@ private void AddEVs(List<string> result, in BattleTemplateExportSettings setting
|
||||
var nameEVs = cfg.GetStatDisplay(settings.StatsEVs);
|
||||
var line = token switch
|
||||
{
|
||||
BattleTemplateToken.EVsWithNature => GetStringStatsNatureAmp(EVs, 0, nameEVs, Nature),
|
||||
BattleTemplateToken.EVsAppendNature => GetStringStatsNatureAmp(EVs, 0, nameEVs, Nature),
|
||||
BattleTemplateToken.EVsWithNature => GetStringStatsStatAlignmentAmp(EVs, 0, nameEVs, Nature),
|
||||
BattleTemplateToken.EVsAppendNature => GetStringStatsStatAlignmentAmp(EVs, 0, nameEVs, Nature),
|
||||
_ => GetStringStats(EVs, 0, nameEVs),
|
||||
};
|
||||
if (token is BattleTemplateToken.EVsAppendNature && Nature.IsFixed)
|
||||
@@ -654,7 +654,7 @@ private static string GetAbilityHeldItem(GameStrings strings, int ability, int i
|
||||
|
||||
/// <inheritdoc cref="GetStringStats{T}(ReadOnlySpan{T}, T, StatDisplayConfig)"/>
|
||||
/// <remarks>Appends the nature amplification to the stat values, if not a neutral nature.</remarks>
|
||||
public static string GetStringStatsNatureAmp<T>(ReadOnlySpan<T> stats, T ignoreValue, StatDisplayConfig statNames, Nature nature) where T : IEquatable<T>
|
||||
public static string GetStringStatsStatAlignmentAmp<T>(ReadOnlySpan<T> stats, T ignoreValue, StatDisplayConfig statNames, Nature nature) where T : IEquatable<T>
|
||||
{
|
||||
var (plus, minus) = nature.GetNatureModification();
|
||||
if (plus == minus)
|
||||
@@ -806,7 +806,7 @@ public ShowdownSet(PKM pk, BattleTemplateLocalization? localization = null)
|
||||
if (Moves.Contains((ushort)Move.HiddenPower))
|
||||
HiddenPowerType = (sbyte)HiddenPower.GetType(IVs, Context);
|
||||
|
||||
Nature = pk.StatNature;
|
||||
Nature = pk.StatAlignment;
|
||||
Gender = pk.Gender < 2 ? pk.Gender : (byte)2;
|
||||
Friendship = pk.CurrentFriendship;
|
||||
Level = pk.CurrentLevel;
|
||||
|
||||
@@ -115,11 +115,11 @@ public void TreatAmpsAsSpeedNotLast()
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts stat indexes from visual to stored, and ignoring HP's index.
|
||||
/// Adjusts stat indexes from visual to amp index, ignoring HP's index.
|
||||
/// </summary>
|
||||
/// <param name="amp">Visual index of the stat to get the adjusted value for.</param>
|
||||
/// <returns>Stored index of the stat.</returns>
|
||||
private static sbyte GetSpeedMiddleIndex(sbyte amp) => amp switch
|
||||
/// <param name="visualStatIndex">Visual index of the stat to get the adjusted value for.</param>
|
||||
/// <returns>Amp index of the stat, ignoring HP's index.</returns>
|
||||
private static sbyte GetSpeedMiddleIndex(sbyte visualStatIndex) => visualStatIndex switch
|
||||
{
|
||||
// 0 => NoStatAmp -- handle via default case
|
||||
1 => 0, // Atk
|
||||
|
||||
@@ -123,6 +123,7 @@ public bool TryGetPropertyType(string propertyName, [NotNullWhen(true)] out stri
|
||||
/// <summary>
|
||||
/// Checks if the entity is filtered by the provided filters.
|
||||
/// </summary>
|
||||
[RequiresUnreferencedCode("Uses reflection-backed property caches to evaluate batch filters.")]
|
||||
public bool IsFilterMatch(IEnumerable<StringInstruction> filters, TObject entity)
|
||||
{
|
||||
var info = CreateMeta(entity);
|
||||
@@ -138,12 +139,14 @@ public bool IsFilterMatch(IEnumerable<StringInstruction> filters, TObject entity
|
||||
/// <summary>
|
||||
/// Tries to modify the entity.
|
||||
/// </summary>
|
||||
[RequiresUnreferencedCode("Uses reflection-backed property caches to modify entity properties.")]
|
||||
public bool TryModifyIsSuccess(TObject entity, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<TObject, bool>? modifier = null)
|
||||
=> TryModify(entity, filters, modifications, modifier) is ModifyResult.Modified;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to modify the entity using instructions and a custom modifier delegate.
|
||||
/// </summary>
|
||||
[RequiresUnreferencedCode("Uses reflection-backed property caches to modify entity properties.")]
|
||||
public ModifyResult TryModify(TObject entity, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications, Func<TObject, bool>? modifier = null)
|
||||
{
|
||||
if (!ShouldModify(entity))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
@@ -18,7 +19,8 @@ public static class BatchEditingUtil
|
||||
/// <param name="filters">Filters which must be satisfied.</param>
|
||||
/// <param name="obj">Object to check.</param>
|
||||
/// <returns>True if <see cref="obj"/> matches all filters.</returns>
|
||||
public static bool IsFilterMatch<T>(IEnumerable<StringInstruction> filters, T obj) where T : notnull
|
||||
[RequiresUnreferencedCode("Uses reflection to evaluate property-based batch filters.")]
|
||||
public static bool IsFilterMatch<T>(IEnumerable<StringInstruction> filters, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T obj) where T : notnull
|
||||
{
|
||||
foreach (var cmd in filters)
|
||||
{
|
||||
|
||||
@@ -28,8 +28,8 @@ public static class BatchMods
|
||||
new TypeSuggestion<PKM>(nameof(PKM.EggMetDate), p => p.EggMetDate = p.MetDate),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.MetDate), p => p.MetDate = p.EggMetDate),
|
||||
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Nature), p => p.Format >= 8, p => p.Nature = p.StatNature),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.StatNature), p => p.Format >= 8, p => p.StatNature = p.Nature),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Nature), p => p.Format >= 8, p => p.Nature = p.StatAlignment),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.StatAlignment), p => p.Format >= 8, p => p.StatAlignment = p.Nature),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Stats), p => p.ResetPartyStats()),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Ball), p => BallApplicator.ApplyBallLegalByColor(p)),
|
||||
new TypeSuggestion<PKM>(nameof(PKM.Heal), p => p.Heal()),
|
||||
|
||||
@@ -13,7 +13,7 @@ public static class CommonEdits
|
||||
public static bool ShowdownSetIVMarkings { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Setting which causes the <see cref="PKM.StatNature"/> to the <see cref="PKM.Nature"/> in Gen8+ formats.
|
||||
/// Setting which causes the <see cref="PKM.StatAlignment"/> to the <see cref="PKM.Nature"/> in Gen8+ formats.
|
||||
/// </summary>
|
||||
public static bool ShowdownSetBehaviorNature { get; set; }
|
||||
|
||||
@@ -144,7 +144,7 @@ public void SetNature(Nature nature)
|
||||
|
||||
var format = pk.Format;
|
||||
if (format >= 8)
|
||||
pk.StatNature = nature;
|
||||
pk.StatAlignment = nature;
|
||||
else if (format is 3 or 4)
|
||||
pk.SetPIDNature(nature);
|
||||
else
|
||||
@@ -250,7 +250,7 @@ public void ApplySetDetails(IBattleTemplate set)
|
||||
s.SetMoveShopFlags(set.Moves, pk);
|
||||
|
||||
if (ShowdownSetBehaviorNature && pk.Format >= 8)
|
||||
pk.Nature = pk.StatNature;
|
||||
pk.Nature = pk.StatAlignment;
|
||||
|
||||
var legal = new LegalityAnalysis(pk);
|
||||
if (pk is ITechRecord t)
|
||||
|
||||
@@ -13,7 +13,7 @@ public static class NatureAmp
|
||||
/// <summary>
|
||||
/// Mutate the nature amp indexes to match the request
|
||||
/// </summary>
|
||||
/// <param name="statIndex">Stat Index to mutate</param>
|
||||
/// <param name="statIndex">Stat Index to mutate, internal order</param>
|
||||
/// <param name="currentNature">Current nature to derive the current amps from</param>
|
||||
/// <returns>New nature value</returns>
|
||||
public Nature GetNewNature(int statIndex, Nature currentNature)
|
||||
@@ -26,6 +26,9 @@ public Nature GetNewNature(int statIndex, Nature currentNature)
|
||||
return type.GetNewNature(statIndex, up, dn);
|
||||
}
|
||||
|
||||
/// <param name="statIndex">Stat Index to mutate, internal order</param>
|
||||
/// <param name="up">Current increased stat index, internal order</param>
|
||||
/// <param name="dn">Current decreased stat index, internal order</param>
|
||||
/// <inheritdoc cref="GetNewNature(NatureAmpRequest,int,Nature)"/>
|
||||
public Nature GetNewNature(int statIndex, int up, int dn)
|
||||
{
|
||||
@@ -54,6 +57,7 @@ public Nature GetNewNature(int statIndex, int up, int dn)
|
||||
/// <summary>
|
||||
/// Decompose the nature to the two stat indexes that are modified
|
||||
/// </summary>
|
||||
/// <returns>Tuple containing the increased and decreased stat indexes, internal order</returns>
|
||||
public (int up, int dn) GetNatureModification()
|
||||
{
|
||||
var up = ((byte)nature / 5);
|
||||
@@ -71,8 +75,8 @@ public bool IsNeutralOrInvalid()
|
||||
/// <summary>
|
||||
/// Checks if the nature is out of range or the stat amplifications are not neutral.
|
||||
/// </summary>
|
||||
/// <param name="up">Increased stat</param>
|
||||
/// <param name="dn">Decreased stat</param>
|
||||
/// <param name="up">Increased stat, internal order</param>
|
||||
/// <param name="dn">Decreased stat, internal order</param>
|
||||
/// <returns>True if nature modification values are equal or the Nature is out of range.</returns>
|
||||
public bool IsNeutralOrInvalid(int up, int dn)
|
||||
{
|
||||
@@ -83,7 +87,7 @@ public bool IsNeutralOrInvalid(int up, int dn)
|
||||
/// Updates stats according to the specified nature.
|
||||
/// </summary>
|
||||
/// <param name="stats">Current stats to amplify if appropriate</param>
|
||||
public void ModifyStatsForNature(Span<ushort> stats)
|
||||
public void ModifyStatsForAlignment(Span<ushort> stats)
|
||||
{
|
||||
var (up, dn) = nature.GetNatureModification();
|
||||
if (nature.IsNeutralOrInvalid(up, dn))
|
||||
@@ -99,8 +103,8 @@ public void ModifyStatsForNature(Span<ushort> stats)
|
||||
/// <summary>
|
||||
/// Recombine the stat amps into a nature value.
|
||||
/// </summary>
|
||||
/// <param name="up">Increased stat</param>
|
||||
/// <param name="dn">Decreased stat</param>
|
||||
/// <param name="up">Increased stat, internal order</param>
|
||||
/// <param name="dn">Decreased stat, internal order</param>
|
||||
/// <returns>Nature</returns>
|
||||
public static Nature CreateNatureFromAmps(int up, int dn)
|
||||
{
|
||||
@@ -110,7 +114,7 @@ public static Nature CreateNatureFromAmps(int up, int dn)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nature Amplification Table
|
||||
/// Nature / Stat Alignment Amplification Table, speed last (visual order).
|
||||
/// </summary>
|
||||
/// <remarks>-1 is 90%, 0 is 100%, 1 is 110%.</remarks>
|
||||
public static ReadOnlySpan<sbyte> Table =>
|
||||
@@ -145,6 +149,13 @@ public static Nature CreateNatureFromAmps(int up, int dn)
|
||||
private const byte NatureCount = 25;
|
||||
private const int AmpWidth = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Amplify the stat according to the nature. If the nature is out of range, it will be treated as neutral.
|
||||
/// </summary>
|
||||
/// <param name="nature">Nature to use for amplification</param>
|
||||
/// <param name="index">Stat index to amplify (0-4), visual index</param>
|
||||
/// <param name="initial">Initial stat value</param>
|
||||
/// <returns>Amplified stat value</returns>
|
||||
public static int AmplifyStat(Nature nature, int index, int initial) => GetNatureAmp(nature, index) switch
|
||||
{
|
||||
1 => 110 * initial / 100, // 110%
|
||||
@@ -152,6 +163,12 @@ public static Nature CreateNatureFromAmps(int up, int dn)
|
||||
_ => initial,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Get the nature amp for the specified stat index. If the nature is out of range, it will be treated as neutral.
|
||||
/// </summary>
|
||||
/// <param name="nature">Nature to use for amplification</param>
|
||||
/// <param name="index">Stat index to amplify (0-4), visual index</param>
|
||||
/// <returns>Nature amp value: 1 for 110%, -1 for 90%, 0 for 100%.</returns>
|
||||
private static sbyte GetNatureAmp(Nature nature, int index)
|
||||
{
|
||||
if ((uint)nature >= NatureCount)
|
||||
@@ -160,6 +177,11 @@ private static sbyte GetNatureAmp(Nature nature, int index)
|
||||
return amps[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the nature amps for all stats. If the nature is out of range, it will be treated as neutral.
|
||||
/// </summary>
|
||||
/// <param name="nature">Nature to use for amplification</param>
|
||||
/// <returns>ReadOnlySpan of stat amps for all stats (visual order)</returns>
|
||||
public static ReadOnlySpan<sbyte> GetAmps(Nature nature)
|
||||
{
|
||||
if ((uint)nature >= NatureCount)
|
||||
|
||||
@@ -19,7 +19,7 @@ public class EntitySummary : IFatefulEncounterReadOnly // do NOT seal, allow inh
|
||||
public virtual string Position => "???";
|
||||
public string Nickname => Entity.Nickname;
|
||||
public string Species => Get(Strings.specieslist, Entity.Species);
|
||||
public string Nature => Get(Strings.natures, (byte)Entity.StatNature);
|
||||
public string Nature => Get(Strings.natures, (byte)Entity.StatAlignment);
|
||||
public string Gender => Get(GenderSymbols, Entity.Gender);
|
||||
public string ESV => Entity.PSV.ToString("0000");
|
||||
public string HP_Type => GetSpan(Strings.HiddenPowerTypes, Entity.HPType);
|
||||
|
||||
@@ -18,7 +18,14 @@ public sealed class LegalMoveInfo
|
||||
/// </summary>
|
||||
/// <param name="move">Move to check if it can be learned</param>
|
||||
/// <returns>True if it can learn the move</returns>
|
||||
public bool CanLearn(ushort move) => AllowedMoves[move] != None;
|
||||
public bool CanLearn(ushort move) => GetMoveSources(move) != None;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the sources that allow the provided move to be learned, or <see cref="None"/> if it cannot be learned.
|
||||
/// </summary>
|
||||
/// <param name="move">Move to check the sources for</param>
|
||||
/// <returns>Sources that allow the move to be learned</returns>
|
||||
public IndicatedSourceType GetMoveSources(ushort move) => AllowedMoves[move];
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the legality sources to permit the provided legal info.
|
||||
@@ -57,58 +64,54 @@ public bool ReloadMoves(LegalityAnalysis la)
|
||||
|
||||
private static void ComputeEval(Span<IndicatedSourceType> type, ReadOnlySpan<bool> learn, LegalityAnalysis la)
|
||||
{
|
||||
for (int i = 0; i < type.Length; i++)
|
||||
// Wipe or set as learnable based on learnability; encounter moves will be added later.
|
||||
for (int i = 1; i < type.Length; i++)
|
||||
type[i] = learn[i] ? Learn : None;
|
||||
|
||||
// If the original moveset is deleted, then encounter moves are not relevant to legality and should not be added.
|
||||
if (!la.Entity.IsOriginalMovesetDeleted())
|
||||
AddEncounterMoves(type, la.EncounterOriginal);
|
||||
|
||||
type[0] = None; // Move ID 0 is always None
|
||||
}
|
||||
|
||||
private static void AddEncounterMoves(Span<IndicatedSourceType> type, IEncounterTemplate enc)
|
||||
private static void AddEncounterMoves(Span<IndicatedSourceType> result, IEncounterTemplate enc)
|
||||
{
|
||||
if (enc is IEncounterEgg egg)
|
||||
{
|
||||
var moves = egg.Learn.GetEggMoves(enc.Species, enc.Form);
|
||||
foreach (var move in moves)
|
||||
type[move] = Egg;
|
||||
}
|
||||
else if (enc is IMoveset {Moves: {HasMoves: true} set})
|
||||
{
|
||||
foreach (var move in set.AsSpan())
|
||||
{
|
||||
if (type[move] == None)
|
||||
type[move] = Encounter;
|
||||
}
|
||||
}
|
||||
else if (enc is ISingleMoveBonus single)
|
||||
{
|
||||
var moves = single.GetMoveBonusPossible();
|
||||
foreach (var move in moves)
|
||||
{
|
||||
if (type[move] == None)
|
||||
type[move] = EncounterSingle;
|
||||
}
|
||||
}
|
||||
if (enc is IRelearn { Relearn: { HasMoves: true } relearn })
|
||||
relearn.FlagMoves(result, Relearn);
|
||||
|
||||
if (enc is IRelearn { Relearn: {HasMoves: true} relearn})
|
||||
if (enc is IEncounterEgg egg)
|
||||
FlagMoves(result, Egg, egg.Learn.GetEggMoves(enc.Species, enc.Form));
|
||||
else if (enc is IMoveset { Moves: { HasMoves: true } set})
|
||||
set.FlagMoves(result, Encounter);
|
||||
else if (enc is ISingleMoveBonus { IsMoveBonusPossible: true } single)
|
||||
FlagIfNone(result, EncounterSingle, single.GetMoveBonusPossible());
|
||||
|
||||
return;
|
||||
|
||||
static void FlagMoves(Span<IndicatedSourceType> result, IndicatedSourceType flag, ReadOnlySpan<ushort> moves)
|
||||
{
|
||||
foreach (var move in relearn.AsSpan())
|
||||
foreach (var move in moves)
|
||||
result[move] |= flag;
|
||||
}
|
||||
static void FlagIfNone(Span<IndicatedSourceType> result, IndicatedSourceType flag, ReadOnlySpan<ushort> moves)
|
||||
{
|
||||
foreach (var move in moves)
|
||||
{
|
||||
if (type[move] == None)
|
||||
type[move] = Relearn;
|
||||
if (result[move] == None)
|
||||
result[move] |= flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum IndicatedSourceType : byte
|
||||
{
|
||||
None = 0,
|
||||
Learn,
|
||||
Egg,
|
||||
Encounter,
|
||||
EncounterSingle,
|
||||
Relearn,
|
||||
Learn = 1 << 0,
|
||||
Egg = 1 << 1,
|
||||
Encounter = 1 << 2,
|
||||
EncounterSingle = 1 << 3,
|
||||
Relearn = 1 << 4,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class EncounterDatabaseSettings
|
||||
{
|
||||
private const int ResultsGridRowCountMin = 5;
|
||||
private const int ResultsGridRowCountMax = 20;
|
||||
|
||||
[LocalizedDescription("Skips searching if the user forgot to enter Species / Move(s) into the search criteria.")]
|
||||
public bool ReturnNoneIfEmptySearch { get; set; } = true;
|
||||
|
||||
@@ -13,4 +18,11 @@ public sealed class EncounterDatabaseSettings
|
||||
|
||||
[LocalizedDescription("Use properties from the PKM Editor tabs even if the new encounter isn't the same evolution chain.")]
|
||||
public bool UseTabsAsCriteriaAnySpecies { get; set; } = true;
|
||||
|
||||
[LocalizedDescription("Visible row count for the sprite grid. Clamped from 5 to 20.")]
|
||||
public int ResultsGridRowCount
|
||||
{
|
||||
get;
|
||||
set => field = Math.Clamp(value, ResultsGridRowCountMin, ResultsGridRowCountMax);
|
||||
} = 9;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class EntityDatabaseSettings
|
||||
{
|
||||
private const int ResultsGridRowCountMin = 5;
|
||||
private const int ResultsGridRowCountMax = 20;
|
||||
|
||||
[LocalizedDescription("When loading content for the PKM Database, search within backup save files.")]
|
||||
public bool SearchBackups { get; set; } = true;
|
||||
|
||||
@@ -16,6 +21,13 @@ public sealed class EntityDatabaseSettings
|
||||
|
||||
[LocalizedDescription("Hides unavailable Species if the currently loaded save file cannot import them.")]
|
||||
public bool FilterUnavailableSpecies { get; set; } = true;
|
||||
|
||||
[LocalizedDescription("Visible row count for the sprite grid. Clamped from 5 to 20.")]
|
||||
public int ResultsGridRowCount
|
||||
{
|
||||
get;
|
||||
set => field = Math.Clamp(value, ResultsGridRowCountMin, ResultsGridRowCountMax);
|
||||
} = 9;
|
||||
}
|
||||
|
||||
public enum DatabaseSortMode
|
||||
|
||||
@@ -13,4 +13,7 @@ public sealed class EntityEditorSettings
|
||||
|
||||
[LocalizedDescription("When showing an entity, show any stored Status Condition (Sleep/Burn/etc) it may have.")]
|
||||
public bool ShowStatusCondition { get; set; } = true;
|
||||
|
||||
[LocalizedDescription("When showing an entity, show the Experience Bar to allow editing of EXP and Level.")]
|
||||
public bool ShowExperienceBar { get; set; } = true;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class MysteryGiftDatabaseSettings
|
||||
{
|
||||
private const int ResultsGridRowCountMin = 5;
|
||||
private const int ResultsGridRowCountMax = 20;
|
||||
|
||||
[LocalizedDescription("Hides gifts if the currently loaded save file cannot (indirectly) receive them.")]
|
||||
public bool FilterUnavailableSpecies { get; set; } = true;
|
||||
|
||||
[LocalizedDescription("Visible row count for the sprite grid. Clamped from 5 to 20.")]
|
||||
public int ResultsGridRowCount
|
||||
{
|
||||
get;
|
||||
set => field = Math.Clamp(value, ResultsGridRowCountMin, ResultsGridRowCountMax);
|
||||
} = 9;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ namespace PKHeX.Core;
|
||||
|
||||
public sealed class SetImportSettings
|
||||
{
|
||||
[LocalizedDescription("Apply StatNature to Nature on Import")]
|
||||
public bool ApplyNature { get; set; } = true;
|
||||
[LocalizedDescription("Apply Stat Alignment to Nature on Import")]
|
||||
public bool ApplyStatAlignment { get; set; } = true;
|
||||
|
||||
[LocalizedDescription("Apply Markings on Import")]
|
||||
public bool ApplyMarkings { get; set; } = true;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public static void ReloadSettings(IProgramSettings settings)
|
||||
SaveFile.SetUpdatePKM = write.SetUpdatePKM ? EntityImportOption.Enable : EntityImportOption.Disable;
|
||||
SaveFile.SetUpdateRecords = write.SetUpdateRecords ? EntityImportOption.Enable : EntityImportOption.Disable;
|
||||
CommonEdits.ShowdownSetIVMarkings = settings.Import.ApplyMarkings;
|
||||
CommonEdits.ShowdownSetBehaviorNature = settings.Import.ApplyNature;
|
||||
CommonEdits.ShowdownSetBehaviorNature = settings.Import.ApplyStatAlignment;
|
||||
ParseSettings.Initialize(settings.Legality);
|
||||
|
||||
var converter = settings.Converter;
|
||||
|
||||
111
PKHeX.Core/Editing/Saves/Editors/Controls/TrainerIDManager.cs
Normal file
111
PKHeX.Core/Editing/Saves/Editors/Controls/TrainerIDManager.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class TrainerIDManager : ITrainerIDControl
|
||||
{
|
||||
private bool _loading;
|
||||
private readonly ITrainerIDControl _tid;
|
||||
private readonly ITrainerIDControl _sid;
|
||||
public byte Generation { private get; set; }
|
||||
|
||||
public event EventHandler? ValueChanged;
|
||||
|
||||
public ITrainerID32 Trainer
|
||||
{
|
||||
get => field ?? throw new InvalidOperationException("Trainer is not initialized.");
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public TrainerIDManager(ITrainerIDControl tid, ITrainerIDControl sid)
|
||||
{
|
||||
_tid = tid;
|
||||
_sid = sid;
|
||||
|
||||
tid.ValueChanged += RaiseValueChanged;
|
||||
sid.ValueChanged += RaiseValueChanged;
|
||||
}
|
||||
|
||||
private void RaiseValueChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (_loading)
|
||||
return;
|
||||
_loading = true;
|
||||
SaveTrainer(Trainer);
|
||||
if (!_sid.IsValueSame(Trainer))
|
||||
_sid.LoadTrainer(Trainer, Trainer.TrainerIDDisplayFormat);
|
||||
SetToolTip();
|
||||
ValueChanged?.Invoke(this, EventArgs.Empty);
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
public void LoadTrainer<T>(T trainer) where T : ITrainerID32, IGeneration
|
||||
{
|
||||
Generation = trainer.Generation;
|
||||
LoadTrainer(trainer, trainer.GetTrainerIDFormat());
|
||||
}
|
||||
|
||||
public void LoadTrainer(ITrainerID32 trainer, byte generation)
|
||||
{
|
||||
Generation = generation;
|
||||
LoadTrainer(trainer, trainer.GetTrainerIDFormat());
|
||||
}
|
||||
|
||||
public void LoadTrainer(ITrainerID32 trainer, TrainerIDFormat displayType)
|
||||
{
|
||||
Trainer = trainer;
|
||||
LoadTrainer();
|
||||
}
|
||||
|
||||
public void LoadTrainer()
|
||||
{
|
||||
_loading = true;
|
||||
try
|
||||
{
|
||||
var trainer = Trainer;
|
||||
var format = Trainer.TrainerIDDisplayFormat;
|
||||
_tid.LoadTrainer(trainer, format);
|
||||
_sid.LoadTrainer(trainer, format);
|
||||
SetToolTip();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveTrainer(ITrainerID32 trainer)
|
||||
{
|
||||
_sid.SaveTrainer(trainer);
|
||||
_tid.SaveTrainer(trainer);
|
||||
}
|
||||
|
||||
public bool IsValueSame(ITrainerID32 trainer) => _tid.IsValueSame(trainer) && _sid.IsValueSame(trainer);
|
||||
|
||||
public void SetToolTip()
|
||||
{
|
||||
var tsv = Trainer.GetTSV(Generation);
|
||||
var text = tsv > ushort.MaxValue
|
||||
? string.Empty
|
||||
: $"TSV: {tsv:D4}{Environment.NewLine}{Trainer.GetTextRepresentation()}";
|
||||
|
||||
SetToolTip(text);
|
||||
}
|
||||
|
||||
public void SetToolTip(string text)
|
||||
{
|
||||
_tid.SetToolTip(text);
|
||||
_sid.SetToolTip(text);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ITrainerIDControl
|
||||
{
|
||||
event EventHandler? ValueChanged;
|
||||
|
||||
void LoadTrainer(ITrainerID32 trainer, TrainerIDFormat displayType);
|
||||
void SaveTrainer(ITrainerID32 trainer);
|
||||
bool IsValueSame(ITrainerID32 trainer);
|
||||
void SetToolTip(string text);
|
||||
}
|
||||
@@ -135,10 +135,18 @@ private static void LoadAbilityList(IPersonalAbility pi, Span<ComboItem> list, R
|
||||
for (int i = 0; i < list.Length; i++)
|
||||
{
|
||||
var value = pi.GetAbilityAtIndex(i);
|
||||
var name = names[value];
|
||||
char suffix = i == 2 ? HiddenAbilitySuffix : (char)(AbilityIndexSuffix + i);
|
||||
var display = $"{name} ({suffix})";
|
||||
list[i] = new ComboItem(display, value);
|
||||
var item = GetAbilityItem(names, value, suffix);
|
||||
list[i] = item;
|
||||
}
|
||||
}
|
||||
|
||||
public static ComboItem GetAbilityItem(ReadOnlySpan<string> names, int value, char suffix)
|
||||
=> GetAbilityItem(names[value], suffix, value);
|
||||
|
||||
public static ComboItem GetAbilityItem(string name, char suffix, int value)
|
||||
{
|
||||
var display = $"{name} ({suffix})";
|
||||
return new ComboItem(display, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ public enum ProgramLanguage
|
||||
Deutsch,
|
||||
|
||||
/// <summary>
|
||||
/// Spanish
|
||||
/// Spanish (España)
|
||||
/// </summary>
|
||||
Español,
|
||||
Español_España,
|
||||
|
||||
/// <summary>
|
||||
/// Spanish (LATAM)
|
||||
|
||||
@@ -39,15 +39,15 @@ public static class HeldItemLumpUtil
|
||||
/// <param name="item">Held Item index</param>
|
||||
/// <param name="context">Generation context</param>
|
||||
/// <returns>Evaluation result.</returns>
|
||||
public static HeldItemLumpImage GetIsLump(int item, EntityContext context) => context.Generation switch
|
||||
public static HeldItemLumpImage GetIsLump(int item, EntityContext context) => item switch
|
||||
{
|
||||
<= 4 when item is (>= 0328 and <= 0419) => HeldItemLumpImage.TechnicalMachine, // Gen2/3/4 TM
|
||||
8 when item is (>= 0328 and <= 0427) => HeldItemLumpImage.TechnicalMachine, // BD/SP TMs
|
||||
8 when item is (>= 1130 and <= 1229) => HeldItemLumpImage.TechnicalRecord, // Gen8 TR
|
||||
9 when item is (>= 0328 and <= 0419) // TM01-TM92
|
||||
or (>= 0618 and <= 0620) // TM093-TM095
|
||||
or (>= 0690 and <= 0693) // TM096-TM099
|
||||
or (>= 2160 and <= 2289) /* TM100-TM229 */ => HeldItemLumpImage.TechnicalMachine,
|
||||
(>= 0328 and <= 0425) // TM001-TM092, HM01-06 (Gen2/3/4 TMs)
|
||||
or (>= 0618 and <= 0620) // TM093-TM095
|
||||
or (>= 0690 and <= 0694) or 737 // TM096-TM099, HM07, TM100(pre-Gen9)
|
||||
or (>= 2160 and <= 2289) // TM100-TM229
|
||||
=> HeldItemLumpImage.TechnicalMachine,
|
||||
|
||||
(>= 1130 and <= 1229) => HeldItemLumpImage.TechnicalRecord, // Gen8 TR
|
||||
_ => HeldItemLumpImage.NotLump,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ public abstract class ItemStorage4
|
||||
|
||||
public static ReadOnlySpan<ushort> BallsDPPt =>
|
||||
[
|
||||
// Safari Ball is only available in Great Marsh - cannot save, cannot give, does not show in pouch.
|
||||
1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
];
|
||||
|
||||
|
||||
@@ -65,11 +65,22 @@ public sealed class ItemStorage8LA : IItemStorage
|
||||
1828,
|
||||
];
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount) => GetItems(type).BinarySearch((ushort)itemIndex) >= 0;
|
||||
// excludes from pouch gifting all, no held items
|
||||
private static ReadOnlySpan<ushort> Unreleased =>
|
||||
[
|
||||
1785, // Strange Ball
|
||||
];
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount)
|
||||
{
|
||||
if (type is InventoryType.KeyItems)
|
||||
return true;
|
||||
|
||||
return itemCount != 0 && !Unreleased.Contains((ushort)itemIndex);
|
||||
}
|
||||
|
||||
public ReadOnlySpan<ushort> GetItems(InventoryType type) => type switch
|
||||
{
|
||||
|
||||
InventoryType.Items => General,
|
||||
InventoryType.KeyItems => Key,
|
||||
InventoryType.PCItems => General,
|
||||
|
||||
@@ -195,6 +195,16 @@ public sealed class ItemStorage9SV : IItemStorage
|
||||
1785, // Strange Ball
|
||||
];
|
||||
|
||||
private static ReadOnlySpan<ushort> UnreleasedIngredients =>
|
||||
[
|
||||
2395, // Blue Dish
|
||||
//2396, // Green Dish (Mesagoza Treasure Hunt)
|
||||
2397, // Orange Dish
|
||||
2398, // Red Dish
|
||||
2399, // White Dish
|
||||
2400, // Yellow Dish
|
||||
];
|
||||
|
||||
public int GetMax(InventoryType type) => type switch
|
||||
{
|
||||
InventoryType.Items => 999,
|
||||
@@ -212,7 +222,10 @@ public sealed class ItemStorage9SV : IItemStorage
|
||||
|
||||
public bool IsLegal(InventoryType type, int itemIndex, int itemCount)
|
||||
{
|
||||
return itemCount != 0 && Unreleased.BinarySearch((ushort)itemIndex) < 0;
|
||||
if (itemCount == 0) return false;
|
||||
if (type is InventoryType.Ingredients)
|
||||
return !UnreleasedIngredients.Contains((ushort)itemIndex);
|
||||
return Unreleased.BinarySearch((ushort)itemIndex) < 0;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<ushort> GetItems(InventoryType type) => GetLegal(type);
|
||||
|
||||
@@ -509,6 +509,7 @@ internal static class Encounters8
|
||||
new() { Species = 479, Level = 50, Location = 186, Form = 05, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Rotom-5 in the Workout Sea
|
||||
new() { Species = 132, Level = 50, Location = 186, FlawlessIVCount = 3 }, // Ditto in the Workout Sea
|
||||
//new() { Species = 242, Level = 50, Location = -1 }, // Blissey
|
||||
new() { Species = 103, Level = 50, Location = 186, Weather = Normal | Intense_Sun }, // Exeggutor in the Workout Sea
|
||||
new() { Species = 103, Level = 50, Location = 190, Weather = Normal | Raining | Intense_Sun }, // Exeggutor in the Insular Sea
|
||||
new() { Species = 571, Level = 50, Location = 190, Weather = Overcast }, // Zoroark in the Insular Sea
|
||||
new() { Species = 462, Level = 50, Location = 190, Weather = Thunderstorm }, // Magnezone in the Insular Sea
|
||||
|
||||
@@ -244,6 +244,7 @@ public static class EncounterServerDate
|
||||
{9024, new(2024, 10, 16)}, // Shiny Meloetta
|
||||
{9025, new(2024, 11, 01)}, // PokéCenter Birthday Tandemaus
|
||||
{9030, new(2025, 10, 31)}, // PokéCenter Fidough Birthday Gift
|
||||
{9035, new(2026, 06, 05, +1)} // PJCS26 Garchomp
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -255,9 +256,10 @@ public static class EncounterServerDate
|
||||
{0102, new(2025, 10, 23, 2026, 02, 01, +2)}, // Slowpoke PokéCenter Gift
|
||||
{0101, new(2025, 10, 31, 2027, 02, 01)}, // PokéCenter Audino Birthday Gift
|
||||
{1607, new(2025, 12, 09, 2026, 01, 20)}, // Alpha Charizard
|
||||
|
||||
|
||||
{9031, new(2026, 04, 02)}, // Alpha Chikorita
|
||||
{9032, new(2026, 04, 02)}, // Alpha Tepig
|
||||
{9033, new(2026, 04, 02)}, // Alpha Totodile
|
||||
{9034, new(2026, 04, 26)}, // Shiny Volcanion
|
||||
};
|
||||
}
|
||||
|
||||
@@ -217,6 +217,20 @@ public bool IsSatisfiedNature(Nature nature)
|
||||
return nature == Nature || Mutations.HasFlag(CanMintNature);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the Generation 3/4 PID satisfies the Nature criteria.
|
||||
/// </summary>
|
||||
/// <param name="pid">The original PID to check.</param>
|
||||
/// <returns><see langword="true"/> if the Nature satisfies the criteria; otherwise, <see langword="false"/>.</returns>
|
||||
public bool IsSatisfiedNature(uint pid)
|
||||
{
|
||||
if (Mutations.HasFlag(AllowOnlyNeutralNature))
|
||||
return ((Nature)(pid % 25)).IsNeutral;
|
||||
if (Nature == Nature.Random)
|
||||
return true;
|
||||
return Mutations.HasFlag(CanMintNature) || ((Nature)(pid % 25)) == Nature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified level satisfies the level range criteria.
|
||||
/// </summary>
|
||||
@@ -574,6 +588,29 @@ public bool IsSatisfiedIVs(uint iv32)
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the IV at the specified index should be generated randomly.
|
||||
/// </summary>
|
||||
/// <param name="index">Stat index (internal order).</param>
|
||||
/// <param name="value">Requested fixed IV value, if specified.</param>
|
||||
/// <returns><see langword="true"/> if the IV should be random; otherwise, <see langword="false"/>.</returns>
|
||||
public bool IsRandomIV(int index, out sbyte value) => (value = GetIVInternal(index)) == RandomIV;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IV based on the specified index (internal order).
|
||||
/// </summary>
|
||||
/// <param name="index">Stat index (internal order).</param>
|
||||
public sbyte GetIVInternal(int index) => index switch
|
||||
{
|
||||
0 => IV_HP,
|
||||
1 => IV_ATK,
|
||||
2 => IV_DEF,
|
||||
3 => IV_SPE,
|
||||
4 => IV_SPA,
|
||||
5 => IV_SPD,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index), index, null),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IV based on the specified index (visual order).
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using static PKHeX.Core.AbilityPermission;
|
||||
using static PKHeX.Core.EncounterMutation;
|
||||
using static PKHeX.Core.EntityContext;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
@@ -25,7 +28,7 @@ public enum EncounterMutation : byte
|
||||
|
||||
public static class EncounterMutationUtil
|
||||
{
|
||||
public static bool IsComplexNature(this EncounterMutation m) => m.HasFlag(EncounterMutation.AllowOnlyNeutralNature);
|
||||
public static bool IsComplexNature(this EncounterMutation m) => m.HasFlag(AllowOnlyNeutralNature);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the suggested post-generation mutations allowed for the given target context and level.
|
||||
@@ -38,18 +41,48 @@ public static EncounterMutation GetSuggested(EntityContext targetContext, byte l
|
||||
return EncounterMutation.None;
|
||||
if (targetContext.IsEraPreSwitch)
|
||||
{
|
||||
if (targetContext is EntityContext.Gen7b)
|
||||
return level != 100 ? EncounterMutation.None : EncounterMutation.CanMaxIndividualStat;
|
||||
return EncounterMutation.CanAbilityCapsule;
|
||||
if (targetContext is Gen7b)
|
||||
return level != 100 ? EncounterMutation.None : CanMaxIndividualStat;
|
||||
return CanAbilityCapsule;
|
||||
}
|
||||
|
||||
var result = EncounterMutation.AllCanChange;
|
||||
var result = AllCanChange;
|
||||
|
||||
// In PLA, Hyper Training is equivalent to using Grit, which can be done at any level.
|
||||
// If bottle caps can't be used, don't allow maxing IVs.
|
||||
var minBottleLevel = targetContext.GetHyperTrainMinLevel();
|
||||
if (level < minBottleLevel && targetContext is not EntityContext.Gen8a)
|
||||
result &= ~EncounterMutation.CanMaxIndividualStat;
|
||||
if (level < minBottleLevel && targetContext is not Gen8a)
|
||||
result &= ~CanMaxIndividualStat;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the given mutation allows for the encounter to eventually arrive at the specified ability permissions.
|
||||
/// </summary>
|
||||
/// <param name="mutation">The encounter mutation allowed after capture.</param>
|
||||
/// <param name="start">The encounter's ability permission.</param>
|
||||
/// <param name="end">The end-state ability permission.</param>
|
||||
/// <returns>True if the mutation allows for the encounter to eventually arrive at the specified ability permissions; otherwise, false.</returns>
|
||||
public static bool CanGetAbility(this EncounterMutation mutation, AbilityPermission start, AbilityPermission end) => start switch
|
||||
{
|
||||
Any12 => end switch
|
||||
{
|
||||
OnlyHidden => mutation.HasFlag(CanAbilityPatch),
|
||||
_ => false,
|
||||
},
|
||||
OnlyFirst => end switch
|
||||
{
|
||||
OnlySecond => mutation.HasFlag(CanAbilityCapsule),
|
||||
OnlyHidden => mutation.HasFlag(CanAbilityPatch),
|
||||
_ => false,
|
||||
},
|
||||
OnlySecond => end switch
|
||||
{
|
||||
OnlyFirst => mutation.HasFlag(CanAbilityCapsule),
|
||||
OnlyHidden => mutation.HasFlag(CanAbilityPatch),
|
||||
_ => false,
|
||||
},
|
||||
OnlyHidden => end.CanBeHidden() || mutation.HasFlag(CanAbilityPatch),
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ private void SetPINGA(PB7 pk, in EncounterCriteria criteria)
|
||||
var ability = criteria.GetAbilityFromNumber(Ability);
|
||||
|
||||
criteria.SetRandomIVsGO(pk, Type.MinimumIV);
|
||||
pk.Nature = pk.StatNature = nature;
|
||||
pk.Nature = pk.StatAlignment = nature;
|
||||
pk.Gender = gender;
|
||||
pk.RefreshAbility(ability);
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ private void SetPINGA(PKM pk, in EncounterCriteria criteria)
|
||||
var nature = criteria.GetNature();
|
||||
var ability = criteria.GetAbilityFromNumber(Ability);
|
||||
|
||||
pk.Nature = pk.StatNature = nature;
|
||||
pk.Nature = pk.StatAlignment = nature;
|
||||
pk.Gender = gender;
|
||||
|
||||
pk.AbilityNumber = 1 << ability;
|
||||
|
||||
@@ -138,12 +138,11 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
|
||||
if (!IsOddEggTrainerNameValid(pk))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Once hatched, EXP can vary. Must be at least the starting value.
|
||||
if (pk.EXP < OddEggEXP)
|
||||
return false;
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// // Once hatched, EXP can vary.
|
||||
// // Daycare can reset EXP gained back to 0, below the initial EXP had by the egg.
|
||||
//}
|
||||
}
|
||||
|
||||
if (!IsMatchEggLocation(pk))
|
||||
|
||||
@@ -97,7 +97,7 @@ private uint GetRandomPID(in EncounterCriteria criteria, byte gr, uint id32)
|
||||
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(gender))
|
||||
continue;
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
if (criteria.IsSpecifiedAbility() && !criteria.IsSatisfiedAbility((byte)(pid % 2)))
|
||||
continue;
|
||||
|
||||
@@ -137,7 +137,7 @@ private static bool SetRoamerPINGA(PK3 pk, in EncounterCriteria criteria)
|
||||
var rand2 = LCRNG.Prev16(ref state);
|
||||
var rand1 = LCRNG.Prev16(ref state);
|
||||
var pid = (rand2 << 16) | rand1;
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
bool shiny = ShinyUtil.GetIsShiny3(id32, pid);
|
||||
if (criteria.Shiny.IsShiny() != shiny)
|
||||
@@ -166,11 +166,10 @@ private static bool TrySetMethod1(PK3 pk, in EncounterCriteria criteria, byte gr
|
||||
{
|
||||
var seed = LCRNG.Prev2(s); // Unwind the RNG to get the real origin seed for the PID/IV
|
||||
var pid = ClassicEraRNG.GetSequentialPID(seed);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
|
||||
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(gender))
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(EntityGender.GetFromPIDAndRatio(pid, gr)))
|
||||
continue;
|
||||
|
||||
var abit = (int)(pid & 1);
|
||||
@@ -179,7 +178,6 @@ private static bool TrySetMethod1(PK3 pk, in EncounterCriteria criteria, byte gr
|
||||
|
||||
pk.PID = pid;
|
||||
pk.IV32 |= iv2 << 15 | iv1;
|
||||
pk.Gender = gender;
|
||||
pk.RefreshAbility(abit);
|
||||
return true;
|
||||
}
|
||||
@@ -198,7 +196,7 @@ private static void SetMethod1(PK3 pk, in EncounterCriteria criteria, byte gr, u
|
||||
if (criteria.Shiny.IsShiny() != shiny)
|
||||
continue;
|
||||
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
|
||||
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
|
||||
|
||||
@@ -202,7 +202,7 @@ private uint SetPINGA(PK3 pk, in EncounterCriteria criteria, PersonalInfo3 pi)
|
||||
_ when Method is Method_2 => GetMethod2(ref seed),
|
||||
_ => GetRegular(ref seed),
|
||||
};
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue; // try again
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(EntityGender.GetFromPIDAndRatio(pid, gr)))
|
||||
continue;
|
||||
@@ -210,6 +210,8 @@ private uint SetPINGA(PK3 pk, in EncounterCriteria criteria, PersonalInfo3 pi)
|
||||
pk.PID = pid;
|
||||
pk.IV32 = ClassicEraRNG.GetSequentialIVs(ref seed);
|
||||
pk.RefreshAbility((int)(pk.PID & 1));
|
||||
if (ID32 is Wishmkr.TrainerID)
|
||||
pk.HeldItem = Wishmkr.GetHeldItem(LCRNG.Next16(ref seed));
|
||||
return seed;
|
||||
}
|
||||
}
|
||||
@@ -239,13 +241,21 @@ public bool GenerateSeed32(PKM pk, uint seed)
|
||||
|
||||
private static bool TrySetWishmkrShiny(PK3 pk, in EncounterCriteria criteria)
|
||||
{
|
||||
// 9 shinies, none duplicate nature. If nature is specified, try to set that nature.
|
||||
if (criteria.Nature.IsFixed && Wishmkr.TryGetSeed(criteria.Nature, out var u16))
|
||||
{
|
||||
GenerateWishmkr(pk, u16);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Nature can still be a "pick one of" nature, if specified.
|
||||
bool filterIVs = criteria.IsSpecifiedIVs(2);
|
||||
bool filterNature = criteria.IsSpecifiedNature();
|
||||
foreach (var s in Wishmkr.All9)
|
||||
{
|
||||
uint seed = s;
|
||||
var pid = GetRegular(ref seed);
|
||||
if (filterNature && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (filterNature && !criteria.IsSatisfiedNature(pid))
|
||||
continue; // try again
|
||||
|
||||
var iv32 = ClassicEraRNG.GetSequentialIVs(ref seed);
|
||||
@@ -253,12 +263,23 @@ private static bool TrySetWishmkrShiny(PK3 pk, in EncounterCriteria criteria)
|
||||
continue; // try again
|
||||
if (filterIVs && !criteria.IsSatisfiedIVs(iv32))
|
||||
continue; // try again
|
||||
|
||||
pk.PID = pid;
|
||||
pk.IV32 = iv32;
|
||||
pk.HeldItem = Wishmkr.GetHeldItemFromSeed(s);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void GenerateWishmkr(PK3 pk, ushort u16)
|
||||
{
|
||||
uint seed = u16;
|
||||
pk.PID = GetRegular(ref seed);
|
||||
pk.IV32 = ClassicEraRNG.GetSequentialIVs(ref seed);
|
||||
pk.HeldItem = Wishmkr.GetHeldItemFromSeed(u16);
|
||||
}
|
||||
|
||||
private static uint GetMethod2(ref uint seed)
|
||||
{
|
||||
var a = LCRNG.Next16(ref seed);
|
||||
@@ -280,7 +301,7 @@ private static uint SetPINGAChannel(PK3 pk, in EncounterCriteria criteria)
|
||||
continue;
|
||||
SetValuesFromSeedChannel(pk, seed);
|
||||
var pid = pk.EncryptionConstant;
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue; // try again
|
||||
if (criteria.Shiny.IsShiny() != ShinyUtil.GetIsShiny3(pk.ID32, pid))
|
||||
continue; // try again
|
||||
@@ -296,7 +317,7 @@ private static uint SetPINGAChannel(PK3 pk, in EncounterCriteria criteria)
|
||||
SetValuesFromSeedChannel(pk, seed);
|
||||
|
||||
var pid = pk.EncryptionConstant;
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue; // try again
|
||||
if (criteria.Shiny.IsShiny() != ShinyUtil.GetIsShiny3(pk.ID32, pid))
|
||||
continue; // try again
|
||||
|
||||
@@ -79,7 +79,7 @@ private static void SetPINGA(PK3 pk, in EncounterCriteria criteria, PersonalInfo
|
||||
while (true)
|
||||
{
|
||||
var pid = CommonEvent3.GetRegularAntishiny(ref seed, idXor);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue; // try again
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(EntityGender.GetFromPIDAndRatio(pid, gr)))
|
||||
continue;
|
||||
|
||||
@@ -80,10 +80,9 @@ private static void SetPINGA(PK3 pk, in EncounterCriteria criteria, PersonalInfo
|
||||
while (true)
|
||||
{
|
||||
var pid = CommonEvent3.GetAntishiny(ref seed, idXor);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue; // try again
|
||||
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(gender))
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(EntityGender.GetFromPIDAndRatio(pid, gr)))
|
||||
continue;
|
||||
var iv32 = ClassicEraRNG.GetSequentialIVs(ref seed);
|
||||
if (criteria.IsSpecifiedHiddenPower() && !criteria.IsSatisfiedHiddenPower(iv32))
|
||||
|
||||
@@ -102,7 +102,7 @@ private uint GetRandomPID(in EncounterCriteria criteria, byte gr, uint id32, out
|
||||
gender = EntityGender.GetFromPIDAndRatio(pid, gr);
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(gender))
|
||||
continue;
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
if (criteria.IsSpecifiedAbility() && !criteria.IsSatisfiedAbility((byte)(pid % 2)))
|
||||
continue;
|
||||
|
||||
@@ -190,6 +190,9 @@ public RandomCorrelationRating IsCompatible(PIDType type, PKM pk)
|
||||
public PIDType GetSuggestedCorrelation() => PIDType.Method_1;
|
||||
|
||||
public byte PressureLevel => Type != Grass ? LevelMax : Parent.GetPressureMax(Species, LevelMax);
|
||||
|
||||
// HG/SS has some encounter generation routines that sample rejects until a minimum of one 31 IV is present, up to 4x.
|
||||
public bool IsBugContest => Type == BugContest;
|
||||
public bool IsSafariHGSS => Locations4.IsSafari(Location);
|
||||
public bool IsRerollMinimum31 => IsBugContest || IsSafariHGSS;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ private static bool TrySetMethod1(PK4 pk, in EncounterCriteria criteria, byte gr
|
||||
{
|
||||
var seed = LCRNG.Prev2(s); // Unwind the RNG to get the real origin seed for the PID/IV
|
||||
var pid = ClassicEraRNG.GetSequentialPID(seed);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
|
||||
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
|
||||
@@ -167,7 +167,7 @@ private static void SetMethod1(PK4 pk, in EncounterCriteria criteria, byte gr, u
|
||||
if (criteria.Shiny.IsShiny() != shiny)
|
||||
continue;
|
||||
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
|
||||
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
|
||||
@@ -206,7 +206,7 @@ private static bool TrySetChainShiny(PK4 pk, in EncounterCriteria criteria, byte
|
||||
var shiny = ShinyUtil.GetIsShiny3(id32, pid);
|
||||
if (criteria.Shiny.IsShiny() != shiny)
|
||||
continue;
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
|
||||
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
|
||||
@@ -234,7 +234,7 @@ private static void SetChainShiny(PK4 pk, in EncounterCriteria criteria, byte gr
|
||||
while (true)
|
||||
{
|
||||
var pid = ClassicEraRNG.GetChainShinyPID(ref seed, id32);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
|
||||
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
|
||||
|
||||
@@ -77,6 +77,8 @@ public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
||||
pk.PID = pid;
|
||||
pk.Gender = gender;
|
||||
pk.RefreshAbility(ability);
|
||||
if (ability == 0 && this is { Species: (int)Core.Species.Basculin, Form: 1 })
|
||||
pk.Ability = (int)Core.Ability.Reckless;
|
||||
|
||||
return pk;
|
||||
}
|
||||
|
||||
@@ -90,6 +90,8 @@ private void SetPINGA(PK5 pk, in EncounterCriteria criteria, PersonalInfo5B2W2 p
|
||||
var abilityIndex = Ability == AbilityPermission.OnlyHidden ? 2 : (int)((pk.PID >> 16) & 1);
|
||||
pk.RefreshAbility(abilityIndex);
|
||||
criteria.SetRandomIVs(pk);
|
||||
if (abilityIndex == 0 && this is { Species: (int)Core.Species.Basculin, Form: 1, Version: GameVersion.B or GameVersion.W })
|
||||
pk.Ability = (int)Core.Ability.Reckless;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ public PK6 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
||||
Country = geo.Country,
|
||||
Region = geo.Region,
|
||||
};
|
||||
pk.StatNature = pk.Nature;
|
||||
pk.StatAlignment = pk.Nature;
|
||||
pk.SetHatchMemory6();
|
||||
|
||||
if (Species is (int)Core.Species.Scatterbug)
|
||||
|
||||
@@ -66,7 +66,7 @@ public PK7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
||||
Country = geo.Country,
|
||||
Region = geo.Region,
|
||||
};
|
||||
pk.StatNature = pk.Nature;
|
||||
pk.StatAlignment = pk.Nature;
|
||||
|
||||
if (Species is (int)Core.Species.Scatterbug)
|
||||
pk.Form = Vivillon3DS.GetPattern(pk.Country, pk.Region);
|
||||
|
||||
@@ -59,7 +59,7 @@ public PK8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
||||
Nature = criteria.GetNature(),
|
||||
Gender = criteria.GetGender(pi),
|
||||
};
|
||||
pk.StatNature = pk.Nature;
|
||||
pk.StatAlignment = pk.Nature;
|
||||
|
||||
SetEncounterMoves(pk);
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ private void SetPINGA(PK8 pk, in EncounterCriteria criteria, PersonalInfo8SWSH p
|
||||
{
|
||||
bool symbol = Parent.PermitCrossover;
|
||||
pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability));
|
||||
pk.Nature = pk.StatNature = criteria.GetNature();
|
||||
pk.Nature = pk.StatAlignment = criteria.GetNature();
|
||||
pk.Gender = criteria.GetGender(pi);
|
||||
|
||||
var req = GetRequirement(pk);
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed record EncounterStatic8(GameVersion Version = GameVersion.SWSH)
|
||||
public Crossover8 Crossover { get; init; }
|
||||
public AreaWeather8 Weather { get; init; } = AreaWeather8.Normal;
|
||||
public byte DynamaxLevel { get; init; }
|
||||
public Nature Nature { get; init; }
|
||||
public Nature Nature { get; init; } = Nature.Random;
|
||||
public Shiny Shiny { get; init; }
|
||||
public AbilityPermission Ability { get; init; }
|
||||
public byte Gender { get; init; } = FixedGenderUtil.GenderRandom;
|
||||
@@ -119,7 +119,7 @@ private void SetPINGA(PK8 pk, in EncounterCriteria criteria)
|
||||
|
||||
var pi = PersonalTable.SWSH[Species, Form];
|
||||
pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability));
|
||||
pk.Nature = pk.StatNature = criteria.GetNature();
|
||||
pk.Nature = pk.StatAlignment = criteria.GetNature();
|
||||
pk.Gender = criteria.GetGender(Gender, pi);
|
||||
|
||||
var req = GetRequirement(pk);
|
||||
|
||||
@@ -154,7 +154,7 @@ private void SetPINGA(PK8 pk, in EncounterCriteria criteria, PersonalInfo8SWSH p
|
||||
var gender = criteria.GetGender(Gender, pi);
|
||||
var nature = criteria.GetNature(Nature);
|
||||
int ability = criteria.GetAbilityFromNumber(Ability);
|
||||
pk.Nature = pk.StatNature = nature;
|
||||
pk.Nature = pk.StatAlignment = nature;
|
||||
pk.Gender = gender;
|
||||
pk.RefreshAbility(ability);
|
||||
if (IVs.IsSpecified)
|
||||
|
||||
@@ -179,7 +179,7 @@ private void SetPINGA(PB8 pk, in EncounterCriteria criteria, PersonalInfo8BDSP p
|
||||
// Set the rest of the values as per our generating via the egg seed.
|
||||
pk.EncryptionConstant = rng.NextUInt(); // PID would be re-rolled after here, but we aren't going to have re-rolls in our hypothetical setup.
|
||||
pk.SetIVs(ivs);
|
||||
pk.StatNature = pk.Nature = criteria.GetNature(); // Everstone (see above)
|
||||
pk.StatAlignment = pk.Nature = criteria.GetNature(); // Everstone (see above)
|
||||
pk.Gender = gender;
|
||||
pk.RefreshAbility(abilityIndex);
|
||||
return;
|
||||
|
||||
@@ -106,7 +106,7 @@ private void SetPINGA(PB8 pk, in EncounterCriteria criteria, PersonalInfo8BDSP p
|
||||
pk.PID = EncounterUtil.GetRandomPID(pk, rnd, criteria.Shiny);
|
||||
pk.EncryptionConstant = rnd.Rand32();
|
||||
criteria.SetRandomIVs(pk);
|
||||
pk.Nature = pk.StatNature = criteria.GetNature();
|
||||
pk.Nature = pk.StatAlignment = criteria.GetNature();
|
||||
pk.Gender = criteria.GetGender(pi);
|
||||
pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability));
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ public PB8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
||||
MetDate = EncounterDate.GetDateSwitch(),
|
||||
Gender = Gender,
|
||||
Nature = Nature,
|
||||
StatNature = Nature,
|
||||
StatAlignment = Nature,
|
||||
Ball = (byte)FixedBall,
|
||||
|
||||
ID32 = ID32,
|
||||
|
||||
@@ -60,7 +60,7 @@ public PK9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
||||
Nature = criteria.GetNature(),
|
||||
Gender = criteria.GetGender(pi),
|
||||
};
|
||||
pk.StatNature = pk.Nature;
|
||||
pk.StatAlignment = pk.Nature;
|
||||
|
||||
SetEncounterMoves(pk);
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ private void SetPINGA(PK9 pk, in EncounterCriteria criteria, PersonalInfo9SV pi)
|
||||
var rnd = Util.Rand;
|
||||
pk.PID = EncounterUtil.GetRandomPID(pk, rnd, criteria.Shiny);
|
||||
pk.EncryptionConstant = rnd.Rand32();
|
||||
pk.Nature = pk.StatNature = criteria.GetNature();
|
||||
pk.Nature = pk.StatAlignment = criteria.GetNature();
|
||||
pk.Gender = criteria.GetGender(pi);
|
||||
pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability));
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ private void SetPINGA(PK9 pk, in EncounterCriteria criteria, PersonalInfo9SV pi)
|
||||
var rnd = Util.Rand;
|
||||
pk.PID = EncounterUtil.GetRandomPID(pk, rnd, criteria.Shiny);
|
||||
pk.EncryptionConstant = rnd.Rand32();
|
||||
pk.Nature = pk.StatNature = criteria.GetNature();
|
||||
pk.Nature = pk.StatAlignment = criteria.GetNature();
|
||||
pk.Gender = criteria.GetGender(pi);
|
||||
pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability));
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ private void SetPINGA(PK9 pk, in EncounterCriteria criteria, PersonalInfo9SV pi)
|
||||
pk.EncryptionConstant = rnd.Rand32();
|
||||
criteria.SetRandomIVs(pk);
|
||||
|
||||
pk.Nature = pk.StatNature = criteria.GetNature();
|
||||
pk.Nature = pk.StatAlignment = criteria.GetNature();
|
||||
pk.Gender = criteria.GetGender(Gender, pi);
|
||||
pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability));
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ private void SetPINGA(PK9 pk, in EncounterCriteria criteria, PersonalInfo9SV pi)
|
||||
if (Gender != FixedGenderUtil.GenderRandom)
|
||||
pk.Gender = Gender;
|
||||
if (Nature.IsFixed)
|
||||
pk.Nature = pk.StatNature = Nature;
|
||||
pk.Nature = pk.StatAlignment = Nature;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ public PK9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
||||
MetDate = EncounterDate.GetDateSwitch(),
|
||||
Gender = Gender,
|
||||
Nature = Nature,
|
||||
StatNature = Nature,
|
||||
StatAlignment = Nature,
|
||||
Ball = (byte)FixedBall,
|
||||
|
||||
ID32 = ID32,
|
||||
@@ -130,7 +130,7 @@ private void SetPINGA(PK9 pk, in EncounterCriteria criteria, PersonalInfo9SV pi)
|
||||
var rnd = Util.Rand;
|
||||
pk.PID = EncounterUtil.GetRandomPID(pk, rnd, Shiny, criteria.Shiny);
|
||||
pk.EncryptionConstant = rnd.Rand32();
|
||||
pk.Nature = pk.StatNature = criteria.GetNature(Nature);
|
||||
pk.Nature = pk.StatAlignment = criteria.GetNature(Nature);
|
||||
pk.Gender = criteria.GetGender(Gender, pi);
|
||||
pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability));
|
||||
criteria.SetRandomIVs(pk, IVs);
|
||||
|
||||
@@ -131,7 +131,8 @@ public EncounterMatchRating GetMatchRating(PKM pk)
|
||||
|
||||
private bool IsFormArgMismatch(PKM pk) => pk.Species switch
|
||||
{
|
||||
(int)Core.Species.Overqwil when Species is not (int)Core.Species.Overqwil && pk is IFormArgument { FormArgument: 0 } and IHomeTrack { HasTracker: false } => true,
|
||||
// Don't check for HOME tracker cross-evolution unlocks. This stays simple so that the iterator tries to find a no-evolve template match if possible.
|
||||
(int)Core.Species.Overqwil when Species is not (int)Core.Species.Overqwil && pk is IFormArgument { FormArgument: 0 } => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ public PA9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
|
||||
MetDate = EncounterDate.GetDateSwitch(),
|
||||
Gender = Gender,
|
||||
Nature = Nature,
|
||||
StatNature = Nature,
|
||||
StatAlignment = Nature,
|
||||
Ball = (byte)FixedBall,
|
||||
|
||||
ID32 = ID32,
|
||||
|
||||
@@ -110,7 +110,7 @@ public static bool GenerateData(PA9 pk, in GenerateParam9a enc, in EncounterCrit
|
||||
// Compromise on Nature -- some are fixed, some are random. If the request wants a specific nature, just mint it.
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(nature))
|
||||
return false;
|
||||
pk.Nature = pk.StatNature = nature;
|
||||
pk.Nature = pk.StatAlignment = nature;
|
||||
|
||||
// If Hyperspace, the player can have an active Teensy/Humungo boost. The scale is pre-determined outside of the seed=>pa9, consider it not correlated or traceable.
|
||||
// When calling the method to verify the entity, pass SizeType9.VALUE instead.
|
||||
|
||||
@@ -224,9 +224,7 @@ private static bool TryGetSeedNoPID(in GenerateParam9a param, PKM pk, out ulong
|
||||
{
|
||||
if (Volatile.Read(ref found)) { state.Stop(); return; }
|
||||
|
||||
uint start = (uint)range.start;
|
||||
uint endExclusive = (uint)range.end; // safe due to batching within 0..2^32
|
||||
|
||||
var (start, endExclusive) = range; // keep as ulong, can't overflow since endExclusive is at most 2^32
|
||||
for (ulong high = start; high < endExclusive; high++)
|
||||
{
|
||||
if (Volatile.Read(ref found)) { state.Stop(); return; }
|
||||
|
||||
@@ -39,12 +39,6 @@ namespace PKHeX.Core;
|
||||
/// <returns>True if any move is above the maximum; otherwise, false.</returns>
|
||||
public bool AnyAbove(ushort max) => Move1 > max || Move2 > max || Move3 > max || Move4 > max;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the moveset as an array of four move IDs.
|
||||
/// </summary>
|
||||
/// <returns>An array containing the four move IDs.</returns>
|
||||
public ushort[] ToArray() => [Move1, Move2, Move3, Move4];
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only span view of the moveset's four move IDs.
|
||||
/// </summary>
|
||||
@@ -176,4 +170,12 @@ public void FlagMoves(Span<bool> result)
|
||||
result[Move3] = true;
|
||||
result[Move4] = true;
|
||||
}
|
||||
|
||||
public void FlagMoves(Span<IndicatedSourceType> result, IndicatedSourceType value)
|
||||
{
|
||||
result[Move1] |= value;
|
||||
result[Move2] |= value;
|
||||
result[Move3] |= value;
|
||||
result[Move4] |= value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,13 @@ public sealed class LearnGroup8 : ILearnGroup
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_8;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option)
|
||||
{
|
||||
if (option is LearnOption.AtAnyTimeChain)
|
||||
return LearnGroupHOME.Instance;
|
||||
return GoBackwards(pk, history, enc, option);
|
||||
}
|
||||
|
||||
internal static ILearnGroup? GoBackwards(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option)
|
||||
{
|
||||
if (enc.Generation >= Generation)
|
||||
return null;
|
||||
|
||||
@@ -12,7 +12,7 @@ public sealed class LearnGroup8a : ILearnGroup
|
||||
private const EntityContext Context = EntityContext.Gen8a;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_8a;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => null;
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => option == LearnOption.AtAnyTimeChain ? LearnGroupHOME.Instance : null;
|
||||
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedPLA;
|
||||
|
||||
public bool Check(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvolutionHistory history,
|
||||
|
||||
@@ -12,7 +12,7 @@ public sealed class LearnGroup8b : ILearnGroup
|
||||
private const EntityContext Context = EntityContext.Gen8b;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_8b;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => null;
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => option == LearnOption.AtAnyTimeChain ? LearnGroupHOME.Instance : null;
|
||||
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedBDSP;
|
||||
|
||||
public bool Check(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvolutionHistory history,
|
||||
|
||||
@@ -12,7 +12,7 @@ public sealed class LearnGroup9 : ILearnGroup
|
||||
private const EntityContext Context = EntityContext.Gen9;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_9;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => null;
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => option == LearnOption.AtAnyTimeChain ? LearnGroupHOME.Instance : null;
|
||||
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedGen9;
|
||||
|
||||
public bool Check(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvolutionHistory history,
|
||||
|
||||
@@ -12,7 +12,7 @@ public sealed class LearnGroup9a : ILearnGroup
|
||||
private const EntityContext Context = EntityContext.Gen9a;
|
||||
public ushort MaxMoveID => Legal.MaxMoveID_9a;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => null;
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => option == LearnOption.AtAnyTimeChain ? LearnGroupHOME.Instance : null;
|
||||
public bool HasVisited(PKM pk, EvolutionHistory history) => history.HasVisitedZA;
|
||||
|
||||
public bool Check(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvolutionHistory history,
|
||||
@@ -109,14 +109,8 @@ private static void GetAllMovesInternal(Span<bool> result, PKM pk, EvoCriteria e
|
||||
private static void FlagEncounterMoves(IEncounterTemplate enc, Span<bool> result)
|
||||
{
|
||||
if (enc is IMoveset { Moves: { HasMoves: true } x })
|
||||
{
|
||||
foreach (var move in x.AsSpan())
|
||||
result[move] = true;
|
||||
}
|
||||
x.FlagMoves(result);
|
||||
if (enc is IRelearn { Relearn: { HasMoves: true } r })
|
||||
{
|
||||
foreach (var move in r.AsSpan())
|
||||
result[move] = true;
|
||||
}
|
||||
r.FlagMoves(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ public sealed class LearnGroupHOME : ILearnGroup
|
||||
private const LearnOption Option = LearnOption.HOME;
|
||||
public ushort MaxMoveID => 0;
|
||||
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) => null;
|
||||
public ILearnGroup? GetPrevious(PKM pk, EvolutionHistory history, IEncounterTemplate enc, LearnOption option) =>
|
||||
option is LearnOption.AtAnyTimeChain ? LearnGroup8.GoBackwards(pk, history, enc, option) : null;
|
||||
public bool HasVisited(PKM pk, EvolutionHistory history) => pk is IHomeTrack { HasTracker: true } || !ParseSettings.IgnoreTransferIfNoTracker;
|
||||
|
||||
public bool Check(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, EvolutionHistory history,
|
||||
|
||||
@@ -28,6 +28,14 @@ public enum LearnOption
|
||||
/// Required to be distinct in that the rules are different from the other two options. TR/TM flags aren't required if the move was learned via HOME.
|
||||
/// </remarks>
|
||||
HOME,
|
||||
|
||||
/// <summary>
|
||||
/// Check backwards of knowing moves within any game in the visitation chain.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Relevant for Evolution move sanity checks, where the move could have been picked up at any point in the game visitation chain.
|
||||
/// </remarks>
|
||||
AtAnyTimeChain,
|
||||
}
|
||||
|
||||
public static class LearnOptionExtensions
|
||||
@@ -35,7 +43,7 @@ public static class LearnOptionExtensions
|
||||
extension(LearnOption option)
|
||||
{
|
||||
public bool IsCurrent() => option == LearnOption.Current;
|
||||
public bool IsPast() => option is LearnOption.AtAnyTime or LearnOption.HOME;
|
||||
public bool IsPast() => option is LearnOption.AtAnyTime or LearnOption.HOME or LearnOption.AtAnyTimeChain;
|
||||
public bool IsFlagCheckRequired() => option != LearnOption.HOME;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,27 @@ public sealed class LegalityAnalysis
|
||||
/// </summary>
|
||||
public IReadOnlyList<CheckResult> Results => Parse;
|
||||
|
||||
public bool HasResult(LegalityCheckResultCode code)
|
||||
{
|
||||
foreach (var result in Parse)
|
||||
{
|
||||
if (result.Result == code)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int IndexOfResult(LegalityCheckResultCode code)
|
||||
{
|
||||
for (var i = 0; i < Parse.Count; i++)
|
||||
{
|
||||
var result = Parse[i];
|
||||
if (result.Result == code)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matched encounter data for the <see cref="Entity"/>.
|
||||
/// </summary>
|
||||
@@ -201,6 +222,7 @@ private void ParsePK1()
|
||||
Trainer.VerifyOTGB(this);
|
||||
MiscValues.VerifyMiscG12(this);
|
||||
MovePP.Verify(this);
|
||||
EVs.Verify(this);
|
||||
if (Entity.Format == 2)
|
||||
Item.Verify(this);
|
||||
}
|
||||
@@ -293,8 +315,8 @@ private void UpdateVCTransferInfo()
|
||||
private void UpdateChecks()
|
||||
{
|
||||
PIDEC.Verify(this);
|
||||
Nickname.Verify(this);
|
||||
LanguageIndex.Verify(this);
|
||||
Nickname.Verify(this);
|
||||
Trainer.Verify(this);
|
||||
TrainerID.Verify(this);
|
||||
IVs.Verify(this);
|
||||
|
||||
@@ -94,7 +94,7 @@ public sealed class LegalityCheckLocalization
|
||||
public string EggLocationTrade { get; init; } = "Able to hatch a traded Egg at Met Location.";
|
||||
public string EggLocationTradeFail { get; init; } = "Invalid Egg Location, shouldn't be 'traded' while an Egg.";
|
||||
public string EggMetLocationFail { get; init; } = "Can't obtain Egg from Egg Location.";
|
||||
public string EggNature { get; init; } = "Eggs cannot have their Stat Nature changed.";
|
||||
public string EggNature { get; init; } = "Eggs cannot have their Stat Alignment changed.";
|
||||
public string EggPP { get; init; } = "Eggs cannot have modified move PP counts.";
|
||||
public string EggPPUp { get; init; } = "Cannot apply PP Ups to an Egg.";
|
||||
public string EggRelearnFlags { get; init; } = "Expected no Relearn Move Flags.";
|
||||
@@ -203,6 +203,12 @@ public sealed class LegalityCheckLocalization
|
||||
public string G4PartnerMoodZero { get; init; } = "Mood stat value should be zero when not in the player's party.";
|
||||
public string G4ShinyLeafBitsInvalid { get; init; } = "Shiny Leaf/Crown bits are not valid.";
|
||||
public string G4ShinyLeafBitsEgg { get; init; } = "Eggs cannot have Shiny Leaf/Crown.";
|
||||
public string GTSTrainerSanitizedExpected { get; init; } = "Expected a GTS sanitized trainer name.";
|
||||
public string GTSTrainerSanitized { get; init; } = "Trainer name matches a GTS sanitized trainer name.";
|
||||
public string GTSTradedKoreanInternational { get; init; } = "Traded between Korean and International games via GTS.";
|
||||
public string GTSDisallowedClassicRibbon { get; init; } = "Cannot trade Classic Ribbon in the Gen 4 GTS between Korean and International games.";
|
||||
public string GTSDisallowedTradedEgg { get; init; } = "Cannot trade eggs in the Gen 4 GTS between Korean and International games.";
|
||||
|
||||
public string G5IVAll30 { get; init; } = "All IVs of N's Pokémon should be 30.";
|
||||
public string G5PIDShinyGrotto { get; init; } = "Hidden Grotto captures cannot be shiny.";
|
||||
public string G5SparkleInvalid { get; init; } = "Special In-game N's Sparkle flag should not be checked.";
|
||||
@@ -215,13 +221,14 @@ public sealed class LegalityCheckLocalization
|
||||
public string GanbaruStatTooHigh { get; init; } = "One or more Ganbaru Value is above the natural limit of (10 - IV bonus).";
|
||||
|
||||
public string GenderInvalidNone { get; init; } = "Genderless Pokémon should not have a gender.";
|
||||
public string GeoBadOrder { get; init; } = "GeoLocation Memory: Gap/Blank present.";
|
||||
public string GeoHardwareInvalid { get; init; } = "Geolocation: Country is not in 3DS region.";
|
||||
public string GeoHardwareRange { get; init; } = "Invalid Console Region.";
|
||||
public string GeoHardwareValid { get; init; } = "Geolocation: Country is in 3DS region.";
|
||||
public string GeoMemoryMissing { get; init; } = "GeoLocation Memory: Memories should be present.";
|
||||
public string GeoNoCountryHT { get; init; } = "GeoLocation Memory: HT Name present but has no previous Country.";
|
||||
public string GeoNoRegion { get; init; } = "GeoLocation Memory: Region without Country.";
|
||||
public string GeoBadOrder_0 { get; init; } = "GeoLocation Memory #{0}: Gap/Blank present.";
|
||||
public string GeoNoCountry_0 { get; init; } = "GeoLocation Memory #{0}: Region without Country.";
|
||||
public string GeoNoRegion_0 { get; init; } = "GeoLocation Memory #{0}: Country without Region.";
|
||||
|
||||
public string HyperTrainLevelGEQ_0 { get; init; } = "Can't Hyper Train a Pokémon that isn't level {0}.";
|
||||
public string HyperPerfectAll { get; init; } = "Can't Hyper Train a Pokémon with perfect IVs.";
|
||||
@@ -363,7 +370,7 @@ public sealed class LegalityCheckLocalization
|
||||
public string StatIncorrectCP { get; init; } = "Calculated CP does not match stored value.";
|
||||
public string StatGigantamaxInvalid { get; init; } = "Gigantamax Flag mismatch.";
|
||||
public string StatGigantamaxValid { get; init; } = "Gigantamax Flag was changed via Max Soup.";
|
||||
public string StatNatureInvalid { get; init; } = "Stat Nature is not within the expected range.";
|
||||
public string StatAlignmentInvalid { get; init; } = "Stat Alignment is not within the expected range.";
|
||||
public string StatBattleVersionInvalid { get; init; } = "Battle Version is not within the expected range.";
|
||||
public string StatNobleInvalid { get; init; } = "Noble Flag mismatch.";
|
||||
public string StatAlphaInvalid { get; init; } = "Alpha Flag mismatch.";
|
||||
@@ -402,7 +409,6 @@ public sealed class LegalityCheckLocalization
|
||||
public string TransferHTMismatchName { get; init; } = "Handling trainer does not match the expected trainer name.";
|
||||
public string TransferHTMismatchGender { get; init; } = "Handling trainer does not match the expected trainer gender.";
|
||||
public string TransferHTMismatchLanguage { get; init; } = "Handling trainer does not match the expected trainer language.";
|
||||
public string TransferKoreanGen4 { get; init; } = "Korean Generation 4 games cannot interact with International Generation 4 games.";
|
||||
public string TransferMet { get; init; } = "Invalid Met Location, expected Poké Transfer or Crown.";
|
||||
public string TransferNotPossible { get; init; } = "Unable to transfer into current format from origin format.";
|
||||
public string TransferMetLocation { get; init; } = "Invalid Transfer Met Location.";
|
||||
|
||||
@@ -247,13 +247,19 @@ public static class LegalityCheckResultCodeExtensions
|
||||
G7BSocialShouldBe100Mood => localization.G7BSocialShouldBe100Mood,
|
||||
GanbaruStatLEQ_01 => localization.GanbaruStatTooHigh,
|
||||
GenderInvalidNone => localization.GenderInvalidNone,
|
||||
GeoBadOrder => localization.GeoBadOrder,
|
||||
GeoHardwareInvalid => localization.GeoHardwareInvalid,
|
||||
GeoHardwareRange => localization.GeoHardwareRange,
|
||||
GeoHardwareValid => localization.GeoHardwareValid,
|
||||
GeoMemoryMissing => localization.GeoMemoryMissing,
|
||||
GeoNoCountryHT => localization.GeoNoCountryHT,
|
||||
GeoNoRegion => localization.GeoNoRegion,
|
||||
GeoBadOrder_0 => localization.GeoBadOrder_0,
|
||||
GeoNoCountry_0 => localization.GeoNoCountry_0,
|
||||
GeoNoRegion_0 => localization.GeoNoRegion_0,
|
||||
GTSTrainerSanitized => localization.GTSTrainerSanitized,
|
||||
GTSTradedKoreanInternational => localization.GTSTradedKoreanInternational,
|
||||
GTSTrainerSanitizedExpected => localization.GTSTrainerSanitizedExpected,
|
||||
GTSDisallowedClassicRibbon => localization.GTSDisallowedClassicRibbon,
|
||||
GTSDisallowedTradedEgg => localization.GTSDisallowedTradedEgg,
|
||||
HintEvolvesToSpecies_0 => localization.HintEvolvesToSpecies_0,
|
||||
HintEvolvesToRareForm_0 => localization.HintEvolvesToRareForm_0,
|
||||
ItemEgg => localization.ItemEgg,
|
||||
@@ -361,7 +367,7 @@ public static class LegalityCheckResultCodeExtensions
|
||||
StatIncorrectCP_0 => localization.StatIncorrectCP,
|
||||
StatGigantamaxInvalid => localization.StatGigantamaxInvalid,
|
||||
StatGigantamaxValid => localization.StatGigantamaxValid,
|
||||
StatNatureInvalid => localization.StatNatureInvalid,
|
||||
StatAlignmentInvalid => localization.StatAlignmentInvalid,
|
||||
StatBattleVersionInvalid => localization.StatBattleVersionInvalid,
|
||||
StatNobleInvalid => localization.StatNobleInvalid,
|
||||
StatAlphaInvalid => localization.StatAlphaInvalid,
|
||||
@@ -397,7 +403,6 @@ public static class LegalityCheckResultCodeExtensions
|
||||
TransferMetLocation => localization.TransferMetLocation,
|
||||
TransferNature => localization.TransferNature,
|
||||
TransferObedienceLevel => localization.TransferObedienceLevel,
|
||||
TransferKoreanGen4 => localization.TransferKoreanGen4,
|
||||
TransferEncryptGen6BitFlip => localization.TransferPIDECBitFlip,
|
||||
TransferEncryptGen6Equals => localization.TransferPIDECEquals,
|
||||
TransferEncryptGen6Xor => localization.TransferPIDECXor,
|
||||
|
||||
@@ -27,7 +27,7 @@ public static class MethodCXD
|
||||
// * => IV, IV, ability, PID, PID
|
||||
var s = XDRNG.Next3(seed);
|
||||
uint pid = GetPID(s, id32, noShiny);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(EntityGender.GetFromPIDAndRatio(pid, gr)))
|
||||
continue;
|
||||
@@ -65,7 +65,7 @@ public static bool SetFromIVs<TEntity>(TEntity pk, in EncounterCriteria criteria
|
||||
// * => IV, IV, ability, PID, PID
|
||||
var s = XDRNG.Next3(seed);
|
||||
uint pid = GetPID(s, id32, noShiny);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(EntityGender.GetFromPIDAndRatio(pid, gr)))
|
||||
continue;
|
||||
@@ -122,7 +122,7 @@ public static bool SetStarterFromTrainerID(CK3 pk, in EncounterCriteria criteria
|
||||
pid = GetColoStarterPID(ref s, id32);
|
||||
}
|
||||
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
// fixed gender & never shiny, ignore
|
||||
|
||||
@@ -166,7 +166,7 @@ public static bool SetStarterFirstFromIVs(CK3 pk, in EncounterCriteria criteria)
|
||||
var id32 = sid << 16 | tid;
|
||||
|
||||
uint pid = GetColoStarterPID(ref s, id32);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
// fixed gender & never shiny, ignore
|
||||
|
||||
@@ -280,7 +280,7 @@ public static bool SetStarterFromTrainerID(XK3 pk, in EncounterCriteria criteria
|
||||
var s = XDRNG.Next7(seed); // tid/sid (2), fake pid (2), ivs (2), ability (1)
|
||||
|
||||
uint pid = GetPID(s);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(EntityGender.GetFromPID(pid, EntityGender.VM)))
|
||||
continue;
|
||||
@@ -319,7 +319,7 @@ public static bool SetStarterFromIVs(XK3 pk, in EncounterCriteria criteria)
|
||||
var s = XDRNG.Next3(seed);
|
||||
|
||||
uint pid = GetPID(s);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
|
||||
var tid = XDRNG.Prev3(seed) >> 16;
|
||||
@@ -359,7 +359,7 @@ public static void SetStarterRandom(XK3 pk, in EncounterCriteria criteria, uint
|
||||
// Get PID
|
||||
seed = XDRNG.Next7(seed); // tid, sid, fakePID x2, IVs x2, ability* => pid1, pid2
|
||||
var pid = GetPIDRegular(ref seed);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(EntityGender.GetFromPID(pid, EntityGender.VM)))
|
||||
continue;
|
||||
@@ -407,7 +407,7 @@ public static void SetStarterRandom(XK3 pk, in EncounterCriteria criteria, uint
|
||||
{
|
||||
// fakePID x2, IVs x2, ability, pid1*, pid2
|
||||
var pid = GetPIDReuse(ref seed, id32, noShiny);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(EntityGender.GetFromPIDAndRatio(pid, gender)))
|
||||
continue;
|
||||
@@ -451,7 +451,7 @@ public static void SetRandom<T>(T pk, in EncounterCriteria criteria, PersonalInf
|
||||
{
|
||||
// fakePID x2, IVs x2, ability, pid1*, pid2
|
||||
var pid = GetPIDReuse(ref seed, id32, noShiny);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(EntityGender.GetFromPIDAndRatio(pid, gender)))
|
||||
continue;
|
||||
|
||||
@@ -59,7 +59,6 @@ public static PokeSpotSetup IsValidActivation(byte slot, uint seed, out uint ori
|
||||
/// <returns><see langword="true"/> if both the PID and IV origin seeds are successfully retrieved; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool TryGetOriginSeeds(PKM pk, EncounterSlot3XD slot, out uint pid, out uint ivs)
|
||||
{
|
||||
pid = 0;
|
||||
ivs = 0;
|
||||
if (!TryGetOriginSeedPID(pk.PID, slot.SlotNumber, out pid))
|
||||
return false;
|
||||
@@ -258,7 +257,7 @@ public static uint GetRandomPID(uint id32, in EncounterCriteria criteria, byte g
|
||||
continue;
|
||||
}
|
||||
var pid = GetPIDRegular(ref seed);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(EntityGender.GetFromPIDAndRatio(pid, gender)))
|
||||
continue;
|
||||
|
||||
@@ -22,8 +22,7 @@ public static void SetRandom<T>(this T enc, PK3 pk, PersonalInfo3 pi, in Encount
|
||||
{
|
||||
if (checkProc)
|
||||
{
|
||||
var check = new LeadSeed(seed, LeadRequired.None);
|
||||
if (!MethodH.CheckEncounterActivation(enc, ref check))
|
||||
if (!MethodH.CheckEncounterActivation(enc, seed, LeadRequired.None, out _))
|
||||
{
|
||||
seed = LCRNG.Next(seed);
|
||||
continue;
|
||||
@@ -99,7 +98,7 @@ public static void SetRandomUnown<T>(this T enc, PK3 pk, in EncounterCriteria cr
|
||||
continue;
|
||||
|
||||
// Check the nature is what the user requested.
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
break;
|
||||
|
||||
var iv32 = ClassicEraRNG.GetSequentialIVs(ref seed);
|
||||
@@ -133,12 +132,12 @@ public bool SetFromIVs(PK3 pk, PersonalInfo3 pi, in EncounterCriteria criteria,
|
||||
var a = LCRNG.Next16(ref s);
|
||||
var b = LCRNG.Next16(ref s);
|
||||
var pid = GetPIDRegular(a, b);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
{
|
||||
// Try again as Method 2 (AB-DE)
|
||||
var o = seed >> 16;
|
||||
pid = GetPIDRegular(o, a);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
seed = LCRNG.Prev(seed);
|
||||
}
|
||||
@@ -177,7 +176,7 @@ public bool SetFromIVs(PK3 pk, PersonalInfo3 pi, in EncounterCriteria criteria,
|
||||
var a = LCRNG.Next16(ref s);
|
||||
var b = LCRNG.Next16(ref s);
|
||||
var pid = GetPIDRegular(a, b);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
|
||||
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
|
||||
@@ -220,12 +219,12 @@ public bool SetFromIVsUnown(PK3 pk, in EncounterCriteria criteria)
|
||||
var a = LCRNG.Next16(ref s);
|
||||
var b = LCRNG.Next16(ref s);
|
||||
var pid = GetPIDUnown(a, b);
|
||||
if ((criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25))) || EntityPID.GetUnownForm3(pid) != enc.Form)
|
||||
if ((criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid)) || EntityPID.GetUnownForm3(pid) != enc.Form)
|
||||
{
|
||||
// Try again as Method 2 (BA-DE)
|
||||
var o = seed >> 16;
|
||||
pid = GetPIDUnown(o, a);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
var form = EntityPID.GetUnownForm3(pid);
|
||||
if (form != enc.Form)
|
||||
@@ -253,7 +252,7 @@ public bool SetFromIVsUnown(PK3 pk, in EncounterCriteria criteria)
|
||||
var a = LCRNG.Next16(ref s);
|
||||
var b = LCRNG.Next16(ref s);
|
||||
var pid = GetPIDUnown(a, b);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
var form = EntityPID.GetUnownForm3(pid);
|
||||
if (form != enc.Form)
|
||||
|
||||
@@ -37,7 +37,7 @@ public static class MethodH
|
||||
/// <inheritdoc cref="GetSeed{TEnc,TEvo}(TEnc, uint, TEvo, bool, byte, byte)"/>
|
||||
public static LeadSeed GetSeed<TEnc>(TEnc enc, uint seed, bool emerald, byte gender)
|
||||
where TEnc : IEncounterSlot3
|
||||
=> GetSeed(enc, seed, enc, emerald, gender, 0);
|
||||
=> GetSeed(enc, seed, enc, emerald, gender, FormatNoLevelCheck);
|
||||
|
||||
/// <remarks>Used when generating with specific level ranges.</remarks>
|
||||
/// <inheritdoc cref="GetSeed{TEnc,TEvo}(TEnc, uint, TEvo, bool, byte, byte)"/>
|
||||
@@ -55,6 +55,7 @@ public static LeadSeed GetSeed<TEnc>(TEnc enc, uint seed, bool emerald, byte gen
|
||||
// Intimidate/Keen Eye: rand() & 1 == 1; -- 0 will reject the encounter.
|
||||
|
||||
private const byte Format = 3;
|
||||
private const byte FormatNoLevelCheck = 0; // anything but `Format` will ignore met level precision (overwritten via transfer, test logic, etc.)
|
||||
|
||||
private static bool IsCuteCharmFail(uint rand) => (rand % 3) == 0; // 1/3 odds
|
||||
private static bool IsCuteCharmPass(uint rand) => (rand % 3) != 0; // 2/3 odds
|
||||
@@ -140,13 +141,10 @@ private static LeadSeed GetOriginSeedEmerald<T>(T enc, uint seed, byte nature, i
|
||||
{
|
||||
if (TryGetMatch(enc, levelMin, levelMax, seed, nature, format, out var result))
|
||||
{
|
||||
if (CheckEncounterActivationEmerald(enc, ref result))
|
||||
{
|
||||
if (result.IsNoRequirement)
|
||||
return result;
|
||||
if (result.IsBetterThan(prefer))
|
||||
prefer = result;
|
||||
}
|
||||
if (result.IsNoRequirement)
|
||||
return result;
|
||||
if (result.IsBetterThan(prefer))
|
||||
prefer = result;
|
||||
}
|
||||
|
||||
if (reverseCount == 0)
|
||||
@@ -165,13 +163,10 @@ private static LeadSeed GetOriginSeedEmerald<T>(T enc, uint seed, byte nature, i
|
||||
if (TryGetMatch(enc, levelMin, levelMax, seed, nature, format, out var result) && result.IsNoRequirement)
|
||||
{
|
||||
result.Lead = CuteCharm;
|
||||
if (CheckEncounterActivationEmerald(enc, ref result))
|
||||
{
|
||||
if (result.IsNoRequirement)
|
||||
return result;
|
||||
if (result.IsBetterThan(prefer))
|
||||
prefer = result;
|
||||
}
|
||||
if (result.IsNoRequirement)
|
||||
return result;
|
||||
if (result.IsBetterThan(prefer))
|
||||
prefer = result;
|
||||
}
|
||||
revCute--;
|
||||
if (revCute == 0)
|
||||
@@ -180,28 +175,13 @@ private static LeadSeed GetOriginSeedEmerald<T>(T enc, uint seed, byte nature, i
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CheckEncounterActivationEmerald<T>(T enc, ref LeadSeed result)
|
||||
where T : IEncounterSlot3
|
||||
{
|
||||
if (enc.Type is Rock_Smash)
|
||||
return IsRockSmashPossible(enc.AreaRate, ref result.Seed);
|
||||
if (enc.Type.IsFishingRodType())
|
||||
return true; // can just wait and trigger after hooking.
|
||||
|
||||
// Can sweet scent trigger.
|
||||
return true;
|
||||
}
|
||||
|
||||
private static LeadSeed GetOriginSeed<T>(T enc, uint seed, byte nature, int reverseCount, byte levelMin, byte levelMax, byte format = Format)
|
||||
where T : IEncounterSlot3
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (TryGetMatchNoLead(enc, levelMin, levelMax, seed, nature, format, out var result))
|
||||
{
|
||||
if (CheckEncounterActivation(enc, ref result))
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
if (reverseCount == 0)
|
||||
break;
|
||||
reverseCount--;
|
||||
@@ -224,11 +204,30 @@ public static uint SkipToLevelRand<T>(T enc, uint seed)
|
||||
return LCRNG.Next2(seed); // ESV, level.
|
||||
}
|
||||
|
||||
public static bool CheckEncounterActivation<T>(T enc, ref LeadSeed result)
|
||||
/// <summary>
|
||||
/// Checks an input seed and lead by unrolling to the encounter trigger state and checking the encounter conditions along the way.
|
||||
/// </summary>
|
||||
/// <param name="enc">Encounter to check against.</param>
|
||||
/// <param name="seed">Seed that immediately selects the encounter slot.</param>
|
||||
/// <param name="lead">Party lead effect that is active at the moment of encounter slot selection.</param>
|
||||
/// <param name="result">Un-rolled seed and lead at the moment of encounter trigger, if the check passes.</param>
|
||||
/// <returns><see langword="true"/> if the seed and lead can trigger the encounter; otherwise, <see langword="false"/>.</returns>
|
||||
/// <remarks>
|
||||
/// It is necessary to check this when exploring possible leads, as different leads (or lack thereof) may consume a different quantity of RNG calls.
|
||||
/// </remarks>
|
||||
public static bool CheckEncounterActivation<T>(T enc, uint seed, LeadRequired lead, out LeadSeed result)
|
||||
where T : IEncounterSlot3
|
||||
{
|
||||
var pass = CheckEncounterActivation(enc, ref seed, ref lead);
|
||||
result = new(seed, lead);
|
||||
return pass;
|
||||
}
|
||||
|
||||
private static bool CheckEncounterActivation<T>(T enc, ref uint seed, ref LeadRequired _)
|
||||
where T : IEncounterSlot3
|
||||
{
|
||||
if (enc.Type is Rock_Smash)
|
||||
return IsRockSmashPossible(enc.AreaRate, ref result.Seed);
|
||||
return IsRockSmashPossible(enc.AreaRate, ref seed);
|
||||
if (enc.Type.IsFishingRodType())
|
||||
return true; // can just wait and trigger after hooking.
|
||||
// Can sweet scent trigger.
|
||||
@@ -303,8 +302,8 @@ private static bool TryGetMatchNoLead<T>(T enc, byte levelMin, byte levelMax, ui
|
||||
if (IsSafariBlockProc(safariBlockSeed))
|
||||
{
|
||||
var ctx = new FrameCheckDetails<T>(enc, safariBlockSeed, levelMin, levelMax, format);
|
||||
if (IsSlotValidRegular(ctx, out uint origin))
|
||||
{ result = new(origin, None); return true; }
|
||||
if (IsSlotValidRegular(ctx, out result))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (enc.Species is (ushort)Species.Unown) // No Nature in loop
|
||||
@@ -312,8 +311,8 @@ private static bool TryGetMatchNoLead<T>(T enc, byte levelMin, byte levelMax, ui
|
||||
// Consumers of the seed will assume nature is used; shift the frames so the values line up with their uses.
|
||||
var noNatureCallUsed = LCRNG.Next(seed);
|
||||
var ctx = new FrameCheckDetails<T>(enc, noNatureCallUsed, levelMin, levelMax, format);
|
||||
if (IsSlotValidRegular(ctx, out uint origin))
|
||||
{ result = new(origin, None); return true; }
|
||||
if (IsSlotValidRegular(ctx, out result))
|
||||
return true;
|
||||
}
|
||||
else if (GetNature(p0) == nature)
|
||||
{
|
||||
@@ -323,8 +322,8 @@ private static bool TryGetMatchNoLead<T>(T enc, byte levelMin, byte levelMax, ui
|
||||
seed = LCRNG.Prev(seed);
|
||||
|
||||
var ctx = new FrameCheckDetails<T>(enc, seed, levelMin, levelMax, format);
|
||||
if (IsSlotValidRegular(ctx, out uint origin))
|
||||
{ result = new(origin, None); return true; }
|
||||
if (IsSlotValidRegular(ctx, out result))
|
||||
return true;
|
||||
}
|
||||
result = default; return false;
|
||||
}
|
||||
@@ -358,11 +357,8 @@ private static bool TryGetMatch<T>(T enc, byte levelMin, byte levelMax, uint see
|
||||
if (syncProc)
|
||||
{
|
||||
var ctx = new FrameCheckDetails<T>(enc, seed, levelMin, levelMax, format);
|
||||
if (IsSlotValidRegular(ctx, out var regular))
|
||||
{
|
||||
result = new(regular, Synchronize);
|
||||
if (IsSlotValidRegular(ctx, out result, Synchronize))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
var reg = GetNature(p0) == nature;
|
||||
if (reg)
|
||||
@@ -450,27 +446,27 @@ private static bool IsSlotValidHustleVitalFail<T>(in FrameCheckDetails<T> ctx, o
|
||||
private static bool TryGetMatchNoSync<T>(in FrameCheckDetails<T> ctx, out LeadSeed result)
|
||||
where T : IEncounterSlot3
|
||||
{
|
||||
if (IsSlotValidRegular(ctx, out uint seed))
|
||||
{ result = new(seed, None); return true; }
|
||||
if (IsSlotValidRegular(ctx, out result))
|
||||
return true;
|
||||
|
||||
if (IsSlotValidSyncFail(ctx, out seed))
|
||||
{ result = new(seed, SynchronizeFail); return true; }
|
||||
if (IsSlotValidCuteCharmFail(ctx, out seed))
|
||||
{ result = new(seed, CuteCharmFail); return true; }
|
||||
if (IsSlotValidHustleVitalFail(ctx, out seed))
|
||||
{ result = new(seed, PressureHustleSpiritFail); return true; }
|
||||
if (IsSlotValidStaticMagnetFail(ctx, out seed))
|
||||
{ result = new(seed, StaticMagnetFail); return true; }
|
||||
if (IsSlotValidSyncFail(ctx, out var seed) && CheckEncounterActivation(ctx.Encounter, seed, SynchronizeFail, out result))
|
||||
return true;
|
||||
if (IsSlotValidCuteCharmFail(ctx, out seed) && CheckEncounterActivation(ctx.Encounter, seed, CuteCharmFail, out result))
|
||||
return true;
|
||||
if (IsSlotValidHustleVitalFail(ctx, out seed) && CheckEncounterActivation(ctx.Encounter, seed, PressureHustleSpiritFail, out result))
|
||||
return true;
|
||||
if (IsSlotValidStaticMagnetFail(ctx, out seed) && CheckEncounterActivation(ctx.Encounter, seed, StaticMagnetFail, out result))
|
||||
return true;
|
||||
// Intimidate/Keen Eye failing will result in no encounter.
|
||||
|
||||
if (IsSlotValidStaticMagnet(ctx, out seed, out var lead))
|
||||
{ result = new(seed, lead); return true; }
|
||||
if (IsSlotValidHustleVital(ctx, out seed))
|
||||
{ result = new(seed, PressureHustleSpirit); return true; }
|
||||
if (IsSlotValidIntimidate(ctx, out seed))
|
||||
{ result = new(seed, IntimidateKeenEyeFail); return true; }
|
||||
if (TryGetMatchCuteCharm(ctx, out seed))
|
||||
{ result = new(seed, CuteCharm); return true; }
|
||||
if (IsSlotValidStaticMagnet(ctx, out seed, out var lead) && CheckEncounterActivation(ctx.Encounter, seed, lead, out result))
|
||||
return true;
|
||||
if (IsSlotValidHustleVital(ctx, out seed) && CheckEncounterActivation(ctx.Encounter, seed, PressureHustleSpirit, out result))
|
||||
return true;
|
||||
if (IsSlotValidIntimidate(ctx, out seed) && CheckEncounterActivation(ctx.Encounter, seed, IntimidateKeenEyeFail, out result))
|
||||
return true;
|
||||
if (TryGetMatchCuteCharm(ctx, out seed) && CheckEncounterActivation(ctx.Encounter, seed, CuteCharm, out result))
|
||||
return true;
|
||||
|
||||
result = default; return false;
|
||||
}
|
||||
@@ -505,7 +501,7 @@ private static bool IsSlotValidFrom1SkipMinus1<T>(FrameCheckDetails<T> ctx, out
|
||||
result = 0; return false;
|
||||
}
|
||||
|
||||
private static bool IsSlotValidRegular<T>(in FrameCheckDetails<T> ctx, out uint result)
|
||||
private static bool IsSlotValidRegular<T>(in FrameCheckDetails<T> ctx, out LeadSeed result, LeadRequired lead = None)
|
||||
where T : IEncounterSlot3
|
||||
{
|
||||
// -2 ESV
|
||||
@@ -513,10 +509,10 @@ private static bool IsSlotValidRegular<T>(in FrameCheckDetails<T> ctx, out uint
|
||||
// 0 Nature
|
||||
if (IsLevelValid(ctx.Encounter, ctx.LevelMin, ctx.LevelMax, ctx.Format, ctx.Prev1))
|
||||
{
|
||||
if (IsSlotValid(ctx.Encounter, ctx.Prev2))
|
||||
{ result = ctx.Seed3; return true; }
|
||||
if (IsSlotValid(ctx.Encounter, ctx.Prev2) && CheckEncounterActivation(ctx.Encounter, ctx.Seed3, lead, out result))
|
||||
return true;
|
||||
}
|
||||
result = 0; return false;
|
||||
result = default; return false;
|
||||
}
|
||||
|
||||
private static bool IsSlotValidHustleVital<T>(in FrameCheckDetails<T> ctx, out uint result)
|
||||
@@ -634,7 +630,7 @@ private static bool IsOriginalLevelValid(byte min, byte max, byte format, uint l
|
||||
|
||||
private static bool IsRockSmashPossible(byte areaRate, ref uint seed)
|
||||
{
|
||||
if (IsRatePass(seed, areaRate, None)) // Lead doesn't matter, doesn't influence.
|
||||
if (IsRatePass(seed, areaRate)) // Lead doesn't matter, doesn't influence (Emerald Rock Smash ignores ability).
|
||||
{
|
||||
seed = LCRNG.Prev(seed);
|
||||
return true;
|
||||
@@ -644,7 +640,7 @@ private static bool IsRockSmashPossible(byte areaRate, ref uint seed)
|
||||
|
||||
private const ushort MaxEncounterRate = 2880; // 0xB40
|
||||
|
||||
private static bool IsRatePass(uint seed, byte areaRate, LeadRequired lead, bool ignoreAbility = true)
|
||||
private static bool IsRatePass(uint seed, byte areaRate, LeadRequired lead = None, bool ignoreAbility = true)
|
||||
{
|
||||
var u16 = seed >> 16;
|
||||
var encRate = GetEncounterRate(areaRate, lead, ignoreAbility);
|
||||
@@ -654,7 +650,7 @@ private static bool IsRatePass(uint seed, byte areaRate, LeadRequired lead, bool
|
||||
private static uint GetEncounterRate(byte areaRate, LeadRequired lead, bool ignoreAbility)
|
||||
{
|
||||
uint encRate = areaRate * 16u;
|
||||
// We intend to pass the encounter, as we want an encounter to trigger.
|
||||
// We desire to pass the encounter, as we want an encounter to trigger.
|
||||
// Player on a Bike adjusts by *80 /100. We assume the player is not on a bike.
|
||||
// Cleanse Tag adjusts by *2 /3. We assume the player is not using a Cleanse Tag.
|
||||
// Black Flute adjusts by /2. We assume the player is not using a Black Flute.
|
||||
|
||||
@@ -61,4 +61,18 @@ public static bool TryGetSeed(Nature nature, out ushort seed)
|
||||
seed = Seeds[(int)nature];
|
||||
return seed != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the held item for a generated WISHMKR Jirachi.
|
||||
/// </summary>
|
||||
/// <param name="rand16">rand() 16-bits immediately after IVs are determined</param>
|
||||
/// <returns>The item is either 170 (Salac) or 169 (Ganlon)</returns>
|
||||
public static byte GetHeldItem(uint rand16) => (byte)(170 - ((rand16 / 3) & 1));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the held item for a generated WISHMKR Jirachi from the 16-bit seed.
|
||||
/// </summary>
|
||||
/// <param name="seed">The 16-bit seed used to generate the Jirachi.</param>
|
||||
/// <returns>The item is either 170 (Salac) or 169 (Ganlon)</returns>
|
||||
public static byte GetHeldItemFromSeed(ushort seed) => GetHeldItem(LCRNG.Next5(seed) >> 16);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace PKHeX.Core;
|
||||
/// </summary>
|
||||
public static class GenerateMethodJ
|
||||
{
|
||||
/// <param name="enc">Encounter slot to generate for</param>
|
||||
extension<T>(T enc) where T : IEncounterSlot4
|
||||
{
|
||||
/// <summary>
|
||||
@@ -33,8 +32,7 @@ public uint SetRandomJ(PK4 pk, PersonalInfo4 pi, in EncounterCriteria criteria,
|
||||
{
|
||||
if (checkProc)
|
||||
{
|
||||
var check = new LeadSeed(seed, LeadRequired.None);
|
||||
if (!MethodJ.CheckEncounterActivation(enc, ref check))
|
||||
if (!MethodJ.CheckEncounterActivation(enc, seed, LeadRequired.None, out _))
|
||||
{
|
||||
seed = LCRNG.Next(seed);
|
||||
continue;
|
||||
@@ -127,7 +125,7 @@ public bool SetFromIVsJ(PK4 pk, PersonalInfo4 pi, in EncounterCriteria criteria,
|
||||
var pid = GetPIDRegular(a, b);
|
||||
if (criteria.Shiny.IsShiny() != ShinyUtil.GetIsShiny3(id32, pid))
|
||||
continue;
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
|
||||
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
|
||||
|
||||
@@ -33,8 +33,7 @@ public uint SetRandomK(PK4 pk, PersonalInfo4 pi, in EncounterCriteria criteria,
|
||||
{
|
||||
if (checkProc)
|
||||
{
|
||||
var check = new LeadSeed(seed, LeadRequired.None);
|
||||
if (!MethodK.CheckEncounterActivation(enc, ref check))
|
||||
if (!MethodK.CheckEncounterActivation(enc, seed, LeadRequired.None, out _))
|
||||
{
|
||||
seed = LCRNG.Next(seed);
|
||||
continue;
|
||||
@@ -127,7 +126,7 @@ public bool SetFromIVsK(PK4 pk, PersonalInfo4 pi, in EncounterCriteria criteria,
|
||||
var a = LCRNG.Next16(ref s);
|
||||
var b = LCRNG.Next16(ref s);
|
||||
var pid = GetPIDRegular(a, b);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(pid))
|
||||
continue;
|
||||
|
||||
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
|
||||
|
||||
@@ -32,7 +32,7 @@ public static class MethodJ
|
||||
/// <remarks>Used when generating or ignoring level ranges.</remarks>
|
||||
/// <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, 0);
|
||||
=> GetSeed(enc, seed, enc, FormatNoLevelCheck);
|
||||
|
||||
/// <remarks>Used when generating with specific level ranges.</remarks>
|
||||
/// <inheritdoc cref="GetSeed{TEnc,TEvo}(TEnc, uint, TEvo, byte)"/>
|
||||
@@ -54,6 +54,7 @@ public static class MethodJ
|
||||
// Feebas Spot: rand() >> 15 == 1; -- 0 will use regular encounters.
|
||||
|
||||
private const byte Format = 4;
|
||||
private const byte FormatNoLevelCheck = 0; // anything but `Format` will ignore met level precision (overwritten via transfer, test logic, etc.)
|
||||
|
||||
private static bool IsCuteCharmFail(uint rand) => (rand / 0x5556) == 0; // 1/3 odds
|
||||
private static bool IsCuteCharmPass(uint rand) => (rand / 0x5556) != 0; // 2/3 odds
|
||||
@@ -86,13 +87,10 @@ public static LeadSeed GetOriginSeed<T>(T enc, uint seed, byte nature, int rever
|
||||
{
|
||||
if (TryGetMatch(enc, levelMin, levelMax, seed, nature, format, out var result))
|
||||
{
|
||||
if (CheckEncounterActivation(enc, ref result))
|
||||
{
|
||||
if (result.IsNoRequirement)
|
||||
return result;
|
||||
if (result.IsBetterThan(prefer))
|
||||
prefer = result;
|
||||
}
|
||||
if (result.IsNoRequirement)
|
||||
return result;
|
||||
if (result.IsBetterThan(prefer))
|
||||
prefer = result;
|
||||
}
|
||||
if (reverseCount == 0)
|
||||
return prefer;
|
||||
@@ -122,48 +120,45 @@ public static uint SkipToLevelRand<T>(T enc, uint seed)
|
||||
return LCRNG.Next3(seed); // Proc, ESV, level.
|
||||
}
|
||||
|
||||
public static bool CheckEncounterActivation<T>(T enc, ref LeadSeed result)
|
||||
/// <summary>
|
||||
/// Checks an input seed and lead by unrolling to the encounter trigger state and checking the encounter conditions along the way.
|
||||
/// </summary>
|
||||
/// <param name="enc">Encounter to check against.</param>
|
||||
/// <param name="seed">Seed that immediately selects the encounter slot.</param>
|
||||
/// <param name="lead">Party lead effect that is active at the moment of encounter slot selection.</param>
|
||||
/// <param name="result">Un-rolled seed and lead at the moment of encounter trigger, if the check passes.</param>
|
||||
/// <returns><see langword="true"/> if the seed and lead can trigger the encounter; otherwise, <see langword="false"/>.</returns>
|
||||
/// <remarks>
|
||||
/// It is necessary to check this when exploring possible leads, as different leads (or lack thereof) may consume a different quantity of RNG calls.
|
||||
/// </remarks>
|
||||
public static bool CheckEncounterActivation<T>(T enc, uint seed, LeadRequired lead, out LeadSeed result)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
if (enc.Type.IsFishingRodType())
|
||||
{
|
||||
if (enc is EncounterSlot4 { Parent.IsCoronetFeebasArea: true } s4)
|
||||
{
|
||||
if (!IsValidCoronetB1F(s4, ref result.Seed))
|
||||
return false;
|
||||
}
|
||||
// D/P/Pt don't update the rod rate boost for Suction Cups or Sticky Hold correctly.
|
||||
return IsFishPossible(enc.Type, ref result.Seed);
|
||||
}
|
||||
if (enc.Type is HoneyTree)
|
||||
{
|
||||
// Doesn't actually consume the Encounter Slot call, we return true when comparing ESV.
|
||||
// Roll forward once here rather than add a branch in each method.
|
||||
ref var seed = ref result.Seed;
|
||||
seed = LCRNG.Next(seed);
|
||||
}
|
||||
// Can sweet scent trigger.
|
||||
return true;
|
||||
var pass = CheckEncounterActivation(enc, ref seed, ref lead);
|
||||
result = new(seed, lead);
|
||||
return pass;
|
||||
}
|
||||
|
||||
private static bool CheckEncounterActivation<T>(T enc, ref uint result)
|
||||
private static bool CheckEncounterActivation<T>(T enc, ref uint seed, ref LeadRequired _)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
// Lead is required to be Cute Charm.
|
||||
if (enc.Type.IsFishingRodType())
|
||||
{
|
||||
if (enc is EncounterSlot4 { Parent.IsCoronetFeebasArea: true } s4)
|
||||
{
|
||||
if (!IsValidCoronetB1F(s4, ref result))
|
||||
// 50% chance to replace the regular encounter with Feebas, only if on the tile.
|
||||
if (!IsValidCoronetB1F(s4, ref seed))
|
||||
return false;
|
||||
// Previous frame still has to pass the rod check.
|
||||
}
|
||||
return IsFishPossible(enc.Type, ref result);
|
||||
// D/P/Pt don't update the rod rate boost for Suction Cups or Sticky Hold correctly. Having them as lead is equivalent to None.
|
||||
return IsFishPossible(enc.Type, ref seed);
|
||||
}
|
||||
if (enc.Type is HoneyTree)
|
||||
{
|
||||
// Doesn't actually consume the Encounter Slot call, we return true when comparing ESV.
|
||||
// Roll forward once here rather than add a branch in each method.
|
||||
result = LCRNG.Next(result);
|
||||
seed = LCRNG.Next(seed);
|
||||
}
|
||||
// Can sweet scent trigger.
|
||||
return true;
|
||||
@@ -173,6 +168,7 @@ private static bool CheckEncounterActivation<T>(T enc, ref uint result)
|
||||
{
|
||||
// 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.
|
||||
// The rand() call is unavoidable when fishing in the area, regardless of fishing from a valid tile.
|
||||
// 0 - Hook
|
||||
// 1 - CheckTiles -- current seed
|
||||
// 2 - ESV
|
||||
@@ -181,7 +177,6 @@ private static bool CheckEncounterActivation<T>(T enc, ref uint result)
|
||||
|
||||
// Regular slots don't need to falsify the rand().
|
||||
// Players can just fish from a non-Feebas tile.
|
||||
// The rand() call is unavoidable when in the area.
|
||||
result = LCRNG.Prev(result);
|
||||
return true;
|
||||
}
|
||||
@@ -202,9 +197,8 @@ public static bool TryGetMatchCuteCharm<T>(T enc, ReadOnlySpan<uint> seeds, byte
|
||||
|
||||
if (!TryGetMatchCuteCharm(ctx, out var s))
|
||||
continue;
|
||||
if (!CheckEncounterActivation(enc, ref s))
|
||||
if (!CheckEncounterActivation(enc, s, CuteCharm, out result))
|
||||
continue;
|
||||
result = new(s, CuteCharm);
|
||||
return true;
|
||||
}
|
||||
if (CanRadar(enc))
|
||||
@@ -240,14 +234,11 @@ private static bool TryGetMatch<T>(T enc, byte levelMin, byte levelMax, uint see
|
||||
return TryGetMatchNoSync(ctx, out result);
|
||||
}
|
||||
var syncProc = IsSyncPass(p0);
|
||||
if (syncProc && !(enc.Type is Grass && enc.LevelMax < levelMin))
|
||||
if (syncProc && levelMin <= enc.LevelMax) // can't boost level if already using Synchronize
|
||||
{
|
||||
var ctx = new FrameCheckDetails<T>(enc, seed, levelMin, levelMax, format);
|
||||
if (IsSlotValidRegular(ctx, out seed))
|
||||
{
|
||||
result = new(seed, Synchronize);
|
||||
if (IsSlotValidRegular(ctx, out result, Synchronize))
|
||||
return true;
|
||||
}
|
||||
if (CanRadar(enc))
|
||||
{
|
||||
result = new(ctx.Seed1, SynchronizeRadar);
|
||||
@@ -307,37 +298,35 @@ private static bool IsSlotValidHustleVitalFail<T>(in FrameCheckDetails<T> ctx, o
|
||||
private static bool TryGetMatchNoSync<T>(in FrameCheckDetails<T> ctx, out LeadSeed result)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
if (ctx.Encounter.Type is Grass)
|
||||
if (ctx.LevelMin > ctx.Encounter.LevelMax)
|
||||
{
|
||||
if (ctx.Encounter.LevelMax > ctx.LevelMin) // Must be boosted via Pressure/Hustle/Vital Spirit
|
||||
{
|
||||
if (IsSlotValidHustleVital(ctx, out var pressure))
|
||||
{ result = new(pressure, PressureHustleSpirit); return true; }
|
||||
result = default; return false;
|
||||
}
|
||||
// Must be boosted via Pressure/Hustle/Vital Spirit
|
||||
if (IsSlotValidHustleVital(ctx, out var pressure) && CheckEncounterActivation(ctx.Encounter, pressure, PressureHustleSpirit, out result))
|
||||
return true;
|
||||
result = default; return false;
|
||||
}
|
||||
|
||||
if (IsSlotValidRegular(ctx, out uint seed))
|
||||
{ result = new(seed, None); return true; }
|
||||
if (IsSlotValidRegular(ctx, out result))
|
||||
return true;
|
||||
|
||||
if (IsSlotValidSyncFail(ctx, out seed))
|
||||
{ result = new(seed, SynchronizeFail); return true; }
|
||||
if (IsSlotValidCuteCharmFail(ctx, out seed))
|
||||
{ result = new(seed, CuteCharmFail); return true; }
|
||||
if (IsSlotValidHustleVitalFail(ctx, out seed))
|
||||
{ result = new(seed, PressureHustleSpiritFail); return true; }
|
||||
if (IsSlotValidStaticMagnetFail(ctx, out seed))
|
||||
{ result = new(seed, StaticMagnetFail); return true; }
|
||||
if (IsSlotValidSyncFail(ctx, out var seed) && CheckEncounterActivation(ctx.Encounter, seed, SynchronizeFail, out result))
|
||||
return true;
|
||||
if (IsSlotValidCuteCharmFail(ctx, out seed) && CheckEncounterActivation(ctx.Encounter, seed, CuteCharmFail, out result))
|
||||
return true;
|
||||
if (IsSlotValidHustleVitalFail(ctx, out seed) && CheckEncounterActivation(ctx.Encounter, seed, PressureHustleSpiritFail, out result))
|
||||
return true;
|
||||
if (IsSlotValidStaticMagnetFail(ctx, out seed) && CheckEncounterActivation(ctx.Encounter, seed, StaticMagnetFail, out result))
|
||||
return true;
|
||||
// Intimidate/Keen Eye failing will result in no encounter.
|
||||
|
||||
if (IsSlotValidStaticMagnet(ctx, out seed, out var lead))
|
||||
{ result = new(seed, lead); return true; }
|
||||
if (IsSlotValidIntimidate(ctx, out seed))
|
||||
{ result = new(seed, IntimidateKeenEyeFail); return true; }
|
||||
if (ctx.Encounter.PressureLevel <= ctx.LevelMax) // Can be boosted, or not.
|
||||
if (IsSlotValidStaticMagnet(ctx, out seed, out var sm) && CheckEncounterActivation(ctx.Encounter, seed, sm, out result))
|
||||
return true;
|
||||
if (IsSlotValidIntimidate(ctx, out seed) && CheckEncounterActivation(ctx.Encounter, seed, IntimidateKeenEyeFail, out result))
|
||||
return true;
|
||||
if (ctx.LevelMax >= ctx.Encounter.PressureLevel) // Can be boosted, or not.
|
||||
{
|
||||
if (IsSlotValidHustleVital(ctx, out var pressure))
|
||||
{ result = new(pressure, PressureHustleSpirit); return true; }
|
||||
if (IsSlotValidHustleVital(ctx, out var pressure) && CheckEncounterActivation(ctx.Encounter, pressure, PressureHustleSpirit, out result))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (CanRadar(ctx.Encounter))
|
||||
@@ -367,23 +356,23 @@ private static bool IsSlotValidFrom1Skip<T>(FrameCheckDetails<T> ctx, out uint r
|
||||
result = 0; return false;
|
||||
}
|
||||
|
||||
private static bool IsSlotValidRegular<T>(in FrameCheckDetails<T> ctx, out uint result)
|
||||
private static bool IsSlotValidRegular<T>(in FrameCheckDetails<T> ctx, out LeadSeed result, LeadRequired lead = None)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
if (IsLevelRand(ctx.Encounter))
|
||||
{
|
||||
if (ctx.Encounter.IsFixedLevel|| IsLevelValid(ctx.Encounter, ctx.LevelMin, ctx.LevelMax, ctx.Format, ctx.Prev1))
|
||||
if (ctx.Encounter.IsFixedLevel || IsLevelValid(ctx.Encounter, ctx.LevelMin, ctx.LevelMax, ctx.Format, ctx.Prev1))
|
||||
{
|
||||
if (IsSlotValid(ctx.Encounter, ctx.Prev2))
|
||||
{ result = ctx.Seed3; return true; }
|
||||
if (IsSlotValid(ctx.Encounter, ctx.Prev2) && CheckEncounterActivation(ctx.Encounter, ctx.Seed3, lead, out result))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else // Not random level
|
||||
{
|
||||
if (IsSlotValid(ctx.Encounter, ctx.Prev1))
|
||||
{ result = ctx.Seed2; return true; }
|
||||
if (IsSlotValid(ctx.Encounter, ctx.Prev1) && CheckEncounterActivation(ctx.Encounter, ctx.Seed2, lead, out result))
|
||||
return true;
|
||||
}
|
||||
result = 0; return false;
|
||||
result = default; return false;
|
||||
}
|
||||
|
||||
private static bool IsSlotValidHustleVital<T>(in FrameCheckDetails<T> ctx, out uint result)
|
||||
|
||||
@@ -25,23 +25,38 @@ public static class MethodK
|
||||
/// <remarks>Used when generating or ignoring level ranges.</remarks>
|
||||
/// <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, 0);
|
||||
=> GetSeed(enc, seed, enc, FormatNoLevelCheck);
|
||||
|
||||
/// <remarks>Used when generating with specific level ranges.</remarks>
|
||||
/// <inheritdoc cref="GetSeed{TEnc,TEvo}(TEnc, uint, TEvo, byte)"/>
|
||||
public static LeadSeed GetSeed<TEnc, TEvo>(TEnc enc, uint seed, TEvo evo)
|
||||
where TEnc : IEncounterSlot4
|
||||
where TEvo : ILevelRange
|
||||
=> GetSeed(enc, seed, evo, 4);
|
||||
=> GetSeed(enc, seed, evo, Format);
|
||||
|
||||
public static LeadSeed GetSeed<TEnc>(TEnc enc, uint seed, byte levelMin, byte levelMax, byte format = Format, int depth = 0)
|
||||
/// <param name="depth">Recursion depth for checking re-rolls. 0 for no recursion, up to 4 for exhausted recursion (default 0).</param>
|
||||
/// <param name="forceSyncLead">Force a specific nature for synchronization (default random).</param>
|
||||
/// <remarks>
|
||||
/// The additional optional parameters are used internally to track the state of recursion solving for repeat IV attempts. Calling this method externally should omit them.
|
||||
/// </remarks>
|
||||
/// <inheritdoc cref="GetSeed{TEnc,TEvo}(TEnc, uint, TEvo, byte)"/>
|
||||
// ReSharper disable InvalidXmlDocComment
|
||||
private static LeadSeed GetSeed<TEnc>(TEnc enc, uint seed, byte levelMin, byte levelMax, byte format = Format, int depth = 0, Nature forceSyncLead = LeadSyncAllowed)
|
||||
// ReSharper restore InvalidXmlDocComment
|
||||
where TEnc : IEncounterSlot4
|
||||
{
|
||||
var ctx = GetSearchContext(enc, seed, levelMin, levelMax, format);
|
||||
return GetSeedCore(ctx, seed, depth, forceSyncLead);
|
||||
}
|
||||
|
||||
private static LeadSeed GetSeedCore<TEnc>(in SearchContext<TEnc> ctx, uint seed, int depth = 0, Nature forceSyncLead = LeadSyncAllowed)
|
||||
where TEnc : IEncounterSlot4
|
||||
{
|
||||
var pid = ClassicEraRNG.GetSequentialPID(seed);
|
||||
var nature = (byte)(pid % 25);
|
||||
|
||||
var frames = GetReversalWindow(seed, nature);
|
||||
return GetOriginSeed(enc, seed, nature, frames, levelMin, levelMax, format, depth);
|
||||
return GetOriginSeed(ctx, seed, nature, frames, depth, forceSyncLead);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="MethodJ.GetReversalWindow"/>
|
||||
@@ -57,6 +72,7 @@ public static LeadSeed GetSeed<TEnc>(TEnc enc, uint seed, byte levelMin, byte le
|
||||
// Intimidate/Keen Eye: rand() & 1 == 1; -- 0 will reject the encounter.
|
||||
|
||||
private const byte Format = 4;
|
||||
private const byte FormatNoLevelCheck = 0; // anything but `Format` will ignore met level precision (overwritten via transfer, test logic, etc.)
|
||||
|
||||
private static bool IsCuteCharmFail(uint rand) => (rand % 3) == 0; // 1/3 odds
|
||||
private static bool IsCuteCharmPass(uint rand) => (rand % 3) != 0; // 2/3 odds
|
||||
@@ -75,16 +91,31 @@ public static LeadSeed GetSeed<TEnc>(TEnc enc, uint seed, byte levelMin, byte le
|
||||
|
||||
public static uint GetNature(uint rand) => rand % 25;
|
||||
|
||||
private static SearchContext<T> GetSearchContext<T>(T enc, uint seed, byte levelMin, byte levelMax, byte format)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
var isRerollMinimum31 = enc is EncounterSlot4 { IsRerollMinimum31: true };
|
||||
var mustFailAllPreviousRerolls = isRerollMinimum31 && !HasAny31IV(seed); // 1-((31/32)^6)^4) = 53.32% for a BCC/Safari to arise with a 31-IV
|
||||
return new(enc, levelMin, levelMax, format, isRerollMinimum31, mustFailAllPreviousRerolls);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first possible origin seed and lead for the input encounter & constraints.
|
||||
/// </summary>
|
||||
public static LeadSeed GetOriginSeed<T>(T enc, uint seed, byte nature, int reverseCount, byte levelMin, byte levelMax, byte format = Format, int depth = 0)
|
||||
public static LeadSeed GetOriginSeed<T>(T enc, uint seed, byte nature, int reverseCount, byte levelMin, byte levelMax, byte format = Format, int depth = 0, Nature forceSyncLead = LeadSyncAllowed)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
var ctx = GetSearchContext(enc, seed, levelMin, levelMax, format);
|
||||
return GetOriginSeed(ctx, seed, nature, reverseCount, depth, forceSyncLead);
|
||||
}
|
||||
|
||||
private static LeadSeed GetOriginSeed<T>(in SearchContext<T> ctx, uint seed, byte nature, int reverseCount, int depth = 0, Nature forceSyncLead = LeadSyncAllowed)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
LeadSeed prefer = default;
|
||||
while (true)
|
||||
{
|
||||
if (TryGetMatch(enc, levelMin, levelMax, seed, nature, format, out var result, depth))
|
||||
if (TryGetMatch(ctx, seed, nature, out var result, depth, forceSyncLead))
|
||||
{
|
||||
if (result.IsNoRequirement)
|
||||
return result;
|
||||
@@ -114,18 +145,36 @@ public static uint SkipToLevelRand<T>(T enc, uint seed, LeadRequired lead)
|
||||
return LCRNG.Next2(seed); // ESV, level.
|
||||
}
|
||||
|
||||
public static bool CheckEncounterActivation<T>(T enc, ref LeadSeed result)
|
||||
/// <summary>
|
||||
/// Checks an input seed and lead by unrolling to the encounter trigger state and checking the encounter conditions along the way.
|
||||
/// </summary>
|
||||
/// <param name="enc">Encounter to check against.</param>
|
||||
/// <param name="seed">Seed that immediately selects the encounter slot.</param>
|
||||
/// <param name="lead">Party lead effect that is active at the moment of encounter slot selection.</param>
|
||||
/// <param name="result">Un-rolled seed and lead at the moment of encounter trigger, if the check passes.</param>
|
||||
/// <returns><see langword="true"/> if the seed and lead can trigger the encounter; otherwise, <see langword="false"/>.</returns>
|
||||
/// <remarks>
|
||||
/// It is necessary to check this when exploring possible leads, as different leads (or lack thereof) may consume a different quantity of RNG calls.
|
||||
/// </remarks>
|
||||
public static bool CheckEncounterActivation<T>(T enc, uint seed, LeadRequired lead, out LeadSeed result)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
var pass = CheckEncounterActivation(enc, ref seed, ref lead);
|
||||
result = new(seed, lead);
|
||||
return pass;
|
||||
}
|
||||
|
||||
private static bool CheckEncounterActivation<T>(T enc, ref uint seed, ref LeadRequired lead)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
if (enc.Type.IsFishingRodType())
|
||||
return IsFishPossible(enc.Type, ref result.Seed, ref result.Lead);
|
||||
return IsFishPossible(enc.Type, ref seed, ref lead);
|
||||
if (enc.Type is Rock_Smash)
|
||||
return IsRockSmashPossible(enc.AreaRate, ref result.Seed, ref result.Lead);
|
||||
return IsRockSmashPossible(enc.AreaRate, ref seed, ref lead);
|
||||
|
||||
// Ability & Sweet Scent deadlock for BCC:
|
||||
if (enc.Type is BugContest && !result.Lead.IsAbleToSweetScent())
|
||||
return IsBugContestPossibleDeadlock(enc.AreaRate, ref result.Seed);
|
||||
|
||||
if (enc.Type is BugContest && !lead.IsAbleToSweetScent())
|
||||
return IsBugContestPossibleDeadlock(enc.AreaRate, ref seed);
|
||||
// Can sweet scent trigger.
|
||||
return true;
|
||||
}
|
||||
@@ -139,22 +188,6 @@ public static bool CheckEncounterActivation<T>(T enc, ref LeadSeed result)
|
||||
// Static/Magnet Pull: None
|
||||
;
|
||||
|
||||
private static bool CheckEncounterActivationCuteCharm<T>(T enc, ref uint result) where T : IEncounterSlot4
|
||||
{
|
||||
// Lead is required to be Cute Charm.
|
||||
if (enc.Type.IsFishingRodType())
|
||||
return IsFishPossible(enc.Type, ref result);
|
||||
if (enc.Type is Rock_Smash)
|
||||
return IsRockSmashPossible(enc.AreaRate, ref result);
|
||||
|
||||
// Ability & Sweet Scent deadlock for BCC:
|
||||
if (enc.Type is BugContest) // No species with Sweet Scent can have Cute Charm.
|
||||
return IsBugContestPossibleDeadlock(enc.AreaRate, ref result);
|
||||
|
||||
// Can sweet scent trigger.
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to find a matching seed for the given encounter and constraints for Cute Charm buffered PIDs.
|
||||
/// </summary>
|
||||
@@ -170,101 +203,143 @@ public static bool TryGetMatchCuteCharm<T>(T enc, ReadOnlySpan<uint> seeds, byte
|
||||
var ctx = new FrameCheckDetails<T>(enc, seed, levelMin, levelMax, format);
|
||||
if (!TryGetMatchCuteCharm(ctx, out var s))
|
||||
continue;
|
||||
if (!CheckEncounterActivationCuteCharm(enc, ref s))
|
||||
if (!CheckEncounterActivation(enc, s, CuteCharm, out result))
|
||||
continue;
|
||||
|
||||
result = new(s, CuteCharm);
|
||||
return true;
|
||||
}
|
||||
result = default; return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TryGetMatch<T>(T enc, byte levelMin, byte levelMax, uint seed, byte nature, byte format, out LeadSeed result, int depth = 0)
|
||||
private static bool TryGetMatch<T>(in SearchContext<T> ctx, uint seed, byte nature, out LeadSeed result, int depth = 0, Nature forceSyncLead = LeadSyncAllowed)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
var p0 = seed >> 16; // 0
|
||||
var reg = GetNature(p0) == nature;
|
||||
if (reg)
|
||||
{
|
||||
var ctx = new FrameCheckDetails<T>(enc, seed, levelMin, levelMax, format);
|
||||
if (TryGetMatchNoSync(ctx, out result) && CheckEncounterActivation(enc, ref result))
|
||||
return true;
|
||||
if (depth != 4 && enc is EncounterSlot4 s && (s.IsBugContest || s.IsSafariHGSS))
|
||||
return Recurse4x(enc, levelMin, levelMax, seed, nature, format, out result, ++depth);
|
||||
}
|
||||
else if (IsSyncPass(p0) && !(enc.Type is Grass && enc.LevelMax < levelMin))
|
||||
{
|
||||
var ctx = new FrameCheckDetails<T>(enc, seed, levelMin, levelMax, format);
|
||||
if (IsSlotValidRegular(ctx, out seed))
|
||||
var frame = ctx.GetFrameRef(seed);
|
||||
// Ideally, we don't need to check the recursion. Eagerly check the non-recursive paths.
|
||||
if (forceSyncLead is (LeadSyncAllowed or LeadSyncDisallowed))
|
||||
{
|
||||
result = new(seed, Synchronize);
|
||||
if (CheckEncounterActivation(enc, ref result))
|
||||
if (TryGetMatchNoSync(frame, out result) && ctx.CanAcceptDirectMatch(depth))
|
||||
return true;
|
||||
}
|
||||
else // Recursion demands a specific/unspecific synchronization nature.
|
||||
{
|
||||
if (TryGetMatchOnlyFailSync(frame, out result) && ctx.CanAcceptDirectMatch(depth))
|
||||
{
|
||||
if (forceSyncLead is not LeadSyncRequiredFailUnspecific) // If we locked in a specific synchronization nature yet, prefer to indicate the end result.
|
||||
result.Lead = Synchronize; // Override back to the end-result synchronize.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.ShouldRecurse(depth))
|
||||
{
|
||||
// If we haven't locked in to a synchronize lead based on recursion, try earlier frames.
|
||||
if (forceSyncLead is (LeadSyncAllowed or LeadSyncDisallowed))
|
||||
{
|
||||
if (RecurseReject(ctx, seed, out result, depth + 1, LeadSyncDisallowed))
|
||||
return true;
|
||||
}
|
||||
var prev = frame.Seed1;
|
||||
// If rerolls can reach here from a failed synchronize check (unknown nature), then check that path as well.
|
||||
if (forceSyncLead is not LeadSyncDisallowed && !IsSyncPass(prev >> 16) && ctx.LevelMin <= ctx.Encounter.LevelMax) // can't boost level if already using Synchronize
|
||||
{
|
||||
var lead = forceSyncLead == LeadSyncAllowed ? LeadSyncRequiredFailUnspecific : forceSyncLead;
|
||||
if (RecurseReject(ctx, prev, out result, depth + 1, lead))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a successful sync activation.
|
||||
// If recursion demands us to be a specific synchronization nature, ensure it matches. If not, can't be a sync activation.
|
||||
if (forceSyncLead is not (LeadSyncAllowed or LeadSyncRequiredFailUnspecific))
|
||||
{
|
||||
// LeadSyncDisallowed innately passes this check, no need to explicitly check that case.
|
||||
if (forceSyncLead != (Nature)nature)
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var syncProc = IsSyncPass(p0);
|
||||
if (syncProc && ctx.LevelMin <= ctx.Encounter.LevelMax) // can't boost level if already using Synchronize
|
||||
{
|
||||
var frame = ctx.GetFrameRef(seed);
|
||||
if (IsSlotValidRegular(frame, out result, Synchronize) && ctx.CanAcceptDirectMatch(depth))
|
||||
return true;
|
||||
|
||||
if (ctx.ShouldRecurse(depth))
|
||||
{
|
||||
if (RecurseReject(ctx, seed, out result, depth + 1, (Nature)nature))
|
||||
return true;
|
||||
}
|
||||
if (depth != 4 && enc is EncounterSlot4 s && (s.IsBugContest || s.IsSafariHGSS))
|
||||
return Recurse4x(enc, levelMin, levelMax, seed, nature, format, out result, ++depth);
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private const int MaxSafariContest = 4;
|
||||
/// <summary>
|
||||
/// Represents the magic number that that allows any lead to be used, enabling the generation target to require or not require a synchronize lead.
|
||||
/// </summary>
|
||||
private const Nature LeadSyncAllowed = Nature.Random;
|
||||
|
||||
private static bool Recurse4x<T>(T enc, byte levelMin, byte levelMax, uint seed, byte nature, byte format,
|
||||
out LeadSeed result, int depth)
|
||||
/// <summary>
|
||||
/// Represents a special value indicating that synchronize is disallowed for the lead.
|
||||
/// </summary>
|
||||
/// <remarks>When the generation pattern is traversing upwards under the assumption that it was a regular nature check, this value is used to prevent traversing up synchronize origins.</remarks>
|
||||
private const Nature LeadSyncDisallowed = unchecked(LeadSyncAllowed + 1);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a special value indicating that synchronize is required, but no specific nature has been required yet.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Any other value [0,24] is a specific nature being required as a synchronize lead.
|
||||
/// </remarks>
|
||||
private const Nature LeadSyncRequiredFailUnspecific = unchecked(LeadSyncAllowed + 2);
|
||||
|
||||
/// <summary>
|
||||
/// Recursive method to check if the input seed could have been generated after 1-4 failed attempts at re-rolling for 31-IVs.
|
||||
/// </summary>
|
||||
private static bool RecurseReject<T>(in SearchContext<T> ctx, uint seed, out LeadSeed result, int depth, Nature forceSyncLead)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
// When generating Pokémon's IVs, the game tries to give at least one flawless IV.
|
||||
// The game will roll the {nature,PID/IV} up to 4 times if none of the IVs are at 31 (total of 3 re-rolls)
|
||||
// The game will roll the {(sync,)nature..PID/IV} up to 4 times if none of the IVs are at 31 (total of 3 failed attempts at re-rolls).
|
||||
|
||||
// Entering this method, the RNG state (seed) is the nature/sync call that generates the Pokémon we're solving for.
|
||||
// Check that the previous 2 frames are a valid rejection for IVs, then recurse to generate that "failed" as our PID/IV origin search.
|
||||
result = default;
|
||||
|
||||
// Use the depth to keep track of how many we have already burned.
|
||||
// First entry to this method will be 1/4 burned (final result).
|
||||
if (depth == MaxSafariContest)
|
||||
|
||||
// Ensure if the previous 4 consumed frames {PID,PID,IV,IV} yielded a 31-IV Pokémon. If so, it couldn't have been re-rolled from.
|
||||
var iv2 = LCRNG.Prev16(ref seed);
|
||||
if (IsAny31(iv2))
|
||||
return false;
|
||||
var iv1 = LCRNG.Prev16(ref seed);
|
||||
if (IsAny31(iv1))
|
||||
return false;
|
||||
// Cool, the skipped Pokémon was not 31-IV.
|
||||
// The skipped Pokémon probably had a different nature, so we need to be sure to use that result when recursing.
|
||||
seed = LCRNG.Prev3(seed); // Jump back before the PID frames; origin generates the (PID+badIVs)
|
||||
|
||||
// Check if the previous 4 frames were a 31-IV Pokémon. If so, it couldn't have been re-rolled from.
|
||||
// If it was, we can adjust our parameters and try the entire search again (expensive!)
|
||||
// We need to repeat the entire top-level check with slightly adjusted parameters.
|
||||
// The recursion will check that this fail-skipped can be generated (skipped PID/IV could have been landed on via its nature/sync check).
|
||||
// The innate recursion will return true if the skipped frame could have been landed on, or if an even-more previous fail-skipped was landed.
|
||||
// We pass in the `forceSyncLead` param in the event we just locked into a specific synchronization nature that must be used by all previous fail-skips.
|
||||
// The synchronization nature lock-in is required, to prevent the recursion fail-skips from matching a different lead/sync nature.
|
||||
result = GetSeedCore(ctx, seed, depth, forceSyncLead);
|
||||
if (result.IsValid)
|
||||
return true;
|
||||
|
||||
// First we need to determine how far back we can permit the previous skipped encounter to be originating from.
|
||||
// Our only requirement is that the nature PID we currently have not been generated.
|
||||
// This is the same "solved" problem as the regular reversal window.
|
||||
|
||||
// Each loop we have a ~18.75% chance to hit a >=1 31 IV setup and return false.
|
||||
// On average, we look back 5-6 times.
|
||||
bool breakNext = false;
|
||||
while (true)
|
||||
{
|
||||
var iv2 = seed >> 16;
|
||||
if (IsAny31(iv2))
|
||||
return false;
|
||||
|
||||
var iv1 = LCRNG.Prev16(ref seed);
|
||||
if (IsAny31(iv1))
|
||||
return false;
|
||||
|
||||
// Since we're looking backwards and doing recursion in the same method, we need to ensure we don't exceed our previous nature window.
|
||||
var sanityNature = ClassicEraRNG.GetSequentialPID(seed) % 25;
|
||||
if (sanityNature == nature)
|
||||
breakNext = true;
|
||||
|
||||
// Cool, the skipped Pokémon was not 31-IV. Let's try again.
|
||||
// The skipped Pokémon probably had a different nature, so we need to use that value instead.
|
||||
// We basically need to repeat the entire top-level check with slightly adjusted parameters.
|
||||
var origin = LCRNG.Prev2(seed);
|
||||
|
||||
// We need to double-check that this skipped PID/IV could have been landed on via the nature/sync check.
|
||||
// The innate recursion will return true if the skipped frame could have been landed on, or if an even-more previous skipped was landed.
|
||||
result = GetSeed(enc, origin, levelMin, levelMax, format, depth);
|
||||
if (result.IsValid)
|
||||
return true;
|
||||
|
||||
// If the forwards window no longer lets us land on the frame we entered this method from, the window is exhausted.
|
||||
// We need to do at least one recursion as the 4 calls we look backwards from will hide a double-nature PID/frame pair.
|
||||
if (breakNext)
|
||||
return false; // Window exhausted, this nature would have been chosen instead of the frame we entered this method from.
|
||||
}
|
||||
// This rejection target can't be reached from any starting point.
|
||||
// The (unreachable) RNG calls that follow will not be able to generate our input target.
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -280,6 +355,16 @@ public static bool IsAny31(uint iv16)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsLow5Bits31(uint iv16) => (iv16 & 0x1F) == 0x1F;
|
||||
|
||||
private static bool HasAny31IV(uint origin)
|
||||
{
|
||||
var seed = LCRNG.Next3(origin); // hop over pid,pid to get iv1
|
||||
var iv1 = seed >> 16;
|
||||
if (IsAny31(iv1))
|
||||
return true;
|
||||
var iv2 = LCRNG.Next16(ref seed);
|
||||
return IsAny31(iv2);
|
||||
}
|
||||
|
||||
private static bool TryGetMatchCuteCharm<T>(in FrameCheckDetails<T> ctx, out uint result)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
@@ -325,40 +410,53 @@ private static bool IsSlotValidHustleVitalFail<T>(in FrameCheckDetails<T> ctx, o
|
||||
return IsSlotValidFrom1Skip(ctx, out result);
|
||||
}
|
||||
|
||||
private static bool TryGetMatchOnlyFailSync<T>(in FrameCheckDetails<T> ctx, out LeadSeed result)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
if (ctx.LevelMin > ctx.Encounter.LevelMax)
|
||||
{
|
||||
// Must be boosted via Pressure/Hustle/Vital Spirit
|
||||
result = default; return false;
|
||||
}
|
||||
|
||||
if (IsSlotValidSyncFail(ctx, out var seed) && CheckEncounterActivation(ctx.Encounter, seed, SynchronizeFail, out result))
|
||||
return true;
|
||||
|
||||
result = default; return false;
|
||||
}
|
||||
|
||||
private static bool TryGetMatchNoSync<T>(in FrameCheckDetails<T> ctx, out LeadSeed result)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
if (ctx.Encounter.Type is Grass)
|
||||
if (ctx.LevelMin > ctx.Encounter.LevelMax)
|
||||
{
|
||||
if (ctx.Encounter.LevelMax < ctx.LevelMin) // Must be boosted via Pressure/Hustle/Vital Spirit
|
||||
{
|
||||
if (IsSlotValidHustleVital(ctx, out var pressure))
|
||||
{ result = new(pressure, PressureHustleSpirit); return true; }
|
||||
result = default; return false;
|
||||
}
|
||||
// Must be boosted via Pressure/Hustle/Vital Spirit
|
||||
if (IsSlotValidHustleVital(ctx, out var pressure) && CheckEncounterActivation(ctx.Encounter, pressure, PressureHustleSpirit, out result))
|
||||
return true;
|
||||
result = default; return false;
|
||||
}
|
||||
|
||||
if (IsSlotValidRegular(ctx, out uint seed))
|
||||
{ result = new(seed, None); return true; }
|
||||
if (IsSlotValidRegular(ctx, out result))
|
||||
return true;
|
||||
|
||||
if (IsSlotValidSyncFail(ctx, out seed))
|
||||
{ result = new(seed, SynchronizeFail); return true; }
|
||||
if (IsSlotValidCuteCharmFail(ctx, out seed))
|
||||
{ result = new(seed, CuteCharmFail); return true; }
|
||||
if (IsSlotValidHustleVitalFail(ctx, out seed))
|
||||
{ result = new(seed, PressureHustleSpiritFail); return true; }
|
||||
if (IsSlotValidStaticMagnetFail(ctx, out seed))
|
||||
{ result = new(seed, StaticMagnetFail); return true; }
|
||||
if (IsSlotValidSyncFail(ctx, out var seed) && CheckEncounterActivation(ctx.Encounter, seed, SynchronizeFail, out result))
|
||||
return true;
|
||||
if (IsSlotValidCuteCharmFail(ctx, out seed) && CheckEncounterActivation(ctx.Encounter, seed, CuteCharmFail, out result))
|
||||
return true;
|
||||
if (IsSlotValidHustleVitalFail(ctx, out seed) && CheckEncounterActivation(ctx.Encounter, seed, PressureHustleSpiritFail, out result))
|
||||
return true;
|
||||
if (IsSlotValidStaticMagnetFail(ctx, out seed) && CheckEncounterActivation(ctx.Encounter, seed, StaticMagnetFail, out result))
|
||||
return true;
|
||||
// Intimidate/Keen Eye failing will result in no encounter.
|
||||
|
||||
if (IsSlotValidStaticMagnet(ctx, out seed, out var sm))
|
||||
{ result = new(seed, sm); return true; }
|
||||
if (IsSlotValidIntimidate(ctx, out seed))
|
||||
{ result = new(seed, IntimidateKeenEyeFail); return true; }
|
||||
if (ctx.Encounter.PressureLevel <= ctx.LevelMax) // Can be boosted, or not.
|
||||
if (IsSlotValidStaticMagnet(ctx, out seed, out var sm) && CheckEncounterActivation(ctx.Encounter, seed, sm, out result))
|
||||
return true;
|
||||
if (IsSlotValidIntimidate(ctx, out seed) && CheckEncounterActivation(ctx.Encounter, seed, IntimidateKeenEyeFail, out result))
|
||||
return true;
|
||||
if (ctx.LevelMax >= ctx.Encounter.PressureLevel) // Can be boosted, or not.
|
||||
{
|
||||
if (IsSlotValidHustleVital(ctx, out var pressure))
|
||||
{ result = new(pressure, PressureHustleSpirit); return true; }
|
||||
if (IsSlotValidHustleVital(ctx, out var pressure) && CheckEncounterActivation(ctx.Encounter, pressure, PressureHustleSpirit, out result))
|
||||
return true;
|
||||
}
|
||||
|
||||
result = default; return false;
|
||||
@@ -385,23 +483,23 @@ private static bool IsSlotValidFrom1Skip<T>(FrameCheckDetails<T> ctx, out uint r
|
||||
result = 0; return false;
|
||||
}
|
||||
|
||||
private static bool IsSlotValidRegular<T>(in FrameCheckDetails<T> ctx, out uint result)
|
||||
private static bool IsSlotValidRegular<T>(in FrameCheckDetails<T> ctx, out LeadSeed result, LeadRequired lead = None)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
if (IsLevelRand(ctx.Encounter))
|
||||
{
|
||||
if (ctx.Encounter.IsFixedLevel || IsLevelValid(ctx.Encounter, ctx.LevelMin, ctx.LevelMax, ctx.Format, ctx.Prev1))
|
||||
{
|
||||
if (IsSlotValid(ctx.Encounter, ctx.Prev2))
|
||||
{ result = ctx.Seed3; return true; }
|
||||
if (IsSlotValid(ctx.Encounter, ctx.Prev2) && CheckEncounterActivation(ctx.Encounter, ctx.Seed3, lead, out result))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else // Not random level
|
||||
{
|
||||
if (IsSlotValid(ctx.Encounter, ctx.Prev1))
|
||||
{ result = ctx.Seed2; return true; }
|
||||
if (IsSlotValid(ctx.Encounter, ctx.Prev1) && CheckEncounterActivation(ctx.Encounter, ctx.Seed2, lead, out result))
|
||||
return true;
|
||||
}
|
||||
result = 0; return false;
|
||||
result = default; return false;
|
||||
}
|
||||
|
||||
private static bool IsSlotValidHustleVital<T>(in FrameCheckDetails<T> ctx, out uint result)
|
||||
@@ -525,16 +623,18 @@ private static bool IsBugContestPossibleDeadlock(uint areaRate, ref uint result)
|
||||
// The only entry into this method requires an ability that has no species available with Sweet Scent.
|
||||
// Therefore, without Sweet Scent, we need to trigger via turning/walking.
|
||||
// With an area rate of 25, this arrangement will succeed 37% of the time.
|
||||
// However, White Flute cannot stay active as you cannot use it during a BCC, and entering the BCC has a screen transition (disabling it).
|
||||
|
||||
// The game checks 2 random calls to trigger the encounter: movement -> rate -> generate.
|
||||
// HG/SS has an underflow error (via radio) which can pass the first rand call for movement.
|
||||
// Only need to check the second call for rate.
|
||||
// Rate can be improved by 50% if the White Flute is used.
|
||||
// Other abilities can also affect the rate, but we can't use them with our current lead.
|
||||
var rate = areaRate + (areaRate >> 1); // +50% White Flute
|
||||
|
||||
// areaRate += (areaRate >> 1); // +50% White Flute
|
||||
var rand = (result >> 16);
|
||||
var roll = rand % 100;
|
||||
if (roll >= rate)
|
||||
if (roll >= areaRate)
|
||||
return false;
|
||||
|
||||
// Skip backwards before the two calls. Valid encounter seed found.
|
||||
@@ -570,37 +670,6 @@ private static bool IsFishPossible(SlotType4 encType, ref uint seed, ref LeadReq
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lead is something else, and cannot be changed. Does the same as the above method without a ref LeadRequired.
|
||||
private static bool IsFishPossible(SlotType4 encType, ref uint seed)
|
||||
{
|
||||
var rodRate = GetRodRate(encType);
|
||||
var u16 = seed >> 16;
|
||||
var roll = u16 % 100;
|
||||
|
||||
// HG/SS: Lead (Following) Pokémon with >= 250 adds +50 to the rate. Assume the best case.
|
||||
rodRate += 50; // This happens before Suction Cups / Sticky Hold, can be compounded.
|
||||
if (roll < rodRate) // This will always succeed for Good/Super rod due to the base+bonus being >=100
|
||||
{
|
||||
seed = LCRNG.Prev(seed);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Old Rod might reach here (75% < 100%)
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsRockSmashPossible(byte areaRate, ref uint seed)
|
||||
{
|
||||
var u16 = seed >> 16;
|
||||
var roll = u16 % 100;
|
||||
if (roll < areaRate)
|
||||
{
|
||||
seed = LCRNG.Prev(seed);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsRockSmashPossible(byte areaRate, ref uint seed, ref LeadRequired lead)
|
||||
{
|
||||
// No flute boost.
|
||||
@@ -643,4 +712,33 @@ or Super_Rod
|
||||
or Safari_Old_Rod
|
||||
or Safari_Good_Rod
|
||||
or Safari_Super_Rod;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper to cache the encounter details for a given seed and level range, to avoid redundant calculations during recursion for Method K's minimum 31-IV re-roll logic.
|
||||
/// </summary>
|
||||
private readonly record struct SearchContext<T>(T Encounter, byte LevelMin, byte LevelMax, byte CurrentEntityFormat,
|
||||
bool IsRerollMinimum31,
|
||||
bool MustFailAllPreviousRerolls)
|
||||
where T : IEncounterSlot4
|
||||
{
|
||||
public FrameCheckDetails<T> GetFrameRef(uint seed) => new(Encounter, seed, LevelMin, LevelMax, CurrentEntityFormat);
|
||||
|
||||
/// <summary>
|
||||
/// For HG/SS Safari Zone and Bug Catching Contest:
|
||||
/// When generating Pokémon's IVs, the game will reroll the numbers up to 4 times if none of the IVs are at 31.
|
||||
/// </summary>
|
||||
|
||||
private const int MaxSafariContest = 4;
|
||||
|
||||
public bool CanAcceptDirectMatch(int depth) => !MustFailAllPreviousRerolls || depth == (MaxSafariContest - 1);
|
||||
|
||||
public bool ShouldRecurse(int depth)
|
||||
{
|
||||
if (!IsRerollMinimum31)
|
||||
return false; // No reroll requirement, no recursion.
|
||||
if (depth >= MaxSafariContest)
|
||||
return false; // Exhausted all possible rerolls.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Status of passing or failing frame match results.
|
||||
/// </summary>
|
||||
public enum LockInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// PID matches the required parameters.
|
||||
/// </summary>
|
||||
Pass,
|
||||
|
||||
/// <summary>
|
||||
/// PID did not match the required Nature.
|
||||
/// </summary>
|
||||
Nature,
|
||||
|
||||
/// <summary>
|
||||
/// PID did not match the required Gender.
|
||||
/// </summary>
|
||||
Gender,
|
||||
}
|
||||
@@ -139,9 +139,13 @@ public static bool Verify(PKM pk, ulong seed, Span<int> ivs, in GenerateParam8 p
|
||||
if (s.WeightScalar != weight)
|
||||
{
|
||||
if (height == 0 && s.WeightScalar == 0 && HomeQuirks.HasEnteredSetZeroScale(pk))
|
||||
{ } // OK
|
||||
{
|
||||
// OK
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,7 +255,7 @@ public static bool TryApply(PK8 pk, ulong seed, Span<int> ivs, in GenerateParam8
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(nature))
|
||||
return false;
|
||||
|
||||
pk.Nature = pk.StatNature = nature;
|
||||
pk.Nature = pk.StatAlignment = nature;
|
||||
|
||||
var height = rng.NextInt(0x81) + rng.NextInt(0x80);
|
||||
var weight = rng.NextInt(0x81) + rng.NextInt(0x80);
|
||||
|
||||
@@ -176,7 +176,7 @@ public static bool TryApplyFromSeed(PA8 pk, in EncounterCriteria criteria, in Ov
|
||||
var nature = (Nature)rand.NextInt(25);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(nature))
|
||||
return false;
|
||||
pk.Nature = pk.StatNature = nature;
|
||||
pk.Nature = pk.StatAlignment = nature;
|
||||
|
||||
var (height, weight) = para.IsAlpha
|
||||
? (byte.MaxValue, byte.MaxValue)
|
||||
|
||||
@@ -22,7 +22,7 @@ public static void ApplyDetails(PB8 pk, in EncounterCriteria criteria, Shiny shi
|
||||
|
||||
// Since the inner methods do not set Gender (only fixed Genders are applicable) and Nature (assume Synchronize used), set them here.
|
||||
pk.Gender = (byte)(pk.Species == (int)Species.Cresselia ? 1 : 2); // Mesprit
|
||||
pk.Nature = pk.StatNature = criteria.GetNature();
|
||||
pk.Nature = pk.StatAlignment = criteria.GetNature();
|
||||
|
||||
int ctr = 0;
|
||||
var rnd = Util.Rand;
|
||||
|
||||
@@ -134,7 +134,7 @@ public static bool TryApplyFromSeed(PB8 pk, in EncounterCriteria criteria, Shiny
|
||||
if (!criteria.IsSatisfiedNature(nature))
|
||||
return false;
|
||||
|
||||
pk.StatNature = pk.Nature = nature;
|
||||
pk.StatAlignment = pk.Nature = nature;
|
||||
|
||||
// Remainder
|
||||
pk.HeightScalar = (byte)(xors.NextUInt(0x81) + xors.NextUInt(0x80));
|
||||
|
||||
@@ -128,7 +128,7 @@ public static bool GenerateData(PK9 pk, in GenerateParam9 enc, in EncounterCrite
|
||||
// Compromise on Nature -- some are fixed, some are random. If the request wants a specific nature, just mint it.
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(nature))
|
||||
return false;
|
||||
pk.Nature = pk.StatNature = nature;
|
||||
pk.Nature = pk.StatAlignment = nature;
|
||||
|
||||
pk.HeightScalar = enc.Height != 0 ? enc.Height : (byte)(rand.NextInt(0x81) + rand.NextInt(0x80));
|
||||
pk.WeightScalar = enc.Weight != 0 ? enc.Weight : (byte)(rand.NextInt(0x81) + rand.NextInt(0x80));
|
||||
|
||||
@@ -130,7 +130,7 @@ public static bool IsValidEvolutionWithMove(PKM pk, LegalInfo info)
|
||||
if (move is EEVEE)
|
||||
return IsValidEvolutionWithMoveAny(enc, EeveeFairyMoves, pruned, pk, head);
|
||||
|
||||
return MemoryPermissions.GetCanKnowMove(enc, move, pruned, pk, head);
|
||||
return MemoryPermissions.GetCanKnowMove(enc, move, pruned, pk, head, LearnOption.AtAnyTimeChain);
|
||||
}
|
||||
|
||||
private static bool IsMoveInRelearnSource(PKM pk, LegalInfo info, ushort move)
|
||||
@@ -182,7 +182,7 @@ private static bool IsValidEvolutionWithMoveAny(IEncounterTemplate enc, ReadOnly
|
||||
{
|
||||
foreach (var move in any)
|
||||
{
|
||||
if (MemoryPermissions.GetCanKnowMove(enc, move, history, pk, head))
|
||||
if (MemoryPermissions.GetCanKnowMove(enc, move, history, pk, head, LearnOption.AtAnyTimeChain))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -102,7 +102,7 @@ public static bool IsGlitchedHisuianZoroarkSV(PKM pk, IScaledSize s, int cardID)
|
||||
if (s is { HeightScalar: 255, WeightScalar: 255 })
|
||||
{
|
||||
// if Scale is also present, should be 255.
|
||||
return pk is not IScaledSize3 { Scale: not 255 };
|
||||
return pk is not IScaledSize3 { Scale: not 255 };
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
49
PKHeX.Core/Legality/Restrictions/Locale/LocaleNDS4.cs
Normal file
49
PKHeX.Core/Legality/Restrictions/Locale/LocaleNDS4.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides information for Geonet/Battle Revolution player location information.
|
||||
/// </summary>
|
||||
/// <remarks>These values were specific to the NDS games (Generation 4)</remarks>
|
||||
public static class LocaleNDS4
|
||||
{
|
||||
public const int CountryCount = 233;
|
||||
public const int Japan = 103;
|
||||
|
||||
public static ReadOnlySpan<byte> LegalCountries =>
|
||||
[
|
||||
001, 002, 003, 006, 008, 009, 012, 013, 015, 016, 017, 018, 020, 021, 022, 023,
|
||||
025, 027, 028, 029, 031, 033, 034, 035, 036, 040, 042, 043, 045, 048, 049, 050,
|
||||
052, 054, 055, 056, 058, 059, 060, 061, 062, 069, 070, 071, 072, 074, 077, 078,
|
||||
079, 080, 081, 082, 083, 085, 086, 088, 089, 090, 091, 092, 093, 094, 095, 097,
|
||||
098, 100, 101, 102, 103, 104, 107, 111, 115, 117, 118, 121, 122, 126, 129, 131,
|
||||
133, 135, 140, 142, 146, 148, 149, 150, 151, 152, 156, 157, 158, 160, 161, 163,
|
||||
164, 166, 167, 110, 171, 172, 179, 183, 186, 187, 188, 189, 192, 193, 194, 196,
|
||||
198, 199, 200, 202, 205, 207, 211, 212, 216, 218, 219, 204, 221, 220, 222, 224,
|
||||
226, 227,
|
||||
];
|
||||
|
||||
public static byte GetSubregionCount(byte country) => country switch
|
||||
{
|
||||
009 => 24, // Argentina
|
||||
012 => 7, // Australia
|
||||
028 => 27, // Brazil
|
||||
036 => 13, // Canada
|
||||
043 => 31, // China
|
||||
070 => 6, // Finland
|
||||
071 => 22, // France
|
||||
077 => 16, // Germany
|
||||
094 => 35, // India
|
||||
101 => 20, // Italy
|
||||
103 => 50, // Japan
|
||||
156 => 20, // Norway
|
||||
166 => 16, // Poland
|
||||
172 => 7, // Russian Federation
|
||||
193 => 17, // Spain
|
||||
199 => 24, // Sweden
|
||||
219 => 12, // United Kingdom
|
||||
220 => 51, // United States of America
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
49
PKHeX.Core/Legality/Restrictions/Locale/LocaleNDS5.cs
Normal file
49
PKHeX.Core/Legality/Restrictions/Locale/LocaleNDS5.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides information for Unity Tower player location information.
|
||||
/// </summary>
|
||||
/// <remarks>These values were specific to the NDS games (Generation 5)</remarks>
|
||||
public static class LocaleNDS5
|
||||
{
|
||||
public const int CountryCount = 232;
|
||||
public const int Japan = 105;
|
||||
|
||||
public static ReadOnlySpan<byte> LegalCountries =>
|
||||
[
|
||||
001, 002, 003, 006, 008, 009, 012, 013, 015, 016, 017, 018, 020, 021, 022, 023,
|
||||
025, 027, 028, 029, 031, 033, 034, 035, 036, 040, 042, 043, 045, 047, 048, 049,
|
||||
051, 053, 054, 058, 060, 061, 062, 063, 064, 071, 072, 073, 074, 076, 079, 080,
|
||||
081, 082, 083, 084, 085, 087, 088, 090, 091, 092, 093, 094, 095, 096, 098, 099,
|
||||
101, 102, 103, 105, 106, 109, 111, 115, 117, 118, 121, 125, 128, 130, 132, 134,
|
||||
138, 139, 141, 145, 147, 148, 149, 150, 151, 155, 156, 157, 160, 161, 163, 164,
|
||||
166, 167, 170, 173, 174, 181, 185, 186, 188, 189, 190, 191, 194, 195, 196, 198,
|
||||
199, 200, 201, 203, 205, 206, 210, 211, 215, 217, 218, 219, 220, 221, 222, 224,
|
||||
226, 227,
|
||||
];
|
||||
|
||||
public static byte GetSubregionCount(byte country) => country switch
|
||||
{
|
||||
009 => 24, // Argentina
|
||||
012 => 8, // Australia
|
||||
028 => 27, // Brazil
|
||||
036 => 13, // Canada
|
||||
043 => 33, // China
|
||||
072 => 6, // Finland
|
||||
073 => 22, // France
|
||||
079 => 16, // Germany
|
||||
095 => 35, // India
|
||||
102 => 20, // Italy
|
||||
105 => 50, // Japan
|
||||
155 => 22, // Norway
|
||||
166 => 16, // Poland
|
||||
174 => 8, // Russian Federation
|
||||
195 => 17, // Spain
|
||||
200 => 22, // Sweden
|
||||
218 => 12, // United Kingdom
|
||||
220 => 51, // United States of America
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
@@ -161,11 +161,11 @@ private static bool GetCanKnowMove(PKM pk, ushort move, EntityContext context, E
|
||||
return GetCanKnowMove(enc, move, history, pk, game);
|
||||
}
|
||||
|
||||
public static bool GetCanKnowMove(IEncounterTemplate enc, ushort move, EvolutionHistory history, PKM pk, ILearnGroup game)
|
||||
public static bool GetCanKnowMove(IEncounterTemplate enc, ushort move, EvolutionHistory history, PKM pk, ILearnGroup game, LearnOption option = LearnOption.AtAnyTime)
|
||||
{
|
||||
Span<MoveResult> result = stackalloc MoveResult[1];
|
||||
Span<ushort> moves = [move];
|
||||
LearnVerifierHistory.MarkAndIterate(result, moves, enc, pk, history, game, MoveSourceType.All, LearnOption.AtAnyTime);
|
||||
LearnVerifierHistory.MarkAndIterate(result, moves, enc, pk, history, game, MoveSourceType.All, option);
|
||||
return result[0].Valid;
|
||||
}
|
||||
|
||||
|
||||
@@ -193,13 +193,18 @@ public enum LegalityCheckResultCode : ushort
|
||||
GenderInvalidNone,
|
||||
|
||||
// Geography
|
||||
GeoBadOrder,
|
||||
GeoHardwareInvalid,
|
||||
GeoHardwareRange,
|
||||
GeoHardwareValid,
|
||||
GeoMemoryMissing,
|
||||
GeoNoCountryHT,
|
||||
GeoNoRegion,
|
||||
|
||||
// GTS
|
||||
GTSTrainerSanitized, // valid, tag
|
||||
GTSTradedKoreanInternational, // valid
|
||||
GTSTrainerSanitizedExpected,
|
||||
GTSDisallowedClassicRibbon,
|
||||
GTSDisallowedTradedEgg,
|
||||
|
||||
// Hints
|
||||
|
||||
@@ -306,7 +311,7 @@ public enum LegalityCheckResultCode : ushort
|
||||
StatInvalidHeightWeight,
|
||||
StatGigantamaxInvalid,
|
||||
StatGigantamaxValid,
|
||||
StatNatureInvalid,
|
||||
StatAlignmentInvalid,
|
||||
StatBattleVersionInvalid,
|
||||
StatNobleInvalid,
|
||||
StatAlphaInvalid,
|
||||
@@ -352,7 +357,6 @@ public enum LegalityCheckResultCode : ushort
|
||||
TransferMetLocation,
|
||||
TransferNature,
|
||||
TransferObedienceLevel,
|
||||
TransferKoreanGen4,
|
||||
TransferEncryptGen6BitFlip,
|
||||
TransferEncryptGen6Equals,
|
||||
TransferEncryptGen6Xor,
|
||||
@@ -405,6 +409,9 @@ public enum LegalityCheckResultCode : ushort
|
||||
PokerusStrainUnobtainable_0, // strain
|
||||
MemoryHTGender_0, // gender value
|
||||
G6SuperTrainBagInvalid_0,
|
||||
GeoBadOrder_0,
|
||||
GeoNoCountry_0,
|
||||
GeoNoRegion_0,
|
||||
StatIncorrectHeightValue_0,
|
||||
StatIncorrectWeightValue_0,
|
||||
StatIncorrectScaleValue_0,
|
||||
|
||||
@@ -30,6 +30,8 @@ private CheckResult VerifyAbility(LegalityAnalysis data)
|
||||
var pk = data.Entity;
|
||||
if (pk is PA9 pa9)
|
||||
return VerifyBirthAbility(data, pa9);
|
||||
if (pk is PK5 pk5 && IsEdgeCaseBasculin(data, pk5, out var basc))
|
||||
return basc;
|
||||
|
||||
var abilities = (IPersonalAbility12)data.PersonalInfo;
|
||||
|
||||
@@ -95,6 +97,42 @@ private CheckResult VerifyAbility(LegalityAnalysis data)
|
||||
return VerifyAbility(data, abilities, abilIndex);
|
||||
}
|
||||
|
||||
private bool IsEdgeCaseBasculin(LegalityAnalysis data, PK5 pk5, out CheckResult result)
|
||||
{
|
||||
result = default;
|
||||
// Gen5 Only Edge Case issue:
|
||||
// - Basculin-Blue (form 1) *should* have Rock Head.
|
||||
// In B/W, Blue-Striped Basculin have Reckless as its Ability (incorrect) due to referencing the wrong Personal Info.
|
||||
// - However, the in-game trade Blue-Striped Basculin in White that has Rock Head (correct).
|
||||
// In B2/W2, wild Blue-Striped Basculin have Rock Head (correct), but bred Blue-Striped Basculin have Reckless (incorrect).
|
||||
// - When transferred to Pokémon Bank, any Blue-Striped Basculin with Reckless have their Ability changed to Rock Head (fixed).
|
||||
// Impacted encounter types: Egg|Wild (B/W), Egg (B2/W2)
|
||||
|
||||
if (pk5 is not { Species: (int)Species.Basculin, Form: 1, HiddenAbility: false, Ability: not (int)Ability.Adaptability })
|
||||
return false; // check abilities as usual.
|
||||
|
||||
var expect = (int)Ability.RockHead; // correct.
|
||||
if (pk5.Version is GameVersion.B2 or GameVersion.W2)
|
||||
{
|
||||
var enc = data.EncounterMatch;
|
||||
if (enc is EncounterEgg5)
|
||||
expect = (int)Ability.Reckless; // incorrect
|
||||
}
|
||||
else if (pk5.Version is GameVersion.B or GameVersion.W)
|
||||
{
|
||||
var enc = data.EncounterMatch;
|
||||
if (enc is not EncounterTrade5BW)
|
||||
expect = (int)Ability.Reckless; // incorrect
|
||||
}
|
||||
|
||||
var current = pk5.Ability;
|
||||
if (current == expect)
|
||||
result = VALID;
|
||||
else
|
||||
result = GetInvalid(AbilityUnexpected);
|
||||
return true; // tell the analyzer to use this result
|
||||
}
|
||||
|
||||
public static bool IsValidAbilityBits(int bitNum) => bitNum is 1 or 2 or 4;
|
||||
|
||||
private CheckResult VerifyAbility(LegalityAnalysis data, IPersonalAbility12 abilities, int abilIndex)
|
||||
|
||||
@@ -21,20 +21,21 @@ public override void Verify(LegalityAnalysis data)
|
||||
return;
|
||||
}
|
||||
|
||||
// In Generation 1 & 2, when a Pokémon is taken out of the Day Care, its experience will lower to the minimum value for its current level.
|
||||
// When transferred to Gen7+, EVs are reset to 0, so checks will be relevant then.
|
||||
byte format = pk.Format;
|
||||
if (format < 3) // Can abuse daycare for EV training without EXP gain
|
||||
if (format < 3)
|
||||
{
|
||||
VerifyEVsGB(data, pk);
|
||||
return;
|
||||
}
|
||||
|
||||
int sum = pk.EVTotal;
|
||||
if (sum > EffortValues.Max510) // format >= 3
|
||||
data.AddLine(GetInvalid(EffortAbove510));
|
||||
|
||||
var enc = data.EncounterMatch;
|
||||
Span<int> evs = stackalloc int[6];
|
||||
pk.GetEVs(evs);
|
||||
|
||||
var enc = data.EncounterMatch;
|
||||
if (format >= 6 && IsAnyAboveHardLimit6(evs))
|
||||
data.AddLine(GetInvalid(EffortAbove252));
|
||||
else if (format < 5) // 3/4
|
||||
@@ -49,6 +50,48 @@ public override void Verify(LegalityAnalysis data)
|
||||
data.AddLine(Get(Severity.Fishy, EffortAllEqual));
|
||||
}
|
||||
|
||||
private void VerifyEVsGB(LegalityAnalysis data, PKM pk)
|
||||
{
|
||||
ReadOnlySpan<int> evs = [pk.EV_HP, pk.EV_ATK, pk.EV_DEF, pk.EV_SPA, pk.EV_SPE];
|
||||
var hasGainedEVsFromEnemy = IsAnyEVNotVitaminOr0(evs);
|
||||
if (!hasGainedEVsFromEnemy)
|
||||
return;
|
||||
|
||||
// Generation 1/2 have special considerations to verify non-zero EVs, but it really isn't worth verifying.
|
||||
// If an EV stat is not maxed out, then there must be a valid encounter chain (considering teammates diluting the EV gain) that yields the exact EVs (and EXP minimum for Genera1).
|
||||
// In Generation 2, when a Pokémon is taken out of the Daycare, its experience will lower to the minimum value for its current level.
|
||||
// When transferred to Gen7+, EVs are reset to 0, so checks will be relevant then.
|
||||
// For Generation 1 entities that have never visited Generation 2 for the EXP reset:
|
||||
// * We COULD check the EVs but a heuristic for this would be extra annoying for minimal gain.
|
||||
// * Encounter matching deferrals would be required so that there is enough gap in MinLevel=>CurrentLevel to obtain the exact EVs with EXP gain.
|
||||
// * Vitamins in Gen1/2 add 2560 each, to a max of 25600 (10 vitamins used). Would need to subtract off vitamins to check for EVs needed, and species/EXP gain to get them.
|
||||
// * Gen1 EV gain is split same as EXP for participants against the defeated Pokémon; EV gains are the Base Stats of the defeated Pokémon.
|
||||
// * Unexplored: A {species, +EV[6], +EXP} permutation budget. Using Wild Encounters AND trainers, no-Switch AI must disallow the final mon from being used repeatedly.
|
||||
// * Are all EV gains for a specific stat consistent enough to prune to minimum EXP gains for +EV?
|
||||
// * Gen1 daycare can make up the remaining EXP difference.
|
||||
// Same for Generation 2, just with different available enemies and the ability to disregard EXP gain via daycare abuse.
|
||||
|
||||
// Simple sanity check instead of nothing:
|
||||
// Since EV gains absorb base stats of the defeated, you can't have EVs in a stat if any other stat is 0.
|
||||
// Sometimes people may miss entering in a stat accidentally, so this is a simple check to catch that.
|
||||
|
||||
if (evs.Contains(0))
|
||||
data.AddLine(GetInvalid(EffortShouldBeZero));
|
||||
}
|
||||
|
||||
private static bool IsAnyEVNotVitaminOr0(ReadOnlySpan<int> evs)
|
||||
{
|
||||
foreach (var ev in evs)
|
||||
{
|
||||
if (!IsNot0VitaminGB(ev))
|
||||
continue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
static bool IsNot0VitaminGB(int ev) => ev > EffortValues.MaxVitamins12 || (ev % EffortValues.VitaminBoost12) != 0;
|
||||
}
|
||||
|
||||
private void VerifyGainedEVs34(LegalityAnalysis data, IEncounterTemplate enc, ReadOnlySpan<int> evs, PKM pk)
|
||||
{
|
||||
var isVitaminResult = IsWithinVitaminRange34(evs, EffortValues.MaxVitamins34);
|
||||
|
||||
@@ -57,7 +57,7 @@ internal void VerifyCommon(LegalityAnalysis data, PKM pk)
|
||||
{
|
||||
if (record.GetMoveRecordFlagAny())
|
||||
data.AddLine(GetInvalid(Egg, EggRelearnFlags));
|
||||
if (pk.StatNature != pk.Nature)
|
||||
if (pk.StatAlignment != pk.Nature)
|
||||
data.AddLine(GetInvalid(Egg, EggNature));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,11 +261,14 @@ private void VerifyHTMisc(LegalityAnalysis data)
|
||||
|
||||
private void VerifyGeoLocationData(LegalityAnalysis data, IGeoTrack t, PKM pk)
|
||||
{
|
||||
var valid = t.GetValidity();
|
||||
var (valid, index) = t.GetValidity();
|
||||
if (valid == GeoValid.CountryAfterPreviousEmpty)
|
||||
data.AddLine(GetInvalid(GeoBadOrder));
|
||||
data.AddLine(GetInvalid(GeoBadOrder_0, index));
|
||||
else if (valid == GeoValid.RegionWithoutCountry)
|
||||
data.AddLine(GetInvalid(GeoNoRegion));
|
||||
data.AddLine(GetInvalid(GeoNoCountry_0, index));
|
||||
else if (valid == GeoValid.CountryDoesNotHaveRegion)
|
||||
data.AddLine(GetInvalid(GeoNoRegion_0, index));
|
||||
|
||||
if (t.Geo1_Country != 0 && pk.IsUntraded) // traded
|
||||
data.AddLine(GetInvalid(GeoNoCountryHT));
|
||||
}
|
||||
|
||||
@@ -24,14 +24,9 @@ public override void Verify(LegalityAnalysis data)
|
||||
return;
|
||||
}
|
||||
|
||||
// Korean Gen4 games can not trade with other Gen4 languages, but can use Pal Park with any Gen3 game/language.
|
||||
if (pk.Format == 4 && enc.Generation == 4 && !IsValidGen4Korean(currentLanguage)
|
||||
&& enc is not EncounterTrade4PID { IsLanguageSwap: true } // ger magikarp / eng pikachu
|
||||
)
|
||||
{
|
||||
data.AddLine(GetInvalid(TransferKoreanGen4));
|
||||
return;
|
||||
}
|
||||
// Check for GTS trade sanitization.
|
||||
if (pk.Format >= 4)
|
||||
CheckGTS(data, pk, currentLanguage, originalGeneration);
|
||||
|
||||
if (originalGeneration <= 2)
|
||||
{
|
||||
@@ -45,6 +40,47 @@ public override void Verify(LegalityAnalysis data)
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckGTS(LegalityAnalysis data, PKM pk, LanguageID currentLanguage, byte originalGeneration)
|
||||
{
|
||||
bool possiblyRomanizedG4 = false;
|
||||
if (originalGeneration is 4 && currentLanguage is Korean && !pk.IsEgg)
|
||||
{
|
||||
// All OT names are half-width already, so they could have been manually entered.
|
||||
possiblyRomanizedG4 = Gen4GlobalTradeRules.IsRomanizedKoreanTrainerName(pk);
|
||||
// If not nicknamed, the sanitization also applies to the nickname text.
|
||||
// Check that separately, there is some nuance with trade-backs.
|
||||
if (possiblyRomanizedG4) // apply a tag to indicate to the checker, and also downstream checks.
|
||||
data.AddLine(GetValid(GTSTrainerSanitized)); // acts as an info tag.
|
||||
}
|
||||
|
||||
if (pk.Format == 4)
|
||||
{
|
||||
// Any Gen4 trainer can send/receive Korean language, but can't otherwise directly trade across the language barrier.
|
||||
// Check for lockout of Korean GTS trades.
|
||||
var tr = ParseSettings.ActiveTrainer;
|
||||
|
||||
// Check if it must have been traded across the GTS to its current residence.
|
||||
if (tr is null || !Gen4GlobalTradeRules.IsRequiredGTS(tr, currentLanguage))
|
||||
return;
|
||||
|
||||
// Check if it actually can be traded across the GTS.
|
||||
var enc = data.EncounterMatch;
|
||||
if (enc is EncounterTrade4PID { IsLanguageSwap: true })
|
||||
return; // Can originate in Korean games and have international Language ID without traversing the GTS.
|
||||
|
||||
// Eggs and Classic Ribbon cannot be traded on GTS.
|
||||
// If it must have been traded via GTS, it must have been sanitized if Korean.
|
||||
if (pk.IsEgg)
|
||||
data.AddLine(GetInvalid(GTSDisallowedTradedEgg));
|
||||
else if (enc is IRibbonSetEvent4 { RibbonClassic: true })
|
||||
data.AddLine(GetInvalid(GTSDisallowedClassicRibbon));
|
||||
else if (currentLanguage == Korean && !possiblyRomanizedG4)
|
||||
data.AddLine(GetInvalid(GTSTrainerSanitizedExpected));
|
||||
else // OK
|
||||
data.AddLine(GetValid(GTSTradedKoreanInternational));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsValidLanguageID(LanguageID currentLanguage, LanguageID maxLanguageID, PKM pk, IEncounterTemplate enc)
|
||||
{
|
||||
if (currentLanguage == UNUSED_6)
|
||||
@@ -58,26 +94,4 @@ public static bool IsValidLanguageID(LanguageID currentLanguage, LanguageID maxL
|
||||
|
||||
return true; // Language is possible
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the <see cref="pkmLanguage"/> can exist in the Generation 4 save file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Korean Gen4 games can not trade with other Gen4 languages, but can use Pal Park with any Gen3 game/language.
|
||||
/// Anything with Gen4 origin cannot exist in the other language save file.
|
||||
/// </remarks>
|
||||
public static bool IsValidGen4Korean(LanguageID pkmLanguage)
|
||||
{
|
||||
if (ParseSettings.ActiveTrainer is not SAV4 tr)
|
||||
return true; // ignore
|
||||
return IsValidGen4Korean(pkmLanguage, tr);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IsValidGen4Korean(LanguageID)"/>
|
||||
public static bool IsValidGen4Korean(LanguageID pkmLanguage, SAV4 tr)
|
||||
{
|
||||
bool savKOR = (LanguageID)tr.Language == Korean;
|
||||
bool pkmKOR = pkmLanguage == Korean;
|
||||
return savKOR == pkmKOR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,8 +129,8 @@ private void VerifyShedinjaAffixed(LegalityAnalysis data, RibbonIndex affix, PKM
|
||||
clone.Species = (int) Species.Nincada;
|
||||
var args = new RibbonVerifierArguments(clone, enc, data.Info.EvoChainsAllGens);
|
||||
affix.Fix(args, true);
|
||||
bool invalid = RibbonVerifier.IsValidExtra(affix, args);
|
||||
var severity = invalid ? Severity.Invalid : Severity.Fishy;
|
||||
var valid = RibbonVerifier.IsValidExtra(affix, args);
|
||||
var severity = !valid ? Severity.Invalid : Severity.Fishy;
|
||||
data.AddLine(Get(severity, RibbonMarkingAffixed_0, (ushort)affix));
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user