File scoped namespaces (#3529)

[Language Reference](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/file-scoped-namespaces)

Updates all the files, one less level of indentation.

Some small changes were made to API surfaces, renaming `PKM pkm` -> `PKM pk`, and `LegalityAnalysis.pkm` -> `LegalityAnalysis.Entity`
This commit is contained in:
Kurt 2022-06-18 11:04:24 -07:00 committed by GitHub
parent 78092e070d
commit fc754b346b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1124 changed files with 138713 additions and 139627 deletions

45
.editorconfig Normal file
View file

@ -0,0 +1,45 @@
root = true
# All Files
[*]
charset = utf-8
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
# Solution Files
[*.sln]
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
# XML Project Files
[*.csproj]
indent_style = space
indent_size = 2
# Code Files
[*.cs]
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
tab_width = 4
end_of_line = crlf
csharp_prefer_braces = when_multiline:warning
dotnet_diagnostic.IDE0047.severity = none
dotnet_diagnostic.IDE0048.severity = none
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggest
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggest
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggest
dotnet_style_parentheses_in_other_operators = always_for_clarity:suggest
[*.{cs,vb}]
#### Naming styles ####
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.capitalization = pascal_case

View file

@ -3,160 +3,159 @@ using System.Collections.Generic;
using System.Linq;
using static PKHeX.Core.Ball;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Contains logic to apply a new <see cref="Ball"/> value to a <see cref="PKM"/>.
/// </summary>
public static class BallApplicator
{
/// <summary>
/// Contains logic to apply a new <see cref="Ball"/> value to a <see cref="PKM"/>.
/// Gets all balls that are legal for the input <see cref="PKM"/>.
/// </summary>
public static class BallApplicator
/// <remarks>
/// Requires checking the <see cref="LegalityAnalysis"/> for every <see cref="Ball"/> that is tried.
/// </remarks>
/// <param name="pk">Pokémon to retrieve a list of valid balls for.</param>
/// <returns>Enumerable list of <see cref="Ball"/> values that the <see cref="PKM"/> is legal with.</returns>
public static IEnumerable<Ball> GetLegalBalls(PKM pk)
{
/// <summary>
/// Gets all balls that are legal for the input <see cref="PKM"/>.
/// </summary>
/// <remarks>
/// Requires checking the <see cref="LegalityAnalysis"/> for every <see cref="Ball"/> that is tried.
/// </remarks>
/// <param name="pkm">Pokémon to retrieve a list of valid balls for.</param>
/// <returns>Enumerable list of <see cref="Ball"/> values that the <see cref="PKM"/> is legal with.</returns>
public static IEnumerable<Ball> GetLegalBalls(PKM pkm)
var clone = pk.Clone();
foreach (var b in BallList)
{
var clone = pkm.Clone();
foreach (var b in BallList)
{
clone.Ball = (int)b;
if (new LegalityAnalysis(clone).Valid)
yield return b;
}
}
/// <summary>
/// Applies a random legal ball value if any exist.
/// </summary>
/// <remarks>
/// Requires checking the <see cref="LegalityAnalysis"/> for every <see cref="Ball"/> that is tried.
/// </remarks>
/// <param name="pkm">Pokémon to modify.</param>
public static int ApplyBallLegalRandom(PKM pkm)
{
var balls = GetBallListFromColor(pkm).ToArray();
Util.Shuffle(balls.AsSpan());
return ApplyFirstLegalBall(pkm, balls);
}
/// <summary>
/// Applies a legal ball value if any exist, ordered by color.
/// </summary>
/// <remarks>
/// Requires checking the <see cref="LegalityAnalysis"/> for every <see cref="Ball"/> that is tried.
/// </remarks>
/// <param name="pkm">Pokémon to modify.</param>
public static int ApplyBallLegalByColor(PKM pkm)
{
var balls = GetBallListFromColor(pkm);
return ApplyFirstLegalBall(pkm, balls);
}
/// <summary>
/// Applies a random ball value in a cyclical manner.
/// </summary>
/// <param name="pkm">Pokémon to modify.</param>
public static int ApplyBallNext(PKM pkm)
{
var balls = GetBallList(pkm.Ball);
var next = balls.First();
return pkm.Ball = (int)next;
}
private static int ApplyFirstLegalBall(PKM pkm, IEnumerable<Ball> balls)
{
foreach (var b in balls)
{
pkm.Ball = (int)b;
if (new LegalityAnalysis(pkm).Valid)
break;
}
return pkm.Ball;
}
private static IEnumerable<Ball> GetBallList(int ball)
{
var balls = BallList;
var currentBall = (Ball)ball;
return GetCircularOnce(balls, currentBall);
}
private static IEnumerable<Ball> GetBallListFromColor(PKM pkm)
{
// Gen1/2 don't store color in personal info
var pi = pkm.Format >= 3 ? pkm.PersonalInfo : PersonalTable.USUM.GetFormEntry(pkm.Species, 0);
var color = (PersonalColor)pi.Color;
var balls = BallColors[color];
var currentBall = (Ball)pkm.Ball;
return GetCircularOnce(balls, currentBall);
}
private static IEnumerable<T> GetCircularOnce<T>(T[] items, T current)
{
var currentIndex = Array.IndexOf(items, current);
if (currentIndex < 0)
currentIndex = items.Length - 2;
for (int i = currentIndex + 1; i < items.Length; i++)
yield return items[i];
for (int i = 0; i <= currentIndex; i++)
yield return items[i];
}
private static readonly Ball[] BallList = (Ball[]) Enum.GetValues(typeof(Ball));
static BallApplicator()
{
var exclude = new[] {None, Poke};
var end = new[] {Poke};
var allBalls = BallList.Except(exclude).ToArray();
var colors = (PersonalColor[])Enum.GetValues(typeof(PersonalColor));
foreach (var c in colors)
{
var matchingColors = BallColors[c];
var extra = allBalls.Except(matchingColors).ToArray();
Util.Shuffle(extra.AsSpan());
BallColors[c] = ArrayUtil.ConcatAll(matchingColors, extra, end);
}
}
/// <summary>
/// Priority Match ball IDs that match the color ID in descending order
/// </summary>
private static readonly Dictionary<PersonalColor, Ball[]> BallColors = new()
{
[PersonalColor.Red] = new[] { Cherish, Repeat, Fast, Heal, Great, Dream, Lure },
[PersonalColor.Blue] = new[] { Dive, Net, Great, Beast, Lure },
[PersonalColor.Yellow] = new[] { Level, Ultra, Repeat, Quick, Moon },
[PersonalColor.Green] = new[] { Safari, Friend, Nest, Dusk },
[PersonalColor.Black] = new[] { Luxury, Heavy, Ultra, Moon, Net, Beast },
[PersonalColor.Brown] = new[] { Level, Heavy },
[PersonalColor.Purple] = new[] { Master, Love, Dream, Heal },
[PersonalColor.Gray] = new[] { Heavy, Premier, Luxury },
[PersonalColor.White] = new[] { Premier, Timer, Luxury, Ultra },
[PersonalColor.Pink] = new[] { Love, Dream, Heal },
};
/// <summary>
/// Personal Data color IDs
/// </summary>
private enum PersonalColor : byte
{
Red,
Blue,
Yellow,
Green,
Black,
Brown,
Purple,
Gray,
White,
Pink,
clone.Ball = (int)b;
if (new LegalityAnalysis(clone).Valid)
yield return b;
}
}
/// <summary>
/// Applies a random legal ball value if any exist.
/// </summary>
/// <remarks>
/// Requires checking the <see cref="LegalityAnalysis"/> for every <see cref="Ball"/> that is tried.
/// </remarks>
/// <param name="pk">Pokémon to modify.</param>
public static int ApplyBallLegalRandom(PKM pk)
{
var balls = GetBallListFromColor(pk).ToArray();
Util.Shuffle(balls.AsSpan());
return ApplyFirstLegalBall(pk, balls);
}
/// <summary>
/// Applies a legal ball value if any exist, ordered by color.
/// </summary>
/// <remarks>
/// Requires checking the <see cref="LegalityAnalysis"/> for every <see cref="Ball"/> that is tried.
/// </remarks>
/// <param name="pk">Pokémon to modify.</param>
public static int ApplyBallLegalByColor(PKM pk)
{
var balls = GetBallListFromColor(pk);
return ApplyFirstLegalBall(pk, balls);
}
/// <summary>
/// Applies a random ball value in a cyclical manner.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static int ApplyBallNext(PKM pk)
{
var balls = GetBallList(pk.Ball);
var next = balls.First();
return pk.Ball = (int)next;
}
private static int ApplyFirstLegalBall(PKM pk, IEnumerable<Ball> balls)
{
foreach (var b in balls)
{
pk.Ball = (int)b;
if (new LegalityAnalysis(pk).Valid)
break;
}
return pk.Ball;
}
private static IEnumerable<Ball> GetBallList(int ball)
{
var balls = BallList;
var currentBall = (Ball)ball;
return GetCircularOnce(balls, currentBall);
}
private static IEnumerable<Ball> GetBallListFromColor(PKM pk)
{
// Gen1/2 don't store color in personal info
var pi = pk.Format >= 3 ? pk.PersonalInfo : PersonalTable.USUM.GetFormEntry(pk.Species, 0);
var color = (PersonalColor)pi.Color;
var balls = BallColors[color];
var currentBall = (Ball)pk.Ball;
return GetCircularOnce(balls, currentBall);
}
private static IEnumerable<T> GetCircularOnce<T>(T[] items, T current)
{
var currentIndex = Array.IndexOf(items, current);
if (currentIndex < 0)
currentIndex = items.Length - 2;
for (int i = currentIndex + 1; i < items.Length; i++)
yield return items[i];
for (int i = 0; i <= currentIndex; i++)
yield return items[i];
}
private static readonly Ball[] BallList = (Ball[]) Enum.GetValues(typeof(Ball));
static BallApplicator()
{
var exclude = new[] {None, Poke};
var end = new[] {Poke};
var allBalls = BallList.Except(exclude).ToArray();
var colors = (PersonalColor[])Enum.GetValues(typeof(PersonalColor));
foreach (var c in colors)
{
var matchingColors = BallColors[c];
var extra = allBalls.Except(matchingColors).ToArray();
Util.Shuffle(extra.AsSpan());
BallColors[c] = ArrayUtil.ConcatAll(matchingColors, extra, end);
}
}
/// <summary>
/// Priority Match ball IDs that match the color ID in descending order
/// </summary>
private static readonly Dictionary<PersonalColor, Ball[]> BallColors = new()
{
[PersonalColor.Red] = new[] { Cherish, Repeat, Fast, Heal, Great, Dream, Lure },
[PersonalColor.Blue] = new[] { Dive, Net, Great, Beast, Lure },
[PersonalColor.Yellow] = new[] { Level, Ultra, Repeat, Quick, Moon },
[PersonalColor.Green] = new[] { Safari, Friend, Nest, Dusk },
[PersonalColor.Black] = new[] { Luxury, Heavy, Ultra, Moon, Net, Beast },
[PersonalColor.Brown] = new[] { Level, Heavy },
[PersonalColor.Purple] = new[] { Master, Love, Dream, Heal },
[PersonalColor.Gray] = new[] { Heavy, Premier, Luxury },
[PersonalColor.White] = new[] { Premier, Timer, Luxury, Ultra },
[PersonalColor.Pink] = new[] { Love, Dream, Heal },
};
/// <summary>
/// Personal Data color IDs
/// </summary>
private enum PersonalColor : byte
{
Red,
Blue,
Yellow,
Green,
Black,
Brown,
Purple,
Gray,
White,
Pink,
}
}

View file

@ -1,39 +1,38 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Logic for applying a <see cref="PK1.Catch_Rate"/> value.
/// </summary>
public static class CatchRateApplicator
{
/// <summary>
/// Logic for applying a <see cref="PK1.Catch_Rate"/> value.
/// </summary>
public static class CatchRateApplicator
public static int GetSuggestedCatchRate(PK1 pk1, SaveFile sav)
{
public static int GetSuggestedCatchRate(PK1 pk1, SaveFile sav)
var la = new LegalityAnalysis(pk1);
return GetSuggestedCatchRate(pk1, sav, la);
}
public static int GetSuggestedCatchRate(PK1 pk1, SaveFile sav, LegalityAnalysis la)
{
if (la.Valid)
return pk1.Catch_Rate;
if (la.Info.Generation == 2)
return 0;
var v = la.EncounterMatch;
switch (v)
{
var la = new LegalityAnalysis(pk1);
return GetSuggestedCatchRate(pk1, sav, la);
}
public static int GetSuggestedCatchRate(PK1 pk1, SaveFile sav, LegalityAnalysis la)
{
if (la.Valid)
return pk1.Catch_Rate;
if (la.Info.Generation == 2)
return 0;
var v = la.EncounterMatch;
switch (v)
case EncounterTrade1 c:
return c.GetInitialCatchRate();
case EncounterStatic1E { Version: GameVersion.Stadium, Species: (int)Species.Psyduck}:
return pk1.Japanese ? 167 : 168; // Amnesia Psyduck has different catch rates depending on language
default:
{
case EncounterTrade1 c:
return c.GetInitialCatchRate();
case EncounterStatic1E { Version: GameVersion.Stadium, Species: (int)Species.Psyduck}:
return pk1.Japanese ? 167 : 168; // Amnesia Psyduck has different catch rates depending on language
default:
{
if (sav.Version.Contains(v.Version) || v.Version.Contains(sav.Version))
return sav.Personal[v.Species].CatchRate;
if (!GameVersion.RB.Contains(v.Version))
return PersonalTable.Y[v.Species].CatchRate;
return PersonalTable.RB[v.Species].CatchRate;
}
if (sav.Version.Contains(v.Version) || v.Version.Contains(sav.Version))
return sav.Personal[v.Species].CatchRate;
if (!GameVersion.RB.Contains(v.Version))
return PersonalTable.Y[v.Species].CatchRate;
return PersonalTable.RB[v.Species].CatchRate;
}
}
}

View file

@ -1,76 +1,75 @@
using System;
namespace PKHeX.Core
namespace PKHeX.Core;
public static class GenderApplicator
{
public static class GenderApplicator
/// <summary>
/// Sets the <see cref="PKM.Gender"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Gender"/> value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="gender">Desired <see cref="PKM.Gender"/> value to set.</param>
/// <remarks>Has special logic for an unspecified gender.</remarks>
public static void SetSaneGender(this PKM pk, int gender)
{
/// <summary>
/// Sets the <see cref="PKM.Gender"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Gender"/> value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="gender">Desired <see cref="PKM.Gender"/> value to set.</param>
/// <remarks>Has special logic for an unspecified gender.</remarks>
public static void SetSaneGender(this PKM pk, int gender)
int g = gender == -1 ? pk.GetSaneGender() : gender;
pk.SetGender(g);
}
/// <summary>
/// Sets the <see cref="PKM.Gender"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Gender"/> value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="gender">Desired <see cref="PKM.Gender"/> value to set.</param>
public static void SetGender(this PKM pk, int gender)
{
gender = Math.Min(2, Math.Max(0, gender));
if (pk.Gender == gender)
return;
if (pk.Format <= 2)
{
int g = gender == -1 ? pk.GetSaneGender() : gender;
pk.SetGender(g);
pk.SetAttackIVFromGender(gender);
}
/// <summary>
/// Sets the <see cref="PKM.Gender"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Gender"/> value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="gender">Desired <see cref="PKM.Gender"/> value to set.</param>
public static void SetGender(this PKM pk, int gender)
else if (pk.Format <= 5)
{
gender = Math.Min(2, Math.Max(0, gender));
if (pk.Gender == gender)
return;
if (pk.Format <= 2)
{
pk.SetAttackIVFromGender(gender);
}
else if (pk.Format <= 5)
{
pk.SetPIDGender(gender);
pk.Gender = gender;
}
else
{
pk.Gender = gender;
}
pk.SetPIDGender(gender);
pk.Gender = gender;
}
/// <summary>
/// Sanity checks the provided <see cref="PKM.Gender"/> value, and returns a sane value.
/// </summary>
/// <param name="pk"></param>
/// <returns>Most-legal <see cref="PKM.Gender"/> value</returns>
public static int GetSaneGender(this PKM pk)
else
{
int gt = pk.PersonalInfo.Gender;
switch (gt)
{
case PersonalInfo.RatioMagicGenderless: return 2;
case PersonalInfo.RatioMagicFemale: return 1;
case PersonalInfo.RatioMagicMale: return 0;
}
if (!pk.IsGenderValid())
return EntityGender.GetFromPIDAndRatio(pk.PID, gt);
return pk.Gender;
}
/// <summary>
/// Updates the <see cref="PKM.IV_ATK"/> for a Generation 1/2 format <see cref="PKM"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="gender">Desired <see cref="PKM.Gender"/>.</param>
public static void SetAttackIVFromGender(this PKM pk, int gender)
{
var rnd = Util.Rand;
while (pk.Gender != gender)
pk.IV_ATK = rnd.Next(16);
pk.Gender = gender;
}
}
/// <summary>
/// Sanity checks the provided <see cref="PKM.Gender"/> value, and returns a sane value.
/// </summary>
/// <param name="pk"></param>
/// <returns>Most-legal <see cref="PKM.Gender"/> value</returns>
public static int GetSaneGender(this PKM pk)
{
int gt = pk.PersonalInfo.Gender;
switch (gt)
{
case PersonalInfo.RatioMagicGenderless: return 2;
case PersonalInfo.RatioMagicFemale: return 1;
case PersonalInfo.RatioMagicMale: return 0;
}
if (!pk.IsGenderValid())
return EntityGender.GetFromPIDAndRatio(pk.PID, gt);
return pk.Gender;
}
/// <summary>
/// Updates the <see cref="PKM.IV_ATK"/> for a Generation 1/2 format <see cref="PKM"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="gender">Desired <see cref="PKM.Gender"/>.</param>
public static void SetAttackIVFromGender(this PKM pk, int gender)
{
var rnd = Util.Rand;
while (pk.Gender != gender)
pk.IV_ATK = rnd.Next(16);
}
}

View file

@ -1,27 +1,26 @@
using System;
namespace PKHeX.Core
{
public static class HiddenPowerApplicator
{
/// <summary>
/// Sets the <see cref="PKM.IVs"/> to match a provided <see cref="hiddenPowerType"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="hiddenPowerType">Desired Hidden Power typing.</param>
public static void SetHiddenPower(this PKM pk, int hiddenPowerType)
{
Span<int> IVs = stackalloc int[6];
pk.GetIVs(IVs);
HiddenPower.SetIVsForType(hiddenPowerType, IVs, pk.Format);
pk.SetIVs(IVs);
}
namespace PKHeX.Core;
/// <summary>
/// Sets the <see cref="PKM.IVs"/> to match a provided <see cref="hiddenPowerType"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="hiddenPowerType">Desired Hidden Power typing.</param>
public static void SetHiddenPower(this PKM pk, MoveType hiddenPowerType) => pk.SetHiddenPower((int)hiddenPowerType);
public static class HiddenPowerApplicator
{
/// <summary>
/// Sets the <see cref="PKM.IVs"/> to match a provided <see cref="hiddenPowerType"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="hiddenPowerType">Desired Hidden Power typing.</param>
public static void SetHiddenPower(this PKM pk, int hiddenPowerType)
{
Span<int> IVs = stackalloc int[6];
pk.GetIVs(IVs);
HiddenPower.SetIVsForType(hiddenPowerType, IVs, pk.Format);
pk.SetIVs(IVs);
}
/// <summary>
/// Sets the <see cref="PKM.IVs"/> to match a provided <see cref="hiddenPowerType"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="hiddenPowerType">Desired Hidden Power typing.</param>
public static void SetHiddenPower(this PKM pk, MoveType hiddenPowerType) => pk.SetHiddenPower((int)hiddenPowerType);
}

View file

@ -1,69 +1,68 @@
using System;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Logic for modifying the <see cref="PKM.MarkValue"/>.
/// </summary>
public static class MarkingApplicator
{
/// <summary>
/// Logic for modifying the <see cref="PKM.MarkValue"/>.
/// Default <see cref="MarkingMethod"/> when applying markings.
/// </summary>
public static class MarkingApplicator
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public static Func<PKM, Func<int, int, int>> MarkingMethod { get; set; } = FlagHighLow;
/// <summary>
/// Sets the <see cref="PKM.MarkValue"/> to indicate flawless (or near-flawless) <see cref="PKM.IVs"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetMarkings(this PKM pk)
{
/// <summary>
/// Default <see cref="MarkingMethod"/> when applying markings.
/// </summary>
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public static Func<PKM, Func<int, int, int>> MarkingMethod { get; set; } = FlagHighLow;
if (pk.MarkingCount < 6)
return; // insufficient marking indexes
/// <summary>
/// Sets the <see cref="PKM.MarkValue"/> to indicate flawless (or near-flawless) <see cref="PKM.IVs"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetMarkings(this PKM pk)
var method = MarkingMethod(pk);
pk.SetMarking(0, method(pk.IV_HP , 0));
pk.SetMarking(1, method(pk.IV_ATK, 1));
pk.SetMarking(2, method(pk.IV_DEF, 2));
pk.SetMarking(3, method(pk.IV_SPA, 3));
pk.SetMarking(4, method(pk.IV_SPD, 4));
pk.SetMarking(5, method(pk.IV_SPE, 5));
}
/// <summary>
/// Toggles the marking at a given index.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Marking index to toggle</param>
/// <returns>Current marking value</returns>
public static int ToggleMarking(this PKM pk, int index)
{
var marking = pk.GetMarking(index);
var revised = NextMarking(pk.Format, marking);
pk.SetMarking(index, revised);
return revised;
}
private static int NextMarking(int format, int marking) => format switch
{
<= 6 => marking ^ 1, // toggle : 0 (off) | 1 (on)
_ => (marking + 1) % 3, // cycle 0->1->2->0... : 0 (none) | 1 (blue) | 2 (pink)
};
private static Func<int, int, int> FlagHighLow(PKM pk)
{
if (pk.Format < 7)
return GetSimpleMarking;
return GetComplexMarking;
static int GetSimpleMarking(int val, int _) => val == 31 ? 1 : 0;
static int GetComplexMarking(int val, int _) => val switch
{
if (pk.MarkingCount < 6)
return; // insufficient marking indexes
var method = MarkingMethod(pk);
pk.SetMarking(0, method(pk.IV_HP , 0));
pk.SetMarking(1, method(pk.IV_ATK, 1));
pk.SetMarking(2, method(pk.IV_DEF, 2));
pk.SetMarking(3, method(pk.IV_SPA, 3));
pk.SetMarking(4, method(pk.IV_SPD, 4));
pk.SetMarking(5, method(pk.IV_SPE, 5));
}
/// <summary>
/// Toggles the marking at a given index.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Marking index to toggle</param>
/// <returns>Current marking value</returns>
public static int ToggleMarking(this PKM pk, int index)
{
var marking = pk.GetMarking(index);
var revised = NextMarking(pk.Format, marking);
pk.SetMarking(index, revised);
return revised;
}
private static int NextMarking(int format, int marking) => format switch
{
<= 6 => marking ^ 1, // toggle : 0 (off) | 1 (on)
_ => (marking + 1) % 3, // cycle 0->1->2->0... : 0 (none) | 1 (blue) | 2 (pink)
31 or 1 => 1,
30 or 0 => 2,
_ => 0,
};
private static Func<int, int, int> FlagHighLow(PKM pk)
{
if (pk.Format < 7)
return GetSimpleMarking;
return GetComplexMarking;
static int GetSimpleMarking(int val, int _) => val == 31 ? 1 : 0;
static int GetComplexMarking(int val, int _) => val switch
{
31 or 1 => 1,
30 or 0 => 2,
_ => 0,
};
}
}
}

View file

@ -1,51 +1,50 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Logic for modifying the Memory parameters of a <see cref="PKM"/>.
/// </summary>
public static class MemoryApplicator
{
/// <summary>
/// Logic for modifying the Memory parameters of a <see cref="PKM"/>.
/// Sets all Memory related data to the default value (zero).
/// </summary>
public static class MemoryApplicator
/// <param name="pk">Pokémon to modify.</param>
public static void ClearMemories(this PKM pk)
{
/// <summary>
/// Sets all Memory related data to the default value (zero).
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void ClearMemories(this PKM pk)
{
if (pk is IAffection a)
a.OT_Affection = a.HT_Affection = 0;
if (pk is IMemoryOT o)
o.ClearMemoriesOT();
if (pk is IMemoryHT h)
h.ClearMemoriesHT();
}
if (pk is IAffection a)
a.OT_Affection = a.HT_Affection = 0;
if (pk is IMemoryOT o)
o.ClearMemoriesOT();
if (pk is IMemoryHT h)
h.ClearMemoriesHT();
}
/// <summary>
/// Sets the Memory details to a Hatched Egg's memories.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetHatchMemory6(this PKM pk)
/// <summary>
/// Sets the Memory details to a Hatched Egg's memories.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetHatchMemory6(this PKM pk)
{
if (pk is IMemoryOT o)
{
if (pk is IMemoryOT o)
{
o.OT_Memory = 2;
o.OT_Feeling = MemoryContext6.GetRandomFeeling6(2);
o.OT_Intensity = 1;
o.OT_TextVar = pk.XY ? (ushort)43 : (ushort)27; // riverside road : battling spot
}
if (pk is IAffection a)
a.OT_Affection = 0;
o.OT_Memory = 2;
o.OT_Feeling = MemoryContext6.GetRandomFeeling6(2);
o.OT_Intensity = 1;
o.OT_TextVar = pk.XY ? (ushort)43 : (ushort)27; // riverside road : battling spot
}
if (pk is IAffection a)
a.OT_Affection = 0;
}
/// <summary>
/// Sets a random memory specific to <see cref="GameVersion.Gen6"/> locality.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetRandomMemory6(this PK6 pk)
{
// for lack of better randomization :)
pk.OT_Memory = 63;
pk.OT_Intensity = 6;
pk.OT_Feeling = MemoryContext6.GetRandomFeeling6(pk.OT_Memory);
}
/// <summary>
/// Sets a random memory specific to <see cref="GameVersion.Gen6"/> locality.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetRandomMemory6(this PK6 pk)
{
// for lack of better randomization :)
pk.OT_Memory = 63;
pk.OT_Intensity = 6;
pk.OT_Feeling = MemoryContext6.GetRandomFeeling6(pk.OT_Memory);
}
}

View file

@ -1,79 +1,78 @@
using System;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Logic for applying a moveset to a <see cref="PKM"/>.
/// </summary>
public static class MoveApplicator
{
/// <summary>
/// Logic for applying a moveset to a <see cref="PKM"/>.
/// Sets the individual PP Up count values depending if a Move is present in the move's slot or not.
/// </summary>
public static class MoveApplicator
/// <param name="pk">Pokémon to modify.</param>
/// <param name="moves"><see cref="PKM.Moves"/> to use (if already known). Will fetch the current <see cref="PKM.Moves"/> if not provided.</param>
public static void SetMaximumPPUps(this PKM pk, int[] moves)
{
/// <summary>
/// Sets the individual PP Up count values depending if a Move is present in the move's slot or not.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="moves"><see cref="PKM.Moves"/> to use (if already known). Will fetch the current <see cref="PKM.Moves"/> if not provided.</param>
public static void SetMaximumPPUps(this PKM pk, int[] moves)
{
pk.Move1_PPUps = GetPPUpCount(moves[0]);
pk.Move2_PPUps = GetPPUpCount(moves[1]);
pk.Move3_PPUps = GetPPUpCount(moves[2]);
pk.Move4_PPUps = GetPPUpCount(moves[3]);
pk.Move1_PPUps = GetPPUpCount(moves[0]);
pk.Move2_PPUps = GetPPUpCount(moves[1]);
pk.Move3_PPUps = GetPPUpCount(moves[2]);
pk.Move4_PPUps = GetPPUpCount(moves[3]);
pk.SetMaximumPPCurrent(moves);
static int GetPPUpCount(int moveID) => moveID > 0 ? 3 : 0;
}
/// <summary>
/// Sets the individual PP Up count values depending if a Move is present in the move slot or not.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetMaximumPPUps(this PKM pk) => pk.SetMaximumPPUps(pk.Moves);
/// <summary>
/// Updates the <see cref="PKM.Moves"/> and updates the current PP counts.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="moves"><see cref="PKM.Moves"/> to set. Will be resized if 4 entries are not present.</param>
/// <param name="maxPP">Option to maximize PP Ups</param>
public static void SetMoves(this PKM pk, int[] moves, bool maxPP = false)
{
if (Array.FindIndex(moves, z => z > pk.MaxMoveID) != -1)
moves = Array.FindAll(moves, z => z <= pk.MaxMoveID);
if (moves.Length != 4)
Array.Resize(ref moves, 4);
pk.Moves = moves;
if (maxPP && Legal.IsPPUpAvailable(pk))
pk.SetMaximumPPUps(moves);
else
pk.SetMaximumPPCurrent(moves);
pk.FixMoves();
}
/// <summary>
/// Updates the individual PP count values for each move slot based on the maximum possible value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="moves"><see cref="PKM.Moves"/> to use (if already known). Will fetch the current <see cref="PKM.Moves"/> if not provided.</param>
public static void SetMaximumPPCurrent(this PKM pk, ReadOnlySpan<int> moves)
{
pk.Move1_PP = moves.Length == 0 ? 0 : pk.GetMovePP(moves[0], pk.Move1_PPUps);
pk.Move2_PP = moves.Length <= 1 ? 0 : pk.GetMovePP(moves[1], pk.Move2_PPUps);
pk.Move3_PP = moves.Length <= 2 ? 0 : pk.GetMovePP(moves[2], pk.Move3_PPUps);
pk.Move4_PP = moves.Length <= 3 ? 0 : pk.GetMovePP(moves[3], pk.Move4_PPUps);
}
/// <summary>
/// Updates the individual PP count values for each move slot based on the maximum possible value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetMaximumPPCurrent(this PKM pk) => pk.SetMaximumPPCurrent(pk.Moves);
/// <summary>
/// Refreshes the Move PP for the desired move.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Move PP to refresh.</param>
public static void SetSuggestedMovePP(this PKM pk, int index) => pk.HealPPIndex(index);
pk.SetMaximumPPCurrent(moves);
static int GetPPUpCount(int moveID) => moveID > 0 ? 3 : 0;
}
/// <summary>
/// Sets the individual PP Up count values depending if a Move is present in the move slot or not.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetMaximumPPUps(this PKM pk) => pk.SetMaximumPPUps(pk.Moves);
/// <summary>
/// Updates the <see cref="PKM.Moves"/> and updates the current PP counts.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="moves"><see cref="PKM.Moves"/> to set. Will be resized if 4 entries are not present.</param>
/// <param name="maxPP">Option to maximize PP Ups</param>
public static void SetMoves(this PKM pk, int[] moves, bool maxPP = false)
{
if (Array.FindIndex(moves, z => z > pk.MaxMoveID) != -1)
moves = Array.FindAll(moves, z => z <= pk.MaxMoveID);
if (moves.Length != 4)
Array.Resize(ref moves, 4);
pk.Moves = moves;
if (maxPP && Legal.IsPPUpAvailable(pk))
pk.SetMaximumPPUps(moves);
else
pk.SetMaximumPPCurrent(moves);
pk.FixMoves();
}
/// <summary>
/// Updates the individual PP count values for each move slot based on the maximum possible value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="moves"><see cref="PKM.Moves"/> to use (if already known). Will fetch the current <see cref="PKM.Moves"/> if not provided.</param>
public static void SetMaximumPPCurrent(this PKM pk, ReadOnlySpan<int> moves)
{
pk.Move1_PP = moves.Length == 0 ? 0 : pk.GetMovePP(moves[0], pk.Move1_PPUps);
pk.Move2_PP = moves.Length <= 1 ? 0 : pk.GetMovePP(moves[1], pk.Move2_PPUps);
pk.Move3_PP = moves.Length <= 2 ? 0 : pk.GetMovePP(moves[2], pk.Move3_PPUps);
pk.Move4_PP = moves.Length <= 3 ? 0 : pk.GetMovePP(moves[3], pk.Move4_PPUps);
}
/// <summary>
/// Updates the individual PP count values for each move slot based on the maximum possible value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetMaximumPPCurrent(this PKM pk) => pk.SetMaximumPPCurrent(pk.Moves);
/// <summary>
/// Refreshes the Move PP for the desired move.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Move PP to refresh.</param>
public static void SetSuggestedMovePP(this PKM pk, int index) => pk.HealPPIndex(index);
}

View file

@ -2,120 +2,119 @@
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Logic for getting valid movesets.
/// </summary>
public static class MoveSetApplicator
{
/// <summary>
/// Logic for getting valid movesets.
/// Gets a moveset for the provided <see cref="PKM"/> data.
/// </summary>
public static class MoveSetApplicator
/// <param name="pk">PKM to generate for</param>
/// <param name="random">Full movepool &amp; shuffling</param>
/// <returns>4 moves</returns>
public static int[] GetMoveSet(this PKM pk, bool random = false)
{
/// <summary>
/// Gets a moveset for the provided <see cref="PKM"/> data.
/// </summary>
/// <param name="pk">PKM to generate for</param>
/// <param name="random">Full movepool &amp; shuffling</param>
/// <returns>4 moves</returns>
public static int[] GetMoveSet(this PKM pk, bool random = false)
{
var la = new LegalityAnalysis(pk);
var moves = la.GetMoveSet(random);
var la = new LegalityAnalysis(pk);
var moves = la.GetMoveSet(random);
if (random)
return moves;
if (random)
return moves;
var clone = pk.Clone();
clone.SetMoves(moves);
clone.SetMaximumPPCurrent(moves);
var newLa = new LegalityAnalysis(clone);
var clone = pk.Clone();
clone.SetMoves(moves);
clone.SetMaximumPPCurrent(moves);
var newLa = new LegalityAnalysis(clone);
// ReSharper disable once TailRecursiveCall
return newLa.Valid ? moves : GetMoveSet(pk, true);
}
// ReSharper disable once TailRecursiveCall
return newLa.Valid ? moves : GetMoveSet(pk, true);
}
/// <summary>
/// Gets a moveset for the provided <see cref="PKM"/> data.
/// </summary>
/// <param name="la">Precomputed optional</param>
/// <param name="random">Full movepool &amp; shuffling</param>
/// <returns>4 moves</returns>
public static int[] GetMoveSet(this LegalityAnalysis la, bool random = false)
{
int[] m = la.GetSuggestedCurrentMoves(random ? MoveSourceType.All : MoveSourceType.Encounter);
/// <summary>
/// Gets a moveset for the provided <see cref="PKM"/> data.
/// </summary>
/// <param name="la">Precomputed optional</param>
/// <param name="random">Full movepool &amp; shuffling</param>
/// <returns>4 moves</returns>
public static int[] GetMoveSet(this LegalityAnalysis la, bool random = false)
{
int[] m = la.GetSuggestedCurrentMoves(random ? MoveSourceType.All : MoveSourceType.Encounter);
var learn = la.GetSuggestedMovesAndRelearn();
if (!m.All(z => learn.Contains(z)))
m = m.Intersect(learn).ToArray();
var learn = la.GetSuggestedMovesAndRelearn();
if (!m.All(z => learn.Contains(z)))
m = m.Intersect(learn).ToArray();
if (random && !la.pkm.IsEgg)
Util.Shuffle(m.AsSpan());
if (random && !la.Entity.IsEgg)
Util.Shuffle(m.AsSpan());
const int count = 4;
if (m.Length > count)
return m.SliceEnd(m.Length - count);
Array.Resize(ref m, count);
const int count = 4;
if (m.Length > count)
return m.SliceEnd(m.Length - count);
Array.Resize(ref m, count);
return m;
}
/// <summary>
/// Fetches <see cref="PKM.RelearnMoves"/> based on the provided <see cref="LegalityAnalysis"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="enc">Encounter the relearn moves should be suggested for. If not provided, will try to detect it via legality analysis. </param>
/// <returns><see cref="PKM.RelearnMoves"/> best suited for the current <see cref="PKM"/> data.</returns>
public static IReadOnlyList<int> GetSuggestedRelearnMoves(this PKM pk, IEncounterTemplate? enc = null) => GetSuggestedRelearnMoves(new LegalityAnalysis(pk), enc);
/// <summary>
/// Fetches <see cref="PKM.RelearnMoves"/> based on the provided <see cref="LegalityAnalysis"/>.
/// </summary>
/// <param name="legal"><see cref="LegalityAnalysis"/> which contains parsed information pertaining to legality.</param>
/// <param name="enc">Encounter the relearn moves should be suggested for. If not provided, will try to detect it via legality analysis. </param>
/// <returns><see cref="PKM.RelearnMoves"/> best suited for the current <see cref="PKM"/> data.</returns>
public static IReadOnlyList<int> GetSuggestedRelearnMoves(this LegalityAnalysis legal, IEncounterTemplate? enc = null)
{
enc ??= legal.EncounterOriginal;
var m = legal.GetSuggestedRelearnMovesFromEncounter(enc);
if (m.Any(z => z != 0))
return m;
if (enc is MysteryGift or EncounterEgg)
return m;
if (enc is EncounterSlot6AO {CanDexNav: true} dn)
{
var moves = legal.Info.Moves;
for (int i = 0; i < moves.Length; i++)
{
if (!moves[i].ShouldBeInRelearnMoves())
continue;
var move = legal.Entity.GetMove(i);
if (dn.CanBeDexNavMove(move))
return new[] { move, 0, 0, 0 };
}
}
/// <summary>
/// Fetches <see cref="PKM.RelearnMoves"/> based on the provided <see cref="LegalityAnalysis"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="enc">Encounter the relearn moves should be suggested for. If not provided, will try to detect it via legality analysis. </param>
/// <returns><see cref="PKM.RelearnMoves"/> best suited for the current <see cref="PKM"/> data.</returns>
public static IReadOnlyList<int> GetSuggestedRelearnMoves(this PKM pk, IEncounterTemplate? enc = null) => GetSuggestedRelearnMoves(new LegalityAnalysis(pk), enc);
/// <summary>
/// Fetches <see cref="PKM.RelearnMoves"/> based on the provided <see cref="LegalityAnalysis"/>.
/// </summary>
/// <param name="legal"><see cref="LegalityAnalysis"/> which contains parsed information pertaining to legality.</param>
/// <param name="enc">Encounter the relearn moves should be suggested for. If not provided, will try to detect it via legality analysis. </param>
/// <returns><see cref="PKM.RelearnMoves"/> best suited for the current <see cref="PKM"/> data.</returns>
public static IReadOnlyList<int> GetSuggestedRelearnMoves(this LegalityAnalysis legal, IEncounterTemplate? enc = null)
if (enc is EncounterSlot8b { IsUnderground: true } ug)
{
enc ??= legal.EncounterOriginal;
var m = legal.GetSuggestedRelearnMovesFromEncounter(enc);
if (m.Any(z => z != 0))
return m;
if (enc is MysteryGift or EncounterEgg)
return m;
if (enc is EncounterSlot6AO {CanDexNav: true} dn)
var moves = legal.Info.Moves;
for (int i = 0; i < moves.Length; i++)
{
var moves = legal.Info.Moves;
for (int i = 0; i < moves.Length; i++)
{
if (!moves[i].ShouldBeInRelearnMoves())
continue;
if (!moves[i].ShouldBeInRelearnMoves())
continue;
var move = legal.pkm.GetMove(i);
if (dn.CanBeDexNavMove(move))
return new[] { move, 0, 0, 0 };
}
var move = legal.Entity.GetMove(i);
if (ug.CanBeUndergroundMove(move))
return new[] { move, 0, 0, 0 };
}
if (enc is EncounterSlot8b { IsUnderground: true } ug)
{
var moves = legal.Info.Moves;
for (int i = 0; i < moves.Length; i++)
{
if (!moves[i].ShouldBeInRelearnMoves())
continue;
var move = legal.pkm.GetMove(i);
if (ug.CanBeUndergroundMove(move))
return new[] { move, 0, 0, 0 };
}
if (ug.GetBaseEggMove(out int any))
return new[] { any, 0, 0, 0 };
}
var encounter = EncounterSuggestion.GetSuggestedMetInfo(legal.pkm);
if (encounter is IRelearn {Relearn: {Count: > 0} r})
return r;
return m;
if (ug.GetBaseEggMove(out int any))
return new[] { any, 0, 0, 0 };
}
var encounter = EncounterSuggestion.GetSuggestedMetInfo(legal.Entity);
if (encounter is IRelearn {Relearn: {Count: > 0} r})
return r;
return m;
}
}

View file

@ -1,241 +1,241 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Logic for applying ribbons.
/// </summary>
public static class RibbonApplicator
{
private static List<string> GetAllRibbonNames(PKM pk) => RibbonInfo.GetRibbonInfo(pk).Select(z => z.Name).ToList();
/// <summary>
/// Logic for applying ribbons.
/// Gets a list of valid ribbons for the <see cref="pk"/>.
/// </summary>
public static class RibbonApplicator
/// <param name="pk">Entity to fetch the list for.</param>
/// <param name="allRibbons">All ribbon names.</param>
/// <returns>List of all valid ribbon names.</returns>
public static IReadOnlyList<string> GetValidRibbons(PKM pk, IList<string> allRibbons)
{
private static List<string> GetAllRibbonNames(PKM pkm) => RibbonInfo.GetRibbonInfo(pkm).Select(z => z.Name).ToList();
var clone = pk.Clone();
return SetAllValidRibbons(allRibbons, clone);
}
/// <summary>
/// Gets a list of valid ribbons for the <see cref="pkm"/>.
/// </summary>
/// <param name="pkm">Entity to fetch the list for.</param>
/// <param name="allRibbons">All ribbon names.</param>
/// <returns>List of all valid ribbon names.</returns>
public static IReadOnlyList<string> GetValidRibbons(PKM pkm, IList<string> allRibbons)
/// <summary>
/// Gets a list of valid ribbons for the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Entity to fetch the list for.</param>
/// <returns>List of all valid ribbon names.</returns>
public static IReadOnlyList<string> GetValidRibbons(PKM pk)
{
var names = GetAllRibbonNames(pk);
return GetValidRibbons(pk, names);
}
/// <summary>
/// Gets a list of valid ribbons for the <see cref="pk"/> that can be removed.
/// </summary>
/// <param name="pk">Entity to fetch the list for.</param>
/// <param name="allRibbons">All ribbon names.</param>
/// <returns>List of all removable ribbon names.</returns>
public static IReadOnlyList<string> GetRemovableRibbons(PKM pk, IList<string> allRibbons)
{
var clone = pk.Clone();
return RemoveAllValidRibbons(allRibbons, clone);
}
/// <summary>
/// Gets a list of valid ribbons for the <see cref="pk"/> that can be removed.
/// </summary>
/// <param name="pk">Entity to fetch the list for.</param>
/// <returns>List of all removable ribbon names.</returns>
public static IReadOnlyList<string> GetRemovableRibbons(PKM pk)
{
var names = GetAllRibbonNames(pk);
return GetRemovableRibbons(pk, names);
}
/// <summary>
/// Sets all valid ribbons to the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Entity to set ribbons for.</param>
/// <returns>True if any ribbons were applied.</returns>
public static bool SetAllValidRibbons(PKM pk)
{
var ribNames = GetAllRibbonNames(pk);
ribNames.RemoveAll(z => z.StartsWith("RibbonMark", StringComparison.Ordinal)); // until marking legality is handled
return SetAllValidRibbons(pk, ribNames);
}
/// <summary>
/// Sets all valid ribbons to the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Entity to set ribbons for.</param>
/// <param name="ribNames">Ribbon names to try setting.</param>
/// <returns>True if any ribbons were applied.</returns>
public static bool SetAllValidRibbons(PKM pk, List<string> ribNames)
{
var list = SetAllValidRibbons(ribNames, pk);
return list.Count != 0;
}
private static IReadOnlyList<string> SetAllValidRibbons(IList<string> allRibbons, PKM pk)
{
var la = new LegalityAnalysis(pk);
var valid = new List<string>();
while (TryApplyAllRibbons(pk, la, allRibbons, valid) != 0)
{
var pk = pkm.Clone();
return SetAllValidRibbons(allRibbons, pk);
// Repeat the operation until no more ribbons are set.
}
/// <summary>
/// Gets a list of valid ribbons for the <see cref="pkm"/>.
/// </summary>
/// <param name="pkm">Entity to fetch the list for.</param>
/// <returns>List of all valid ribbon names.</returns>
public static IReadOnlyList<string> GetValidRibbons(PKM pkm)
// Ribbon Deadlock
if (pk is IRibbonSetCommon6 c6)
InvertDeadlockContest(c6, la, true);
return valid;
}
/// <summary>
/// Sets all valid ribbons to the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Entity to set ribbons for.</param>
/// <returns>True if any ribbons were removed.</returns>
public static bool RemoveAllValidRibbons(PKM pk)
{
var ribNames = GetAllRibbonNames(pk);
return RemoveAllValidRibbons(pk, ribNames);
}
/// <summary>
/// Sets all valid ribbons to the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Entity to set ribbons for.</param>
/// <param name="ribNames">Ribbon names to try setting.</param>
/// <returns>True if any ribbons were removed.</returns>
public static bool RemoveAllValidRibbons(PKM pk, List<string> ribNames)
{
var list = RemoveAllValidRibbons(ribNames, pk);
return list.Count != 0;
}
private static IReadOnlyList<string> RemoveAllValidRibbons(IList<string> allRibbons, PKM pk)
{
var la = new LegalityAnalysis(pk);
var valid = new List<string>();
// Ribbon Deadlock
if (pk is IRibbonSetCommon6 c6)
InvertDeadlockContest(c6, la, false);
while (TryRemoveAllRibbons(pk, la, allRibbons, valid) != 0)
{
var names = GetAllRibbonNames(pkm);
return GetValidRibbons(pkm, names);
// Repeat the operation until no more ribbons are set.
}
/// <summary>
/// Gets a list of valid ribbons for the <see cref="pkm"/> that can be removed.
/// </summary>
/// <param name="pkm">Entity to fetch the list for.</param>
/// <param name="allRibbons">All ribbon names.</param>
/// <returns>List of all removable ribbon names.</returns>
public static IReadOnlyList<string> GetRemovableRibbons(PKM pkm, IList<string> allRibbons)
return valid;
}
private static int TryApplyAllRibbons(PKM pk, LegalityAnalysis la, IList<string> allRibbons, ICollection<string> valid)
{
int applied = 0;
for (int i = 0; i < allRibbons.Count;)
{
var pk = pkm.Clone();
return RemoveAllValidRibbons(allRibbons, pk);
}
/// <summary>
/// Gets a list of valid ribbons for the <see cref="pkm"/> that can be removed.
/// </summary>
/// <param name="pkm">Entity to fetch the list for.</param>
/// <returns>List of all removable ribbon names.</returns>
public static IReadOnlyList<string> GetRemovableRibbons(PKM pkm)
{
var names = GetAllRibbonNames(pkm);
return GetRemovableRibbons(pkm, names);
}
/// <summary>
/// Sets all valid ribbons to the <see cref="pkm"/>.
/// </summary>
/// <param name="pkm">Entity to set ribbons for.</param>
/// <returns>True if any ribbons were applied.</returns>
public static bool SetAllValidRibbons(PKM pkm)
{
var ribNames = GetAllRibbonNames(pkm);
ribNames.RemoveAll(z => z.StartsWith("RibbonMark")); // until marking legality is handled
return SetAllValidRibbons(pkm, ribNames);
}
/// <summary>
/// Sets all valid ribbons to the <see cref="pkm"/>.
/// </summary>
/// <param name="pkm">Entity to set ribbons for.</param>
/// <param name="ribNames">Ribbon names to try setting.</param>
/// <returns>True if any ribbons were applied.</returns>
public static bool SetAllValidRibbons(PKM pkm, List<string> ribNames)
{
var list = SetAllValidRibbons(ribNames, pkm);
return list.Count != 0;
}
private static IReadOnlyList<string> SetAllValidRibbons(IList<string> allRibbons, PKM pk)
{
var la = new LegalityAnalysis(pk);
var valid = new List<string>();
while (TryApplyAllRibbons(pk, la, allRibbons, valid) != 0)
{
// Repeat the operation until no more ribbons are set.
}
// Ribbon Deadlock
if (pk is IRibbonSetCommon6 c6)
InvertDeadlockContest(c6, la, true);
return valid;
}
/// <summary>
/// Sets all valid ribbons to the <see cref="pkm"/>.
/// </summary>
/// <param name="pkm">Entity to set ribbons for.</param>
/// <returns>True if any ribbons were removed.</returns>
public static bool RemoveAllValidRibbons(PKM pkm)
{
var ribNames = GetAllRibbonNames(pkm);
return RemoveAllValidRibbons(pkm, ribNames);
}
/// <summary>
/// Sets all valid ribbons to the <see cref="pkm"/>.
/// </summary>
/// <param name="pkm">Entity to set ribbons for.</param>
/// <param name="ribNames">Ribbon names to try setting.</param>
/// <returns>True if any ribbons were removed.</returns>
public static bool RemoveAllValidRibbons(PKM pkm, List<string> ribNames)
{
var list = RemoveAllValidRibbons(ribNames, pkm);
return list.Count != 0;
}
private static IReadOnlyList<string> RemoveAllValidRibbons(IList<string> allRibbons, PKM pk)
{
var la = new LegalityAnalysis(pk);
var valid = new List<string>();
// Ribbon Deadlock
if (pk is IRibbonSetCommon6 c6)
InvertDeadlockContest(c6, la, false);
while (TryRemoveAllRibbons(pk, la, allRibbons, valid) != 0)
{
// Repeat the operation until no more ribbons are set.
}
return valid;
}
private static int TryApplyAllRibbons(PKM pk, LegalityAnalysis la, IList<string> allRibbons, ICollection<string> valid)
{
int applied = 0;
for (int i = 0; i < allRibbons.Count;)
{
la.ResetParse();
var rib = allRibbons[i];
var success = TryApplyRibbon(pk, la, rib);
if (success)
{
++applied;
allRibbons.RemoveAt(i);
valid.Add(rib);
}
else
{
RemoveRibbon(pk, rib);
++i;
}
}
return applied;
}
private static int TryRemoveAllRibbons(PKM pk, LegalityAnalysis la, IList<string> allRibbons, ICollection<string> valid)
{
int removed = 0;
for (int i = 0; i < allRibbons.Count;)
{
la.ResetParse();
var rib = allRibbons[i];
var success = TryRemoveRibbon(pk, la, rib);
if (success)
{
++removed;
allRibbons.RemoveAt(i);
valid.Add(rib);
}
else
{
SetRibbonValue(pk, rib, 1);
++i;
}
}
return removed;
}
private static void RemoveRibbon(PKM pk, string rib) => SetRibbonValue(pk, rib, 0);
private static bool TryRemoveRibbon(PKM pk, LegalityAnalysis la, string rib)
{
RemoveRibbon(pk, rib);
return UpdateIsValid(la);
}
private static bool TryApplyRibbon(PKM pk, LegalityAnalysis la, string rib)
{
SetRibbonValue(pk, rib, 1);
return UpdateIsValid(la);
}
private static bool UpdateIsValid(LegalityAnalysis la)
{
LegalityAnalyzers.Ribbon.Verify(la);
return la.Results.All(z => z.Valid);
}
private static void SetRibbonValue(PKM pk, string rib, int value)
{
switch (rib)
{
case nameof(PK7.RibbonCountMemoryBattle):
ReflectUtil.SetValue(pk, rib, value * (pk.Gen4 ? 6 : 8));
break;
case nameof(PK7.RibbonCountMemoryContest):
ReflectUtil.SetValue(pk, rib, value * (pk.Gen4 ? 20 : 40));
break;
default:
if (rib.StartsWith("RibbonCountG3"))
ReflectUtil.SetValue(pk, rib, value * 4);
else
ReflectUtil.SetValue(pk, rib, value != 0);
break;
}
}
private static void InvertDeadlockContest(IRibbonSetCommon6 c6, LegalityAnalysis la, bool desiredState)
{
// RibbonContestStar depends on having all contest ribbons, and having RibbonContestStar requires all.
// Since the above logic sets individual ribbons, we must try setting this deadlock pair manually.
if (c6.RibbonMasterToughness == desiredState || c6.RibbonContestStar == desiredState)
return;
la.ResetParse();
c6.RibbonMasterToughness = c6.RibbonContestStar = desiredState;
bool result = UpdateIsValid(la);
if (!result)
c6.RibbonMasterToughness = c6.RibbonContestStar = !desiredState;
var rib = allRibbons[i];
var success = TryApplyRibbon(pk, la, rib);
if (success)
{
++applied;
allRibbons.RemoveAt(i);
valid.Add(rib);
}
else
{
RemoveRibbon(pk, rib);
++i;
}
}
return applied;
}
private static int TryRemoveAllRibbons(PKM pk, LegalityAnalysis la, IList<string> allRibbons, ICollection<string> valid)
{
int removed = 0;
for (int i = 0; i < allRibbons.Count;)
{
la.ResetParse();
var rib = allRibbons[i];
var success = TryRemoveRibbon(pk, la, rib);
if (success)
{
++removed;
allRibbons.RemoveAt(i);
valid.Add(rib);
}
else
{
SetRibbonValue(pk, rib, 1);
++i;
}
}
return removed;
}
private static void RemoveRibbon(PKM pk, string rib) => SetRibbonValue(pk, rib, 0);
private static bool TryRemoveRibbon(PKM pk, LegalityAnalysis la, string rib)
{
RemoveRibbon(pk, rib);
return UpdateIsValid(la);
}
private static bool TryApplyRibbon(PKM pk, LegalityAnalysis la, string rib)
{
SetRibbonValue(pk, rib, 1);
return UpdateIsValid(la);
}
private static bool UpdateIsValid(LegalityAnalysis la)
{
LegalityAnalyzers.Ribbon.Verify(la);
return la.Results.All(z => z.Valid);
}
private static void SetRibbonValue(PKM pk, string rib, int value)
{
switch (rib)
{
case nameof(PK7.RibbonCountMemoryBattle):
ReflectUtil.SetValue(pk, rib, value * (pk.Gen4 ? 6 : 8));
break;
case nameof(PK7.RibbonCountMemoryContest):
ReflectUtil.SetValue(pk, rib, value * (pk.Gen4 ? 20 : 40));
break;
default:
if (rib.StartsWith("RibbonCountG3", StringComparison.Ordinal))
ReflectUtil.SetValue(pk, rib, value * 4);
else
ReflectUtil.SetValue(pk, rib, value != 0);
break;
}
}
private static void InvertDeadlockContest(IRibbonSetCommon6 c6, LegalityAnalysis la, bool desiredState)
{
// RibbonContestStar depends on having all contest ribbons, and having RibbonContestStar requires all.
// Since the above logic sets individual ribbons, we must try setting this deadlock pair manually.
if (c6.RibbonMasterToughness == desiredState || c6.RibbonContestStar == desiredState)
return;
la.ResetParse();
c6.RibbonMasterToughness = c6.RibbonContestStar = desiredState;
bool result = UpdateIsValid(la);
if (!result)
c6.RibbonMasterToughness = c6.RibbonContestStar = !desiredState;
}
}

View file

@ -1,65 +1,64 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Logic for modifying the Technical Record flags of a <see cref="PK8"/>.
/// </summary>
public static class TechnicalRecordApplicator
{
/// <summary>
/// Logic for modifying the Technical Record flags of a <see cref="PK8"/>.
/// Sets the Technical Record flags for the <see cref="pk"/>.
/// </summary>
public static class TechnicalRecordApplicator
/// <param name="pk">Pokémon to modify.</param>
/// <param name="value">Value to set for the record.</param>
/// <param name="max">Max record to set.</param>
public static void SetRecordFlags(this ITechRecord8 pk, bool value, int max = 100)
{
/// <summary>
/// Sets the Technical Record flags for the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="value">Value to set for the record.</param>
/// <param name="max">Max record to set.</param>
public static void SetRecordFlags(this ITechRecord8 pk, bool value, int max = 100)
for (int i = 0; i < max; i++)
pk.SetMoveRecordFlag(i, value);
}
/// <summary>
/// Clears the Technical Record flags for the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void ClearRecordFlags(this ITechRecord8 pk) => pk.SetRecordFlags(false, 112);
/// <summary>
/// Sets the Technical Record flags for the <see cref="pk"/> based on the current moves.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="moves">Moves to set flags for. If a move is not a Technical Record, it is skipped.</param>
public static void SetRecordFlags(this ITechRecord8 pk, IEnumerable<int> moves)
{
var permit = pk.TechRecordPermitFlags;
var moveIDs = pk.TechRecordPermitIndexes;
if (permit.Length != moveIDs.Length)
return;
foreach (var m in moves)
{
for (int i = 0; i < max; i++)
pk.SetMoveRecordFlag(i, value);
var index = moveIDs.IndexOf(m);
if (index == -1)
continue;
if (permit[index])
pk.SetMoveRecordFlag(index, true);
}
}
/// <summary>
/// Clears the Technical Record flags for the <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void ClearRecordFlags(this ITechRecord8 pk) => pk.SetRecordFlags(false, 112);
/// <summary>
/// Sets the Technical Record flags for the <see cref="pk"/> based on the current moves.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="moves">Moves to set flags for. If a move is not a Technical Record, it is skipped.</param>
public static void SetRecordFlags(this ITechRecord8 pk, IEnumerable<int> moves)
/// <summary>
/// Sets all the Technical Record flags for the <see cref="pk"/> if they are permitted to be learned in-game.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetRecordFlags(this ITechRecord8 pk)
{
var permit = pk.TechRecordPermitFlags;
for (int i = 0; i < permit.Length; i++)
{
var permit = pk.TechRecordPermitFlags;
var moveIDs = pk.TechRecordPermitIndexes;
if (permit.Length != moveIDs.Length)
return;
foreach (var m in moves)
{
var index = moveIDs.IndexOf(m);
if (index == -1)
continue;
if (permit[index])
pk.SetMoveRecordFlag(index, true);
}
}
/// <summary>
/// Sets all the Technical Record flags for the <see cref="pk"/> if they are permitted to be learned in-game.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetRecordFlags(this ITechRecord8 pk)
{
var permit = pk.TechRecordPermitFlags;
for (int i = 0; i < permit.Length; i++)
{
if (permit[i])
pk.SetMoveRecordFlag(i, true);
}
if (permit[i])
pk.SetMoveRecordFlag(i, true);
}
}
}

View file

@ -1,504 +1,503 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using static PKHeX.Core.MessageStrings;
using static PKHeX.Core.BatchModifications;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Logic for editing many <see cref="PKM"/> with user provided <see cref="StringInstruction"/> list.
/// </summary>
public static class BatchEditing
{
/// <summary>
/// Logic for editing many <see cref="PKM"/> with user provided <see cref="StringInstruction"/> list.
/// </summary>
public static class BatchEditing
public static readonly Type[] Types =
{
public static readonly Type[] Types =
typeof (PK8), typeof (PA8), typeof (PB8),
typeof (PB7),
typeof (PK7), typeof (PK6), typeof (PK5), typeof (PK4), typeof(BK4),
typeof (PK3), typeof (XK3), typeof (CK3),
typeof (PK2), typeof (SK2), typeof (PK1),
};
/// <summary>
/// Extra properties to show in the list of selectable properties (GUI)
/// </summary>
public static readonly List<string> CustomProperties = new()
{
PROP_LEGAL, PROP_TYPENAME, PROP_RIBBONS, PROP_CONTESTSTATS, PROP_MOVEMASTERY,
IdentifierContains, nameof(ISlotInfo.Slot), nameof(SlotInfoBox.Box),
};
/// <summary>
/// Property names, indexed by <see cref="Types"/>.
/// </summary>
public static string[][] Properties => GetProperties.Value;
private static readonly Lazy<string[][]> GetProperties = new(() => GetPropArray(Types, CustomProperties));
private static readonly Dictionary<string, PropertyInfo>[] Props = GetPropertyDictionaries(Types);
private static Dictionary<string, PropertyInfo>[] GetPropertyDictionaries(IReadOnlyList<Type> types)
{
var result = new Dictionary<string, PropertyInfo>[types.Count];
for (int i = 0; i < types.Count; i++)
result[i] = GetPropertyDictionary(types[i], ReflectUtil.GetAllPropertyInfoPublic);
return result;
}
private static Dictionary<string, PropertyInfo> GetPropertyDictionary(Type type, Func<Type, IEnumerable<PropertyInfo>> selector)
{
var dict = new Dictionary<string, PropertyInfo>();
var props = selector(type);
foreach (var p in props)
{
typeof (PK8), typeof (PA8), typeof (PB8),
typeof (PB7),
typeof (PK7), typeof (PK6), typeof (PK5), typeof (PK4), typeof(BK4),
typeof (PK3), typeof (XK3), typeof (CK3),
typeof (PK2), typeof (SK2), typeof (PK1),
};
if (!dict.ContainsKey(p.Name))
dict.Add(p.Name, p);
}
return dict;
}
/// <summary>
/// Extra properties to show in the list of selectable properties (GUI)
/// </summary>
public static readonly List<string> CustomProperties = new()
internal const string CONST_RAND = "$rand";
internal const string CONST_SHINY = "$shiny";
internal const string CONST_SUGGEST = "$suggest";
private const string CONST_BYTES = "$[]";
private const char CONST_POINTER = '*';
internal const char CONST_SPECIAL = '$';
internal const string PROP_LEGAL = "Legal";
internal const string PROP_TYPENAME = "ObjectType";
internal const string PROP_RIBBONS = "Ribbons";
internal const string PROP_CONTESTSTATS = "ContestStats";
internal const string PROP_MOVEMASTERY = "MoveMastery";
internal const string IdentifierContains = nameof(IdentifierContains);
private static string[][] GetPropArray(IReadOnlyList<Type> types, IReadOnlyList<string> extra)
{
var result = new string[types.Count + 2][];
var p = result.AsSpan(1, types.Count);
for (int i = 0; i < p.Length; i++)
{
PROP_LEGAL, PROP_TYPENAME, PROP_RIBBONS, PROP_CONTESTSTATS, PROP_MOVEMASTERY,
IdentifierContains, nameof(ISlotInfo.Slot), nameof(SlotInfoBox.Box),
};
/// <summary>
/// Property names, indexed by <see cref="Types"/>.
/// </summary>
public static string[][] Properties => GetProperties.Value;
private static readonly Lazy<string[][]> GetProperties = new(() => GetPropArray(Types, CustomProperties));
private static readonly Dictionary<string, PropertyInfo>[] Props = GetPropertyDictionaries(Types);
private static Dictionary<string, PropertyInfo>[] GetPropertyDictionaries(IReadOnlyList<Type> types)
{
var result = new Dictionary<string, PropertyInfo>[types.Count];
for (int i = 0; i < types.Count; i++)
result[i] = GetPropertyDictionary(types[i], ReflectUtil.GetAllPropertyInfoPublic);
return result;
var props = ReflectUtil.GetPropertiesPublic(types[i]);
p[i] = props.Concat(extra).OrderBy(a => a).ToArray();
}
private static Dictionary<string, PropertyInfo> GetPropertyDictionary(Type type, Func<Type, IEnumerable<PropertyInfo>> selector)
// Properties for any PKM
// Properties shared by all PKM
var first = p[0];
var any = new HashSet<string>(first);
var all = new HashSet<string>(first);
for (int i = 1; i < p.Length; i++)
{
var dict = new Dictionary<string, PropertyInfo>();
var props = selector(type);
foreach (var p in props)
{
if (!dict.ContainsKey(p.Name))
dict.Add(p.Name, p);
}
return dict;
any.UnionWith(p[i]);
all.IntersectWith(p[i]);
}
internal const string CONST_RAND = "$rand";
internal const string CONST_SHINY = "$shiny";
internal const string CONST_SUGGEST = "$suggest";
private const string CONST_BYTES = "$[]";
private const string CONST_POINTER = "*";
result[0] = any.OrderBy(z => z).ToArray();
result[^1] = all.OrderBy(z => z).ToArray();
return result;
}
internal const string PROP_LEGAL = "Legal";
internal const string PROP_TYPENAME = "ObjectType";
internal const string PROP_RIBBONS = "Ribbons";
internal const string PROP_CONTESTSTATS = "ContestStats";
internal const string PROP_MOVEMASTERY = "MoveMastery";
internal const string IdentifierContains = nameof(IdentifierContains);
/// <summary>
/// Tries to fetch the <see cref="PKM"/> property from the cache of available properties.
/// </summary>
/// <param name="pk">Pokémon to check</param>
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
public static bool TryGetHasProperty(PKM pk, string name, [NotNullWhen(true)] out PropertyInfo? pi)
{
var type = pk.GetType();
return TryGetHasProperty(type, name, out pi);
}
private static string[][] GetPropArray(IReadOnlyList<Type> types, IReadOnlyList<string> extra)
/// <summary>
/// Tries to fetch the <see cref="PKM"/> property from the cache of available properties.
/// </summary>
/// <param name="type">Type to check</param>
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
public static bool TryGetHasProperty(Type type, string name, [NotNullWhen(true)] out PropertyInfo? pi)
{
var index = Array.IndexOf(Types, type);
if (index < 0)
{
var result = new string[types.Count + 2][];
var p = result.AsSpan(1, types.Count);
for (int i = 0; i < p.Length; i++)
{
var props = ReflectUtil.GetPropertiesPublic(types[i]);
p[i] = props.Concat(extra).OrderBy(a => a).ToArray();
}
// Properties for any PKM
// Properties shared by all PKM
var first = p[0];
var any = new HashSet<string>(first);
var all = new HashSet<string>(first);
for (int i = 1; i < p.Length; i++)
{
any.UnionWith(p[i]);
all.IntersectWith(p[i]);
}
result[0] = any.OrderBy(z => z).ToArray();
result[^1] = all.OrderBy(z => z).ToArray();
return result;
pi = null;
return false;
}
var props = Props[index];
return props.TryGetValue(name, out pi);
}
/// <summary>
/// Tries to fetch the <see cref="PKM"/> property from the cache of available properties.
/// </summary>
/// <param name="pk">Pokémon to check</param>
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
public static bool TryGetHasProperty(PKM pk, string name, [NotNullWhen(true)] out PropertyInfo? pi)
/// <summary>
/// Gets a list of <see cref="PKM"/> types that implement the requested <see cref="property"/>.
/// </summary>
public static IEnumerable<string> GetTypesImplementing(string property)
{
for (int i = 0; i < Types.Length; i++)
{
var type = pk.GetType();
return TryGetHasProperty(type, name, out pi);
}
/// <summary>
/// Tries to fetch the <see cref="PKM"/> property from the cache of available properties.
/// </summary>
/// <param name="type">Type to check</param>
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
public static bool TryGetHasProperty(Type type, string name, [NotNullWhen(true)] out PropertyInfo? pi)
{
var index = Array.IndexOf(Types, type);
if (index < 0)
{
pi = null;
return false;
}
var props = Props[index];
return props.TryGetValue(name, out pi);
}
/// <summary>
/// Gets a list of <see cref="PKM"/> types that implement the requested <see cref="property"/>.
/// </summary>
public static IEnumerable<string> GetTypesImplementing(string property)
{
for (int i = 0; i < Types.Length; i++)
{
var type = Types[i];
var props = Props[i];
if (!props.TryGetValue(property, out var pi))
continue;
yield return $"{type.Name}: {pi.PropertyType.Name}";
}
}
/// <summary>
/// Gets the type of the <see cref="PKM"/> property using the saved cache of properties.
/// </summary>
/// <param name="propertyName">Property Name to fetch the type for</param>
/// <param name="typeIndex">Type index (within <see cref="Types"/>. Leave empty (0) for a nonspecific format.</param>
/// <returns>Short name of the property's type.</returns>
public static string? GetPropertyType(string propertyName, int typeIndex = 0)
{
if (CustomProperties.Contains(propertyName))
return "Custom";
if (typeIndex == 0) // Any
{
foreach (var p in Props)
{
if (p.TryGetValue(propertyName, out var pi))
return pi.PropertyType.Name;
}
return null;
}
int index = typeIndex - 1 >= Props.Length ? 0 : typeIndex - 1; // All vs Specific
var pr = Props[index];
if (!pr.TryGetValue(propertyName, out var info))
return null;
return info.PropertyType.Name;
}
/// <summary>
/// Initializes the <see cref="StringInstruction"/> list with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
/// </summary>
/// <param name="il">Instructions to initialize.</param>
public static void ScreenStrings(IEnumerable<StringInstruction> il)
{
foreach (var i in il.Where(i => !i.PropertyValue.All(char.IsDigit)))
{
string pv = i.PropertyValue;
if (pv.StartsWith("$") && !pv.StartsWith(CONST_BYTES) && pv.Contains(','))
i.SetRandRange(pv);
SetInstructionScreenedValue(i);
}
}
/// <summary>
/// Initializes the <see cref="StringInstruction"/> with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
/// </summary>
/// <param name="i">Instruction to initialize.</param>
private static void SetInstructionScreenedValue(StringInstruction i)
{
switch (i.PropertyName)
{
case nameof(PKM.Species): i.SetScreenedValue(GameInfo.Strings.specieslist); return;
case nameof(PKM.HeldItem): i.SetScreenedValue(GameInfo.Strings.itemlist); return;
case nameof(PKM.Ability): i.SetScreenedValue(GameInfo.Strings.abilitylist); return;
case nameof(PKM.Nature): i.SetScreenedValue(GameInfo.Strings.natures); return;
case nameof(PKM.Ball): i.SetScreenedValue(GameInfo.Strings.balllist); return;
case nameof(PKM.Move1) or nameof(PKM.Move2) or nameof(PKM.Move3) or nameof(PKM.Move4):
case nameof(PKM.RelearnMove1) or nameof(PKM.RelearnMove2) or nameof(PKM.RelearnMove3) or nameof(PKM.RelearnMove4):
i.SetScreenedValue(GameInfo.Strings.movelist); return;
}
}
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="pk">Object to check.</param>
/// <returns>True if <see cref="pk"/> matches all filters.</returns>
public static bool IsFilterMatch(IEnumerable<StringInstruction> filters, PKM pk) => filters.All(z => IsFilterMatch(z, pk, Props[Array.IndexOf(Types, pk.GetType())]));
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="pk">Object to check.</param>
/// <returns>True if <see cref="pk"/> matches all filters.</returns>
public static bool IsFilterMatchMeta(IEnumerable<StringInstruction> filters, SlotCache pk)
{
foreach (var i in filters)
{
foreach (var filter in BatchFilters.FilterMeta)
{
if (!filter.IsMatch(i.PropertyName))
continue;
if (!filter.IsFiltered(pk, i))
return false;
break;
}
}
return true;
}
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <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(IEnumerable<StringInstruction> filters, object obj)
{
foreach (var cmd in filters)
{
if (cmd.PropertyName is PROP_TYPENAME)
{
if ((obj.GetType().Name == cmd.PropertyValue) != cmd.Evaluator)
return false;
continue;
}
if (!ReflectUtil.HasProperty(obj, cmd.PropertyName, out var pi))
return false;
try
{
if (pi.IsValueEqual(obj, cmd.PropertyValue) == cmd.Evaluator)
continue;
}
// User provided inputs can mismatch the type's required value format, and fail to be compared.
catch (Exception e)
{
Debug.WriteLine($"Unable to compare {cmd.PropertyName} to {cmd.PropertyValue}.");
Debug.WriteLine(e.Message);
}
return false;
}
return true;
}
/// <summary>
/// Tries to modify the <see cref="PKM"/>.
/// </summary>
/// <param name="pk">Object to modify.</param>
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
/// <returns>Result of the attempted modification.</returns>
public static bool TryModify(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
{
var result = TryModifyPKM(pk, filters, modifications);
return result == ModifyResult.Modified;
}
/// <summary>
/// Tries to modify the <see cref="BatchInfo"/>.
/// </summary>
/// <param name="pk">Command Filter</param>
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
/// <returns>Result of the attempted modification.</returns>
internal static ModifyResult TryModifyPKM(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
{
if (!pk.ChecksumValid || pk.Species == 0)
return ModifyResult.Invalid;
var info = new BatchInfo(pk);
var pi = Props[Array.IndexOf(Types, pk.GetType())];
foreach (var cmd in filters)
{
try
{
if (!IsFilterMatch(cmd, info, pi))
return ModifyResult.Filtered;
}
// Swallow any error because this can be malformed user input.
catch (Exception ex)
{
Debug.WriteLine(MsgBEModifyFailCompare + " " + ex.Message, cmd.PropertyName, cmd.PropertyValue);
return ModifyResult.Error;
}
}
ModifyResult result = ModifyResult.Modified;
foreach (var cmd in modifications)
{
try
{
var tmp = SetPKMProperty(cmd, info, pi);
if (tmp != ModifyResult.Modified)
result = tmp;
}
// Swallow any error because this can be malformed user input.
catch (Exception ex)
{
Debug.WriteLine(MsgBEModifyFail + " " + ex.Message, cmd.PropertyName, cmd.PropertyValue);
}
}
return result;
}
/// <summary>
/// Sets the if the <see cref="BatchInfo"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="info">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filtered, else false.</returns>
private static ModifyResult SetPKMProperty(StringInstruction cmd, BatchInfo info, IReadOnlyDictionary<string, PropertyInfo> props)
{
var pk = info.Entity;
if (cmd.PropertyValue.StartsWith(CONST_BYTES))
return SetByteArrayProperty(pk, cmd);
if (cmd.PropertyValue.StartsWith(CONST_SUGGEST, true, CultureInfo.CurrentCulture))
return SetSuggestedPKMProperty(cmd.PropertyName, info, cmd.PropertyValue);
if (cmd.PropertyValue == CONST_RAND && cmd.PropertyName == nameof(PKM.Moves))
return SetMoves(pk, info.Legality.GetMoveSet(true));
if (SetComplexProperty(pk, cmd))
return ModifyResult.Modified;
if (!props.TryGetValue(cmd.PropertyName, out var pi))
return ModifyResult.Error;
if (!pi.CanWrite)
return ModifyResult.Error;
object val;
if (cmd.Random)
val = cmd.RandomValue;
else if (cmd.PropertyValue.StartsWith(CONST_POINTER) && props.TryGetValue(cmd.PropertyValue[1..], out var opi))
val = opi.GetValue(pk);
else
val = cmd.PropertyValue;
ReflectUtil.SetValue(pi, pk, val);
return ModifyResult.Modified;
}
/// <summary>
/// Checks if the <see cref="BatchInfo"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="info">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filter matches, else false.</returns>
private static bool IsFilterMatch(StringInstruction cmd, BatchInfo info, IReadOnlyDictionary<string, PropertyInfo> props)
{
var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
if (match != null)
return match.IsFiltered(info, cmd);
return IsPropertyFiltered(cmd, info.Entity, props);
}
/// <summary>
/// Checks if the <see cref="PKM"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="pk">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filter matches, else false.</returns>
private static bool IsFilterMatch(StringInstruction cmd, PKM pk, IReadOnlyDictionary<string, PropertyInfo> props)
{
var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
if (match != null)
return match.IsFiltered(pk, cmd);
return IsPropertyFiltered(cmd, pk, props);
}
/// <summary>
/// Checks if the <see cref="PKM"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="pk">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache</param>
/// <returns>True if filtered, else false.</returns>
private static bool IsPropertyFiltered(StringInstruction cmd, PKM pk, IReadOnlyDictionary<string, PropertyInfo> props)
{
if (!props.TryGetValue(cmd.PropertyName, out var pi))
return false;
if (!pi.CanRead)
return false;
return pi.IsValueEqual(pk, cmd.PropertyValue) == cmd.Evaluator;
}
/// <summary>
/// Sets the <see cref="PKM"/> data with a suggested value based on its <see cref="LegalityAnalysis"/>.
/// </summary>
/// <param name="name">Property to modify.</param>
/// <param name="info">Cached info storing Legal data.</param>
/// <param name="propValue">Suggestion string which starts with <see cref="CONST_SUGGEST"/></param>
private static ModifyResult SetSuggestedPKMProperty(string name, BatchInfo info, string propValue)
{
var first = BatchMods.SuggestionMods.Find(z => z.IsMatch(name, propValue, info));
if (first != null)
return first.Modify(name, propValue, info);
return ModifyResult.Error;
}
/// <summary>
/// Sets the <see cref="PKM"/> byte array property to a specified value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="cmd">Modification</param>
private static ModifyResult SetByteArrayProperty(PKM pk, StringInstruction cmd)
{
switch (cmd.PropertyName)
{
case nameof(PKM.Nickname_Trash): ConvertToBytes(cmd.PropertyValue).CopyTo(pk.Nickname_Trash); return ModifyResult.Modified;
case nameof(PKM.OT_Trash): ConvertToBytes(cmd.PropertyValue).CopyTo(pk.OT_Trash); return ModifyResult.Modified;
case nameof(PKM.HT_Trash): ConvertToBytes(cmd.PropertyValue).CopyTo(pk.HT_Trash); return ModifyResult.Modified;
default:
return ModifyResult.Error;
}
static byte[] ConvertToBytes(string str)
{
var arr = str[CONST_BYTES.Length..].Split(',');
return Array.ConvertAll(arr, z => Convert.ToByte(z.Trim(), 16));
}
}
/// <summary>
/// Sets the <see cref="PKM"/> property to a non-specific smart value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="cmd">Modification</param>
/// <returns>True if modified, false if no modifications done.</returns>
private static bool SetComplexProperty(PKM pk, StringInstruction cmd)
{
if (cmd.PropertyName.StartsWith("IV") && cmd.PropertyValue == CONST_RAND)
{
SetRandomIVs(pk, cmd);
return true;
}
var match = BatchMods.ComplexMods.Find(z => z.IsMatch(cmd.PropertyName, cmd.PropertyValue));
if (match == null)
return false;
match.Modify(pk, cmd);
return true;
}
/// <summary>
/// Sets the <see cref="PKM"/> IV(s) to a random value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="cmd">Modification</param>
private static void SetRandomIVs(PKM pk, StringInstruction cmd)
{
if (cmd.PropertyName == nameof(PKM.IVs))
{
pk.SetRandomIVs();
return;
}
if (TryGetHasProperty(pk, cmd.PropertyName, out var pi))
ReflectUtil.SetValue(pi, pk, Util.Rand.Next(pk.MaxIV + 1));
var type = Types[i];
var props = Props[i];
if (!props.TryGetValue(property, out var pi))
continue;
yield return $"{type.Name}: {pi.PropertyType.Name}";
}
}
/// <summary>
/// Gets the type of the <see cref="PKM"/> property using the saved cache of properties.
/// </summary>
/// <param name="propertyName">Property Name to fetch the type for</param>
/// <param name="typeIndex">Type index (within <see cref="Types"/>. Leave empty (0) for a nonspecific format.</param>
/// <returns>Short name of the property's type.</returns>
public static string? GetPropertyType(string propertyName, int typeIndex = 0)
{
if (CustomProperties.Contains(propertyName))
return "Custom";
if (typeIndex == 0) // Any
{
foreach (var p in Props)
{
if (p.TryGetValue(propertyName, out var pi))
return pi.PropertyType.Name;
}
return null;
}
int index = typeIndex - 1 >= Props.Length ? 0 : typeIndex - 1; // All vs Specific
var pr = Props[index];
if (!pr.TryGetValue(propertyName, out var info))
return null;
return info.PropertyType.Name;
}
/// <summary>
/// Initializes the <see cref="StringInstruction"/> list with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
/// </summary>
/// <param name="il">Instructions to initialize.</param>
public static void ScreenStrings(IEnumerable<StringInstruction> il)
{
foreach (var i in il.Where(i => !i.PropertyValue.All(char.IsDigit)))
{
string pv = i.PropertyValue;
if (pv.StartsWith(CONST_SPECIAL) && !pv.StartsWith(CONST_BYTES, StringComparison.Ordinal) && pv.Contains(','))
i.SetRandRange(pv);
SetInstructionScreenedValue(i);
}
}
/// <summary>
/// Initializes the <see cref="StringInstruction"/> with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
/// </summary>
/// <param name="i">Instruction to initialize.</param>
private static void SetInstructionScreenedValue(StringInstruction i)
{
switch (i.PropertyName)
{
case nameof(PKM.Species): i.SetScreenedValue(GameInfo.Strings.specieslist); return;
case nameof(PKM.HeldItem): i.SetScreenedValue(GameInfo.Strings.itemlist); return;
case nameof(PKM.Ability): i.SetScreenedValue(GameInfo.Strings.abilitylist); return;
case nameof(PKM.Nature): i.SetScreenedValue(GameInfo.Strings.natures); return;
case nameof(PKM.Ball): i.SetScreenedValue(GameInfo.Strings.balllist); return;
case nameof(PKM.Move1) or nameof(PKM.Move2) or nameof(PKM.Move3) or nameof(PKM.Move4):
case nameof(PKM.RelearnMove1) or nameof(PKM.RelearnMove2) or nameof(PKM.RelearnMove3) or nameof(PKM.RelearnMove4):
i.SetScreenedValue(GameInfo.Strings.movelist); return;
}
}
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="pk">Object to check.</param>
/// <returns>True if <see cref="pk"/> matches all filters.</returns>
public static bool IsFilterMatch(IEnumerable<StringInstruction> filters, PKM pk) => filters.All(z => IsFilterMatch(z, pk, Props[Array.IndexOf(Types, pk.GetType())]));
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="pk">Object to check.</param>
/// <returns>True if <see cref="pk"/> matches all filters.</returns>
public static bool IsFilterMatchMeta(IEnumerable<StringInstruction> filters, SlotCache pk)
{
foreach (var i in filters)
{
foreach (var filter in BatchFilters.FilterMeta)
{
if (!filter.IsMatch(i.PropertyName))
continue;
if (!filter.IsFiltered(pk, i))
return false;
break;
}
}
return true;
}
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <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(IEnumerable<StringInstruction> filters, object obj)
{
foreach (var cmd in filters)
{
if (cmd.PropertyName is PROP_TYPENAME)
{
if ((obj.GetType().Name == cmd.PropertyValue) != cmd.Evaluator)
return false;
continue;
}
if (!ReflectUtil.HasProperty(obj, cmd.PropertyName, out var pi))
return false;
try
{
if (pi.IsValueEqual(obj, cmd.PropertyValue) == cmd.Evaluator)
continue;
}
// User provided inputs can mismatch the type's required value format, and fail to be compared.
catch (Exception e)
{
Debug.WriteLine($"Unable to compare {cmd.PropertyName} to {cmd.PropertyValue}.");
Debug.WriteLine(e.Message);
}
return false;
}
return true;
}
/// <summary>
/// Tries to modify the <see cref="PKM"/>.
/// </summary>
/// <param name="pk">Object to modify.</param>
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
/// <returns>Result of the attempted modification.</returns>
public static bool TryModify(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
{
var result = TryModifyPKM(pk, filters, modifications);
return result == ModifyResult.Modified;
}
/// <summary>
/// Tries to modify the <see cref="BatchInfo"/>.
/// </summary>
/// <param name="pk">Command Filter</param>
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
/// <returns>Result of the attempted modification.</returns>
internal static ModifyResult TryModifyPKM(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
{
if (!pk.ChecksumValid || pk.Species == 0)
return ModifyResult.Invalid;
var info = new BatchInfo(pk);
var pi = Props[Array.IndexOf(Types, pk.GetType())];
foreach (var cmd in filters)
{
try
{
if (!IsFilterMatch(cmd, info, pi))
return ModifyResult.Filtered;
}
// Swallow any error because this can be malformed user input.
catch (Exception ex)
{
Debug.WriteLine(MsgBEModifyFailCompare + " " + ex.Message, cmd.PropertyName, cmd.PropertyValue);
return ModifyResult.Error;
}
}
ModifyResult result = ModifyResult.Modified;
foreach (var cmd in modifications)
{
try
{
var tmp = SetPKMProperty(cmd, info, pi);
if (tmp != ModifyResult.Modified)
result = tmp;
}
// Swallow any error because this can be malformed user input.
catch (Exception ex)
{
Debug.WriteLine(MsgBEModifyFail + " " + ex.Message, cmd.PropertyName, cmd.PropertyValue);
}
}
return result;
}
/// <summary>
/// Sets the if the <see cref="BatchInfo"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="info">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filtered, else false.</returns>
private static ModifyResult SetPKMProperty(StringInstruction cmd, BatchInfo info, IReadOnlyDictionary<string, PropertyInfo> props)
{
var pk = info.Entity;
if (cmd.PropertyValue.StartsWith(CONST_BYTES, StringComparison.Ordinal))
return SetByteArrayProperty(pk, cmd);
if (cmd.PropertyValue.StartsWith(CONST_SUGGEST, StringComparison.OrdinalIgnoreCase))
return SetSuggestedPKMProperty(cmd.PropertyName, info, cmd.PropertyValue);
if (cmd.PropertyValue == CONST_RAND && cmd.PropertyName == nameof(PKM.Moves))
return SetMoves(pk, info.Legality.GetMoveSet(true));
if (SetComplexProperty(pk, cmd))
return ModifyResult.Modified;
if (!props.TryGetValue(cmd.PropertyName, out var pi))
return ModifyResult.Error;
if (!pi.CanWrite)
return ModifyResult.Error;
object val;
if (cmd.Random)
val = cmd.RandomValue;
else if (cmd.PropertyValue.StartsWith(CONST_POINTER) && props.TryGetValue(cmd.PropertyValue[1..], out var opi))
val = opi.GetValue(pk);
else
val = cmd.PropertyValue;
ReflectUtil.SetValue(pi, pk, val);
return ModifyResult.Modified;
}
/// <summary>
/// Checks if the <see cref="BatchInfo"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="info">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filter matches, else false.</returns>
private static bool IsFilterMatch(StringInstruction cmd, BatchInfo info, IReadOnlyDictionary<string, PropertyInfo> props)
{
var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
if (match != null)
return match.IsFiltered(info, cmd);
return IsPropertyFiltered(cmd, info.Entity, props);
}
/// <summary>
/// Checks if the <see cref="PKM"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="pk">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filter matches, else false.</returns>
private static bool IsFilterMatch(StringInstruction cmd, PKM pk, IReadOnlyDictionary<string, PropertyInfo> props)
{
var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName));
if (match != null)
return match.IsFiltered(pk, cmd);
return IsPropertyFiltered(cmd, pk, props);
}
/// <summary>
/// Checks if the <see cref="PKM"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="pk">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache</param>
/// <returns>True if filtered, else false.</returns>
private static bool IsPropertyFiltered(StringInstruction cmd, PKM pk, IReadOnlyDictionary<string, PropertyInfo> props)
{
if (!props.TryGetValue(cmd.PropertyName, out var pi))
return false;
if (!pi.CanRead)
return false;
return pi.IsValueEqual(pk, cmd.PropertyValue) == cmd.Evaluator;
}
/// <summary>
/// Sets the <see cref="PKM"/> data with a suggested value based on its <see cref="LegalityAnalysis"/>.
/// </summary>
/// <param name="name">Property to modify.</param>
/// <param name="info">Cached info storing Legal data.</param>
/// <param name="propValue">Suggestion string which starts with <see cref="CONST_SUGGEST"/></param>
private static ModifyResult SetSuggestedPKMProperty(string name, BatchInfo info, string propValue)
{
var first = BatchMods.SuggestionMods.Find(z => z.IsMatch(name, propValue, info));
if (first != null)
return first.Modify(name, propValue, info);
return ModifyResult.Error;
}
/// <summary>
/// Sets the <see cref="PKM"/> byte array property to a specified value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="cmd">Modification</param>
private static ModifyResult SetByteArrayProperty(PKM pk, StringInstruction cmd)
{
switch (cmd.PropertyName)
{
case nameof(PKM.Nickname_Trash): ConvertToBytes(cmd.PropertyValue).CopyTo(pk.Nickname_Trash); return ModifyResult.Modified;
case nameof(PKM.OT_Trash): ConvertToBytes(cmd.PropertyValue).CopyTo(pk.OT_Trash); return ModifyResult.Modified;
case nameof(PKM.HT_Trash): ConvertToBytes(cmd.PropertyValue).CopyTo(pk.HT_Trash); return ModifyResult.Modified;
default:
return ModifyResult.Error;
}
static byte[] ConvertToBytes(string str)
{
var arr = str[CONST_BYTES.Length..].Split(',');
return Array.ConvertAll(arr, z => Convert.ToByte(z.Trim(), 16));
}
}
/// <summary>
/// Sets the <see cref="PKM"/> property to a non-specific smart value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="cmd">Modification</param>
/// <returns>True if modified, false if no modifications done.</returns>
private static bool SetComplexProperty(PKM pk, StringInstruction cmd)
{
if (cmd.PropertyName.StartsWith("IV", StringComparison.Ordinal) && cmd.PropertyValue == CONST_RAND)
{
SetRandomIVs(pk, cmd);
return true;
}
var match = BatchMods.ComplexMods.Find(z => z.IsMatch(cmd.PropertyName, cmd.PropertyValue));
if (match == null)
return false;
match.Modify(pk, cmd);
return true;
}
/// <summary>
/// Sets the <see cref="PKM"/> IV(s) to a random value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="cmd">Modification</param>
private static void SetRandomIVs(PKM pk, StringInstruction cmd)
{
if (cmd.PropertyName == nameof(PKM.IVs))
{
pk.SetRandomIVs();
return;
}
if (TryGetHasProperty(pk, cmd.PropertyName, out var pi))
ReflectUtil.SetValue(pi, pk, Util.Rand.Next(pk.MaxIV + 1));
}
}

View file

@ -4,83 +4,82 @@ using System.Diagnostics;
using System.Linq;
using static PKHeX.Core.MessageStrings;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Carries out a batch edit and contains information summarizing the results.
/// </summary>
public sealed class BatchEditor
{
private int Modified { get; set; }
private int Iterated { get; set; }
private int Failed { get; set; }
/// <summary>
/// Carries out a batch edit and contains information summarizing the results.
/// Tries to modify the <see cref="PKM"/>.
/// </summary>
public sealed class BatchEditor
/// <param name="pk">Object to modify.</param>
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
/// <param name="modifications">Modifications to perform on the <see cref="pk"/>.</param>
/// <returns>Result of the attempted modification.</returns>
public bool Process(PKM pk, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
{
private int Modified { get; set; }
private int Iterated { get; set; }
private int Failed { get; set; }
/// <summary>
/// Tries to modify the <see cref="PKM"/>.
/// </summary>
/// <param name="pkm">Object to modify.</param>
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
/// <param name="modifications">Modifications to perform on the <see cref="pkm"/>.</param>
/// <returns>Result of the attempted modification.</returns>
public bool Process(PKM pkm, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
if (pk.Species <= 0)
return false;
if (!pk.Valid)
{
if (pkm.Species <= 0)
return false;
if (!pkm.Valid)
{
Iterated++;
const string reason = "Not Valid.";
Debug.WriteLine($"{MsgBEModifyFailBlocked} {reason}");
return false;
}
var result = BatchEditing.TryModifyPKM(pkm, filters, modifications);
if (result != ModifyResult.Invalid)
Iterated++;
if (result == ModifyResult.Error)
Failed++;
if (result != ModifyResult.Modified)
return false;
pkm.RefreshChecksum();
Modified++;
return true;
Iterated++;
const string reason = "Not Valid.";
Debug.WriteLine($"{MsgBEModifyFailBlocked} {reason}");
return false;
}
/// <summary>
/// Gets a message indicating the overall result of all modifications performed across multiple Batch Edit jobs.
/// </summary>
/// <param name="sets">Collection of modifications.</param>
/// <returns>Friendly (multi-line) string indicating the result of the batch edits.</returns>
public string GetEditorResults(ICollection<StringInstructionSet> sets)
var result = BatchEditing.TryModifyPKM(pk, filters, modifications);
if (result != ModifyResult.Invalid)
Iterated++;
if (result == ModifyResult.Error)
Failed++;
if (result != ModifyResult.Modified)
return false;
pk.RefreshChecksum();
Modified++;
return true;
}
/// <summary>
/// Gets a message indicating the overall result of all modifications performed across multiple Batch Edit jobs.
/// </summary>
/// <param name="sets">Collection of modifications.</param>
/// <returns>Friendly (multi-line) string indicating the result of the batch edits.</returns>
public string GetEditorResults(ICollection<StringInstructionSet> sets)
{
if (sets.Count == 0)
return MsgBEInstructionNone;
int ctr = Modified / sets.Count;
int len = Iterated / sets.Count;
string maybe = sets.Count == 1 ? string.Empty : "~";
string result = string.Format(MsgBEModifySuccess, maybe, ctr, len);
if (Failed > 0)
result += Environment.NewLine + maybe + string.Format(MsgBEModifyFailError, Failed);
return result;
}
public static BatchEditor Execute(IList<string> lines, IEnumerable<PKM> data)
{
var editor = new BatchEditor();
var sets = StringInstructionSet.GetBatchSets(lines).ToArray();
foreach (var pk in data)
{
if (sets.Count == 0)
return MsgBEInstructionNone;
int ctr = Modified / sets.Count;
int len = Iterated / sets.Count;
string maybe = sets.Count == 1 ? string.Empty : "~";
string result = string.Format(MsgBEModifySuccess, maybe, ctr, len);
if (Failed > 0)
result += Environment.NewLine + maybe + string.Format(MsgBEModifyFailError, Failed);
return result;
foreach (var set in sets)
editor.Process(pk, set.Filters, set.Instructions);
}
public static BatchEditor Execute(IList<string> lines, IEnumerable<PKM> data)
{
var editor = new BatchEditor();
var sets = StringInstructionSet.GetBatchSets(lines).ToArray();
foreach (var pk in data)
{
foreach (var set in sets)
editor.Process(pk, set.Filters, set.Instructions);
}
return editor;
}
return editor;
}
public void AddSkipped()
{
++Iterated;
}
public void AddSkipped()
{
++Iterated;
}
}

View file

@ -1,31 +1,30 @@
using System.Collections.Generic;
using System.Collections.Generic;
using static PKHeX.Core.BatchEditing;
namespace PKHeX.Core
namespace PKHeX.Core;
public static class BatchFilters
{
public static class BatchFilters
public static readonly List<IComplexFilter> FilterMods = new()
{
public static readonly List<IComplexFilter> FilterMods = new()
{
new ComplexFilter(PROP_LEGAL,
(pkm, cmd) => bool.TryParse(cmd.PropertyValue, out var b) && (b == new LegalityAnalysis(pkm).Valid) == cmd.Evaluator,
(info, cmd) => bool.TryParse(cmd.PropertyValue, out var b) && (b == info.Legality.Valid) == cmd.Evaluator),
new ComplexFilter(PROP_LEGAL,
(pk, cmd) => bool.TryParse(cmd.PropertyValue, out var b) && (b == new LegalityAnalysis(pk).Valid) == cmd.Evaluator,
(info, cmd) => bool.TryParse(cmd.PropertyValue, out var b) && (b == info.Legality.Valid) == cmd.Evaluator),
new ComplexFilter(PROP_TYPENAME,
(pkm, cmd) => (pkm.GetType().Name == cmd.PropertyValue) == cmd.Evaluator,
(info, cmd) => (info.Entity.GetType().Name == cmd.PropertyValue) == cmd.Evaluator),
};
new ComplexFilter(PROP_TYPENAME,
(pk, cmd) => (pk.GetType().Name == cmd.PropertyValue) == cmd.Evaluator,
(info, cmd) => (info.Entity.GetType().Name == cmd.PropertyValue) == cmd.Evaluator),
};
public static readonly List<IComplexFilterMeta> FilterMeta = new()
{
new MetaFilter(IdentifierContains,
(obj, cmd) => obj is SlotCache s && s.Identify().Contains(cmd.PropertyValue) == cmd.Evaluator),
public static readonly List<IComplexFilterMeta> FilterMeta = new()
{
new MetaFilter(IdentifierContains,
(obj, cmd) => obj is SlotCache s && s.Identify().Contains(cmd.PropertyValue) == cmd.Evaluator),
new MetaFilter(nameof(SlotInfoBox.Box),
(obj, cmd) => obj is SlotCache { Source: SlotInfoBox b } && int.TryParse(cmd.PropertyValue, out var box) && b.Box + 1 == box),
new MetaFilter(nameof(SlotInfoBox.Box),
(obj, cmd) => obj is SlotCache { Source: SlotInfoBox b } && int.TryParse(cmd.PropertyValue, out var box) && b.Box + 1 == box),
new MetaFilter(nameof(ISlotInfo.Slot),
(obj, cmd) => obj is SlotCache s && int.TryParse(cmd.PropertyValue, out var slot) && s.Source.Slot + 1 == slot),
};
}
new MetaFilter(nameof(ISlotInfo.Slot),
(obj, cmd) => obj is SlotCache s && int.TryParse(cmd.PropertyValue, out var slot) && s.Source.Slot + 1 == slot),
};
}

View file

@ -1,19 +1,18 @@
using System.Collections.Generic;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Information wrapper used for Batch Editing to apply suggested values.
/// </summary>
public sealed class BatchInfo
{
/// <summary>
/// Information wrapper used for Batch Editing to apply suggested values.
/// </summary>
public sealed class BatchInfo
{
internal PKM Entity { get; }
internal BatchInfo(PKM pk) => Entity = pk;
internal PKM Entity { get; }
internal BatchInfo(PKM pk) => Entity = pk;
private LegalityAnalysis? la;
internal LegalityAnalysis Legality => la ??= new LegalityAnalysis(Entity);
private LegalityAnalysis? la;
internal LegalityAnalysis Legality => la ??= new LegalityAnalysis(Entity);
public bool Legal => Legality.Valid;
internal IReadOnlyList<int> SuggestedRelearn => Legality.GetSuggestedRelearnMoves();
}
public bool Legal => Legality.Valid;
internal IReadOnlyList<int> SuggestedRelearn => Legality.GetSuggestedRelearnMoves();
}

View file

@ -3,95 +3,94 @@ using System.Collections.Generic;
using System.Globalization;
using static PKHeX.Core.BatchEditing;
namespace PKHeX.Core
namespace PKHeX.Core;
public static class BatchMods
{
public static class BatchMods
public static readonly List<ISuggestModification> SuggestionMods = new()
{
public static readonly List<ISuggestModification> SuggestionMods = new()
{
// Interface Specific
new TypeSuggestion<ICombatPower>(nameof(ICombatPower.Stat_CP), p => p.ResetCP()),
new TypeSuggestion<IScaledSizeValue>(nameof(IScaledSizeValue.HeightAbsolute), p => p.ResetHeight()),
new TypeSuggestion<IScaledSizeValue>(nameof(IScaledSizeValue.WeightAbsolute), p => p.ResetWeight()),
new TypeSuggestion<IHyperTrain>(nameof(Extensions.HyperTrainClear), p => p.HyperTrainClear()),
new TypeSuggestion<IGeoTrack>(nameof(Extensions.ClearGeoLocationData), p => p.ClearGeoLocationData()),
new TypeSuggestion<IAwakened>(nameof(Extensions.AwakeningClear), p => p.AwakeningClear()),
new TypeSuggestion<IAwakened>(nameof(Extensions.AwakeningMax), p => p.AwakeningMax()),
new TypeSuggestion<IGanbaru>(nameof(GanbaruExtensions.ClearGanbaruValues), p => p.ClearGanbaruValues()),
new TypeSuggestion<IGanbaru>(nameof(GanbaruExtensions.SetSuggestedGanbaruValues), p => p.SetSuggestedGanbaruValues((PKM)p)),
// Interface Specific
new TypeSuggestion<ICombatPower>(nameof(ICombatPower.Stat_CP), p => p.ResetCP()),
new TypeSuggestion<IScaledSizeValue>(nameof(IScaledSizeValue.HeightAbsolute), p => p.ResetHeight()),
new TypeSuggestion<IScaledSizeValue>(nameof(IScaledSizeValue.WeightAbsolute), p => p.ResetWeight()),
new TypeSuggestion<IHyperTrain>(nameof(Extensions.HyperTrainClear), p => p.HyperTrainClear()),
new TypeSuggestion<IGeoTrack>(nameof(Extensions.ClearGeoLocationData), p => p.ClearGeoLocationData()),
new TypeSuggestion<IAwakened>(nameof(Extensions.AwakeningClear), p => p.AwakeningClear()),
new TypeSuggestion<IAwakened>(nameof(Extensions.AwakeningMax), p => p.AwakeningMax()),
new TypeSuggestion<IGanbaru>(nameof(GanbaruExtensions.ClearGanbaruValues), p => p.ClearGanbaruValues()),
new TypeSuggestion<IGanbaru>(nameof(GanbaruExtensions.SetSuggestedGanbaruValues), p => p.SetSuggestedGanbaruValues((PKM)p)),
// Date Copy
new TypeSuggestion<PKM>(nameof(PKM.EggMetDate), p => p.EggMetDate = p.MetDate),
new TypeSuggestion<PKM>(nameof(PKM.MetDate), p => p.MetDate = p.EggMetDate),
// Date Copy
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.Stats), p => p.ResetPartyStats()),
new TypeSuggestion<PKM>(nameof(PKM.Ball), p => BallApplicator.ApplyBallLegalByColor(p)),
new TypeSuggestion<PKM>(nameof(PKM.Heal), p => p.Heal()),
new TypeSuggestion<PKM>(nameof(PKM.HealPP), p => p.HealPP()),
new TypeSuggestion<PKM>(nameof(IHyperTrain.HyperTrainFlags), p => p.SetSuggestedHyperTrainingData()),
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.Stats), p => p.ResetPartyStats()),
new TypeSuggestion<PKM>(nameof(PKM.Ball), p => BallApplicator.ApplyBallLegalByColor(p)),
new TypeSuggestion<PKM>(nameof(PKM.Heal), p => p.Heal()),
new TypeSuggestion<PKM>(nameof(PKM.HealPP), p => p.HealPP()),
new TypeSuggestion<PKM>(nameof(IHyperTrain.HyperTrainFlags), p => p.SetSuggestedHyperTrainingData()),
new TypeSuggestion<PKM>(nameof(PKM.Move1_PP), p => p.SetSuggestedMovePP(0)),
new TypeSuggestion<PKM>(nameof(PKM.Move2_PP), p => p.SetSuggestedMovePP(1)),
new TypeSuggestion<PKM>(nameof(PKM.Move3_PP), p => p.SetSuggestedMovePP(2)),
new TypeSuggestion<PKM>(nameof(PKM.Move4_PP), p => p.SetSuggestedMovePP(3)),
new TypeSuggestion<PKM>(nameof(PKM.Move1_PP), p => p.SetSuggestedMovePP(0)),
new TypeSuggestion<PKM>(nameof(PKM.Move2_PP), p => p.SetSuggestedMovePP(1)),
new TypeSuggestion<PKM>(nameof(PKM.Move3_PP), p => p.SetSuggestedMovePP(2)),
new TypeSuggestion<PKM>(nameof(PKM.Move4_PP), p => p.SetSuggestedMovePP(3)),
new ComplexSuggestion(nameof(PKM.Moves), (_, _, info) => BatchModifications.SetMoves(info.Entity, info.Legality.GetMoveSet())),
new ComplexSuggestion(nameof(PKM.EVs), (_, _, info) => BatchModifications.SetEVs(info.Entity)),
new ComplexSuggestion(nameof(PKM.RelearnMoves), (_, value, info) => BatchModifications.SetSuggestedRelearnData(info, value)),
new ComplexSuggestion(PROP_RIBBONS, (_, value, info) => BatchModifications.SetSuggestedRibbons(info, value)),
new ComplexSuggestion(nameof(PKM.Met_Location), (_, _, info) => BatchModifications.SetSuggestedMetData(info)),
new ComplexSuggestion(nameof(PKM.CurrentLevel), (_, _, info) => BatchModifications.SetMinimumCurrentLevel(info)),
new ComplexSuggestion(PROP_CONTESTSTATS, p => p is IContestStatsMutable, (_, value, info) => BatchModifications.SetContestStats(info.Entity, info.Legality, value)),
new ComplexSuggestion(PROP_MOVEMASTERY, (_, value, info) => BatchModifications.SetSuggestedMasteryData(info, value)),
};
new ComplexSuggestion(nameof(PKM.Moves), (_, _, info) => BatchModifications.SetMoves(info.Entity, info.Legality.GetMoveSet())),
new ComplexSuggestion(nameof(PKM.EVs), (_, _, info) => BatchModifications.SetEVs(info.Entity)),
new ComplexSuggestion(nameof(PKM.RelearnMoves), (_, value, info) => BatchModifications.SetSuggestedRelearnData(info, value)),
new ComplexSuggestion(PROP_RIBBONS, (_, value, info) => BatchModifications.SetSuggestedRibbons(info, value)),
new ComplexSuggestion(nameof(PKM.Met_Location), (_, _, info) => BatchModifications.SetSuggestedMetData(info)),
new ComplexSuggestion(nameof(PKM.CurrentLevel), (_, _, info) => BatchModifications.SetMinimumCurrentLevel(info)),
new ComplexSuggestion(PROP_CONTESTSTATS, p => p is IContestStatsMutable, (_, value, info) => BatchModifications.SetContestStats(info.Entity, info.Legality, value)),
new ComplexSuggestion(PROP_MOVEMASTERY, (_, value, info) => BatchModifications.SetSuggestedMasteryData(info, value)),
};
private static DateTime ParseDate(string val) => DateTime.ParseExact(val, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None);
private static DateTime ParseDate(string val) => DateTime.ParseExact(val, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None);
public static readonly List<IComplexSet> ComplexMods = new()
{
// Date
new ComplexSet(nameof(PKM.MetDate), (pk, cmd) => pk.MetDate = ParseDate(cmd.PropertyValue)),
new ComplexSet(nameof(PKM.EggMetDate), (pk, cmd) => pk.EggMetDate = ParseDate(cmd.PropertyValue)),
public static readonly List<IComplexSet> ComplexMods = new()
{
// Date
new ComplexSet(nameof(PKM.MetDate), (pk, cmd) => pk.MetDate = ParseDate(cmd.PropertyValue)),
new ComplexSet(nameof(PKM.EggMetDate), (pk, cmd) => pk.EggMetDate = ParseDate(cmd.PropertyValue)),
// Value Swap
new ComplexSet(nameof(PKM.EncryptionConstant), value => value == nameof(PKM.PID), (pk, _) => pk.EncryptionConstant = pk.PID),
new ComplexSet(nameof(PKM.PID), value => value == nameof(PKM.EncryptionConstant), (pk, _) => pk.PID = pk.EncryptionConstant),
// Value Swap
new ComplexSet(nameof(PKM.EncryptionConstant), value => value == nameof(PKM.PID), (pk, _) => pk.EncryptionConstant = pk.PID),
new ComplexSet(nameof(PKM.PID), value => value == nameof(PKM.EncryptionConstant), (pk, _) => pk.PID = pk.EncryptionConstant),
// Realign to Derived Value
new ComplexSet(nameof(PKM.Ability), value => value.StartsWith("$"), (pk, cmd) => pk.RefreshAbility(Convert.ToInt16(cmd.PropertyValue[1]) - 0x30)),
new ComplexSet(nameof(PKM.AbilityNumber), value => value.StartsWith("$"), (pk, cmd) => pk.RefreshAbility(Convert.ToInt16(cmd.PropertyValue[1]) - 0x30)),
// Realign to Derived Value
new ComplexSet(nameof(PKM.Ability), value => value.StartsWith(CONST_SPECIAL), (pk, cmd) => pk.RefreshAbility(Convert.ToInt16(cmd.PropertyValue[1]) - 0x30)),
new ComplexSet(nameof(PKM.AbilityNumber), value => value.StartsWith(CONST_SPECIAL), (pk, cmd) => pk.RefreshAbility(Convert.ToInt16(cmd.PropertyValue[1]) - 0x30)),
// Random
new ComplexSet(nameof(PKM.EncryptionConstant), value => value == CONST_RAND, (pk, _) => pk.EncryptionConstant = Util.Rand32()),
new ComplexSet(nameof(PKM.PID), value => value == CONST_RAND, (pk, _) => pk.PID = Util.Rand32()),
new ComplexSet(nameof(PKM.Gender), value => value == CONST_RAND, (pk, _) => pk.SetPIDGender(pk.Gender)),
new ComplexSet(nameof(PKM.EVs), value => value == CONST_RAND, (pk, _) => SetRandomEVs(pk)),
// Random
new ComplexSet(nameof(PKM.EncryptionConstant), value => value == CONST_RAND, (pk, _) => pk.EncryptionConstant = Util.Rand32()),
new ComplexSet(nameof(PKM.PID), value => value == CONST_RAND, (pk, _) => pk.PID = Util.Rand32()),
new ComplexSet(nameof(PKM.Gender), value => value == CONST_RAND, (pk, _) => pk.SetPIDGender(pk.Gender)),
new ComplexSet(nameof(PKM.EVs), value => value == CONST_RAND, (pk, _) => SetRandomEVs(pk)),
// Shiny
new ComplexSet(nameof(PKM.PID),
value => value.StartsWith(CONST_SHINY, true, CultureInfo.CurrentCulture),
(pk, cmd) => CommonEdits.SetShiny(pk, GetRequestedShinyState(cmd.PropertyValue))),
// Shiny
new ComplexSet(nameof(PKM.PID),
value => value.StartsWith(CONST_SHINY, true, CultureInfo.CurrentCulture),
(pk, cmd) => CommonEdits.SetShiny(pk, GetRequestedShinyState(cmd.PropertyValue))),
new ComplexSet(nameof(PKM.Species), value => value == "0", (pk, _) => Array.Clear(pk.Data, 0, pk.Data.Length)),
new ComplexSet(nameof(PKM.IsNicknamed), value => string.Equals(value, "false", StringComparison.OrdinalIgnoreCase), (pk, _) => pk.SetDefaultNickname()),
};
new ComplexSet(nameof(PKM.Species), value => value == "0", (pk, _) => Array.Clear(pk.Data, 0, pk.Data.Length)),
new ComplexSet(nameof(PKM.IsNicknamed), value => string.Equals(value, "false", StringComparison.OrdinalIgnoreCase), (pk, _) => pk.SetDefaultNickname()),
};
private static void SetRandomEVs(PKM pk)
{
Span<int> evs = stackalloc int[6];
EffortValues.SetRandom(evs, pk.Format);
pk.SetEVs(evs);
}
private static Shiny GetRequestedShinyState(string text)
{
if (text.EndsWith("0"))
return Shiny.AlwaysSquare;
if (text.EndsWith("1"))
return Shiny.AlwaysStar;
return Shiny.Random;
}
private static void SetRandomEVs(PKM pk)
{
Span<int> evs = stackalloc int[6];
EffortValues.SetRandom(evs, pk.Format);
pk.SetEVs(evs);
}
private static Shiny GetRequestedShinyState(string text) => text.Length == 0 ? Shiny.Random : GetRequestedShinyState(text[^1]);
private static Shiny GetRequestedShinyState(char last) => last switch
{
'0' => Shiny.AlwaysSquare,
'1' => Shiny.AlwaysStar,
_ => Shiny.Random,
};
}

View file

@ -1,26 +1,25 @@
using System;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <inheritdoc cref="IComplexFilter"/>
public sealed class ComplexFilter : IComplexFilter
{
/// <inheritdoc cref="IComplexFilter"/>
public sealed class ComplexFilter : IComplexFilter
private readonly string Property;
private readonly Func<PKM, StringInstruction, bool> FilterPKM;
private readonly Func<BatchInfo, StringInstruction, bool> FilterBulk;
public ComplexFilter(
string property,
Func<PKM, StringInstruction, bool> filterPkm,
Func<BatchInfo, StringInstruction, bool> filterBulk)
{
private readonly string Property;
private readonly Func<PKM, StringInstruction, bool> FilterPKM;
private readonly Func<BatchInfo, StringInstruction, bool> FilterBulk;
public ComplexFilter(
string property,
Func<PKM, StringInstruction, bool> filterPkm,
Func<BatchInfo, StringInstruction, bool> filterBulk)
{
Property = property;
FilterPKM = filterPkm;
FilterBulk = filterBulk;
}
public bool IsMatch(string prop) => prop == Property;
public bool IsFiltered(PKM pkm, StringInstruction cmd) => FilterPKM(pkm, cmd);
public bool IsFiltered(BatchInfo info, StringInstruction cmd) => FilterBulk(info, cmd);
Property = property;
FilterPKM = filterPkm;
FilterBulk = filterBulk;
}
public bool IsMatch(string prop) => prop == Property;
public bool IsFiltered(PKM pk, StringInstruction value) => FilterPKM(pk, value);
public bool IsFiltered(BatchInfo info, StringInstruction value) => FilterBulk(info, value);
}

View file

@ -1,24 +1,23 @@
using System;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <inheritdoc cref="IComplexSet"/>
public sealed class ComplexSet : IComplexSet
{
/// <inheritdoc cref="IComplexSet"/>
public sealed class ComplexSet : IComplexSet
public readonly string PropertyName;
public readonly Func<string, bool> IsValueCompatible = _ => true;
private readonly Action<PKM, StringInstruction> Action;
public ComplexSet(string propertyName, Action<PKM, StringInstruction> modify)
{
public readonly string PropertyName;
public readonly Func<string, bool> IsValueCompatible = _ => true;
private readonly Action<PKM, StringInstruction> Action;
public ComplexSet(string propertyName, Action<PKM, StringInstruction> modify)
{
PropertyName = propertyName;
Action = modify;
}
public ComplexSet(string propertyName, Func<string, bool> criteria, Action<PKM, StringInstruction> modify) : this(propertyName, modify) => IsValueCompatible = criteria;
public bool IsMatch(string name, string value) => name == PropertyName && IsValueCompatible(value);
public void Modify(PKM pk, StringInstruction instr) => Action(pk, instr);
PropertyName = propertyName;
Action = modify;
}
public ComplexSet(string propertyName, Func<string, bool> criteria, Action<PKM, StringInstruction> modify) : this(propertyName, modify) => IsValueCompatible = criteria;
public bool IsMatch(string name, string value) => name == PropertyName && IsValueCompatible(value);
public void Modify(PKM pk, StringInstruction instr) => Action(pk, instr);
}

View file

@ -1,12 +1,11 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Complex filter of data based on a string value.
/// </summary>
public interface IComplexFilter
{
/// <summary>
/// Complex filter of data based on a string value.
/// </summary>
public interface IComplexFilter
{
bool IsMatch(string prop);
bool IsFiltered(PKM pkm, StringInstruction value);
bool IsFiltered(BatchInfo info, StringInstruction value);
}
bool IsMatch(string prop);
bool IsFiltered(PKM pk, StringInstruction value);
bool IsFiltered(BatchInfo info, StringInstruction value);
}

View file

@ -1,11 +1,10 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Complex filter of data based on a string value.
/// </summary>
public interface IComplexFilterMeta
{
/// <summary>
/// Complex filter of data based on a string value.
/// </summary>
public interface IComplexFilterMeta
{
bool IsMatch(string prop);
bool IsFiltered(object cache, StringInstruction value);
}
bool IsMatch(string prop);
bool IsFiltered(object cache, StringInstruction value);
}

View file

@ -1,11 +1,10 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Complex modification of data to a string value.
/// </summary>
public interface IComplexSet
{
/// <summary>
/// Complex modification of data to a string value.
/// </summary>
public interface IComplexSet
{
bool IsMatch(string name, string value);
void Modify(PKM pk, StringInstruction instr);
}
bool IsMatch(string name, string value);
void Modify(PKM pk, StringInstruction instr);
}

View file

@ -1,22 +1,21 @@
using System;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <inheritdoc cref="IComplexFilter"/>
public sealed class MetaFilter : IComplexFilterMeta
{
/// <inheritdoc cref="IComplexFilter"/>
public sealed class MetaFilter : IComplexFilterMeta
private readonly string Property;
private readonly Func<object, StringInstruction, bool> FilterPKM;
public MetaFilter(
string property,
Func<object, StringInstruction, bool> filterPkm)
{
private readonly string Property;
private readonly Func<object, StringInstruction, bool> FilterPKM;
public MetaFilter(
string property,
Func<object, StringInstruction, bool> filterPkm)
{
Property = property;
FilterPKM = filterPkm;
}
public bool IsMatch(string prop) => prop == Property;
public bool IsFiltered(object pkm, StringInstruction cmd) => FilterPKM(pkm, cmd);
Property = property;
FilterPKM = filterPkm;
}
public bool IsMatch(string prop) => prop == Property;
public bool IsFiltered(object pk, StringInstruction value) => FilterPKM(pk, value);
}

View file

@ -1,28 +1,27 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Batch Editor Modification result for an individual <see cref="PKM"/>.
/// </summary>
public enum ModifyResult
{
/// <summary>
/// Batch Editor Modification result for an individual <see cref="PKM"/>.
/// The <see cref="PKM"/> has invalid data and is not a suitable candidate for modification.
/// </summary>
public enum ModifyResult
{
/// <summary>
/// The <see cref="PKM"/> has invalid data and is not a suitable candidate for modification.
/// </summary>
Invalid,
Invalid,
/// <summary>
/// An error was occurred while iterating modifications for this <see cref="PKM"/>.
/// </summary>
Error,
/// <summary>
/// An error was occurred while iterating modifications for this <see cref="PKM"/>.
/// </summary>
Error,
/// <summary>
/// The <see cref="PKM"/> was skipped due to a matching Filter.
/// </summary>
Filtered,
/// <summary>
/// The <see cref="PKM"/> was skipped due to a matching Filter.
/// </summary>
Filtered,
/// <summary>
/// The <see cref="PKM"/> was modified.
/// </summary>
Modified,
}
/// <summary>
/// The <see cref="PKM"/> was modified.
/// </summary>
Modified,
}

View file

@ -2,123 +2,122 @@
using System.Collections.Generic;
using System.Diagnostics;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Batch Editing instruction
/// </summary>
/// <remarks>
/// Can be a filter (skip), or a modification instruction (modify)
/// </remarks>
/// <see cref="Exclude"/>
/// <see cref="Require"/>
/// <see cref="Apply"/>
public sealed class StringInstruction
{
public string PropertyName { get; }
public string PropertyValue { get; private set; }
/// <summary> True if ==, false if != </summary>
public bool Evaluator { get; private init; }
public StringInstruction(string name, string value)
{
PropertyName = name;
PropertyValue = value;
}
public void SetScreenedValue(string[] arr)
{
int index = Array.IndexOf(arr, PropertyValue);
PropertyValue = index > -1 ? index.ToString() : PropertyValue;
}
public static readonly IReadOnlyList<char> Prefixes = new[] { Apply, Require, Exclude };
private const char Exclude = '!';
private const char Require = '=';
private const char Apply = '.';
private const char SplitRange = ',';
/// <summary>
/// Batch Editing instruction
/// Character which divides a property and a value.
/// </summary>
/// <remarks>
/// Can be a filter (skip), or a modification instruction (modify)
/// Example:
/// =Species=1
/// The second = is the split.
/// </remarks>
/// <see cref="Exclude"/>
/// <see cref="Require"/>
/// <see cref="Apply"/>
public sealed class StringInstruction
public const char SplitInstruction = '=';
// Extra Functionality
private int RandomMinimum, RandomMaximum;
public bool Random { get; private set; }
public int RandomValue => Util.Rand.Next(RandomMinimum, RandomMaximum + 1);
public void SetRandRange(string pv)
{
public string PropertyName { get; }
public string PropertyValue { get; private set; }
string str = pv[1..];
var split = str.Split(SplitRange);
int.TryParse(split[0], out RandomMinimum);
int.TryParse(split[1], out RandomMaximum);
/// <summary> True if ==, false if != </summary>
public bool Evaluator { get; private init; }
public StringInstruction(string name, string value)
if (RandomMinimum == RandomMaximum)
{
PropertyName = name;
PropertyValue = value;
PropertyValue = RandomMinimum.ToString();
Debug.WriteLine($"{PropertyName} randomization range Min/Max same?");
}
public void SetScreenedValue(string[] arr)
else
{
int index = Array.IndexOf(arr, PropertyValue);
PropertyValue = index > -1 ? index.ToString() : PropertyValue;
Random = true;
}
}
public static readonly IReadOnlyList<char> Prefixes = new[] { Apply, Require, Exclude };
private const char Exclude = '!';
private const char Require = '=';
private const char Apply = '.';
private const char SplitRange = ',';
/// <summary>
/// Character which divides a property and a value.
/// </summary>
/// <remarks>
/// Example:
/// =Species=1
/// The second = is the split.
/// </remarks>
public const char SplitInstruction = '=';
// Extra Functionality
private int RandomMinimum, RandomMaximum;
public bool Random { get; private set; }
public int RandomValue => Util.Rand.Next(RandomMinimum, RandomMaximum + 1);
public void SetRandRange(string pv)
public static IEnumerable<StringInstruction> GetFilters(IEnumerable<string> lines)
{
foreach (var line in lines)
{
string str = pv[1..];
var split = str.Split(SplitRange);
int.TryParse(split[0], out RandomMinimum);
int.TryParse(split[1], out RandomMaximum);
if (line.Length is 0 || line[0] is not (Exclude or Require))
continue;
if (RandomMinimum == RandomMaximum)
{
PropertyValue = RandomMinimum.ToString();
Debug.WriteLine($"{PropertyName} randomization range Min/Max same?");
}
else
{
Random = true;
}
const int start = 1;
var splitIndex = line.IndexOf(SplitInstruction, start);
if (splitIndex == -1)
continue;
var noExtra = line.IndexOf(SplitInstruction, splitIndex + 1);
if (noExtra != -1)
continue;
var name = line.AsSpan(start, splitIndex - start);
if (name.IsWhiteSpace())
continue;
bool eval = line[0] == Require;
var value = line[(splitIndex + 1)..];
yield return new StringInstruction(name.ToString(), value) { Evaluator = eval };
}
}
public static IEnumerable<StringInstruction> GetFilters(IEnumerable<string> lines)
public static IEnumerable<StringInstruction> GetInstructions(IEnumerable<string> lines)
{
foreach (var line in lines)
{
foreach (var line in lines)
{
if (line.Length is 0 || line[0] is not (Exclude or Require))
continue;
if (line.Length is 0 || line[0] is not Apply)
continue;
const int start = 1;
var splitIndex = line.IndexOf(SplitInstruction, start);
if (splitIndex == -1)
continue;
var noExtra = line.IndexOf(SplitInstruction, splitIndex + 1);
if (noExtra != -1)
continue;
const int start = 1;
var splitIndex = line.IndexOf(SplitInstruction, start);
if (splitIndex == -1)
continue;
var noExtra = line.IndexOf(SplitInstruction, splitIndex + 1);
if (noExtra != -1)
continue;
var name = line.AsSpan(start, splitIndex - start);
if (name.IsWhiteSpace())
continue;
var name = line.AsSpan(start, splitIndex - start);
if (name.IsWhiteSpace())
continue;
bool eval = line[0] == Require;
var value = line[(splitIndex + 1)..];
yield return new StringInstruction(name.ToString(), value) { Evaluator = eval };
}
}
public static IEnumerable<StringInstruction> GetInstructions(IEnumerable<string> lines)
{
foreach (var line in lines)
{
if (line.Length is 0 || line[0] is not Apply)
continue;
const int start = 1;
var splitIndex = line.IndexOf(SplitInstruction, start);
if (splitIndex == -1)
continue;
var noExtra = line.IndexOf(SplitInstruction, splitIndex + 1);
if (noExtra != -1)
continue;
var name = line.AsSpan(start, splitIndex - start);
if (name.IsWhiteSpace())
continue;
var value = line[(splitIndex + 1)..];
yield return new StringInstruction(name.ToString(), value);
}
var value = line[(splitIndex + 1)..];
yield return new StringInstruction(name.ToString(), value);
}
}
}

View file

@ -1,38 +1,38 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Processes input of strings into a list of valid Filters and Instructions.
/// </summary>
public sealed class StringInstructionSet
{
/// <summary>
/// Processes input of strings into a list of valid Filters and Instructions.
/// </summary>
public sealed class StringInstructionSet
public readonly IReadOnlyList<StringInstruction> Filters;
public readonly IReadOnlyList<StringInstruction> Instructions;
private const string SetSeparator = ";";
public StringInstructionSet(IReadOnlyList<StringInstruction> filters, IReadOnlyList<StringInstruction> instructions)
{
public readonly IReadOnlyList<StringInstruction> Filters;
public readonly IReadOnlyList<StringInstruction> Instructions;
Filters = filters;
Instructions = instructions;
}
private const string SetSeparator = ";";
public StringInstructionSet(ICollection<string> set)
{
Filters = StringInstruction.GetFilters(set).ToList();
Instructions = StringInstruction.GetInstructions(set).ToList();
}
public StringInstructionSet(IReadOnlyList<StringInstruction> filters, IReadOnlyList<StringInstruction> instructions)
public static IEnumerable<StringInstructionSet> GetBatchSets(IList<string> lines)
{
int start = 0;
while (start < lines.Count)
{
Filters = filters;
Instructions = instructions;
}
public StringInstructionSet(ICollection<string> set)
{
Filters = StringInstruction.GetFilters(set).ToList();
Instructions = StringInstruction.GetInstructions(set).ToList();
}
public static IEnumerable<StringInstructionSet> GetBatchSets(IList<string> lines)
{
int start = 0;
while (start < lines.Count)
{
var list = lines.Skip(start).TakeWhile(_ => !lines[start++].StartsWith(SetSeparator)).ToList();
yield return new StringInstructionSet(list);
}
var list = lines.Skip(start).TakeWhile(_ => !lines[start++].StartsWith(SetSeparator, StringComparison.Ordinal)).ToList();
yield return new StringInstructionSet(list);
}
}
}

View file

@ -1,119 +1,117 @@
using System;
using System.Globalization;
using System;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Modifications using <see cref="BatchInfo"/> legality.
/// </summary>
internal static class BatchModifications
{
/// <summary>
/// Modifications using <see cref="BatchInfo"/> legality.
/// </summary>
internal static class BatchModifications
private static bool IsAll(string p) => p.EndsWith("All", StringComparison.OrdinalIgnoreCase);
private static bool IsNone(string p) => p.EndsWith("None", StringComparison.OrdinalIgnoreCase);
public static ModifyResult SetSuggestedRelearnData(BatchInfo info, string propValue)
{
private static bool IsAll(string p) => p.EndsWith("All", true, CultureInfo.CurrentCulture);
private static bool IsNone(string p) => p.EndsWith("None", true, CultureInfo.CurrentCulture);
public static ModifyResult SetSuggestedRelearnData(BatchInfo info, string propValue)
var pk = info.Entity;
if (pk is ITechRecord8 t)
{
var pk = info.Entity;
if (pk is ITechRecord8 t)
{
t.ClearRecordFlags();
if (IsAll(propValue))
t.SetRecordFlags(); // all
else if (!IsNone(propValue))
t.SetRecordFlags(pk.Moves); // whatever fit the current moves
}
pk.SetRelearnMoves(info.SuggestedRelearn);
return ModifyResult.Modified;
}
public static ModifyResult SetSuggestedMasteryData(BatchInfo info, string propValue)
{
var pk = info.Entity;
if (pk is not IMoveShop8Mastery t)
return ModifyResult.Invalid;
t.ClearMoveShopFlags();
if (IsNone(propValue))
return ModifyResult.Modified;
var e = info.Legality.EncounterMatch;
if (e is IMasteryInitialMoveShop8 enc)
enc.SetInitialMastery(pk);
t.ClearRecordFlags();
if (IsAll(propValue))
t.SetMoveShopFlagsAll(pk);
else
t.SetMoveShopFlags(pk);
t.SetRecordFlags(); // all
else if (!IsNone(propValue))
t.SetRecordFlags(pk.Moves); // whatever fit the current moves
}
pk.SetRelearnMoves(info.SuggestedRelearn);
return ModifyResult.Modified;
}
public static ModifyResult SetSuggestedMasteryData(BatchInfo info, string propValue)
{
var pk = info.Entity;
if (pk is not IMoveShop8Mastery t)
return ModifyResult.Invalid;
t.ClearMoveShopFlags();
if (IsNone(propValue))
return ModifyResult.Modified;
}
public static ModifyResult SetSuggestedRibbons(BatchInfo info, string value)
{
var pk = info.Entity;
if (IsNone(value))
RibbonApplicator.RemoveAllValidRibbons(pk);
else // All
RibbonApplicator.SetAllValidRibbons(pk);
return ModifyResult.Modified;
}
var e = info.Legality.EncounterMatch;
if (e is IMasteryInitialMoveShop8 enc)
enc.SetInitialMastery(pk);
if (IsAll(propValue))
t.SetMoveShopFlagsAll(pk);
else
t.SetMoveShopFlags(pk);
return ModifyResult.Modified;
}
public static ModifyResult SetSuggestedMetData(BatchInfo info)
{
var pk = info.Entity;
var encounter = EncounterSuggestion.GetSuggestedMetInfo(pk);
if (encounter == null)
return ModifyResult.Error;
public static ModifyResult SetSuggestedRibbons(BatchInfo info, string value)
{
var pk = info.Entity;
if (IsNone(value))
RibbonApplicator.RemoveAllValidRibbons(pk);
else // All
RibbonApplicator.SetAllValidRibbons(pk);
return ModifyResult.Modified;
}
int level = encounter.LevelMin;
int location = encounter.Location;
int minimumLevel = EncounterSuggestion.GetLowestLevel(pk, encounter.LevelMin);
public static ModifyResult SetSuggestedMetData(BatchInfo info)
{
var pk = info.Entity;
var encounter = EncounterSuggestion.GetSuggestedMetInfo(pk);
if (encounter == null)
return ModifyResult.Error;
pk.Met_Level = level;
pk.Met_Location = location;
pk.CurrentLevel = Math.Max(minimumLevel, level);
int level = encounter.LevelMin;
int location = encounter.Location;
int minimumLevel = EncounterSuggestion.GetLowestLevel(pk, encounter.LevelMin);
return ModifyResult.Modified;
}
pk.Met_Level = level;
pk.Met_Location = location;
pk.CurrentLevel = Math.Max(minimumLevel, level);
public static ModifyResult SetMinimumCurrentLevel(BatchInfo info)
{
var result = EncounterSuggestion.IterateMinimumCurrentLevel(info.Entity, info.Legal);
return result ? ModifyResult.Modified : ModifyResult.Filtered;
}
return ModifyResult.Modified;
}
/// <summary>
/// Sets the provided moves in a random order.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="moves">Moves to apply.</param>
public static ModifyResult SetMoves(PKM pk, ReadOnlySpan<int> moves)
{
pk.SetMoves(moves);
pk.HealPP();
return ModifyResult.Modified;
}
public static ModifyResult SetMinimumCurrentLevel(BatchInfo info)
{
var result = EncounterSuggestion.IterateMinimumCurrentLevel(info.Entity, info.Legal);
return result ? ModifyResult.Modified : ModifyResult.Filtered;
}
public static ModifyResult SetEVs(PKM pk)
{
Span<int> evs = stackalloc int[6];
EffortValues.SetMax(evs, pk);
pk.SetEVs(evs);
return ModifyResult.Modified;
}
/// <summary>
/// Sets the provided moves in a random order.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="moves">Moves to apply.</param>
public static ModifyResult SetMoves(PKM pk, ReadOnlySpan<int> moves)
{
pk.SetMoves(moves);
pk.HealPP();
return ModifyResult.Modified;
}
/// <summary>
/// Sets the contests stats as requested.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="la">Legality Information matched to.</param>
/// <param name="option">Option to apply with</param>
public static ModifyResult SetContestStats(PKM pk, LegalityAnalysis la, string option)
{
if (option.Length != 0 && option[BatchEditing.CONST_SUGGEST.Length..] is not "0")
pk.SetMaxContestStats(la.EncounterMatch, la.Info.EvoChainsAllGens);
else
pk.SetSuggestedContestStats(la.EncounterMatch, la.Info.EvoChainsAllGens);
return ModifyResult.Modified;
}
public static ModifyResult SetEVs(PKM pk)
{
Span<int> evs = stackalloc int[6];
EffortValues.SetMax(evs, pk);
pk.SetEVs(evs);
return ModifyResult.Modified;
}
/// <summary>
/// Sets the contests stats as requested.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="la">Legality Information matched to.</param>
/// <param name="option">Option to apply with</param>
public static ModifyResult SetContestStats(PKM pk, LegalityAnalysis la, string option)
{
if (option.Length != 0 && option[BatchEditing.CONST_SUGGEST.Length..] is not "0")
pk.SetMaxContestStats(la.EncounterMatch, la.Info.EvoChainsAllGens);
else
pk.SetSuggestedContestStats(la.EncounterMatch, la.Info.EvoChainsAllGens);
return ModifyResult.Modified;
}
}

View file

@ -1,38 +1,37 @@
using System;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <inheritdoc cref="ISuggestModification"/>
public sealed class ComplexSuggestion : ISuggestModification
{
/// <inheritdoc cref="ISuggestModification"/>
public sealed class ComplexSuggestion : ISuggestModification
public readonly string Keyword;
public readonly Func<PKM, bool> Criteria = _ => true;
public readonly Func<string, string, BatchInfo, ModifyResult> Action;
public ComplexSuggestion(
string keyword,
Func<PKM, bool> criteria,
Func<string, string, BatchInfo, ModifyResult> action) : this(keyword, action)
{
public readonly string Keyword;
public readonly Func<PKM, bool> Criteria = _ => true;
public readonly Func<string, string, BatchInfo, ModifyResult> Action;
Criteria = criteria;
}
public ComplexSuggestion(
string keyword,
Func<PKM, bool> criteria,
Func<string, string, BatchInfo, ModifyResult> action) : this(keyword, action)
{
Criteria = criteria;
}
public ComplexSuggestion(
string keyword,
Func<string, string, BatchInfo, ModifyResult> action)
{
Keyword = keyword;
Action = action;
}
public ComplexSuggestion(
string keyword,
Func<string, string, BatchInfo, ModifyResult> action)
{
Keyword = keyword;
Action = action;
}
public bool IsMatch(string name, string value, BatchInfo info)
{
return name == Keyword && Criteria(info.Entity);
}
public bool IsMatch(string name, string value, BatchInfo info)
{
return name == Keyword && Criteria(info.Entity);
}
public ModifyResult Modify(string name, string value, BatchInfo info)
{
return Action(name, value, info);
}
public ModifyResult Modify(string name, string value, BatchInfo info)
{
return Action(name, value, info);
}
}

View file

@ -1,11 +1,10 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Modifies a property to have a "correct" value based on derived legality.
/// </summary>
public interface ISuggestModification
{
/// <summary>
/// Modifies a property to have a "correct" value based on derived legality.
/// </summary>
public interface ISuggestModification
{
public bool IsMatch(string name, string value, BatchInfo info);
public ModifyResult Modify(string name, string value, BatchInfo info);
}
public bool IsMatch(string name, string value, BatchInfo info);
public ModifyResult Modify(string name, string value, BatchInfo info);
}

View file

@ -1,40 +1,39 @@
using System;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <inheritdoc cref="ISuggestModification"/>
/// <typeparam name="T">Specific (or not) type</typeparam>
public sealed class TypeSuggestion<T> : ISuggestModification
{
/// <inheritdoc cref="ISuggestModification"/>
/// <typeparam name="T">Specific (or not) type</typeparam>
public sealed class TypeSuggestion<T> : ISuggestModification
public readonly string Keyword;
public readonly Action<T, string> Action;
public readonly Func<T, bool> Criteria = _ => true;
public TypeSuggestion(string keyword, Action<T> action)
{
public readonly string Keyword;
public readonly Action<T, string> Action;
public readonly Func<T, bool> Criteria = _ => true;
Keyword = keyword;
Action = (pk, _) => action(pk);
}
public TypeSuggestion(string keyword, Action<T> action)
{
Keyword = keyword;
Action = (pkm, _) => action(pkm);
}
public TypeSuggestion(string keyword, Func<T, bool> criteria, Action<T> action) : this(keyword, action)
{
Criteria = criteria;
}
public TypeSuggestion(string keyword, Func<T, bool> criteria, Action<T> action) : this(keyword, action)
{
Criteria = criteria;
}
public bool IsMatch(string name, string value, BatchInfo info)
{
return name == Keyword && info.Entity is T;
}
public bool IsMatch(string name, string value, BatchInfo info)
{
return name == Keyword && info.Entity is T;
}
public ModifyResult Modify(string name, string value, BatchInfo info)
{
var pk = info.Entity;
if (pk is not T x)
return ModifyResult.Invalid;
if (!Criteria(x))
return ModifyResult.Invalid;
Action(x, value);
return ModifyResult.Modified;
}
public ModifyResult Modify(string name, string value, BatchInfo info)
{
var pk = info.Entity;
if (pk is not T x)
return ModifyResult.Invalid;
if (!Criteria(x))
return ModifyResult.Invalid;
Action(x, value);
return ModifyResult.Modified;
}
}

View file

@ -1,446 +1,444 @@
using System;
using System;
using System.Linq;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Contains extension logic for modifying <see cref="PKM"/> data.
/// </summary>
public static class CommonEdits
{
/// <summary>
/// Contains extension logic for modifying <see cref="PKM"/> data.
/// Setting which enables/disables automatic manipulation of <see cref="PKM.MarkValue"/> when importing from a <see cref="IBattleTemplate"/>.
/// </summary>
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.
/// </summary>
public static bool ShowdownSetBehaviorNature { get; set; }
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to the provided value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="nick"><see cref="PKM.Nickname"/> to set. If no nickname is provided, the <see cref="PKM.Nickname"/> is set to the default value for its current language and format.</param>
public static void SetNickname(this PKM pk, string nick)
{
/// <summary>
/// Setting which enables/disables automatic manipulation of <see cref="PKM.MarkValue"/> when importing from a <see cref="IBattleTemplate"/>.
/// </summary>
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.
/// </summary>
public static bool ShowdownSetBehaviorNature { get; set; }
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to the provided value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="nick"><see cref="PKM.Nickname"/> to set. If no nickname is provided, the <see cref="PKM.Nickname"/> is set to the default value for its current language and format.</param>
public static void SetNickname(this PKM pk, string nick)
if (string.IsNullOrWhiteSpace(nick))
{
if (string.IsNullOrWhiteSpace(nick))
{
pk.ClearNickname();
return;
}
pk.IsNicknamed = true;
pk.Nickname = nick;
}
/// <summary>
/// Clears the <see cref="PKM.Nickname"/> to the default value.
/// </summary>
/// <param name="pk"></param>
public static string ClearNickname(this PKM pk)
{
pk.IsNicknamed = false;
string nick = SpeciesName.GetSpeciesNameGeneration(pk.Species, pk.Language, pk.Format);
pk.Nickname = nick;
if (pk is GBPKM pk12)
pk12.SetNotNicknamed();
return nick;
}
/// <summary>
/// Sets the <see cref="PKM.Ability"/> value by sanity checking the provided <see cref="PKM.Ability"/> against the possible pool of abilities.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="abil">Desired <see cref="PKM.Ability"/> to set.</param>
public static void SetAbility(this PKM pk, int abil)
{
if (abil < 0)
return;
var index = pk.PersonalInfo.GetAbilityIndex(abil);
index = Math.Max(0, index);
pk.SetAbilityIndex(index);
}
/// <summary>
/// Sets the <see cref="PKM.Ability"/> value based on the provided ability index (0-2)
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Desired <see cref="PKM.AbilityNumber"/> (shifted by 1) to set.</param>
public static void SetAbilityIndex(this PKM pk, int index)
{
if (pk is PK5 pk5 && index == 2)
pk5.HiddenAbility = true;
else if (pk.Format <= 5)
pk.PID = EntityPID.GetRandomPID(Util.Rand, pk.Species, pk.Gender, pk.Version, pk.Nature, pk.Form, (uint)(index * 0x10001));
pk.RefreshAbility(index);
}
/// <summary>
/// Sets a Random <see cref="PKM.EncryptionConstant"/> value. The <see cref="PKM.EncryptionConstant"/> is not updated if the value should match the <see cref="PKM.PID"/> instead.
/// </summary>
/// <remarks>Accounts for Wurmple evolutions.</remarks>
/// <param name="pk">Pokémon to modify.</param>
public static void SetRandomEC(this PKM pk)
{
int gen = pk.Generation;
if (gen is 3 or 4 or 5)
{
pk.EncryptionConstant = pk.PID;
return;
}
int wIndex = WurmpleUtil.GetWurmpleEvoGroup(pk.Species);
if (wIndex != -1)
{
pk.EncryptionConstant = WurmpleUtil.GetWurmpleEncryptionConstant(wIndex);
return;
}
pk.EncryptionConstant = Util.Rand32();
}
/// <summary>
/// Sets the <see cref="PKM.IsShiny"/> derived value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="shiny">Desired <see cref="PKM.IsShiny"/> state to set.</param>
/// <returns></returns>
public static bool SetIsShiny(this PKM pk, bool shiny) => shiny ? SetShiny(pk) : pk.SetUnshiny();
/// <summary>
/// Makes a <see cref="PKM"/> shiny.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="type">Shiny type to force. Only use Always* or Random</param>
/// <returns>Returns true if the <see cref="PKM"/> data was modified.</returns>
public static bool SetShiny(PKM pk, Shiny type = Shiny.Random)
{
if (pk.IsShiny && type.IsValid(pk))
return false;
if (type == Shiny.Random || pk.FatefulEncounter || pk.Version == (int)GameVersion.GO || pk.Format <= 2)
{
pk.SetShiny();
return true;
}
do { pk.SetShiny(); }
while (!type.IsValid(pk));
return true;
}
/// <summary>
/// Makes a <see cref="PKM"/> not-shiny.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <returns>Returns true if the <see cref="PKM"/> data was modified.</returns>
public static bool SetUnshiny(this PKM pk)
{
if (!pk.IsShiny)
return false;
pk.SetPIDGender(pk.Gender);
return true;
}
/// <summary>
/// Sets the <see cref="PKM.Nature"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Nature"/> value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="nature">Desired <see cref="PKM.Nature"/> value to set.</param>
public static void SetNature(this PKM pk, int nature)
{
var value = Math.Min((int)Nature.Quirky, Math.Max((int)Nature.Hardy, nature));
var format = pk.Format;
if (format >= 8)
pk.StatNature = value;
else if (format is 3 or 4)
pk.SetPIDNature(value);
else
pk.Nature = value;
}
/// <summary>
/// Copies <see cref="IBattleTemplate"/> details to the <see cref="PKM"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="Set"><see cref="IBattleTemplate"/> details to copy from.</param>
public static void ApplySetDetails(this PKM pk, IBattleTemplate Set)
{
pk.Species = Math.Min(pk.MaxSpeciesID, Set.Species);
pk.Form = Set.Form;
pk.SetMoves(Set.Moves, true);
pk.ApplyHeldItem(Set.HeldItem, Set.Format);
pk.CurrentLevel = Set.Level;
pk.CurrentFriendship = Set.Friendship;
pk.SetIVs(Set.IVs);
if (pk is GBPKM gb)
{
// In Generation 1/2 Format sets, when IVs are not specified with a Hidden Power set, we might not have the hidden power type.
// Under this scenario, just force the Hidden Power type.
if (Set.Moves.Contains(237) && pk.HPType != Set.HiddenPowerType && Set.IVs.Any(z => z >= 30))
pk.SetHiddenPower(Set.HiddenPowerType);
// In Generation 1/2 Format sets, when EVs are not specified at all, it implies maximum EVs instead!
// Under this scenario, just apply maximum EVs (65535).
if (Set.EVs.All(z => z == 0))
gb.MaxEVs();
else
pk.EVs = Set.EVs;
}
else
{
pk.EVs = Set.EVs;
}
// IVs have no side effects such as hidden power type in gen 8
// therefore all specified IVs are deliberate and should not be Hyper Trained for pokemon met in gen 8
if (!pk.Gen8)
pk.SetSuggestedHyperTrainingData(Set.IVs);
if (ShowdownSetIVMarkings)
pk.SetMarkings();
pk.SetNickname(Set.Nickname);
pk.SetSaneGender(Set.Gender);
if (Legal.IsPPUpAvailable(pk))
pk.SetMaximumPPUps(Set.Moves);
if (pk.Format >= 3)
{
pk.SetAbility(Set.Ability);
pk.SetNature(Set.Nature);
}
pk.SetIsShiny(Set.Shiny);
pk.SetRandomEC();
if (pk is IAwakened a)
{
a.SetSuggestedAwakenedValues(pk);
if (pk is PB7 b)
{
for (int i = 0; i < 6; i++)
b.SetEV(i, 0);
b.ResetCalculatedValues();
}
}
if (pk is IGanbaru g)
g.SetSuggestedGanbaruValues(pk);
if (pk is IGigantamax c)
c.CanGigantamax = Set.CanGigantamax;
if (pk is IDynamaxLevel d)
d.DynamaxLevel = d.GetSuggestedDynamaxLevel(pk);
if (pk is ITechRecord8 t)
{
t.ClearRecordFlags();
t.SetRecordFlags(Set.Moves);
}
if (pk is IMoveShop8Mastery s)
s.SetMoveShopFlags(Set.Moves, pk);
if (ShowdownSetBehaviorNature && pk.Format >= 8)
pk.Nature = pk.StatNature;
var legal = new LegalityAnalysis(pk);
if (legal.Parsed && legal.Info.Relearn.Any(z => !z.Valid))
pk.SetRelearnMoves(legal.GetSuggestedRelearnMoves());
pk.ResetPartyStats();
pk.RefreshChecksum();
}
/// <summary>
/// Sets the <see cref="PKM.HeldItem"/> value depending on the current format and the provided item index &amp; format.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="item">Held Item to apply</param>
/// <param name="format">Format required for importing</param>
public static void ApplyHeldItem(this PKM pk, int item, int format)
{
item = ItemConverter.GetItemForFormat(item, format, pk.Format);
pk.HeldItem = ((uint)item > pk.MaxItemID) ? 0 : item;
}
/// <summary>
/// Sets one of the <see cref="PKM.EVs"/> based on its index within the array.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to set to</param>
/// <param name="value">Value to set</param>
public static int SetEV(this PKM pk, int index, int value) => index switch
{
0 => pk.EV_HP = value,
1 => pk.EV_ATK = value,
2 => pk.EV_DEF = value,
3 => pk.EV_SPE = value,
4 => pk.EV_SPA = value,
5 => pk.EV_SPD = value,
_ => throw new ArgumentOutOfRangeException(nameof(index)),
};
/// <summary>
/// Sets one of the <see cref="PKM.IVs"/> based on its index within the array.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to set to</param>
/// <param name="value">Value to set</param>
public static int SetIV(this PKM pk, int index, int value) => index switch
{
0 => pk.IV_HP = value,
1 => pk.IV_ATK = value,
2 => pk.IV_DEF = value,
3 => pk.IV_SPE = value,
4 => pk.IV_SPA = value,
5 => pk.IV_SPD = value,
_ => throw new ArgumentOutOfRangeException(nameof(index)),
};
/// <summary>
/// Fetches the highest value the provided <see cref="PKM.EVs"/> index can be while considering others.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to fetch for</param>
/// <returns>Highest value the value can be.</returns>
public static int GetMaximumEV(this PKM pk, int index)
{
if (pk.Format < 3)
return ushort.MaxValue;
var sum = pk.EVTotal - pk.GetEV(index);
int remaining = 510 - sum;
return Math.Min(Math.Max(remaining, 0), 252);
}
/// <summary>
/// Fetches the highest value the provided <see cref="PKM.IVs"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to fetch for</param>
/// <param name="allow30">Causes the returned value to be dropped down -1 if the value is already at a maximum.</param>
/// <returns>Highest value the value can be.</returns>
public static int GetMaximumIV(this PKM pk, int index, bool allow30 = false)
{
if (pk.GetIV(index) == pk.MaxIV && allow30)
return pk.MaxIV - 1;
return pk.MaxIV;
}
/// <summary>
/// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
/// <param name="reHatch">Re-hatch already hatched <see cref="PKM"/> inputs</param>
public static void ForceHatchPKM(this PKM pk, bool reHatch = false)
{
if (!pk.IsEgg && !reHatch)
return;
pk.IsEgg = false;
pk.ClearNickname();
pk.CurrentFriendship = pk.PersonalInfo.BaseFriendship;
if (pk.IsTradedEgg)
pk.Egg_Location = pk.Met_Location;
var loc = EncounterSuggestion.GetSuggestedEggMetLocation(pk);
if (loc >= 0)
pk.Met_Location = loc;
pk.MetDate = DateTime.Today;
if (pk.Gen6)
pk.SetHatchMemory6();
return;
}
pk.IsNicknamed = true;
pk.Nickname = nick;
}
/// <summary>
/// Clears the <see cref="PKM.Nickname"/> to the default value.
/// </summary>
/// <param name="pk"></param>
public static string ClearNickname(this PKM pk)
{
pk.IsNicknamed = false;
string nick = SpeciesName.GetSpeciesNameGeneration(pk.Species, pk.Language, pk.Format);
pk.Nickname = nick;
if (pk is GBPKM pk12)
pk12.SetNotNicknamed();
return nick;
}
/// <summary>
/// Sets the <see cref="PKM.Ability"/> value by sanity checking the provided <see cref="PKM.Ability"/> against the possible pool of abilities.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="abil">Desired <see cref="PKM.Ability"/> to set.</param>
public static void SetAbility(this PKM pk, int abil)
{
if (abil < 0)
return;
var index = pk.PersonalInfo.GetAbilityIndex(abil);
index = Math.Max(0, index);
pk.SetAbilityIndex(index);
}
/// <summary>
/// Sets the <see cref="PKM.Ability"/> value based on the provided ability index (0-2)
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Desired <see cref="PKM.AbilityNumber"/> (shifted by 1) to set.</param>
public static void SetAbilityIndex(this PKM pk, int index)
{
if (pk is PK5 pk5 && index == 2)
pk5.HiddenAbility = true;
else if (pk.Format <= 5)
pk.PID = EntityPID.GetRandomPID(Util.Rand, pk.Species, pk.Gender, pk.Version, pk.Nature, pk.Form, (uint)(index * 0x10001));
pk.RefreshAbility(index);
}
/// <summary>
/// Sets a Random <see cref="PKM.EncryptionConstant"/> value. The <see cref="PKM.EncryptionConstant"/> is not updated if the value should match the <see cref="PKM.PID"/> instead.
/// </summary>
/// <remarks>Accounts for Wurmple evolutions.</remarks>
/// <param name="pk">Pokémon to modify.</param>
public static void SetRandomEC(this PKM pk)
{
int gen = pk.Generation;
if (gen is 3 or 4 or 5)
{
pk.EncryptionConstant = pk.PID;
return;
}
/// <summary>
/// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
/// <param name="origin">Game the egg originated from</param>
/// <param name="dest">Game the egg is currently present on</param>
public static void SetEggMetData(this PKM pk, GameVersion origin, GameVersion dest)
int wIndex = WurmpleUtil.GetWurmpleEvoGroup(pk.Species);
if (wIndex != -1)
{
bool traded = origin != dest;
var today = pk.MetDate = DateTime.Today;
pk.Egg_Location = EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(pk.Generation, origin, traded);
pk.EggMetDate = today;
pk.EncryptionConstant = WurmpleUtil.GetWurmpleEncryptionConstant(wIndex);
return;
}
pk.EncryptionConstant = Util.Rand32();
}
/// <summary>
/// Sets the <see cref="PKM.IsShiny"/> derived value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="shiny">Desired <see cref="PKM.IsShiny"/> state to set.</param>
public static bool SetIsShiny(this PKM pk, bool shiny) => shiny ? SetShiny(pk) : pk.SetUnshiny();
/// <summary>
/// Makes a <see cref="PKM"/> shiny.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="type">Shiny type to force. Only use Always* or Random</param>
/// <returns>Returns true if the <see cref="PKM"/> data was modified.</returns>
public static bool SetShiny(PKM pk, Shiny type = Shiny.Random)
{
if (pk.IsShiny && type.IsValid(pk))
return false;
if (type == Shiny.Random || pk.FatefulEncounter || pk.Version == (int)GameVersion.GO || pk.Format <= 2)
{
pk.SetShiny();
return true;
}
/// <summary>
/// Maximizes the <see cref="PKM.CurrentFriendship"/>. If the <see cref="PKM.IsEgg"/>, the hatch counter is set to 1.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
public static void MaximizeFriendship(this PKM pk)
do { pk.SetShiny(); }
while (!type.IsValid(pk));
return true;
}
/// <summary>
/// Makes a <see cref="PKM"/> not-shiny.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <returns>Returns true if the <see cref="PKM"/> data was modified.</returns>
public static bool SetUnshiny(this PKM pk)
{
if (!pk.IsShiny)
return false;
pk.SetPIDGender(pk.Gender);
return true;
}
/// <summary>
/// Sets the <see cref="PKM.Nature"/> value, with special consideration for the <see cref="PKM.Format"/> values which derive the <see cref="PKM.Nature"/> value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="nature">Desired <see cref="PKM.Nature"/> value to set.</param>
public static void SetNature(this PKM pk, int nature)
{
var value = Math.Min((int)Nature.Quirky, Math.Max((int)Nature.Hardy, nature));
var format = pk.Format;
if (format >= 8)
pk.StatNature = value;
else if (format is 3 or 4)
pk.SetPIDNature(value);
else
pk.Nature = value;
}
/// <summary>
/// Copies <see cref="IBattleTemplate"/> details to the <see cref="PKM"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="Set"><see cref="IBattleTemplate"/> details to copy from.</param>
public static void ApplySetDetails(this PKM pk, IBattleTemplate Set)
{
pk.Species = Math.Min(pk.MaxSpeciesID, Set.Species);
pk.Form = Set.Form;
pk.SetMoves(Set.Moves, true);
pk.ApplyHeldItem(Set.HeldItem, Set.Format);
pk.CurrentLevel = Set.Level;
pk.CurrentFriendship = Set.Friendship;
pk.SetIVs(Set.IVs);
if (pk is GBPKM gb)
{
if (pk.IsEgg)
pk.OT_Friendship = 1;
// In Generation 1/2 Format sets, when IVs are not specified with a Hidden Power set, we might not have the hidden power type.
// Under this scenario, just force the Hidden Power type.
if (Set.Moves.Contains(237) && pk.HPType != Set.HiddenPowerType && Set.IVs.Any(z => z >= 30))
pk.SetHiddenPower(Set.HiddenPowerType);
// In Generation 1/2 Format sets, when EVs are not specified at all, it implies maximum EVs instead!
// Under this scenario, just apply maximum EVs (65535).
if (Set.EVs.All(z => z == 0))
gb.MaxEVs();
else
pk.CurrentFriendship = byte.MaxValue;
if (pk is ICombatPower pb)
pb.ResetCP();
pk.EVs = Set.EVs;
}
/// <summary>
/// Maximizes the <see cref="PKM.CurrentLevel"/>. If the <see cref="PKM.IsEgg"/>, the <see cref="PKM"/> is ignored.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
public static void MaximizeLevel(this PKM pk)
else
{
if (pk.IsEgg)
return;
pk.CurrentLevel = 100;
if (pk is ICombatPower pb)
pb.ResetCP();
pk.EVs = Set.EVs;
}
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to its default value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="la">Precomputed optional</param>
public static void SetDefaultNickname(this PKM pk, LegalityAnalysis la)
// IVs have no side effects such as hidden power type in gen 8
// therefore all specified IVs are deliberate and should not be Hyper Trained for pokemon met in gen 8
if (!pk.Gen8)
pk.SetSuggestedHyperTrainingData(Set.IVs);
if (ShowdownSetIVMarkings)
pk.SetMarkings();
pk.SetNickname(Set.Nickname);
pk.SetSaneGender(Set.Gender);
if (Legal.IsPPUpAvailable(pk))
pk.SetMaximumPPUps(Set.Moves);
if (pk.Format >= 3)
{
if (la.Parsed && la.EncounterOriginal is EncounterTrade {HasNickname: true} t)
pk.SetNickname(t.GetNickname(pk.Language));
else
pk.ClearNickname();
pk.SetAbility(Set.Ability);
pk.SetNature(Set.Nature);
}
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to its default value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetDefaultNickname(this PKM pk) => pk.SetDefaultNickname(new LegalityAnalysis(pk));
pk.SetIsShiny(Set.Shiny);
pk.SetRandomEC();
private static readonly string[] PotentialUnicode = { "★☆☆☆", "★★☆☆", "★★★☆", "★★★★" };
private static readonly string[] PotentialNoUnicode = { "+", "++", "+++", "++++" };
/// <summary>
/// Gets the Potential evaluation of the input <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon to analyze.</param>
/// <param name="unicode">Returned value is unicode or not</param>
/// <returns>Potential string</returns>
public static string GetPotentialString(this PKM pk, bool unicode = true)
if (pk is IAwakened a)
{
var arr = unicode ? PotentialUnicode : PotentialNoUnicode;
return arr[pk.PotentialRating];
a.SetSuggestedAwakenedValues(pk);
if (pk is PB7 b)
{
for (int i = 0; i < 6; i++)
b.SetEV(i, 0);
b.ResetCalculatedValues();
}
}
if (pk is IGanbaru g)
g.SetSuggestedGanbaruValues(pk);
// Extensions
/// <summary>
/// Gets the Location Name for the <see cref="PKM"/>
/// </summary>
/// <param name="pk">PKM to fetch data for</param>
/// <param name="eggmet">Location requested is the egg obtained location, not met location.</param>
/// <returns>Location string</returns>
public static string GetLocationString(this PKM pk, bool eggmet)
if (pk is IGigantamax c)
c.CanGigantamax = Set.CanGigantamax;
if (pk is IDynamaxLevel d)
d.DynamaxLevel = d.GetSuggestedDynamaxLevel(pk);
if (pk is ITechRecord8 t)
{
if (pk.Format < 2)
return string.Empty;
int location = eggmet ? pk.Egg_Location : pk.Met_Location;
return GameInfo.GetLocationName(eggmet, location, pk.Format, pk.Generation, (GameVersion)pk.Version);
t.ClearRecordFlags();
t.SetRecordFlags(Set.Moves);
}
if (pk is IMoveShop8Mastery s)
s.SetMoveShopFlags(Set.Moves, pk);
if (ShowdownSetBehaviorNature && pk.Format >= 8)
pk.Nature = pk.StatNature;
var legal = new LegalityAnalysis(pk);
if (legal.Parsed && legal.Info.Relearn.Any(z => !z.Valid))
pk.SetRelearnMoves(legal.GetSuggestedRelearnMoves());
pk.ResetPartyStats();
pk.RefreshChecksum();
}
/// <summary>
/// Sets the <see cref="PKM.HeldItem"/> value depending on the current format and the provided item index &amp; format.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="item">Held Item to apply</param>
/// <param name="format">Format required for importing</param>
public static void ApplyHeldItem(this PKM pk, int item, int format)
{
item = ItemConverter.GetItemForFormat(item, format, pk.Format);
pk.HeldItem = ((uint)item > pk.MaxItemID) ? 0 : item;
}
/// <summary>
/// Sets one of the <see cref="PKM.EVs"/> based on its index within the array.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to set to</param>
/// <param name="value">Value to set</param>
public static int SetEV(this PKM pk, int index, int value) => index switch
{
0 => pk.EV_HP = value,
1 => pk.EV_ATK = value,
2 => pk.EV_DEF = value,
3 => pk.EV_SPE = value,
4 => pk.EV_SPA = value,
5 => pk.EV_SPD = value,
_ => throw new ArgumentOutOfRangeException(nameof(index)),
};
/// <summary>
/// Sets one of the <see cref="PKM.IVs"/> based on its index within the array.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to set to</param>
/// <param name="value">Value to set</param>
public static int SetIV(this PKM pk, int index, int value) => index switch
{
0 => pk.IV_HP = value,
1 => pk.IV_ATK = value,
2 => pk.IV_DEF = value,
3 => pk.IV_SPE = value,
4 => pk.IV_SPA = value,
5 => pk.IV_SPD = value,
_ => throw new ArgumentOutOfRangeException(nameof(index)),
};
/// <summary>
/// Fetches the highest value the provided <see cref="PKM.EVs"/> index can be while considering others.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to fetch for</param>
/// <returns>Highest value the value can be.</returns>
public static int GetMaximumEV(this PKM pk, int index)
{
if (pk.Format < 3)
return ushort.MaxValue;
var sum = pk.EVTotal - pk.GetEV(index);
int remaining = 510 - sum;
return Math.Min(Math.Max(remaining, 0), 252);
}
/// <summary>
/// Fetches the highest value the provided <see cref="PKM.IVs"/>.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="index">Index to fetch for</param>
/// <param name="allow30">Causes the returned value to be dropped down -1 if the value is already at a maximum.</param>
/// <returns>Highest value the value can be.</returns>
public static int GetMaximumIV(this PKM pk, int index, bool allow30 = false)
{
if (pk.GetIV(index) == pk.MaxIV && allow30)
return pk.MaxIV - 1;
return pk.MaxIV;
}
/// <summary>
/// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
/// <param name="reHatch">Re-hatch already hatched <see cref="PKM"/> inputs</param>
public static void ForceHatchPKM(this PKM pk, bool reHatch = false)
{
if (!pk.IsEgg && !reHatch)
return;
pk.IsEgg = false;
pk.ClearNickname();
pk.CurrentFriendship = pk.PersonalInfo.BaseFriendship;
if (pk.IsTradedEgg)
pk.Egg_Location = pk.Met_Location;
var loc = EncounterSuggestion.GetSuggestedEggMetLocation(pk);
if (loc >= 0)
pk.Met_Location = loc;
pk.MetDate = DateTime.Today;
if (pk.Gen6)
pk.SetHatchMemory6();
}
/// <summary>
/// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
/// <param name="origin">Game the egg originated from</param>
/// <param name="dest">Game the egg is currently present on</param>
public static void SetEggMetData(this PKM pk, GameVersion origin, GameVersion dest)
{
bool traded = origin != dest;
var today = pk.MetDate = DateTime.Today;
pk.Egg_Location = EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(pk.Generation, origin, traded);
pk.EggMetDate = today;
}
/// <summary>
/// Maximizes the <see cref="PKM.CurrentFriendship"/>. If the <see cref="PKM.IsEgg"/>, the hatch counter is set to 1.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
public static void MaximizeFriendship(this PKM pk)
{
if (pk.IsEgg)
pk.OT_Friendship = 1;
else
pk.CurrentFriendship = byte.MaxValue;
if (pk is ICombatPower pb)
pb.ResetCP();
}
/// <summary>
/// Maximizes the <see cref="PKM.CurrentLevel"/>. If the <see cref="PKM.IsEgg"/>, the <see cref="PKM"/> is ignored.
/// </summary>
/// <param name="pk">PKM to apply hatch details to</param>
public static void MaximizeLevel(this PKM pk)
{
if (pk.IsEgg)
return;
pk.CurrentLevel = 100;
if (pk is ICombatPower pb)
pb.ResetCP();
}
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to its default value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="la">Precomputed optional</param>
public static void SetDefaultNickname(this PKM pk, LegalityAnalysis la)
{
if (la.Parsed && la.EncounterOriginal is EncounterTrade {HasNickname: true} t)
pk.SetNickname(t.GetNickname(pk.Language));
else
pk.ClearNickname();
}
/// <summary>
/// Sets the <see cref="PKM.Nickname"/> to its default value.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
public static void SetDefaultNickname(this PKM pk) => pk.SetDefaultNickname(new LegalityAnalysis(pk));
private static readonly string[] PotentialUnicode = { "★☆☆☆", "★★☆☆", "★★★☆", "★★★★" };
private static readonly string[] PotentialNoUnicode = { "+", "++", "+++", "++++" };
/// <summary>
/// Gets the Potential evaluation of the input <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon to analyze.</param>
/// <param name="unicode">Returned value is unicode or not</param>
/// <returns>Potential string</returns>
public static string GetPotentialString(this PKM pk, bool unicode = true)
{
var arr = unicode ? PotentialUnicode : PotentialNoUnicode;
return arr[pk.PotentialRating];
}
// Extensions
/// <summary>
/// Gets the Location Name for the <see cref="PKM"/>
/// </summary>
/// <param name="pk">PKM to fetch data for</param>
/// <param name="eggmet">Location requested is the egg obtained location, not met location.</param>
/// <returns>Location string</returns>
public static string GetLocationString(this PKM pk, bool eggmet)
{
if (pk.Format < 2)
return string.Empty;
int location = eggmet ? pk.Egg_Location : pk.Met_Location;
return GameInfo.GetLocationName(eggmet, location, pk.Format, pk.Generation, (GameVersion)pk.Version);
}
}

View file

@ -114,11 +114,11 @@ public sealed class TrainerDatabase
}
/// <summary>
/// Adds the trainer details of the <see cref="pkm"/> to the <see cref="Database"/>.
/// Adds the trainer details of the <see cref="pk"/> to the <see cref="Database"/>.
/// </summary>
/// <param name="pkm">Pokémon with Trainer details to add.</param>
/// <param name="pk">Pokémon with Trainer details to add.</param>
/// <remarks>A copy of the object will be made to prevent modifications, just in case.</remarks>
public void RegisterCopy(PKM pkm) => Register(GetTrainerReference(pkm));
public void RegisterCopy(PKM pk) => Register(GetTrainerReference(pk));
/// <summary>
/// Adds the trainer details of the <see cref="info"/> to the <see cref="Database"/>.
@ -127,16 +127,16 @@ public sealed class TrainerDatabase
/// <remarks>A copy of the object will be made to prevent modifications, just in case.</remarks>
public void RegisterCopy(ITrainerInfo info) => Register(new SimpleTrainerInfo(info));
private static ITrainerInfo GetTrainerReference(PKM pkm)
private static ITrainerInfo GetTrainerReference(PKM pk)
{
var result = new SimpleTrainerInfo((GameVersion)pkm.Version)
var result = new SimpleTrainerInfo((GameVersion)pk.Version)
{
TID = pkm.TID, SID = pkm.SID, OT = pkm.OT_Name, Gender = pkm.OT_Gender,
Language = pkm.Language,
Generation = pkm.Generation,
TID = pk.TID, SID = pk.SID, OT = pk.OT_Name, Gender = pk.OT_Gender,
Language = pk.Language,
Generation = pk.Generation,
};
if (pkm is IRegionOrigin r)
if (pk is IRegionOrigin r)
r.CopyRegionOrigin(result);
else
result.SetDefaultRegionOrigins();

View file

@ -1,217 +1,216 @@
using System;
using System.Runtime.CompilerServices;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Logic for calculating a Hidden Power Type based on IVs and generation-format.
/// </summary>
public static class HiddenPower
{
/// <summary>
/// Logic for calculating a Hidden Power Type based on IVs and generation-format.
/// Gets the current Hidden Power Type of the input <see cref="IVs"/> for the requested format generation.
/// </summary>
public static class HiddenPower
/// <param name="IVs">Current IVs</param>
/// <returns>Hidden Power Type of the <see cref="IVs"/></returns>
/// <param name="format">Generation format</param>
public static int GetType(ReadOnlySpan<int> IVs, int format)
{
/// <summary>
/// Gets the current Hidden Power Type of the input <see cref="IVs"/> for the requested format generation.
/// </summary>
/// <param name="IVs">Current IVs</param>
/// <returns>Hidden Power Type of the <see cref="IVs"/></returns>
/// <param name="format">Generation format</param>
public static int GetType(ReadOnlySpan<int> IVs, int format)
{
if (format <= 2)
return GetTypeGB(IVs);
return GetType(IVs);
}
/// <summary>
/// Gets the current Hidden Power Type of the input <see cref="IVs"/> for Generations 3+
/// </summary>
/// <param name="IVs">Current IVs</param>
/// <returns>Hidden Power Type of the <see cref="IVs"/></returns>
public static int GetType(ReadOnlySpan<int> IVs)
{
int hp = 0;
for (int i = 0; i < 6; i++)
hp |= (IVs[i] & 1) << i;
hp *= 0xF;
hp /= 0x3F;
return hp;
}
/// <summary>
/// Gets the current Hidden Power Type of the input <see cref="IVs"/> for Generations 1 &amp; 2
/// </summary>
/// <param name="IVs">Current IVs</param>
/// <returns>Hidden Power Type of the <see cref="IVs"/></returns>
public static int GetTypeGB(ReadOnlySpan<int> IVs)
{
var atk = IVs[1];
var def = IVs[2];
return ((atk & 3) << 2) | (def & 3);
}
/// <summary>
/// Modifies the provided <see cref="IVs"/> to have the requested <see cref="hiddenPowerType"/> for Generations 1 &amp; 2
/// </summary>
/// <param name="hiddenPowerType">Hidden Power Type</param>
/// <param name="IVs">Current IVs</param>
/// <returns>True if the Hidden Power of the <see cref="IVs"/> is obtained, with or without modifications</returns>
public static bool SetTypeGB(int hiddenPowerType, Span<int> IVs)
{
IVs[1] = (IVs[1] & ~3) | (hiddenPowerType >> 2);
IVs[2] = (IVs[2] & ~3) | (hiddenPowerType & 3);
return true;
}
/// <summary>
/// Modifies the provided <see cref="IVs"/> to have the requested <see cref="hiddenPowerType"/>.
/// </summary>
/// <param name="hiddenPowerType">Hidden Power Type</param>
/// <param name="IVs">Current IVs (6 total)</param>
/// <param name="format">Generation format</param>
/// <returns>True if the Hidden Power of the <see cref="IVs"/> is obtained, with or without modifications</returns>
public static bool SetIVsForType(int hiddenPowerType, Span<int> IVs, int format)
{
if (format <= 2)
return SetTypeGB(hiddenPowerType, IVs);
return SetIVsForType(hiddenPowerType, IVs);
}
/// <summary>
/// Sets the <see cref="IVs"/> to the requested <see cref="hpVal"/> for Generation 3+ game formats.
/// </summary>
/// <param name="hpVal">Hidden Power Type</param>
/// <param name="IVs">Current IVs (6 total)</param>
/// <returns>True if the Hidden Power of the <see cref="IVs"/> is obtained, with or without modifications</returns>
public static bool SetIVsForType(int hpVal, Span<int> IVs)
{
int flawlessCount = IVs.Count(31);
if (flawlessCount == 0)
return false;
if (flawlessCount == IVs.Length)
{
SetIVs(hpVal, IVs); // Get IVs
return true;
}
int current = GetType(IVs);
if (current == hpVal)
return true; // no mods necessary
// Required HP type doesn't match IVs. Make currently-flawless IVs flawed.
Span<int> scratch = stackalloc int[IVs.Length];
Span<int> result = stackalloc int[IVs.Length];
var success = GetSuggestedHiddenPowerIVs(hpVal, IVs, scratch, result);
if (!success)
return false; // can't force hidden power?
// set IVs back to array
result.CopyTo(IVs);
return true;
}
// Non-recursive https://en.wikipedia.org/wiki/Heap%27s_algorithm
private static bool GetSuggestedHiddenPowerIVs(int hpVal, ReadOnlySpan<int> original, Span<int> ivs, Span<int> best)
{
const int max = 31;
// Get a list of indexes that can be mutated
Span<int> indexes = stackalloc int[original.Length];
int flaw = 0;
for (int i = 0; i < original.Length; i++)
{
if (original[i] == max)
indexes[flaw++] = i;
}
indexes = indexes[..flaw];
Span<int> c = stackalloc int[indexes.Length];
int mutated = c.Length + 1; // result tracking
for (int i = 1; i < c.Length;)
{
if (c[i] >= i) // Reset the state and simulate popping the stack by incrementing the pointer.
{
c[i++] = 0;
continue;
}
var x = (i & 1) == 1 ? c[i] : 0;
Swap(ref indexes[i], ref indexes[x]);
// Inlined continuance check
original.CopyTo(ivs);
var q = Math.Min(indexes.Length, mutated);
for (var j = 0; j < q; j++)
{
ivs[indexes[j]] ^= 1;
if (hpVal != GetType(ivs))
continue;
var ct = j + 1;
if (ct >= mutated)
break; // any further flaws are always worse
mutated = ct;
ivs.CopyTo(best);
if (j == 0) // nothing will be better than only 1 flaw
return true;
break; // any further flaws are always worse
}
c[i]++;
i = 1;
}
return mutated <= c.Length; // did we actually find a suitable result?
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Swap<T>(ref T a, ref T b) => (a, b) = (b, a);
/// <summary>Calculate the Hidden Power Type of the entered IVs.</summary>
/// <param name="type">Hidden Power Type</param>
/// <param name="ivs">Individual Values (H/A/B/S/C/D)</param>
/// <param name="format">Generation specific format</param>
public static void SetIVs(int type, Span<int> ivs, int format = PKX.Generation)
{
if (format <= 2)
{
ivs[1] = (ivs[1] & ~3) | (type >> 2);
ivs[2] = (ivs[2] & ~3) | (type & 3);
}
else
{
var bits = DefaultLowBits[type];
for (int i = 0; i < 6; i++)
ivs[i] = (ivs[i] & 0x1E) + ((bits >> i) & 1);
}
}
/// <summary>
/// Hidden Power IV values (even or odd) to achieve a specified Hidden Power Type
/// </summary>
/// <remarks>
/// There are other IV combinations to achieve the same Hidden Power Type.
/// These are just precomputed for fast modification.
/// Individual Values (H/A/B/S/C/D)
/// </remarks>
public static readonly byte[] DefaultLowBits =
{
0b000011, // Fighting
0b001000, // Flying
0b001011, // Poison
0b001111, // Ground
0b010011, // Rock
0b011001, // Bug
0b011101, // Ghost
0b011111, // Steel
0b100101, // Fire
0b101001, // Water
0b101101, // Grass
0b101111, // Electric
0b110101, // Psychic
0b111001, // Ice
0b111101, // Dragon
0b111111, // Dark
};
if (format <= 2)
return GetTypeGB(IVs);
return GetType(IVs);
}
/// <summary>
/// Gets the current Hidden Power Type of the input <see cref="IVs"/> for Generations 3+
/// </summary>
/// <param name="IVs">Current IVs</param>
/// <returns>Hidden Power Type of the <see cref="IVs"/></returns>
public static int GetType(ReadOnlySpan<int> IVs)
{
int hp = 0;
for (int i = 0; i < 6; i++)
hp |= (IVs[i] & 1) << i;
hp *= 0xF;
hp /= 0x3F;
return hp;
}
/// <summary>
/// Gets the current Hidden Power Type of the input <see cref="IVs"/> for Generations 1 &amp; 2
/// </summary>
/// <param name="IVs">Current IVs</param>
/// <returns>Hidden Power Type of the <see cref="IVs"/></returns>
public static int GetTypeGB(ReadOnlySpan<int> IVs)
{
var atk = IVs[1];
var def = IVs[2];
return ((atk & 3) << 2) | (def & 3);
}
/// <summary>
/// Modifies the provided <see cref="IVs"/> to have the requested <see cref="hiddenPowerType"/> for Generations 1 &amp; 2
/// </summary>
/// <param name="hiddenPowerType">Hidden Power Type</param>
/// <param name="IVs">Current IVs</param>
/// <returns>True if the Hidden Power of the <see cref="IVs"/> is obtained, with or without modifications</returns>
public static bool SetTypeGB(int hiddenPowerType, Span<int> IVs)
{
IVs[1] = (IVs[1] & ~3) | (hiddenPowerType >> 2);
IVs[2] = (IVs[2] & ~3) | (hiddenPowerType & 3);
return true;
}
/// <summary>
/// Modifies the provided <see cref="IVs"/> to have the requested <see cref="hiddenPowerType"/>.
/// </summary>
/// <param name="hiddenPowerType">Hidden Power Type</param>
/// <param name="IVs">Current IVs (6 total)</param>
/// <param name="format">Generation format</param>
/// <returns>True if the Hidden Power of the <see cref="IVs"/> is obtained, with or without modifications</returns>
public static bool SetIVsForType(int hiddenPowerType, Span<int> IVs, int format)
{
if (format <= 2)
return SetTypeGB(hiddenPowerType, IVs);
return SetIVsForType(hiddenPowerType, IVs);
}
/// <summary>
/// Sets the <see cref="IVs"/> to the requested <see cref="hpVal"/> for Generation 3+ game formats.
/// </summary>
/// <param name="hpVal">Hidden Power Type</param>
/// <param name="IVs">Current IVs (6 total)</param>
/// <returns>True if the Hidden Power of the <see cref="IVs"/> is obtained, with or without modifications</returns>
public static bool SetIVsForType(int hpVal, Span<int> IVs)
{
int flawlessCount = IVs.Count(31);
if (flawlessCount == 0)
return false;
if (flawlessCount == IVs.Length)
{
SetIVs(hpVal, IVs); // Get IVs
return true;
}
int current = GetType(IVs);
if (current == hpVal)
return true; // no mods necessary
// Required HP type doesn't match IVs. Make currently-flawless IVs flawed.
Span<int> scratch = stackalloc int[IVs.Length];
Span<int> result = stackalloc int[IVs.Length];
var success = GetSuggestedHiddenPowerIVs(hpVal, IVs, scratch, result);
if (!success)
return false; // can't force hidden power?
// set IVs back to array
result.CopyTo(IVs);
return true;
}
// Non-recursive https://en.wikipedia.org/wiki/Heap%27s_algorithm
private static bool GetSuggestedHiddenPowerIVs(int hpVal, ReadOnlySpan<int> original, Span<int> ivs, Span<int> best)
{
const int max = 31;
// Get a list of indexes that can be mutated
Span<int> indexes = stackalloc int[original.Length];
int flaw = 0;
for (int i = 0; i < original.Length; i++)
{
if (original[i] == max)
indexes[flaw++] = i;
}
indexes = indexes[..flaw];
Span<int> c = stackalloc int[indexes.Length];
int mutated = c.Length + 1; // result tracking
for (int i = 1; i < c.Length;)
{
if (c[i] >= i) // Reset the state and simulate popping the stack by incrementing the pointer.
{
c[i++] = 0;
continue;
}
var x = (i & 1) == 1 ? c[i] : 0;
Swap(ref indexes[i], ref indexes[x]);
// Inlined continuance check
original.CopyTo(ivs);
var q = Math.Min(indexes.Length, mutated);
for (var j = 0; j < q; j++)
{
ivs[indexes[j]] ^= 1;
if (hpVal != GetType(ivs))
continue;
var ct = j + 1;
if (ct >= mutated)
break; // any further flaws are always worse
mutated = ct;
ivs.CopyTo(best);
if (j == 0) // nothing will be better than only 1 flaw
return true;
break; // any further flaws are always worse
}
c[i]++;
i = 1;
}
return mutated <= c.Length; // did we actually find a suitable result?
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Swap<T>(ref T a, ref T b) => (a, b) = (b, a);
/// <summary>Calculate the Hidden Power Type of the entered IVs.</summary>
/// <param name="type">Hidden Power Type</param>
/// <param name="ivs">Individual Values (H/A/B/S/C/D)</param>
/// <param name="format">Generation specific format</param>
public static void SetIVs(int type, Span<int> ivs, int format = PKX.Generation)
{
if (format <= 2)
{
ivs[1] = (ivs[1] & ~3) | (type >> 2);
ivs[2] = (ivs[2] & ~3) | (type & 3);
}
else
{
var bits = DefaultLowBits[type];
for (int i = 0; i < 6; i++)
ivs[i] = (ivs[i] & 0x1E) + ((bits >> i) & 1);
}
}
/// <summary>
/// Hidden Power IV values (even or odd) to achieve a specified Hidden Power Type
/// </summary>
/// <remarks>
/// There are other IV combinations to achieve the same Hidden Power Type.
/// These are just precomputed for fast modification.
/// Individual Values (H/A/B/S/C/D)
/// </remarks>
public static readonly byte[] DefaultLowBits =
{
0b000011, // Fighting
0b001000, // Flying
0b001011, // Poison
0b001111, // Ground
0b010011, // Rock
0b011001, // Bug
0b011101, // Ghost
0b011111, // Steel
0b100101, // Fire
0b101001, // Water
0b101101, // Grass
0b101111, // Electric
0b110101, // Psychic
0b111001, // Ice
0b111101, // Dragon
0b111111, // Dark
};
}

View file

@ -1,73 +1,72 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Interface containing details relevant for battling.
/// </summary>
public interface IBattleTemplate : ISpeciesForm, IGigantamax, INature
{
/// <summary>
/// Interface containing details relevant for battling.
/// <see cref="PKM.Format"/> of the Set entity it is specific to.
/// </summary>
public interface IBattleTemplate : ISpeciesForm, IGigantamax, INature
{
/// <summary>
/// <see cref="PKM.Format"/> of the Set entity it is specific to.
/// </summary>
int Format { get; }
int Format { get; }
/// <summary>
/// <see cref="PKM.Nickname"/> of the Set entity.
/// </summary>
string Nickname { get; }
/// <summary>
/// <see cref="PKM.Nickname"/> of the Set entity.
/// </summary>
string Nickname { get; }
/// <summary>
/// <see cref="PKM.Gender"/> name of the Set entity.
/// </summary>
int Gender { get; }
/// <summary>
/// <see cref="PKM.Gender"/> name of the Set entity.
/// </summary>
int Gender { get; }
/// <summary>
/// <see cref="PKM.HeldItem"/> of the Set entity.
/// </summary>
int HeldItem { get; }
/// <summary>
/// <see cref="PKM.HeldItem"/> of the Set entity.
/// </summary>
int HeldItem { get; }
/// <summary>
/// <see cref="PKM.Ability"/> of the Set entity.
/// </summary>
int Ability { get; }
/// <summary>
/// <see cref="PKM.Ability"/> of the Set entity.
/// </summary>
int Ability { get; }
/// <summary>
/// <see cref="PKM.CurrentLevel"/> of the Set entity.
/// </summary>
int Level { get; }
/// <summary>
/// <see cref="PKM.CurrentLevel"/> of the Set entity.
/// </summary>
int Level { get; }
/// <summary>
/// <see cref="PKM.CurrentLevel"/> of the Set entity.
/// </summary>
bool Shiny { get; }
/// <summary>
/// <see cref="PKM.CurrentLevel"/> of the Set entity.
/// </summary>
bool Shiny { get; }
/// <summary>
/// <see cref="PKM.CurrentFriendship"/> of the Set entity.
/// </summary>
int Friendship { get; }
/// <summary>
/// <see cref="PKM.CurrentFriendship"/> of the Set entity.
/// </summary>
int Friendship { get; }
/// <summary>
/// <see cref="PKM.Form"/> name of the Set entity, stored in PKHeX style (instead of Showdown's)
/// </summary>
string FormName { get; }
/// <summary>
/// <see cref="PKM.Form"/> name of the Set entity, stored in PKHeX style (instead of Showdown's)
/// </summary>
string FormName { get; }
/// <summary>
/// <see cref="PKM.HPType"/> of the Set entity.
/// </summary>
int HiddenPowerType { get; }
/// <summary>
/// <see cref="PKM.HPType"/> of the Set entity.
/// </summary>
int HiddenPowerType { get; }
/// <summary>
/// <see cref="PKM.EVs"/> of the Set entity.
/// </summary>
int[] EVs { get; }
/// <summary>
/// <see cref="PKM.EVs"/> of the Set entity.
/// </summary>
int[] EVs { get; }
/// <summary>
/// <see cref="PKM.IVs"/> of the Set entity.
/// </summary>
int[] IVs { get; }
/// <summary>
/// <see cref="PKM.IVs"/> of the Set entity.
/// </summary>
int[] IVs { get; }
/// <summary>
/// <see cref="PKM.Moves"/> of the Set entity.
/// </summary>
int[] Moves { get; }
}
/// <summary>
/// <see cref="PKM.Moves"/> of the Set entity.
/// </summary>
int[] Moves { get; }
}

View file

@ -1,48 +1,47 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Simple interface representing a <see cref="PKM"/> viewer.
/// </summary>
public interface IPKMView
{
/// <summary>
/// Simple interface representing a <see cref="PKM"/> viewer.
/// Fetches the currently loaded <see cref="PKM"/> data from the viewer.
/// </summary>
public interface IPKMView
{
/// <summary>
/// Fetches the currently loaded <see cref="PKM"/> data from the viewer.
/// </summary>
PKM Data { get; }
PKM Data { get; }
/// <summary>
/// Indicates if the Viewer supports using Unicode characters or not.
/// </summary>
bool Unicode { get; }
/// <summary>
/// Indicates if the Viewer supports using Unicode characters or not.
/// </summary>
bool Unicode { get; }
/// <summary>
/// Indicates if the Viewer is providing extra flexibility or not.
/// </summary>
bool HaX { get; }
/// <summary>
/// Indicates if the Viewer is providing extra flexibility or not.
/// </summary>
bool HaX { get; }
/// <summary>
/// Indicates if the Viewer's controls are changing their values and should avoid triggering other updates.
/// </summary>
bool ChangingFields { get; set; }
/// <summary>
/// Indicates if the Viewer's controls are changing their values and should avoid triggering other updates.
/// </summary>
bool ChangingFields { get; set; }
/// <summary>
/// Fetches the currently loaded <see cref="PKM"/> data from the viewer by finishing any pending changes or auto-modifications.
/// </summary>
/// <param name="click">Cause the viewer to do extra actions to force validation of its children.</param>
/// <returns>Prepared <see cref="PKM"/> data from the viewer.</returns>
PKM PreparePKM(bool click = true);
/// <summary>
/// Fetches the currently loaded <see cref="PKM"/> data from the viewer by finishing any pending changes or auto-modifications.
/// </summary>
/// <param name="click">Cause the viewer to do extra actions to force validation of its children.</param>
/// <returns>Prepared <see cref="PKM"/> data from the viewer.</returns>
PKM PreparePKM(bool click = true);
/// <summary>
/// Indicates if the currently loaded <see cref="PKM"/> data is ready for exporting.
/// </summary>
bool EditsComplete { get; }
/// <summary>
/// Indicates if the currently loaded <see cref="PKM"/> data is ready for exporting.
/// </summary>
bool EditsComplete { get; }
/// <summary>
/// Loads a given <see cref="PKM"/> data to the viewer.
/// </summary>
/// <param name="pk">Pokémon data to load.</param>
/// <param name="focus">Cause the viewer to give focus to itself.</param>
/// <param name="skipConversionCheck">Cause the viewer to skip converting the data. Faster if it is known that the format is the same as the previous format.</param>
void PopulateFields(PKM pk, bool focus = true, bool skipConversionCheck = false);
}
/// <summary>
/// Loads a given <see cref="PKM"/> data to the viewer.
/// </summary>
/// <param name="pk">Pokémon data to load.</param>
/// <param name="focus">Cause the viewer to give focus to itself.</param>
/// <param name="skipConversionCheck">Cause the viewer to skip converting the data. Faster if it is known that the format is the same as the previous format.</param>
void PopulateFields(PKM pk, bool focus = true, bool skipConversionCheck = false);
}

View file

@ -1,41 +1,40 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Plugin interface used by an editor to notify third-party code providers.
/// </summary>
public interface IPlugin
{
/// <summary>
/// Plugin interface used by an editor to notify third-party code providers.
/// Plugin Name.
/// </summary>
public interface IPlugin
{
/// <summary>
/// Plugin Name.
/// </summary>
string Name { get; }
string Name { get; }
/// <summary>
/// Plugin Loading Priority (lowest is initialized first).
/// </summary>
int Priority { get; }
/// <summary>
/// Plugin Loading Priority (lowest is initialized first).
/// </summary>
int Priority { get; }
/// <summary>
/// Entry point for the parent to initialize the plugin with provided arguments.
/// </summary>
/// <param name="args">Arguments containing objects useful for initializing the plugin.</param>
void Initialize(params object[] args);
/// <summary>
/// Entry point for the parent to initialize the plugin with provided arguments.
/// </summary>
/// <param name="args">Arguments containing objects useful for initializing the plugin.</param>
void Initialize(params object[] args);
/// <summary>
/// Notifies the plugin that a save file was just loaded.
/// </summary>
void NotifySaveLoaded();
/// <summary>
/// Notifies the plugin that a save file was just loaded.
/// </summary>
void NotifySaveLoaded();
/// <summary>
/// Attempts to load a file using the plugin.
/// </summary>
/// <param name="filePath">Path to file to be loaded.</param>
/// <returns>True if the plugin has handled the file.</returns>
bool TryLoadFile(string filePath);
/// <summary>
/// Attempts to load a file using the plugin.
/// </summary>
/// <param name="filePath">Path to file to be loaded.</param>
/// <returns>True if the plugin has handled the file.</returns>
bool TryLoadFile(string filePath);
/// <summary>
/// Retrieves the <see cref="ISaveFileProvider"/> object which can provide a <see cref="SaveFile"/>.
/// </summary>
ISaveFileProvider SaveFileEditor { get; }
}
/// <summary>
/// Retrieves the <see cref="ISaveFileProvider"/> object which can provide a <see cref="SaveFile"/>.
/// </summary>
ISaveFileProvider SaveFileEditor { get; }
}

View file

@ -1,23 +1,22 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Simple interface representing a Save File viewer.
/// </summary>
public interface ISaveFileProvider
{
/// <summary>
/// Simple interface representing a Save File viewer.
/// Retrieves the save file the <see cref="ISaveFileProvider"/> has control over.
/// </summary>
public interface ISaveFileProvider
{
/// <summary>
/// Retrieves the save file the <see cref="ISaveFileProvider"/> has control over.
/// </summary>
SaveFile SAV { get; }
SaveFile SAV { get; }
/// <summary>
/// Retrieves the current box the <see cref="ISaveFileProvider"/> has control over.
/// </summary>
int CurrentBox { get; }
/// <summary>
/// Retrieves the current box the <see cref="ISaveFileProvider"/> has control over.
/// </summary>
int CurrentBox { get; }
/// <summary>
/// Triggers a refresh of any individual <see cref="PKM"/> view slots.
/// </summary>
void ReloadSlots();
}
/// <summary>
/// Triggers a refresh of any individual <see cref="PKM"/> view slots.
/// </summary>
void ReloadSlots();
}

View file

@ -1,17 +1,16 @@
namespace PKHeX.Core
namespace PKHeX.Core;
public interface ISpriteBuilder<T>
{
public interface ISpriteBuilder<T>
{
T GetSprite(int species, int form, int gender, uint formarg, int heldItem, bool isEgg, bool isShiny,
int generation = -1,
bool isBoxBGRed = false,
bool isAltShiny = false);
T GetSprite(int species, int form, int gender, uint formarg, int heldItem, bool isEgg, bool isShiny,
int generation = -1,
bool isBoxBGRed = false,
bool isAltShiny = false);
T GetSprite(T baseSprite, int species, int heldItem, bool isEgg, bool isShiny,
int generation = -1,
bool isBoxBGRed = false,
bool isAltShiny = false);
T GetSprite(T baseSprite, int species, int heldItem, bool isEgg, bool isShiny,
int generation = -1,
bool isBoxBGRed = false,
bool isAltShiny = false);
void Initialize(SaveFile sav);
}
void Initialize(SaveFile sav);
}

View file

@ -2,26 +2,25 @@
using System.Linq;
using static PKHeX.Core.MessageStrings;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Utility for editing a <see cref="PKM"/>
/// </summary>
public static class EntitySuggestionUtil
{
/// <summary>
/// Utility for editing a <see cref="PKM"/>
/// </summary>
public static class EntitySuggestionUtil
public static List<string> GetMetLocationSuggestionMessage(PKM pk, int level, int location, int minimumLevel)
{
public static List<string> GetMetLocationSuggestionMessage(PKM pkm, int level, int location, int minimumLevel)
var suggestion = new List<string> { MsgPKMSuggestionStart };
if (pk.Format >= 3)
{
var suggestion = new List<string> { MsgPKMSuggestionStart };
if (pkm.Format >= 3)
{
var metList = GameInfo.GetLocationList((GameVersion)pkm.Version, pkm.Context, egg: false);
var locationName = metList.First(loc => loc.Value == location).Text;
suggestion.Add($"{MsgPKMSuggestionMetLocation} {locationName}");
suggestion.Add($"{MsgPKMSuggestionMetLevel} {level}");
}
if (pkm.CurrentLevel < minimumLevel)
suggestion.Add($"{MsgPKMSuggestionLevel} {minimumLevel}");
return suggestion;
var metList = GameInfo.GetLocationList((GameVersion)pk.Version, pk.Context, egg: false);
var locationName = metList.First(loc => loc.Value == location).Text;
suggestion.Add($"{MsgPKMSuggestionMetLocation} {locationName}");
suggestion.Add($"{MsgPKMSuggestionMetLevel} {level}");
}
if (pk.CurrentLevel < minimumLevel)
suggestion.Add($"{MsgPKMSuggestionLevel} {minimumLevel}");
return suggestion;
}
}

View file

@ -1,126 +1,125 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Bindable summary object that can fetch strings that summarize a <see cref="PKM"/>.
/// </summary>
public class EntitySummary // do NOT seal, allow inheritance
{
/// <summary>
/// Bindable summary object that can fetch strings that summarize a <see cref="PKM"/>.
/// </summary>
public class EntitySummary // do NOT seal, allow inheritance
private static readonly IReadOnlyList<string> GenderSymbols = GameInfo.GenderSymbolASCII;
private readonly GameStrings Strings;
private readonly ushort[] Stats;
protected readonly PKM pk; // protected for children generating extra properties
public virtual string Position => "???";
public string Nickname => pk.Nickname;
public string Species => Get(Strings.specieslist, pk.Species);
public string Nature => Get(Strings.natures, pk.StatNature);
public string Gender => Get(GenderSymbols, pk.Gender);
public string ESV => pk.PSV.ToString("0000");
public string HP_Type => Get(Strings.types, pk.HPType + 1);
public string Ability => Get(Strings.abilitylist, pk.Ability);
public string Move1 => Get(Strings.movelist, pk.Move1);
public string Move2 => Get(Strings.movelist, pk.Move2);
public string Move3 => Get(Strings.movelist, pk.Move3);
public string Move4 => Get(Strings.movelist, pk.Move4);
public string HeldItem => GetSpan(Strings.GetItemStrings(pk.Format), pk.HeldItem);
public string HP => Stats[0].ToString();
public string ATK => Stats[1].ToString();
public string DEF => Stats[2].ToString();
public string SPA => Stats[4].ToString();
public string SPD => Stats[5].ToString();
public string SPE => Stats[3].ToString();
public string MetLoc => pk.GetLocationString(eggmet: false);
public string EggLoc => pk.GetLocationString(eggmet: true);
public string Ball => Get(Strings.balllist, pk.Ball);
public string OT => pk.OT_Name;
public string Version => Get(Strings.gamelist, pk.Version);
public string OTLang => ((LanguageID)pk.Language).ToString();
public string Legal { get { var la = new LegalityAnalysis(pk); return la.Parsed ? la.Valid.ToString() : "-"; } }
#region Extraneous
public string EC => pk.EncryptionConstant.ToString("X8");
public string PID => pk.PID.ToString("X8");
public int HP_IV => pk.IV_HP;
public int ATK_IV => pk.IV_ATK;
public int DEF_IV => pk.IV_DEF;
public int SPA_IV => pk.IV_SPA;
public int SPD_IV => pk.IV_SPD;
public int SPE_IV => pk.IV_SPE;
public uint EXP => pk.EXP;
public int Level => pk.CurrentLevel;
public int HP_EV => pk.EV_HP;
public int ATK_EV => pk.EV_ATK;
public int DEF_EV => pk.EV_DEF;
public int SPA_EV => pk.EV_SPA;
public int SPD_EV => pk.EV_SPD;
public int SPE_EV => pk.EV_SPE;
public int Cool => pk is IContestStats s ? s.CNT_Cool : 0;
public int Beauty => pk is IContestStats s ? s.CNT_Beauty : 0;
public int Cute => pk is IContestStats s ? s.CNT_Cute : 0;
public int Smart => pk is IContestStats s ? s.CNT_Smart : 0;
public int Tough => pk is IContestStats s ? s.CNT_Tough : 0;
public int Sheen => pk is IContestStats s ? s.CNT_Sheen : 0;
public int Markings => pk.MarkValue;
public string NotOT => pk.Format > 5 ? pk.HT_Name : "N/A";
public int AbilityNum => pk.Format > 5 ? pk.AbilityNumber : -1;
public int GenderFlag => pk.Gender;
public int Form => pk.Form;
public int PKRS_Strain => pk.PKRS_Strain;
public int PKRS_Days => pk.PKRS_Days;
public int MetLevel => pk.Met_Level;
public int OT_Gender => pk.OT_Gender;
public bool FatefulFlag => pk.FatefulEncounter;
public bool IsEgg => pk.IsEgg;
public bool IsNicknamed => pk.IsNicknamed;
public bool IsShiny => pk.IsShiny;
public int TID => pk.DisplayTID;
public int SID => pk.DisplaySID;
public int TSV => pk.TSV;
public int Move1_PP => pk.Move1_PP;
public int Move2_PP => pk.Move2_PP;
public int Move3_PP => pk.Move3_PP;
public int Move4_PP => pk.Move4_PP;
public int Move1_PPUp => pk.Move1_PPUps;
public int Move2_PPUp => pk.Move2_PPUps;
public int Move3_PPUp => pk.Move3_PPUps;
public int Move4_PPUp => pk.Move4_PPUps;
public string Relearn1 => Get(Strings.movelist, pk.RelearnMove1);
public string Relearn2 => Get(Strings.movelist, pk.RelearnMove2);
public string Relearn3 => Get(Strings.movelist, pk.RelearnMove3);
public string Relearn4 => Get(Strings.movelist, pk.RelearnMove4);
public ushort Checksum => pk is ISanityChecksum s ? s.Checksum : Checksums.CRC16_CCITT(pk.Data.AsSpan(pk.SIZE_STORED));
public int Friendship => pk.OT_Friendship;
public int Egg_Year => pk.EggMetDate.GetValueOrDefault().Year;
public int Egg_Month => pk.EggMetDate.GetValueOrDefault().Month;
public int Egg_Day => pk.EggMetDate.GetValueOrDefault().Day;
public int Met_Year => pk.MetDate.GetValueOrDefault().Year;
public int Met_Month => pk.MetDate.GetValueOrDefault().Month;
public int Met_Day => pk.MetDate.GetValueOrDefault().Day;
#endregion
protected EntitySummary(PKM p, GameStrings strings)
{
private static readonly IReadOnlyList<string> GenderSymbols = GameInfo.GenderSymbolASCII;
private readonly GameStrings Strings;
private readonly ushort[] Stats;
protected readonly PKM pkm; // protected for children generating extra properties
public virtual string Position => "???";
public string Nickname => pkm.Nickname;
public string Species => Get(Strings.specieslist, pkm.Species);
public string Nature => Get(Strings.natures, pkm.StatNature);
public string Gender => Get(GenderSymbols, pkm.Gender);
public string ESV => pkm.PSV.ToString("0000");
public string HP_Type => Get(Strings.types, pkm.HPType + 1);
public string Ability => Get(Strings.abilitylist, pkm.Ability);
public string Move1 => Get(Strings.movelist, pkm.Move1);
public string Move2 => Get(Strings.movelist, pkm.Move2);
public string Move3 => Get(Strings.movelist, pkm.Move3);
public string Move4 => Get(Strings.movelist, pkm.Move4);
public string HeldItem => GetSpan(Strings.GetItemStrings(pkm.Format), pkm.HeldItem);
public string HP => Stats[0].ToString();
public string ATK => Stats[1].ToString();
public string DEF => Stats[2].ToString();
public string SPA => Stats[4].ToString();
public string SPD => Stats[5].ToString();
public string SPE => Stats[3].ToString();
public string MetLoc => pkm.GetLocationString(eggmet: false);
public string EggLoc => pkm.GetLocationString(eggmet: true);
public string Ball => Get(Strings.balllist, pkm.Ball);
public string OT => pkm.OT_Name;
public string Version => Get(Strings.gamelist, pkm.Version);
public string OTLang => ((LanguageID)pkm.Language).ToString();
public string Legal { get { var la = new LegalityAnalysis(pkm); return la.Parsed ? la.Valid.ToString() : "-"; } }
#region Extraneous
public string EC => pkm.EncryptionConstant.ToString("X8");
public string PID => pkm.PID.ToString("X8");
public int HP_IV => pkm.IV_HP;
public int ATK_IV => pkm.IV_ATK;
public int DEF_IV => pkm.IV_DEF;
public int SPA_IV => pkm.IV_SPA;
public int SPD_IV => pkm.IV_SPD;
public int SPE_IV => pkm.IV_SPE;
public uint EXP => pkm.EXP;
public int Level => pkm.CurrentLevel;
public int HP_EV => pkm.EV_HP;
public int ATK_EV => pkm.EV_ATK;
public int DEF_EV => pkm.EV_DEF;
public int SPA_EV => pkm.EV_SPA;
public int SPD_EV => pkm.EV_SPD;
public int SPE_EV => pkm.EV_SPE;
public int Cool => pkm is IContestStats s ? s.CNT_Cool : 0;
public int Beauty => pkm is IContestStats s ? s.CNT_Beauty : 0;
public int Cute => pkm is IContestStats s ? s.CNT_Cute : 0;
public int Smart => pkm is IContestStats s ? s.CNT_Smart : 0;
public int Tough => pkm is IContestStats s ? s.CNT_Tough : 0;
public int Sheen => pkm is IContestStats s ? s.CNT_Sheen : 0;
public int Markings => pkm.MarkValue;
public string NotOT => pkm.Format > 5 ? pkm.HT_Name : "N/A";
public int AbilityNum => pkm.Format > 5 ? pkm.AbilityNumber : -1;
public int GenderFlag => pkm.Gender;
public int Form => pkm.Form;
public int PKRS_Strain => pkm.PKRS_Strain;
public int PKRS_Days => pkm.PKRS_Days;
public int MetLevel => pkm.Met_Level;
public int OT_Gender => pkm.OT_Gender;
public bool FatefulFlag => pkm.FatefulEncounter;
public bool IsEgg => pkm.IsEgg;
public bool IsNicknamed => pkm.IsNicknamed;
public bool IsShiny => pkm.IsShiny;
public int TID => pkm.DisplayTID;
public int SID => pkm.DisplaySID;
public int TSV => pkm.TSV;
public int Move1_PP => pkm.Move1_PP;
public int Move2_PP => pkm.Move2_PP;
public int Move3_PP => pkm.Move3_PP;
public int Move4_PP => pkm.Move4_PP;
public int Move1_PPUp => pkm.Move1_PPUps;
public int Move2_PPUp => pkm.Move2_PPUps;
public int Move3_PPUp => pkm.Move3_PPUps;
public int Move4_PPUp => pkm.Move4_PPUps;
public string Relearn1 => Get(Strings.movelist, pkm.RelearnMove1);
public string Relearn2 => Get(Strings.movelist, pkm.RelearnMove2);
public string Relearn3 => Get(Strings.movelist, pkm.RelearnMove3);
public string Relearn4 => Get(Strings.movelist, pkm.RelearnMove4);
public ushort Checksum => pkm is ISanityChecksum s ? s.Checksum : Checksums.CRC16_CCITT(pkm.Data.AsSpan(pkm.SIZE_STORED));
public int Friendship => pkm.OT_Friendship;
public int Egg_Year => pkm.EggMetDate.GetValueOrDefault().Year;
public int Egg_Month => pkm.EggMetDate.GetValueOrDefault().Month;
public int Egg_Day => pkm.EggMetDate.GetValueOrDefault().Day;
public int Met_Year => pkm.MetDate.GetValueOrDefault().Year;
public int Met_Month => pkm.MetDate.GetValueOrDefault().Month;
public int Met_Day => pkm.MetDate.GetValueOrDefault().Day;
#endregion
protected EntitySummary(PKM p, GameStrings strings)
{
pkm = p;
Strings = strings;
Stats = pkm.GetStats(pkm.PersonalInfo);
}
/// <summary>
/// Safely fetches the string from the array.
/// </summary>
/// <param name="arr">Array of strings</param>
/// <param name="val">Index to fetch</param>
/// <returns>Null if array is null</returns>
private static string Get(IReadOnlyList<string> arr, int val) => (uint)val < arr.Count ? arr[val] : string.Empty;
private static string GetSpan(ReadOnlySpan<string> arr, int val) => (uint)val < arr.Length ? arr[val] : string.Empty;
pk = p;
Strings = strings;
Stats = pk.GetStats(pk.PersonalInfo);
}
/// <summary>
/// Safely fetches the string from the array.
/// </summary>
/// <param name="arr">Array of strings</param>
/// <param name="val">Index to fetch</param>
/// <returns>Null if array is null</returns>
private static string Get(IReadOnlyList<string> arr, int val) => (uint)val < arr.Count ? arr[val] : string.Empty;
private static string GetSpan(ReadOnlySpan<string> arr, int val) => (uint)val < arr.Length ? arr[val] : string.Empty;
}

View file

@ -1,78 +1,77 @@
using System;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Logic for filling in template data for <see cref="PKM"/> objects.
/// </summary>
public static class EntityTemplates
{
/// <summary>
/// Logic for filling in template data for <see cref="PKM"/> objects.
/// Applies junk data to a <see cref="SaveFile.BlankPKM"/>, which is preferable to a completely empty entity.
/// </summary>
public static class EntityTemplates
/// <param name="pk">Blank data</param>
/// <param name="tr">Trainer info to apply</param>
public static void TemplateFields(PKM pk, ITrainerInfo tr)
{
/// <summary>
/// Applies junk data to a <see cref="SaveFile.BlankPKM"/>, which is preferable to a completely empty entity.
/// </summary>
/// <param name="pk">Blank data</param>
/// <param name="tr">Trainer info to apply</param>
public static void TemplateFields(PKM pk, ITrainerInfo tr)
pk.Move1 = (int)Move.Pound;
pk.HealPP();
pk.Ball = 4;
pk.MetDate = DateTime.Today;
if (tr.Game >= 0)
pk.Version = tr.Game;
pk.Species = GetTemplateSpecies(pk, tr);
pk.Language = GetTemplateLanguage(tr);
pk.Gender = pk.GetSaneGender();
pk.ClearNickname();
pk.OT_Name = tr.OT;
pk.OT_Gender = tr.Gender;
pk.TID = tr.TID;
pk.SID = tr.SID;
if (tr is IRegionOrigin o && pk is IRegionOrigin gt)
{
pk.Move1 = (int)Move.Pound;
pk.HealPP();
pk.Ball = 4;
pk.MetDate = DateTime.Today;
if (tr.Game >= 0)
pk.Version = tr.Game;
pk.Species = GetTemplateSpecies(pk, tr);
pk.Language = GetTemplateLanguage(tr);
pk.Gender = pk.GetSaneGender();
pk.ClearNickname();
pk.OT_Name = tr.OT;
pk.OT_Gender = tr.Gender;
pk.TID = tr.TID;
pk.SID = tr.SID;
if (tr is IRegionOrigin o && pk is IRegionOrigin gt)
{
gt.ConsoleRegion = o.ConsoleRegion;
gt.Country = o.Country;
gt.Region = o.Region;
}
ApplyTrashBytes(pk, tr);
pk.RefreshChecksum();
gt.ConsoleRegion = o.ConsoleRegion;
gt.Country = o.Country;
gt.Region = o.Region;
}
private static void ApplyTrashBytes(PKM pk, ITrainerInfo tr)
{
// Copy OT trash bytes for sensitive games (Gen1/2)
if (pk is not GBPKM pk12)
return;
switch (tr)
{
case SAV1 s1:
s1.OT_Trash.CopyTo(pk12.OT_Trash);
break;
case SAV2 s2:
s2.OT_Trash.CopyTo(pk12.OT_Trash);
break;
}
}
ApplyTrashBytes(pk, tr);
pk.RefreshChecksum();
}
private static int GetTemplateSpecies(PKM pk, ITrainerInfo tr)
private static void ApplyTrashBytes(PKM pk, ITrainerInfo tr)
{
// Copy OT trash bytes for sensitive games (Gen1/2)
if (pk is not GBPKM pk12)
return;
switch (tr)
{
int species = tr is IGameValueLimit s ? s.MaxSpeciesID : ((GameVersion)pk.Version).GetMaxSpeciesID();
if (species <= 0)
species = pk.MaxSpeciesID;
return species;
}
private static int GetTemplateLanguage(ITrainerInfo tr)
{
var lang = tr.Language;
if (lang <= 0)
lang = (int)LanguageID.English;
return lang;
case SAV1 s1:
s1.OT_Trash.CopyTo(pk12.OT_Trash);
break;
case SAV2 s2:
s2.OT_Trash.CopyTo(pk12.OT_Trash);
break;
}
}
private static int GetTemplateSpecies(PKM pk, ITrainerInfo tr)
{
int species = tr is IGameValueLimit s ? s.MaxSpeciesID : ((GameVersion)pk.Version).GetMaxSpeciesID();
if (species <= 0)
species = pk.MaxSpeciesID;
return species;
}
private static int GetTemplateLanguage(ITrainerInfo tr)
{
var lang = tr.Language;
if (lang <= 0)
lang = (int)LanguageID.English;
return lang;
}
}

View file

@ -1,111 +1,113 @@
using System;
using System.Linq;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// QR Message reading &amp; writing logic
/// </summary>
public static class QRMessageUtil
{
private const string QR6PathBad = "null/#";
private const string QR6Path = "http://lunarcookies.github.io/b1s1.html#";
private const string QR6PathWC = "http://lunarcookies.github.io/wc.html#";
private static string GetExploitURLPrefixPKM(int format) => format == 6 ? QR6Path : QR6PathBad;
private static string GetExploitURLPrefixWC(int format) => format == 6 ? QR6PathWC : QR6PathBad;
/// <summary>
/// QR Message reading &amp; writing logic
/// Gets the <see cref="PKM"/> data from the message that is encoded in a QR.
/// </summary>
public static class QRMessageUtil
/// <param name="message">QR Message</param>
/// <param name="context">Preferred <see cref="PKM.Context"/> to expect.</param>
/// <returns>Decoded <see cref="PKM"/> object, null if invalid.</returns>
public static PKM? GetPKM(string message, EntityContext context)
{
private const string QR6PathBad = "null/#";
private const string QR6Path = "http://lunarcookies.github.io/b1s1.html#";
private const string QR6PathWC = "http://lunarcookies.github.io/wc.html#";
private static string GetExploitURLPrefixPKM(int format) => format == 6 ? QR6Path : QR6PathBad;
private static string GetExploitURLPrefixWC(int format) => format == 6 ? QR6PathWC : QR6PathBad;
var data = DecodeMessagePKM(message);
if (data == null)
return null;
return EntityFormat.GetFromBytes(data, context);
}
/// <summary>
/// Gets the <see cref="PKM"/> data from the message that is encoded in a QR.
/// </summary>
/// <param name="message">QR Message</param>
/// <param name="context">Preferred <see cref="PKM.Context"/> to expect.</param>
/// <returns>Decoded <see cref="PKM"/> object, null if invalid.</returns>
public static PKM? GetPKM(string message, EntityContext context)
/// <summary>
/// Gets a QR Message from the input <see cref="PKM"/> data.
/// </summary>
/// <param name="pk">Pokémon to encode</param>
/// <returns>QR Message</returns>
public static string GetMessage(PKM pk)
{
if (pk is PK7 pk7)
{
var data = DecodeMessagePKM(message);
if (data == null)
byte[] payload = QR7.GenerateQRData(pk7);
return GetMessage(payload);
}
var server = GetExploitURLPrefixPKM(pk.Format);
var data = pk.EncryptedBoxData;
return GetMessageBase64(data, server);
}
/// <summary>
/// Gets a QR Message from the input <see cref="byte"/> data.
/// </summary>
/// <param name="payload">Data to encode</param>
/// <returns>QR Message</returns>
public static string GetMessage(byte[] payload) => string.Concat(payload.Select(z => (char) z));
/// <summary>
/// Gets a QR Message from the input <see cref="MysteryGift"/> data.
/// </summary>
/// <param name="mg">Gift data to encode</param>
/// <returns>QR Message</returns>
public static string GetMessage(DataMysteryGift mg)
{
var server = GetExploitURLPrefixWC(mg.Generation);
var data = mg.Write();
return GetMessageBase64(data, server);
}
public static string GetMessageBase64(byte[] data, string server)
{
string payload = Convert.ToBase64String(data);
return server + payload;
}
private static byte[]? DecodeMessagePKM(string message)
{
if (message.Length < 32) // arbitrary length check; everything should be greater than this
return null;
if (message.StartsWith(QR6PathBad, StringComparison.Ordinal)) // fake url
return DecodeMessageDataBase64(message);
if (message.StartsWith("http", StringComparison.Ordinal)) // inject url
return DecodeMessageDataBase64(message);
if (message.StartsWith("POKE", StringComparison.Ordinal) && message.Length > 0x30 + 0xE8) // G7 data
return GetBytesFromMessage(message, 0x30, 0xE8);
return null;
}
private static byte[]? DecodeMessageDataBase64(string url)
{
if (url.Length == 0 || url[^1] == '#')
return null;
try
{
int payloadBegin = url.IndexOf('#');
if (payloadBegin < 0) // bad URL, need the payload separator
return null;
return EntityFormat.GetFromBytes(data, context);
url = url[(payloadBegin + 1)..]; // Trim URL to right after #
return Convert.FromBase64String(url);
}
/// <summary>
/// Gets a QR Message from the input <see cref="PKM"/> data.
/// </summary>
/// <param name="pkm">Pokémon to encode</param>
/// <returns>QR Message</returns>
public static string GetMessage(PKM pkm)
catch (FormatException)
{
if (pkm is PK7 pk7)
{
byte[] payload = QR7.GenerateQRData(pk7);
return GetMessage(payload);
}
var server = GetExploitURLPrefixPKM(pkm.Format);
var data = pkm.EncryptedBoxData;
return GetMessageBase64(data, server);
}
/// <summary>
/// Gets a QR Message from the input <see cref="byte"/> data.
/// </summary>
/// <param name="payload">Data to encode</param>
/// <returns>QR Message</returns>
public static string GetMessage(byte[] payload) => string.Concat(payload.Select(z => (char) z));
/// <summary>
/// Gets a QR Message from the input <see cref="MysteryGift"/> data.
/// </summary>
/// <param name="mg">Gift data to encode</param>
/// <returns>QR Message</returns>
public static string GetMessage(DataMysteryGift mg)
{
var server = GetExploitURLPrefixWC(mg.Generation);
var data = mg.Write();
return GetMessageBase64(data, server);
}
public static string GetMessageBase64(byte[] data, string server)
{
string payload = Convert.ToBase64String(data);
return server + payload;
}
private static byte[]? DecodeMessagePKM(string message)
{
if (message.Length < 32) // arbitrary length check; everything should be greater than this
return null;
if (message.StartsWith(QR6PathBad)) // fake url
return DecodeMessageDataBase64(message);
if (message.StartsWith("http")) // inject url
return DecodeMessageDataBase64(message);
if (message.StartsWith("POKE") && message.Length > 0x30 + 0xE8) // G7 data
return GetBytesFromMessage(message, 0x30, 0xE8);
return null;
}
private static byte[]? DecodeMessageDataBase64(string url)
{
try
{
int payloadBegin = url.IndexOf('#');
if (payloadBegin < 0) // bad URL, need the payload separator
return null;
url = url[(payloadBegin + 1)..]; // Trim URL to right after #
return Convert.FromBase64String(url);
}
catch
{
return null;
}
}
private static byte[] GetBytesFromMessage(string seed, int skip, int take)
{
byte[] data = new byte[take];
for (int i = 0; i < take; i++)
data[i] = (byte)seed[i + skip];
return data;
}
}
}
private static byte[] GetBytesFromMessage(string seed, int skip, int take)
{
byte[] data = new byte[take];
for (int i = 0; i < take; i++)
data[i] = (byte)seed[i + skip];
return data;
}
}

View file

@ -1,123 +1,122 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core
namespace PKHeX.Core;
public sealed class QRPK7 : IEncounterInfo
{
public sealed class QRPK7 : IEncounterInfo
public GameVersion Version => (GameVersion)CassetteVersion;
public bool EggEncounter => false;
public byte LevelMin => Level;
public byte LevelMax => Level;
public int Generation => Version.GetGeneration();
public bool IsShiny => false;
private readonly byte[] Data;
public const int SIZE = 0x30;
public QRPK7(byte[] d) => Data = (byte[])d.Clone();
public uint EncryptionConstant => ReadUInt32LittleEndian(Data.AsSpan(0));
public byte HT_Flags => Data[4];
public int Unk_5 => Data[5];
public int Unk_6 => Data[6];
public int Unk_7 => Data[7];
public int Move1_PPUps => Data[8];
public int Move2_PPUps => Data[9];
public int Move3_PPUps => Data[0xA];
public int Move4_PPUps => Data[0xB];
public uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0xC)); set => WriteUInt32LittleEndian(Data.AsSpan(0xC), value); }
public int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 00)) | (uint)((value > 31 ? 31 : value) << 00)); }
public int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 05)) | (uint)((value > 31 ? 31 : value) << 05)); }
public int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 10)) | (uint)((value > 31 ? 31 : value) << 10)); }
public int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 15)) | (uint)((value > 31 ? 31 : value) << 15)); }
public int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 20)) | (uint)((value > 31 ? 31 : value) << 20)); }
public int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 25)) | (uint)((value > 31 ? 31 : value) << 25)); }
public uint PID => ReadUInt32LittleEndian(Data.AsSpan(0x10));
public int Species => ReadUInt16LittleEndian(Data.AsSpan(0x14));
public ushort HeldItem => ReadUInt16LittleEndian(Data.AsSpan(0x16));
public ushort Move1 => ReadUInt16LittleEndian(Data.AsSpan(0x18));
public ushort Move2 => ReadUInt16LittleEndian(Data.AsSpan(0x1A));
public ushort Move3 => ReadUInt16LittleEndian(Data.AsSpan(0x1C));
public ushort Move4 => ReadUInt16LittleEndian(Data.AsSpan(0x1E));
public int Unk_20 => Data[0x20];
public int AbilityIndex => Data[0x21];
public int Nature => Data[0x22];
public bool FatefulEncounter => (Data[0x23] & 1) == 1;
public int Gender => (Data[0x23] >> 1) & 3;
public int Form => Data[0x23] >> 3;
public int EV_HP => Data[0x24];
public int EV_ATK => Data[0x25];
public int EV_DEF => Data[0x26];
public int EV_SPE => Data[0x27];
public int EV_SPA => Data[0x28];
public int EV_SPD => Data[0x29];
public int Unk_2A => Data[0x2A];
public int Friendship => Data[0x2B];
public int Ball => Data[0x2C];
public byte Level => Data[0x2D];
public int CassetteVersion => Data[0x2E];
public int Language => Data[0x2F];
/// <summary>
/// Converts the <see cref="Data"/> to a rough PKM.
/// </summary>
public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
/// <summary>
/// Converts the <see cref="Data"/> to a rough PKM.
/// </summary>
public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
public GameVersion Version => (GameVersion)CassetteVersion;
public bool EggEncounter => false;
public byte LevelMin => Level;
public byte LevelMax => Level;
public int Generation => Version.GetGeneration();
public bool IsShiny => false;
private readonly byte[] Data;
public const int SIZE = 0x30;
public QRPK7(byte[] d) => Data = (byte[])d.Clone();
public uint EncryptionConstant => ReadUInt32LittleEndian(Data.AsSpan(0));
public byte HT_Flags => Data[4];
public int Unk_5 => Data[5];
public int Unk_6 => Data[6];
public int Unk_7 => Data[7];
public int Move1_PPUps => Data[8];
public int Move2_PPUps => Data[9];
public int Move3_PPUps => Data[0xA];
public int Move4_PPUps => Data[0xB];
public uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0xC)); set => WriteUInt32LittleEndian(Data.AsSpan(0xC), value); }
public int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 00)) | (uint)((value > 31 ? 31 : value) << 00)); }
public int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 05)) | (uint)((value > 31 ? 31 : value) << 05)); }
public int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 10)) | (uint)((value > 31 ? 31 : value) << 10)); }
public int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 15)) | (uint)((value > 31 ? 31 : value) << 15)); }
public int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 20)) | (uint)((value > 31 ? 31 : value) << 20)); }
public int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 25)) | (uint)((value > 31 ? 31 : value) << 25)); }
public uint PID => ReadUInt32LittleEndian(Data.AsSpan(0x10));
public int Species => ReadUInt16LittleEndian(Data.AsSpan(0x14));
public ushort HeldItem => ReadUInt16LittleEndian(Data.AsSpan(0x16));
public ushort Move1 => ReadUInt16LittleEndian(Data.AsSpan(0x18));
public ushort Move2 => ReadUInt16LittleEndian(Data.AsSpan(0x1A));
public ushort Move3 => ReadUInt16LittleEndian(Data.AsSpan(0x1C));
public ushort Move4 => ReadUInt16LittleEndian(Data.AsSpan(0x1E));
public int Unk_20 => Data[0x20];
public int AbilityIndex => Data[0x21];
public int Nature => Data[0x22];
public bool FatefulEncounter => (Data[0x23] & 1) == 1;
public int Gender => (Data[0x23] >> 1) & 3;
public int Form => Data[0x23] >> 3;
public int EV_HP => Data[0x24];
public int EV_ATK => Data[0x25];
public int EV_DEF => Data[0x26];
public int EV_SPE => Data[0x27];
public int EV_SPA => Data[0x28];
public int EV_SPD => Data[0x29];
public int Unk_2A => Data[0x2A];
public int Friendship => Data[0x2B];
public int Ball => Data[0x2C];
public byte Level => Data[0x2D];
public int CassetteVersion => Data[0x2E];
public int Language => Data[0x2F];
/// <summary>
/// Converts the <see cref="Data"/> to a rough PKM.
/// </summary>
public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted);
/// <summary>
/// Converts the <see cref="Data"/> to a rough PKM.
/// </summary>
public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
var pk = new PK7
{
var pk = new PK7
{
EncryptionConstant = EncryptionConstant,
PID = PID,
Language = Language,
Species = Species,
Gender = Gender,
Nature = Nature,
FatefulEncounter = FatefulEncounter,
Form = Form,
HyperTrainFlags = HT_Flags,
IV_HP = IV_HP,
IV_ATK = IV_ATK,
IV_DEF = IV_DEF,
IV_SPA = IV_SPA,
IV_SPD = IV_SPD,
IV_SPE = IV_SPE,
EV_HP = EV_HP,
EV_ATK = EV_ATK,
EV_DEF = EV_DEF,
EV_SPA = EV_SPA,
EV_SPD = EV_SPD,
EV_SPE = EV_SPE,
Move1 = Move1,
Move2 = Move2,
Move3 = Move3,
Move4 = Move4,
Move1_PPUps = Move1_PPUps,
Move2_PPUps = Move2_PPUps,
Move3_PPUps = Move3_PPUps,
Move4_PPUps = Move4_PPUps,
HeldItem = HeldItem,
HT_Friendship = Friendship,
OT_Friendship = Friendship,
Ball = Ball,
Version = CassetteVersion,
EncryptionConstant = EncryptionConstant,
PID = PID,
Language = Language,
Species = Species,
Gender = Gender,
Nature = Nature,
FatefulEncounter = FatefulEncounter,
Form = Form,
HyperTrainFlags = HT_Flags,
IV_HP = IV_HP,
IV_ATK = IV_ATK,
IV_DEF = IV_DEF,
IV_SPA = IV_SPA,
IV_SPD = IV_SPD,
IV_SPE = IV_SPE,
EV_HP = EV_HP,
EV_ATK = EV_ATK,
EV_DEF = EV_DEF,
EV_SPA = EV_SPA,
EV_SPD = EV_SPD,
EV_SPE = EV_SPE,
Move1 = Move1,
Move2 = Move2,
Move3 = Move3,
Move4 = Move4,
Move1_PPUps = Move1_PPUps,
Move2_PPUps = Move2_PPUps,
Move3_PPUps = Move3_PPUps,
Move4_PPUps = Move4_PPUps,
HeldItem = HeldItem,
HT_Friendship = Friendship,
OT_Friendship = Friendship,
Ball = Ball,
Version = CassetteVersion,
OT_Name = sav.OT,
HT_Name = sav.OT,
CurrentLevel = Level,
Met_Level = Level,
MetDate = DateTime.Now,
};
RecentTrainerCache.SetConsoleRegionData3DS(pk, sav);
OT_Name = tr.OT,
HT_Name = tr.OT,
CurrentLevel = Level,
Met_Level = Level,
MetDate = DateTime.Now,
};
RecentTrainerCache.SetConsoleRegionData3DS(pk, tr);
pk.RefreshAbility(AbilityIndex >> 1);
pk.ForcePartyData();
pk.RefreshAbility(AbilityIndex >> 1);
pk.ForcePartyData();
pk.RefreshChecksum();
return pk;
}
pk.RefreshChecksum();
return pk;
}
}

View file

@ -1,60 +1,59 @@
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
namespace PKHeX.Core;
public static class QRPKM
{
public static class QRPKM
/// <summary>
/// Summarizes the details of a <see cref="PKM"/> into multiple lines for display in an image.
/// </summary>
/// <param name="pk">Pokémon to generate details for.</param>
/// <returns>Lines representing a Header, Moves, and IVs/EVs</returns>
public static string[] GetQRLines(this PKM pk)
{
/// <summary>
/// Summarizes the details of a <see cref="PKM"/> into multiple lines for display in an image.
/// </summary>
/// <param name="pkm">Pokémon to generate details for.</param>
/// <returns>Lines representing a Header, Moves, and IVs/EVs</returns>
public static string[] GetQRLines(this PKM pkm)
var s = GameInfo.Strings;
var header = GetHeader(pk, s);
string moves = string.Join(" / ", pk.Moves.Select(move => move < s.movelist.Length ? s.movelist[move] : "ERROR"));
string IVs = $"IVs: {pk.IV_HP:00}/{pk.IV_ATK:00}/{pk.IV_DEF:00}/{pk.IV_SPA:00}/{pk.IV_SPD:00}/{pk.IV_SPE:00}";
string EVs = $"EVs: {pk.EV_HP:00}/{pk.EV_ATK:00}/{pk.EV_DEF:00}/{pk.EV_SPA:00}/{pk.EV_SPD:00}/{pk.EV_SPE:00}";
return new[]
{
var s = GameInfo.Strings;
string.Join(" ", header),
moves,
IVs + " " + EVs,
};
}
var header = GetHeader(pkm, s);
string moves = string.Join(" / ", pkm.Moves.Select(move => move < s.movelist.Length ? s.movelist[move] : "ERROR"));
string IVs = $"IVs: {pkm.IV_HP:00}/{pkm.IV_ATK:00}/{pkm.IV_DEF:00}/{pkm.IV_SPA:00}/{pkm.IV_SPD:00}/{pkm.IV_SPE:00}";
string EVs = $"EVs: {pkm.EV_HP:00}/{pkm.EV_ATK:00}/{pkm.EV_DEF:00}/{pkm.EV_SPA:00}/{pkm.EV_SPD:00}/{pkm.EV_SPE:00}";
private static IEnumerable<string> GetHeader(PKM pk, GameStrings s)
{
string filename = pk.Nickname;
if ((uint) pk.Species < s.Species.Count)
{
var name = s.Species[pk.Species];
if (pk.Nickname != name)
filename += $" ({name})";
}
yield return filename;
return new[]
{
string.Join(" ", header),
moves,
IVs + " " + EVs,
};
if (pk.Format >= 3 && (uint)pk.Ability < s.Ability.Count)
yield return $"[{s.Ability[pk.Ability]}]";
var level = pk.Stat_Level;
if (level == 0)
level = pk.CurrentLevel;
yield return $"lv{level}";
if (pk.HeldItem > 0)
{
var items = s.GetItemStrings(pk.Format);
if ((uint)pk.HeldItem < items.Length)
yield return $" @ {items[pk.HeldItem]}";
}
private static IEnumerable<string> GetHeader(PKM pkm, GameStrings s)
{
string filename = pkm.Nickname;
if ((uint) pkm.Species < s.Species.Count)
{
var name = s.Species[pkm.Species];
if (pkm.Nickname != name)
filename += $" ({name})";
}
yield return filename;
if (pkm.Format >= 3 && (uint)pkm.Ability < s.Ability.Count)
yield return $"[{s.Ability[pkm.Ability]}]";
var level = pkm.Stat_Level;
if (level == 0)
level = pkm.CurrentLevel;
yield return $"lv{level}";
if (pkm.HeldItem > 0)
{
var items = s.GetItemStrings(pkm.Format);
if ((uint)pkm.HeldItem < items.Length)
yield return $" @ {items[pkm.HeldItem]}";
}
if (pkm.Format >= 3 && (uint)pkm.Nature < s.Natures.Count)
yield return s.natures[pkm.Nature];
}
if (pk.Format >= 3 && (uint)pk.Nature < s.Natures.Count)
yield return s.natures[pk.Nature];
}
}

View file

@ -1,23 +1,22 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Option to load a save file automatically to an editing environment.
/// </summary>
public enum AutoLoadSetting
{
/// <summary>
/// Option to load a save file automatically to an editing environment.
/// Doesn't auto load a save file, and instead uses a fake save file data.
/// </summary>
public enum AutoLoadSetting
{
/// <summary>
/// Doesn't auto load a save file, and instead uses a fake save file data.
/// </summary>
Disabled,
Disabled,
/// <summary>
/// Loads the most recently created Save File in the usual backup locations.
/// </summary>
RecentBackup,
/// <summary>
/// Loads the most recently created Save File in the usual backup locations.
/// </summary>
RecentBackup,
/// <summary>
/// Loads the most recently opened Save File path.
/// </summary>
LastLoaded,
}
/// <summary>
/// Loads the most recently opened Save File path.
/// </summary>
LastLoaded,
}

View file

@ -1,25 +1,24 @@
using System.Collections.Generic;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Settings used for starting up an editing environment.
/// </summary>
public interface IStartupSettings
{
/// <summary>
/// Settings used for starting up an editing environment.
/// Save File version to start the environment with if a preexisting save file has not been chosen.
/// </summary>
public interface IStartupSettings
{
/// <summary>
/// Save File version to start the environment with if a preexisting save file has not been chosen.
/// </summary>
GameVersion DefaultSaveVersion { get; }
GameVersion DefaultSaveVersion { get; }
/// <summary>
/// Method to load the environment's initial save file.
/// </summary>
AutoLoadSetting AutoLoadSaveOnStartup { get; }
/// <summary>
/// Method to load the environment's initial save file.
/// </summary>
AutoLoadSetting AutoLoadSaveOnStartup { get; }
/// <summary>
/// List of recently loaded save file paths.
/// </summary>
List<string> RecentlyLoaded { get; }
}
/// <summary>
/// List of recently loaded save file paths.
/// </summary>
List<string> RecentlyLoaded { get; }
}

View file

@ -3,126 +3,123 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Logic object that assembles parameters used for starting up an editing environment.
/// </summary>
public sealed class StartupArguments
{
public PKM? Entity { get; private set; }
public SaveFile? SAV { get; private set; }
// ReSharper disable once UnassignedGetOnlyAutoProperty
public Exception? Error { get; }
// ReSharper disable once CollectionNeverQueried.Global
public readonly List<object> Extra = new();
/// <summary>
/// Logic object that assembles parameters used for starting up an editing environment.
/// Step 1: Reads in command line arguments.
/// </summary>
public sealed class StartupArguments
public void ReadArguments(IEnumerable<string> args)
{
public PKM? Entity { get; private set; }
public SaveFile? SAV { get; private set; }
// ReSharper disable once UnassignedGetOnlyAutoProperty
public Exception? Error { get; }
// ReSharper disable once CollectionNeverQueried.Global
public readonly List<object> Extra = new();
/// <summary>
/// Step 1: Reads in command line arguments.
/// </summary>
public void ReadArguments(IEnumerable<string> args)
foreach (string path in args)
{
foreach (string path in args)
var other = FileUtil.GetSupportedFile(path, SAV);
if (other is SaveFile s)
{
var other = FileUtil.GetSupportedFile(path, SAV);
if (other is SaveFile s)
{
s.Metadata.SetExtraInfo(path);
SAV = s;
}
else if (other is PKM pkm)
{
Entity = pkm;
}
else if (other is not null)
{
Extra.Add(other);
}
s.Metadata.SetExtraInfo(path);
SAV = s;
}
else if (other is PKM pk)
{
Entity = pk;
}
else if (other is not null)
{
Extra.Add(other);
}
}
/// <summary>
/// Step 2: Reads settings config.
/// </summary>
public void ReadSettings(IStartupSettings startup)
{
if (SAV is not null)
return;
if (Entity is { } x)
SAV = ReadSettingsDefinedPKM(startup, x) ?? GetBlank(x);
else
SAV = ReadSettingsAnyPKM(startup) ?? GetBlankSaveFile(startup.DefaultSaveVersion, SAV);
}
// step 3
public void ReadTemplateIfNoEntity(string path)
{
if (Entity is not null)
return;
var sav = SAV;
if (sav is null)
throw new NullReferenceException(nameof(sav));
var pk = sav.LoadTemplate(path);
var isBlank = pk.Data.SequenceEqual(sav.BlankPKM.Data);
if (isBlank)
EntityTemplates.TemplateFields(pk, sav);
Entity = pk;
}
private static SaveFile? ReadSettingsDefinedPKM(IStartupSettings startup, PKM pkm) => startup.AutoLoadSaveOnStartup switch
{
AutoLoadSetting.RecentBackup => SaveFinder.DetectSaveFiles().FirstOrDefault(z => z.IsCompatiblePKM(pkm)),
AutoLoadSetting.LastLoaded => GetMostRecentlyLoaded(startup.RecentlyLoaded).FirstOrDefault(z => z.IsCompatiblePKM(pkm)),
_ => null,
};
private static SaveFile? ReadSettingsAnyPKM(IStartupSettings startup) => startup.AutoLoadSaveOnStartup switch
{
AutoLoadSetting.RecentBackup => SaveFinder.DetectSaveFiles().FirstOrDefault(),
AutoLoadSetting.LastLoaded => GetMostRecentlyLoaded(startup.RecentlyLoaded).FirstOrDefault(),
_ => null,
};
#region Utility
private static SaveFile GetBlank(PKM pk)
{
var ctx = pk.Context;
var ver = ctx.GetSingleGameVersion();
if (pk is { Format: 1, Japanese: true })
ver = GameVersion.BU;
return SaveUtil.GetBlankSAV(ver, pk.OT_Name, (LanguageID)pk.Language);
}
private static SaveFile GetBlankSaveFile(GameVersion version, SaveFile? current)
{
var lang = SaveUtil.GetSafeLanguage(current);
var tr = SaveUtil.GetSafeTrainerName(current, lang);
var sav = SaveUtil.GetBlankSAV(version, tr, lang);
if (sav.Version == GameVersion.Invalid) // will fail to load
sav = SaveUtil.GetBlankSAV((GameVersion)GameInfo.VersionDataSource.Max(z => z.Value), tr, lang);
return sav;
}
private static IEnumerable<SaveFile> GetMostRecentlyLoaded(IEnumerable<string> paths)
{
foreach (var path in paths)
{
if (!File.Exists(path))
continue;
var sav = SaveUtil.GetVariantSAV(path);
if (sav is null)
continue;
yield return sav;
}
}
#endregion
}
/// <summary>
/// Step 2: Reads settings config.
/// </summary>
public void ReadSettings(IStartupSettings startup)
{
if (SAV is not null)
return;
if (Entity is { } x)
SAV = ReadSettingsDefinedPKM(startup, x) ?? GetBlank(x);
else
SAV = ReadSettingsAnyPKM(startup) ?? GetBlankSaveFile(startup.DefaultSaveVersion, SAV);
}
// step 3
public void ReadTemplateIfNoEntity(string path)
{
if (Entity is not null)
return;
if (SAV is not { } sav)
return;
var pk = sav.LoadTemplate(path);
var isBlank = pk.Data.SequenceEqual(sav.BlankPKM.Data);
if (isBlank)
EntityTemplates.TemplateFields(pk, sav);
Entity = pk;
}
private static SaveFile? ReadSettingsDefinedPKM(IStartupSettings startup, PKM pk) => startup.AutoLoadSaveOnStartup switch
{
AutoLoadSetting.RecentBackup => SaveFinder.DetectSaveFiles().FirstOrDefault(z => z.IsCompatiblePKM(pk)),
AutoLoadSetting.LastLoaded => GetMostRecentlyLoaded(startup.RecentlyLoaded).FirstOrDefault(z => z.IsCompatiblePKM(pk)),
_ => null,
};
private static SaveFile? ReadSettingsAnyPKM(IStartupSettings startup) => startup.AutoLoadSaveOnStartup switch
{
AutoLoadSetting.RecentBackup => SaveFinder.DetectSaveFiles().FirstOrDefault(),
AutoLoadSetting.LastLoaded => GetMostRecentlyLoaded(startup.RecentlyLoaded).FirstOrDefault(),
_ => null,
};
#region Utility
private static SaveFile GetBlank(PKM pk)
{
var ctx = pk.Context;
var ver = ctx.GetSingleGameVersion();
if (pk is { Format: 1, Japanese: true })
ver = GameVersion.BU;
return SaveUtil.GetBlankSAV(ver, pk.OT_Name, (LanguageID)pk.Language);
}
private static SaveFile GetBlankSaveFile(GameVersion version, SaveFile? current)
{
var lang = SaveUtil.GetSafeLanguage(current);
var tr = SaveUtil.GetSafeTrainerName(current, lang);
var sav = SaveUtil.GetBlankSAV(version, tr, lang);
if (sav.Version == GameVersion.Invalid) // will fail to load
sav = SaveUtil.GetBlankSAV((GameVersion)GameInfo.VersionDataSource.Max(z => z.Value), tr, lang);
return sav;
}
private static IEnumerable<SaveFile> GetMostRecentlyLoaded(IEnumerable<string> paths)
{
foreach (var path in paths)
{
if (!File.Exists(path))
continue;
var sav = SaveUtil.GetVariantSAV(path);
if (sav is null)
continue;
yield return sav;
}
}
#endregion
}

View file

@ -20,4 +20,4 @@ public sealed class BoxManipModify : BoxManipBase
var (start, stop, _) = param;
return sav.ModifyBoxes(Action, start, stop);
}
}
}

View file

@ -25,4 +25,4 @@ public sealed class BoxManipSortComplex : BoxManipBase
var (start, stop, reverse) = param;
return sav.SortBoxes(start, stop, Method, reverse);
}
}
}

View file

@ -46,4 +46,4 @@ public enum BoxManipType
ModifyRemoveNicknames,
ModifyRemoveItem,
ModifyHeal,
}
}

View file

@ -1,64 +1,63 @@
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
namespace PKHeX.Core;
public static class BoxManipUtil
{
public static class BoxManipUtil
/// <summary>
/// Grouped categories of different <see cref="IBoxManip"/>.
/// </summary>
public static readonly IReadOnlyList<IBoxManip>[] ManipCategories =
{
/// <summary>
/// Grouped categories of different <see cref="IBoxManip"/>.
/// </summary>
public static readonly IReadOnlyList<IBoxManip>[] ManipCategories =
{
BoxManipDefaults.ClearCommon,
BoxManipDefaults.SortCommon,
BoxManipDefaults.SortAdvanced,
BoxManipDefaults.ModifyCommon,
};
BoxManipDefaults.ClearCommon,
BoxManipDefaults.SortCommon,
BoxManipDefaults.SortAdvanced,
BoxManipDefaults.ModifyCommon,
};
public static readonly string[] ManipCategoryNames =
{
"Delete",
"Sort",
"SortAdvanced",
"Modify",
};
public static readonly string[] ManipCategoryNames =
{
"Delete",
"Sort",
"SortAdvanced",
"Modify",
};
/// <summary>
/// Gets a <see cref="IBoxManip"/> reference that carries out the action of the requested <see cref="type"/>.
/// </summary>
/// <param name="type">Manipulation type.</param>
/// <returns>Reference to <see cref="IBoxManip"/>.</returns>
public static IBoxManip GetManip(this BoxManipType type) => ManipCategories.SelectMany(c => c).First(m => m.Type == type);
/// <summary>
/// Gets a <see cref="IBoxManip"/> reference that carries out the action of the requested <see cref="type"/>.
/// </summary>
/// <param name="type">Manipulation type.</param>
/// <returns>Reference to <see cref="IBoxManip"/>.</returns>
public static IBoxManip GetManip(this BoxManipType type) => ManipCategories.SelectMany(c => c).First(m => m.Type == type);
/// <summary>
/// Gets the corresponding name from <see cref="ManipCategoryNames"/> for the requested <see cref="type"/>.
/// </summary>
/// <param name="type">Manipulation type.</param>
/// <returns>Category Name</returns>
public static string? GetManipCategoryName(this BoxManipType type)
/// <summary>
/// Gets the corresponding name from <see cref="ManipCategoryNames"/> for the requested <see cref="type"/>.
/// </summary>
/// <param name="type">Manipulation type.</param>
/// <returns>Category Name</returns>
public static string? GetManipCategoryName(this BoxManipType type)
{
for (int i = 0; i < ManipCategories.Length; i++)
{
for (int i = 0; i < ManipCategories.Length; i++)
{
if (ManipCategories[i].Any(z => z.Type == type))
return ManipCategoryNames[i];
}
return null;
if (ManipCategories[i].Any(z => z.Type == type))
return ManipCategoryNames[i];
}
return null;
}
/// <summary>
/// Gets the corresponding name from <see cref="ManipCategoryNames"/> for the requested <see cref="manip"/>.
/// </summary>
/// <param name="manip">Manipulation type.</param>
/// <returns>Category Name</returns>
public static string? GetManipCategoryName(this IBoxManip manip)
/// <summary>
/// Gets the corresponding name from <see cref="ManipCategoryNames"/> for the requested <see cref="manip"/>.
/// </summary>
/// <param name="manip">Manipulation type.</param>
/// <returns>Category Name</returns>
public static string? GetManipCategoryName(this IBoxManip manip)
{
for (int i = 0; i < ManipCategories.Length; i++)
{
for (int i = 0; i < ManipCategories.Length; i++)
{
if (ManipCategories[i].Any(z => z == manip))
return ManipCategoryNames[i];
}
return null;
if (ManipCategories[i].Any(z => z == manip))
return ManipCategoryNames[i];
}
return null;
}
}

View file

@ -1,73 +1,71 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Manipulates boxes of a <see cref="SaveFile"/>.
/// </summary>
public abstract class BoxManipulator
{
protected abstract SaveFile SAV { get; }
/// <summary>
/// Manipulates boxes of a <see cref="SaveFile"/>.
/// Executes the provided <see cref="manip"/> with the provided parameters.
/// </summary>
public abstract class BoxManipulator
/// <param name="manip">Manipulation to perform on the <see cref="SAV"/> box data.</param>
/// <param name="box">Single box to modify; if <see cref="allBoxes"/> is set, this param is ignored.</param>
/// <param name="allBoxes">Indicates if all boxes are to be manipulated, or just one box.</param>
/// <param name="reverse">Manipulation action should be inverted (criteria) or reversed (sort).</param>
/// <returns>True if operation succeeded, false if no changes made.</returns>
public bool Execute(IBoxManip manip, int box, bool allBoxes, bool reverse = false)
{
protected abstract SaveFile SAV { get; }
bool usable = manip.Usable.Invoke(SAV);
if (!usable)
return false;
/// <summary>
/// Executes the provided <see cref="manip"/> with the provided parameters.
/// </summary>
/// <param name="manip">Manipulation to perform on the <see cref="SAV"/> box data.</param>
/// <param name="box">Single box to modify; if <see cref="allBoxes"/> is set, this param is ignored.</param>
/// <param name="allBoxes">Indicates if all boxes are to be manipulated, or just one box.</param>
/// <param name="reverse">Manipulation action should be inverted (criteria) or reversed (sort).</param>
/// <returns>True if operation succeeded, false if no changes made.</returns>
public bool Execute(IBoxManip manip, int box, bool allBoxes, bool reverse = false)
{
bool usable = manip.Usable.Invoke(SAV);
if (!usable)
return false;
var start = allBoxes ? 0 : box;
var stop = allBoxes ? SAV.BoxCount - 1 : box;
var param = new BoxManipParam(start, stop, reverse);
var start = allBoxes ? 0 : box;
var stop = allBoxes ? SAV.BoxCount - 1 : box;
var param = new BoxManipParam(start, stop, reverse);
var prompt = manip.GetPrompt(allBoxes);
var fail = manip.GetFail(allBoxes);
if (!CanManipulateRegion(param.Start, param.Stop, prompt, fail))
return false;
var prompt = manip.GetPrompt(allBoxes);
var fail = manip.GetFail(allBoxes);
if (!CanManipulateRegion(param.Start, param.Stop, prompt, fail))
return false;
var result = manip.Execute(SAV, param);
if (result <= 0)
return false;
var success = manip.GetSuccess(allBoxes);
FinishBoxManipulation(success, allBoxes, result);
return true;
}
/// <summary>
/// Executes the provided <see cref="type"/> with the provided parameters.
/// </summary>
/// <param name="type">Manipulation to perform on the <see cref="SAV"/> box data.</param>
/// <param name="box">Single box to modify; if <see cref="allBoxes"/> is set, this param is ignored.</param>
/// <param name="allBoxes">Indicates if all boxes are to be manipulated, or just one box.</param>
/// <param name="reverse">Manipulation action should be inverted (criteria) or reversed (sort).</param>
/// <returns>True if operation succeeded, false if no changes made.</returns>
public bool Execute(BoxManipType type, int box, bool allBoxes, bool reverse = false)
{
var manip = type.GetManip();
return Execute(manip, box, allBoxes, reverse);
}
/// <summary>
/// Sanity check for modifying the box data.
/// </summary>
/// <param name="start">Start box</param>
/// <param name="end">End box</param>
/// <param name="prompt">Message asking about the operation.</param>
/// <param name="fail">Message indicating the operation cannot be performed.</param>
/// <returns></returns>
protected virtual bool CanManipulateRegion(int start, int end, string prompt, string fail) => !SAV.IsAnySlotLockedInBox(start, end);
/// <summary>
/// Called if the <see cref="IBoxManip"/> operation modified any data.
/// </summary>
/// <param name="message">Optional message to show if applicable.</param>
/// <param name="all">Indicates if all boxes were manipulated, or just one box.</param>
/// <param name="count">Count of manipulated slots</param>
protected abstract void FinishBoxManipulation(string message, bool all, int count);
var result = manip.Execute(SAV, param);
if (result <= 0)
return false;
var success = manip.GetSuccess(allBoxes);
FinishBoxManipulation(success, allBoxes, result);
return true;
}
/// <summary>
/// Executes the provided <see cref="type"/> with the provided parameters.
/// </summary>
/// <param name="type">Manipulation to perform on the <see cref="SAV"/> box data.</param>
/// <param name="box">Single box to modify; if <see cref="allBoxes"/> is set, this param is ignored.</param>
/// <param name="allBoxes">Indicates if all boxes are to be manipulated, or just one box.</param>
/// <param name="reverse">Manipulation action should be inverted (criteria) or reversed (sort).</param>
/// <returns>True if operation succeeded, false if no changes made.</returns>
public bool Execute(BoxManipType type, int box, bool allBoxes, bool reverse = false)
{
var manip = type.GetManip();
return Execute(manip, box, allBoxes, reverse);
}
/// <summary>
/// Sanity check for modifying the box data.
/// </summary>
/// <param name="start">Start box</param>
/// <param name="end">End box</param>
/// <param name="prompt">Message asking about the operation.</param>
/// <param name="fail">Message indicating the operation cannot be performed.</param>
protected virtual bool CanManipulateRegion(int start, int end, string prompt, string fail) => !SAV.IsAnySlotLockedInBox(start, end);
/// <summary>
/// Called if the <see cref="IBoxManip"/> operation modified any data.
/// </summary>
/// <param name="message">Optional message to show if applicable.</param>
/// <param name="all">Indicates if all boxes were manipulated, or just one box.</param>
/// <param name="count">Count of manipulated slots</param>
protected abstract void FinishBoxManipulation(string message, bool all, int count);
}

View file

@ -1,279 +1,278 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core
namespace PKHeX.Core;
public sealed class EventLabelCollection
{
public sealed class EventLabelCollection
public readonly IReadOnlyList<NamedEventWork> Work;
public readonly IReadOnlyList<NamedEventValue> Flag;
public EventLabelCollection(string game, int maxFlag = int.MaxValue, int maxValue = int.MaxValue)
{
public readonly IReadOnlyList<NamedEventWork> Work;
public readonly IReadOnlyList<NamedEventValue> Flag;
public EventLabelCollection(string game, int maxFlag = int.MaxValue, int maxValue = int.MaxValue)
{
var f = GameLanguage.GetStrings(game, GameInfo.CurrentLanguage, "flags");
var c = GameLanguage.GetStrings(game, GameInfo.CurrentLanguage, "const");
Flag = GetFlags(f, maxFlag);
Work = GetValues(c, maxValue);
}
private static List<NamedEventValue> GetFlags(IReadOnlyCollection<string> strings, int maxValue)
{
var result = new List<NamedEventValue>(strings.Count);
var processed = new HashSet<int>();
foreach (var s in strings)
{
var split = s.Split('\t');
if (split.Length != 3)
continue;
var index = TryParseHexDec(split[0]);
if (index >= maxValue)
throw new ArgumentOutOfRangeException(nameof(index));
if (processed.Contains(index))
throw new ArgumentException("Already have an entry for this!", nameof(index));
var type = GetEventType(split[1]);
var desc = split[2];
var item = new NamedEventValue(desc, index, type);
result.Add(item);
processed.Add(index);
}
return result;
}
private static readonly NamedEventConst Custom = new("Custom", NamedEventConst.CustomMagicValue);
private static readonly NamedEventConst[] Empty = {Custom};
private static IReadOnlyList<NamedEventWork> GetValues(IReadOnlyCollection<string> strings, int maxValue)
{
var result = new List<NamedEventWork>(strings.Count);
var processed = new HashSet<int>();
foreach (var s in strings)
{
var split = s.Split('\t');
if (split.Length is not (3 or 4))
continue;
var index = TryParseHexDecConst(split[0]);
if (index >= maxValue)
throw new ArgumentOutOfRangeException(nameof(index));
if (processed.Contains(index))
throw new ArgumentException("Already have an entry for this!", nameof(index));
var type = GetEventType(split[1]);
var desc = split[2];
var predefined = split.Length is 3 ? Empty : GetPredefinedArray(split[3]);
var item = new NamedEventWork(desc, index, type, predefined);
result.Add(item);
processed.Add(index);
}
return result;
}
private static IReadOnlyList<NamedEventConst> GetPredefinedArray(string combined)
{
var result = new List<NamedEventConst> {Custom};
var split = combined.Split(',');
foreach (var entry in split)
{
var subsplit = entry.Split(':');
var name = subsplit[1];
var value = Convert.ToUInt16(subsplit[0]);
result.Add(new NamedEventConst(name, value));
}
return result;
}
private static int TryParseHexDec(string flag)
{
if (!flag.StartsWith("0x"))
return Convert.ToInt16(flag);
flag = flag[2..];
return Convert.ToInt16(flag, 16);
}
private static int TryParseHexDecConst(string c)
{
if (!c.StartsWith("0x40"))
return Convert.ToInt16(c);
c = c[4..];
return Convert.ToInt16(c, 16);
}
private static NamedEventType GetEventType(string s) => s.Length == 0 ? 0 : GetEventType(s[0]);
private static NamedEventType GetEventType(char c) => c switch
{
'h' => NamedEventType.HiddenItem,
'm' => NamedEventType.Misc,
'f' => NamedEventType.FlyToggle,
't' => NamedEventType.TrainerToggle,
's' => NamedEventType.StoryProgress,
'a' => NamedEventType.Achievement,
'+' => NamedEventType.Statistic,
'*' => NamedEventType.UsefulFeature,
'e' => NamedEventType.EncounterEvent,
'g' => NamedEventType.GiftAvailable,
'r' => NamedEventType.Rebattle,
_ => NamedEventType.None,
};
var f = GameLanguage.GetStrings(game, GameInfo.CurrentLanguage, "flags");
var c = GameLanguage.GetStrings(game, GameInfo.CurrentLanguage, "const");
Flag = GetFlags(f, maxFlag);
Work = GetValues(c, maxValue);
}
public sealed class EventLabelCollectionSystem
private static List<NamedEventValue> GetFlags(IReadOnlyCollection<string> strings, int maxValue)
{
public readonly IReadOnlyList<NamedEventWork> Work;
public readonly IReadOnlyList<NamedEventValue> Flag;
public readonly IReadOnlyList<NamedEventValue> System;
public EventLabelCollectionSystem(string game, int maxFlag = int.MaxValue, int maxSystem = int.MaxValue, int maxValue = int.MaxValue)
var result = new List<NamedEventValue>(strings.Count);
var processed = new HashSet<int>();
foreach (var s in strings)
{
var f = GameLanguage.GetStrings(game, GameInfo.CurrentLanguage, "flag");
var s = GameLanguage.GetStrings(game, GameInfo.CurrentLanguage, "system");
var c = GameLanguage.GetStrings(game, GameInfo.CurrentLanguage, "work");
Flag = GetFlags(f, maxFlag);
System = GetFlags(s, maxSystem);
Work = GetValues(c, maxValue);
var split = s.Split('\t');
if (split.Length != 3)
continue;
var index = TryParseHexDec(split[0]);
if (index >= maxValue)
throw new ArgumentOutOfRangeException(nameof(index), index, "Value too high.");
if (processed.Contains(index))
throw new ArgumentOutOfRangeException(nameof(index), index, "Already have an entry for this!");
var type = GetEventType(split[1]);
var desc = split[2];
var item = new NamedEventValue(desc, index, type);
result.Add(item);
processed.Add(index);
}
private static List<NamedEventValue> GetFlags(IReadOnlyCollection<string> strings, int maxValue)
{
var result = new List<NamedEventValue>(strings.Count);
var processed = new HashSet<int>();
foreach (var s in strings)
{
var split = s.Split('\t');
if (split.Length != 3)
continue;
var index = TryParseHexDec(split[0]);
if (index >= maxValue)
throw new ArgumentOutOfRangeException(nameof(index));
if (processed.Contains(index))
throw new ArgumentException("Already have an entry for this!", nameof(index));
var type = GetEventType(split[1]);
var desc = split[2];
var item = new NamedEventValue(desc, index, type);
result.Add(item);
processed.Add(index);
}
return result;
}
private static readonly NamedEventConst Custom = new("Custom", NamedEventConst.CustomMagicValue);
private static readonly NamedEventConst[] Empty = { Custom };
private static IReadOnlyList<NamedEventWork> GetValues(IReadOnlyCollection<string> strings, int maxValue)
{
var result = new List<NamedEventWork>(strings.Count);
var processed = new HashSet<int>();
foreach (var s in strings)
{
var split = s.Split('\t');
if (split.Length is not (3 or 4))
continue;
var index = TryParseHexDecConst(split[0]);
if (index >= maxValue)
throw new ArgumentOutOfRangeException(nameof(index));
if (processed.Contains(index))
throw new ArgumentException("Already have an entry for this!", nameof(index));
var type = GetEventType(split[1]);
var desc = split[2];
var predefined = split.Length is 3 ? Empty : GetPredefinedArray(split[3]);
var item = new NamedEventWork(desc, index, type, predefined);
result.Add(item);
processed.Add(index);
}
return result;
}
private static IReadOnlyList<NamedEventConst> GetPredefinedArray(string combined)
{
var result = new List<NamedEventConst> { Custom };
var split = combined.Split(',');
foreach (var entry in split)
{
var subsplit = entry.Split(':');
var name = subsplit[1];
var value = Convert.ToUInt16(subsplit[0]);
result.Add(new NamedEventConst(name, value));
}
return result;
}
private static int TryParseHexDec(string flag)
{
if (!flag.StartsWith("0x"))
return Convert.ToInt16(flag);
flag = flag[2..];
return Convert.ToInt16(flag, 16);
}
private static int TryParseHexDecConst(string c)
{
if (!c.StartsWith("0x40"))
return Convert.ToInt16(c);
c = c[4..];
return Convert.ToInt16(c, 16);
}
private static NamedEventType GetEventType(string s) => s.Length == 0 ? 0 : GetEventType(s[0]);
private static NamedEventType GetEventType(char c) => c switch
{
'h' => NamedEventType.HiddenItem,
'm' => NamedEventType.Misc,
'f' => NamedEventType.FlyToggle,
't' => NamedEventType.TrainerToggle,
's' => NamedEventType.StoryProgress,
'a' => NamedEventType.Achievement,
'+' => NamedEventType.Statistic,
'*' => NamedEventType.UsefulFeature,
'e' => NamedEventType.EncounterEvent,
'g' => NamedEventType.GiftAvailable,
'r' => NamedEventType.Rebattle,
_ => NamedEventType.None,
};
return result;
}
public enum NamedEventType
{
None,
HiddenItem,
TrainerToggle,
StoryProgress,
FlyToggle,
Misc,
Statistic,
private static readonly NamedEventConst Custom = new("Custom", NamedEventConst.CustomMagicValue);
private static readonly NamedEventConst[] Empty = {Custom};
Achievement,
UsefulFeature,
EncounterEvent,
GiftAvailable,
Rebattle = 100,
private static IReadOnlyList<NamedEventWork> GetValues(IReadOnlyCollection<string> strings, int maxValue)
{
var result = new List<NamedEventWork>(strings.Count);
var processed = new HashSet<int>();
foreach (var s in strings)
{
var split = s.Split('\t');
if (split.Length is not (3 or 4))
continue;
var index = TryParseHexDecConst(split[0]);
if (index >= maxValue)
throw new ArgumentOutOfRangeException(nameof(index), index, "Value too high.");
if (processed.Contains(index))
throw new ArgumentOutOfRangeException(nameof(index), index, "Already have an entry for this!");
var type = GetEventType(split[1]);
var desc = split[2];
var predefined = split.Length is 3 ? Empty : GetPredefinedArray(split[3]);
var item = new NamedEventWork(desc, index, type, predefined);
result.Add(item);
processed.Add(index);
}
return result;
}
public record NamedEventValue(string Name, int Index, NamedEventType Type);
public sealed record NamedEventWork(string Name, int Index, NamedEventType Type, IReadOnlyList<NamedEventConst> PredefinedValues) : NamedEventValue(Name, Index, Type);
public sealed record NamedEventConst(string Name, ushort Value)
private static IReadOnlyList<NamedEventConst> GetPredefinedArray(string combined)
{
public bool IsCustom => Value == CustomMagicValue;
public const ushort CustomMagicValue = ushort.MaxValue;
var result = new List<NamedEventConst> {Custom};
var split = combined.Split(',');
foreach (var entry in split)
{
var subsplit = entry.Split(':');
var name = subsplit[1];
var value = Convert.ToUInt16(subsplit[0], 10);
result.Add(new NamedEventConst(name, value));
}
return result;
}
private static int TryParseHexDec(string flag)
{
if (!flag.StartsWith("0x", StringComparison.Ordinal))
return Convert.ToInt16(flag, 10);
flag = flag[2..];
return Convert.ToInt16(flag, 16);
}
private static int TryParseHexDecConst(string c)
{
if (!c.StartsWith("0x40", StringComparison.Ordinal))
return Convert.ToInt16(c, 10);
c = c[4..];
return Convert.ToInt16(c, 16);
}
private static NamedEventType GetEventType(string s) => s.Length == 0 ? 0 : GetEventType(s[0]);
private static NamedEventType GetEventType(char c) => c switch
{
'h' => NamedEventType.HiddenItem,
'm' => NamedEventType.Misc,
'f' => NamedEventType.FlyToggle,
't' => NamedEventType.TrainerToggle,
's' => NamedEventType.StoryProgress,
'a' => NamedEventType.Achievement,
'+' => NamedEventType.Statistic,
'*' => NamedEventType.UsefulFeature,
'e' => NamedEventType.EncounterEvent,
'g' => NamedEventType.GiftAvailable,
'r' => NamedEventType.Rebattle,
_ => NamedEventType.None,
};
}
public sealed class EventLabelCollectionSystem
{
public readonly IReadOnlyList<NamedEventWork> Work;
public readonly IReadOnlyList<NamedEventValue> Flag;
public readonly IReadOnlyList<NamedEventValue> System;
public EventLabelCollectionSystem(string game, int maxFlag = int.MaxValue, int maxSystem = int.MaxValue, int maxValue = int.MaxValue)
{
var f = GameLanguage.GetStrings(game, GameInfo.CurrentLanguage, "flag");
var s = GameLanguage.GetStrings(game, GameInfo.CurrentLanguage, "system");
var c = GameLanguage.GetStrings(game, GameInfo.CurrentLanguage, "work");
Flag = GetFlags(f, maxFlag);
System = GetFlags(s, maxSystem);
Work = GetValues(c, maxValue);
}
private static List<NamedEventValue> GetFlags(IReadOnlyCollection<string> strings, int maxValue)
{
var result = new List<NamedEventValue>(strings.Count);
var processed = new HashSet<int>();
foreach (var s in strings)
{
var split = s.Split('\t');
if (split.Length != 3)
continue;
var index = TryParseHexDec(split[0]);
if (index >= maxValue)
throw new ArgumentOutOfRangeException(nameof(index), index, "Value too high.");
if (processed.Contains(index))
throw new ArgumentOutOfRangeException(nameof(index), index, "Already have an entry for this!");
var type = GetEventType(split[1]);
var desc = split[2];
var item = new NamedEventValue(desc, index, type);
result.Add(item);
processed.Add(index);
}
return result;
}
private static readonly NamedEventConst Custom = new("Custom", NamedEventConst.CustomMagicValue);
private static readonly NamedEventConst[] Empty = { Custom };
private static IReadOnlyList<NamedEventWork> GetValues(IReadOnlyCollection<string> strings, int maxValue)
{
var result = new List<NamedEventWork>(strings.Count);
var processed = new HashSet<int>();
foreach (var s in strings)
{
var split = s.Split('\t');
if (split.Length is not (3 or 4))
continue;
var index = TryParseHexDecConst(split[0]);
if (index >= maxValue)
throw new ArgumentOutOfRangeException(nameof(index), index, "Value too high.");
if (processed.Contains(index))
throw new ArgumentOutOfRangeException(nameof(index), index, "Already have an entry for this!");
var type = GetEventType(split[1]);
var desc = split[2];
var predefined = split.Length is 3 ? Empty : GetPredefinedArray(split[3]);
var item = new NamedEventWork(desc, index, type, predefined);
result.Add(item);
processed.Add(index);
}
return result;
}
private static IReadOnlyList<NamedEventConst> GetPredefinedArray(string combined)
{
var result = new List<NamedEventConst> { Custom };
var split = combined.Split(',');
foreach (var entry in split)
{
var subsplit = entry.Split(':');
var name = subsplit[1];
var value = Convert.ToUInt16(subsplit[0], 10);
result.Add(new NamedEventConst(name, value));
}
return result;
}
private static int TryParseHexDec(string flag)
{
if (!flag.StartsWith("0x", StringComparison.Ordinal))
return Convert.ToInt16(flag, 10);
flag = flag[2..];
return Convert.ToInt16(flag, 16);
}
private static int TryParseHexDecConst(string c)
{
if (!c.StartsWith("0x40", StringComparison.Ordinal))
return Convert.ToInt16(c, 10);
c = c[4..];
return Convert.ToInt16(c, 16);
}
private static NamedEventType GetEventType(string s) => s.Length == 0 ? 0 : GetEventType(s[0]);
private static NamedEventType GetEventType(char c) => c switch
{
'h' => NamedEventType.HiddenItem,
'm' => NamedEventType.Misc,
'f' => NamedEventType.FlyToggle,
't' => NamedEventType.TrainerToggle,
's' => NamedEventType.StoryProgress,
'a' => NamedEventType.Achievement,
'+' => NamedEventType.Statistic,
'*' => NamedEventType.UsefulFeature,
'e' => NamedEventType.EncounterEvent,
'g' => NamedEventType.GiftAvailable,
'r' => NamedEventType.Rebattle,
_ => NamedEventType.None,
};
}
public enum NamedEventType
{
None,
HiddenItem,
TrainerToggle,
StoryProgress,
FlyToggle,
Misc,
Statistic,
Achievement,
UsefulFeature,
EncounterEvent,
GiftAvailable,
Rebattle = 100,
}
public record NamedEventValue(string Name, int Index, NamedEventType Type);
public sealed record NamedEventWork(string Name, int Index, NamedEventType Type, IReadOnlyList<NamedEventConst> PredefinedValues) : NamedEventValue(Name, Index, Type);
public sealed record NamedEventConst(string Name, ushort Value)
{
public bool IsCustom => Value == CustomMagicValue;
public const ushort CustomMagicValue = ushort.MaxValue;
}

View file

@ -5,10 +5,10 @@ public class EventUnlocker8b : EventUnlocker<SAV8BS>
public EventUnlocker8b(SAV8BS sav) : base(sav) { }
public bool UnlockReadySpiritomb => SAV.UgSaveData.TalkedNPC < 32;
public bool UnlockReadyBoxLegend => SAV.Work.GetFlag(308) && SAV.Work.GetWork(84) != 5; // FE_D05R0114_SPPOKE_GET, WK_SCENE_D05R0114 (1-3 story related, 4 = captured, 5 = can retry)
public bool UnlockReadyShaymin => SAV.Work.GetFlag(545) || !(SAV.Work.GetWork(276) == 1 && SAV.Zukan.HasNationalDex && SAV.Items.GetItemQuantity(452) == 1 && SAV.Work.GetSystemFlag(5)); // HAIHUEVENT_ID_D30, Oak's Letter
public bool UnlockReadyDarkrai => SAV.Work.GetFlag(301) || !(SAV.Work.GetWork(275) == 1 && SAV.Zukan.HasNationalDex && SAV.Items.GetItemQuantity(454) == 1); // HAIHUEVENT_ID_D18, Member Card
public bool UnlockReadyArceus => SAV.Work.GetFlag(531) || !(SAV.Work.GetWork(188) == 0 && SAV.Zukan.HasNationalDex && SAV.Items.GetItemQuantity(455) == 1 && SAV.Work.GetSystemFlag(5)); // FE_D05R0116_LEGEND_CLEAR, Azure Flute
public bool UnlockReadyBoxLegend => SAV.FlagWork.GetFlag(308) && SAV.FlagWork.GetWork(84) != 5; // FE_D05R0114_SPPOKE_GET, WK_SCENE_D05R0114 (1-3 story related, 4 = captured, 5 = can retry)
public bool UnlockReadyShaymin => SAV.FlagWork.GetFlag(545) || !(SAV.FlagWork.GetWork(276) == 1 && SAV.Zukan.HasNationalDex && SAV.Items.GetItemQuantity(452) == 1 && SAV.FlagWork.GetSystemFlag(5)); // HAIHUEVENT_ID_D30, Oak's Letter
public bool UnlockReadyDarkrai => SAV.FlagWork.GetFlag(301) || !(SAV.FlagWork.GetWork(275) == 1 && SAV.Zukan.HasNationalDex && SAV.Items.GetItemQuantity(454) == 1); // HAIHUEVENT_ID_D18, Member Card
public bool UnlockReadyArceus => SAV.FlagWork.GetFlag(531) || !(SAV.FlagWork.GetWork(188) == 0 && SAV.Zukan.HasNationalDex && SAV.Items.GetItemQuantity(455) == 1 && SAV.FlagWork.GetSystemFlag(5)); // FE_D05R0116_LEGEND_CLEAR, Azure Flute
// 0 = inactive, 1 = roaming, 2 = KOed, 3 = captured
public bool UnlockReadyRoamerMesprit => SAV.Encounter.Roamer1Encount != 1;
@ -18,10 +18,10 @@ public class EventUnlocker8b : EventUnlocker<SAV8BS>
public void UnlockBoxLegend()
{
SAV.Work.SetFlag(308, false); // captured
SAV.Work.SetFlag(393, false); // clear vanish
SAV.Work.SetFlag(1623, false); // can retry
SAV.Work.SetWork(84, 5); // can retry
SAV.FlagWork.SetFlag(308, false); // captured
SAV.FlagWork.SetFlag(393, false); // clear vanish
SAV.FlagWork.SetFlag(1623, false); // can retry
SAV.FlagWork.SetWork(84, 5); // can retry
}
public void UnlockSpiritomb()
@ -34,29 +34,29 @@ public class EventUnlocker8b : EventUnlocker<SAV8BS>
public void UnlockShaymin()
{
SAV.Zukan.HasNationalDex = true; // dex
SAV.Work.SetSystemFlag(5, true); // clear
SAV.Work.SetWork(276, 1); // haihu
SAV.FlagWork.SetSystemFlag(5, true); // clear
SAV.FlagWork.SetWork(276, 1); // haihu
SAV.Items.SetItemQuantity(452, 1); // letter
SAV.Work.SetFlag(545, false); // clear vanish
SAV.FlagWork.SetFlag(545, false); // clear vanish
}
public void UnlockDarkrai()
{
SAV.Zukan.HasNationalDex = true; // dex
SAV.Work.SetWork(275, 1); // haihu
SAV.FlagWork.SetWork(275, 1); // haihu
SAV.Items.SetItemQuantity(454, 1); // member
SAV.Work.SetFlag(301, false); // clear vanish
SAV.FlagWork.SetFlag(301, false); // clear vanish
}
public void UnlockArceus()
{
SAV.Zukan.HasNationalDex = true; // dex
SAV.Items.SetItemQuantity(455, 1); // flute
SAV.Work.SetSystemFlag(5, true); // clear
SAV.Work.SetFlag(1508, true); // wildcard
SAV.Work.SetFlag(244, false); // captured
SAV.Work.SetFlag(531, false); // clear vanish
SAV.Work.SetWork(188, 0); // staircase
SAV.FlagWork.SetSystemFlag(5, true); // clear
SAV.FlagWork.SetFlag(1508, true); // wildcard
SAV.FlagWork.SetFlag(244, false); // captured
SAV.FlagWork.SetFlag(531, false); // clear vanish
SAV.FlagWork.SetWork(188, 0); // staircase
}
public void UnlockZones()
@ -66,16 +66,16 @@ public class EventUnlocker8b : EventUnlocker<SAV8BS>
for (int i = ZONE_START; i <= ZONE_END; i++)
{
SAV.Work.SetSystemFlag(i, true);
SAV.FlagWork.SetSystemFlag(i, true);
}
// uncover hidden zones
SAV.Work.SetWork(278, 1); // Fullmoon Island
SAV.Work.SetWork(279, 1); // Newmoon Island
SAV.Work.SetWork(280, 1); // Spring Path / Sendoff Spring
SAV.Work.SetWork(281, 1); // Seabreak Path / Flower Paradise
SAV.Work.SetWork(291, 1); // Pokémon League (Victory Road entrance)
SAV.Work.SetWork(292, 1); // Ramanas Park
SAV.FlagWork.SetWork(278, 1); // Fullmoon Island
SAV.FlagWork.SetWork(279, 1); // Newmoon Island
SAV.FlagWork.SetWork(280, 1); // Spring Path / Sendoff Spring
SAV.FlagWork.SetWork(281, 1); // Seabreak Path / Flower Paradise
SAV.FlagWork.SetWork(291, 1); // Pokémon League (Victory Road entrance)
SAV.FlagWork.SetWork(292, 1); // Ramanas Park
}
public void RespawnRoamer()
@ -86,15 +86,15 @@ public class EventUnlocker8b : EventUnlocker<SAV8BS>
public void RespawnMesprit()
{
SAV.Work.SetFlag(249, false); // clear met
SAV.Work.SetFlag(420, false); // clear vanish
SAV.FlagWork.SetFlag(249, false); // clear met
SAV.FlagWork.SetFlag(420, false); // clear vanish
SAV.Encounter.Roamer1Encount = 0; // not actively roaming
}
public void RespawnCresselia()
{
SAV.Work.SetFlag(245, false); // clear met
SAV.Work.SetFlag(532, false); // clear vanish
SAV.FlagWork.SetFlag(245, false); // clear met
SAV.FlagWork.SetFlag(532, false); // clear vanish
SAV.Encounter.Roamer2Encount = 0; // not actively roaming
}
}

View file

@ -36,7 +36,7 @@ public sealed class EventBlockDiff<T, T2> : IEventWorkDiff where T : IEventFlagA
Diff(t1, t2);
}
private EventWorkDiffCompatibility SanityCheckSaveInfo(T s1, T s2)
private static EventWorkDiffCompatibility SanityCheckSaveInfo(T s1, T s2)
{
if (s1.GetType() != s2.GetType())
return DifferentGameGroup;
@ -74,9 +74,9 @@ public sealed class EventBlockDiff<T, T2> : IEventWorkDiff where T : IEventFlagA
public IReadOnlyList<string> Summarize()
{
var fOn = SetFlags.Select(z => z.ToString());
var fOff = ClearedFlags.Select(z => z.ToString());
var wt = WorkChanged.Select((z, _) => z.ToString());
var fOn = SetFlags.Select(z => $"{z}");
var fOff = ClearedFlags.Select(z => $"{z}");
var wt = WorkChanged.Select((z, _) => $"{z}");
var list = new List<string> { "Flags: ON", "=========" };
list.AddRange(fOn);

View file

@ -46,9 +46,9 @@ public sealed class EventWorkDiff8b : IEventWorkDiff
return;
}
DiffSavesFlag(s1.Work, s2.Work, SetFlags, ClearedFlags);
DiffSavesSystem(s1.Work, s2.Work, SetSystem, ClearedSystem);
DiffSavesWork(s1.Work, s2.Work, WorkChanged, WorkDiff);
DiffSavesFlag(s1.FlagWork, s2.FlagWork, SetFlags, ClearedFlags);
DiffSavesSystem(s1.FlagWork, s2.FlagWork, SetSystem, ClearedSystem);
DiffSavesWork(s1.FlagWork, s2.FlagWork, WorkChanged, WorkDiff);
S1 = s1;
}

View file

@ -1,16 +1,15 @@
using System.Collections.Generic;
namespace PKHeX.Core
{
/// <summary>
/// Event Flag that toggles certain features / entities on and off.
/// </summary>
public sealed class EventFlag : EventVar
{
public bool Flag;
namespace PKHeX.Core;
public EventFlag(int index, EventVarType t, IReadOnlyList<string> pieces) : base(index, t, pieces[1])
{
}
/// <summary>
/// Event Flag that toggles certain features / entities on and off.
/// </summary>
public sealed class EventFlag : EventVar
{
public bool Flag;
public EventFlag(int index, EventVarType t, IReadOnlyList<string> pieces) : base(index, t, pieces[1])
{
}
}
}

View file

@ -1,35 +1,34 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Event variable used to determine game events.
/// </summary>
public abstract class EventVar
{
/// <summary>
/// Event variable used to determine game events.
/// Name of event variable
/// </summary>
public abstract class EventVar
public readonly string Name;
/// <summary>
/// Type of event variable
/// </summary>
public readonly EventVarType Type;
/// <summary>
/// Raw index within the event variable (type) region.
/// </summary>
public int RawIndex;
/// <summary>
/// Unpacked structure's index.
/// </summary>
public readonly int RelativeIndex;
protected EventVar(int index, EventVarType t, string name)
{
/// <summary>
/// Name of event variable
/// </summary>
public readonly string Name;
/// <summary>
/// Type of event variable
/// </summary>
public readonly EventVarType Type;
/// <summary>
/// Raw index within the event variable (type) region.
/// </summary>
public int RawIndex;
/// <summary>
/// Unpacked structure's index.
/// </summary>
public readonly int RelativeIndex;
protected EventVar(int index, EventVarType t, string name)
{
RelativeIndex = index;
Type = t;
Name = name;
}
RelativeIndex = index;
Type = t;
Name = name;
}
}
}

View file

@ -1,12 +1,11 @@
using System.Collections.Generic;
namespace PKHeX.Core
{
public sealed class EventVarGroup
{
public readonly EventVarType Type;
public readonly List<EventVar> Vars = new();
namespace PKHeX.Core;
public EventVarGroup(EventVarType type) => Type = type;
}
}
public sealed class EventVarGroup
{
public readonly EventVarType Type;
public readonly List<EventVar> Vars = new();
public EventVarGroup(EventVarType type) => Type = type;
}

View file

@ -1,32 +1,31 @@
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Event number storage for more complex logic events.
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class EventWork<T> : EventVar where T : struct
{
/// <summary>
/// Event number storage for more complex logic events.
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class EventWork<T> : EventVar where T : struct
public T Value;
public readonly IList<EventWorkVal> Options = new List<EventWorkVal> { new() };
public EventWork(int index, EventVarType t, IReadOnlyList<string> pieces) : base(index, t, pieces[1])
{
public T Value;
public readonly IList<EventWorkVal> Options = new List<EventWorkVal> { new() };
if (pieces.Count < 3)
return;
public EventWork(int index, EventVarType t, IReadOnlyList<string> pieces) : base(index, t, pieces[1])
var items = pieces[2]
.Split(',')
.Select(z => z.Split(':'))
.Where(z => z.Length == 2);
foreach (var s in items)
{
if (pieces.Count < 3)
return;
var items = pieces[2]
.Split(',')
.Select(z => z.Split(':'))
.Where(z => z.Length == 2);
foreach (var s in items)
{
if (int.TryParse(s[0], out var value))
Options.Add(new EventWorkVal(s[1], value));
}
if (int.TryParse(s[0], out var value))
Options.Add(new EventWorkVal(s[1], value));
}
}
}
}

View file

@ -4,148 +4,147 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using static PKHeX.Core.MessageStrings;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Provides utility for various <see cref="EventWork{T}"/> logic.
/// </summary>
public static class EventWorkUtil
{
/// <summary>
/// Provides utility for various <see cref="EventWork{T}"/> logic.
/// </summary>
public static class EventWorkUtil
private static readonly Dictionary<char, EventVarType> TypeDict = new()
{
private static readonly Dictionary<char, EventVarType> TypeDict = new()
['z'] = EventVarType.Zone,
['s'] = EventVarType.System,
['v'] = EventVarType.Vanish,
['c'] = EventVarType.Scene,
['e'] = EventVarType.Event,
};
private static bool GetIndex(string l, out int index, out EventVarType type)
{
var typeChar = l[0];
if (!TypeDict.TryGetValue(typeChar, out type))
{
['z'] = EventVarType.Zone,
['s'] = EventVarType.System,
['v'] = EventVarType.Vanish,
['c'] = EventVarType.Scene,
['e'] = EventVarType.Event,
};
private static bool GetIndex(string l, out int index, out EventVarType type)
{
var typeChar = l[0];
if (!TypeDict.TryGetValue(typeChar, out type))
{
Debug.WriteLine($"Rejected line due to bad type: {typeChar}");
index = -1;
return false;
}
var indexString = l[1..];
if (int.TryParse(indexString, out index))
return true;
Debug.WriteLine($"Rejected line due to bad index: {indexString}");
Debug.WriteLine($"Rejected line due to bad type: {typeChar}");
index = -1;
return false;
}
/// <summary>
/// Parses and converts <see cref="lines"/> into <see cref="EventVarGroup"/> values with the provided <see cref="constructor"/>.
/// </summary>
/// <param name="lines">Lines to parse</param>
/// <param name="constructor">Object constructor</param>
/// <returns>Converted lines grouped together by <see cref="EventVarType"/></returns>
public static List<EventVarGroup> GetVars(IEnumerable<string> lines, Func<int, EventVarType, string[], EventVar> constructor)
{
var list = new List<EventVarGroup>();
foreach (var l in lines)
{
var split = l.Split('\t');
if (split.Length < 2 || split[0].Length < 2)
continue;
if (!GetIndex(split[0], out var index, out var type))
continue;
var group = list.Find(z => z.Type == type);
if (group == null)
{
group = new EventVarGroup(type);
list.Add(group);
}
var entry = constructor(index, type, split);
group.Vars.Add(entry);
}
return list;
}
/// <summary>
/// Compares a <see cref="before"/> and <see cref="after"/> <see cref="IEventWork{T}"/> object of the same type to find <see cref="EventFlag"/> changes.
/// </summary>
/// <param name="before">Data before the event was triggered</param>
/// <param name="after">Data after the event was triggered</param>
/// <param name="on">List of flags that were turned on</param>
/// <param name="off">List of flags that were turned off</param>
public static void DiffSavesFlag(IEventFlag before, IEventFlag after, List<int> on, List<int> off)
{
int max = before.CountFlag;
for (int i = 0; i < max; i++)
{
var b = before.GetFlag(i);
var a = after.GetFlag(i);
if (b == a)
continue;
var arr = a ? on : off;
arr.Add(i);
}
}
/// <summary>
/// Compares a <see cref="before"/> and <see cref="after"/> <see cref="IEventWork{T}"/> object of the same type to find <see cref="EventFlag"/> changes.
/// </summary>
/// <param name="before">Data before the event was triggered</param>
/// <param name="after">Data after the event was triggered</param>
/// <param name="on">List of flags that were turned on</param>
/// <param name="off">List of flags that were turned off</param>
public static void DiffSavesSystem(ISystemFlag before, ISystemFlag after, List<int> on, List<int> off)
{
int max = before.CountSystem;
for (int i = 0; i < max; i++)
{
var b = before.GetSystemFlag(i);
var a = after.GetSystemFlag(i);
if (b == a)
continue;
var arr = a ? on : off;
arr.Add(i);
}
}
/// <summary>
/// Compares a <see cref="before"/> and <see cref="after"/> <see cref="IEventWork{T}"/> object of the same type to find <see cref="EventWork{T}"/> changes.
/// </summary>
/// <typeparam name="T">Type of value used by <see cref="EventWork{T}"/></typeparam>
/// <param name="before">Data before the event was triggered</param>
/// <param name="after">Data after the event was triggered</param>
/// <param name="changed"><see cref="EventVar.RawIndex"/> values that changed</param>
/// <param name="changes">Summary of the <see cref="EventWork{T}"/> value change</param>
public static void DiffSavesWork<T>(IEventWork<T> before, IEventWork<T> after, List<int> changed, List<string> changes) where T : unmanaged, IEquatable<T>
{
int max = before.CountWork;
for (int i = 0; i < max; i++)
{
var b = before.GetWork(i);
var a = after.GetWork(i);
if (b.Equals(a))
continue;
changed.Add(i);
changes.Add($"{b} => {a}");
}
}
public static bool SanityCheckSaveInfo<T>(T s1, T s2, [NotNullWhen(false)] out string? Message) where T : SaveFile
{
if (s1.GetType() != s2.GetType())
{ Message = string.Format(MsgSaveDifferentTypes, $"S1: {s1.GetType().Name}", $"S2: {s2.GetType().Name}"); return false; }
if (s1.Version != s2.Version)
{ Message = string.Format(MsgSaveDifferentVersions, $"S1: {s1.Version}", $"S2: {s2.Version}"); return false; }
Message = null;
var indexString = l[1..];
if (int.TryParse(indexString, out index))
return true;
Debug.WriteLine($"Rejected line due to bad index: {indexString}");
return false;
}
/// <summary>
/// Parses and converts <see cref="lines"/> into <see cref="EventVarGroup"/> values with the provided <see cref="constructor"/>.
/// </summary>
/// <param name="lines">Lines to parse</param>
/// <param name="constructor">Object constructor</param>
/// <returns>Converted lines grouped together by <see cref="EventVarType"/></returns>
public static List<EventVarGroup> GetVars(IEnumerable<string> lines, Func<int, EventVarType, string[], EventVar> constructor)
{
var list = new List<EventVarGroup>();
foreach (var l in lines)
{
var split = l.Split('\t');
if (split.Length < 2 || split[0].Length < 2)
continue;
if (!GetIndex(split[0], out var index, out var type))
continue;
var group = list.Find(z => z.Type == type);
if (group == null)
{
group = new EventVarGroup(type);
list.Add(group);
}
var entry = constructor(index, type, split);
group.Vars.Add(entry);
}
return list;
}
/// <summary>
/// Compares a <see cref="before"/> and <see cref="after"/> <see cref="IEventWork{T}"/> object of the same type to find <see cref="EventFlag"/> changes.
/// </summary>
/// <param name="before">Data before the event was triggered</param>
/// <param name="after">Data after the event was triggered</param>
/// <param name="on">List of flags that were turned on</param>
/// <param name="off">List of flags that were turned off</param>
public static void DiffSavesFlag(IEventFlag before, IEventFlag after, List<int> on, List<int> off)
{
int max = before.CountFlag;
for (int i = 0; i < max; i++)
{
var b = before.GetFlag(i);
var a = after.GetFlag(i);
if (b == a)
continue;
var arr = a ? on : off;
arr.Add(i);
}
}
/// <summary>
/// Compares a <see cref="before"/> and <see cref="after"/> <see cref="IEventWork{T}"/> object of the same type to find <see cref="EventFlag"/> changes.
/// </summary>
/// <param name="before">Data before the event was triggered</param>
/// <param name="after">Data after the event was triggered</param>
/// <param name="on">List of flags that were turned on</param>
/// <param name="off">List of flags that were turned off</param>
public static void DiffSavesSystem(ISystemFlag before, ISystemFlag after, List<int> on, List<int> off)
{
int max = before.CountSystem;
for (int i = 0; i < max; i++)
{
var b = before.GetSystemFlag(i);
var a = after.GetSystemFlag(i);
if (b == a)
continue;
var arr = a ? on : off;
arr.Add(i);
}
}
/// <summary>
/// Compares a <see cref="before"/> and <see cref="after"/> <see cref="IEventWork{T}"/> object of the same type to find <see cref="EventWork{T}"/> changes.
/// </summary>
/// <typeparam name="T">Type of value used by <see cref="EventWork{T}"/></typeparam>
/// <param name="before">Data before the event was triggered</param>
/// <param name="after">Data after the event was triggered</param>
/// <param name="changed"><see cref="EventVar.RawIndex"/> values that changed</param>
/// <param name="changes">Summary of the <see cref="EventWork{T}"/> value change</param>
public static void DiffSavesWork<T>(IEventWork<T> before, IEventWork<T> after, List<int> changed, List<string> changes) where T : unmanaged, IEquatable<T>
{
int max = before.CountWork;
for (int i = 0; i < max; i++)
{
var b = before.GetWork(i);
var a = after.GetWork(i);
if (b.Equals(a))
continue;
changed.Add(i);
changes.Add($"{b} => {a}");
}
}
public static bool SanityCheckSaveInfo<T>(T s1, T s2, [NotNullWhen(false)] out string? Message) where T : SaveFile
{
if (s1.GetType() != s2.GetType())
{ Message = string.Format(MsgSaveDifferentTypes, $"S1: {s1.GetType().Name}", $"S2: {s2.GetType().Name}"); return false; }
if (s1.Version != s2.Version)
{ Message = string.Format(MsgSaveDifferentVersions, $"S1: {s1.Version}", $"S2: {s2.Version}"); return false; }
Message = null;
return true;
}
}

View file

@ -1,25 +1,24 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Represents a known value for a <see cref="EventWork{T}"/> of type <see cref="int"/>.
/// </summary>
public sealed class EventWorkVal
{
/// <summary>
/// Represents a known value for a <see cref="EventWork{T}"/> of type <see cref="int"/>.
/// </summary>
public sealed class EventWorkVal
public readonly bool Custom;
public readonly string Text;
public readonly int Value;
public EventWorkVal()
{
public readonly bool Custom;
public readonly string Text;
public readonly int Value;
public EventWorkVal()
{
Custom = true;
Text = nameof(Custom);
Value = int.MinValue;
}
public EventWorkVal(string text, int val)
{
Text = text;
Value = val;
}
Custom = true;
Text = nameof(Custom);
Value = int.MinValue;
}
}
public EventWorkVal(string text, int val)
{
Text = text;
Value = val;
}
}

View file

@ -1,66 +1,65 @@
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Editor object that unpacks <see cref="EventWork{T}"/> into flags & work groups, and handles value get/set operations.
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class SplitEventEditor<T> where T : struct
{
/// <summary>
/// Editor object that unpacks <see cref="EventWork{T}"/> into flags & work groups, and handles value get/set operations.
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class SplitEventEditor<T> where T : struct
public readonly IList<EventVarGroup> Work;
public readonly IList<EventVarGroup> Flag;
public readonly IEventVar<T> Block;
public SplitEventEditor(IEventVar<T> block, IEnumerable<string> work, IEnumerable<string> flag)
{
public readonly IList<EventVarGroup> Work;
public readonly IList<EventVarGroup> Flag;
public readonly IEventVar<T> Block;
Block = block;
// load lines
var workLines = work.Where(z => !string.IsNullOrWhiteSpace(z) && z.Length > 5);
Work = EventWorkUtil.GetVars(workLines, (index, t, data) => new EventWork<T>(index, t, data));
var flagLines = flag.Where(z => !string.IsNullOrWhiteSpace(z) && z.Length > 5);
Flag = EventWorkUtil.GetVars(flagLines, (index, t, data) => new EventFlag(index, t, data));
public SplitEventEditor(IEventVar<T> block, IEnumerable<string> work, IEnumerable<string> flag)
// initialize lines
foreach (var group in Work)
{
Block = block;
// load lines
var workLines = work.Where(z => !string.IsNullOrWhiteSpace(z) && z.Length > 5);
Work = EventWorkUtil.GetVars(workLines, (index, t, data) => new EventWork<T>(index, t, data));
var flagLines = flag.Where(z => !string.IsNullOrWhiteSpace(z) && z.Length > 5);
Flag = EventWorkUtil.GetVars(flagLines, (index, t, data) => new EventFlag(index, t, data));
// initialize lines
foreach (var group in Work)
foreach (var item in group.Vars)
{
foreach (var item in group.Vars)
{
item.RawIndex = block.GetWorkRawIndex(item.Type, item.RelativeIndex);
((EventWork<T>)item).Value = block.GetWork(item.RawIndex);
}
}
foreach (var group in Flag)
{
foreach (var item in group.Vars)
{
item.RawIndex = block.GetFlagRawIndex(item.Type, item.RelativeIndex);
((EventFlag)item).Flag = block.GetFlag(item.RawIndex);
}
item.RawIndex = block.GetWorkRawIndex(item.Type, item.RelativeIndex);
((EventWork<T>)item).Value = block.GetWork(item.RawIndex);
}
}
/// <summary>
/// Writes all of the updated event values back to the block.
/// </summary>
public void Save()
foreach (var group in Flag)
{
foreach (var g in Work)
foreach (var item in group.Vars)
{
foreach (var item in g.Vars)
{
var value = ((EventWork<T>)item).Value;
Block.SetWork(item.RawIndex, value);
}
item.RawIndex = block.GetFlagRawIndex(item.Type, item.RelativeIndex);
((EventFlag)item).Flag = block.GetFlag(item.RawIndex);
}
foreach (var g in Flag)
}
}
/// <summary>
/// Writes all of the updated event values back to the block.
/// </summary>
public void Save()
{
foreach (var g in Work)
{
foreach (var item in g.Vars)
{
foreach (var item in g.Vars)
{
var value = ((EventFlag)item).Flag;
Block.SetFlag(item.RawIndex, value);
}
var value = ((EventWork<T>)item).Value;
Block.SetWork(item.RawIndex, value);
}
}
foreach (var g in Flag)
{
foreach (var item in g.Vars)
{
var value = ((EventFlag)item).Flag;
Block.SetFlag(item.RawIndex, value);
}
}
}

View file

@ -1,9 +1,8 @@
namespace PKHeX.Core
namespace PKHeX.Core;
public interface INamedFolderPath
{
public interface INamedFolderPath
{
string Path { get; }
string DisplayText { get; }
bool Custom { get; }
}
string Path { get; }
string DisplayText { get; }
bool Custom { get; }
}

View file

@ -1,49 +1,49 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Bindable Summary of a <see cref="SaveFile"/> for use in a sortable table.
/// </summary>
public sealed class SavePreview
{
/// <summary>
/// Bindable Summary of a <see cref="SaveFile"/> for use in a sortable table.
/// </summary>
public sealed class SavePreview
public readonly SaveFile Save;
public readonly string FilePath;
public SavePreview(SaveFile sav, string parent, string path)
{
public readonly SaveFile Save;
public readonly string FilePath;
public SavePreview(SaveFile sav, string parent, string path)
{
Save = sav;
Folder = parent;
FilePath = path;
}
public SavePreview(SaveFile sav, List<INamedFolderPath> paths)
{
var meta = sav.Metadata;
var dir = meta.FileFolder;
const string notFound = "???";
var parent = dir == null ? notFound : paths.Find(z => dir.StartsWith(z.Path))?.DisplayText ?? new DirectoryInfo(dir).Name;
Save = sav;
Folder = parent;
FilePath = meta.FilePath ?? notFound;
}
public string OT => Save.OT;
public int G => Save.Generation;
public GameVersion Game => Save.Version;
public string Played => Save.PlayTimeString.PadLeft(9, '0');
public string FileTime => File.GetLastWriteTimeUtc(FilePath).ToString("yyyy.MM.dd:hh:mm:ss");
public string TID => Save.Generation >= 7 ? Save.TrainerID7.ToString("000000") : Save.TID.ToString("00000");
public string SID => Save.Generation >= 7 ? Save.TrainerSID7.ToString("0000") : Save.SID.ToString("00000");
// ReSharper disable once MemberCanBePrivate.Local
// ReSharper disable once UnusedAutoPropertyAccessor.Local
public string Folder { get; }
public string Name => Path.GetFileName(FilePath);
Save = sav;
Folder = parent;
FilePath = path;
}
public SavePreview(SaveFile sav, List<INamedFolderPath> paths)
{
var meta = sav.Metadata;
var dir = meta.FileFolder;
const string notFound = "???";
var parent = dir == null ? notFound : paths.Find(z => dir.StartsWith(z.Path, StringComparison.Ordinal))?.DisplayText ?? new DirectoryInfo(dir).Name;
Save = sav;
Folder = parent;
FilePath = meta.FilePath ?? notFound;
}
public string OT => Save.OT;
public int G => Save.Generation;
public GameVersion Game => Save.Version;
public string Played => Save.PlayTimeString.PadLeft(9, '0');
public string FileTime => File.GetLastWriteTimeUtc(FilePath).ToString("yyyy.MM.dd:hh:mm:ss");
public string TID => Save.Generation >= 7 ? Save.TrainerID7.ToString("000000") : Save.TID.ToString("00000");
public string SID => Save.Generation >= 7 ? Save.TrainerSID7.ToString("0000") : Save.SID.ToString("00000");
// ReSharper disable once MemberCanBePrivate.Local
// ReSharper disable once UnusedAutoPropertyAccessor.Local
public string Folder { get; }
public string Name => Path.GetFileName(FilePath);
}

View file

@ -1,58 +1,57 @@
using System;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Represents a Box Editor that loads the contents for easy manipulation.
/// </summary>
public sealed class BoxEdit
{
/// <summary>
/// Represents a Box Editor that loads the contents for easy manipulation.
/// </summary>
public sealed class BoxEdit
private readonly SaveFile SAV;
private readonly PKM[] CurrentContents;
public BoxEdit(SaveFile sav)
{
private readonly SaveFile SAV;
private readonly PKM[] CurrentContents;
SAV = sav;
CurrentContents = new PKM[sav.BoxSlotCount];
}
public BoxEdit(SaveFile sav)
public void Reload() => LoadBox(CurrentBox);
public void LoadBox(int box)
{
if ((uint)box >= SAV.BoxCount)
throw new ArgumentOutOfRangeException(nameof(box));
SAV.AddBoxData(CurrentContents, box, 0);
CurrentBox = box;
}
public PKM this[int index]
{
get => CurrentContents[index];
set
{
SAV = sav;
CurrentContents = new PKM[sav.BoxSlotCount];
}
public void Reload() => LoadBox(CurrentBox);
public void LoadBox(int box)
{
if ((uint)box >= SAV.BoxCount)
throw new ArgumentOutOfRangeException(nameof(box));
SAV.AddBoxData(CurrentContents, box, 0);
CurrentBox = box;
}
public PKM this[int index]
{
get => CurrentContents[index];
set
{
CurrentContents[index] = value;
SAV.SetBoxSlotAtIndex(value, index);
}
}
public int CurrentBox { get; private set; }
public int BoxWallpaper { get => SAV.GetBoxWallpaper(CurrentBox); set => SAV.SetBoxWallpaper(CurrentBox, value); }
public string BoxName { get => SAV.GetBoxName(CurrentBox); set => SAV.SetBoxName(CurrentBox, value); }
public int MoveLeft(bool max = false)
{
int newBox = max ? 0 : (CurrentBox + SAV.BoxCount - 1) % SAV.BoxCount;
LoadBox(newBox);
return newBox;
}
public int MoveRight(bool max = false)
{
int newBox = max ? SAV.BoxCount - 1 : (CurrentBox + 1) % SAV.BoxCount;
LoadBox(newBox);
return newBox;
CurrentContents[index] = value;
SAV.SetBoxSlotAtIndex(value, index);
}
}
public int CurrentBox { get; private set; }
public int BoxWallpaper { get => SAV.GetBoxWallpaper(CurrentBox); set => SAV.SetBoxWallpaper(CurrentBox, value); }
public string BoxName { get => SAV.GetBoxName(CurrentBox); set => SAV.SetBoxName(CurrentBox, value); }
public int MoveLeft(bool max = false)
{
int newBox = max ? 0 : (CurrentBox + SAV.BoxCount - 1) % SAV.BoxCount;
LoadBox(newBox);
return newBox;
}
public int MoveRight(bool max = false)
{
int newBox = max ? SAV.BoxCount - 1 : (CurrentBox + 1) % SAV.BoxCount;
LoadBox(newBox);
return newBox;
}
}

View file

@ -1,228 +1,227 @@
using System.Collections.Generic;
namespace PKHeX.Core
namespace PKHeX.Core;
public static partial class Extensions
{
public static partial class Extensions
public static IReadOnlyList<PKM> GetAllPKM(this SaveFile sav)
{
public static IReadOnlyList<PKM> GetAllPKM(this SaveFile sav)
{
var result = new List<PKM>();
if (sav.HasBox)
result.AddRange(sav.BoxData);
if (sav.HasParty)
result.AddRange(sav.PartyData);
var result = new List<PKM>();
if (sav.HasBox)
result.AddRange(sav.BoxData);
if (sav.HasParty)
result.AddRange(sav.PartyData);
var extra = sav.GetExtraPKM();
result.AddRange(extra);
result.RemoveAll(z => z.Species == 0);
return result;
var extra = sav.GetExtraPKM();
result.AddRange(extra);
result.RemoveAll(z => z.Species == 0);
return result;
}
public static PKM[] GetExtraPKM(this SaveFile sav) => sav.GetExtraPKM(sav.GetExtraSlots());
public static PKM[] GetExtraPKM(this SaveFile sav, IList<SlotInfoMisc> slots)
{
var arr = new PKM[slots.Count];
for (int i = 0; i < slots.Count; i++)
arr[i] = slots[i].Read(sav);
return arr;
}
public static List<SlotInfoMisc> GetExtraSlots(this SaveFile sav, bool all = false)
{
var slots = GetExtraSlotsUnsafe(sav, all);
for (int i = 0; i < slots.Count;)
{
if (slots[i].Offset < 0)
slots.RemoveAt(i);
else
++i;
}
return slots;
}
public static PKM[] GetExtraPKM(this SaveFile sav) => sav.GetExtraPKM(sav.GetExtraSlots());
private static readonly List<SlotInfoMisc> None = new();
public static PKM[] GetExtraPKM(this SaveFile sav, IList<SlotInfoMisc> slots)
private static List<SlotInfoMisc> GetExtraSlotsUnsafe(SaveFile sav, bool all) => sav switch
{
SAV2 sav2 => GetExtraSlots2(sav2),
SAV3 sav3 => GetExtraSlots3(sav3),
SAV4 sav4 => GetExtraSlots4(sav4),
SAV5 sav5 => GetExtraSlots5(sav5),
SAV6XY xy => GetExtraSlots6XY(xy),
SAV6AO xy => GetExtraSlots6AO(xy),
SAV7 sav7 => GetExtraSlots7(sav7, all),
SAV7b lgpe => GetExtraSlots7b(lgpe),
SAV8SWSH ss => GetExtraSlots8(ss),
SAV8BS bs => GetExtraSlots8b(bs),
SAV8LA la => GetExtraSlots8a(la),
_ => None,
};
private static List<SlotInfoMisc> GetExtraSlots2(SAV2 sav)
{
return new List<SlotInfoMisc>
{
var arr = new PKM[slots.Count];
for (int i = 0; i < slots.Count; i++)
arr[i] = slots[i].Read(sav);
return arr;
}
new(sav.Data, 0, sav.GetDaycareSlotOffset(0, 2)) {Type = StorageSlotType.Daycare }, // egg
};
}
public static List<SlotInfoMisc> GetExtraSlots(this SaveFile sav, bool all = false)
private static List<SlotInfoMisc> GetExtraSlots3(SAV3 sav)
{
if (sav is not SAV3FRLG)
return None;
return new List<SlotInfoMisc>
{
var slots = GetExtraSlotsUnsafe(sav, all);
for (int i = 0; i < slots.Count;)
new(sav.Large, 0, 0x3C98) {Type = StorageSlotType.Daycare},
};
}
private static List<SlotInfoMisc> GetExtraSlots4(SAV4 sav)
{
var list = new List<SlotInfoMisc>
{
new(sav.General, 0, sav.GTS) {Type = StorageSlotType.GTS},
};
if (sav is SAV4HGSS)
list.Add(new SlotInfoMisc(sav.General, 1, SAV4HGSS.WalkerPair) {Type = StorageSlotType.Misc});
return list;
}
private static List<SlotInfoMisc> GetExtraSlots5(SAV5 sav)
{
return new List<SlotInfoMisc>
{
new(sav.Data, 0, sav.GTS) {Type = StorageSlotType.GTS},
new(sav.Data, 0, sav.Fused) {Type = StorageSlotType.Fused},
new(sav.Data, 0, sav.PGL) { Type = StorageSlotType.Misc },
new(sav.Data, 0, sav.GetBattleBoxSlot(0)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 1, sav.GetBattleBoxSlot(1)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 2, sav.GetBattleBoxSlot(2)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 3, sav.GetBattleBoxSlot(3)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 4, sav.GetBattleBoxSlot(4)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 5, sav.GetBattleBoxSlot(5)) {Type = StorageSlotType.BattleBox},
};
}
private static List<SlotInfoMisc> GetExtraSlots6XY(SAV6XY sav)
{
return new List<SlotInfoMisc>
{
new(sav.Data, 0, sav.GTS) {Type = StorageSlotType.GTS},
new(sav.Data, 0, sav.Fused) {Type = StorageSlotType.Fused},
new(sav.Data, 0, sav.SUBE.Give) {Type = StorageSlotType.Misc}, // Old Man
new(sav.Data, 0, sav.GetBattleBoxSlot(0)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 1, sav.GetBattleBoxSlot(1)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 2, sav.GetBattleBoxSlot(2)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 3, sav.GetBattleBoxSlot(3)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 4, sav.GetBattleBoxSlot(4)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 5, sav.GetBattleBoxSlot(5)) {Type = StorageSlotType.BattleBox},
};
}
private static List<SlotInfoMisc> GetExtraSlots6AO(SAV6AO sav)
{
return new List<SlotInfoMisc>
{
new(sav.Data, 0, SAV6AO.GTS) {Type = StorageSlotType.GTS},
new(sav.Data, 0, SAV6AO.Fused) {Type = StorageSlotType.Fused},
new(sav.Data, 0, sav.SUBE.Give) {Type = StorageSlotType.Misc},
new(sav.Data, 0, sav.GetBattleBoxSlot(0)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 1, sav.GetBattleBoxSlot(1)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 2, sav.GetBattleBoxSlot(2)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 3, sav.GetBattleBoxSlot(3)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 4, sav.GetBattleBoxSlot(4)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 5, sav.GetBattleBoxSlot(5)) {Type = StorageSlotType.BattleBox},
};
}
private static List<SlotInfoMisc> GetExtraSlots7(SAV7 sav, bool all)
{
var list = new List<SlotInfoMisc>
{
new(sav.Data, 0, sav.AllBlocks[07].Offset) {Type = StorageSlotType.GTS},
new(sav.Data, 0, sav.GetFusedSlotOffset(0)) {Type = StorageSlotType.Fused},
};
if (sav is SAV7USUM uu)
{
list.AddRange(new[]
{
if (slots[i].Offset < 0)
slots.RemoveAt(i);
else
++i;
}
return slots;
new SlotInfoMisc(uu.Data, 1, uu.GetFusedSlotOffset(1)) {Type = StorageSlotType.Fused},
new SlotInfoMisc(uu.Data, 2, uu.GetFusedSlotOffset(2)) {Type = StorageSlotType.Fused},
});
var ba = uu.BattleAgency;
list.AddRange(new[]
{
new SlotInfoMisc(uu.Data, 0, ba.GetSlotOffset(0)) {Type = StorageSlotType.Misc},
new SlotInfoMisc(uu.Data, 1, ba.GetSlotOffset(1)) {Type = StorageSlotType.Misc},
new SlotInfoMisc(uu.Data, 2, ba.GetSlotOffset(2)) {Type = StorageSlotType.Misc},
});
}
private static readonly List<SlotInfoMisc> None = new();
if (!all)
return list;
private static List<SlotInfoMisc> GetExtraSlotsUnsafe(SaveFile sav, bool all) => sav switch
for (int i = 0; i < ResortSave7.ResortCount; i++)
list.Add(new SlotInfoMisc(sav.Data, i, sav.ResortSave.GetResortSlotOffset(i)) { Type = StorageSlotType.Resort });
return list;
}
private static List<SlotInfoMisc> GetExtraSlots7b(SAV7b sav)
{
return new List<SlotInfoMisc>
{
SAV2 sav2 => GetExtraSlots2(sav2),
SAV3 sav3 => GetExtraSlots3(sav3),
SAV4 sav4 => GetExtraSlots4(sav4),
SAV5 sav5 => GetExtraSlots5(sav5),
SAV6XY xy => GetExtraSlots6XY(xy),
SAV6AO xy => GetExtraSlots6AO(xy),
SAV7 sav7 => GetExtraSlots7(sav7, all),
SAV7b lgpe => GetExtraSlots7b(lgpe),
SAV8SWSH ss => GetExtraSlots8(ss),
SAV8BS bs => GetExtraSlots8b(bs),
SAV8LA la => GetExtraSlots8a(la),
_ => None,
new(sav.Data, 0, sav.Blocks.GetBlockOffset(BelugaBlockIndex.Daycare) + 8, true) {Type = StorageSlotType.Daycare},
};
}
private static List<SlotInfoMisc> GetExtraSlots8(ISaveBlock8Main sav)
{
var fused = sav.Fused;
var dc = sav.Daycare;
var list = new List<SlotInfoMisc>
{
new(fused.Data, 0, Fused8.GetFusedSlotOffset(0), true) {Type = StorageSlotType.Fused},
new(fused.Data, 1, Fused8.GetFusedSlotOffset(1), true) {Type = StorageSlotType.Fused},
new(fused.Data, 2, Fused8.GetFusedSlotOffset(2), true) {Type = StorageSlotType.Fused},
new(dc.Data, 0, Daycare8.GetDaycareSlotOffset(0, 0)) {Type = StorageSlotType.Daycare},
new(dc.Data, 0, Daycare8.GetDaycareSlotOffset(0, 1)) {Type = StorageSlotType.Daycare},
new(dc.Data, 0, Daycare8.GetDaycareSlotOffset(1, 0)) {Type = StorageSlotType.Daycare},
new(dc.Data, 0, Daycare8.GetDaycareSlotOffset(1, 1)) {Type = StorageSlotType.Daycare},
};
private static List<SlotInfoMisc> GetExtraSlots2(SAV2 sav)
if (sav is SAV8SWSH {SaveRevision: >= 2} s8)
{
return new List<SlotInfoMisc>
{
new(sav.Data, 0, sav.GetDaycareSlotOffset(0, 2)) {Type = StorageSlotType.Daycare }, // egg
};
var block = s8.Blocks.GetBlockSafe(SaveBlockAccessor8SWSH.KFusedCalyrex);
var c = new SlotInfoMisc(block.Data, 3, 0, true) {Type = StorageSlotType.Fused};
list.Insert(3, c);
}
private static List<SlotInfoMisc> GetExtraSlots3(SAV3 sav)
return list;
}
private static List<SlotInfoMisc> GetExtraSlots8b(SAV8BS sav)
{
return new List<SlotInfoMisc>
{
if (sav is not SAV3FRLG)
return None;
return new List<SlotInfoMisc>
{
new(sav.Large, 0, 0x3C98) {Type = StorageSlotType.Daycare},
};
}
new(sav.Data, 0, sav.UgSaveData.GetSlotOffset(0), true) { Type = StorageSlotType.Misc },
new(sav.Data, 1, sav.UgSaveData.GetSlotOffset(1), true) { Type = StorageSlotType.Misc },
new(sav.Data, 2, sav.UgSaveData.GetSlotOffset(2), true) { Type = StorageSlotType.Misc },
new(sav.Data, 3, sav.UgSaveData.GetSlotOffset(3), true) { Type = StorageSlotType.Misc },
new(sav.Data, 4, sav.UgSaveData.GetSlotOffset(4), true) { Type = StorageSlotType.Misc },
new(sav.Data, 5, sav.UgSaveData.GetSlotOffset(5), true) { Type = StorageSlotType.Misc },
new(sav.Data, 6, sav.UgSaveData.GetSlotOffset(6), true) { Type = StorageSlotType.Misc },
new(sav.Data, 7, sav.UgSaveData.GetSlotOffset(7), true) { Type = StorageSlotType.Misc },
new(sav.Data, 8, sav.UgSaveData.GetSlotOffset(8), true) { Type = StorageSlotType.Misc },
};
}
private static List<SlotInfoMisc> GetExtraSlots4(SAV4 sav)
{
var list = new List<SlotInfoMisc>
{
new(sav.General, 0, sav.GTS) {Type = StorageSlotType.GTS},
};
if (sav is SAV4HGSS)
list.Add(new SlotInfoMisc(sav.General, 1, SAV4HGSS.WalkerPair) {Type = StorageSlotType.Misc});
return list;
}
private static List<SlotInfoMisc> GetExtraSlots5(SAV5 sav)
{
return new List<SlotInfoMisc>
{
new(sav.Data, 0, sav.GTS) {Type = StorageSlotType.GTS},
new(sav.Data, 0, sav.Fused) {Type = StorageSlotType.Fused},
new(sav.Data, 0, sav.PGL) { Type = StorageSlotType.Misc },
new(sav.Data, 0, sav.GetBattleBoxSlot(0)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 1, sav.GetBattleBoxSlot(1)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 2, sav.GetBattleBoxSlot(2)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 3, sav.GetBattleBoxSlot(3)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 4, sav.GetBattleBoxSlot(4)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 5, sav.GetBattleBoxSlot(5)) {Type = StorageSlotType.BattleBox},
};
}
private static List<SlotInfoMisc> GetExtraSlots6XY(SAV6XY sav)
{
return new List<SlotInfoMisc>
{
new(sav.Data, 0, sav.GTS) {Type = StorageSlotType.GTS},
new(sav.Data, 0, sav.Fused) {Type = StorageSlotType.Fused},
new(sav.Data, 0, sav.SUBE.Give) {Type = StorageSlotType.Misc}, // Old Man
new(sav.Data, 0, sav.GetBattleBoxSlot(0)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 1, sav.GetBattleBoxSlot(1)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 2, sav.GetBattleBoxSlot(2)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 3, sav.GetBattleBoxSlot(3)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 4, sav.GetBattleBoxSlot(4)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 5, sav.GetBattleBoxSlot(5)) {Type = StorageSlotType.BattleBox},
};
}
private static List<SlotInfoMisc> GetExtraSlots6AO(SAV6AO sav)
{
return new List<SlotInfoMisc>
{
new(sav.Data, 0, SAV6AO.GTS) {Type = StorageSlotType.GTS},
new(sav.Data, 0, SAV6AO.Fused) {Type = StorageSlotType.Fused},
new(sav.Data, 0, sav.SUBE.Give) {Type = StorageSlotType.Misc},
new(sav.Data, 0, sav.GetBattleBoxSlot(0)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 1, sav.GetBattleBoxSlot(1)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 2, sav.GetBattleBoxSlot(2)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 3, sav.GetBattleBoxSlot(3)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 4, sav.GetBattleBoxSlot(4)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 5, sav.GetBattleBoxSlot(5)) {Type = StorageSlotType.BattleBox},
};
}
private static List<SlotInfoMisc> GetExtraSlots7(SAV7 sav, bool all)
{
var list = new List<SlotInfoMisc>
{
new(sav.Data, 0, sav.AllBlocks[07].Offset) {Type = StorageSlotType.GTS},
new(sav.Data, 0, sav.GetFusedSlotOffset(0)) {Type = StorageSlotType.Fused},
};
if (sav is SAV7USUM uu)
{
list.AddRange(new[]
{
new SlotInfoMisc(uu.Data, 1, uu.GetFusedSlotOffset(1)) {Type = StorageSlotType.Fused},
new SlotInfoMisc(uu.Data, 2, uu.GetFusedSlotOffset(2)) {Type = StorageSlotType.Fused},
});
var ba = uu.BattleAgency;
list.AddRange(new[]
{
new SlotInfoMisc(uu.Data, 0, ba.GetSlotOffset(0)) {Type = StorageSlotType.Misc},
new SlotInfoMisc(uu.Data, 1, ba.GetSlotOffset(1)) {Type = StorageSlotType.Misc},
new SlotInfoMisc(uu.Data, 2, ba.GetSlotOffset(2)) {Type = StorageSlotType.Misc},
});
}
if (!all)
return list;
for (int i = 0; i < ResortSave7.ResortCount; i++)
list.Add(new SlotInfoMisc(sav.Data, i, sav.ResortSave.GetResortSlotOffset(i)) { Type = StorageSlotType.Resort });
return list;
}
private static List<SlotInfoMisc> GetExtraSlots7b(SAV7b sav)
{
return new List<SlotInfoMisc>
{
new(sav.Data, 0, sav.Blocks.GetBlockOffset(BelugaBlockIndex.Daycare) + 8, true) {Type = StorageSlotType.Daycare},
};
}
private static List<SlotInfoMisc> GetExtraSlots8(ISaveBlock8Main sav)
{
var fused = sav.Fused;
var dc = sav.Daycare;
var list = new List<SlotInfoMisc>
{
new(fused.Data, 0, Fused8.GetFusedSlotOffset(0), true) {Type = StorageSlotType.Fused},
new(fused.Data, 1, Fused8.GetFusedSlotOffset(1), true) {Type = StorageSlotType.Fused},
new(fused.Data, 2, Fused8.GetFusedSlotOffset(2), true) {Type = StorageSlotType.Fused},
new(dc.Data, 0, Daycare8.GetDaycareSlotOffset(0, 0)) {Type = StorageSlotType.Daycare},
new(dc.Data, 0, Daycare8.GetDaycareSlotOffset(0, 1)) {Type = StorageSlotType.Daycare},
new(dc.Data, 0, Daycare8.GetDaycareSlotOffset(1, 0)) {Type = StorageSlotType.Daycare},
new(dc.Data, 0, Daycare8.GetDaycareSlotOffset(1, 1)) {Type = StorageSlotType.Daycare},
};
if (sav is SAV8SWSH {SaveRevision: >= 2} s8)
{
var block = s8.Blocks.GetBlockSafe(SaveBlockAccessor8SWSH.KFusedCalyrex);
var c = new SlotInfoMisc(block.Data, 3, 0, true) {Type = StorageSlotType.Fused};
list.Insert(3, c);
}
return list;
}
private static List<SlotInfoMisc> GetExtraSlots8b(SAV8BS sav)
{
return new List<SlotInfoMisc>
{
new(sav.Data, 0, sav.UgSaveData.GetSlotOffset(0), true) { Type = StorageSlotType.Misc },
new(sav.Data, 1, sav.UgSaveData.GetSlotOffset(1), true) { Type = StorageSlotType.Misc },
new(sav.Data, 2, sav.UgSaveData.GetSlotOffset(2), true) { Type = StorageSlotType.Misc },
new(sav.Data, 3, sav.UgSaveData.GetSlotOffset(3), true) { Type = StorageSlotType.Misc },
new(sav.Data, 4, sav.UgSaveData.GetSlotOffset(4), true) { Type = StorageSlotType.Misc },
new(sav.Data, 5, sav.UgSaveData.GetSlotOffset(5), true) { Type = StorageSlotType.Misc },
new(sav.Data, 6, sav.UgSaveData.GetSlotOffset(6), true) { Type = StorageSlotType.Misc },
new(sav.Data, 7, sav.UgSaveData.GetSlotOffset(7), true) { Type = StorageSlotType.Misc },
new(sav.Data, 8, sav.UgSaveData.GetSlotOffset(8), true) { Type = StorageSlotType.Misc },
};
}
private static List<SlotInfoMisc> GetExtraSlots8a(SAV8LA sav)
{
return new List<SlotInfoMisc>();
}
private static List<SlotInfoMisc> GetExtraSlots8a(SAV8LA sav)
{
return new List<SlotInfoMisc>();
}
}

View file

@ -1,54 +1,51 @@
using System.Collections.Generic;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Slot Viewer that shows many slots of <see cref="PKM"/> data.
/// </summary>
/// <typeparam name="T">Object that displays the <see cref="PKM"/> slot.</typeparam>
public interface ISlotViewer<T>
{
/// <summary>
/// Slot Viewer that shows many slots of <see cref="PKM"/> data.
/// Current index the viewer is viewing.
/// </summary>
/// <typeparam name="T">Object that displays the <see cref="PKM"/> slot.</typeparam>
public interface ISlotViewer<T>
{
/// <summary>
/// Current index the viewer is viewing.
/// </summary>
int ViewIndex { get; }
int ViewIndex { get; }
/// <summary>
/// Notification that the <see cref="previous"/> slot is no longer the last interacted slot.
/// </summary>
/// <param name="previous">Last interacted slot</param>
void NotifySlotOld(ISlotInfo previous);
/// <summary>
/// Notification that the <see cref="previous"/> slot is no longer the last interacted slot.
/// </summary>
/// <param name="previous">Last interacted slot</param>
void NotifySlotOld(ISlotInfo previous);
/// <summary>
/// Notification that the <see cref="slot"/> has just been interacted with.
/// </summary>
/// <param name="slot">Last interacted slot</param>
/// <param name="type">Last interacted slot interaction type</param>
/// <param name="pkm">Last interacted slot interaction data</param>
void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pkm);
/// <summary>
/// Notification that the <see cref="slot"/> has just been interacted with.
/// </summary>
/// <param name="slot">Last interacted slot</param>
/// <param name="type">Last interacted slot interaction type</param>
/// <param name="pk">Last interacted slot interaction data</param>
void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pk);
/// <summary>
/// Gets the information for the requested slot's view picture.
/// </summary>
/// <param name="view"></param>
/// <returns></returns>
ISlotInfo GetSlotData(T view);
/// <summary>
/// Gets the information for the requested slot's view picture.
/// </summary>
/// <param name="view"></param>
ISlotInfo GetSlotData(T view);
/// <summary>
/// Gets the index of the <see cref="T"/> view within the <see cref="ISlotViewer{T}"/>'s list of slots.
/// </summary>
/// <param name="slot"></param>
/// <returns></returns>
int GetViewIndex(ISlotInfo slot);
/// <summary>
/// Gets the index of the <see cref="T"/> view within the <see cref="ISlotViewer{T}"/>'s list of slots.
/// </summary>
/// <param name="slot"></param>
int GetViewIndex(ISlotInfo slot);
/// <summary>
/// List of <see cref="T"/> views the <see cref="ISlotViewer{T}"/> is showing.
/// </summary>
IList<T> SlotPictureBoxes { get; }
/// <summary>
/// List of <see cref="T"/> views the <see cref="ISlotViewer{T}"/> is showing.
/// </summary>
IList<T> SlotPictureBoxes { get; }
/// <summary>
/// Save data the <see cref="ISlotViewer{T}"/> is showing data from.
/// </summary>
SaveFile SAV { get; }
}
}
/// <summary>
/// Save data the <see cref="ISlotViewer{T}"/> is showing data from.
/// </summary>
SaveFile SAV { get; }
}

View file

@ -1,48 +1,47 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Data representing info for an individual slot.
/// </summary>
public interface ISlotInfo
{
/// <summary>
/// Data representing info for an individual slot.
/// Indicates the type of format the slot originates. Useful for legality purposes.
/// </summary>
public interface ISlotInfo
{
/// <summary>
/// Indicates the type of format the slot originates. Useful for legality purposes.
/// </summary>
SlotOrigin Origin { get; }
SlotOrigin Origin { get; }
/// <summary>
/// Differentiating slot number from other infos of the same type.
/// </summary>
int Slot { get; }
/// <summary>
/// Differentiating slot number from other infos of the same type.
/// </summary>
int Slot { get; }
/// <summary>
/// Indicates if this slot can write to the requested <see cref="sav"/>.
/// </summary>
/// <param name="sav">Save file to try writing to.</param>
/// <returns>True if can write to</returns>
bool CanWriteTo(SaveFile sav);
/// <summary>
/// Indicates if this slot can write to the requested <see cref="sav"/>.
/// </summary>
/// <param name="sav">Save file to try writing to.</param>
/// <returns>True if can write to</returns>
bool CanWriteTo(SaveFile sav);
/// <summary>
/// Checks if the <see cref="pkm"/> can be written to the <see cref="sav"/> for this slot.
/// </summary>
/// <param name="sav">Save file to try writing to.</param>
/// <param name="pkm">Entity data to try writing.</param>
/// <returns>True if can write to</returns>
WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pkm);
/// <summary>
/// Checks if the <see cref="pk"/> can be written to the <see cref="sav"/> for this slot.
/// </summary>
/// <param name="sav">Save file to try writing to.</param>
/// <param name="pk">Entity data to try writing.</param>
/// <returns>True if can write to</returns>
WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pk);
/// <summary>
/// Tries writing the <see cref="pkm"/> to the <see cref="sav"/>.
/// </summary>
/// <param name="sav">Save file to try writing to.</param>
/// <param name="pkm">Entity data to try writing.</param>
/// <param name="setting">Setting to use when importing the <see cref="pkm"/> data</param>
/// <returns>Returns false if it did not succeed.</returns>
bool WriteTo(SaveFile sav, PKM pkm, PKMImportSetting setting = PKMImportSetting.UseDefault);
/// <summary>
/// Tries writing the <see cref="pk"/> to the <see cref="sav"/>.
/// </summary>
/// <param name="sav">Save file to try writing to.</param>
/// <param name="pk">Entity data to try writing.</param>
/// <param name="setting">Setting to use when importing the <see cref="pk"/> data</param>
/// <returns>Returns false if it did not succeed.</returns>
bool WriteTo(SaveFile sav, PKM pk, PKMImportSetting setting = PKMImportSetting.UseDefault);
/// <summary>
/// Reads a <see cref="PKM"/> from the <see cref="sav"/>.
/// </summary>
/// <param name="sav">Save file to read from.</param>
PKM Read(SaveFile sav);
}
/// <summary>
/// Reads a <see cref="PKM"/> from the <see cref="sav"/>.
/// </summary>
/// <param name="sav">Save file to read from.</param>
PKM Read(SaveFile sav);
}

View file

@ -1,95 +1,94 @@
using System;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Contains slot data and metadata indicating where the <see cref="PKM"/> originated from.
/// </summary>
public sealed class SlotCache : IComparable<SlotCache>
{
/// <summary>
/// Contains slot data and metadata indicating where the <see cref="PKM"/> originated from.
/// Information regarding how the <see cref="Entity"/> was obtained.
/// </summary>
public sealed class SlotCache : IComparable<SlotCache>
public readonly ISlotInfo Source;
/// <summary>
/// Save File reference that obtained the <see cref="Entity"/>.
/// </summary>
public readonly SaveFile SAV;
/// <summary>
/// Data that was loaded.
/// </summary>
public readonly PKM Entity;
private static readonly FakeSaveFile NoSaveFile = new();
public SlotCache(SlotInfoFile source, PKM entity)
{
/// <summary>
/// Information regarding how the <see cref="Entity"/> was obtained.
/// </summary>
public readonly ISlotInfo Source;
Source = source;
Entity = entity;
SAV = NoSaveFile;
}
/// <summary>
/// Save File reference that obtained the <see cref="Entity"/>.
/// </summary>
public readonly SaveFile SAV;
public SlotCache(ISlotInfo source, PKM entity, SaveFile sav)
{
Source = source;
Entity = entity;
SAV = sav;
}
/// <summary>
/// Data that was loaded.
/// </summary>
public readonly PKM Entity;
public string Identify() => GetFileName() + Source switch
{
SlotInfoBox box => $"[{box.Box + 1:00}] ({SAV.GetBoxName(box.Box)})-{box.Slot + 1:00}: {Entity.FileName}",
SlotInfoFile file => $"File: {file.Path}",
SlotInfoMisc misc => $"{misc.Type}-{misc.Slot}: {Entity.FileName}",
SlotInfoParty party => $"Party: {party.Slot}: {Entity.FileName}",
_ => throw new ArgumentOutOfRangeException(nameof(Source)),
};
private static readonly FakeSaveFile NoSaveFile = new();
private string GetFileName()
{
var fn = SAV.Metadata.FileName;
if (fn is null)
return string.Empty;
return $"{fn} @ ";
}
public SlotCache(SlotInfoFile source, PKM entity)
{
Source = source;
Entity = entity;
SAV = NoSaveFile;
}
public bool IsDataValid() => Entity.Species != 0 && Entity.Valid;
public SlotCache(ISlotInfo source, PKM entity, SaveFile sav)
{
Source = source;
Entity = entity;
SAV = sav;
}
public int CompareTo(SlotCache? other)
{
if (other is null)
return -1;
return string.CompareOrdinal(Identify(), other.Identify());
}
public string Identify() => GetFileName() + Source switch
{
SlotInfoBox box => $"[{box.Box + 1:00}] ({SAV.GetBoxName(box.Box)})-{box.Slot + 1:00}: {Entity.FileName}",
SlotInfoFile file => $"File: {file.Path}",
SlotInfoMisc misc => $"{misc.Type}-{misc.Slot}: {Entity.FileName}",
SlotInfoParty party => $"Party: {party.Slot}: {Entity.FileName}",
_ => throw new ArgumentOutOfRangeException(nameof(Source)),
};
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
return true;
return obj is SlotCache c && c.Identify() == Identify();
}
private string GetFileName()
{
var fn = SAV.Metadata.FileName;
if (fn is null)
return string.Empty;
return $"{fn} @ ";
}
public override int GetHashCode() => Identify().GetHashCode();
public static bool operator ==(SlotCache left, SlotCache right) => left.Equals(right);
public static bool operator !=(SlotCache left, SlotCache right) => !(left == right);
public static bool operator <(SlotCache left, SlotCache right) => left.CompareTo(right) < 0;
public static bool operator <=(SlotCache left, SlotCache right) => left.CompareTo(right) <= 0;
public static bool operator >(SlotCache left, SlotCache right) => left.CompareTo(right) > 0;
public static bool operator >=(SlotCache left, SlotCache right) => left.CompareTo(right) >= 0;
public bool IsDataValid() => Entity.Species != 0 && Entity.Valid;
public int CompareTo(SlotCache? other)
{
if (other is null)
return -1;
return string.CompareOrdinal(Identify(), other.Identify());
}
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
return true;
return obj is SlotCache c && c.Identify() == Identify();
}
public override int GetHashCode() => Identify().GetHashCode();
public static bool operator ==(SlotCache left, SlotCache right) => left.Equals(right);
public static bool operator !=(SlotCache left, SlotCache right) => !(left == right);
public static bool operator <(SlotCache left, SlotCache right) => left.CompareTo(right) < 0;
public static bool operator <=(SlotCache left, SlotCache right) => left.CompareTo(right) <= 0;
public static bool operator >(SlotCache left, SlotCache right) => left.CompareTo(right) > 0;
public static bool operator >=(SlotCache left, SlotCache right) => left.CompareTo(right) >= 0;
public int CompareToSpeciesForm(SlotCache other)
{
var s1 = Entity;
var s2 = other.Entity;
var c1 = s1.Species.CompareTo(s2.Species);
if (c1 != 0)
return c1;
var c2 = s1.Form.CompareTo(s2.Form);
if (c2 != 0)
return c2;
return CompareTo(other);
}
public int CompareToSpeciesForm(SlotCache other)
{
var s1 = Entity;
var s2 = other.Entity;
var c1 = s1.Species.CompareTo(s2.Species);
if (c1 != 0)
return c1;
var c2 = s1.Form.CompareTo(s2.Form);
if (c2 != 0)
return c2;
return CompareTo(other);
}
}

View file

@ -1,20 +1,19 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Box Data <see cref="ISlotInfo"/>
/// </summary>
public sealed record SlotInfoBox(int Box, int Slot) : ISlotInfo
{
/// <summary>
/// Box Data <see cref="ISlotInfo"/>
/// </summary>
public sealed record SlotInfoBox(int Box, int Slot) : ISlotInfo
public SlotOrigin Origin => SlotOrigin.Box;
public bool CanWriteTo(SaveFile sav) => sav.HasBox && !sav.IsSlotLocked(Box, Slot);
public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pk) => WriteBlockedMessage.None;
public bool WriteTo(SaveFile sav, PKM pk, PKMImportSetting setting = PKMImportSetting.UseDefault)
{
public SlotOrigin Origin => SlotOrigin.Box;
public bool CanWriteTo(SaveFile sav) => sav.HasBox && !sav.IsSlotLocked(Box, Slot);
public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pkm) => WriteBlockedMessage.None;
public bool WriteTo(SaveFile sav, PKM pkm, PKMImportSetting setting = PKMImportSetting.UseDefault)
{
sav.SetBoxSlotAtIndex(pkm, Box, Slot, setting, setting);
return true;
}
public PKM Read(SaveFile sav) => sav.GetBoxSlotAtIndex(Box, Slot);
sav.SetBoxSlotAtIndex(pk, Box, Slot, setting, setting);
return true;
}
public PKM Read(SaveFile sav) => sav.GetBoxSlotAtIndex(Box, Slot);
}

View file

@ -1,13 +1,12 @@
namespace PKHeX.Core
{
public sealed record SlotInfoFile(string Path) : ISlotInfo
{
public SlotOrigin Origin => SlotOrigin.Party;
public int Slot => 0;
namespace PKHeX.Core;
public bool CanWriteTo(SaveFile sav) => false;
public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pkm) => WriteBlockedMessage.InvalidDestination;
public bool WriteTo(SaveFile sav, PKM pkm, PKMImportSetting setting = PKMImportSetting.UseDefault) => false;
public PKM Read(SaveFile sav) => sav.BlankPKM;
}
public sealed record SlotInfoFile(string Path) : ISlotInfo
{
public SlotOrigin Origin => SlotOrigin.Party;
public int Slot => 0;
public bool CanWriteTo(SaveFile sav) => false;
public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pk) => WriteBlockedMessage.InvalidDestination;
public bool WriteTo(SaveFile sav, PKM pk, PKMImportSetting setting = PKMImportSetting.UseDefault) => false;
public PKM Read(SaveFile sav) => sav.BlankPKM;
}

View file

@ -2,160 +2,159 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
namespace PKHeX.Core
namespace PKHeX.Core;
public static class SlotInfoLoader
{
public static class SlotInfoLoader
// The "Add" method isn't shared for any interface... so we'll just do this twice.
#region ConcurrentBag Implementation
public static void AddFromSaveFile(SaveFile sav, ConcurrentBag<SlotCache> db)
{
// The "Add" method isn't shared for any interface... so we'll just do this twice.
if (sav.HasBox)
AddBoxData(sav, db);
#region ConcurrentBag Implementation
public static void AddFromSaveFile(SaveFile sav, ConcurrentBag<SlotCache> db)
{
if (sav.HasBox)
AddBoxData(sav, db);
if (sav.HasParty)
AddPartyData(sav, db);
if (sav.HasParty)
AddPartyData(sav, db);
AddExtraData(sav, db);
}
public static void AddFromLocalFile(string file, ConcurrentBag<SlotCache> db, ITrainerInfo dest, ICollection<string> validExtensions)
{
var fi = new FileInfo(file);
if (!validExtensions.Contains(fi.Extension) || !EntityDetection.IsSizePlausible(fi.Length))
return;
var data = File.ReadAllBytes(file);
_ = FileUtil.TryGetPKM(data, out var pk, fi.Extension, dest);
if (pk?.Species is not > 0)
return;
var info = new SlotInfoFile(file);
var entry = new SlotCache(info, pk);
db.Add(entry);
}
private static void AddBoxData(SaveFile sav, ConcurrentBag<SlotCache> db)
{
var bd = sav.BoxData;
var bc = sav.BoxCount;
var sc = sav.BoxSlotCount;
int ctr = 0;
for (int box = 0; box < bc; box++)
{
for (int slot = 0; slot < sc; slot++, ctr++)
{
var ident = new SlotInfoBox(box, slot);
var result = new SlotCache(ident, bd[ctr], sav);
db.Add(result);
}
}
}
private static void AddPartyData(SaveFile sav, ConcurrentBag<SlotCache> db)
{
var pd = sav.PartyData;
for (var index = 0; index < pd.Count; index++)
{
var pk = pd[index];
if (pk.Species == 0)
continue;
var ident = new SlotInfoParty(index);
var result = new SlotCache(ident, pk, sav);
db.Add(result);
}
}
private static void AddExtraData(SaveFile sav, ConcurrentBag<SlotCache> db)
{
var extra = sav.GetExtraSlots(true);
foreach (var x in extra)
{
var pk = x.Read(sav);
if (pk.Species == 0)
continue;
var result = new SlotCache(x, pk, sav);
db.Add(result);
}
}
#endregion
#region ICollection Implementation
public static void AddFromSaveFile(SaveFile sav, ICollection<SlotCache> db)
{
if (sav.HasBox)
AddBoxData(sav, db);
if (sav.HasParty)
AddPartyData(sav, db);
AddExtraData(sav, db);
}
public static void AddFromLocalFile(string file, ICollection<SlotCache> db, ITrainerInfo dest, ICollection<string> validExtensions)
{
var fi = new FileInfo(file);
if (!validExtensions.Contains(fi.Extension) || !EntityDetection.IsSizePlausible(fi.Length))
return;
var data = File.ReadAllBytes(file);
_ = FileUtil.TryGetPKM(data, out var pk, fi.Extension, dest);
if (pk?.Species is not > 0)
return;
var info = new SlotInfoFile(file);
var entry = new SlotCache(info, pk);
db.Add(entry);
}
public static void AddBoxData(SaveFile sav, ICollection<SlotCache> db)
{
var bd = sav.BoxData;
var bc = sav.BoxCount;
var sc = sav.BoxSlotCount;
int ctr = 0;
for (int box = 0; box < bc; box++)
{
for (int slot = 0; slot < sc; slot++, ctr++)
{
var ident = new SlotInfoBox(box, slot);
var result = new SlotCache(ident, bd[ctr], sav);
db.Add(result);
}
}
}
public static void AddPartyData(SaveFile sav, ICollection<SlotCache> db)
{
var pd = sav.PartyData;
for (var index = 0; index < pd.Count; index++)
{
var pk = pd[index];
if (pk.Species == 0)
continue;
var ident = new SlotInfoParty(index);
var result = new SlotCache(ident, pk, sav);
db.Add(result);
}
}
private static void AddExtraData(SaveFile sav, ICollection<SlotCache> db)
{
var extra = sav.GetExtraSlots(true);
foreach (var x in extra)
{
var pk = x.Read(sav);
if (pk.Species == 0)
continue;
var result = new SlotCache(x, pk, sav);
db.Add(result);
}
}
#endregion
AddExtraData(sav, db);
}
public static void AddFromLocalFile(string file, ConcurrentBag<SlotCache> db, ITrainerInfo dest, ICollection<string> validExtensions)
{
var fi = new FileInfo(file);
if (!validExtensions.Contains(fi.Extension) || !EntityDetection.IsSizePlausible(fi.Length))
return;
var data = File.ReadAllBytes(file);
_ = FileUtil.TryGetPKM(data, out var pk, fi.Extension, dest);
if (pk?.Species is not > 0)
return;
var info = new SlotInfoFile(file);
var entry = new SlotCache(info, pk);
db.Add(entry);
}
private static void AddBoxData(SaveFile sav, ConcurrentBag<SlotCache> db)
{
var bd = sav.BoxData;
var bc = sav.BoxCount;
var sc = sav.BoxSlotCount;
int ctr = 0;
for (int box = 0; box < bc; box++)
{
for (int slot = 0; slot < sc; slot++, ctr++)
{
var ident = new SlotInfoBox(box, slot);
var result = new SlotCache(ident, bd[ctr], sav);
db.Add(result);
}
}
}
private static void AddPartyData(SaveFile sav, ConcurrentBag<SlotCache> db)
{
var pd = sav.PartyData;
for (var index = 0; index < pd.Count; index++)
{
var pk = pd[index];
if (pk.Species == 0)
continue;
var ident = new SlotInfoParty(index);
var result = new SlotCache(ident, pk, sav);
db.Add(result);
}
}
private static void AddExtraData(SaveFile sav, ConcurrentBag<SlotCache> db)
{
var extra = sav.GetExtraSlots(true);
foreach (var x in extra)
{
var pk = x.Read(sav);
if (pk.Species == 0)
continue;
var result = new SlotCache(x, pk, sav);
db.Add(result);
}
}
#endregion
#region ICollection Implementation
public static void AddFromSaveFile(SaveFile sav, ICollection<SlotCache> db)
{
if (sav.HasBox)
AddBoxData(sav, db);
if (sav.HasParty)
AddPartyData(sav, db);
AddExtraData(sav, db);
}
public static void AddFromLocalFile(string file, ICollection<SlotCache> db, ITrainerInfo dest, ICollection<string> validExtensions)
{
var fi = new FileInfo(file);
if (!validExtensions.Contains(fi.Extension) || !EntityDetection.IsSizePlausible(fi.Length))
return;
var data = File.ReadAllBytes(file);
_ = FileUtil.TryGetPKM(data, out var pk, fi.Extension, dest);
if (pk?.Species is not > 0)
return;
var info = new SlotInfoFile(file);
var entry = new SlotCache(info, pk);
db.Add(entry);
}
public static void AddBoxData(SaveFile sav, ICollection<SlotCache> db)
{
var bd = sav.BoxData;
var bc = sav.BoxCount;
var sc = sav.BoxSlotCount;
int ctr = 0;
for (int box = 0; box < bc; box++)
{
for (int slot = 0; slot < sc; slot++, ctr++)
{
var ident = new SlotInfoBox(box, slot);
var result = new SlotCache(ident, bd[ctr], sav);
db.Add(result);
}
}
}
public static void AddPartyData(SaveFile sav, ICollection<SlotCache> db)
{
var pd = sav.PartyData;
for (var index = 0; index < pd.Count; index++)
{
var pk = pd[index];
if (pk.Species == 0)
continue;
var ident = new SlotInfoParty(index);
var result = new SlotCache(ident, pk, sav);
db.Add(result);
}
}
private static void AddExtraData(SaveFile sav, ICollection<SlotCache> db)
{
var extra = sav.GetExtraSlots(true);
foreach (var x in extra)
{
var pk = x.Read(sav);
if (pk.Species == 0)
continue;
var result = new SlotCache(x, pk, sav);
db.Add(result);
}
}
#endregion
}

View file

@ -1,36 +1,35 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Miscellaneous origination <see cref="ISlotInfo"/>
/// </summary>
public sealed record SlotInfoMisc(byte[] Data, int Slot, int Offset, bool PartyFormat = false) : ISlotInfo
{
/// <summary>
/// Miscellaneous origination <see cref="ISlotInfo"/>
/// </summary>
public sealed record SlotInfoMisc(byte[] Data, int Slot, int Offset, bool PartyFormat = false) : ISlotInfo
public SlotOrigin Origin => PartyFormat ? SlotOrigin.Party : SlotOrigin.Box;
public bool CanWriteTo(SaveFile sav) => false;
public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pk) => WriteBlockedMessage.InvalidDestination;
public StorageSlotType Type { get; init; }
public SlotInfoMisc(SaveFile sav, int slot, int offset, bool party = false) : this(GetBuffer(sav), slot, offset, party) { }
private static byte[] GetBuffer(SaveFile sav) => sav switch
{
public SlotOrigin Origin => PartyFormat ? SlotOrigin.Party : SlotOrigin.Box;
public bool CanWriteTo(SaveFile sav) => false;
public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pkm) => WriteBlockedMessage.InvalidDestination;
public StorageSlotType Type { get; init; }
SAV4 s => s.General,
SAV3 s3 => s3.Large,
_ => sav.Data,
};
public SlotInfoMisc(SaveFile sav, int slot, int offset, bool party = false) : this(GetBuffer(sav), slot, offset, party) { }
private static byte[] GetBuffer(SaveFile sav) => sav switch
{
SAV4 s => s.General,
SAV3 s3 => s3.Large,
_ => sav.Data,
};
public bool WriteTo(SaveFile sav, PKM pkm, PKMImportSetting setting = PKMImportSetting.UseDefault)
{
if (PartyFormat)
sav.SetSlotFormatParty(pkm, Data, Offset, setting, setting);
else
sav.SetSlotFormatStored(pkm, Data, Offset, setting, setting);
return true;
}
public PKM Read(SaveFile sav)
{
return PartyFormat ? sav.GetPartySlot(Data, Offset) : sav.GetStoredSlot(Data, Offset);
}
public bool WriteTo(SaveFile sav, PKM pk, PKMImportSetting setting = PKMImportSetting.UseDefault)
{
if (PartyFormat)
sav.SetSlotFormatParty(pk, Data, Offset, setting, setting);
else
sav.SetSlotFormatStored(pk, Data, Offset, setting, setting);
return true;
}
}
public PKM Read(SaveFile sav)
{
return PartyFormat ? sav.GetPartySlot(Data, Offset) : sav.GetStoredSlot(Data, Offset);
}
}

View file

@ -1,33 +1,32 @@
using System;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Party Data <see cref="ISlotInfo"/>
/// </summary>
public sealed record SlotInfoParty(int Slot) : ISlotInfo
{
/// <summary>
/// Party Data <see cref="ISlotInfo"/>
/// </summary>
public sealed record SlotInfoParty(int Slot) : ISlotInfo
public int Slot { get; private set; } = Slot;
public SlotOrigin Origin => SlotOrigin.Party;
public bool CanWriteTo(SaveFile sav) => sav.HasParty;
public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pk) => pk.IsEgg && sav.IsPartyAllEggs(Slot)
? WriteBlockedMessage.InvalidPartyConfiguration
: WriteBlockedMessage.None;
public bool WriteTo(SaveFile sav, PKM pk, PKMImportSetting setting = PKMImportSetting.UseDefault)
{
public int Slot { get; private set; } = Slot;
public SlotOrigin Origin => SlotOrigin.Party;
public bool CanWriteTo(SaveFile sav) => sav.HasParty;
public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pkm) => pkm.IsEgg && sav.IsPartyAllEggs(Slot)
? WriteBlockedMessage.InvalidPartyConfiguration
: WriteBlockedMessage.None;
public bool WriteTo(SaveFile sav, PKM pkm, PKMImportSetting setting = PKMImportSetting.UseDefault)
if (pk.Species == 0)
{
if (pkm.Species == 0)
{
sav.DeletePartySlot(Slot);
Slot = Math.Max(0, sav.PartyCount - 1);
return true;
}
Slot = Math.Min(Slot, sav.PartyCount); // realign if necessary
sav.SetPartySlotAtIndex(pkm, Slot, setting, setting);
sav.DeletePartySlot(Slot);
Slot = Math.Max(0, sav.PartyCount - 1);
return true;
}
public PKM Read(SaveFile sav) => sav.GetPartySlotAtIndex(Slot);
Slot = Math.Min(Slot, sav.PartyCount); // realign if necessary
sav.SetPartySlotAtIndex(pk, Slot, setting, setting);
return true;
}
public PKM Read(SaveFile sav) => sav.GetPartySlotAtIndex(Slot);
}

View file

@ -1,8 +1,7 @@
namespace PKHeX.Core
namespace PKHeX.Core;
public enum SlotOrigin : byte
{
public enum SlotOrigin : byte
{
Party = 0,
Box = 1,
}
Party = 0,
Box = 1,
}

View file

@ -1,13 +1,12 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Message enumeration indicating why a Write Operation would be blocked for a given <see cref="ISlotInfo"/>
/// </summary>
public enum WriteBlockedMessage
{
/// <summary>
/// Message enumeration indicating why a Write Operation would be blocked for a given <see cref="ISlotInfo"/>
/// </summary>
public enum WriteBlockedMessage
{
None,
InvalidPartyConfiguration,
IncompatibleFormat,
InvalidDestination,
}
}
None,
InvalidPartyConfiguration,
IncompatibleFormat,
InvalidDestination,
}

View file

@ -1,84 +1,83 @@
using System.Collections.Generic;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Tracks <see cref="PKM"/> slot changes and provides the ability to revert a change.
/// </summary>
public sealed class SlotChangelog
{
/// <summary>
/// Tracks <see cref="PKM"/> slot changes and provides the ability to revert a change.
/// </summary>
public sealed class SlotChangelog
private readonly SaveFile SAV;
private readonly Stack<SlotReversion> UndoStack = new();
private readonly Stack<SlotReversion> RedoStack = new();
public SlotChangelog(SaveFile sav) => SAV = sav;
public bool CanUndo => UndoStack.Count != 0;
public bool CanRedo => RedoStack.Count != 0;
public void AddNewChange(ISlotInfo info)
{
private readonly SaveFile SAV;
private readonly Stack<SlotReversion> UndoStack = new();
private readonly Stack<SlotReversion> RedoStack = new();
public SlotChangelog(SaveFile sav) => SAV = sav;
public bool CanUndo => UndoStack.Count != 0;
public bool CanRedo => RedoStack.Count != 0;
public void AddNewChange(ISlotInfo info)
{
var revert = GetReversion(info, SAV);
AddUndo(revert);
}
public ISlotInfo Undo()
{
var change = UndoStack.Pop();
var revert = GetReversion(change.Info, SAV);
AddRedo(revert);
change.Revert(SAV);
return change.Info;
}
public ISlotInfo Redo()
{
var change = RedoStack.Pop();
var revert = GetReversion(change.Info, SAV);
AddUndo(revert);
change.Revert(SAV);
return change.Info;
}
private void AddRedo(SlotReversion change)
{
RedoStack.Push(change);
}
private void AddUndo(SlotReversion change)
{
UndoStack.Push(change);
RedoStack.Clear();
}
private static SlotReversion GetReversion(ISlotInfo info, SaveFile sav) => info switch
{
SlotInfoParty p => new PartyReversion(p, sav),
_ => new SingleSlotReversion(info, sav),
};
private abstract class SlotReversion
{
internal readonly ISlotInfo Info;
protected SlotReversion(ISlotInfo info) => Info = info;
public abstract void Revert(SaveFile sav);
}
private sealed class PartyReversion : SlotReversion
{
private readonly IList<PKM> Party;
public PartyReversion(ISlotInfo info, SaveFile s) : base(info) => Party = s.PartyData;
public override void Revert(SaveFile sav) => sav.PartyData = Party;
}
private sealed class SingleSlotReversion : SlotReversion
{
private readonly PKM Entity;
public SingleSlotReversion(ISlotInfo info, SaveFile sav) : base(info) => Entity = info.Read(sav);
public override void Revert(SaveFile sav) => Info.WriteTo(sav, Entity, PKMImportSetting.Skip);
}
var revert = GetReversion(info, SAV);
AddUndo(revert);
}
}
public ISlotInfo Undo()
{
var change = UndoStack.Pop();
var revert = GetReversion(change.Info, SAV);
AddRedo(revert);
change.Revert(SAV);
return change.Info;
}
public ISlotInfo Redo()
{
var change = RedoStack.Pop();
var revert = GetReversion(change.Info, SAV);
AddUndo(revert);
change.Revert(SAV);
return change.Info;
}
private void AddRedo(SlotReversion change)
{
RedoStack.Push(change);
}
private void AddUndo(SlotReversion change)
{
UndoStack.Push(change);
RedoStack.Clear();
}
private static SlotReversion GetReversion(ISlotInfo info, SaveFile sav) => info switch
{
SlotInfoParty p => new PartyReversion(p, sav),
_ => new SingleSlotReversion(info, sav),
};
private abstract class SlotReversion
{
internal readonly ISlotInfo Info;
protected SlotReversion(ISlotInfo info) => Info = info;
public abstract void Revert(SaveFile sav);
}
private sealed class PartyReversion : SlotReversion
{
private readonly IList<PKM> Party;
public PartyReversion(ISlotInfo info, SaveFile s) : base(info) => Party = s.PartyData;
public override void Revert(SaveFile sav) => sav.PartyData = Party;
}
private sealed class SingleSlotReversion : SlotReversion
{
private readonly PKM Entity;
public SingleSlotReversion(ISlotInfo info, SaveFile sav) : base(info) => Entity = info.Read(sav);
public override void Revert(SaveFile sav) => Info.WriteTo(sav, Entity, PKMImportSetting.Skip);
}
}

View file

@ -1,119 +1,118 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Facilitates interaction with a <see cref="SaveFile"/> or other data location's slot data.
/// </summary>
public sealed class SlotEditor<T>
{
/// <summary>
/// Facilitates interaction with a <see cref="SaveFile"/> or other data location's slot data.
/// </summary>
public sealed class SlotEditor<T>
private readonly SaveFile SAV;
public readonly SlotChangelog Changelog;
public readonly SlotPublisher<T> Publisher;
public SlotEditor(SaveFile sav)
{
private readonly SaveFile SAV;
public readonly SlotChangelog Changelog;
public readonly SlotPublisher<T> Publisher;
SAV = sav;
Changelog = new SlotChangelog(sav);
Publisher = new SlotPublisher<T>();
}
public SlotEditor(SaveFile sav)
{
SAV = sav;
Changelog = new SlotChangelog(sav);
Publisher = new SlotPublisher<T>();
}
private void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pk) => Publisher.NotifySlotChanged(slot, type, pk);
private void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pkm) => Publisher.NotifySlotChanged(slot, type, pkm);
/// <summary>
/// Gets data from a slot.
/// </summary>
/// <param name="slot">Slot to retrieve from.</param>
/// <returns>Operation succeeded or not via enum value.</returns>
public PKM Get(ISlotInfo slot)
{
// Reading from a slot is always allowed.
var pk = slot.Read(SAV);
NotifySlotChanged(slot, SlotTouchType.Get, pk);
return pk;
}
/// <summary>
/// Gets data from a slot.
/// </summary>
/// <param name="slot">Slot to retrieve from.</param>
/// <returns>Operation succeeded or not via enum value.</returns>
public PKM Get(ISlotInfo slot)
{
// Reading from a slot is always allowed.
var pk = slot.Read(SAV);
NotifySlotChanged(slot, SlotTouchType.Get, pk);
return pk;
}
/// <summary>
/// Sets data to a slot.
/// </summary>
/// <param name="slot">Slot to be set to.</param>
/// <param name="pk">Data to set.</param>
/// <param name="type">Type of slot action</param>
/// <returns>Operation succeeded or not via enum value.</returns>
public SlotTouchResult Set(ISlotInfo slot, PKM pk, SlotTouchType type = SlotTouchType.Set)
{
if (!slot.CanWriteTo(SAV))
return SlotTouchResult.FailWrite;
/// <summary>
/// Sets data to a slot.
/// </summary>
/// <param name="slot">Slot to be set to.</param>
/// <param name="pkm">Data to set.</param>
/// <param name="type">Type of slot action</param>
/// <returns>Operation succeeded or not via enum value.</returns>
public SlotTouchResult Set(ISlotInfo slot, PKM pkm, SlotTouchType type = SlotTouchType.Set)
{
if (!slot.CanWriteTo(SAV))
return SlotTouchResult.FailWrite;
WriteSlot(slot, pk, type);
NotifySlotChanged(slot, type, pk);
WriteSlot(slot, pkm, type);
NotifySlotChanged(slot, type, pkm);
return SlotTouchResult.Success;
}
return SlotTouchResult.Success;
}
/// <summary>
/// Deletes a slot.
/// </summary>
/// <param name="slot">Slot to be deleted.</param>
/// <returns>Operation succeeded or not via enum value.</returns>
public SlotTouchResult Delete(ISlotInfo slot)
{
if (!slot.CanWriteTo(SAV))
return SlotTouchResult.FailDelete;
/// <summary>
/// Deletes a slot.
/// </summary>
/// <param name="slot">Slot to be deleted.</param>
/// <returns>Operation succeeded or not via enum value.</returns>
public SlotTouchResult Delete(ISlotInfo slot)
{
if (!slot.CanWriteTo(SAV))
return SlotTouchResult.FailDelete;
var pk = DeleteSlot(slot);
NotifySlotChanged(slot, SlotTouchType.Delete, pk);
var pk = DeleteSlot(slot);
NotifySlotChanged(slot, SlotTouchType.Delete, pk);
return SlotTouchResult.Success;
}
return SlotTouchResult.Success;
}
/// <summary>
/// Swaps two slots.
/// </summary>
/// <param name="source">Source slot to be switched with <see cref="dest"/>.</param>
/// <param name="dest">Destination slot to be switched with <see cref="source"/>.</param>
/// <returns>Operation succeeded or not via enum value.</returns>
public SlotTouchResult Swap(ISlotInfo source, ISlotInfo dest)
{
if (!source.CanWriteTo(SAV))
return SlotTouchResult.FailSource;
if (!dest.CanWriteTo(SAV))
return SlotTouchResult.FailDestination;
/// <summary>
/// Swaps two slots.
/// </summary>
/// <param name="source">Source slot to be switched with <see cref="dest"/>.</param>
/// <param name="dest">Destination slot to be switched with <see cref="source"/>.</param>
/// <returns>Operation succeeded or not via enum value.</returns>
public SlotTouchResult Swap(ISlotInfo source, ISlotInfo dest)
{
if (!source.CanWriteTo(SAV))
return SlotTouchResult.FailSource;
if (!dest.CanWriteTo(SAV))
return SlotTouchResult.FailDestination;
NotifySlotChanged(source, SlotTouchType.None, source.Read(SAV));
NotifySlotChanged(dest, SlotTouchType.Swap, dest.Read(SAV));
NotifySlotChanged(source, SlotTouchType.None, source.Read(SAV));
NotifySlotChanged(dest, SlotTouchType.Swap, dest.Read(SAV));
return SlotTouchResult.Success;
}
return SlotTouchResult.Success;
}
private void WriteSlot(ISlotInfo slot, PKM pk, SlotTouchType type = SlotTouchType.Set)
{
Changelog.AddNewChange(slot);
var setDetail = type is SlotTouchType.Swap ? PKMImportSetting.Skip : PKMImportSetting.UseDefault;
var result = slot.WriteTo(SAV, pk, setDetail);
if (result)
NotifySlotChanged(slot, type, pk);
}
private void WriteSlot(ISlotInfo slot, PKM pkm, SlotTouchType type = SlotTouchType.Set)
{
Changelog.AddNewChange(slot);
var setDetail = type is SlotTouchType.Swap ? PKMImportSetting.Skip : PKMImportSetting.UseDefault;
var result = slot.WriteTo(SAV, pkm, setDetail);
if (result)
NotifySlotChanged(slot, type, pkm);
}
private PKM DeleteSlot(ISlotInfo slot)
{
var pk = SAV.BlankPKM;
WriteSlot(slot, pk, SlotTouchType.Delete);
return pk;
}
private PKM DeleteSlot(ISlotInfo slot)
{
var pkm = SAV.BlankPKM;
WriteSlot(slot, pkm, SlotTouchType.Delete);
return pkm;
}
public void Undo()
{
if (!Changelog.CanUndo)
return;
var slot = Changelog.Undo();
NotifySlotChanged(slot, SlotTouchType.Set, slot.Read(SAV));
}
public void Undo()
{
if (!Changelog.CanUndo)
return;
var slot = Changelog.Undo();
NotifySlotChanged(slot, SlotTouchType.Set, slot.Read(SAV));
}
public void Redo()
{
if (!Changelog.CanRedo)
return;
var slot = Changelog.Redo();
NotifySlotChanged(slot, SlotTouchType.Set, slot.Read(SAV));
}
public void Redo()
{
if (!Changelog.CanRedo)
return;
var slot = Changelog.Redo();
NotifySlotChanged(slot, SlotTouchType.Set, slot.Read(SAV));
}
}

View file

@ -1,50 +1,49 @@
using System.Collections.Generic;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Pushes slot update notifications out to all subscribers.
/// </summary>
public sealed class SlotPublisher<T>
{
/// <summary>
/// Pushes slot update notifications out to all subscribers.
/// All <see cref="ISlotViewer{T}"/> instances that provide a view on individual <see cref="ISlotInfo"/> content.
/// </summary>
public sealed class SlotPublisher<T>
public List<ISlotViewer<T>> Subscribers { get; } = new();
public ISlotInfo? Previous { get; private set; }
public SlotTouchType PreviousType { get; private set; } = SlotTouchType.None;
public PKM? PreviousEntity { get; private set; }
/// <summary>
/// Notifies all <see cref="Subscribers"/> with the latest slot change details.
/// </summary>
/// <param name="slot">Last interacted slot</param>
/// <param name="type">Last interacted slot interaction type</param>
/// <param name="pk">Last interacted slot interaction data</param>
public void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pk)
{
/// <summary>
/// All <see cref="ISlotViewer{T}"/> instances that provide a view on individual <see cref="ISlotInfo"/> content.
/// </summary>
public List<ISlotViewer<T>> Subscribers { get; } = new();
foreach (var sub in Subscribers)
ResetView(sub, slot, type, pk);
Previous = slot;
PreviousType = type;
PreviousEntity = pk;
}
public ISlotInfo? Previous { get; private set; }
public SlotTouchType PreviousType { get; private set; } = SlotTouchType.None;
public PKM? PreviousEntity { get; private set; }
private void ResetView(ISlotViewer<T> sub, ISlotInfo slot, SlotTouchType type, PKM pk)
{
if (Previous != null)
sub.NotifySlotOld(Previous);
/// <summary>
/// Notifies all <see cref="Subscribers"/> with the latest slot change details.
/// </summary>
/// <param name="slot">Last interacted slot</param>
/// <param name="type">Last interacted slot interaction type</param>
/// <param name="pkm">Last interacted slot interaction data</param>
public void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pkm)
{
foreach (var sub in Subscribers)
ResetView(sub, slot, type, pkm);
Previous = slot;
PreviousType = type;
PreviousEntity = pkm;
}
if (slot is not SlotInfoBox b || sub.ViewIndex == b.Box)
sub.NotifySlotChanged(slot, type, pk);
}
private void ResetView(ISlotViewer<T> sub, ISlotInfo slot, SlotTouchType type, PKM pkm)
{
if (Previous != null)
sub.NotifySlotOld(Previous);
if (slot is not SlotInfoBox b || sub.ViewIndex == b.Box)
sub.NotifySlotChanged(slot, type, pkm);
}
public void ResetView(ISlotViewer<T> sub)
{
if (Previous == null || PreviousEntity == null)
return;
ResetView(sub, Previous, PreviousType, PreviousEntity);
}
public void ResetView(ISlotViewer<T> sub)
{
if (Previous == null || PreviousEntity == null)
return;
ResetView(sub, Previous, PreviousType, PreviousEntity);
}
}

View file

@ -1,38 +1,37 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Result indicators for modifying a Slot within a <see cref="SaveFile"/> or other data location.
/// </summary>
public enum SlotTouchResult
{
/// <summary>
/// Result indicators for modifying a Slot within a <see cref="SaveFile"/> or other data location.
/// Slot interaction was successful.
/// </summary>
public enum SlotTouchResult
{
/// <summary>
/// Slot interaction was successful.
/// </summary>
Success,
Success,
/// <summary>
/// Slot interaction failed to do anything.
/// </summary>
FailNone,
/// <summary>
/// Slot interaction failed to do anything.
/// </summary>
FailNone,
/// <summary>
/// Slot interaction failed to apply the data.
/// </summary>
FailWrite,
/// <summary>
/// Slot interaction failed to apply the data.
/// </summary>
FailWrite,
/// <summary>
/// Slot interaction failed to delete the data.
/// </summary>
FailDelete,
/// <summary>
/// Slot interaction failed to delete the data.
/// </summary>
FailDelete,
/// <summary>
/// Slot interaction failed due to a bad/unmodifiable source.
/// </summary>
FailSource,
/// <summary>
/// Slot interaction failed due to a bad/unmodifiable source.
/// </summary>
FailSource,
/// <summary>
/// Slot interaction failed due to a bad/unmodifiable destination.
/// </summary>
FailDestination,
}
}
/// <summary>
/// Slot interaction failed due to a bad/unmodifiable destination.
/// </summary>
FailDestination,
}

View file

@ -1,16 +1,15 @@
namespace PKHeX.Core
{
public enum SlotTouchType
{
None,
Get,
Set,
Delete,
Swap,
}
namespace PKHeX.Core;
public static class SlotTouchTypeUtil
{
public static bool IsContentChange(this SlotTouchType t) => t > SlotTouchType.Get;
}
}
public enum SlotTouchType
{
None,
Get,
Set,
Delete,
Swap,
}
public static class SlotTouchTypeUtil
{
public static bool IsContentChange(this SlotTouchType t) => t > SlotTouchType.Get;
}

View file

@ -1,39 +1,38 @@
using System;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Tuple containing data for a <see cref="Slot"/> and the originating <see cref="View"/>
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class SlotViewInfo<T> : IEquatable<T>
{
/// <summary>
/// Tuple containing data for a <see cref="Slot"/> and the originating <see cref="View"/>
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class SlotViewInfo<T> : IEquatable<T>
public readonly ISlotInfo Slot;
public readonly ISlotViewer<T> View;
public PKM ReadCurrent() => Slot.Read(View.SAV);
public bool CanWriteTo() => Slot.CanWriteTo(View.SAV);
public WriteBlockedMessage CanWriteTo(PKM pk) => Slot.CanWriteTo(View.SAV, pk);
public SlotViewInfo(ISlotInfo slot, ISlotViewer<T> view)
{
public readonly ISlotInfo Slot;
public readonly ISlotViewer<T> View;
public PKM ReadCurrent() => Slot.Read(View.SAV);
public bool CanWriteTo() => Slot.CanWriteTo(View.SAV);
public WriteBlockedMessage CanWriteTo(PKM pkm) => Slot.CanWriteTo(View.SAV, pkm);
public SlotViewInfo(ISlotInfo slot, ISlotViewer<T> view)
{
Slot = slot;
View = view;
}
private bool Equals(SlotViewInfo<T> other)
{
if (other.View.SAV != View.SAV)
return false;
if (other.View.ViewIndex != View.ViewIndex)
return false;
if (other.Slot.Slot != Slot.Slot)
return false;
return other.Slot.GetType() == Slot.GetType();
}
public override bool Equals(object obj) => ReferenceEquals(this, obj) || (obj is SlotViewInfo<T> other && Equals(other));
public override int GetHashCode() => (Slot.GetHashCode() * 397) ^ View.GetHashCode();
bool IEquatable<T>.Equals(T other) => other != null && Equals(other);
Slot = slot;
View = view;
}
}
private bool Equals(SlotViewInfo<T> other)
{
if (other.View.SAV != View.SAV)
return false;
if (other.View.ViewIndex != View.ViewIndex)
return false;
if (other.Slot.Slot != Slot.Slot)
return false;
return other.Slot.GetType() == Slot.GetType();
}
public override bool Equals(object obj) => ReferenceEquals(this, obj) || (obj is SlotViewInfo<T> other && Equals(other));
public override int GetHashCode() => (Slot.GetHashCode() * 397) ^ View.GetHashCode();
bool IEquatable<T>.Equals(T other) => other != null && Equals(other);
}

View file

@ -1,14 +1,13 @@
namespace PKHeX.Core
namespace PKHeX.Core;
public enum StorageSlotType
{
public enum StorageSlotType
{
Box,
Party,
BattleBox,
Daycare,
GTS,
Fused,
Misc,
Resort,
}
Box,
Party,
BattleBox,
Daycare,
GTS,
Fused,
Misc,
Resort,
}

View file

@ -1,183 +1,181 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using static PKHeX.Core.Species;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Logic for parsing details for <see cref="ShowdownSet"/> objects.
/// </summary>
public static class ShowdownParsing
{
private static readonly string[] genderForms = { "", "F", "" };
/// <summary>
/// Logic for parsing details for <see cref="ShowdownSet"/> objects.
/// Gets the Form ID from the input <see cref="name"/>.
/// </summary>
public static class ShowdownParsing
/// <param name="name"></param>
/// <param name="strings"></param>
/// <param name="species">Species ID the form belongs to</param>
/// <param name="format">Format the form name should appear in</param>
/// <returns>Zero (base form) if no form matches the input string.</returns>
public static int GetFormFromString(string name, GameStrings strings, int species, int format)
{
private static readonly string[] genderForms = { "", "F", "" };
if (name.Length == 0)
return 0;
/// <summary>
/// Gets the Form ID from the input <see cref="name"/>.
/// </summary>
/// <param name="name"></param>
/// <param name="strings"></param>
/// <param name="species">Species ID the form belongs to</param>
/// <param name="format">Format the form name should appear in</param>
/// <returns>Zero (base form) if no form matches the input string.</returns>
public static int GetFormFromString(string name, GameStrings strings, int species, int format)
string[] formStrings = FormConverter.GetFormList(species, strings.Types, strings.forms, genderForms, format);
return Math.Max(0, Array.FindIndex(formStrings, z => z.Contains(name)));
}
/// <summary>
/// Converts a Form ID to string.
/// </summary>
/// <param name="form"></param>
/// <param name="strings"></param>
/// <param name="species"></param>
/// <param name="format"></param>
public static string GetStringFromForm(int form, GameStrings strings, int species, int format)
{
if (form <= 0)
return string.Empty;
var forms = FormConverter.GetFormList(species, strings.Types, strings.forms, genderForms, format);
return form >= forms.Length ? string.Empty : forms[form];
}
private const string MiniorFormName = "Meteor";
/// <summary>
/// Converts the PKHeX standard form name to Showdown's form name.
/// </summary>
/// <param name="species">Species ID</param>
/// <param name="form">PKHeX form name</param>
public static string GetShowdownFormName(int species, string form)
{
if (form.Length == 0)
{
if (name.Length == 0)
return 0;
string[] formStrings = FormConverter.GetFormList(species, strings.Types, strings.forms, genderForms, format);
return Math.Max(0, Array.FindIndex(formStrings, z => z.Contains(name)));
}
/// <summary>
/// Converts a Form ID to string.
/// </summary>
/// <param name="form"></param>
/// <param name="strings"></param>
/// <param name="species"></param>
/// <param name="format"></param>
/// <returns></returns>
public static string GetStringFromForm(int form, GameStrings strings, int species, int format)
{
if (form <= 0)
return string.Empty;
var forms = FormConverter.GetFormList(species, strings.Types, strings.forms, genderForms, format);
return form >= forms.Length ? string.Empty : forms[form];
}
private const string MiniorFormName = "Meteor";
/// <summary>
/// Converts the PKHeX standard form name to Showdown's form name.
/// </summary>
/// <param name="species">Species ID</param>
/// <param name="form">PKHeX form name</param>
public static string GetShowdownFormName(int species, string form)
{
if (form.Length == 0)
{
return species switch
{
(int)Minior => MiniorFormName,
_ => form,
};
}
return species switch
{
(int)Basculin when form is "Blue" => "Blue-Striped",
(int)Vivillon when form is "Poké Ball" => "Pokeball",
(int)Zygarde => form.Replace("-C", string.Empty).Replace("50%", string.Empty),
(int)Minior when form.StartsWith("M-") => MiniorFormName,
(int)Minior => form.Replace("C-", string.Empty),
(int)Necrozma when form is "Dusk" => $"{form}-Mane",
(int)Necrozma when form is "Dawn" => $"{form}-Wings",
(int)Polteageist or (int)Sinistea => form == "Antique" ? form : string.Empty,
(int)Furfrou or (int)Greninja or (int)Rockruff => string.Empty,
_ => Legal.Totem_USUM.Contains(species) && form == "Large"
? Legal.Totem_Alolan.Contains(species) && species != (int)Mimikyu ? "Alola-Totem" : "Totem"
: form.Replace(' ', '-'),
(int)Minior => MiniorFormName,
_ => form,
};
}
/// <summary>
/// Converts the Showdown form name to PKHeX's form name.
/// </summary>
/// <param name="species">Species ID</param>
/// <param name="form">Showdown form name</param>
/// <param name="ability">Showdown ability ID</param>
public static string SetShowdownFormName(int species, string form, int ability)
return species switch
{
if (form.Length != 0)
form = form.Replace(' ', '-'); // inconsistencies are great
(int)Basculin when form is "Blue" => "Blue-Striped",
(int)Vivillon when form is "Poké Ball" => "Pokeball",
(int)Zygarde => form.Replace("-C", string.Empty).Replace("50%", string.Empty),
(int)Minior when form.StartsWith("M-", StringComparison.Ordinal) => MiniorFormName,
(int)Minior => form.Replace("C-", string.Empty),
(int)Necrozma when form is "Dusk" => $"{form}-Mane",
(int)Necrozma when form is "Dawn" => $"{form}-Wings",
(int)Polteageist or (int)Sinistea => form == "Antique" ? form : string.Empty,
return species switch
{
(int)Basculin when form == "Blue-Striped" => "Blue",
(int)Vivillon when form == "Pokeball" => "Poké Ball",
(int)Necrozma when form == "Dusk-Mane" => "Dusk",
(int)Necrozma when form == "Dawn-Wings" => "Dawn",
(int)Toxtricity when form == "Low-Key" => "Low Key",
(int)Darmanitan when form == "Galar-Zen" => "Galar Zen",
(int)Minior when form != MiniorFormName => $"C-{form}",
(int)Zygarde when form == "Complete" => form,
(int)Zygarde when ability == 211 => $"{(string.IsNullOrWhiteSpace(form) ? "50%" : "10%")}-C",
(int)Greninja when ability == 210 => "Ash", // Battle Bond
(int)Rockruff when ability == 020 => "Dusk", // Rockruff-1
(int)Urshifu or (int)Pikachu or (int)Alcremie => form.Replace('-', ' '), // Strike and Cosplay
(int)Furfrou or (int)Greninja or (int)Rockruff => string.Empty,
_ => Legal.Totem_USUM.Contains(species) && form.EndsWith("Totem") ? "Large" : form,
};
}
_ => Legal.Totem_USUM.Contains(species) && form == "Large"
? Legal.Totem_Alolan.Contains(species) && species != (int)Mimikyu ? "Alola-Totem" : "Totem"
: form.Replace(' ', '-'),
};
}
/// <summary>
/// Fetches <see cref="ShowdownSet"/> data from the input <see cref="lines"/>.
/// </summary>
/// <param name="lines">Raw lines containing numerous multi-line set data.</param>
/// <returns><see cref="ShowdownSet"/> objects until <see cref="lines"/> is consumed.</returns>
public static IEnumerable<ShowdownSet> GetShowdownSets(IEnumerable<string> lines)
/// <summary>
/// Converts the Showdown form name to PKHeX's form name.
/// </summary>
/// <param name="species">Species ID</param>
/// <param name="form">Showdown form name</param>
/// <param name="ability">Showdown ability ID</param>
public static string SetShowdownFormName(int species, string form, int ability)
{
if (form.Length != 0)
form = form.Replace(' ', '-'); // inconsistencies are great
return species switch
{
// exported sets always have >4 moves; new List will always require 1 resizing, allocate 2x to save 1 reallocation.
// intro, nature, ability, (ivs, evs, shiny, level) 4*moves
var setLines = new List<string>(8);
foreach (var line in lines)
(int)Basculin when form == "Blue-Striped" => "Blue",
(int)Vivillon when form == "Pokeball" => "Poké Ball",
(int)Necrozma when form == "Dusk-Mane" => "Dusk",
(int)Necrozma when form == "Dawn-Wings" => "Dawn",
(int)Toxtricity when form == "Low-Key" => "Low Key",
(int)Darmanitan when form == "Galar-Zen" => "Galar Zen",
(int)Minior when form != MiniorFormName => $"C-{form}",
(int)Zygarde when form == "Complete" => form,
(int)Zygarde when ability == 211 => $"{(string.IsNullOrWhiteSpace(form) ? "50%" : "10%")}-C",
(int)Greninja when ability == 210 => "Ash", // Battle Bond
(int)Rockruff when ability == 020 => "Dusk", // Rockruff-1
(int)Urshifu or (int)Pikachu or (int)Alcremie => form.Replace('-', ' '), // Strike and Cosplay
_ => Legal.Totem_USUM.Contains(species) && form.EndsWith("Totem", StringComparison.Ordinal) ? "Large" : form,
};
}
/// <summary>
/// Fetches <see cref="ShowdownSet"/> data from the input <see cref="lines"/>.
/// </summary>
/// <param name="lines">Raw lines containing numerous multi-line set data.</param>
/// <returns><see cref="ShowdownSet"/> objects until <see cref="lines"/> is consumed.</returns>
public static IEnumerable<ShowdownSet> GetShowdownSets(IEnumerable<string> lines)
{
// exported sets always have >4 moves; new List will always require 1 resizing, allocate 2x to save 1 reallocation.
// intro, nature, ability, (ivs, evs, shiny, level) 4*moves
var setLines = new List<string>(8);
foreach (var line in lines)
{
if (!string.IsNullOrWhiteSpace(line))
{
if (!string.IsNullOrWhiteSpace(line))
{
setLines.Add(line);
continue;
}
if (setLines.Count == 0)
continue;
yield return new ShowdownSet(setLines);
setLines.Clear();
setLines.Add(line);
continue;
}
if (setLines.Count != 0)
yield return new ShowdownSet(setLines);
if (setLines.Count == 0)
continue;
yield return new ShowdownSet(setLines);
setLines.Clear();
}
if (setLines.Count != 0)
yield return new ShowdownSet(setLines);
}
/// <summary>
/// Converts the <see cref="PKM"/> data into an importable set format for Pokémon Showdown.
/// </summary>
/// <param name="pkm">PKM to convert to string</param>
/// <returns>Multi line set data</returns>
public static string GetShowdownText(PKM pkm)
{
if (pkm.Species == 0)
return string.Empty;
return new ShowdownSet(pkm).Text;
}
/// <summary>
/// Converts the <see cref="PKM"/> data into an importable set format for Pokémon Showdown.
/// </summary>
/// <param name="pk">PKM to convert to string</param>
/// <returns>Multi line set data</returns>
public static string GetShowdownText(PKM pk)
{
if (pk.Species == 0)
return string.Empty;
return new ShowdownSet(pk).Text;
}
/// <summary>
/// Fetches ShowdownSet lines from the input <see cref="PKM"/> data.
/// </summary>
/// <param name="data">Pokémon data to summarize.</param>
/// <returns>Consumable list of <see cref="ShowdownSet.Text"/> lines.</returns>
public static IEnumerable<string> GetShowdownSets(IEnumerable<PKM> data) => data.Where(p => p.Species != 0).Select(GetShowdownText);
/// <summary>
/// Fetches ShowdownSet lines from the input <see cref="PKM"/> data.
/// </summary>
/// <param name="data">Pokémon data to summarize.</param>
/// <returns>Consumable list of <see cref="ShowdownSet.Text"/> lines.</returns>
public static IEnumerable<string> GetShowdownSets(IEnumerable<PKM> data) => data.Where(p => p.Species != 0).Select(GetShowdownText);
/// <summary>
/// Fetches ShowdownSet lines from the input <see cref="PKM"/> data, and combines it into one string.
/// </summary>
/// <param name="data">Pokémon data to summarize.</param>
/// <param name="separator">Splitter between each set.</param>
/// <returns>Single string containing all <see cref="ShowdownSet.Text"/> lines.</returns>
public static string GetShowdownSets(IEnumerable<PKM> data, string separator) => string.Join(separator, GetShowdownSets(data));
/// <summary>
/// Fetches ShowdownSet lines from the input <see cref="PKM"/> data, and combines it into one string.
/// </summary>
/// <param name="data">Pokémon data to summarize.</param>
/// <param name="separator">Splitter between each set.</param>
/// <returns>Single string containing all <see cref="ShowdownSet.Text"/> lines.</returns>
public static string GetShowdownSets(IEnumerable<PKM> data, string separator) => string.Join(separator, GetShowdownSets(data));
/// <summary>
/// Gets a localized string preview of the provided <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon data</param>
/// <param name="language">Language code</param>
/// <returns>Multi-line string</returns>
public static string GetLocalizedPreviewText(PKM pk, string language)
{
var set = new ShowdownSet(pk);
if (pk.Format <= 2) // Nature preview from IVs
set.Nature = Experience.GetNatureVC(pk.EXP);
return set.LocalizedText(language);
}
/// <summary>
/// Gets a localized string preview of the provided <see cref="pk"/>.
/// </summary>
/// <param name="pk">Pokémon data</param>
/// <param name="language">Language code</param>
/// <returns>Multi-line string</returns>
public static string GetLocalizedPreviewText(PKM pk, string language)
{
var set = new ShowdownSet(pk);
if (pk.Format <= 2) // Nature preview from IVs
set.Nature = Experience.GetNatureVC(pk.EXP);
return set.LocalizedText(language);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,59 +1,58 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Indicates or coerces values pertaining to <see cref="Species.Wurmple"/> and its branched evolutions.
/// </summary>
public static class WurmpleUtil
{
/// <summary>
/// Indicates or coerces values pertaining to <see cref="Species.Wurmple"/> and its branched evolutions.
/// Gets the Wurmple Evolution Value for a given <see cref="PKM.EncryptionConstant"/>
/// </summary>
public static class WurmpleUtil
/// <param name="encryptionConstant">Encryption Constant</param>
/// <returns>Wurmple Evolution Value</returns>
public static uint GetWurmpleEvoVal(uint encryptionConstant)
{
/// <summary>
/// Gets the Wurmple Evolution Value for a given <see cref="PKM.EncryptionConstant"/>
/// </summary>
/// <param name="encryptionConstant">Encryption Constant</param>
/// <returns>Wurmple Evolution Value</returns>
public static uint GetWurmpleEvoVal(uint encryptionConstant)
{
var evoVal = encryptionConstant >> 16;
return evoVal % 10 / 5;
}
var evoVal = encryptionConstant >> 16;
return evoVal % 10 / 5;
}
/// <summary>
/// Gets the evo chain of Wurmple
/// </summary>
/// <param name="species">Current species</param>
/// <returns>-1 if not a Wurmple Evo, 0 if Silcoon chain, 1 if Cascoon chain</returns>
public static int GetWurmpleEvoGroup(int species)
{
int wIndex = species - (int)Species.Silcoon;
if ((wIndex & 3) != wIndex) // Wurmple evo, [0,3]
return -1;
return wIndex >> 1; // Silcoon, Cascoon
}
/// <summary>
/// Gets the evo chain of Wurmple
/// </summary>
/// <param name="species">Current species</param>
/// <returns>-1 if not a Wurmple Evo, 0 if Silcoon chain, 1 if Cascoon chain</returns>
public static int GetWurmpleEvoGroup(int species)
{
int wIndex = species - (int)Species.Silcoon;
if ((wIndex & 3) != wIndex) // Wurmple evo, [0,3]
return -1;
return wIndex >> 1; // Silcoon, Cascoon
}
/// <summary>
/// Gets the Wurmple <see cref="PKM.EncryptionConstant"/> for a given Evolution Value
/// </summary>
/// <param name="evoVal">Wurmple Evolution Value</param>
/// <remarks>0 = Silcoon, 1 = Cascoon</remarks>
/// <returns>Encryption Constant</returns>
public static uint GetWurmpleEncryptionConstant(int evoVal)
{
uint result;
var rnd = Util.Rand;
do result = rnd.Rand32();
while (evoVal != GetWurmpleEvoVal(result));
return result;
}
/// <summary>
/// Gets the Wurmple <see cref="PKM.EncryptionConstant"/> for a given Evolution Value
/// </summary>
/// <param name="evoVal">Wurmple Evolution Value</param>
/// <remarks>0 = Silcoon, 1 = Cascoon</remarks>
/// <returns>Encryption Constant</returns>
public static uint GetWurmpleEncryptionConstant(int evoVal)
{
uint result;
var rnd = Util.Rand;
do result = rnd.Rand32();
while (evoVal != GetWurmpleEvoVal(result));
return result;
}
/// <summary>
/// Checks to see if the input <see cref="pkm"/>, with species being that of Wurmple's evo chain, is valid.
/// </summary>
/// <param name="pkm">Pokémon data</param>
/// <returns>True if valid, false if invalid</returns>
public static bool IsWurmpleEvoValid(PKM pkm)
{
uint evoVal = GetWurmpleEvoVal(pkm.EncryptionConstant);
int wIndex = GetWurmpleEvoGroup(pkm.Species);
return evoVal == wIndex;
}
/// <summary>
/// Checks to see if the input <see cref="pk"/>, with species being that of Wurmple's evo chain, is valid.
/// </summary>
/// <param name="pk">Pokémon data</param>
/// <returns>True if valid, false if invalid</returns>
public static bool IsWurmpleEvoValid(PKM pk)
{
uint evoVal = GetWurmpleEvoVal(pk.EncryptionConstant);
int wIndex = GetWurmpleEvoGroup(pk.Species);
return evoVal == wIndex;
}
}

View file

@ -1,278 +1,277 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Ability IDs for the corresponding English ability name.
/// </summary>
public enum Ability
{
/// <summary>
/// Ability IDs for the corresponding English ability name.
/// </summary>
public enum Ability
{
None,
Stench,
Drizzle,
SpeedBoost,
BattleArmor,
Sturdy,
Damp,
Limber,
SandVeil,
Static,
VoltAbsorb,
WaterAbsorb,
Oblivious,
CloudNine,
CompoundEyes,
Insomnia,
ColorChange,
Immunity,
FlashFire,
ShieldDust,
OwnTempo,
SuctionCups,
Intimidate,
ShadowTag,
RoughSkin,
WonderGuard,
Levitate,
EffectSpore,
Synchronize,
ClearBody,
NaturalCure,
LightningRod,
SereneGrace,
SwiftSwim,
Chlorophyll,
Illuminate,
Trace,
HugePower,
PoisonPoint,
InnerFocus,
MagmaArmor,
WaterVeil,
MagnetPull,
Soundproof,
RainDish,
SandStream,
Pressure,
ThickFat,
EarlyBird,
FlameBody,
RunAway,
KeenEye,
HyperCutter,
Pickup,
Truant,
Hustle,
CuteCharm,
Plus,
Minus,
Forecast,
StickyHold,
ShedSkin,
Guts,
MarvelScale,
LiquidOoze,
Overgrow,
Blaze,
Torrent,
Swarm,
RockHead,
Drought,
ArenaTrap,
VitalSpirit,
WhiteSmoke,
PurePower,
ShellArmor,
AirLock,
TangledFeet,
MotorDrive,
Rivalry,
Steadfast,
SnowCloak,
Gluttony,
AngerPoint,
Unburden,
Heatproof,
Simple,
DrySkin,
Download,
IronFist,
PoisonHeal,
Adaptability,
SkillLink,
Hydration,
SolarPower,
QuickFeet,
Normalize,
Sniper,
MagicGuard,
NoGuard,
Stall,
Technician,
LeafGuard,
Klutz,
MoldBreaker,
SuperLuck,
Aftermath,
Anticipation,
Forewarn,
Unaware,
TintedLens,
Filter,
SlowStart,
Scrappy,
StormDrain,
IceBody,
SolidRock,
SnowWarning,
HoneyGather,
Frisk,
Reckless,
Multitype,
FlowerGift,
BadDreams,
Pickpocket,
SheerForce,
Contrary,
Unnerve,
Defiant,
Defeatist,
CursedBody,
Healer,
FriendGuard,
WeakArmor,
HeavyMetal,
LightMetal,
Multiscale,
ToxicBoost,
FlareBoost,
Harvest,
Telepathy,
Moody,
Overcoat,
PoisonTouch,
Regenerator,
BigPecks,
SandRush,
WonderSkin,
Analytic,
Illusion,
Imposter,
Infiltrator,
Mummy,
Moxie,
Justified,
Rattled,
MagicBounce,
SapSipper,
Prankster,
SandForce,
IronBarbs,
ZenMode,
VictoryStar,
Turboblaze,
Teravolt,
AromaVeil,
FlowerVeil,
CheekPouch,
Protean,
FurCoat,
Magician,
Bulletproof,
Competitive,
StrongJaw,
Refrigerate,
SweetVeil,
StanceChange,
GaleWings,
MegaLauncher,
GrassPelt,
Symbiosis,
ToughClaws,
Pixilate,
Gooey,
Aerilate,
ParentalBond,
DarkAura,
FairyAura,
AuraBreak,
PrimordialSea,
DesolateLand,
DeltaStream,
Stamina,
WimpOut,
EmergencyExit,
WaterCompaction,
Merciless,
ShieldsDown,
Stakeout,
WaterBubble,
Steelworker,
Berserk,
SlushRush,
LongReach,
LiquidVoice,
Triage,
Galvanize,
SurgeSurfer,
Schooling,
Disguise,
BattleBond,
PowerConstruct,
Corrosion,
Comatose,
QueenlyMajesty,
InnardsOut,
Dancer,
Battery,
Fluffy,
Dazzling,
SoulHeart,
TanglingHair,
Receiver,
PowerofAlchemy,
BeastBoost,
RKSSystem,
ElectricSurge,
PsychicSurge,
MistySurge,
GrassySurge,
FullMetalBody,
ShadowShield,
PrismArmor,
Neuroforce,
IntrepidSword,
DauntlessShield,
Libero,
BallFetch,
CottonDown,
PropellerTail,
MirrorArmor,
GulpMissile,
Stalwart,
SteamEngine,
PunkRock,
SandSpit,
IceScales,
Ripen,
IceFace,
PowerSpot,
Mimicry,
ScreenCleaner,
SteelySpirit,
PerishBody,
WanderingSpirit,
GorillaTactics,
NeutralizingGas,
PastelVeil,
HungerSwitch,
QuickDraw,
UnseenFist,
CuriousMedicine,
Transistor,
DragonsMaw,
ChillingNeigh,
GrimNeigh,
AsOneI,
AsOneG,
MAX_COUNT,
}
None,
Stench,
Drizzle,
SpeedBoost,
BattleArmor,
Sturdy,
Damp,
Limber,
SandVeil,
Static,
VoltAbsorb,
WaterAbsorb,
Oblivious,
CloudNine,
CompoundEyes,
Insomnia,
ColorChange,
Immunity,
FlashFire,
ShieldDust,
OwnTempo,
SuctionCups,
Intimidate,
ShadowTag,
RoughSkin,
WonderGuard,
Levitate,
EffectSpore,
Synchronize,
ClearBody,
NaturalCure,
LightningRod,
SereneGrace,
SwiftSwim,
Chlorophyll,
Illuminate,
Trace,
HugePower,
PoisonPoint,
InnerFocus,
MagmaArmor,
WaterVeil,
MagnetPull,
Soundproof,
RainDish,
SandStream,
Pressure,
ThickFat,
EarlyBird,
FlameBody,
RunAway,
KeenEye,
HyperCutter,
Pickup,
Truant,
Hustle,
CuteCharm,
Plus,
Minus,
Forecast,
StickyHold,
ShedSkin,
Guts,
MarvelScale,
LiquidOoze,
Overgrow,
Blaze,
Torrent,
Swarm,
RockHead,
Drought,
ArenaTrap,
VitalSpirit,
WhiteSmoke,
PurePower,
ShellArmor,
AirLock,
TangledFeet,
MotorDrive,
Rivalry,
Steadfast,
SnowCloak,
Gluttony,
AngerPoint,
Unburden,
Heatproof,
Simple,
DrySkin,
Download,
IronFist,
PoisonHeal,
Adaptability,
SkillLink,
Hydration,
SolarPower,
QuickFeet,
Normalize,
Sniper,
MagicGuard,
NoGuard,
Stall,
Technician,
LeafGuard,
Klutz,
MoldBreaker,
SuperLuck,
Aftermath,
Anticipation,
Forewarn,
Unaware,
TintedLens,
Filter,
SlowStart,
Scrappy,
StormDrain,
IceBody,
SolidRock,
SnowWarning,
HoneyGather,
Frisk,
Reckless,
Multitype,
FlowerGift,
BadDreams,
Pickpocket,
SheerForce,
Contrary,
Unnerve,
Defiant,
Defeatist,
CursedBody,
Healer,
FriendGuard,
WeakArmor,
HeavyMetal,
LightMetal,
Multiscale,
ToxicBoost,
FlareBoost,
Harvest,
Telepathy,
Moody,
Overcoat,
PoisonTouch,
Regenerator,
BigPecks,
SandRush,
WonderSkin,
Analytic,
Illusion,
Imposter,
Infiltrator,
Mummy,
Moxie,
Justified,
Rattled,
MagicBounce,
SapSipper,
Prankster,
SandForce,
IronBarbs,
ZenMode,
VictoryStar,
Turboblaze,
Teravolt,
AromaVeil,
FlowerVeil,
CheekPouch,
Protean,
FurCoat,
Magician,
Bulletproof,
Competitive,
StrongJaw,
Refrigerate,
SweetVeil,
StanceChange,
GaleWings,
MegaLauncher,
GrassPelt,
Symbiosis,
ToughClaws,
Pixilate,
Gooey,
Aerilate,
ParentalBond,
DarkAura,
FairyAura,
AuraBreak,
PrimordialSea,
DesolateLand,
DeltaStream,
Stamina,
WimpOut,
EmergencyExit,
WaterCompaction,
Merciless,
ShieldsDown,
Stakeout,
WaterBubble,
Steelworker,
Berserk,
SlushRush,
LongReach,
LiquidVoice,
Triage,
Galvanize,
SurgeSurfer,
Schooling,
Disguise,
BattleBond,
PowerConstruct,
Corrosion,
Comatose,
QueenlyMajesty,
InnardsOut,
Dancer,
Battery,
Fluffy,
Dazzling,
SoulHeart,
TanglingHair,
Receiver,
PowerofAlchemy,
BeastBoost,
RKSSystem,
ElectricSurge,
PsychicSurge,
MistySurge,
GrassySurge,
FullMetalBody,
ShadowShield,
PrismArmor,
Neuroforce,
IntrepidSword,
DauntlessShield,
Libero,
BallFetch,
CottonDown,
PropellerTail,
MirrorArmor,
GulpMissile,
Stalwart,
SteamEngine,
PunkRock,
SandSpit,
IceScales,
Ripen,
IceFace,
PowerSpot,
Mimicry,
ScreenCleaner,
SteelySpirit,
PerishBody,
WanderingSpirit,
GorillaTactics,
NeutralizingGas,
PastelVeil,
HungerSwitch,
QuickDraw,
UnseenFist,
CuriousMedicine,
Transistor,
DragonsMaw,
ChillingNeigh,
GrimNeigh,
AsOneI,
AsOneG,
MAX_COUNT,
}

View file

@ -1,65 +1,64 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Ball IDs for the corresponding English ball name.
/// </summary>
public enum Ball : byte
{
None = 0,
Master = 1,
Ultra = 2,
Great = 3,
Poke = 4,
Safari = 5,
Net = 6,
Dive = 7,
Nest = 8,
Repeat = 9,
Timer = 10,
Luxury = 11,
Premier = 12,
Dusk = 13,
Heal = 14,
Quick = 15,
Cherish = 16,
Fast = 17,
Level = 18,
Lure = 19,
Heavy = 20,
Love = 21,
Friend = 22,
Moon = 23,
Sport = 24,
Dream = 25,
Beast = 26,
// Legends: Arceus
Strange = 27,
LAPoke = 28,
LAGreat = 29,
LAUltra = 30,
LAFeather = 31,
LAWing = 32,
LAJet = 33,
LAHeavy = 34,
LALeaden = 35,
LAGigaton = 36,
LAOrigin = 37,
}
public static class BallExtensions
{
/// <summary>
/// Ball IDs for the corresponding English ball name.
/// Checks if the <see cref="ball"/> is an Apricorn Ball (HG/SS)
/// </summary>
public enum Ball : byte
{
None = 0,
Master = 1,
Ultra = 2,
Great = 3,
Poke = 4,
Safari = 5,
Net = 6,
Dive = 7,
Nest = 8,
Repeat = 9,
Timer = 10,
Luxury = 11,
Premier = 12,
Dusk = 13,
Heal = 14,
Quick = 15,
Cherish = 16,
Fast = 17,
Level = 18,
Lure = 19,
Heavy = 20,
Love = 21,
Friend = 22,
Moon = 23,
Sport = 24,
Dream = 25,
Beast = 26,
// Legends: Arceus
Strange = 27,
LAPoke = 28,
LAGreat = 29,
LAUltra = 30,
LAFeather = 31,
LAWing = 32,
LAJet = 33,
LAHeavy = 34,
LALeaden = 35,
LAGigaton = 36,
LAOrigin = 37,
}
public static class BallExtensions
{
/// <summary>
/// Checks if the <see cref="ball"/> is an Apricorn Ball (HG/SS)
/// </summary>
/// <param name="ball">Ball ID</param>
/// <returns>True if Apricorn, false if not.</returns>
public static bool IsApricornBall(this Ball ball) => ball is >= Ball.Fast and <= Ball.Moon;
}
/// <param name="ball">Ball ID</param>
/// <returns>True if Apricorn, false if not.</returns>
public static bool IsApricornBall(this Ball ball) => ball is >= Ball.Fast and <= Ball.Moon;
}

View file

@ -1,51 +1,50 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// <see cref="GameVersion"/> analogues used by Colosseum/XD instead of the main-series values.
/// </summary>
public enum GCVersion : byte
{
None = 0,
FR = 1,
LG = 2,
S = 8,
R = 9,
E = 10,
CXD = 11,
}
public static class GCVersionExtensions
{
/// <summary>
/// <see cref="GameVersion"/> analogues used by Colosseum/XD instead of the main-series values.
/// Translates a main-series <see cref="GameVersion"/> to the corresponding <see cref="GCVersion"/> value.
/// </summary>
public enum GCVersion : byte
/// <param name="gbaVersion">Version ID while present in the main-series games</param>
/// <returns>Version ID while present in the GameCube games</returns>
public static GCVersion GetCXDVersionID(this GameVersion gbaVersion) => gbaVersion switch
{
None = 0,
FR = 1,
LG = 2,
S = 8,
R = 9,
E = 10,
CXD = 11,
}
GameVersion.S => GCVersion.S,
GameVersion.R => GCVersion.R,
GameVersion.E => GCVersion.E,
GameVersion.FR => GCVersion.FR,
GameVersion.LG => GCVersion.LG,
GameVersion.CXD => GCVersion.CXD,
_ => GCVersion.None,
};
public static class GCVersionExtensions
/// <summary>
/// Translates a <see cref="GCVersion"/> to the corresponding main-series <see cref="GameVersion"/> value.
/// </summary>
/// <param name="gcVersion">Version ID while present in the GameCube games</param>
/// <returns>Version ID while present in the main-series games</returns>
public static GameVersion GetG3VersionID(this GCVersion gcVersion) => gcVersion switch
{
/// <summary>
/// Translates a main-series <see cref="GameVersion"/> to the corresponding <see cref="GCVersion"/> value.
/// </summary>
/// <param name="gbaVersion">Version ID while present in the main-series games</param>
/// <returns>Version ID while present in the GameCube games</returns>
public static GCVersion GetCXDVersionID(this GameVersion gbaVersion) => gbaVersion switch
{
GameVersion.S => GCVersion.S,
GameVersion.R => GCVersion.R,
GameVersion.E => GCVersion.E,
GameVersion.FR => GCVersion.FR,
GameVersion.LG => GCVersion.LG,
GameVersion.CXD => GCVersion.CXD,
_ => GCVersion.None,
};
/// <summary>
/// Translates a <see cref="GCVersion"/> to the corresponding main-series <see cref="GameVersion"/> value.
/// </summary>
/// <param name="gcVersion">Version ID while present in the GameCube games</param>
/// <returns>Version ID while present in the main-series games</returns>
public static GameVersion GetG3VersionID(this GCVersion gcVersion) => gcVersion switch
{
GCVersion.S => GameVersion.S,
GCVersion.R => GameVersion.R,
GCVersion.E => GameVersion.E,
GCVersion.FR => GameVersion.FR,
GCVersion.LG => GameVersion.LG,
GCVersion.CXD => GameVersion.CXD,
_ => GameVersion.Unknown,
};
}
GCVersion.S => GameVersion.S,
GCVersion.R => GameVersion.R,
GCVersion.E => GameVersion.E,
GCVersion.FR => GameVersion.FR,
GCVersion.LG => GameVersion.LG,
GCVersion.CXD => GameVersion.CXD,
_ => GameVersion.Unknown,
};
}

View file

@ -1,485 +1,484 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Game Version ID enum shared between actual Version IDs and lumped version groupings.
/// </summary>
public enum GameVersion
{
#region Indicators for method empty arguments & result indication. Not stored values.
Invalid = -2,
Any = -1,
Unknown = 0,
#endregion
// The following values are IDs stored within PKM data, and can also identify individual games.
#region Gen3
/// <summary>
/// Game Version ID enum shared between actual Version IDs and lumped version groupings.
/// Pokémon Sapphire (GBA)
/// </summary>
public enum GameVersion
{
#region Indicators for method empty arguments & result indication. Not stored values.
Invalid = -2,
Any = -1,
Unknown = 0,
#endregion
// The following values are IDs stored within PKM data, and can also identify individual games.
#region Gen3
/// <summary>
/// Pokémon Sapphire (GBA)
/// </summary>
S = 1,
/// <summary>
/// Pokémon Ruby (GBA)
/// </summary>
R = 2,
/// <summary>
/// Pokémon Emerald (GBA)
/// </summary>
E = 3,
/// <summary>
/// Pokémon FireRed (GBA)
/// </summary>
FR = 4,
/// <summary>
/// Pokémon LeafGreen (GBA)
/// </summary>
LG = 5,
/// <summary>
/// Pokémon Colosseum &amp; Pokémon XD (GameCube)
/// </summary>
CXD = 15,
#endregion
#region Gen4
/// <summary>
/// Pokémon Diamond (NDS)
/// </summary>
D = 10,
/// <summary>
/// Pokémon Pearl (NDS)
/// </summary>
P = 11,
/// <summary>
/// Pokémon Platinum (NDS)
/// </summary>
Pt = 12,
/// <summary>
/// Pokémon HeartGold (NDS)
/// </summary>
HG = 7,
/// <summary>
/// Pokémon SoulSilver (NDS)
/// </summary>
SS = 8,
#endregion
#region Gen5
/// <summary>
/// Pokémon White (NDS)
/// </summary>
W = 20,
/// <summary>
/// Pokémon Black (NDS)
/// </summary>
B = 21,
/// <summary>
/// Pokémon White 2 (NDS)
/// </summary>
W2 = 22,
/// <summary>
/// Pokémon Black 2 (NDS)
/// </summary>
B2 = 23,
#endregion
#region Gen6
/// <summary>
/// Pokémon X (3DS)
/// </summary>
X = 24,
/// <summary>
/// Pokémon Y (3DS)
/// </summary>
Y = 25,
/// <summary>
/// Pokémon Alpha Sapphire (3DS)
/// </summary>
AS = 26,
/// <summary>
/// Pokémon Omega Ruby (3DS)
/// </summary>
OR = 27,
#endregion
#region Gen7
/// <summary>
/// Pokémon Sun (3DS)
/// </summary>
SN = 30,
/// <summary>
/// Pokémon Moon (3DS)
/// </summary>
MN = 31,
/// <summary>
/// Pokémon Ultra Sun (3DS)
/// </summary>
US = 32,
/// <summary>
/// Pokémon Ultra Moon (3DS)
/// </summary>
UM = 33,
#endregion
/// <summary>
/// Pokémon GO (GO -> Let's Go/HOME transfers)
/// </summary>
GO = 34,
#region Virtual Console (3DS) Gen1
/// <summary>
/// Pokémon Red (3DS Virtual Console)
/// </summary>
RD = 35,
/// <summary>
/// Pokémon Green[JP]/Blue[INT] (3DS Virtual Console)
/// </summary>
GN = 36,
/// <summary>
/// Pokémon Blue[JP] (3DS Virtual Console)
/// </summary>
BU = 37,
/// <summary>
/// Pokémon Yellow [JP] (3DS Virtual Console)
/// </summary>
YW = 38,
#endregion
#region Virtual Console (3DS) Gen2
/// <summary>
/// Pokémon Gold (3DS Virtual Console)
/// </summary>
GD = 39,
/// <summary>
/// Pokémon Silver (3DS Virtual Console)
/// </summary>
SI = 40,
/// <summary>
/// Pokémon Crystal (3DS Virtual Console)
/// </summary>
C = 41,
#endregion
#region Nintendo Switch
/// <summary>
/// Pokémon: Let's Go, Pikachu! (NX)
/// </summary>
GP = 42,
/// <summary>
/// Pokémon: Let's Go, Eevee! (NX)
/// </summary>
GE = 43,
/// <summary>
/// Pokémon Sword (NX)
/// </summary>
SW = 44,
/// <summary>
/// Pokémon Shield (NX)
/// </summary>
SH = 45,
// HOME = 46,
/// <summary>
/// Pokémon Legends: Arceus (NX)
/// </summary>
PLA = 47,
/// <summary>
/// Pokémon Brilliant Diamond (NX)
/// </summary>
BD = 48,
/// <summary>
/// Pokémon Shining Pearl (NX)
/// </summary>
SP = 49,
#endregion
// The following values are not actually stored values in pkm data,
// These values are assigned within PKHeX as properties for various logic branching.
#region Game Groupings (SaveFile type, roughly)
/// <summary>
/// Pokémon Red &amp; Blue [<see cref="SAV1"/>] identifier.
/// </summary>
/// <seealso cref="RD"/>
/// <seealso cref="GN"/>
/// <seealso cref="BU"/>
RB,
/// <summary>
/// Pokémon Red/Blue/Yellow [<see cref="SAV1"/>] identifier.
/// </summary>
/// <see cref="RD"/>
/// <see cref="GN"/>
/// <see cref="BU"/>
/// <see cref="YW"/>
RBY,
/// <summary>
/// Pokémon Gold &amp; Silver [<see cref="SAV2"/>] identifier.
/// </summary>
/// <see cref="GD"/>
/// <see cref="SI"/>
GS,
/// <summary>
/// Pokémon Gold/Silver/Crystal [<see cref="SAV2"/>] identifier.
/// </summary>
/// <see cref="GD"/>
/// <see cref="SI"/>
/// <see cref="C"/>
GSC,
/// <summary>
/// Pokémon Ruby &amp; Sapphire [<see cref="SAV3"/>] identifier.
/// </summary>
/// <see cref="R"/>
/// <see cref="S"/>
RS,
/// <summary>
/// Pokémon Ruby/Sapphire/Emerald [<see cref="SAV3"/>] identifier.
/// </summary>
/// <see cref="R"/>
/// <see cref="S"/>
/// <see cref="E"/>
RSE,
/// <summary>
/// Pokémon FireRed/LeafGreen [<see cref="SAV3"/>] identifier.
/// </summary>
/// <see cref="FR"/>
/// <see cref="LG"/>
FRLG,
/// <summary>
/// Pokémon Box Ruby &amp; Sapphire [<see cref="SAV3RSBox"/>] identifier.
/// </summary>
RSBOX,
/// <summary>
/// Pokémon Colosseum [<see cref="SAV3Colosseum"/>] identifier.
/// </summary>
/// <see cref="CXD"/>
/// <remarks>Also used to mark Colosseum-only origin data as this game shares a version ID with <see cref="XD"/></remarks>
COLO,
/// <summary>
/// Pokémon XD [<see cref="SAV3XD"/>] identifier.
/// </summary>
/// <see cref="CXD"/>
/// <remarks>Also used to mark XD-only origin data as this game shares a version ID with <see cref="COLO"/></remarks>
XD,
/// <summary>
/// Pokémon Diamond &amp; Pearl [<see cref="SAV4"/>] identifier.
/// </summary>
/// <see cref="D"/>
/// <see cref="P"/>
DP,
/// <summary>
/// Pokémon Diamond/Pearl/Platinum version group.
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="D"/>
/// <see cref="P"/>
/// <see cref="Pt"/>
DPPt,
/// <summary>
/// Pokémon HeartGold &amp; SoulSilver [<see cref="SAV4"/>] identifier.
/// </summary>
/// <see cref="HG"/>
/// <see cref="SS"/>
HGSS,
/// <summary>
/// Pokémon Battle Revolution [<see cref="SAV4BR"/>] identifier.
/// </summary>
BATREV,
/// <summary>
/// Pokémon Black &amp; White version group.
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="B"/>
/// <see cref="W"/>
BW,
/// <summary>
/// Pokémon Black 2 &amp; White 2 version group.
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="B2"/>
/// <see cref="W2"/>
B2W2,
/// <summary>
/// Pokémon X &amp; Y
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="X"/>
/// <see cref="Y"/>
XY,
/// <summary>
/// Pokémon Omega Ruby &amp; Alpha Sapphire Demo [<see cref="SAV6"/>] identifier.
/// </summary>
/// <see cref="ORAS"/>
ORASDEMO,
/// <summary>
/// Pokémon Omega Ruby &amp; Alpha Sapphire version group.
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="OR"/>
/// <see cref="AS"/>
ORAS,
/// <summary>
/// Pokémon Sun &amp; Moon
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="SN"/>
/// <see cref="MN"/>
SM,
/// <summary>
/// Pokémon Ultra Sun &amp; Ultra Moon
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="US"/>
/// <see cref="UM"/>
USUM,
/// <summary>
/// Pokémon Let's Go Pikachu &amp; Eevee
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="GP"/>
/// <see cref="GE"/>
GG,
/// <summary>
/// Pokémon Sword &amp; Shield
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="SW"/>
/// <see cref="SH"/>
SWSH,
/// <summary>
/// Pokémon Brilliant Diamond &amp; Shining Pearl
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="BD"/>
/// <see cref="SP"/>
BDSP,
/// <summary>
/// Generation 1 Games
/// </summary>
/// <see cref="RBY"/>
Gen1,
/// <summary>
/// Generation 2 Games
/// </summary>
/// <see cref="GSC"/>
Gen2,
/// <summary>
/// Generation 3 Games
/// </summary>
/// <see cref="RSE"/>
/// <see cref="FRLG"/>
Gen3,
/// <summary>
/// Generation 4 Games
/// </summary>
/// <see cref="DPPt"/>
/// <see cref="HGSS"/>
Gen4,
/// <summary>
/// Generation 5 Games
/// </summary>
/// <see cref="BW"/>
/// <see cref="B2W2"/>
Gen5,
/// <summary>
/// Generation 6 Games
/// </summary>
/// <see cref="XY"/>
/// <see cref="ORAS"/>
Gen6,
/// <summary>
/// Generation 7 Games on the Nintendo 3DS
/// </summary>
/// <see cref="SM"/>
/// <see cref="USUM"/>
Gen7,
/// <summary>
/// Generation 7 Games on the Nintendo Switch
/// </summary>
/// <see cref="GG"/>
/// <see cref="GO"/>
Gen7b,
/// <summary>
/// Generation 8 Games
/// </summary>
/// <see cref="SWSH"/>
/// <see cref="BDSP"/>
/// <see cref="PLA"/>
Gen8,
/// <summary>
/// Pocket Monsters Stadium data origin identifier
/// </summary>
StadiumJ,
/// <summary>
/// Pokémon Stadium data origin identifier
/// </summary>
Stadium,
/// <summary>
/// Pokémon Stadium 2 data origin identifier
/// </summary>
Stadium2,
#endregion
}
S = 1,
/// <summary>
/// Pokémon Ruby (GBA)
/// </summary>
R = 2,
/// <summary>
/// Pokémon Emerald (GBA)
/// </summary>
E = 3,
/// <summary>
/// Pokémon FireRed (GBA)
/// </summary>
FR = 4,
/// <summary>
/// Pokémon LeafGreen (GBA)
/// </summary>
LG = 5,
/// <summary>
/// Pokémon Colosseum &amp; Pokémon XD (GameCube)
/// </summary>
CXD = 15,
#endregion
#region Gen4
/// <summary>
/// Pokémon Diamond (NDS)
/// </summary>
D = 10,
/// <summary>
/// Pokémon Pearl (NDS)
/// </summary>
P = 11,
/// <summary>
/// Pokémon Platinum (NDS)
/// </summary>
Pt = 12,
/// <summary>
/// Pokémon HeartGold (NDS)
/// </summary>
HG = 7,
/// <summary>
/// Pokémon SoulSilver (NDS)
/// </summary>
SS = 8,
#endregion
#region Gen5
/// <summary>
/// Pokémon White (NDS)
/// </summary>
W = 20,
/// <summary>
/// Pokémon Black (NDS)
/// </summary>
B = 21,
/// <summary>
/// Pokémon White 2 (NDS)
/// </summary>
W2 = 22,
/// <summary>
/// Pokémon Black 2 (NDS)
/// </summary>
B2 = 23,
#endregion
#region Gen6
/// <summary>
/// Pokémon X (3DS)
/// </summary>
X = 24,
/// <summary>
/// Pokémon Y (3DS)
/// </summary>
Y = 25,
/// <summary>
/// Pokémon Alpha Sapphire (3DS)
/// </summary>
AS = 26,
/// <summary>
/// Pokémon Omega Ruby (3DS)
/// </summary>
OR = 27,
#endregion
#region Gen7
/// <summary>
/// Pokémon Sun (3DS)
/// </summary>
SN = 30,
/// <summary>
/// Pokémon Moon (3DS)
/// </summary>
MN = 31,
/// <summary>
/// Pokémon Ultra Sun (3DS)
/// </summary>
US = 32,
/// <summary>
/// Pokémon Ultra Moon (3DS)
/// </summary>
UM = 33,
#endregion
/// <summary>
/// Pokémon GO (GO -> Let's Go/HOME transfers)
/// </summary>
GO = 34,
#region Virtual Console (3DS) Gen1
/// <summary>
/// Pokémon Red (3DS Virtual Console)
/// </summary>
RD = 35,
/// <summary>
/// Pokémon Green[JP]/Blue[INT] (3DS Virtual Console)
/// </summary>
GN = 36,
/// <summary>
/// Pokémon Blue[JP] (3DS Virtual Console)
/// </summary>
BU = 37,
/// <summary>
/// Pokémon Yellow [JP] (3DS Virtual Console)
/// </summary>
YW = 38,
#endregion
#region Virtual Console (3DS) Gen2
/// <summary>
/// Pokémon Gold (3DS Virtual Console)
/// </summary>
GD = 39,
/// <summary>
/// Pokémon Silver (3DS Virtual Console)
/// </summary>
SI = 40,
/// <summary>
/// Pokémon Crystal (3DS Virtual Console)
/// </summary>
C = 41,
#endregion
#region Nintendo Switch
/// <summary>
/// Pokémon: Let's Go, Pikachu! (NX)
/// </summary>
GP = 42,
/// <summary>
/// Pokémon: Let's Go, Eevee! (NX)
/// </summary>
GE = 43,
/// <summary>
/// Pokémon Sword (NX)
/// </summary>
SW = 44,
/// <summary>
/// Pokémon Shield (NX)
/// </summary>
SH = 45,
// HOME = 46,
/// <summary>
/// Pokémon Legends: Arceus (NX)
/// </summary>
PLA = 47,
/// <summary>
/// Pokémon Brilliant Diamond (NX)
/// </summary>
BD = 48,
/// <summary>
/// Pokémon Shining Pearl (NX)
/// </summary>
SP = 49,
#endregion
// The following values are not actually stored values in pk data,
// These values are assigned within PKHeX as properties for various logic branching.
#region Game Groupings (SaveFile type, roughly)
/// <summary>
/// Pokémon Red &amp; Blue [<see cref="SAV1"/>] identifier.
/// </summary>
/// <seealso cref="RD"/>
/// <seealso cref="GN"/>
/// <seealso cref="BU"/>
RB,
/// <summary>
/// Pokémon Red/Blue/Yellow [<see cref="SAV1"/>] identifier.
/// </summary>
/// <see cref="RD"/>
/// <see cref="GN"/>
/// <see cref="BU"/>
/// <see cref="YW"/>
RBY,
/// <summary>
/// Pokémon Gold &amp; Silver [<see cref="SAV2"/>] identifier.
/// </summary>
/// <see cref="GD"/>
/// <see cref="SI"/>
GS,
/// <summary>
/// Pokémon Gold/Silver/Crystal [<see cref="SAV2"/>] identifier.
/// </summary>
/// <see cref="GD"/>
/// <see cref="SI"/>
/// <see cref="C"/>
GSC,
/// <summary>
/// Pokémon Ruby &amp; Sapphire [<see cref="SAV3"/>] identifier.
/// </summary>
/// <see cref="R"/>
/// <see cref="S"/>
RS,
/// <summary>
/// Pokémon Ruby/Sapphire/Emerald [<see cref="SAV3"/>] identifier.
/// </summary>
/// <see cref="R"/>
/// <see cref="S"/>
/// <see cref="E"/>
RSE,
/// <summary>
/// Pokémon FireRed/LeafGreen [<see cref="SAV3"/>] identifier.
/// </summary>
/// <see cref="FR"/>
/// <see cref="LG"/>
FRLG,
/// <summary>
/// Pokémon Box Ruby &amp; Sapphire [<see cref="SAV3RSBox"/>] identifier.
/// </summary>
RSBOX,
/// <summary>
/// Pokémon Colosseum [<see cref="SAV3Colosseum"/>] identifier.
/// </summary>
/// <see cref="CXD"/>
/// <remarks>Also used to mark Colosseum-only origin data as this game shares a version ID with <see cref="XD"/></remarks>
COLO,
/// <summary>
/// Pokémon XD [<see cref="SAV3XD"/>] identifier.
/// </summary>
/// <see cref="CXD"/>
/// <remarks>Also used to mark XD-only origin data as this game shares a version ID with <see cref="COLO"/></remarks>
XD,
/// <summary>
/// Pokémon Diamond &amp; Pearl [<see cref="SAV4"/>] identifier.
/// </summary>
/// <see cref="D"/>
/// <see cref="P"/>
DP,
/// <summary>
/// Pokémon Diamond/Pearl/Platinum version group.
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="D"/>
/// <see cref="P"/>
/// <see cref="Pt"/>
DPPt,
/// <summary>
/// Pokémon HeartGold &amp; SoulSilver [<see cref="SAV4"/>] identifier.
/// </summary>
/// <see cref="HG"/>
/// <see cref="SS"/>
HGSS,
/// <summary>
/// Pokémon Battle Revolution [<see cref="SAV4BR"/>] identifier.
/// </summary>
BATREV,
/// <summary>
/// Pokémon Black &amp; White version group.
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="B"/>
/// <see cref="W"/>
BW,
/// <summary>
/// Pokémon Black 2 &amp; White 2 version group.
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="B2"/>
/// <see cref="W2"/>
B2W2,
/// <summary>
/// Pokémon X &amp; Y
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="X"/>
/// <see cref="Y"/>
XY,
/// <summary>
/// Pokémon Omega Ruby &amp; Alpha Sapphire Demo [<see cref="SAV6"/>] identifier.
/// </summary>
/// <see cref="ORAS"/>
ORASDEMO,
/// <summary>
/// Pokémon Omega Ruby &amp; Alpha Sapphire version group.
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="OR"/>
/// <see cref="AS"/>
ORAS,
/// <summary>
/// Pokémon Sun &amp; Moon
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="SN"/>
/// <see cref="MN"/>
SM,
/// <summary>
/// Pokémon Ultra Sun &amp; Ultra Moon
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="US"/>
/// <see cref="UM"/>
USUM,
/// <summary>
/// Pokémon Let's Go Pikachu &amp; Eevee
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="GP"/>
/// <see cref="GE"/>
GG,
/// <summary>
/// Pokémon Sword &amp; Shield
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="SW"/>
/// <see cref="SH"/>
SWSH,
/// <summary>
/// Pokémon Brilliant Diamond &amp; Shining Pearl
/// </summary>
/// <remarks>Used to lump data from the associated games as data assets are shared.</remarks>
/// <see cref="BD"/>
/// <see cref="SP"/>
BDSP,
/// <summary>
/// Generation 1 Games
/// </summary>
/// <see cref="RBY"/>
Gen1,
/// <summary>
/// Generation 2 Games
/// </summary>
/// <see cref="GSC"/>
Gen2,
/// <summary>
/// Generation 3 Games
/// </summary>
/// <see cref="RSE"/>
/// <see cref="FRLG"/>
Gen3,
/// <summary>
/// Generation 4 Games
/// </summary>
/// <see cref="DPPt"/>
/// <see cref="HGSS"/>
Gen4,
/// <summary>
/// Generation 5 Games
/// </summary>
/// <see cref="BW"/>
/// <see cref="B2W2"/>
Gen5,
/// <summary>
/// Generation 6 Games
/// </summary>
/// <see cref="XY"/>
/// <see cref="ORAS"/>
Gen6,
/// <summary>
/// Generation 7 Games on the Nintendo 3DS
/// </summary>
/// <see cref="SM"/>
/// <see cref="USUM"/>
Gen7,
/// <summary>
/// Generation 7 Games on the Nintendo Switch
/// </summary>
/// <see cref="GG"/>
/// <see cref="GO"/>
Gen7b,
/// <summary>
/// Generation 8 Games
/// </summary>
/// <see cref="SWSH"/>
/// <see cref="BDSP"/>
/// <see cref="PLA"/>
Gen8,
/// <summary>
/// Pocket Monsters Stadium data origin identifier
/// </summary>
StadiumJ,
/// <summary>
/// Pokémon Stadium data origin identifier
/// </summary>
Stadium,
/// <summary>
/// Pokémon Stadium 2 data origin identifier
/// </summary>
Stadium2,
#endregion
}

View file

@ -1,15 +1,14 @@
namespace PKHeX.Core
{
/// <summary>
/// Gender a <see cref="PKM"/> can have
/// </summary>
/// <remarks><see cref="Random"/> provided to function for Encounter template values</remarks>
public enum Gender : byte
{
Male = 0,
Female = 1,
namespace PKHeX.Core;
Genderless = 2,
Random = Genderless,
}
/// <summary>
/// Gender a <see cref="PKM"/> can have
/// </summary>
/// <remarks><see cref="Random"/> provided to function for Encounter template values</remarks>
public enum Gender : byte
{
Male = 0,
Female = 1,
Genderless = 2,
Random = Genderless,
}

View file

@ -1,50 +1,49 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// <see cref="GameVersion.CXD"/> Game Language IDs
/// </summary>
public enum LanguageGC : byte
{
/// <summary>
/// <see cref="GameVersion.CXD"/> Game Language IDs
/// Undefined Language ID, usually indicative of a value not being set.
/// </summary>
public enum LanguageGC : byte
{
/// <summary>
/// Undefined Language ID, usually indicative of a value not being set.
/// </summary>
/// <remarks>Gen5 Japanese In-game Trades happen to not have their Language value set, and express Language=0.</remarks>
Hacked = 0,
/// <remarks>Gen5 Japanese In-game Trades happen to not have their Language value set, and express Language=0.</remarks>
Hacked = 0,
/// <summary>
/// Japanese (日本語)
/// </summary>
Japanese = 1,
/// <summary>
/// Japanese (日本語)
/// </summary>
Japanese = 1,
/// <summary>
/// English (US/UK/AU)
/// </summary>
English = 2,
/// <summary>
/// English (US/UK/AU)
/// </summary>
English = 2,
/// <summary>
/// German (Deutsch)
/// </summary>
German = 3,
/// <summary>
/// German (Deutsch)
/// </summary>
German = 3,
/// <summary>
/// French (Français)
/// </summary>
French = 4,
/// <summary>
/// French (Français)
/// </summary>
French = 4,
/// <summary>
/// Italian (Italiano)
/// </summary>
Italian = 5,
/// <summary>
/// Italian (Italiano)
/// </summary>
Italian = 5,
/// <summary>
/// Spanish (Español)
/// </summary>
Spanish = 6,
/// <summary>
/// Spanish (Español)
/// </summary>
Spanish = 6,
/// <summary>
/// Unused Language ID
/// </summary>
/// <remarks>Was reserved for Korean in Gen3 but never utilized.</remarks>
UNUSED_6 = 7,
}
}
/// <summary>
/// Unused Language ID
/// </summary>
/// <remarks>Was reserved for Korean in Gen3 but never utilized.</remarks>
UNUSED_6 = 7,
}

View file

@ -1,65 +1,64 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Contiguous series Game Language IDs
/// </summary>
public enum LanguageID : byte
{
/// <summary>
/// Contiguous series Game Language IDs
/// Undefined Language ID, usually indicative of a value not being set.
/// </summary>
public enum LanguageID : byte
{
/// <summary>
/// Undefined Language ID, usually indicative of a value not being set.
/// </summary>
/// <remarks>Gen5 Japanese In-game Trades happen to not have their Language value set, and express Language=0.</remarks>
Hacked = 0,
/// <remarks>Gen5 Japanese In-game Trades happen to not have their Language value set, and express Language=0.</remarks>
Hacked = 0,
/// <summary>
/// Japanese (日本語)
/// </summary>
Japanese = 1,
/// <summary>
/// Japanese (日本語)
/// </summary>
Japanese = 1,
/// <summary>
/// English (US/UK/AU)
/// </summary>
English = 2,
/// <summary>
/// English (US/UK/AU)
/// </summary>
English = 2,
/// <summary>
/// French (Français)
/// </summary>
French = 3,
/// <summary>
/// French (Français)
/// </summary>
French = 3,
/// <summary>
/// Italian (Italiano)
/// </summary>
Italian = 4,
/// <summary>
/// Italian (Italiano)
/// </summary>
Italian = 4,
/// <summary>
/// German (Deutsch)
/// </summary>
German = 5,
/// <summary>
/// German (Deutsch)
/// </summary>
German = 5,
/// <summary>
/// Unused Language ID
/// </summary>
/// <remarks>Was reserved for Korean in Gen3 but never utilized.</remarks>
UNUSED_6 = 6,
/// <summary>
/// Unused Language ID
/// </summary>
/// <remarks>Was reserved for Korean in Gen3 but never utilized.</remarks>
UNUSED_6 = 6,
/// <summary>
/// Spanish (Español)
/// </summary>
Spanish = 7,
/// <summary>
/// Spanish (Español)
/// </summary>
Spanish = 7,
/// <summary>
/// Korean (한국어)
/// </summary>
Korean = 8,
/// <summary>
/// Korean (한국어)
/// </summary>
Korean = 8,
/// <summary>
/// Chinese Simplified (简体中文)
/// </summary>
ChineseS = 9,
/// <summary>
/// Chinese Simplified (简体中文)
/// </summary>
ChineseS = 9,
/// <summary>
/// Chinese Traditional (繁體中文)
/// </summary>
ChineseT = 10,
}
/// <summary>
/// Chinese Traditional (繁體中文)
/// </summary>
ChineseT = 10,
}

File diff suppressed because it is too large Load diff

View file

@ -1,49 +1,48 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Elemental type a move has; additionally, types a <see cref="PKM"/> can have.
/// </summary>
public enum MoveType : sbyte
{
/// <summary>
/// Elemental type a move has; additionally, types a <see cref="PKM"/> can have.
/// </summary>
public enum MoveType : sbyte
Any = -1,
Normal,
Fighting,
Flying,
Poison,
Ground,
Rock,
Bug,
Ghost,
Steel,
Fire,
Water,
Grass,
Electric,
Psychic,
Ice,
Dragon,
Dark,
Fairy,
}
public static class MoveTypeExtensions
{
public static MoveType GetMoveTypeGeneration(this MoveType type, int generation)
{
Any = -1,
Normal,
Fighting,
Flying,
Poison,
Ground,
Rock,
Bug,
Ghost,
Steel,
Fire,
Water,
Grass,
Electric,
Psychic,
Ice,
Dragon,
Dark,
Fairy,
if (generation <= 2)
return GetMoveTypeFromG12(type);
return type;
}
public static class MoveTypeExtensions
private static MoveType GetMoveTypeFromG12(this MoveType type)
{
public static MoveType GetMoveTypeGeneration(this MoveType type, int generation)
{
if (generation <= 2)
return GetMoveTypeFromG12(type);
if (type <= MoveType.Rock)
return type;
}
private static MoveType GetMoveTypeFromG12(this MoveType type)
{
if (type <= MoveType.Rock)
return type;
type--; // Skip unused Bird type
if (type <= MoveType.Steel)
return type;
type -= 10; // 10 Normal duplicates
type--; // Skip unused Bird type
if (type <= MoveType.Steel)
return type;
}
type -= 10; // 10 Normal duplicates
return type;
}
}

View file

@ -1,49 +1,48 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Nature ID values for the corresponding English nature name.
/// </summary>
public enum Nature : byte
{
/// <summary>
/// Nature ID values for the corresponding English nature name.
/// </summary>
public enum Nature : byte
{
Hardy = 0,
Lonely = 1,
Brave = 2,
Adamant = 3,
Naughty = 4,
Bold = 5,
Docile = 6,
Relaxed = 7,
Impish = 8,
Lax = 9,
Timid = 10,
Hasty = 11,
Serious = 12,
Jolly = 13,
Naive = 14,
Modest = 15,
Mild = 16,
Quiet = 17,
Bashful = 18,
Rash = 19,
Calm = 20,
Gentle = 21,
Sassy = 22,
Careful = 23,
Quirky = 24,
Hardy = 0,
Lonely = 1,
Brave = 2,
Adamant = 3,
Naughty = 4,
Bold = 5,
Docile = 6,
Relaxed = 7,
Impish = 8,
Lax = 9,
Timid = 10,
Hasty = 11,
Serious = 12,
Jolly = 13,
Naive = 14,
Modest = 15,
Mild = 16,
Quiet = 17,
Bashful = 18,
Rash = 19,
Calm = 20,
Gentle = 21,
Sassy = 22,
Careful = 23,
Quirky = 24,
Random = 25,
}
public static class NatureUtil
{
public static Nature GetNature(int value) => value switch
{
< 0 or >= (int)Nature.Random => Nature.Random,
_ => (Nature)value,
};
public static bool IsFixed(this Nature value) => value is >= 0 and < Nature.Random;
public static bool IsNeutral(this Nature value) => value.IsFixed() && (byte)value % 6 == 0;
}
Random = 25,
}
public static class NatureUtil
{
public static Nature GetNature(int value) => value switch
{
< 0 or >= (int)Nature.Random => Nature.Random,
_ => (Nature)value,
};
public static bool IsFixed(this Nature value) => value is >= 0 and < Nature.Random;
public static bool IsNeutral(this Nature value) => value.IsFixed() && (byte)value % 6 == 0;
}

View file

@ -1,16 +1,15 @@
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// 3DS Console Region Identifiers used for Generation 6 &amp; 7 Mystery Gifts
/// </summary>
public enum Region3DSIndex : byte
{
/// <summary>
/// 3DS Console Region Identifiers used for Generation 6 &amp; 7 Mystery Gifts
/// </summary>
public enum Region3DSIndex : byte
{
None = 0,
Japan = 1,
NorthAmerica = 2,
Europe = 3,
China = 4,
Korea = 5,
Taiwan = 6,
}
None = 0,
Japan = 1,
NorthAmerica = 2,
Europe = 3,
China = 4,
Korea = 5,
Taiwan = 6,
}

File diff suppressed because it is too large Load diff

View file

@ -2,105 +2,104 @@
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// <see cref="SaveFile"/> sensitive provider for <see cref="ComboItem"/> data sources.
/// </summary>
public sealed class FilteredGameDataSource
{
/// <summary>
/// <see cref="SaveFile"/> sensitive provider for <see cref="ComboItem"/> data sources.
/// </summary>
public sealed class FilteredGameDataSource
public FilteredGameDataSource(SaveFile sav, GameDataSource source, bool HaX = false)
{
public FilteredGameDataSource(SaveFile sav, GameDataSource source, bool HaX = false)
Source = source;
Species = GetFilteredSpecies(sav, source, HaX).ToList();
Moves = GetFilteredMoves(sav, source, HaX).ToList();
if (sav.Generation > 1)
{
Source = source;
Species = GetFilteredSpecies(sav, source, HaX).ToList();
Moves = GetFilteredMoves(sav, source, HaX).ToList();
if (sav.Generation > 1)
{
var items = Source.GetItemDataSource(sav.Version, sav.Generation, sav.HeldItems, HaX);
items.RemoveAll(i => i.Value > sav.MaxItemID);
Items = items;
}
else
{
Items = Array.Empty<ComboItem>();
}
var gamelist = GameUtil.GetVersionsWithinRange(sav, sav.Generation).ToList();
Games = Source.VersionDataSource.Where(g => gamelist.Contains((GameVersion)g.Value)).ToList();
Languages = GameDataSource.LanguageDataSource(sav.Generation);
Balls = Source.BallDataSource.Where(b => b.Value <= sav.MaxBallID).ToList();
Abilities = Source.AbilityDataSource.Where(a => a.Value <= sav.MaxAbilityID).ToList();
G4GroundTiles = Source.GroundTileDataSource;
Natures = Source.NatureDataSource;
var items = Source.GetItemDataSource(sav.Version, sav.Generation, sav.HeldItems, HaX);
items.RemoveAll(i => i.Value > sav.MaxItemID);
Items = items;
}
else
{
Items = Array.Empty<ComboItem>();
}
private static IEnumerable<ComboItem> GetFilteredSpecies(IGameValueLimit sav, GameDataSource source, bool HaX = false)
{
if (HaX)
return source.SpeciesDataSource.Where(s => s.Value <= sav.MaxSpeciesID);
var gamelist = GameUtil.GetVersionsWithinRange(sav, sav.Generation).ToList();
Games = Source.VersionDataSource.Where(g => gamelist.Contains((GameVersion)g.Value)).ToList();
// Some games cannot acquire every Species that exists. Some can only acquire a subset.
return sav switch
{
SAV7b => source.SpeciesDataSource // LGPE: Kanto 151, Meltan/Melmetal
.Where(s => s.Value is <= (int)Core.Species.Mew or (int)Core.Species.Meltan or (int)Core.Species.Melmetal),
SAV8LA => source.SpeciesDataSource
.Where(s => PersonalTable.LA.IsSpeciesInGame(s.Value)),
_ => source.SpeciesDataSource.Where(s => s.Value <= sav.MaxSpeciesID),
};
}
Languages = GameDataSource.LanguageDataSource(sav.Generation);
Balls = Source.BallDataSource.Where(b => b.Value <= sav.MaxBallID).ToList();
Abilities = Source.AbilityDataSource.Where(a => a.Value <= sav.MaxAbilityID).ToList();
private static IEnumerable<ComboItem> GetFilteredMoves(IGameValueLimit sav, GameDataSource source, bool HaX = false)
{
if (HaX)
return source.HaXMoveDataSource.Where(m => m.Value <= sav.MaxMoveID);
var legal = source.LegalMoveDataSource;
return sav switch
{
SAV7b => legal.Where(s => Legal.AllowedMovesGG.Contains((short) s.Value)), // LGPE: Not all moves are available
_ => legal.Where(m => m.Value <= sav.MaxMoveID),
};
}
public readonly GameDataSource Source;
public readonly IReadOnlyList<ComboItem> Moves;
public readonly IReadOnlyList<ComboItem> Balls;
public readonly IReadOnlyList<ComboItem> Games;
public readonly IReadOnlyList<ComboItem> Items;
public readonly IReadOnlyList<ComboItem> Species;
public readonly IReadOnlyList<ComboItem> Languages;
public readonly IReadOnlyList<ComboItem> Abilities;
public readonly IReadOnlyList<ComboItem> Natures;
public readonly IReadOnlyList<ComboItem> G4GroundTiles;
public readonly IReadOnlyList<ComboItem> ConsoleRegions = GameDataSource.Regions;
public IReadOnlyList<ComboItem> GetAbilityList(PKM pkm)
{
var abilities = pkm.PersonalInfo.Abilities;
int format = pkm.Format;
return GetAbilityList(abilities, format);
}
public IReadOnlyList<ComboItem> GetAbilityList(IReadOnlyList<int> abilities, int format)
{
var count = format == 3 && (abilities[1] == 0 || abilities[1] == abilities[0]) ? 1 : abilities.Count;
var list = new ComboItem[count];
var alist = Source.Strings.Ability;
var suffix = AbilityIndexSuffixes;
for (int i = 0; i < list.Length; i++)
{
var ability = abilities[i];
list[i] = new ComboItem(alist[ability] + suffix[i], ability);
}
return list;
}
private static readonly string[] AbilityIndexSuffixes = { " (1)", " (2)", " (H)" };
G4GroundTiles = Source.GroundTileDataSource;
Natures = Source.NatureDataSource;
}
private static IEnumerable<ComboItem> GetFilteredSpecies(IGameValueLimit sav, GameDataSource source, bool HaX = false)
{
if (HaX)
return source.SpeciesDataSource.Where(s => s.Value <= sav.MaxSpeciesID);
// Some games cannot acquire every Species that exists. Some can only acquire a subset.
return sav switch
{
SAV7b => source.SpeciesDataSource // LGPE: Kanto 151, Meltan/Melmetal
.Where(s => s.Value is <= (int)Core.Species.Mew or (int)Core.Species.Meltan or (int)Core.Species.Melmetal),
SAV8LA => source.SpeciesDataSource
.Where(s => PersonalTable.LA.IsSpeciesInGame(s.Value)),
_ => source.SpeciesDataSource.Where(s => s.Value <= sav.MaxSpeciesID),
};
}
private static IEnumerable<ComboItem> GetFilteredMoves(IGameValueLimit sav, GameDataSource source, bool HaX = false)
{
if (HaX)
return source.HaXMoveDataSource.Where(m => m.Value <= sav.MaxMoveID);
var legal = source.LegalMoveDataSource;
return sav switch
{
SAV7b => legal.Where(s => Legal.AllowedMovesGG.Contains((short) s.Value)), // LGPE: Not all moves are available
_ => legal.Where(m => m.Value <= sav.MaxMoveID),
};
}
public readonly GameDataSource Source;
public readonly IReadOnlyList<ComboItem> Moves;
public readonly IReadOnlyList<ComboItem> Balls;
public readonly IReadOnlyList<ComboItem> Games;
public readonly IReadOnlyList<ComboItem> Items;
public readonly IReadOnlyList<ComboItem> Species;
public readonly IReadOnlyList<ComboItem> Languages;
public readonly IReadOnlyList<ComboItem> Abilities;
public readonly IReadOnlyList<ComboItem> Natures;
public readonly IReadOnlyList<ComboItem> G4GroundTiles;
public readonly IReadOnlyList<ComboItem> ConsoleRegions = GameDataSource.Regions;
public IReadOnlyList<ComboItem> GetAbilityList(PKM pk)
{
var abilities = pk.PersonalInfo.Abilities;
int format = pk.Format;
return GetAbilityList(abilities, format);
}
public IReadOnlyList<ComboItem> GetAbilityList(IReadOnlyList<int> abilities, int format)
{
var count = format == 3 && (abilities[1] == 0 || abilities[1] == abilities[0]) ? 1 : abilities.Count;
var list = new ComboItem[count];
var alist = Source.Strings.Ability;
var suffix = AbilityIndexSuffixes;
for (int i = 0; i < list.Length; i++)
{
var ability = abilities[i];
list[i] = new ComboItem(alist[ability] + suffix[i], ability);
}
return list;
}
private static readonly string[] AbilityIndexSuffixes = { " (1)", " (2)", " (H)" };
}

View file

@ -1,127 +1,126 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Bundles raw string inputs into lists that can be used in data binding.
/// </summary>
public sealed class GameDataSource
{
/// <summary>
/// Bundles raw string inputs into lists that can be used in data binding.
/// </summary>
public sealed class GameDataSource
public static readonly IReadOnlyList<ComboItem> Regions = new List<ComboItem>
{
public static readonly IReadOnlyList<ComboItem> Regions = new List<ComboItem>
new ("Japan (日本)", 0),
new ("Americas (NA/SA)", 1),
new ("Europe (EU/AU)", 2),
new ("China (中国大陆)", 4),
new ("Korea (한국)", 5),
new ("Taiwan (香港/台灣)", 6),
};
private static readonly List<ComboItem> LanguageList = new()
{
new ComboItem("JPN (日本語)", (int)LanguageID.Japanese),
new ComboItem("ENG (English)", (int)LanguageID.English),
new ComboItem("FRE (Français)", (int)LanguageID.French),
new ComboItem("ITA (Italiano)", (int)LanguageID.Italian),
new ComboItem("GER (Deutsch)", (int)LanguageID.German),
new ComboItem("ESP (Español)", (int)LanguageID.Spanish),
new ComboItem("KOR (한국어)", (int)LanguageID.Korean),
new ComboItem("CHS (简体中文)", (int)LanguageID.ChineseS),
new ComboItem("CHT (繁體中文)", (int)LanguageID.ChineseT),
};
public GameDataSource(GameStrings s)
{
Strings = s;
BallDataSource = GetBalls(s.itemlist);
SpeciesDataSource = Util.GetCBList(s.specieslist);
NatureDataSource = Util.GetCBList(s.natures);
AbilityDataSource = Util.GetCBList(s.abilitylist);
GroundTileDataSource = Util.GetUnsortedCBList(s.groundtiletypes, GroundTileTypeExtensions.ValidTileTypes);
var moves = Util.GetCBList(s.movelist);
HaXMoveDataSource = moves;
var legal = new List<ComboItem>(moves);
legal.RemoveAll(m => MoveInfo.Z_Moves.Contains(m.Value));
LegalMoveDataSource = legal;
VersionDataSource = GetVersionList(s);
Met = new MetDataSource(s);
Empty = new ComboItem(s.Species[0], 0);
}
/// <summary> Strings that this object's lists were generated with. </summary>
public readonly GameStrings Strings;
/// <summary> Contains Met Data lists to source lists from. </summary>
public readonly MetDataSource Met;
/// <summary> Represents "(None)", localized to this object's language strings. </summary>
public readonly ComboItem Empty;
public readonly IReadOnlyList<ComboItem> SpeciesDataSource;
public readonly IReadOnlyList<ComboItem> BallDataSource;
public readonly IReadOnlyList<ComboItem> NatureDataSource;
public readonly IReadOnlyList<ComboItem> AbilityDataSource;
public readonly IReadOnlyList<ComboItem> VersionDataSource;
public readonly IReadOnlyList<ComboItem> LegalMoveDataSource;
public readonly IReadOnlyList<ComboItem> HaXMoveDataSource;
public readonly IReadOnlyList<ComboItem> GroundTileDataSource;
private static IReadOnlyList<ComboItem> GetBalls(string[] itemList)
{
// ignores Poke/Great/Ultra
ReadOnlySpan<ushort> ball_nums = stackalloc ushort[] { 007, 576, 013, 492, 497, 014, 495, 493, 496, 494, 011, 498, 008, 006, 012, 015, 009, 005, 499, 010, 001, 016, 851, 1785, 1710, 1711, 1712, 1713, 1746, 1747, 1748, 1749, 1750, 1771 };
ReadOnlySpan<byte> ball_vals = stackalloc byte[] { 007, 025, 013, 017, 022, 014, 020, 018, 021, 019, 011, 023, 008, 006, 012, 015, 009, 005, 024, 010, 001, 016, 026, 0027, 0028, 0029, 0030, 0031, 0032, 0033, 0034, 0035, 0036, 0037 };
return Util.GetVariedCBListBall(itemList, ball_nums, ball_vals);
}
private static IReadOnlyList<ComboItem> GetVersionList(GameStrings s)
{
var list = s.gamelist;
ReadOnlySpan<byte> games = stackalloc byte[]
{
new ("Japan (日本)", 0),
new ("Americas (NA/SA)", 1),
new ("Europe (EU/AU)", 2),
new ("China (中国大陆)", 4),
new ("Korea (한국)", 5),
new ("Taiwan (香港/台灣)", 6),
47, // 8 legends arceus
48, 49, // 8 bdsp
44, 45, // 8 swsh
42, 43, // 7 gg
30, 31, // 7 sm
32, 33, // 7 usum
24, 25, // 6 xy
27, 26, // 6 oras
21, 20, // 5 bw
23, 22, // 5 b2w2
10, 11, 12, // 4 dppt
07, 08, // 4 hgss
02, 01, 03, // 3 rse
04, 05, // 3 frlg
15, // 3 cxd
39, 40, 41, // 7vc2
35, 36, 37, 38, // 7vc1
34, // 7go
};
private static readonly List<ComboItem> LanguageList = new()
{
new ComboItem("JPN (日本語)", (int)LanguageID.Japanese),
new ComboItem("ENG (English)", (int)LanguageID.English),
new ComboItem("FRE (Français)", (int)LanguageID.French),
new ComboItem("ITA (Italiano)", (int)LanguageID.Italian),
new ComboItem("GER (Deutsch)", (int)LanguageID.German),
new ComboItem("ESP (Español)", (int)LanguageID.Spanish),
new ComboItem("KOR (한국어)", (int)LanguageID.Korean),
new ComboItem("CHS (简体中文)", (int)LanguageID.ChineseS),
new ComboItem("CHT (繁體中文)", (int)LanguageID.ChineseT),
};
return Util.GetUnsortedCBList(list, games);
}
public GameDataSource(GameStrings s)
{
Strings = s;
BallDataSource = GetBalls(s.itemlist);
SpeciesDataSource = Util.GetCBList(s.specieslist);
NatureDataSource = Util.GetCBList(s.natures);
AbilityDataSource = Util.GetCBList(s.abilitylist);
GroundTileDataSource = Util.GetUnsortedCBList(s.groundtiletypes, GroundTileTypeExtensions.ValidTileTypes);
public List<ComboItem> GetItemDataSource(GameVersion game, int generation, IReadOnlyList<ushort> allowed, bool HaX = false)
{
var items = Strings.GetItemStrings(generation, game);
return HaX ? Util.GetCBList(items) : Util.GetCBList(items, allowed);
}
var moves = Util.GetCBList(s.movelist);
HaXMoveDataSource = moves;
var legal = new List<ComboItem>(moves);
legal.RemoveAll(m => MoveInfo.Z_Moves.Contains(m.Value));
LegalMoveDataSource = legal;
VersionDataSource = GetVersionList(s);
Met = new MetDataSource(s);
Empty = new ComboItem(s.Species[0], 0);
}
/// <summary> Strings that this object's lists were generated with. </summary>
public readonly GameStrings Strings;
/// <summary> Contains Met Data lists to source lists from. </summary>
public readonly MetDataSource Met;
/// <summary> Represents "(None)", localized to this object's language strings. </summary>
public readonly ComboItem Empty;
public readonly IReadOnlyList<ComboItem> SpeciesDataSource;
public readonly IReadOnlyList<ComboItem> BallDataSource;
public readonly IReadOnlyList<ComboItem> NatureDataSource;
public readonly IReadOnlyList<ComboItem> AbilityDataSource;
public readonly IReadOnlyList<ComboItem> VersionDataSource;
public readonly IReadOnlyList<ComboItem> LegalMoveDataSource;
public readonly IReadOnlyList<ComboItem> HaXMoveDataSource;
public readonly IReadOnlyList<ComboItem> GroundTileDataSource;
private static IReadOnlyList<ComboItem> GetBalls(string[] itemList)
{
// ignores Poke/Great/Ultra
ReadOnlySpan<ushort> ball_nums = stackalloc ushort[] { 007, 576, 013, 492, 497, 014, 495, 493, 496, 494, 011, 498, 008, 006, 012, 015, 009, 005, 499, 010, 001, 016, 851, 1785, 1710, 1711, 1712, 1713, 1746, 1747, 1748, 1749, 1750, 1771 };
ReadOnlySpan<byte> ball_vals = stackalloc byte[] { 007, 025, 013, 017, 022, 014, 020, 018, 021, 019, 011, 023, 008, 006, 012, 015, 009, 005, 024, 010, 001, 016, 026, 0027, 0028, 0029, 0030, 0031, 0032, 0033, 0034, 0035, 0036, 0037 };
return Util.GetVariedCBListBall(itemList, ball_nums, ball_vals);
}
private static IReadOnlyList<ComboItem> GetVersionList(GameStrings s)
{
var list = s.gamelist;
ReadOnlySpan<byte> games = stackalloc byte[]
{
47, // 8 legends arceus
48, 49, // 8 bdsp
44, 45, // 8 swsh
42, 43, // 7 gg
30, 31, // 7 sm
32, 33, // 7 usum
24, 25, // 6 xy
27, 26, // 6 oras
21, 20, // 5 bw
23, 22, // 5 b2w2
10, 11, 12, // 4 dppt
07, 08, // 4 hgss
02, 01, 03, // 3 rse
04, 05, // 3 frlg
15, // 3 cxd
39, 40, 41, // 7vc2
35, 36, 37, 38, // 7vc1
34, // 7go
};
return Util.GetUnsortedCBList(list, games);
}
public List<ComboItem> GetItemDataSource(GameVersion game, int generation, IReadOnlyList<ushort> allowed, bool HaX = false)
{
var items = Strings.GetItemStrings(generation, game);
return HaX ? Util.GetCBList(items) : Util.GetCBList(items, allowed);
}
public static IReadOnlyList<ComboItem> LanguageDataSource(int gen)
{
var languages = new List<ComboItem>(LanguageList);
if (gen == 3)
languages.RemoveAll(l => l.Value >= (int)LanguageID.Korean);
else if (gen < 7)
languages.RemoveAll(l => l.Value > (int)LanguageID.Korean);
return languages;
}
public static IReadOnlyList<ComboItem> LanguageDataSource(int gen)
{
var languages = new List<ComboItem>(LanguageList);
if (gen == 3)
languages.RemoveAll(l => l.Value >= (int)LanguageID.Korean);
else if (gen < 7)
languages.RemoveAll(l => l.Value > (int)LanguageID.Korean);
return languages;
}
}

Some files were not shown because too many files have changed in this diff Show more